Przejdź do głównej zawartości

Przykłady API

Użyj tej strony, aby skopiować właściwy kształt żądania dla każdego przepływu rozszerzenia.

  • W przykładach mostów iframe używane są akcje postMessage, takie jak session.get, state.get i files.get.
  • Uprzywilejowane przykłady wykorzystują własne rozszerzenie back-end z Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY i x-chastify-main-token.
  • Nigdy nie wysyłaj klucza API dla programistów do kodu iframe lub przeglądarki.
informacja

Most iframe służy do uruchamiania interfejsu użytkownika i operacji sesyjnych o niskim ryzyku. Służy do odczytywania kontekstu, przechowywania stanu należącego do rozszerzenia i rozwiązywania adresów URL plików.

uwaga

Kod iframe przeglądarki jest kontrolowany przez użytkownika. Nie należy używać żądań mostu iframe do stosowania/usuwania czasu, wykonywania zadań, usuwania blokad odblokowujących, przesyłania/usuwania plików środowiska wykonawczego, wysyłania powiadomień, zapisywania logów ani wydawania poleceń urządzeniom.

Format żądania i odpowiedzi

Bezpieczne działania mostu iframe są wysyłane w tej kopercie żądania mostu:

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

I otrzymasz:

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

Jeśli ok jest równe false, sprawdź error.code i error.message.

Przykłady dotyczące wyłącznie zaplecza wykorzystują zwykłe żądania HTTPS z serwera. Nie są one wysyłane przez most iframe.

Sesja i kontekst

session.get

Używaj tego w pierwszej kolejności w prawie każdym przepływie rozszerzeń.

Co robi:

  • Sprawdza, czy połączenie mostowe działa.
  • Zwraca kontekst sesji (stan blokady, rolę, konfigurację rozszerzenia, możliwości).
  • Zwraca informacje o możliwościach urządzenia, z których może skorzystać interfejs użytkownika przed poproszeniem zaplecza o wywołanie device.command.

Do czego służy:

  • Uruchamianie interfejsu użytkownika.
  • Włączanie/wyłączanie funkcji na podstawie roli i uprawnień.
  • Przed renderowaniem elementów sterujących urządzeniem należy sprawdzić obsługiwane polecenia urządzenia.

Przykładowy ładunek akcji:

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

Przykładowy fragment odpowiedzi session.get (dane blokady w czasie wykonywania):

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

Uwagi dotyczące prywatności:

  • wearerLastSeenTimestamp / keyholderLastSeenTimestamp są zwracane tylko wtedy, gdy dany użytkownik ma showOnlineStatus !== false.
  • Jeśli widoczność statusu online jest wyłączona, pola te mają postać null.

Przepływy rozszerzeń zaplecza

Poniższe przykłady pokazują bezpieczny schemat produkcji:

  1. Ramka iframe odczytuje mainToken i sessionId z danych skrótu.
  2. Ramka iframe przesyła je do Twojego zaplecza.
  3. Twoje zaplecze weryfikuje stan Twojej gry/zadania/firmy.
  4. Twój serwer wywołuje Chastify z kluczem API dla programisty obejmującym aplikację oraz x-chastify-main-token.

Nie używaj sessionId jako samego uwierzytelnienia. Traktuj mainToken jako widoczny w przeglądarce kontekst uruchamiania, a klucz API dla programistów jako tajny klucz dostępny tylko dla zaplecza.

ostrzeżenie

Twój back-end musi zweryfikować stan własnej gry/zadania przed wywołaniem Chastify. Przekazanie kodów mainToken i sessionId z ramki iframe identyfikuje jedynie otwartą sesję rozszerzenia; nie dowodzi to, że użytkownik ukończył wyzwanie.

Bezpieczne pobieranie kontekstu sesji

Twoja ramka iframe może używać bezpiecznego mostu session.get do bootstrapu interfejsu użytkownika. Jeśli Twój backend potrzebuje tego samego kontekstu przed zastosowaniem uprzywilejowanej akcji, pobierz go z backendu, używając obu poświadczeń.

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

Twój back-end:

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

Zastosuj czas z zaplecza rozszerzenia

