API 예제
이 페이지를 사용하여 각 확장 흐름에 맞는 요청 양식을 복사하세요.
- iframe 브리지 예제에서는
session.get,state.get,files.get와 같은postMessage액션을 사용합니다. - 권한이 있는 예제는
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY및x-chastify-main-token를 사용하여 자체 확장 백엔드를 사용합니다. - 개발자 API 키를 iframe이나 브라우저 코드에 절대 보내지 마세요.
iframe 브리지는 UI 부트스트랩 및 위험도가 낮은 세션 작업에 사용됩니다. 컨텍스트 읽기, 확장 프로그램 소유 상태 저장 및 파일 URL 확인에 활용하세요.
브라우저 iframe 코드는 사용자가 제어합니다. iframe 브리지 요청을 사용하여 시간을 적용/제거하거나, 작업을 완료하거나, 잠금 해제 차단을 해제하거나, 런타임 파일을 업로드/삭제하거나, 알림을 보내거나, 로그를 기록하거나, 장치를 제어하지 마십시오.
요청 및 응답 형식
안전한 iframe 브리지 작업은 이 브리지 요청 봉투 안에 전송됩니다.
{
"type": "chastify:ext:req", // required
"v": 1, // protocol version
"id": "req-123", // your unique request id
"nonce": "from-iframe-hash",
"action": "session.get",
"payload": {}
}
그러면 다음을 받게 됩니다:
{
"type": "chastify:ext:resp",
"v": 1,
"id": "req-123", // same id you sent
"ok": true,
"data": {}
}
ok가 false인 경우, error.code와 error.message를 확인하십시오.
백엔드 전용 예제는 서버에서 일반 HTTPS 요청을 사용합니다. 이러한 요청은 iframe 브리지를 거치지 않습니다.
세션 및 컨텍스트
session.get
거의 모든 확장 워크플로에서 이것을 먼저 사용하십시오.
이 제품의 기능:
- 브리지 연결이 정상적으로 작동하는지 확인합니다.
- 세션 컨텍스트(잠금 상태, 역할, 확장 프로그램 구성, 기능)를 반환합니다.
device.command를 호출하기 전에 UI에서 사용할 수 있는 장치 기능 정보를 반환합니다.
용도:
- UI 부트스트래핑.
- 역할 및 권한에 따라 기능을 활성화/비활성화합니다.
- 장치 제어를 렌더링하기 전에 지원되는 장치 명령을 확인합니다.
액션 페이로드 예시:
{
"action": "session.get",
"payload": {}
}
예시 session.get 응답 발췌 (런타임 잠금 데이터):
{
"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
}
}
}
개인정보 보호 관련 유의사항:
wearerLastSeenTimestamp/keyholderLastSeenTimestamp는 해당 사용자가showOnlineStatus !== false를 가지고 있는 경우에만 반환됩니다.- 온라인 상태 표시가 비활성화된 경우 이러한 필드는
null입니다.
백엔드 확장 흐름
다음 예시들은 안전한 생산 패턴을 보여줍니다.
- iframe은 해시 페이로드에서
mainToken와sessionId를 읽습니다. - iframe은 해당 데이터를 백엔드로 전송합니다.
- 백엔드에서 게임/작업/비즈니스 상태를 직접 검증합니다.
- 백엔드에서 앱 범위의 개발자 API 키와
x-chastify-main-token를 사용하여 Chastify를 호출합니다.
sessionId를 단독으로 인증에 사용하지 마십시오. mainToken는 브라우저에 표시되는 시작 컨텍스트로 취급하고, 개발자 API 키는 백엔드 전용 비밀 키로 취급하십시오.
백엔드는 Chastify를 호출하기 전에 자체 게임/작업 상태를 검증해야 합니다. iframe에서 mainToken 및 sessionId를 전달하는 것은 열린 확장 프로그램 세션만 식별할 뿐, 사용자가 과제를 완료했음을 증명하는 것은 아닙니다.
세션 컨텍스트를 안전하게 가져오기
iframe은 UI 부트스트랩을 위해 안전 브리지 session.get를 사용할 수 있습니다. 백엔드에서 권한 있는 작업을 적용하기 전에 동일한 컨텍스트가 필요한 경우, 두 자격 증명을 모두 사용하여 백엔드에서 컨텍스트를 가져오세요.
아이프레임:
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
})
});
백엔드:
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
});
});
확장 프로그램 백엔드에서 시간을 적용합니다.
브라우저는 백엔드에 보상이나 처벌을 적용하도록 요청할 수 있지만, 해당 행위의 유효성을 판단해서는 안 됩니다. 먼저 서버 측에서 행위의 유효성을 검증해야 합니다.
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());
});
처벌에는 add_time를, 보상에는 remove_time를 사용하십시오.
서버 검증 완료
사이먼 세즈와 같은 게임의 경우, 클라이언트가 보고한 승리를 신뢰하지 마십시오. 서버 측에서 실행을 생성하고 예상 순서 또는 해당 순서의 해시값을 저장한 다음, Chastify를 호출하기 전에 제출된 입력을 검증하십시오.
브라우저에서 바로 볼 수 있는 메모리 게임은 플레이어가 정직하게 플레이했는지 증명할 수 없습니다. 브라우저가 게임을 렌더링하기 위해 순서를 수신해야 하기 때문입니다. 하지만 서버 검증을 통해 위조된 Chastify 변형을 방지하고, 보상이나 처벌을 적용하기 전에 백엔드에서 실행 ID, 만료 시간, 난이도, 주기, 점수 계산, 리플레이 보호 등의 설정을 적용할 수 있습니다.
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 });
});
정확한 실행 저장 스키마는 사용자가 지정할 수 있습니다. verifySessionLaunch는 sessionId를 신뢰하기 전에 앱 범위의 개발자 API 키와 x-chastify-main-token를 사용하여 Chastify를 호출해야 합니다. 중요한 규칙은 Chastify 변경은 백엔드에서 실행 및 결과를 확인한 후에만 이루어져야 한다는 것입니다.
예정된 요구사항
일정 관리, 주기 확인 및 증명 검증은 백엔드에서 담당합니다. Chastify는 백엔드에서 요구 사항이 충족되었다고 판단한 후 신뢰할 수 있는 진행 상황을 기록하거나 잠금 해제 차단 요소를 업데이트하는 용도로만 사용하십시오.
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
})
});
}
현재 설치된 확장 프로그램 세션 API는 x-chastify-main-token 형식의 유효한 iframe 실행 토큰을 필요로 합니다. 실행 토큰은 현재 10시간 후에 만료됩니다. 토큰 만료 후 실행되는 예약 작업의 경우, 자체적으로 보류 중인 인증 정보를 저장하고 다음 유효한 확장 프로그램 실행 시 진행 상황을 제출하거나, 무인 백그라운드 작업을 위해 설계된 기본 제공 서버 흐름을 사용하십시오.
예약된 작업이 서버에서 실행된다는 이유만으로 신뢰할 수 있는 작업으로 간주하지 마십시오. 작업 진행 상황을 기록하기 전에 서버 측 검증, 실행 주기 확인, 재실행 방지 및 유효한 Chastify 인증 경로가 여전히 필요합니다.
알림
notifications.custom
이 기능을 확장 프로그램 관리자 페이지에서 사용하여 착용자, 키 소지자 또는 둘 다에게 사용자 지정 확장 프로그램 알림을 보낼 수 있습니다.
엔드포인트:
POST /api/extensions/sessions/:sessionId/notifications/custom
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
본문 예시:
{
"title": "Extension Reminder",
"message": "Your next challenge is ready.",
"showPageOverlay": false,
"target": "both"
}
참고:
showPageOverlay는 기본적으로false로 설정됩니다.target는 기본적으로wearer로 설정됩니다.- API는 알림 유형
extension_app_message를 생성합니다.
확장 상태
확장 프로그램 상태는 현재 잠금 세션에 대한 확장 프로그램 소유의 JSON 데이터입니다.
상태 쓰기는 백엔드에서만 가능하며 앱 범위의 개발자 API 키와 세션 키(mainToken)가 필요합니다. iframe은 세션 키(state.get)를 사용하여 상태를 읽을 수 있지만 직접 상태를 쓸 수는 없습니다.
state.put
이 제품의 기능:
- 기존 상태 객체 전체를 새로운
data객체로 교체합니다. - 백엔드 자격 증명이 필요합니다.
사용 시점:
- 초기 저장.
- 이미 완전히 새로운 상태가 된 경우 전체 덮어쓰기를 수행합니다.
예:
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"
}
}'
현장 기록:
payload.data: 확장 프로그램에 필요한 유효한 JSON 값/객체입니다.- Avoid Mongo-unsafe key names (
$prefix or keys containing.). - 상태는 세션 범위로 제한되며 확장 앱의
stateMaxBytes에 따라 크기가 제한되며 기본값은 64KiB입니다. - 파일 ID는 바이너리 파일이나 서명된 URL이 아닌 상태에 저장하십시오. 미디어를 렌더링하기 전에
files.get를 사용하여 서명된 URL을 새로 고치십시오.
state.patch
이 제품의 기능:
- 기존 상태에 JSON 병합 패치를 적용합니다.
- 변경된 키만 보내주시면 됩니다.
- 백엔드 자격 증명이 필요합니다.
사용 시점:
- 사용자 상호작용을 통한 점진적 업데이트.
- 전체 데이터를 다시 전송하지 않고 특정 필드만 업데이트합니다.
예:
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
이 제품의 기능:
- 현재 확장 프로그램 상태를 읽습니다.
- iframe 브리지를 통해 이용 가능합니다.
사용 시점:
- iframe 로드 시.
- 쓰기 작업 후 로컬 UI를 다시 동기화하려면 다음을 수행하십시오.
예:
{
"action": "state.get",
"payload": {}
}
파일 저장소
퍼즐 이미지, 생성된 미리보기 또는 챌린지 사진과 같은 바이너리 미디어에는 files.*를 사용하십시오. 반환된 file.id는 백엔드에 기록된 상태 또는 자체 데이터베이스에 저장하십시오. file.signedUrl를 사용하여 렌더링하고, 나중에 iframe이 로드될 때 files.get를 사용하여 서명된 URL을 새로 고치십시오.
잠금/세션이 종료되기 전에 실행되는 설정 화면은 files.upload 대신 단계별 업로드를 사용합니다. 단계별 파일은 확장 프로그램 구성이 provider: "chastify_storage" 및 fileId 참조와 함께 저장될 때까지 임시로 유지됩니다. Chastify는 잠금/템플릿 저장 시 해당 파일을 자동으로 점유하며, 브라우저 측에서 별도의 점유 호출이 필요하지 않습니다.
백엔드에서 상태/파일 분할 예시를 보여드리겠습니다.
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"
}
}'
나중에 표시하기 전에 이미지를 해결하세요.
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
업로드 컨트롤을 표시하기 전에 이것을 확인하세요.
{
"action": "files.capabilities",
"payload": {}
}
런타임 files.upload
런타임 파일 업로드는 확장 프로그램 세션 데이터를 변경하므로 iframe 브리지 명령이 아닙니다. 백엔드에서 앱 범위의 개발자 API 키와 x-chastify-main-token를 사용하여 런타임 파일을 업로드하세요.
files.get
안정적인 파일 ID에서 서명된 R2 URL 하나를 새로 고칩니다.
{
"action": "files.get",
"payload": {
"fileId": "file_record_id"
}
}
files.list
{
"action": "files.list",
"payload": {}
}
런타임 files.delete 또한 업로드와 동일한 이유로 백엔드 전용입니다.
잠금 작업
시간을 추가하세요
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
deltaSeconds에 따라 잠금 카운트다운에 시간을 추가하거나 제거합니다.
사용 시점:
- 보상/처벌 버튼.
- 게임 결과 (승리 시 시간 추가, 패배 시 시간 감소).
예:
{
"name": "add_time",
"params": 300 // +300 sec = +5 minutes
}
현장 기록:
- 양수 값은 시간을 추가합니다.
- 음수 값은 시간을 제거합니다(서버 규칙에서 허용하는 경우).
꼭 매달리게 하다
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 진행이 멈추는 현상은 일정 시간 동안 게임 진행을 차단합니다.
사용 시점:
- 쿨다운 메커니즘.
- 보상 체크포인트.
예:
{
"name": "freeze",
"params": { "durationSeconds": 120 }
}
durationSeconds 없이도 호출할 수 있습니다.
{
"name": "freeze",
"params": {}
}
현장 기록:
durationSeconds는 선택 사항입니다.- 생략할 경우 현재 기본값은
3600초(1시간)입니다. - 허용되는 범위는
60초부터86400초까지입니다.
녹이다
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 활성 동결을 종료하고 정상적인 타이머 동작을 재개합니다.
사용 시점:
- 확장 워크플로에서 수동으로 재정의합니다.
- "동결 취소" 제어.
예:
{
"name": "unfreeze",
"params": {}
}
칼
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 활성 잠금 세션에 대한 구속 기간을 시작합니다.
사용 시점:
- 과제/도전 과제 실패 후 적용되는 페널티 메커니즘.
- 잠금 상호 작용을 일시적으로 제한하는 에스컬레이션 흐름.
예:
{
"name": "pillory",
"params": {
"durationSeconds": 600,
"reason": "Missed scheduled check-in"
}
}
현장 기록:
name는pillory여야 합니다.params.durationSeconds가 필수입니다.params.reason는 선택 사항입니다.- 세션 구성에서 형틀 기능을 활성화해야 합니다.
끝 형틀
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 현재 진행 중인 형틀 고문 세션을 즉시 종료합니다.
예:
{
"name": "pillory.end",
"params": {}
}
현장 기록:
name는pillory.end여야 합니다.- 자물쇠가 현재 형틀에 걸려 있지 않으면
pillory_not_active오류가 발생합니다.
작업 할당
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 확장 로직에서 착용자를 위한 활성 작업 실행을 생성합니다.
- 이미 실행 중인 작업을 덮어쓸 수 있습니다.
예:
{
"name": "task.assign",
"params": {
"taskText": "Clean your room",
"points": 10,
"verificationRequired": true,
"durationSeconds": 1800
}
}
현장 기록:
taskText가 필수입니다.points는 선택 사항이며 서버 측에서 제한됩니다.verificationRequired는 기본적으로false로 설정됩니다.durationSeconds는 선택 사항입니다(0는 타이머가 필요하지 않음을 의미합니다).- 잠금 장치에서 작업 모듈이 활성화되어 있어야 합니다.
작업 타이머 시작
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 현재 활성화된 시간 제한 작업의 카운트다운 창을 시작하거나 다시 시작합니다.
예:
{
"name": "task.start_timer",
"params": {}
}
현장 기록:
- 활성 작업 실행이 필요합니다.
- 현재 작업에 설정된 기간이 없으면 실패합니다.
작업을 완료하세요
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 현재 실행 중인 작업의 완료 또는 실패 여부를 표시합니다.
예시 (성공 사례):
{
"name": "task.complete",
"params": {
"successful": true
}
}
예시 (실패):
{
"name": "task.complete",
"params": {
"successful": false,
"reason": "Did not finish in time"
}
}
일시적 개방을 시작하세요
엔드포인트: POST /api/extensions/sessions/:sessionId/action
이 제품의 기능:
- 확장 로직에서 임시 위생 개방 창을 시작합니다.
예:
{
"name": "hygienic_unlock.start",
"params": {
"durationSeconds": 900
}
}
현장 기록:
- 잠금 장치에 위생적 개방 기능이 활성화되어 있어야 합니다.
- 위생 점검이 이미 진행 중인 경우 실패합니다.
durationSeconds는 선택 사항이며, 생략 시 기본값이 사용됩니다.
메타데이터 및 홈 액션
metadata.patch
이 제품의 기능:
- 잠금 페이지 UI에서 사용하는 확장 프로그램 메타데이터를 저장합니다.
unlockBlockers및homeActions를 지원합니다.- 확장 프로그램이 열릴 때 딥링크 동작을 위해
homeActions[].intent를 지원합니다.
사용 시점:
- 확장 프로그램에서 소유한 세션 잠금 해제 조건을 적용합니다.
- 잠금 페이지에 빠른 실행 기능을 추가하여 의도에 따라 확장 프로그램을 열 수 있도록 하세요.
- 사용자가 홈 버튼을 클릭했을 때 특정 화면/워크플로우로 바로 이동하도록 합니다.
엔드포인트:
PATCH /api/extensions/sessions/:sessionId/metadata
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
본문 예시:
{
"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"
}
}
}
]
}
현장 기록:
unlockBlockers: 확장 프로그램에서 활성화된 잠금 세션 해제 차단기 목록입니다.- 한 번에 여러 개의 차단 요소를 포함할 수 있으며(최대 25개), 충족되지 않은 조건 하나당 하나씩 추가할 수 있습니다.
- 활성화된 확장 프로그램 전체에 차단 요소가 하나라도 존재하는 한 잠금 해제는 계속 차단된 상태로 유지됩니다.
- Chastify는 잠금 세션에 대한 모든 확장 프로그램의 차단기를 집계합니다.
- 확장 프로그램은 자체 메타데이터에서만 자체 차단기를 추가/제거해야 합니다.
- 다른 확장 프로그램의 차단 요소를 제거하지 마십시오. 자신의 조건이 충족될 때만 배열을 지우십시오.
homeActions: 잠금 화면에 표시되는 빠른 실행 버튼.homeActions[].slug: 작업에 대한 안정적인 ID입니다.homeActions[].title: 사용자에게 표시되는 레이블입니다.homeActions[].description: 선택적 도우미 텍스트입니다.homeActions[].intent: 확장 프로그램이 열릴 때 전달되는 선택적 딥링크 명령어입니다.- 확장 카드 UI에서 이러한 작업은 작업 제목(내부적으로 슬러그로 키 지정됨)별 메뉴/목록으로 표시됩니다.
- 사용자가 해당 버튼을 클릭하면 Chastify가 확장 프로그램을 열고 다음을 전달합니다.
homeActionSlughomeAction(선택된 액션 객체)intent(정규화된 인텐트 객체) 이렇게 하면 확장 프로그램이 로드될 때 올바른 뷰/액션으로 즉시 이동할 수 있습니다.
의도: 개발자 앱 예시
확장 앱 로드 시 메뉴 클릭 의도에 반응하도록 하려면 이 패턴을 사용하세요.
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");
이 예제의 기능은 다음과 같습니다.
- iframe 해시 페이로드에서
homeActionSlug+intent를 읽습니다. - 각 잠금/액션 슬러그당 한 번씩만 클릭을 처리합니다.
- 의도가
open_panel일 때 패널로 이동합니다. - 사용자 지정 인텐트 유형의 경우 인텐트 메시지를 표시하는 것으로 대체됩니다.
장치 명령
device.command
이 제품의 기능:
- (해당 세션/장치에서 지원되는 경우) 장치 제어 명령을 보냅니다.
- Chastify를 통해 확장 프로그램이 표준화된 충격/진동 동작을 작동시킬 수 있도록 합니다.
사용 시점:
- 확장 로직에서 지원되는 충격/진동 명령을 실행합니다.
- 상호작용형 확장 기능(게임, 처벌, 보상, 루틴) 구축.
엔드포인트:
POST /api/extensions/sessions/:sessionId/device-command
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
본문 예시:
{
"command": "shock.start",
"params": {
"durationSeconds": 30,
"intensityPct": 50
}
}
자주 사용하는 명령어:
shock.start매개변수:{ durationSeconds, intensityPct, message? }shock.stopvibration.start매개변수:{ durationSeconds, intensityPct, frequencyPct?, message? }vibration.stopall.stopshock.random.set매개변수:{ enabled, minIntensityPct?, maxIntensityPct?, message? }(Lockink AA-A1012만 해당)shock.berserk.set매개변수:{ enabled, message? }(Lockink AA-A1012만 해당)
권장 흐름:
- 부하 시
session.get를 호출하세요. deviceControl.supportedCommands에서 장치 기능을 읽어옵니다.- 지원되는 명령에 대한 컨트롤만 렌더링합니다.
- 유효성이 검증된 값을 포함하여
device.command를 전송하십시오. - 실시간 UI 상태를 확인하려면 응답
active플래그를 새로 고침하거나 신뢰하십시오.
매개변수 안내:
durationSeconds: 서버가 안전한 한계치로 제한됩니다(현재 정책 최대값은 300초입니다).intensityPct: 예상 백분율 값(1-100스타일 입력, 서버 측에서 클램핑됨).frequencyPct: 진동 흐름에 대한 예상 백분율 값(1-100)(클램핑된 서버 측).- 랜덤 모드를 사용하려면
minIntensityPct <= maxIntensityPct를 확인하십시오.
더 안전한 UI 패턴 예시:
// 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 }
});
}
중요한:
- 먼저
session.get를 호출하고 지원되는 명령어를 확인하세요. - 현재 세션에서 지원되는 명령에 대한 컨트롤만 표시합니다.
- 명령 매개변수를 보내기 전에 사용자 입력을 검증하십시오.
device.command를 사용하려면 확장 세션에 대한 쓰기 권한(locks:write)이 필요합니다.- 브리지/서버 오류(
insufficient_scope, 지원되지 않는 명령, 유효성 검사 오류)를 적절하게 처리하십시오.
연장 요건
확장 프로그램 요구 사항을 통해 확장 프로그램은 잠금 장치의 오늘 진행 상황 UI에 표시되는 반복 완료 규칙을 정의할 수 있으며, 착용자가 해당 기간을 놓쳤을 때 불이익을 적용할 수 있습니다.
다음과 같은 서버 신뢰 확장 활동에 사용하십시오.
- 하루에 퍼즐 하나씩 완성하세요.
- 2일에 한 번씩 3회의 체크인을 완료하세요.
- 매주 5개의 확장 활동을 완료하세요.
구성 형태
해당 요구사항은 확장 세션 구성의 extensionRequirements에 저장됩니다.
{
"extensionRequirements": {
"enabled": true,
"metric": "completion",
"requiredCount": 1,
"cadence": {
"every": 1,
"unit": "day"
},
"punishment": {
"type": "add_time",
"seconds": 900,
"reason": "Missed extension requirement"
}
}
}
전지:
enabled: 반복 요구 사항을 켜거나 끕니다.metric: 안정적인 카운터 이름입니다.completion,win또는verification와 같은 간단한 이름을 사용하십시오.requiredCount: 해당 기간 동안 몇 개의 신뢰할 수 있는 이벤트가 발생해야 합니까?cadence.every: 간격 크기.cadence.unit:day또는week.- 창 시간대는 착용자가 설정한
User.timezone를 기반으로 Chastify를 통해 결정됩니다. 확장 프로그램 설정에서 시간대를 하드코딩해서는 안 됩니다. punishment.type:none,add_time,freeze또는pillory.punishment.seconds:add_time,freeze또는pillory에 대한 처벌 기간.punishment.reason: 선택적 감사/디버그 사유.
진행 모델
요구사항 진행 상황은 iframe 로컬 상태가 아닌 신뢰할 수 있는 서버 측 상태입니다.
요구사항을 완료로 표시할 때 state.patch / state.put를 사용하지 마십시오. 이러한 작업은 백엔드 전용 일반 상태 기록 작업이며, 요구사항 진행 상황 API가 아닙니다. 요구사항 진행 상황은 서버에서 해당 이벤트의 유효성을 검사한 후에만 기록해야 합니다.
예를 들어:
- 퍼즐 확장 프로그램은 서버가 서명된 퍼즐 실행 및 완료 상태를 검증한 후에만 진행 상황을 기록해야 합니다.
- 검증 확장 프로그램은 서버가 제출된 증명을 승인한 후에만 진행 상황을 기록해야 합니다.
- 게임 확장 프로그램은 백엔드에서 게임 결과 또는 신뢰할 수 있는 완료 이벤트를 검증한 후에만 진행 상황을 기록해야 합니다.
런타임 동작
설정 시:
- 잠금 대시보드에서 오늘의 진행 상황에 필요한 사항을 확인할 수 있습니다.
- Chastify는 확장 세션별 및 주기별 진행 상황을 추적합니다.
- 예약된 요구 사항 작업은 완료된 작업 기간을 평가하고, 누락된 작업 기간마다 구성된 벌칙을 한 번씩 적용합니다.
- 처벌은 윈도우별로 동일하게 적용되므로, 재시도 시 중복된 처벌이 누적되지 않습니다.
권장 요구사항 흐름
- 확장 프로그램 설정/구성 UI에서
extensionRequirements를 구성하십시오. - 런타임 시작 시
session.get를 호출하고 활성 구성을 읽습니다. - 사용자 인터페이스에서 확장 활동을 완료하세요.
- 완료 메시지를 해당 확장 프로그램에 대한 신뢰할 수 있는 백엔드 경로로 보냅니다.
- 백엔드에서 이벤트를 검증하고 요구 사항 진행 상황을 기록하도록 합니다.
- 완료 후
session.get/state.get를 사용하여 로컬 UI를 새로 고치십시오.
중요한:
state.*는 확장 프로그램 소유 저장소로만 취급하십시오. 진행 상황, 시도 횟수, 보상 및 처벌에는 전용 신뢰할 수 있는 API를 사용하십시오.- 클라이언트 전용 완료 표시를 요구 사항 확인에 사용하지 마십시오.
metric이름은 안정적으로 유지하세요. 해당 메트릭 이름을 변경하면 다른 범주로 분류되기 시작합니다.- Chastify는 착용자가 설정한 시간대를 사용하여 운동 리듬 범위를 지정합니다. 사용자가 설정한 시간대가 없는 경우, 서버는
UTC를 사용합니다. - 처벌은 범위가 명확하고 감사 기록에 설명이 가능하도록 하십시오.
권장 조치 명령
- 시작 시
session.get를 호출합니다. state.get를 사용하여 상태를 읽습니다.- 필요할 때 백엔드에서
PUT/PATCH /state를 사용하여 상태 쓰기를 수행하십시오. - 잠금/장치 관련 작업은 지원되는 경우에만 실행하고 UI에 표시되도록 합니다.
- 요구사항 기반 활동의 경우, 완료 보고는 신뢰할 수 있는 백엔드 경로로 전송하십시오.
- 중요 쓰기/작업 후 로컬 보기를 새로 고치세요.