Pular para o conteúdo principal

Exemplos de API

Use esta página para copiar o formato de solicitação correto para cada fluxo de extensão.

  • Os exemplos de ponte Iframe usam ações postMessage, como session.get, state.get e files.get.
  • Os exemplos privilegiados utilizam seu próprio backend de extensão com Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY e x-chastify-main-token.
  • Nunca envie uma chave de API de desenvolvedor para um iframe ou código de navegador.
informação

A ponte iframe serve para inicialização da interface do usuário e operações de sessão de baixo risco. Use-a para ler o contexto, armazenar o estado pertencente à extensão e resolver URLs de arquivos.

cuidado

O código iframe do navegador é controlado pelo usuário. Não utilize solicitações de ponte iframe para aplicar/remover tempo, concluir tarefas, remover bloqueadores de desbloqueio, carregar/excluir arquivos de tempo de execução, enviar notificações, gravar registros ou comandar dispositivos.

Formato de solicitação e resposta

As ações seguras da ponte iframe são enviadas dentro deste envelope de solicitação de ponte:

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

E você recebe:

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

Se ok for igual a false, verifique error.code e error.message.

Os exemplos que utilizam apenas o backend usam solicitações HTTPS normais do seu servidor. Eles não são enviados através da ponte iframe.

Sessão e Contexto

session.get

Use isso em primeiro lugar em quase todos os fluxos de extensão.

O que faz:

  • Verifica se a sua conexão de ponte está funcionando.
  • Retorna o contexto da sessão (estado de bloqueio, função, configuração de extensão, recursos).
  • Retorna informações sobre a capacidade do dispositivo que sua interface de usuário pode usar antes de solicitar ao seu servidor que chame device.command.

Para que serve:

  • Inicializando sua interface de usuário.
  • Habilitar/desabilitar funcionalidades com base em funções e permissões.
  • Verificar os comandos de dispositivo compatíveis antes de renderizar os controles do dispositivo.

Exemplo de carga útil da ação:

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

Exemplo de trecho da resposta session.get (dados de bloqueio em tempo de execução):

{
"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 privacidade:

  • Os códigos wearerLastSeenTimestamp e keyholderLastSeenTimestamp são retornados somente se o usuário possuir o código showOnlineStatus !== false.
  • Se a visibilidade do status online estiver desativada, esses campos serão null.

Fluxos de extensão de back-end

Estes exemplos mostram o padrão de produção seguro:

  1. O iframe lê mainToken e sessionId do payload de hash.
  2. O iframe os envia para o seu servidor.
  3. Seu sistema de backend valida o estado do seu jogo/tarefa/negócio.
  4. Seu backend chama Chastify com uma chave de API de desenvolvedor com escopo de aplicativo, além de x-chastify-main-token.

Não utilize apenas sessionId para autenticação. Considere mainToken como um contexto de inicialização visível no navegador e trate sua chave de API de desenvolvedor como um segredo exclusivo do backend.

aviso

Seu backend deve validar seu próprio estado de jogo/tarefa antes de chamar Chastify. Passar mainToken e sessionId do iframe apenas identifica a sessão de extensão aberta; não comprova que o usuário concluiu um desafio.

Recuperar contexto de sessão com segurança

Seu iframe pode usar a ponte segura session.get para inicialização da interface do usuário. Se o seu backend precisar do mesmo contexto antes de aplicar uma ação privilegiada, busque-o no backend com ambas as credenciais.

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
})
});

Seu 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 tempo a partir do painel administrativo de uma extensão

O navegador pode solicitar ao seu servidor que aplique uma recompensa ou punição, mas não deve decidir se a ação é válida. Verifique a ação primeiro no 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());
});

Use add_time para punições e remove_time para recompensas.

Conclusão do jogo verificada pelo servidor

Para jogos como Simon Says, não confie na vitória relatada pelo cliente. Crie a execução no servidor, armazene a sequência esperada ou um hash dela e verifique a entrada enviada antes de chamar Chastify.

Um jogo da memória visível no navegador não pode provar que um humano jogou honestamente, porque o navegador precisa receber a sequência para renderizar o jogo. A verificação do servidor ainda impede mutações Chastify falsificadas e permite que seu backend aplique IDs de execução, expiração, dificuldade, cadência, pontuação e proteção contra repetição antes de aplicar recompensas ou punições.

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 });
});

