Saltar al contenido principal

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 postMessage como session.get, state.get y files.get.
  • Los ejemplos privilegiados utilizan su propio backend de extensión con Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY y x-chastify-main-token.
  • Nunca envíes una clave de API de desarrollador a un iframe o código del navegador.
info

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.

precaución

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 / keyholderLastSeenTimestamp se devuelven solo si ese usuario tiene showOnlineStatus !== 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:

  1. El iframe lee mainToken y sessionId de la carga útil del hash.
  2. El iframe los envía a tu servidor.
  3. Tu sistema backend valida el estado de tu juego/tarea/negocio.
  4. 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.

aviso

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.

precaució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:

  • showPageOverlay se establece por defecto en false.
  • target se establece por defecto en wearer.
  • 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 stateMaxBytes de 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.get para 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:

  • durationSeconds es opcional.
  • Si se omite, el valor predeterminado actual es 3600 segundos (1 hora).
  • El rango aceptado es de 60 a 86400 segundos.

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:

  • name debe ser pillory.
  • Se requiere params.durationSeconds.
  • params.reason es 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:

  • name debe ser pillory.end.
  • Falla con pillory_not_active si 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.
  • points es opcional y está restringido en el lado del servidor.
  • verificationRequired se establece por defecto en false.
  • durationSeconds es opcional (0 significa 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.
  • durationSeconds es 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 unlockBlockers y homeActions.
  • Admite homeActions[].intent para 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:
    • homeActionSlug
    • homeAction (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 + intent de 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.start con parámetros: { durationSeconds, intensityPct, message? }
  • shock.stop
  • vibration.start con parámetros: { durationSeconds, intensityPct, frequencyPct?, message? }
  • vibration.stop
  • all.stop
  • shock.random.set con parámetros: { enabled, minIntensityPct?, maxIntensityPct?, message? } (solo Lockink AA-A1012)
  • shock.berserk.set con parámetros: { enabled, message? } (solo Lockink AA-A1012)

Flujo recomendado:

  1. Llamada a session.get al cargar.
  2. Lea las capacidades del dispositivo desde deviceControl.supportedCommands.
  3. Mostrar únicamente los controles para los comandos compatibles.
  4. Envíe device.command con valores validados.
  5. Actualizar o confiar en las banderas de respuesta active para 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 estilo 1-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.get y 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.command requiere 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 como completion, win o verification.
  • requiredCount: cuántos eventos confiables deben ocurrir en la ventana.
  • cadence.every: tamaño del intervalo.
  • cadence.unit: day o week.
  • La zona horaria de la ventana se determina mediante Chastify a partir del User.timezone configurado por el usuario. La configuración de la extensión no debe incluir una zona horaria fija.
  • punishment.type: none, add_time, freeze o pillory.
  • punishment.seconds: duración del castigo para add_time, freeze o pillory.
  • 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.
  1. Configure extensionRequirements en la interfaz de configuración de la extensión.
  2. Al iniciar la ejecución, llame a session.get y lea la configuración activa.
  3. Completa la actividad de extensión en la interfaz de usuario.
  4. Envía la finalización a una ruta de backend de confianza para esa extensión.
  5. Permitir que el backend valide el evento y registre el progreso del requisito.
  6. Actualice la interfaz de usuario local con session.get / state.get una 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.
  1. Llama a session.get al iniciar.
  2. Lea el estado con state.get.
  3. Realice escrituras de estado desde su backend con PUT/PATCH /state cuando sea necesario.
  4. Ejecuta las acciones de bloqueo/dispositivo solo cuando sean compatibles y visibles en la interfaz de usuario.
  5. Para las actividades que requieren confirmación, informe de su finalización a una ruta de backend de confianza.
  6. Actualizar la vista local después de escrituras/acciones importantes.