Aller au contenu principal

Exemples d'API

Utilisez cette page pour copier la forme de requête appropriée pour chaque flux d'extension.

  • Les exemples de pont Iframe utilisent des actions postMessage telles que session.get, state.get et files.get.
  • Les exemples privilégiés utilisent votre propre backend d'extension avec Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY et x-chastify-main-token.
  • Ne jamais envoyer une clé API développeur à un code iframe ou navigateur.
info

Le pont iframe sert à initialiser l'interface utilisateur et à gérer les opérations de session à faible risque. Utilisez-le pour lire le contexte, stocker l'état géré par l'extension et résoudre les URL de fichiers.

attention

Le code iframe du navigateur est contrôlé par l'utilisateur. N'utilisez pas les requêtes de pont iframe pour appliquer/supprimer du temps, effectuer des tâches, lever les blocages de déverrouillage, télécharger/supprimer des fichiers d'exécution, envoyer des notifications, écrire des journaux ou commander des appareils.

Format de demande et de réponse

Les actions sécurisées du pont iframe sont envoyées dans cette enveloppe de requête de pont :

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

Et vous recevez :

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

Si ok est false, vérifiez error.code et error.message.

Les exemples côté serveur utilisent des requêtes HTTPS classiques depuis votre serveur. Elles ne transitent pas par le pont iframe.

Session et contexte

session.get

Utilisez ceci en premier dans presque tous les flux d'extension.

Ce que cela fait :

  • Vérifie que votre connexion pont fonctionne.
  • Renvoie le contexte de session (état de verrouillage, rôle, configuration de l'extension, capacités).
  • Renvoie les informations de capacité du périphérique que votre interface utilisateur peut utiliser avant de demander à votre serveur d'appeler device.command.

À quoi ça sert :

  • Initialisation de votre interface utilisateur.
  • Activation/désactivation des fonctionnalités en fonction du rôle et des autorisations.
  • Vérification des commandes prises en charge par le périphérique avant le rendu des commandes du périphérique.

Exemple de charge utile d'action :

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

Exemple d'extrait de réponse session.get (données de verrouillage d'exécution) :

{
"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
}
}
}

Remarques relatives à la confidentialité :

  • Les codes wearerLastSeenTimestamp et keyholderLastSeenTimestamp ne sont renvoyés que si l'utilisateur possède le code showOnlineStatus !== false.
  • Si la visibilité du statut en ligne est désactivée, ces champs sont null.

Flux d'extension du backend

Ces exemples illustrent le modèle de production sûr :

  1. L'iframe lit mainToken et sessionId à partir de la charge utile de hachage.
  2. L'iframe les envoie à votre serveur.
  3. Votre système dorsal valide l'état de votre jeu/tâche/activité.
  4. Votre serveur appelle Chastify avec une clé API développeur à portée d'application plus x-chastify-main-token.

N’utilisez pas sessionId seul comme méthode d’authentification. Considérez mainToken comme un contexte de lancement visible par le navigateur et votre clé API développeur comme un secret réservé au serveur.

attention

Votre serveur doit valider l'état de son propre jeu/de sa propre tâche avant d'appeler Chastify. La transmission de mainToken et sessionId depuis l'iframe identifie uniquement la session d'extension ouverte ; elle ne prouve pas que l'utilisateur a relevé un défi.

Récupérer le contexte de session en toute sécurité

Votre iframe peut utiliser le pont sécurisé session.get pour l'initialisation de l'interface utilisateur. Si votre serveur a besoin du même contexte avant d'appliquer une action privilégiée, récupérez-le auprès de votre serveur avec les deux identifiants.

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

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

Appliquer l'heure depuis un backend d'extension

Le navigateur peut demander à votre serveur d'appliquer une récompense ou une sanction, mais il ne doit pas décider de la validité de cette action. La vérification côté serveur doit être effectuée en premier lieu.

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

Utilisez add_time pour les punitions et remove_time pour les récompenses.

Terminaison du jeu vérifiée par le serveur

Pour les jeux comme Simon Says, ne vous fiez pas à une victoire annoncée par le client. Créez la séquence d'exécution côté serveur, stockez la séquence attendue ou son hachage, et vérifiez les données saisies avant d'appeler Chastify.

Un jeu de mémoire accessible via un navigateur ne permet pas de prouver l'honnêteté d'un joueur, car le navigateur doit recevoir la séquence pour afficher le jeu. La vérification côté serveur empêche néanmoins la falsification des mutations Chastify et permet à votre système de gérer les identifiants de partie, l'expiration, la difficulté, la cadence, le système de points et la protection contre la relecture avant l'attribution des récompenses ou des pénalités.

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