O esquema exato de armazenamento de execução é seu. verifySessionLaunch deve chamar Chastify com sua chave de API de desenvolvedor com escopo de aplicativo e x-chastify-main-token antes de confiar em sessionId. A regra importante é que as mutações de Chastify ocorrem somente depois que seu backend verifica a inicialização e o resultado.

Requisitos programados

Seu sistema de backend gerencia agendamentos, verificações de cadência e validação de provas. Use Chastify somente para registrar o progresso confiável ou atualizar os bloqueadores de desbloqueio depois que seu sistema de backend determinar que o requisito foi atendido.

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
})
});
}

As APIs de sessão das extensões instaladas atualmente exigem um token de inicialização de iframe válido em x-chastify-main-token; os tokens de inicialização expiram após 10 horas. Para tarefas agendadas que são executadas após a expiração desse token, armazene sua própria prova pendente e envie o progresso na próxima inicialização de extensão válida ou use um fluxo de servidor integrado/de primeira parte projetado para trabalho em segundo plano não supervisionado.

cuidado

Não considere as tarefas agendadas como confiáveis ​​apenas porque são executadas no seu servidor. A tarefa ainda precisa de comprovação no servidor, verificações de cadência, proteção contra repetição e um caminho de autorização Chastify válido antes de registrar o progresso dos requisitos.

Notificações

notifications.custom

Use esta opção no painel administrativo da sua extensão para enviar uma notificação personalizada ao usuário, ao detentor da chave ou a ambos.

Ponto final:

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

Exemplo de corpo:

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

Notas:

  • showPageOverlay tem como valor padrão false.
  • target tem como valor padrão wearer.
  • A API cria o tipo de notificação extension_app_message.

Estado de extensão

O estado da extensão são os dados JSON pertencentes à sua extensão para a sessão de bloqueio atual. A gravação de estado é exclusiva do backend e requer uma chave de API de desenvolvedor com escopo de aplicativo, além do código de sessão mainToken. Iframes podem ler o estado com state.get, mas não podem gravá-lo diretamente.

state.put

O que faz:

  • Substitui todo o objeto de estado pelo novo objeto data.
  • Requer credenciais de backend.

Quando usar:

  • Salvamento inicial.
  • Sobrescrever completamente quando você já tiver o novo estado totalmente definido.

Exemplo:

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: qualquer valor/objeto JSON válido que sua extensão necessite.
  • Avoid Mongo-unsafe key names ($ prefix or keys containing .).
  • O estado tem escopo de sessão e tamanho limitado pelo código stateMaxBytes do aplicativo de extensão, com valor padrão de 64 KiB.
  • Armazene os IDs dos arquivos no estado, não em arquivos binários ou URLs assinadas. Use files.get para atualizar as URLs assinadas antes de renderizar a mídia.

state.patch

O que faz:

  • Aplica um patch de mesclagem JSON ao estado existente.
  • Somente as chaves alteradas precisam ser enviadas.
  • Requer credenciais de backend.

Quando usar:

  • Atualizações incrementais a partir das interações do usuário.
  • Atualizar um campo sem reenviar tudo.

Exemplo:

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

O que faz:

  • Lê o estado atual da extensão.
  • Disponível através da ponte iframe.

Quando usar:

  • Ao carregar o iframe.
  • Após as gravações, se desejar ressincronizar a interface de usuário local.

Exemplo:

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

Armazenamento de arquivos

Use files.* para mídias binárias, como imagens de quebra-cabeças, pré-visualizações geradas ou fotos de desafios. Armazene o file.id retornado no estado gravado no backend ou em seu próprio banco de dados. Renderize com file.signedUrl e atualize a URL assinada com files.get quando o iframe for carregado posteriormente.

As telas de configuração que são exibidas antes do término de um bloqueio/sessão usam uploads em etapas em vez de files.upload. Os arquivos em etapas são temporários até que a configuração da extensão seja salva com uma referência provider: "chastify_storage" e fileId. Chastify reivindica esses arquivos automaticamente ao bloquear/salvar o modelo; não há nenhuma chamada de reivindicação separada no navegador.