Przeglądarka może poprosić system o zastosowanie nagrody lub kary, ale nie może decydować o prawidłowości działania. Najpierw zweryfikuj działanie po stronie serwera.

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

Użyj add_time w przypadku kar i remove_time w przypadku nagród.

Ukończenie gry zweryfikowane przez serwer

W przypadku gier takich jak Simon Says nie ufaj wygranej zgłaszanej przez klienta. Utwórz polecenie uruchomienia po stronie serwera, zapisz oczekiwaną sekwencję lub jej skrót i zweryfikuj przesłane dane wejściowe przed wywołaniem funkcji Chastify.

Gra pamięciowa widoczna w przeglądarce nie może udowodnić, że człowiek grał uczciwie, ponieważ przeglądarka musi otrzymać sekwencję, aby wyrenderować grę. Weryfikacja serwera nadal zapobiega sfałszowanym mutacjom Chastify i pozwala zapleczu wymuszać identyfikatory przebiegów, wygaśnięcie, poziom trudności, rytm, punktację i ochronę powtórek przed przyznaniem nagród lub kar.

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

Dokładny schemat przechowywania danych uruchomieniowych należy do Ciebie. verifySessionLaunch powinien wywołać Chastify z kluczem API dla programisty w zakresie aplikacji i x-chastify-main-token przed zaufaniem sessionId. Ważną zasadą jest, że mutacje Chastify występują dopiero po zweryfikowaniu uruchomienia i wyniku przez zaplecze.

Zaplanowane wymagania

Twój system zaplecza odpowiada za harmonogramy, sprawdzanie rytmu i walidację dowodów. Używaj Chastify tylko do rejestrowania zaufanych postępów lub aktualizacji blokad odblokowujących po tym, jak system zaplecza uzna, że ​​wymaganie jest spełnione.

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

Obecne interfejsy API sesji zainstalowanych rozszerzeń wymagają prawidłowego tokenu uruchomienia ramki iframe w x-chastify-main-token; tokeny uruchomienia wygasają obecnie po 10 godzinach. W przypadku zaplanowanych zadań uruchamianych po wygaśnięciu tego tokenu, należy zapisać własny dowód i przesłać postęp przy kolejnym prawidłowym uruchomieniu rozszerzenia lub skorzystać z własnego/wbudowanego przepływu serwera przeznaczonego do pracy w tle bez nadzoru.

uwaga

Nie traktuj zaplanowanych zadań jako zaufanych tylko dlatego, że działają na Twoim serwerze. Zadanie nadal wymaga potwierdzenia po stronie serwera, kontroli rytmu, ochrony przed odtwarzaniem oraz prawidłowej ścieżki autoryzacji Chastify przed zarejestrowaniem postępu wymagań.

Powiadomienia

notifications.custom

Użyj tej funkcji w zapleczu rozszerzenia, aby wysłać niestandardowe powiadomienie rozszerzenia do użytkownika, posiadacza klucza lub obu stron.

Punkt końcowy:

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

Przykładowe ciało:

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

Uwagi:

  • showPageOverlay domyślnie ma wartość false.
  • target domyślnie ma wartość wearer.
  • API tworzy typ powiadomienia extension_app_message.

Stan rozszerzenia

Stan rozszerzenia to dane JSON będące własnością rozszerzenia i dotyczące bieżącej sesji blokady. Zapis stanu odbywa się wyłącznie w zapleczu i wymaga klucza API dla programisty w zakresie aplikacji oraz sesji mainToken. Ramki iframe mogą odczytywać stan za pomocą state.get, ale nie mogą zapisywać stanu bezpośrednio.

state.put

Co robi:

  • Zastępuje cały obiekt stanu nowym obiektem data.
  • Wymagane są dane uwierzytelniające zaplecza.

Kiedy stosować:

  • Początkowy zapis.
  • Pełne nadpisanie, gdy masz już całkowicie nowy stan.

Przykład:

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

Notatki terenowe:

  • payload.data: dowolna prawidłowa wartość/obiekt JSON, którego potrzebuje Twoje rozszerzenie.
  • Avoid Mongo-unsafe key names ($ prefix or keys containing .).
  • Stan jest ograniczony zakresem sesji i rozmiarem przez stateMaxBytes aplikacji rozszerzenia, domyślnie wynosi 64 KiB.
  • Przechowuj identyfikatory plików w stanie, a nie w plikach binarnych ani podpisanych adresach URL. Użyj kodu files.get, aby odświeżyć podpisane adresy URL przed renderowaniem multimediów.

