跳到主要内容

API 示例

使用此页面为每个扩展流程复制正确的请求形状。

  • Iframe 桥接示例使用 postMessage 操作,例如 session.getstate.getfiles.get
  • 特权示例使用您自己的扩展后端,并带有 Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEYx-chastify-main-token
  • 切勿将开发者 API 密钥发送到 iframe 或浏览器代码中。
信息

iframe桥接器用于UI引导和低风险的会话操作。它可用于读取上下文、存储扩展程序拥有的状态以及解析文件URL。

警告

浏览器 iframe 代码由用户控制。请勿使用 iframe 桥接请求来应用/移除时间、完成任务、清除解锁障碍、上传/删除运行时文件、发送通知、写入日志或控制设备。

请求和响应格式

安全的 iframe 桥接操作会在此桥接请求信封内发送:

{
"type": "chastify:ext:req", // required
"v": 1, // protocol version
"id": "req-123", // your unique request id
"nonce": "from-iframe-hash",
"action": "session.get",
"payload": {}
}

您将收到:

{
"type": "chastify:ext:resp",
"v": 1,
"id": "req-123", // same id you sent
"ok": true,
"data": {}
}

如果 okfalse,则检查 error.codeerror.message

仅后端示例使用来自服务器的普通 HTTPS 请求,不会通过 iframe 桥接器发送。

会话和上下文

session.get

几乎在所有扩展流程中都要首先使用此功能。

它的功能:

  • 验证您的桥接连接是否正常工作。
  • 返回会话上下文(锁定状态、角色、扩展配置、功能)。
  • 返回您的 UI 可以在请求后端调用 device.command 之前使用的设备功能信息。

它的用途:

  • 引导你的用户界面。
  • 根据角色和权限启用/禁用功能。
  • 在渲染设备控件之前,检查支持的设备命令。

示例操作有效载荷:

{
"action": "session.get",
"payload": {}
}

示例 session.get 响应摘录(运行时锁定数据):

{
"ok": true,
"data": {
"lockData": {
"frozen": false,
"unlockable": false,
"trusted": true,
"taskAssigned": true,
"timeLockedSeconds": 1420,
"timeRemainingSeconds": 27800,
"maxTimeRemainingSeconds": 86400,
"taskPoints": 12,
"taskPointsRequired": 20,
"lockTitle": "Weekend Challenge",
"wearerUsername": "alice",
"keyholderUsername": "kh_bob",
"wearerLastSeenTimestamp": 1739640505123,
"keyholderLastSeenTimestamp": null
}
}
}

隐私声明:

  • 只有当该用户拥有 showOnlineStatus !== false 时,才会返回 wearerLastSeenTimestamp / keyholderLastSeenTimestamp
  • 如果在线状态可见性被禁用,这些字段为 null

后端扩展流程

以下示例展示了安全生产模式:

  1. iframe 从哈希有效载荷中读取 mainTokensessionId
  2. iframe 会将它们发送到您的后端。
  3. 您的后端会验证您自己的游戏/任务/业务状态。
  4. 您的后端使用应用程序范围的开发者 API 密钥加上 x-chastify-main-token 调用 Chastify。

请勿单独使用 sessionId 作为身份验证。请将 mainToken 视为浏览器可见的启动上下文,并将您的开发者 API 密钥视为仅供后端使用的密钥。

注意

您的后端必须先验证自身的游戏/任务状态,然后才能调用 Chastify。从 iframe 传递 mainTokensessionId 只能识别已打开的扩展会话,并不能证明用户已完成挑战。

安全地获取会话上下文

您的 iframe 可以使用安全桥接器 session.get 进行 UI 启动。如果您的后端在执行特权操作之前需要相同的上下文,请使用两种凭据从后端获取该上下文。

内嵌框架:

const hash = JSON.parse(decodeURIComponent(window.location.hash.slice(1)));

await fetch("/api/my-extension/session-context", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
mainToken: hash.mainToken,
sessionId: hash.sessionId
})
});

您的后端:

app.post("/api/my-extension/session-context", async (req, res) => {
const { mainToken, sessionId } = req.body;

const response = await fetch(
`https://chastify.net/api/extensions/sessions/${encodeURIComponent(sessionId)}`,
{
headers: {
Authorization: `Bearer ${process.env.CHASTIFY_APP_DEVELOPER_KEY}`,
"x-chastify-main-token": mainToken
}
}
);

if (!response.ok) {
return res.status(response.status).json(await response.json());
}

const context = await response.json();

res.json({
role: context.role,
config: context.extensionConfig,
lockData: context.lockData
});
});