Exemplo de divisão de estado/arquivo a partir do seu 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, resolva a imagem antes de exibi-la:

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 isso antes de exibir os controles de upload.

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

Tempo de execução files.upload

O envio de arquivos em tempo de execução altera os dados da sessão da extensão, portanto, não é um comando de ponte iframe. Envie arquivos em tempo de execução do seu backend com uma chave de API de desenvolvedor com escopo de aplicativo e x-chastify-main-token.

files.get

Atualizar um URL R2 assinado a partir de um ID de arquivo estável.

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

files.list

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

O ambiente de execução files.delete também é exclusivo do backend pelo mesmo motivo que o upload.

Ações de bloqueio

Adicionar tempo

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Adiciona ou remove tempo da contagem regressiva do bloqueio, dependendo de deltaSeconds.

Quando usar:

  • Botões de recompensa/penalidade.
  • Resultados do jogo (vitória adiciona tempo, derrota remove tempo).

Exemplo:

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

Notas de campo:

  • Valor positivo adiciona tempo.
  • Um valor negativo remove o tempo (se permitido pelas regras do servidor).

Congelar

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Congelamentos bloqueiam o progresso por um período determinado.

Quando usar:

  • Mecânicas de recarga.
  • Pontos de recompensa.

Exemplo:

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

Você também pode chamá-lo sem durationSeconds:

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

Notas de campo:

  • durationSeconds é opcional.
  • Se omitido, o valor padrão atual é 3600 segundos (1 hora).
  • O intervalo aceito é de 60 a 86400 segundos.

Descongelar

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Encerra o congelamento ativo e retoma o comportamento normal do temporizador.

Quando usar:

  • Substituição manual em fluxos de trabalho de extensão.
  • Controles de “cancelar congelamento”.

Exemplo:

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

Pelourinho

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Inicia um período de imobilização no pelourinho durante a sessão de bloqueio ativa.

Quando usar:

  • Mecanismo de penalidades após tarefas/desafios falhos.
  • Fluxos de escalonamento que restringem temporariamente a interação com o bloqueio.

Exemplo:

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

Notas de campo:

  • name deve ser pillory.
  • params.durationSeconds é obrigatório.
  • params.reason é opcional.
  • Requer que o pelourinho esteja habilitado na configuração da sessão.

Fim do pelourinho

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Encerra imediatamente a sessão de prisão ativa atual.

Exemplo:

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

Notas de campo:

  • name deve ser pillory.end.
  • Falha com pillory_not_active se a fechadura não estiver atualmente em pilar.

Atribuir tarefa

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Cria uma tarefa ativa para o usuário a partir da lógica da extensão.
  • Pode substituir uma execução de tarefa já em andamento.

Exemplo:

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

Notas de campo:

  • taskText é obrigatório.
  • points é opcional e está definido no servidor.
  • verificationRequired tem como valor padrão false.
  • durationSeconds é opcional (0 significa que não há necessidade de temporizador).
  • Requer que o módulo Tarefas esteja habilitado na fechadura.

Iniciar temporizador de tarefas

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Inicia ou reinicia a contagem regressiva da tarefa cronometrada atualmente ativa.

Exemplo:

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

Notas de campo:

  • Requer uma tarefa em execução.
  • Falha se a tarefa atual não tiver uma duração configurada.

Concluir tarefa

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Marca a execução da tarefa ativa como concluída ou com falha.

Exemplo (sucesso):

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

Exemplo (falha):

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

Acionar abertura temporária

Ponto final: POST /api/extensions/sessions/:sessionId/action

O que faz:

  • Inicia uma janela de abertura temporária para higienização a partir da lógica de extensão.

Exemplo:

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

Notas de campo:

  • Requer que a abertura higiênica esteja ativada na fechadura.
  • Falha se uma abertura para higienização já estiver em andamento.
  • durationSeconds é opcional; o padrão de bloqueio é usado quando omitido.

Metadados e ações da página inicial

metadata.patch

O que faz:

  • Armazena metadados de extensão usados ​​pela interface do usuário da página de bloqueio.
  • Suporta unlockBlockers e homeActions.
  • Suporta homeActions[].intent para comportamento de link direto quando a extensão é aberta.