state.patch

Co robi:

  • Zastosowuje poprawkę scalającą JSON do istniejącego stanu.
  • Należy wysłać tylko wymienione klucze.
  • Wymagane są dane uwierzytelniające zaplecza.

Kiedy stosować:

  • Przyrostowe aktualizacje wynikające z interakcji użytkowników.
  • Aktualizowanie jednego pola bez ponownego wysyłania całości.

Przykład:

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

Co robi:

  • Odczytuje aktualny stan rozszerzenia.
  • Dostępne poprzez most iframe.

Kiedy stosować:

  • Podczas ładowania ramki iframe.
  • Po zapisie, jeśli chcesz ponownie zsynchronizować lokalny interfejs użytkownika.

Przykład:

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

Przechowywanie plików

Użyj files.* dla multimediów binarnych, takich jak obrazki puzzli, wygenerowane podglądy lub zdjęcia z wyzwaniami. Przechowuj zwrócony kod file.id w stanie zapisanym przez backend lub we własnej bazie danych. Renderuj kodem file.signedUrl i odśwież ten podpisany adres URL kodem files.get, gdy ramka iframe zostanie załadowana później.

Ekrany instalacyjne uruchamiane przed utworzeniem blokady/sesji korzystają z przesyłania etapowego zamiast files.upload. Pliki etapowe są tymczasowe do momentu zapisania konfiguracji rozszerzenia z odniesieniami provider: "chastify_storage" i fileId. Chastify automatycznie przejmuje te pliki podczas zapisywania blokady/szablonu; nie ma oddzielnego wywołania żądania po stronie przeglądarki.

Przykład podziału stanu/pliku z poziomu zaplecza:

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

Następnie rozwiąż obraz przed wyświetleniem:

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

Sprawdź to przed wyświetleniem elementów sterujących przesyłaniem.

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

Czas wykonania files.upload

Przesyłanie plików w czasie wykonywania modyfikuje dane sesji rozszerzenia, więc nie jest to polecenie mostu iframe. Prześlij pliki w czasie wykonywania z zaplecza, używając klucza API dla programisty o zasięgu aplikacji i kodu x-chastify-main-token.

files.get

Odśwież jeden podpisany adres URL R2 ze stabilnego identyfikatora pliku.

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

files.list

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

Środowisko wykonawcze files.delete jest również dostępne tylko w trybie back-end z tego samego powodu, co przesyłanie.

Blokada akcji

Dodaj czas

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Dodaje lub usuwa czas z odliczania blokady w zależności od deltaSeconds.

Kiedy stosować:

  • Przyciski nagrody/kary.
  • Wyniki gry (zwycięstwo dodaje czas, przegrana go odejmuje).

Przykład:

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

Notatki terenowe:

  • Wartość dodatnia dodaje czasu.
  • Wartość ujemna usuwa czas (jeśli jest to dozwolone przez zasady serwera).

Zamrażać

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Zamraża postęp blokady na określony czas.

Kiedy stosować:

  • Mechanika czasu odnowienia.
  • Punkty kontrolne z nagrodami.

Przykład:

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

Można również wywołać go bez durationSeconds:

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

Notatki terenowe:

  • durationSeconds jest opcjonalny.
  • Jeżeli pominięto, bieżąca wartość domyślna wynosi 3600 sekund (1 godzina).
  • Akceptowany zakres wynosi od 60 do 86400 sekund.

Odmrozić

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Kończy aktywne zamrożenie i wznawia normalne zachowanie licznika czasu.

Kiedy stosować:

  • Ręczne nadpisywanie w przepływach pracy rozszerzeń.
  • Sterowanie „Anuluj zamrożenie”.

Przykład:

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

Pręgierz

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Rozpoczyna okres pręgierza dla aktywnej sesji blokady.

Kiedy stosować:

  • Mechanika kar po nieudanych zadaniach/wyzwaniach.
  • Przepływy eskalacji, które tymczasowo ograniczają interakcję z blokadami.