从扩展后端应用时间

浏览器可能会请求后端服务器执行奖励或惩罚操作,但浏览器本身不应判断该操作是否有效。请先在服务器端验证该操作。

app.post("/api/my-extension/apply-reward", async (req, res) => {
const { mainToken, sessionId, runId } = req.body;

const run = await db.gameRuns.findUnique({ where: { id: runId } });
if (!run || run.sessionId !== sessionId || !run.serverVerifiedWin) {
return res.status(403).json({ error: "game_not_verified" });
}

const response = await fetch(
`https://chastify.net/api/extensions/sessions/${encodeURIComponent(sessionId)}/action`,
{
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.CHASTIFY_APP_DEVELOPER_KEY}`,
"x-chastify-main-token": mainToken
},
body: JSON.stringify({
name: "remove_time",
params: 300
})
}
);

if (!response.ok) {
return res.status(response.status).json(await response.json());
}

res.json(await response.json());
});

使用 add_time 表示惩罚,使用 remove_time 表示奖励。

服务器验证的游戏完成情况

对于像“西蒙说”这样的游戏,不要相信客户端报告的获胜结果。在服务器端创建运行,存储预期序列或其哈希值,并在调用 Chastify 之前验证提交的输入。

浏览器可见的记忆游戏无法证明玩家是真人玩家,因为浏览器必须接收到序列才能渲染游戏。服务器验证仍然可以防止伪造 Chastify 序列,并允许后端在应用奖励或惩罚之前强制执行运行 ID、过期时间、难度、节奏、计分和重玩保护等功能。

app.post("/api/simon/runs", async (req, res) => {
const { mainToken, sessionId } = req.body;
await verifySessionLaunch({ mainToken, sessionId });

const sequence = createSimonSequence();

const run = await db.simonRuns.create({
data: {
sessionId,
sequenceHash: hashSequence(sequence),
expiresAt: new Date(Date.now() + 5 * 60_000)
}
});

res.json({
runId: run.id,
sequence
});
});

app.post("/api/simon/runs/:runId/complete", async (req, res) => {
const { mainToken, sessionId, input } = req.body;
await verifySessionLaunch({ mainToken, sessionId });

const run = await db.simonRuns.findUnique({ where: { id: req.params.runId } });

if (!run || run.sessionId !== sessionId || run.expiresAt < new Date()) {
return res.status(403).json({ error: "run_invalid" });
}

const won = hashSequence(input) === run.sequenceHash;
await db.simonRuns.update({
where: { id: run.id },
data: { completedAt: new Date(), serverVerifiedWin: won }
});

if (won) {
await fetch(`https://chastify.net/api/extensions/sessions/${encodeURIComponent(sessionId)}/requirements/progress`, {
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.CHASTIFY_APP_DEVELOPER_KEY}`,
"x-chastify-main-token": mainToken
},
body: JSON.stringify({
key: "simon_says_wins",
amount: 1
})
});
}

res.json({ won });
});

具体的运行存储方案由您决定。在信任 sessionId 之前,verifySessionLaunch 应该使用您的应用级开发者 API 密钥和 x-chastify-main-token 调用 Chastify。重要的规则是,只有在您的后端验证了启动和结果之后,Chastify 的变更才会发生。

计划要求

后端负责进度安排、节奏检查和验证。Chastify 仅用于记录可靠的进度,或在后端确认需求已满足后更新解锁阻塞项。

async function recordDailyRequirementProgress({ sessionId, mainToken, userId }) {
const completed = await db.dailyCheckins.exists({
where: {
userId,
day: new Date().toISOString().slice(0, 10),
verified: true
}
});

if (!completed) return;

await fetch(`https://chastify.net/api/extensions/sessions/${encodeURIComponent(sessionId)}/requirements/progress`, {
method: "POST",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${process.env.CHASTIFY_APP_DEVELOPER_KEY}`,
"x-chastify-main-token": mainToken
},
body: JSON.stringify({
key: "daily_checkin",
amount: 1
})
});
}

当前已安装扩展程序的会话 API 需要 x-chastify-main-token 格式的有效 iframe 启动令牌;启动令牌目前会在 10 小时后过期。对于在令牌过期后运行的计划任务,请自行存储待处理的证明,并在下次扩展程序有效启动时提交进度,或者使用专为无人值守后台工作而设计的第一方/内置服务器流程。

警告

不要因为计划任务运行在您的服务器上就将其视为可信任务。在记录需求进度之前,该任务仍然需要服务器端验证、节奏检查、重放保护以及有效的 Chastify 授权路径。

通知

notifications.custom

在您的扩展程序后端使用此功能,向佩戴者、钥匙持有者或两者都发送自定义扩展程序通知。

终点:

POST /api/extensions/sessions/:sessionId/notifications/custom
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH

示例正文:

{
"title": "Extension Reminder",
"message": "Your next challenge is ready.",
"showPageOverlay": false,
"target": "both"
}

笔记:

  • showPageOverlay 默认为 false
  • target 默认为 wearer
  • API 创建通知类型 extension_app_message

扩展州

扩展状态是扩展程序拥有的当前锁定会话的 JSON 数据。 状态写入仅限后端操作,需要应用范围的开发者 API 密钥以及会话 ID mainToken。iframe 可以使用 state.get 读取状态,但不能直接写入状态。

state.put

它的功能:

  • 将整个状态对象替换为新的 data 对象。
  • 需要后端凭据。

何时使用:

  • 初始保存。
  • 当您已经拥有完整的新状态时,进行完全覆盖。

例子:

curl -X PUT "https://chastify.net/api/extensions/sessions/$SESSION_ID/state" \
-H "Authorization: Bearer $DEVELOPER_KEY" \
-H "x-chastify-main-token: $MAIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"counter": 1,
"notes": "first test"
}
}'

实地考察笔记:

  • payload.data:您的扩展程序所需的任何有效的 JSON 值/对象。
  • Avoid Mongo-unsafe key names ($ prefix or keys containing .).
  • 状态是会话范围的,大小受扩展应用程序的 stateMaxBytes 限制,默认为 64 KiB。
  • 将文件 ID 存储在状态中,而不是二进制文件或签名 URL 中。使用 files.get 在渲染媒体之前刷新签名 URL。

state.patch

它的功能:

  • 将 JSON 合并补丁应用到现有状态。
  • 只需发送已更改的密钥。
  • 需要后端凭据。

何时使用:

  • 基于用户交互的增量更新。
  • 仅更新一个字段,无需重新发送所有内容。

例子:

curl -X PATCH "https://chastify.net/api/extensions/sessions/$SESSION_ID/state" \
-H "Authorization: Bearer $DEVELOPER_KEY" \
-H "x-chastify-main-token: $MAIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"counter": 2
}
}'

state.get

它的功能:

  • 读取当前扩展状态。
  • 可通过 iframe 桥接实现。

何时使用:

  • iframe 加载时。
  • 写入完成后,如果想要重新同步本地 UI。

例子:

{
"action": "state.get",
"payload": {}
}

文件存储

对于拼图图像、生成的预览或挑战照片等二进制媒体,请使用 files.*。将返回的 file.id 存储在后端写入的状态或您自己的数据库中。使用 file.signedUrl 进行渲染,并在 iframe 稍后加载时使用 files.get 刷新该签名 URL。

在锁定/会话建立之前运行的设置屏幕使用暂存上传,而不是 files.upload。暂存文件是临时的,直到使用 provider: "chastify_storage"fileId 引用保存扩展配置为止。Chastify 会在锁定/模板保存时自动声明这些文件;无需单独的浏览器端声明调用。

后端状态/文件拆分示例:

curl -X PATCH "https://chastify.net/api/extensions/sessions/$SESSION_ID/state" \
-H "Authorization: Bearer $DEVELOPER_KEY" \
-H "x-chastify-main-token: $MAIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": {
"puzzleImageFileId": "file_record_id"
}
}'

稍后,在显示之前解析图像:

const state = await bridge.request("state.get", {});
const file = await bridge.request("files.get", {
fileId: state.data.puzzleImageFileId
});
image.src = file.file.signedUrl;

files.capabilities

在显示上传控件之前,请检查此项。

{
"action": "files.capabilities",
"payload": {}
}

运行时 files.upload

运行时文件上传会修改扩展程序会话数据,因此它不是 iframe 桥接命令。请使用应用范围的开发者 API 密钥和 x-chastify-main-token 从后端上传运行时文件。

files.get

从稳定的文件 ID 刷新一个已签名的 R2 URL。

{
"action": "files.get",
"payload": {
"fileId": "file_record_id"
}
}

files.list

{
"action": "files.list",
"payload": {}
}

运行时 files.delete 也仅限后端使用,原因与上传相同。

锁定操作

增加时间

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 根据 deltaSeconds 的值,增加或减少锁定倒计时的时间。

何时使用:

  • 奖励/惩罚按钮。
  • 游戏结果(赢增加时间,输减少时间)。

例子:

{
"name": "add_time",
"params": 300 // +300 sec = +5 minutes
}

实地考察笔记:

  • 正值会增加时间。
  • 负值会扣除时间(如果服务器规则允许)。

冻结

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 冻结并锁定游戏进度一段时间。

何时使用:

  • 冷却机制。
  • 奖励检查点。

例子:

{
"name": "freeze",
"params": { "durationSeconds": 120 }
}

您也可以不输入 durationSeconds 来调用它:

{
"name": "freeze",
"params": {}
}

实地考察笔记:

  • durationSeconds 为可选。
  • 如果省略,则当前默认值为 3600 秒(1 小时)。
  • 可接受的范围为 6086400 秒。

解冻

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 结束活动冻结并恢复正常计时器行为。

何时使用:

  • 扩展工作流程中的手动覆盖。
  • “取消冻结”控件。

例子:

{
"name": "unfreeze",
"params": {}
}

耻辱柱

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 开始对活跃的锁定阶段进行示众。

何时使用:

  • 任务/挑战失败后的惩罚机制。
  • 暂时限制锁定交互的升级流程。

例子:

{
"name": "pillory",
"params": {
"durationSeconds": 600,
"reason": "Missed scheduled check-in"
}
}

实地考察笔记:

  • name 必须为 pillory
  • 必须输入 params.durationSeconds
  • params.reason 为可选。
  • 需要在会话配置中启用示众栏。

末示众台

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 立即结束当前正在进行的示众仪式。

例子:

{
"name": "pillory.end",
"params": {}
}

实地考察笔记:

  • name 必须为 pillory.end
  • 如果锁当前不在枷锁中,则失败并返回 pillory_not_active

分配任务

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 根据扩展逻辑为佩戴者创建一个活动任务运行。
  • 可以覆盖已打开的任务运行。

例子:

{
"name": "task.assign",
"params": {
"taskText": "Clean your room",
"points": 10,
"verificationRequired": true,
"durationSeconds": 1800
}
}

实地考察笔记:

  • 必须输入 taskText
  • points 是可选的,并在服务器端进行限制。
  • verificationRequired 默认为 false
  • durationSeconds 是可选的(0 表示不需要定时器)。
  • 需要为锁启用“任务”模块。

启动任务计时器

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 启动或重新启动当前活动定时任务的倒计时窗口。

例子:

{
"name": "task.start_timer",
"params": {}
}

实地考察笔记:

  • 需要正在运行的任务。
  • 如果当前任务未配置持续时间,则失败。

完成任务

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 将当前运行的任务标记为已完成或失败。

示例(成功):

{
"name": "task.complete",
"params": {
"successful": true
}
}

示例(失败):

{
"name": "task.complete",
"params": {
"successful": false,
"reason": "Did not finish in time"
}
}

触发临时开启

端点:POST /api/extensions/sessions/:sessionId/action

它的功能:

  • 从扩展逻辑启动一个临时卫生开放窗口。

例子:

{
"name": "hygienic_unlock.start",
"params": {
"durationSeconds": 900
}
}

实地考察笔记:

  • 需要启用锁具的卫生开启功能。
  • 如果卫生清理工作已经开始,则此操作失败。
  • durationSeconds 为可选值;省略时使用默认锁定值。

元数据和主页操作

metadata.patch

它的功能:

  • 存储锁定页面 UI 使用的扩展元数据。
  • 支持 unlockBlockershomeActions
  • 扩展程序打开时,支持 homeActions[].intent 实现深度链接行为。

何时使用:

  • 强制执行您的扩展程序拥有的锁定会话解锁条件。
  • 在锁定页面添加快捷操作,以便用户通过特定意图打开扩展程序。
  • 当用户点击主页操作时,直接将用户引导至特定屏幕/工作流程。

终点:

PATCH /api/extensions/sessions/:sessionId/metadata
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH

示例正文:

{
"unlockBlockers": ["Finish extension task"],
"homeActions": [
{
"slug": "tasks",
"title": "Tasks",
"description": "Open tasks panel",
"intent": {
"type": "open_panel",
"title": "Tasks",
"message": "Open tasks panel",
"payload": {
"panel": "regular-actions"
}
}
}
]
}

实地考察笔记:

  • unlockBlockers:您的扩展程序中已激活的锁定会话解锁阻止程序列表。
  • 您可以同时添加多个障碍物(最多 25 个),每个未满足的条件添加一个障碍物。
  • 只要已启用的扩展程序中存在任何阻止程序,解锁功能就会一直处于阻止状态。
  • Chastify 汇总了锁定会话中所有扩展的阻塞程序。
  • 您的扩展程序应该只在自身的元数据中添加/删除自身的拦截器。
  • 不要清除其他扩展程序的阻塞物;只有当满足您自己的条件时才清除您的数组。
  • homeActions:锁定体验中显示的快捷操作按钮。
  • homeActions[].slug:您的操作的稳定 ID。
  • homeActions[].title:面向用户的标签。
  • homeActions[].description:可选的辅助文本。
  • homeActions[].intent:打开扩展程序时传递给扩展程序的可选深度链接指令。
  • 在扩展卡用户界面中,这些操作按操作标题(内部由别名控制)显示为菜单/列表。
  • 当用户点击其中一个时,Chastify 会打开扩展程序并传递以下信息:
    • homeActionSlug
    • homeAction(选定的操作对象)
    • intent(规范化意图对象) 这样,您的扩展程序就可以在加载时立即路由到正确的视图/操作。

意图:开发者应用示例

在扩展应用程序中使用此模式,以便在加载时响应菜单点击意图。

import { useEffect, useRef } from "react";
import { parseHashPayload, type IframeHashPayload } from "../lib/ChastifyBridge";

export function useHomeActionIntent(
payload: IframeHashPayload,
routeToPanel: (panel: string) => void,
showToast: (message: string) => void,
) {
const handledRef = useRef<string>("");

useEffect(() => {
const homeActionSlug = payload?.homeActionSlug ?? null;
if (!homeActionSlug) return;

// Prevent duplicate handling if component re-renders.
const key = `${payload.lockId || "lock"}:${homeActionSlug}`;
if (handledRef.current === key) return;
handledRef.current = key;

const intent = payload?.intent ?? payload?.homeAction?.intent ?? null;
if (!intent) return;

if (intent.type === "open_panel") {
const panel = String(intent.payload?.panel || "");
if (panel) routeToPanel(panel);
return;
}

if (intent.message) {
showToast(String(intent.message));
}
}, [payload, routeToPanel, showToast]);
}

// Example app bootstrap
const payload = parseHashPayload();
if (!payload) throw new Error("Missing iframe hash payload");

此示例的作用:

  • 从 iframe 哈希有效载荷中读取 homeActionSlug + intent
  • 每个锁定/操作指令只处理一次点击。
  • 当意图为 open_panel 时,路由到面板。
  • 对于自定义意图类型,则回退到显示意图消息。

设备命令

device.command

它的功能:

  • 发送设备控制命令(如果该会话/设备支持)。
  • 让你的扩展程序通过 Chastify 触发标准化的冲击/振动动作。

何时使用:

  • 通过扩展逻辑触发支持的冲击/振动命令。
  • 构建交互式扩展功能(游戏、惩罚、奖励、例行程序)。

终点:

POST /api/extensions/sessions/:sessionId/device-command
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH

示例正文:

{
"command": "shock.start",
"params": {
"durationSeconds": 30,
"intensityPct": 50
}
}

常用命令:

  • shock.start,参数:{ durationSeconds, intensityPct, message? }
  • shock.stop
  • vibration.start,参数:{ durationSeconds, intensityPct, frequencyPct?, message? }
  • vibration.stop
  • all.stop
  • shock.random.set,参数:{ enabled, minIntensityPct?, maxIntensityPct?, message? }(仅限 Lockink AA-A1012)
  • shock.berserk.set,参数:{ enabled, message? }(仅限 Lockink AA-A1012)

推荐流量:

  1. 加载时调用 session.get
  2. deviceControl.supportedCommands 读取设备功能。
  3. 仅渲染受支持命令的控件。
  4. 发送已验证值的 device.command
  5. 刷新或信任响应 active 标志以获取实时 UI 状态。

参数指导:

  • durationSeconds:服务器限制在安全范围内(当前策略最大值为 300 秒)。
  • intensityPct:预期百分比值(1-100 样式输入,服务器端限制)。
  • frequencyPct:振动流量的预期百分比值(1-100)(服务器端钳位)。
  • 对于随机模式,请确保 minIntensityPct <= maxIntensityPct

更安全的用户界面模式示例:

// 1) Get capabilities first
const session = await bridge.request("session.get", {});
const supported = new Set(session?.deviceControl?.supportedCommands ?? []);

// 2) Only call command if supported
if (supported.has("shock.start")) {
await bridge.request("device.command", {
command: "shock.start",
params: { durationSeconds: 30, intensityPct: 50 }
});
}

重要的:

  • 请先调用 session.get 并读取支持的命令。
  • 仅显示当前会话支持的命令的控件。
  • 在发送命令参数之前,请验证用户输入。
  • device.command 需要对扩展会话授予写入权限 (locks:write)。
  • 优雅地处理桥接/服务器错误(insufficient_scope、不支持的命令、验证错误)。

扩展要求

扩展要求允许扩展程序定义重复完成规则,这些规则会显示在锁的“今日进度”用户界面中,并在佩戴者错过某个时间窗口时施加惩罚。

此功能可用于服务器信任的扩展活动,例如:

  • 每天完成1个拼图。
  • 每两天完成 3 次签到。
  • 每周完成 5 项拓展行动。

配置形状

该需求存储在扩展会话配置中的 extensionRequirements 下:

{
"extensionRequirements": {
"enabled": true,
"metric": "completion",
"requiredCount": 1,
"cadence": {
"every": 1,
"unit": "day"
},
"punishment": {
"type": "add_time",
"seconds": 900,
"reason": "Missed extension requirement"
}
}
}

字段:

  • enabled:开启/关闭重复性需求。
  • metric:稳定的计数器名称。请使用简单名称,例如 completionwinverification
  • requiredCount:窗口期内必须发生多少个可信事件。
  • cadence.every:区间大小。
  • cadence.unit: dayweek
  • 窗口时区由佩戴者配置的 User.timezone 解析而来,解析依据为 Chastify。扩展程序配置不应硬编码时区。
  • punishment.typenoneadd_timefreezepillory
  • punishment.secondsadd_timefreezepillory 的惩罚持续时间。
  • punishment.reason:可选的审计/调试原因。

进度模型

需求进度是可信的服务器端状态,而不是 iframe 本地状态。

请勿使用 state.patch / state.put 将需求标记为已完成。这些操作仅用于后端通用状态写入,并非需求进度 API。需求进度必须在服务器验证应计入的事件后才能记录。

例如:

  • 谜题扩展程序只有在服务器验证了签名谜题运行和完成状态后才能记录进度。
  • 验证扩展程序只有在服务器接受提交的证明后才能记录进度。
  • 游戏扩展程序只有在后端验证游戏结果或可信完成事件后才能记录进度。

运行时行为

配置后:

  • 锁定仪表盘可以显示今日进度中的需求。
  • Chastify 跟踪每次扩展训练和每个节奏窗口的进度。
  • 计划需求作业会评估已完成的窗口,并对每个错过的窗口应用一次配置的惩罚。
  • 每个窗口的惩罚都是幂等的,因此重试不会叠加重复的惩罚。
  1. 在扩展设置/配置界面中配置 extensionRequirements
  2. 运行时启动时,调用 session.get 并读取活动配置。
  3. 在用户界面中完成扩展活动。
  4. 将完成信息发送到该扩展程序的可信后端路由。
  5. 让后端验证事件并记录需求进度。
  6. 完成后,使用 session.get / state.get 刷新本地 UI。

重要的:

  • 请将 state.* 仅视为扩展程序拥有的存储空间。进度、尝试次数、奖励和惩罚等信息应使用专用的可信 API。
  • 不要依赖客户端提供的完成标记来判断需求。
  • 保持 metric 名称稳定;更改指标将开始计入不同的桶中。
  • Chastify 使用佩戴者配置的时区作为踏频窗口。如果用户未配置时区,服务器将回退到 UTC
  • 惩罚措施应有限制,并可在审计日志中加以解释。
  1. 启动时调用 session.get
  2. 使用 state.get 读取状态。
  3. 需要时,使用 PUT/PATCH /state 从后端执行状态写入。
  4. 仅在用户界面支持且可见的情况下执行锁定/设备操作。
  5. 对于有需求驱动的活动,将完成情况报告给可信的后端路由。
  6. 重要写入/操作后刷新本地视图。