Le schéma de stockage exact des exécutions vous appartient. verifySessionLaunch doit appeler Chastify avec votre clé API développeur (à portée de l'application) et x-chastify-main-token avant de faire confiance à sessionId. Il est important de noter que les modifications de Chastify ne sont effectuées qu'après vérification du lancement et du résultat par votre système dorsal.

Exigences planifiées

Votre système dorsal gère les planifications, les contrôles de cadence et la validation des preuves. Utilisez Chastify uniquement pour enregistrer les progrès fiables ou mettre à jour les éléments bloquants une fois que votre système dorsal a déterminé que la condition est remplie.

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

Les API de session des extensions installées nécessitent actuellement un jeton de lancement iframe valide au format x-chastify-main-token. Ces jetons expirent après 10 heures. Pour les tâches planifiées s'exécutant après cette expiration, stockez votre propre preuve d'exécution en attente et soumettez la progression lors du prochain lancement valide de l'extension, ou utilisez un flux serveur interne conçu pour les opérations en arrière-plan automatisées.

attention

Ne considérez pas les tâches planifiées comme fiables simplement parce qu'elles s'exécutent sur votre serveur. La tâche nécessite toujours une preuve côté serveur, des contrôles de cadence, une protection contre la relecture et un chemin d'autorisation Chastify valide avant l'enregistrement de la progression des exigences.

Notifications

notifications.custom

Utilisez cette fonction depuis le backend de votre extension pour envoyer une notification personnalisée à l'utilisateur, au détenteur de la clé ou aux deux.

Point final :

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

Exemple de corps :

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

Remarques :

  • showPageOverlay est par défaut égal à false.
  • target est par défaut égal à wearer.
  • L'API crée un type de notification extension_app_message.

État d'extension

L'état de l'extension correspond à vos données JSON appartenant à l'extension pour la session de verrouillage actuelle. Les modifications d'état sont réservées au serveur et nécessitent une clé API développeur spécifique à l'application ainsi que la session mainToken. Les iframes peuvent lire l'état avec state.get, mais ne peuvent pas y modifier directement l'état.

state.put

Ce que cela fait :

  • Remplace l'objet d'état entier par le nouvel objet data.
  • Nécessite des identifiants d'accès au serveur.

Quand l'utiliser :

  • Sauvegarde initiale.
  • Écrasement complet alors que vous disposez déjà du nouvel état complet.

Exemple:

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"
}
}'

Notes de terrain :

  • payload.data : toute valeur/objet JSON valide dont votre extension a besoin.
  • Avoid Mongo-unsafe key names ($ prefix or keys containing .).
  • L'état est limité à la session et à la taille par le stateMaxBytes de l'application d'extension, avec une valeur par défaut de 64 KiB.
  • Stockez les identifiants de fichiers dans l'état, et non les fichiers binaires ou les URL signées. Utilisez files.get pour actualiser les URL signées avant le rendu multimédia.

state.patch

Ce que cela fait :

  • Applique un correctif de fusion JSON à l'état existant.
  • Seules les clés changées doivent être envoyées.
  • Nécessite des identifiants d'accès au serveur.

Quand l'utiliser :

  • Mises à jour incrémentales issues des interactions des utilisateurs.
  • Mise à jour d'un seul champ sans tout renvoyer.

Exemple:

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

Ce que cela fait :

  • Lit l'état actuel de l'extension.
  • Disponible via le pont iframe.

Quand l'utiliser :

  • Au chargement de l'iframe.
  • Après l'écriture, si vous souhaitez resynchroniser l'interface utilisateur locale.

Exemple:

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

Stockage de fichiers

Utilisez files.* pour les médias binaires tels que les images de puzzle, les aperçus générés ou les photos de défis. Stockez le code file.id renvoyé dans l'état côté serveur ou dans votre propre base de données. Effectuez le rendu avec file.signedUrl, puis actualisez l'URL signée avec files.get lors du chargement ultérieur de l'iframe.

Les écrans de configuration qui s'affichent avant la fin d'une session utilisent des chargements intermédiaires au lieu de files.upload. Les fichiers intermédiaires sont temporaires jusqu'à ce que la configuration de l'extension soit enregistrée avec les références provider: "chastify_storage" et fileId. Chastify revendique automatiquement ces fichiers lors de l'enregistrement du verrouillage ou du modèle ; aucun appel de revendication supplémentaire n'est nécessaire côté navigateur.

Exemple de séparation état/fichier depuis votre 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"
}
}'