Przykład:

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

Notatki terenowe:

  • name musi być pillory.
  • params.durationSeconds jest wymagany.
  • params.reason jest opcjonalny.
  • Wymaga włączenia pręgierza w konfiguracji sesji.

Koniec pręgierza

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Natychmiast kończy bieżącą aktywną sesję pręgierza.

Przykład:

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

Notatki terenowe:

  • name musi być pillory.end.
  • Zwraca błąd pillory_not_active, jeśli zamek nie znajduje się obecnie w pręgierzu.

Przypisz zadanie

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Tworzy aktywne zadanie dla użytkownika z poziomu logiki rozszerzenia.
  • Można zastąpić już otwarte zadanie.

Przykład:

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

Notatki terenowe:

  • taskText jest wymagany.
  • points jest opcjonalny i blokowany po stronie serwera.
  • verificationRequired domyślnie ma wartość false.
  • durationSeconds jest opcjonalne (0 oznacza, że ​​nie jest wymagany żaden timer).
  • Wymaga włączonego modułu Zadania na zamku.

Uruchom licznik zadań

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Rozpoczyna lub restartuje okno odliczania dla aktualnie aktywnego zadania ograniczonego czasowo.

Przykład:

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

Notatki terenowe:

  • Wymaga uruchomienia aktywnego zadania.
  • Nie powiedzie się, jeśli bieżące zadanie nie ma skonfigurowanego czasu trwania.

Wykonaj zadanie

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Oznacza aktywne zadanie jako ukończone lub nieudane.

Przykład (sukces):

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

Przykład (niepowodzenie):

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

Wyzwalacz tymczasowego otwierania

Punkt końcowy: POST /api/extensions/sessions/:sessionId/action

Co robi:

  • Rozpoczyna tymczasowe okno otwierania higieny z poziomu logiki rozszerzenia.

Przykład:

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

Notatki terenowe:

  • Wymaga włączenia funkcji Higienicznego Otwierania w zamku.
  • Nie powiedzie się, jeśli otwarcie higieniczne jest już w toku.
  • durationSeconds jest opcjonalne; w przypadku pominięcia używana jest opcja lock default.

Metadane i działania domowe

metadata.patch

Co robi:

  • Przechowuje metadane rozszerzenia używane przez interfejs użytkownika strony blokady.
  • Obsługuje unlockBlockers i homeActions.
  • Obsługuje homeActions[].intent w celu zapewnienia głębokiego łącza podczas otwierania rozszerzenia.

Kiedy stosować:

  • Wymuś warunki blokowania i odblokowywania sesji określone przez Twoje rozszerzenie.
  • Dodaj szybkie akcje na stronie blokady, które będą celowo otwierać Twoje rozszerzenie.
  • Przekieruj użytkowników bezpośrednio do konkretnego ekranu/procesu pracy po kliknięciu akcji domowej.

Punkt końcowy:

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

Przykładowe ciało:

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

Notatki terenowe:

  • unlockBlockers: lista aktywnych blokad odblokowania sesji blokady z Twojego rozszerzenia.
  • Można dodać wiele blokad jednocześnie (maksymalnie 25), po jednej na każdy niespełniony warunek.
  • Odblokowanie pozostaje zablokowane tak długo, jak długo istnieje jakakolwiek blokada we wszystkich włączonych rozszerzeniach.
  • Chastify agreguje blokady ze wszystkich rozszerzeń dla sesji blokady.
  • Rozszerzenie powinno dodawać/usuwać blokady wyłącznie w swoich własnych metadanych.
  • Nie usuwaj blokad z innych rozszerzeń; wyczyść swoją tablicę dopiero wtedy, gdy spełnione zostaną Twoje własne warunki.
  • homeActions: przyciski szybkich akcji wyświetlane w trybie blokady.
  • homeActions[].slug: stabilny identyfikator Twojej akcji.
  • homeActions[].title: etykieta widoczna dla użytkownika.
  • homeActions[].description: opcjonalny tekst pomocniczy.
  • homeActions[].intent: opcjonalna instrukcja głębokiego łącza przekazywana do rozszerzenia podczas jego otwierania.
  • W interfejsie użytkownika karty rozszerzenia akcje te są wyświetlane jako menu/lista według tytułu akcji (wewnętrznie określonego przez ślimak).
  • Po kliknięciu przez użytkownika, Chastify otwiera rozszerzenie i przekazuje:
    • homeActionSlug
    • homeAction (wybrany obiekt akcji)
    • intent (znormalizowany obiekt intencji) dzięki czemu rozszerzenie może od razu skierować się do właściwego widoku/akcji po załadowaniu.

