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 jaksession.get,state.getifiles.get. - Uprzywilejowane przykłady wykorzystują własne rozszerzenie back-end z
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEYix-chastify-main-token. - Nigdy nie wysyłaj klucza API dla programistów do kodu iframe lub przeglądarki.
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.
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/keyholderLastSeenTimestampsą zwracane tylko wtedy, gdy dany użytkownik mashowOnlineStatus !== 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:
- Ramka iframe odczytuje
mainTokenisessionIdz danych skrótu. - Ramka iframe przesyła je do Twojego zaplecza.
- Twoje zaplecze weryfikuje stan Twojej gry/zadania/firmy.
- 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.
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.
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:
showPageOverlaydomyślnie ma wartośćfalse.targetdomyś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
stateMaxBytesaplikacji 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:
durationSecondsjest opcjonalny.- Jeżeli pominięto, bieżąca wartość domyślna wynosi
3600sekund (1 godzina). - Akceptowany zakres wynosi od
60do86400sekund.
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:
namemusi byćpillory.params.durationSecondsjest wymagany.params.reasonjest 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:
namemusi 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:
taskTextjest wymagany.pointsjest opcjonalny i blokowany po stronie serwera.verificationRequireddomyślnie ma wartośćfalse.durationSecondsjest opcjonalne (0oznacza, ż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.
durationSecondsjest 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
unlockBlockersihomeActions. - Obsługuje
homeActions[].intentw 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:
homeActionSlughomeAction(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+intentz 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.startz parametrami:{ durationSeconds, intensityPct, message? }shock.stopvibration.startz parametrami:{ durationSeconds, intensityPct, frequencyPct?, message? }vibration.stopall.stopshock.random.setz parametrami:{ enabled, minIntensityPct?, maxIntensityPct?, message? }(tylko Lockink AA-A1012)shock.berserk.setz parametrami:{ enabled, message? }(tylko Lockink AA-A1012)
Zalecany przepływ:
- Wywołaj
session.getpodczas ładowania. - Odczytaj możliwości urządzenia z
deviceControl.supportedCommands. - Sterowanie renderowaniem dotyczy tylko obsługiwanych poleceń.
- Wyślij
device.commandze sprawdzonymi wartościami. - Odśwież lub zaufaj flagom odpowiedzi
activedla 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 stylu1-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.geti 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.commandwymaga 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 jakcompletion,winlubverification.requiredCount: ile zaufanych zdarzeń musi mieć miejsce w oknie.cadence.every: rozmiar interwału.cadence.unit:daylubweek.- Strefa czasowa okna jest ustalana za pomocą kodu Chastify na podstawie kodu
User.timezoneskonfigurowanego przez użytkownika. Konfiguracja rozszerzenia nie powinna zapisywać strefy czasowej na stałe. punishment.type:none,add_time,freezelubpillory.punishment.seconds: czas trwania kary dlaadd_time,freezelubpillory.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.
Zalecany przepływ wymagań
- Skonfiguruj
extensionRequirementsw interfejsie użytkownika konfiguracji/konfiguracji rozszerzenia. - Podczas uruchamiania środowiska wykonawczego wywołaj
session.geti odczytaj aktywną konfigurację. - Wykonaj czynność rozszerzoną w interfejsie użytkownika.
- Wyślij uzupełnienie do zaufanej trasy zaplecza dla tego rozszerzenia.
- Pozwól, aby zaplecze zatwierdziło zdarzenie i zarejestrowało postęp wymagań.
- 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
metricpowinny 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.
Zalecana kolejność działań
- Wywołaj
session.getpodczas uruchamiania. - Odczytaj stan za pomocą
state.get. - W razie potrzeby wykonaj zapis stanu z poziomu zaplecza za pomocą
PUT/PATCH /state. - Uruchamiaj akcje blokady/urządzenia tylko wtedy, gdy są obsługiwane i widoczne w interfejsie użytkownika.
- W przypadku działań popartych wymaganiami należy zgłosić ich ukończenie do zaufanego serwera zaplecza.
- Odśwież widok lokalny po ważnych zapisach/działaniach.