Ensuite, résolvez l'image avant l'affichage :

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

Vérifiez ceci avant d'afficher les commandes de téléchargement.

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

Exécution files.upload

Le chargement de fichiers à l'exécution modifie les données de session de l'extension ; il ne s'agit donc pas d'une commande de pont iframe. Chargez les fichiers d'exécution depuis votre serveur avec une clé API développeur spécifique à l'application et le code x-chastify-main-token.

files.get

Actualiser une URL R2 signée à partir d'un identifiant de fichier stable.

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

files.list

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

L'environnement d'exécution files.delete est également réservé au backend pour la même raison que le téléchargement.

Actions de verrouillage

Ajouter du temps

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Ajoute ou retire du temps au compte à rebours du verrouillage en fonction de deltaSeconds.

Quand l'utiliser :

  • Boutons de récompense/pénalité.
  • Résultats de la partie (une victoire ajoute du temps, une défaite en retire).

Exemple:

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

Notes de terrain :

  • La valeur positive ajoute du temps.
  • Une valeur négative supprime du temps (si les règles du serveur le permettent).

Geler

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Les gels bloquent la progression pendant une durée déterminée.

Quand l'utiliser :

  • Mécanismes de temps de recharge.
  • Points de contrôle de récompense.

Exemple:

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

Vous pouvez également l'appeler sans durationSeconds :

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

Notes de terrain :

  • durationSeconds est facultatif.
  • Si cette valeur est omise, la valeur par défaut actuelle est de 3600 secondes (1 heure).
  • La plage acceptée est de 60 à 86400 secondes.

Dégeler

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Met fin au gel actif et rétablit le comportement normal du minuteur.

Quand l'utiliser :

  • Remplacement manuel dans les flux de travail d'extension.
  • Commandes « Annuler le gel ».

Exemple:

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

Pilori

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Démarre une période de mise au pilori pour la session de verrouillage active.

Quand l'utiliser :

  • Mécanismes de pénalité après échec de tâches/défis.
  • Flux d'escalade qui restreignent temporairement l'interaction avec les verrous.

Exemple:

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

Notes de terrain :

  • name doit être pillory.
  • params.durationSeconds est requis.
  • params.reason est facultatif.
  • Nécessite l'activation du pilori dans la configuration de la session.

Pilori final

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Met fin immédiatement à la séance de pilori en cours.

Exemple:

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

Notes de terrain :

  • name doit être pillory.end.
  • Échec avec le code pillory_not_active si le cadenas n'est pas actuellement au pilori.

Attribuer une tâche

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Crée une exécution de tâche active pour l'utilisateur à partir de la logique d'extension.
  • Peut remplacer une tâche déjà en cours d'exécution.

Exemple:

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

Notes de terrain :

  • taskText est requis.
  • points est optionnel et limité côté serveur.
  • verificationRequired est par défaut égal à false.
  • durationSeconds est facultatif (0 signifie qu'aucune minuterie n'est requise).
  • Nécessite l'activation du module Tâches sur le verrou.

Démarrer le minuteur de tâche

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Démarre ou redémarre le compte à rebours de la tâche chronométrée actuellement active.

Exemple:

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

Notes de terrain :

  • Nécessite l'exécution d'une tâche active.
  • Échec si aucune durée n'est configurée pour la tâche en cours.

Tâche terminée

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Indique si la tâche en cours d'exécution est terminée ou a échoué.

Exemple (succès) :

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

Exemple (échec) :

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

Déclencher l'ouverture temporaire

Point de terminaison : POST /api/extensions/sessions/:sessionId/action

Ce que cela fait :

  • Démarre une fenêtre d'ouverture temporaire pour l'hygiène à partir de la logique d'extension.

Exemple:

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

Notes de terrain :

  • Nécessite l'activation de l'ouverture hygiénique sur la serrure.
  • Échoue si une opération d'hygiène est déjà en cours.
  • durationSeconds est facultatif ; la valeur par défaut du verrou est utilisée lorsqu'il est omis.

Métadonnées et actions d'accueil

metadata.patch

Ce que cela fait :

  • Stocke les métadonnées de l'extension utilisées par l'interface utilisateur de la page de verrouillage.
  • Prend en charge unlockBlockers et homeActions.
  • Prend en charge homeActions[].intent pour le comportement des liens profonds lors de l'ouverture de l'extension.

Quand l'utiliser :

  • Appliquez les conditions de déverrouillage de session verrouillée propres à votre extension.
  • Ajoutez des actions rapides sur la page de verrouillage qui ouvrent votre extension avec une intention précise.
  • Dirigez les utilisateurs directement vers un écran/flux de travail spécifique lorsqu'ils cliquent sur une action de retour à la page d'accueil.

