Ejemplos de API
Utilice esta página para copiar la estructura de solicitud correcta para cada flujo de extensión.
- Los ejemplos de puentes de iframe utilizan acciones
postMessagecomosession.get,state.getyfiles.get. - Los ejemplos privilegiados utilizan su propio backend de extensión con
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEYyx-chastify-main-token. - Nunca envíes una clave de API de desarrollador a un iframe o código del navegador.
El puente iframe sirve para el arranque de la interfaz de usuario y operaciones de sesión de bajo riesgo. Úselo para leer el contexto, almacenar el estado propiedad de la extensión y resolver las URL de los archivos.
El código del iframe del navegador está bajo el control del usuario. No utilice solicitudes de puente iframe para aplicar/eliminar tiempo, completar tareas, borrar bloqueadores de desbloqueo, cargar/eliminar archivos de tiempo de ejecución, enviar notificaciones, escribir registros o dar comandos a dispositivos.
Formato de solicitud y respuesta
Las acciones seguras del puente iframe se envían dentro de este sobre de solicitud de puente:
{
"type": "chastify:ext:req", // required
"v": 1, // protocol version
"id": "req-123", // your unique request id
"nonce": "from-iframe-hash",
"action": "session.get",
"payload": {}
}
Y usted recibe:
{
"type": "chastify:ext:resp",
"v": 1,
"id": "req-123", // same id you sent
"ok": true,
"data": {}
}
Si ok es false, compruebe error.code y error.message.
Los ejemplos que solo utilizan el backend emplean solicitudes HTTPS normales desde su servidor. No se envían a través del puente iframe.
Sesión y contexto
session.get
Utilice esto primero en casi todos los flujos de extensión.
Qué hace:
- Verifica que la conexión del puente funcione correctamente.
- Devuelve el contexto de la sesión (estado de bloqueo, rol, configuración de extensión, capacidades).
- Devuelve información sobre las capacidades del dispositivo que tu interfaz de usuario puede usar antes de pedirle a tu backend que llame a
device.command.
Para qué se utiliza:
- Inicializando tu interfaz de usuario.
- Activación/desactivación de funciones en función del rol y los permisos.
- Se comprueban los comandos de dispositivo compatibles antes de renderizar los controles del dispositivo.
Ejemplo de carga útil de acción:
{
"action": "session.get",
"payload": {}
}
Ejemplo de extracto de respuesta session.get (datos de bloqueo en tiempo de ejecución):
{
"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
}
}
}
Notas sobre privacidad:
wearerLastSeenTimestamp/keyholderLastSeenTimestampse devuelven solo si ese usuario tieneshowOnlineStatus !== false.- Si la visibilidad del estado en línea está deshabilitada, estos campos son
null.
Flujos de extensión de backend
Estos ejemplos muestran el patrón de producción seguro:
- El iframe lee
mainTokenysessionIdde la carga útil del hash. - El iframe los envía a tu servidor.
- Tu sistema backend valida el estado de tu juego/tarea/negocio.
- Tu backend llama a Chastify con una clave API de desarrollador con ámbito de aplicación más
x-chastify-main-token.
No utilice sessionId solo como método de autenticación. Considere mainToken como contexto de lanzamiento visible en el navegador y trate su clave de API de desarrollador como un secreto exclusivo del backend.
Tu backend debe validar el estado de su propio juego/tarea antes de llamar a Chastify. Pasar mainToken y sessionId desde el iframe solo identifica la sesión de extensión abierta; no prueba que el usuario haya completado un desafío.
Obtener el contexto de la sesión de forma segura
Tu iframe puede usar el puente seguro session.get para el arranque de la interfaz de usuario. Si tu backend necesita el mismo contexto antes de aplicar una acción privilegiada, obténlo de tu backend con ambas credenciales.
Iframe:
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
})
});
Tu backend:
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
});
});
Aplicar tiempo desde un backend de extensión
El navegador puede solicitar a tu servidor backend que aplique una recompensa o un castigo, pero no debe decidir si la acción es válida. Primero, verifica la acción en el servidor.
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());
});
Utilice add_time para castigos y remove_time para recompensas.
Finalización del juego verificada por el servidor
Para juegos como Simón dice, no confíes en una victoria reportada por el cliente. Crea la ejecución en el servidor, almacena la secuencia esperada o un hash de la misma y verifica la entrada enviada antes de llamar a Chastify.
Un juego de memoria visible en el navegador no puede demostrar que un humano haya jugado honestamente, ya que el navegador debe recibir la secuencia para renderizar el juego. La verificación del servidor sigue evitando mutaciones Chastify falsificadas y permite que tu backend aplique identificadores de ejecución, caducidad, dificultad, cadencia, puntuación y protección contra repeticiones antes de aplicar recompensas o castigos.
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 });
});
El esquema exacto de almacenamiento de ejecución es suyo. verifySessionLaunch debe llamar a Chastify con su clave API de desarrollador con ámbito de aplicación y x-chastify-main-token antes de confiar en sessionId. La regla importante es que las modificaciones de Chastify solo se producen después de que su backend verifique el lanzamiento y el resultado.
Requisitos programados
Tu sistema backend gestiona los cronogramas, las comprobaciones de cadencia y la validación de pruebas. Usa Chastify únicamente para registrar el progreso de confianza o actualizar los bloqueadores de desbloqueo una vez que tu sistema backend determine que se cumple el requisito.
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
})
});
}
Las API de sesión de extensiones instaladas actuales requieren un token de lanzamiento de iframe válido en x-chastify-main-token; estos tokens caducan a las 10 horas. Para las tareas programadas que se ejecutan después de que caduque dicho token, almacene su propia prueba pendiente y envíe el progreso en el siguiente lanzamiento válido de la extensión, o utilice un flujo de servidor integrado diseñado para el trabajo en segundo plano sin supervisión.
No considere que las tareas programadas son de confianza solo porque se ejecutan en su servidor. La tarea aún requiere verificación en el servidor, comprobaciones de cadencia, protección contra repetición y una ruta de autorización Chastify válida antes de registrar el progreso del requisito.
Notificaciones
notifications.custom
Utilice esto desde el panel de administración de su extensión para enviar una notificación personalizada de la extensión al usuario, al titular de la llave o a ambos.
Punto final:
POST /api/extensions/sessions/:sessionId/notifications/custom
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Ejemplo de cuerpo:
{
"title": "Extension Reminder",
"message": "Your next challenge is ready.",
"showPageOverlay": false,
"target": "both"
}
Notas:
showPageOverlayse establece por defecto enfalse.targetse establece por defecto enwearer.- La API crea el tipo de notificación
extension_app_message.
Estado de extensión
El estado de la extensión son los datos JSON propiedad de la extensión para la sesión de bloqueo actual.
Las operaciones de escritura de estado solo se realizan en el backend y requieren una clave de API de desarrollador con ámbito de aplicación, además del código de sesión mainToken. Los iframes pueden leer el estado con state.get, pero no pueden escribirlo directamente.
state.put
Qué hace:
- Reemplaza todo el objeto de estado con el nuevo objeto
data. - Requiere credenciales de backend.
Cuándo usarlo:
- Guardado inicial.
- Sobrescritura completa cuando ya se dispone del nuevo estado completo.
Ejemplo:
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"
}
}'
Notas de campo:
payload.data: cualquier valor/objeto JSON válido que necesite su extensión.- Avoid Mongo-unsafe key names (
$prefix or keys containing.). - El estado tiene un alcance de sesión y su tamaño está limitado por el código
stateMaxBytesde la aplicación de extensión, con un valor predeterminado de 64 KiB. - Almacena los identificadores de archivo en el estado, no los archivos binarios ni las URL firmadas. Usa
files.getpara actualizar las URL firmadas antes de renderizar el contenido multimedia.
state.patch
Qué hace:
- Aplica un parche de fusión JSON al estado existente.
- Solo es necesario enviar las claves modificadas.
- Requiere credenciales de backend.
Cuándo usarlo:
- Actualizaciones incrementales a partir de las interacciones del usuario.
- Actualizar un campo sin reenviar todo.
Ejemplo:
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
Qué hace:
- Lee el estado actual de la extensión.
- Disponible a través del puente iframe.
Cuándo usarlo:
- Al cargar el iframe.
- Después de escribir, si desea volver a sincronizar la interfaz de usuario local.
Ejemplo:
{
"action": "state.get",
"payload": {}
}
Almacenamiento de archivos
Utilice files.* para contenido binario como imágenes de rompecabezas, previsualizaciones generadas o fotos de desafíos. Almacene el file.id devuelto en el estado escrito en el servidor o en su propia base de datos. Renderice con file.signedUrl y actualice esa URL firmada con files.get cuando el iframe se cargue posteriormente.
Las pantallas de configuración que se ejecutan antes de que exista un bloqueo/sesión utilizan cargas provisionales en lugar de files.upload. Los archivos provisionales son temporales hasta que se guarda la configuración de la extensión con una referencia a provider: "chastify_storage" y fileId. Chastify reclama automáticamente esos archivos al guardar el bloqueo/plantilla; no hay ninguna llamada de reclamación independiente en el navegador.
Ejemplo de división de estado/archivo desde su backend:
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"
}
}'
Posteriormente, resuelva la imagen antes de mostrarla:
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
Verifique esto antes de mostrar los controles de carga.
{
"action": "files.capabilities",
"payload": {}
}
Tiempo de ejecución files.upload
La carga de archivos en tiempo de ejecución modifica los datos de sesión de la extensión, por lo que no es un comando puente de iframe. Cargue archivos en tiempo de ejecución desde su backend con una clave de API de desarrollador con ámbito de aplicación y x-chastify-main-token.
files.get
Actualizar una URL R2 firmada a partir de un ID de archivo estable.
{
"action": "files.get",
"payload": {
"fileId": "file_record_id"
}
}
files.list
{
"action": "files.list",
"payload": {}
}
El entorno de ejecución files.delete también es solo para el backend por la misma razón que la carga.
Acciones de bloqueo
Agregar tiempo
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Agrega o quita tiempo a la cuenta regresiva del bloqueo dependiendo de
deltaSeconds.
Cuándo usarlo:
- Botones de recompensa/penalización.
- Resultados del juego (ganar suma tiempo, perder lo resta).
Ejemplo:
{
"name": "add_time",
"params": 300 // +300 sec = +5 minutes
}
Notas de campo:
- El valor positivo añade tiempo.
- Un valor negativo elimina tiempo (si lo permiten las reglas del servidor).
Congelar
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Congela el progreso durante un tiempo.
Cuándo usarlo:
- Mecánicas de enfriamiento.
- Puntos de control de recompensa.
Ejemplo:
{
"name": "freeze",
"params": { "durationSeconds": 120 }
}
También puedes llamarlo sin durationSeconds:
{
"name": "freeze",
"params": {}
}
Notas de campo:
durationSecondses opcional.- Si se omite, el valor predeterminado actual es
3600segundos (1 hora). - El rango aceptado es de
60a86400segundos.
Descongelar
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Finaliza el bloqueo activo y reanuda el comportamiento normal del temporizador.
Cuándo usarlo:
- Anulación manual en flujos de trabajo de extensión.
- Controles de “cancelación de congelación”.
Ejemplo:
{
"name": "unfreeze",
"params": {}
}
Picota
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Comienza un período de castigo para la sesión de encierro activa.
Cuándo usarlo:
- Mecanismos de penalización tras tareas/desafíos fallidos.
- Flujos de escalada que restringen temporalmente la interacción de bloqueo.
Ejemplo:
{
"name": "pillory",
"params": {
"durationSeconds": 600,
"reason": "Missed scheduled check-in"
}
}
Notas de campo:
namedebe serpillory.- Se requiere
params.durationSeconds. params.reasones opcional.- Requiere que la picota esté habilitada en la configuración de la sesión.
Fin de la picota
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Finaliza inmediatamente la sesión de la picota en curso.
Ejemplo:
{
"name": "pillory.end",
"params": {}
}
Notas de campo:
namedebe serpillory.end.- Falla con
pillory_not_activesi el candado no está actualmente en la picota.
Asignar tarea
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Crea una tarea activa en ejecución para el usuario a partir de la lógica de extensión.
- Puede sobrescribir la ejecución de una tarea que ya esté abierta.
Ejemplo:
{
"name": "task.assign",
"params": {
"taskText": "Clean your room",
"points": 10,
"verificationRequired": true,
"durationSeconds": 1800
}
}
Notas de campo:
- Se requiere
taskText. pointses opcional y está restringido en el lado del servidor.verificationRequiredse establece por defecto enfalse.durationSecondses opcional (0significa que no se requiere temporizador).- Requiere que el módulo Tareas esté habilitado en la cerradura.
Iniciar temporizador de tareas
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Inicia o reinicia la ventana de cuenta regresiva para la tarea programada actualmente activa.
Ejemplo:
{
"name": "task.start_timer",
"params": {}
}
Notas de campo:
- Requiere que la tarea esté en ejecución.
- Fallará si la tarea actual no tiene configurada una duración.
Completar la tarea
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Indica si la ejecución de la tarea activa se ha completado o ha fallado.
Ejemplo (éxito):
{
"name": "task.complete",
"params": {
"successful": true
}
}
Ejemplo (fallo):
{
"name": "task.complete",
"params": {
"successful": false,
"reason": "Did not finish in time"
}
}
Activar apertura temporal
Punto final: POST /api/extensions/sessions/:sessionId/action
Qué hace:
- Inicia una ventana de apertura de higiene temporal desde la lógica de extensión.
Ejemplo:
{
"name": "hygienic_unlock.start",
"params": {
"durationSeconds": 900
}
}
Notas de campo:
- Requiere que la apertura higiénica esté habilitada en la cerradura.
- Se considera fallido si ya se está llevando a cabo una apertura por motivos de higiene.
durationSecondses opcional; se utiliza el valor predeterminado de bloqueo cuando se omite.
Metadatos y acciones de inicio
metadata.patch
Qué hace:
- Almacena los metadatos de extensión utilizados por la interfaz de usuario de la página de bloqueo.
- Admite
unlockBlockersyhomeActions. - Admite
homeActions[].intentpara el comportamiento de enlace profundo cuando se abre la extensión.
Cuándo usarlo:
- Aplique las condiciones de desbloqueo de sesión bloqueada que pertenecen a su extensión.
- Agrega acciones rápidas en la página de bloqueo que abran tu extensión con la intención del usuario.
- Cuando los usuarios hagan clic en una acción de inicio, se les redirigirá directamente a una pantalla o flujo de trabajo específico.
Punto final:
PATCH /api/extensions/sessions/:sessionId/metadata
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Ejemplo de cuerpo:
{
"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"
}
}
}
]
}
Notas de campo:
unlockBlockers: lista de bloqueadores de desbloqueo de sesión activos de su extensión.- Puede incluir varios bloqueadores a la vez (hasta 25), uno por cada afección no satisfecha.
- El desbloqueo permanece bloqueado mientras exista algún bloqueador en las extensiones habilitadas.
- Chastify agrega los bloqueadores de todas las extensiones para la sesión de bloqueo.
- Tu extensión solo debe agregar/eliminar sus propios bloqueadores en sus propios metadatos.
- No elimine los bloqueadores de otras extensiones; borre su matriz solo cuando se cumplan sus propias condiciones.
homeActions: botones de acción rápida que se muestran en la experiencia de bloqueo.homeActions[].slug: ID estable para su acción.homeActions[].title: etiqueta visible para el usuario.homeActions[].description: texto auxiliar opcional.homeActions[].intent: instrucción de enlace profundo opcional que se pasa a su extensión cuando se abre.- En la interfaz de usuario de la tarjeta de extensión, estas acciones se muestran como un menú/lista por título de acción (identificado internamente por slug).
- Cuando un usuario hace clic en uno, Chastify abre la extensión y pasa:
homeActionSlughomeAction(objeto de acción seleccionado)intent(objeto de intención normalizado) De esta forma, tu extensión podrá redirigir inmediatamente a la vista/acción correcta al cargarse.
Intenciones: ejemplo de aplicación para desarrolladores
Utilice este patrón en su aplicación de extensión para reaccionar a las intenciones de clic en el menú al cargar la página.
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");
Lo que hace este ejemplo:
- Lee
homeActionSlug+intentde la carga útil del hash del iframe. - Gestiona cada clic solo una vez por cada bloque de bloqueo/acción.
- Enruta a un panel cuando la intención es
open_panel. - En caso contrario, se muestra un mensaje de intención para los tipos de intención personalizados.
Comando del dispositivo
device.command
Qué hace:
- Envía un comando de control del dispositivo (si es compatible con esa sesión/dispositivo).
- Permite que tu extensión active acciones estandarizadas de choque/vibración a través de Chastify.
Cuándo usarlo:
- Activación de comandos de choque/vibración compatibles desde la lógica de extensión.
- Creación de funciones de extensión interactivas (juegos, castigos, recompensas, rutinas).
Punto final:
POST /api/extensions/sessions/:sessionId/device-command
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Ejemplo de cuerpo:
{
"command": "shock.start",
"params": {
"durationSeconds": 30,
"intensityPct": 50
}
}
Comandos comunes:
shock.startcon parámetros:{ durationSeconds, intensityPct, message? }shock.stopvibration.startcon parámetros:{ durationSeconds, intensityPct, frequencyPct?, message? }vibration.stopall.stopshock.random.setcon parámetros:{ enabled, minIntensityPct?, maxIntensityPct?, message? }(solo Lockink AA-A1012)shock.berserk.setcon parámetros:{ enabled, message? }(solo Lockink AA-A1012)
Flujo recomendado:
- Llamada a
session.getal cargar. - Lea las capacidades del dispositivo desde
deviceControl.supportedCommands. - Mostrar únicamente los controles para los comandos compatibles.
- Envíe
device.commandcon valores validados. - Actualizar o confiar en las banderas de respuesta
activepara el estado de la interfaz de usuario en vivo.
Guía de parámetros:
durationSeconds: el servidor se ajusta a límites seguros (el máximo de la política actual es de 300 s).intensityPct: valor porcentual esperado (entrada de estilo1-100, limitada en el lado del servidor).frequencyPct: valor porcentual esperado (1-100) para flujos de vibración (limitados en el lado del servidor).- Para el modo aleatorio, asegúrese de que
minIntensityPct <= maxIntensityPct.
Ejemplo de patrón de interfaz de usuario más seguro:
// 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 }
});
}
Importante:
- Llame primero a
session.gety lea los comandos compatibles. - Mostrar únicamente los controles de los comandos compatibles con la sesión actual.
- Validar las entradas del usuario antes de enviar los parámetros del comando.
device.commandrequiere permiso de escritura (locks:write) para la sesión de extensión.- Gestionar adecuadamente los errores del puente/servidor (
insufficient_scope, comando no compatible, errores de validación).
Requisitos de extensión
Los requisitos de extensión permiten que una extensión defina reglas de finalización recurrentes que se muestran en la interfaz de usuario de Progreso de hoy del candado y pueden aplicar una penalización cuando el usuario pierde una ventana de tiempo.
Utilice esto para actividades de extensión de confianza del servidor, tales como:
- Completa 1 rompecabezas por día.
- Realiza 3 registros de entrada cada 2 días.
- Completa 5 acciones de extensión por semana.
Forma de configuración
El requisito se almacena en la configuración de la sesión de extensión bajo extensionRequirements:
{
"extensionRequirements": {
"enabled": true,
"metric": "completion",
"requiredCount": 1,
"cadence": {
"every": 1,
"unit": "day"
},
"punishment": {
"type": "add_time",
"seconds": 900,
"reason": "Missed extension requirement"
}
}
}
Campos:
enabled: activa/desactiva el requisito recurrente.metric: nombre de contador estable. Utilice nombres sencillos comocompletion,winoverification.requiredCount: cuántos eventos confiables deben ocurrir en la ventana.cadence.every: tamaño del intervalo.cadence.unit:dayoweek.- La zona horaria de la ventana se determina mediante Chastify a partir del
User.timezoneconfigurado por el usuario. La configuración de la extensión no debe incluir una zona horaria fija. punishment.type:none,add_time,freezeopillory.punishment.seconds: duración del castigo paraadd_time,freezeopillory.punishment.reason: motivo de auditoría/depuración opcional.
Modelo de progreso
El progreso de los requisitos es un estado confiable del servidor, no un estado local del iframe.
No utilice state.patch ni state.put para marcar un requisito como completado. Estas acciones son escrituras de estado generales exclusivas del servidor, no API de progreso de requisitos. El progreso del requisito debe registrarse únicamente después de que el servidor valide el evento que debe contabilizarse.
Por ejemplo:
- Una extensión de rompecabezas solo debe registrar el progreso después de que el servidor valide la ejecución firmada del rompecabezas y el estado de finalización.
- Una extensión de verificación solo debe registrar el progreso después de que el servidor acepte la prueba enviada.
- Una extensión de juego solo debe registrar el progreso después de que el backend valide el resultado del juego o el evento de finalización de confianza.
Comportamiento en tiempo de ejecución
Cuando se configura:
- El panel de bloqueo puede mostrar el requisito en el Progreso de hoy.
- Chastify realiza un seguimiento del progreso por sesión de extensión y por ventana de cadencia.
- La tarea programada evalúa las ventanas completadas y aplica la penalización configurada una vez por cada ventana no completada.
- Las penalizaciones son idempotentes por ventana, por lo que los reintentos no acumulan penalizaciones duplicadas.
Flujo de requisitos recomendado
- Configure
extensionRequirementsen la interfaz de configuración de la extensión. - Al iniciar la ejecución, llame a
session.gety lea la configuración activa. - Completa la actividad de extensión en la interfaz de usuario.
- Envía la finalización a una ruta de backend de confianza para esa extensión.
- Permitir que el backend valide el evento y registre el progreso del requisito.
- Actualice la interfaz de usuario local con
session.get/state.getuna vez finalizado.
Importante:
- Trate
state.*como almacenamiento propiedad exclusiva de la extensión. Utilice las API dedicadas y de confianza para el progreso, los intentos, las recompensas y las penalizaciones. - No confíe en los indicadores de finalización exclusivos del cliente para los requisitos.
- Mantenga estables los nombres
metric; al cambiar la métrica, se empiezan a contabilizar en un grupo diferente. - Chastify utiliza la zona horaria configurada por el usuario para las ventanas de cadencia. Si el usuario no tiene disponible ninguna zona horaria, el servidor recurre a
UTC. - Limite los castigos y asegúrese de que queden registrados en los informes de auditoría.
Orden de acción recomendada
- Llama a
session.getal iniciar. - Lea el estado con
state.get. - Realice escrituras de estado desde su backend con
PUT/PATCH /statecuando sea necesario. - Ejecuta las acciones de bloqueo/dispositivo solo cuando sean compatibles y visibles en la interfaz de usuario.
- Para las actividades que requieren confirmación, informe de su finalización a una ruta de backend de confianza.
- Actualizar la vista local después de escrituras/acciones importantes.