Quando usar:

  • Impor as condições de desbloqueio de sessão bloqueadas que pertencem à sua extensão.
  • Adicione ações rápidas à página de bloqueio que abram sua extensão com base na intenção.
  • Direcione os usuários diretamente para uma tela/fluxo de trabalho específico quando eles clicarem em uma ação da página inicial.

Ponto final:

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

Exemplo de corpo:

{
"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 desbloqueio de sessão ativos da sua extensão.
  • Você pode incluir vários bloqueadores de uma só vez (até 25), um para cada condição não atendida.
  • O desbloqueio permanece bloqueado enquanto houver qualquer bloqueador ativo nas extensões habilitadas.
  • Chastify agrega bloqueadores de todas as extensões para a sessão de bloqueio.
  • Sua extensão deve adicionar/remover seus próprios bloqueadores apenas em seus próprios metadados.
  • Não remova os bloqueadores de outras extensões; limpe seu array somente quando suas próprias condições forem satisfeitas.
  • homeActions: botões de ação rápida exibidos na experiência de bloqueio.
  • homeActions[].slug: ID estável para sua ação.
  • homeActions[].title: rótulo voltado para o usuário.
  • homeActions[].description: texto auxiliar opcional.
  • homeActions[].intent: instrução opcional de link direto passada para sua extensão quando aberta.
  • Na interface do cartão de extensão, essas ações são exibidas como um menu/lista pelo título da ação (interiormente indexado pelo slug).
  • Quando um usuário clica em um deles, Chastify abre a extensão e passa:
    • homeActionSlug
    • homeAction (objeto de ação selecionado)
    • intent (objeto de intenção normalizado) Assim, sua extensão poderá ser direcionada imediatamente para a visualização/ação correta ao carregar a página.

Objetivos: exemplo de aplicativo para desenvolvedores

Use esse padrão no seu aplicativo de extensão para reagir a intents de clique no menu ao carregar a 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");

O que este exemplo faz:

  • homeActionSlug + intent do conteúdo hash do iframe.
  • Processa cada clique apenas uma vez por bloco de bloqueio/ação.
  • Encaminha para um painel quando a intenção é open_panel.
  • Recorre à exibição de uma mensagem de intenção para tipos de intenção personalizados.

Comando do dispositivo

device.command

O que faz:

  • Envia um comando de controle do dispositivo (se compatível com essa sessão/dispositivo).
  • Permite que sua extensão acione ações padronizadas de choque/vibração através de Chastify.

Quando usar:

  • Acionamento de comandos de choque/vibração suportados a partir da lógica de extensão.
  • Desenvolvimento de funcionalidades interativas adicionais (jogos, punições, recompensas, rotinas).

Ponto final:

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

Exemplo de corpo:

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

Comandos comuns:

  • shock.start com parâmetros: { durationSeconds, intensityPct, message? }
  • shock.stop
  • vibration.start com parâmetros: { durationSeconds, intensityPct, frequencyPct?, message? }
  • vibration.stop
  • all.stop
  • shock.random.set com parâmetros: { enabled, minIntensityPct?, maxIntensityPct?, message? } (somente Lockink AA-A1012)
  • shock.berserk.set com parâmetros: { enabled, message? } (somente Lockink AA-A1012)

Fluxo recomendado:

  1. Chame session.get ao carregar.
  2. Leia as capacidades do dispositivo a partir de deviceControl.supportedCommands.
  3. Renderiza apenas os controles para os comandos suportados.
  4. Envie device.command com valores validados.
  5. Atualizar ou confiar nos sinalizadores de resposta active para o estado da interface do usuário em tempo real.

Orientações sobre parâmetros:

  • durationSeconds: o servidor limita-se a valores seguros (o máximo da política atual é de 300 segundos).
  • intensityPct: valor percentual esperado (entrada no estilo 1-100, limitada no servidor).
  • frequencyPct: valor percentual esperado (1-100) para fluxos de vibração (fixados no lado do servidor).
  • Para o modo aleatório, certifique-se de usar minIntensityPct <= maxIntensityPct.

Exemplo de padrão de interface de usuário mais 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:

  • Chame primeiro session.get e leia os comandos suportados.
  • Exibir apenas os controles para os comandos compatíveis com a sessão atual.
  • Valide as entradas do usuário antes de enviar os parâmetros do comando.
  • device.command requer permissão de escrita (locks:write) para a sessão de extensão.
  • Lidar com erros de ponte/servidor de forma adequada (insufficient_scope, comando não suportado, erros de validação).

Requisitos de extensão

Os requisitos de extensão permitem que uma extensão defina regras de conclusão recorrentes que são exibidas na interface de progresso de hoje do dispositivo e podem aplicar uma penalidade quando o usuário perde um prazo.

Use isso para atividades de extensão confiáveis ​​do servidor, como:

  • Complete 1 quebra-cabeça por dia.
  • Faça 3 check-ins a cada 2 dias.
  • Conclua 5 ações de extensão por semana.

Formato de configuração

O requisito está armazenado na configuração da sessão de extensão em 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: ativa/desativa a recorrência da exigência.
  • metric: nome de contador estável. Use nomes simples como completion, win ou verification.
  • requiredCount: quantos eventos confiáveis ​​devem ocorrer na janela.
  • cadence.every: tamanho do intervalo.
  • cadence.unit: day ou week.
  • O fuso horário da janela é resolvido por Chastify a partir do User.timezone configurado pelo usuário. A configuração da extensão não deve definir um fuso horário fixo.
  • punishment.type: none, add_time, freeze ou pillory.
  • punishment.seconds: duração da punição para add_time, freeze ou pillory.
  • punishment.reason: motivo opcional de auditoria/depuração.

Modelo de progresso

O progresso dos requisitos é um estado confiável do lado do servidor, não um estado local do iframe.

Não utilize state.patch / state.put para marcar um requisito como concluído. Essas ações são gravações de estado geral exclusivas do backend, não APIs de progresso de requisitos. O progresso do requisito deve ser registrado somente após o servidor validar o evento que deve ser contabilizado.

Por exemplo:

  • Uma extensão de quebra-cabeça deve registrar o progresso somente depois que o servidor validar a execução e o estado de conclusão do quebra-cabeça, devidamente assinados.
  • Uma extensão de verificação deve registrar o progresso somente após o servidor aceitar a prova enviada.
  • Uma extensão de jogo deve registrar o progresso somente após o servidor validar o resultado do jogo ou um evento de conclusão confiável.

Comportamento em tempo de execução

Quando configurado:

  • O painel de bloqueio pode mostrar o requisito no Progresso de Hoje.
  • Chastify monitora o progresso por sessão de extensão e por janela de cadência.
  • A tarefa de requisito agendada avalia as janelas concluídas e aplica a penalidade configurada uma vez por janela não cumprida.
  • As punições são idempotentes por janela, portanto, novas tentativas não acumulam penalidades duplicadas.
  1. Configure extensionRequirements na interface de configuração da extensão.
  2. Na inicialização do ambiente de execução, chame session.get e leia a configuração ativa.
  3. Conclua a atividade de extensão na interface do usuário.
  4. Enviar a conclusão para uma rota de backend confiável para essa extensão.
  5. Permita que o sistema de backend valide o evento e registre o progresso dos requisitos.
  6. Atualize a interface de usuário local com session.get / state.get após a conclusão.

Importante:

  • Considere state.* como armazenamento exclusivo da extensão. Use APIs dedicadas e confiáveis ​​para progresso, tentativas, recompensas e punições.
  • Não confie em indicadores de conclusão exclusivos do cliente para requisitos.
  • Mantenha os nomes metric estáveis; alterar a métrica faz com que ela passe a ser contabilizada em um bucket diferente.
  • Chastify usa o fuso horário configurado pelo usuário para as janelas de cadência. Se nenhum fuso horário estiver disponível para o usuário, o servidor usa UTC como alternativa.
  • Mantenha as punições delimitadas e explicáveis ​​nos registros de auditoria.
  1. Chame session.get na inicialização.
  2. Leia o estado com state.get.
  3. Execute gravações de estado do seu backend com PUT/PATCH /state quando necessário.
  4. Execute ações de bloqueio/dispositivo somente quando forem compatíveis e visíveis na interface do usuário.
  5. Para atividades baseadas em requisitos, reporte a conclusão para uma rota de backend confiável.
  6. Atualizar a visualização local após gravações/ações importantes.