Intencje: przykład aplikacji dla programistów

Użyj tego wzorca w swojej aplikacji rozszerzenia, aby reagować na kliknięcia menu podczas ładowania.

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

Co robi ten przykład:

  • Odczytuje homeActionSlug + intent z danych skrótu iframe.
  • Obsługuje każde kliknięcie tylko raz na blokadę/akcję.
  • Kieruje do panelu, gdy intencją jest open_panel.
  • Wraca do wyświetlania komunikatu o intencji dla niestandardowych typów intencji.

Polecenie urządzenia

device.command

Co robi:

  • Wysyła polecenie sterujące urządzeniem (jeśli jest obsługiwane przez daną sesję/urządzenie).
  • Umożliwia Twojemu przedłużeniu wyzwalanie standardowych akcji wstrząsów/wibracji poprzez Chastify.

Kiedy stosować:

  • Wyzwalanie obsługiwanych poleceń wstrząsów/wibracji z poziomu logiki rozszerzenia.
  • Tworzenie interaktywnych funkcji rozszerzeń (gry, kary, nagrody, procedury).

Punkt końcowy:

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

Przykładowe ciało:

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

Popularne polecenia:

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

Zalecany przepływ:

  1. Wywołaj session.get podczas ładowania.
  2. Odczytaj możliwości urządzenia z deviceControl.supportedCommands.
  3. Sterowanie renderowaniem dotyczy tylko obsługiwanych poleceń.
  4. Wyślij device.command ze sprawdzonymi wartościami.
  5. Odśwież lub zaufaj flagom odpowiedzi active dla stanu interfejsu użytkownika na żywo.

Wskazówki dotyczące parametrów:

  • durationSeconds: serwer ogranicza czas do bezpiecznych limitów (maksymalna wartość bieżącej polityki wynosi 300 s).
  • intensityPct: oczekiwana wartość procentowa (dane wejściowe w stylu 1-100, ograniczone po stronie serwera).
  • frequencyPct: oczekiwana wartość procentowa (1-100) dla przepływów wibracyjnych (zaciskanych po stronie serwera).
  • W przypadku trybu losowego upewnij się, że minIntensityPct <= maxIntensityPct.

Przykład wzorca bezpieczniejszego interfejsu użytkownika:

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

Ważny:

  • Najpierw wywołaj session.get i przeczytaj obsługiwane polecenia.
  • Pokaż tylko kontrolki poleceń obsługiwanych w bieżącej sesji.
  • Przed wysłaniem parametrów polecenia należy sprawdzić poprawność danych wprowadzonych przez użytkownika.
  • device.command wymaga uprawnień zapisu (locks:write) dla sesji rozszerzenia.
  • Obsługa błędów mostu/serwera w sposób prawidłowy (insufficient_scope, nieobsługiwane polecenie, błędy walidacji).

Wymagania dotyczące rozszerzenia

Wymagania rozszerzenia pozwalają rozszerzeniu definiować powtarzające się reguły realizacji, które są wyświetlane w interfejsie użytkownika postępu zamka i mogą nakładać karę, gdy użytkownik pominie okno.

Użyj tego w przypadku działań rozszerzeń zaufanych przez serwer, takich jak:

  • Rozwiąż 1 łamigłówkę dziennie.
  • Wykonaj 3 odprawy co 2 dni.
  • Wykonaj 5 działań dodatkowych tygodniowo.

Kształt konfiguracji

Wymaganie jest zapisane w konfiguracji sesji rozszerzenia pod extensionRequirements:

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