Point final :

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

Exemple de corps :

{
"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"
}
}
}
]
}

Notes de terrain :

  • unlockBlockers : liste des bloqueurs de déverrouillage de session de verrouillage actifs de votre extension.
  • Vous pouvez inclure plusieurs bloqueurs à la fois (jusqu'à 25), un par condition non satisfaite.
  • Le déverrouillage reste bloqué tant qu'un bloqueur est présent sur les extensions activées.
  • Chastify regroupe les bloqueurs de toutes les extensions pour la session de verrouillage.
  • Votre extension ne devrait ajouter/supprimer que ses propres bloqueurs dans ses propres métadonnées.
  • Ne supprimez pas les éléments bloquants provenant d'autres extensions ; effacez votre tableau uniquement lorsque vos propres conditions sont remplies.
  • homeActions : boutons d’action rapide affichés dans l’interface de verrouillage.
  • homeActions[].slug : identifiant stable pour votre action.
  • homeActions[].title : étiquette destinée à l’utilisateur.
  • homeActions[].description : texte d'aide facultatif.
  • homeActions[].intent : instruction de lien profond optionnelle transmise à votre extension lors de son ouverture.
  • Dans l'interface utilisateur de la carte d'extension, ces actions sont affichées sous forme de menu/liste par titre d'action (indexé en interne par slug).
  • Lorsqu'un utilisateur clique sur un élément, Chastify ouvre l'extension et transmet :
    • homeActionSlug
    • homeAction (objet d'action sélectionné)
    • intent (objet d'intention normalisé) Ainsi, votre extension pourra immédiatement rediriger vers la vue/action appropriée au chargement.

Intentions : exemple d'application pour développeur

Utilisez ce modèle dans votre application d'extension pour réagir aux intentions de clic sur le menu lors du chargement.

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

Ce que fait cet exemple :

  • Lit homeActionSlug + intent à partir de la charge utile de hachage de l'iframe.
  • Chaque clic n'est traité qu'une seule fois par verrou/élément d'action.
  • Itinéraires vers un panneau lorsque l'intention est open_panel.
  • Pour les types d'intentions personnalisés, un message d'intention est affiché.

Commande de périphérique

device.command

Ce que cela fait :

  • Envoie une commande de contrôle de périphérique (si prise en charge pour cette session/ce périphérique).
  • Permet à votre extension de déclencher des actions de choc/vibration standardisées via Chastify.

Quand l'utiliser :

  • Déclenchement des commandes de choc/vibration prises en charge à partir de la logique d'extension.
  • Création de fonctionnalités d'extension interactives (jeux, punitions, récompenses, routines).

Point final :

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

Exemple de corps :

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

Commandes courantes :

  • shock.start avec les paramètres : { durationSeconds, intensityPct, message? }
  • shock.stop
  • vibration.start avec les paramètres : { durationSeconds, intensityPct, frequencyPct?, message? }
  • vibration.stop
  • all.stop
  • shock.random.set avec paramètres : { enabled, minIntensityPct?, maxIntensityPct?, message? } (Lockink AA-A1012 uniquement)
  • shock.berserk.set avec paramètres : { enabled, message? } (Lockink AA-A1012 uniquement)

Débit recommandé :

  1. Appelez session.get au chargement.
  2. Lire les capacités du périphérique à partir de deviceControl.supportedCommands.
  3. Afficher uniquement les commandes compatibles.
  4. Envoyez device.command avec des valeurs validées.
  5. Actualisez ou faites confiance aux indicateurs de réponse active pour l'état de l'interface utilisateur en direct.

Instructions relatives aux paramètres :

  • durationSeconds : le serveur limite les risques (la durée maximale actuelle est de 300 secondes).
  • intensityPct : valeur de pourcentage attendue (entrée de style 1-100, limitée côté serveur).
  • frequencyPct : valeur de pourcentage attendue (1-100) pour les flux de vibration (côté serveur bloqué).
  • Pour le mode aléatoire, assurez-vous que minIntensityPct <= maxIntensityPct.

Exemple de modèle d'interface utilisateur plus sûr :

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

Important:

  • Appelez d'abord session.get et consultez les commandes prises en charge.
  • Afficher uniquement les commandes compatibles avec la session en cours.
  • Validez les entrées de l'utilisateur avant d'envoyer les paramètres de commande.
  • device.command requiert l'autorisation d'écriture (locks:write) pour la session d'extension.
  • Gérer les erreurs de pont/serveur avec élégance (insufficient_scope, commande non prise en charge, erreurs de validation).

Exigences de prolongation

Les exigences d'extension permettent à une extension de définir des règles d'achèvement récurrentes qui s'affichent dans l'interface utilisateur de progression actuelle du verrou et peuvent appliquer une pénalité lorsque le porteur manque une fenêtre.

Utilisez ceci pour les activités d'extension de confiance du serveur, telles que :

  • Résolvez 1 puzzle par jour.
  • Effectuez 3 enregistrements tous les 2 jours.
  • Effectuez 5 actions de prolongement par semaine.

Forme de configuration

L'exigence est stockée dans la configuration de session d'extension sous extensionRequirements :

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

Champs :

  • enabled : active/désactive l’exigence récurrente.
  • metric : nom du compteur stable. Utilisez des noms simples tels que completion, win ou verification.
  • requiredCount : nombre d’événements de confiance qui doivent se produire dans la fenêtre.
  • cadence.every : taille de l’intervalle.
  • cadence.unit : day ou week.
  • Le fuseau horaire de la fenêtre est déterminé par Chastify à partir du fuseau horaire configuré par l'utilisateur (User.timezone). La configuration de l'extension ne doit pas définir de fuseau horaire en dur.
  • punishment.type : none, add_time, freeze ou pillory.
  • punishment.seconds : durée de la punition pour add_time, freeze ou pillory.
  • punishment.reason : motif d’audit/de débogage facultatif.

Modèle de progrès

L'état d'avancement des exigences est un état fiable côté serveur, et non un état local à l'iframe.

N’utilisez pas state.patch / state.put pour marquer une exigence comme terminée. Ces actions correspondent à des modifications d’état générales côté serveur et ne constituent pas des API de suivi de la progression des exigences. La progression des exigences ne doit être enregistrée qu’après validation par le serveur de l’événement concerné.

Par exemple:

  • Une extension de puzzle ne devrait enregistrer la progression qu'après que le serveur a validé l'exécution et l'état final du puzzle signé.
  • Une extension de vérification ne devrait enregistrer la progression qu'une fois que le serveur a accepté la preuve soumise.
  • Une extension de jeu ne devrait enregistrer la progression qu'après que le serveur ait validé le résultat du jeu ou l'événement de fin de jeu confirmé.

Comportement d'exécution

Une fois configuré :

  • Le tableau de bord de verrouillage peut afficher l'état d'avancement dans la section « Progression du jour ».
  • Chastify suit la progression par session d'extension et par fenêtre de cadence.
  • La tâche planifiée évalue les fenêtres terminées et applique la sanction configurée une fois par fenêtre manquée.
  • Les sanctions sont idempotentes par fenêtre, les nouvelles tentatives n'entraînent donc pas de pénalités identiques.
  1. Configurez extensionRequirements dans l'interface utilisateur de configuration de l'extension.
  2. Au démarrage en cours d'exécution, appelez session.get et lisez la configuration active.
  3. Terminez l'activité d'extension dans l'interface utilisateur.
  4. Envoyer la confirmation à une route backend de confiance pour cette extension.
  5. Laissez le système dorsal valider l'événement et enregistrer la progression des exigences.
  6. Actualisez l'interface utilisateur locale avec session.get / state.get une fois l'opération terminée.

Important:

  • Considérez state.* comme un espace de stockage appartenant exclusivement à l'extension. Utilisez des API dédiées et fiables pour la progression, les tentatives, les récompenses et les sanctions.
  • Ne vous fiez pas aux indicateurs de complétion réservés au client pour les exigences.
  • Veillez à ce que les noms metric restent stables ; toute modification de la métrique entraîne un comptage dans un compartiment différent.
  • Chastify utilise le fuseau horaire configuré par l'utilisateur pour les fenêtres de cadence. Si aucun fuseau horaire n'est disponible pour l'utilisateur, le serveur utilise UTC par défaut.
  • Les sanctions doivent être limitées et explicables dans les journaux d'audit.
  1. Appelez session.get au démarrage.
  2. Lire l'état avec state.get.
  3. Effectuez des écritures d'état depuis votre backend avec PUT/PATCH /state lorsque nécessaire.
  4. N’exécutez les actions de verrouillage/périphérique que lorsqu’elles sont prises en charge et visibles dans l’interface utilisateur.
  5. Pour les activités basées sur des exigences, signalez leur achèvement à un système dorsal de confiance.
  6. Actualiser la vue locale après les écritures/actions importantes.