Pola:

  • enabled: włącza/wyłącza cykliczne wymaganie.
  • metric: stabilna nazwa licznika. Użyj prostych nazw, takich jak completion, win lub verification.
  • requiredCount: ile zaufanych zdarzeń musi mieć miejsce w oknie.
  • cadence.every: rozmiar interwału.
  • cadence.unit: day lub week.
  • Strefa czasowa okna jest ustalana za pomocą kodu Chastify na podstawie kodu User.timezone skonfigurowanego przez użytkownika. Konfiguracja rozszerzenia nie powinna zapisywać strefy czasowej na stałe.
  • punishment.type: none, add_time, freeze lub pillory.
  • punishment.seconds: czas trwania kary dla add_time, freeze lub pillory.
  • punishment.reason: opcjonalny powód audytu/debugowania.

Model postępu

Postęp wymagań to zaufany stan po stronie serwera, a nie stan lokalny ramki iframe.

Nie używaj state.patch / state.put do oznaczania wymagania jako ukończonego. Te akcje to ogólne zapisy stanu tylko w zapleczu, a nie interfejsy API postępu wymagania. Postęp wymagania musi być rejestrowany dopiero po zatwierdzeniu przez serwer zdarzenia, które powinno zostać uwzględnione.

Na przykład:

  • Rozszerzenie łamigłówki powinno rejestrować postęp dopiero po sprawdzeniu przez serwer podpisanego przebiegu łamigłówki i stanu jej ukończenia.
  • Rozszerzenie weryfikacyjne powinno rejestrować postęp dopiero po zaakceptowaniu przez serwer przesłanego dowodu.
  • Rozszerzenie gry powinno rejestrować postępy dopiero po sprawdzeniu przez zaplecze wyniku gry lub zaufanego zdarzenia ukończenia.

Zachowanie w czasie wykonywania

Po skonfigurowaniu:

  • Na pulpicie blokady można zobaczyć wymagania w ramach postępu dnia.
  • Chastify śledzi postępy dla każdej sesji rozszerzenia i każdego okna kadencji.
  • Zadanie zaplanowanego wymagania ocenia ukończone okna i stosuje skonfigurowaną karę jeden raz za każde pominięte okno.
  • Kary są idempotentne dla każdego okna, więc ponowne próby nie powodują duplikowania kar.
  1. Skonfiguruj extensionRequirements w interfejsie użytkownika konfiguracji/konfiguracji rozszerzenia.
  2. Podczas uruchamiania środowiska wykonawczego wywołaj session.get i odczytaj aktywną konfigurację.
  3. Wykonaj czynność rozszerzoną w interfejsie użytkownika.
  4. Wyślij uzupełnienie do zaufanej trasy zaplecza dla tego rozszerzenia.
  5. Pozwól, aby zaplecze zatwierdziło zdarzenie i zarejestrowało postęp wymagań.
  6. Po zakończeniu odśwież lokalny interfejs użytkownika poleceniem session.get / state.get.

Ważny:

  • Traktuj state.* wyłącznie jako pamięć masową należącą do rozszerzenia. Używaj dedykowanych, zaufanych interfejsów API do śledzenia postępów, prób, nagród i kar.
  • Nie należy ufać flagom ukończenia dotyczącym wyłącznie klienta w przypadku wymagań.
  • Nazwy metric powinny być stabilne; zmiana metryki powoduje rozpoczęcie zliczania do innego przedziału.
  • Chastify używa skonfigurowanej strefy czasowej użytkownika do określania okien kadencji. Jeśli użytkownik nie ma dostępnej strefy czasowej, serwer przełącza się na UTC.
  • Utrzymuj kary ograniczone i możliwe do wyjaśnienia w dziennikach audytu.
  1. Wywołaj session.get podczas uruchamiania.
  2. Odczytaj stan za pomocą state.get.
  3. W razie potrzeby wykonaj zapis stanu z poziomu zaplecza za pomocą PUT/PATCH /state.
  4. Uruchamiaj akcje blokady/urządzenia tylko wtedy, gdy są obsługiwane i widoczne w interfejsie użytkownika.
  5. W przypadku działań popartych wymaganiami należy zgłosić ich ukończenie do zaufanego serwera zaplecza.
  6. Odśwież widok lokalny po ważnych zapisach/działaniach.