본문으로 건너뛰기

확장 파일 저장소

확장 파일 저장소를 사용하면 활성화된 확장 프로그램이 잠금 확장 세션에 속하는 이미지 파일을 업로드할 수 있습니다.

예를 들어 확장 프로그램 상태 JSON만으로는 충분하지 않을 때 사용하세요.

  • 퍼즐 이미지
  • 생성된 미리보기
  • 챌린지 사진
  • 잠금/세션과 함께 정리해야 하는 확장 프로그램별 미디어

이 저장소는 state.*와 별개입니다. 작은 JSON 데이터는 state.*를 사용하십시오. 파일 저장소는 바이너리 미디어에만 사용하십시오.

상태 엔드포인트와의 관계

확장 상태는 세션 범위의 작은 JSON 데이터에 사용됩니다. iframe에서 REST API를 직접 호출하는 대신 bridge read 명령을 사용하세요.

state.get

해당 브리지 명령은 다음으로 연결됩니다.

GET /api/extensions/sessions/:sessionId/state

상태를 기록하기 위한 백엔드 직접 호출은 설치된 확장 프로그램 API 인증 모델을 사용합니다.

Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH

개발자 API 키를 iframe/브라우저 코드에 전송하지 마십시오.

예를 들어, 영구적인 참조 및 UI 데이터에는 백엔드에서 작성된 상태를 사용하세요.

{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}

바이너리 데이터, base64 이미지, 브라우저의 blob: URL 또는 signedUrl의 장기 복사본을 상태에 저장하지 마십시오. 서명된 URL은 만료되므로 이미지가 렌더링될 때 files.get로 새로 고쳐야 합니다. 상태 쓰기 크기는 확장 프로그램 앱의 stateMaxBytes 설정에 따라 제한되며 기본값은 64KiB입니다.

저장 모델

Chastify는 확장 파일을 Chastify가 관리하는 R2 스토리지에 저장합니다.

업로드된 각 파일은 다음과 같은 방식으로 추적됩니다.

  • scope: "extension"
  • appId
  • 런타임/세션 파일에 대한 sessionIdlockId
  • 저장된 잠금 템플릿에 의해 점유된 파일에 대한 templateId 코드입니다.
  • 아직 소유권이 주장되지 않은 설정 업로드에 대한 staged, draftIdexpiresAt 코드입니다.
  • extensionKey
  • purpose

관리자 게이트 및 할당량

스테이징 엔드포인트 설정

확장 프로그램 세션이 시작되기 전에 설치 UI가 실행되는 경우에만 설치 준비 단계를 사용하십시오.

이는 임시 파일 업로드 흐름입니다. 사용자가 파일을 업로드한 후 모달 창을 닫거나 확장 프로그램을 비활성화하기 전에 잠금/세션을 생성할 수 있도록 설정/구성 화면에 사용됩니다. 임시로 저장된 파일은 해당 파일을 참조하는 확장 프로그램 구성이 저장될 때까지 영구적으로 유지되지 않습니다.

설정 UI는 업로드 컨트롤을 표시하기 전에 가용성과 현재 단계적 사용량을 확인할 수 있습니다.

GET /api/extensions/apps/:appId/files/capabilities

응답에는 해당 확장 앱에서 현재 사용자의 만료되지 않은 스테이징 파일에 대한 stagedQuota가 포함됩니다.

{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"supportsStagedSetupFiles": true,
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
POST /api/extensions/apps/:appId/files/stage
Content-Type: multipart/form-data

양식 입력란:

  • file: 필수 이미지 파일
  • purpose: jigsaw-config-image와 같은 선택적 약식 식별자
  • draftId: 선택 사항인 설정 초안 ID입니다. 하나의 설정 모달이 열려 있는 동안에는 동일한 값을 재사용합니다.

응답에는 안정적인 file.id와 즉시 미리 보기를 위한 수명이 짧은 서명된 URL이 포함되어 있습니다.

예시 응답:

{
"file": {
"id": "file_record_id",
"signedUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"publicUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"urlExpiresAt": "2026-05-28T12:10:00.000Z",
"sizeBytes": 123456,
"originalName": "puzzle.jpg",
"mimeType": "image/webp",
"purpose": "jigsaw-config-image",
"uploadedAt": "2026-05-28T12:00:00.000Z"
},
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}

페이지 새로 고침 후 설정 업로드를 복원하려면 현재 사용자와 확장 프로그램 앱에 대해 준비된 파일 목록을 확인하세요.

GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image

선택적 쿼리 매개변수:

  • purpose: 특정 설정 사용 사례에 대해 준비된 파일만 반환합니다.
  • draftId: 알려진 설정 초안의 파일만 반환합니다.

draftId를 생략하면 Chastify는 현재 사용자의 해당 앱에 대해 만료되지 않은 임시 파일을 반환합니다. 이는 설정 UI를 위한 의도적인 기능입니다. 사용자가 파일을 업로드하거나 페이지를 새로고침하거나 기기를 변경하더라도 설정 화면에서 잠금/템플릿이 저장되기 전에 임시로 업로드된 파일을 복원할 수 있습니다.

스테이징 목록 응답에는 현재 사용자의 해당 앱에 대한 만료되지 않은 스테이징 사용량을 보고하는 stagedQuota도 포함됩니다.

{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}

설치 UI에서 준비된 파일 하나를 새로 고치거나 삭제할 수도 있습니다.

GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId

조작된 파일 나중에 주장하기

브라우저 측에서 별도의 "클레임" 엔드포인트는 없습니다. 스테이징된 파일은 Chastify가 스테이징된 파일을 참조하는 잠금 또는 템플릿 확장 구성 정보를 저장할 때 자동으로 클레임됩니다.

해당 경로는 잠금/템플릿 문서에 ID가 부여된 후 스테이징된 파일을 확보하고, 확보된 파일 참조를 사용하여 확장 프로그램 구성을 다시 저장합니다. 확보에 실패하면 새로 생성된 잠금/템플릿이 롤백되어 스테이징된 파일이 손상된 구성에 연결된 상태로 남지 않습니다.

공유 템플릿 참고: 승인된 공유 잠금은 복제된 활성 잠금에 원본 템플릿 ID를 유지합니다. 따라서 템플릿에 의해 점유된 파일은 복제된 잠금이 해당 템플릿을 참조하는 동안 유지됩니다. 원본 템플릿이 삭제되면 Chastify는 삭제된 템플릿을 참조하는 마지막 복제된 잠금이 삭제되거나 보관될 때까지 확장 파일 삭제를 지연합니다.

스테이징된 파일을 사용 가능하게 하려면 설치 UI에서 제출하는 확장 프로그램 구성에 다음과 같은 참조를 저장하세요.

{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}

저장 시 Chastify는 유효한 fileId를 가진 provider: "chastify_storage" 레코드를 찾기 위해 구성을 스캔한 다음 다음 작업을 수행합니다.

  • 해당 파일이 현재 사용자와 확장 프로그램 앱에 속하는지 확인합니다.
  • 만료된 스테이징 파일을 거부합니다.
  • 다른 잠금/템플릿 컨텍스트에서 이미 점유된 파일을 거부합니다.
  • 참조된 스테이징 파일을 주장된 대로 표시합니다.
  • 소유권이 주장된 파일을 저장된 잠금 또는 템플릿에 연결합니다.
  • 설정이 저장되기 전에 임시 필드인 signedUrl, publicUrlurlExpiresAt를 제거합니다.
  • 동일한 storageDraftId에서 참조되지 않은 스테이징 파일을 삭제합니다.

저장되지 않고 방치된 스테이징 파일은 만료 후 정리 작업을 통해 삭제됩니다.

현재 사용자와 확장 프로그램 앱이 소유한 파일 ID에 대한 설정 미리 보기를 새로 고치려면 다음 단계를 따르세요.

GET /api/extensions/apps/:appId/files/:fileId

세션 엔드포인트

이러한 엔드포인트에는 일반적인 확장 세션 권한 부여 및 범위가 필요합니다.

기본 경로:

/api/extensions/sessions/:sessionId/files

역량

업로드 컨트롤을 렌더링하기 전에 이를 확인하세요.

GET /api/extensions/sessions/:sessionId/files/capabilities

locks:read가 필요합니다.

예시 응답:

{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}

파일 목록

GET /api/extensions/sessions/:sessionId/files

locks:read가 필요합니다.

예시 응답:

{
"items": [
{
"id": "file_record_id",
"signedUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"publicUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"urlExpiresAt": "2026-05-28T12:10:00.000Z",
"sizeBytes": 123456,
"originalName": "puzzle.jpg",
"mimeType": "image/webp",
"purpose": "puzzle-image",
"uploadedAt": "2026-05-28T12:00:00.000Z"
}
]
}

파일 하나를 가져오세요

확장 프로그램에 이미 파일 ID가 저장되어 있고 새로 서명된 R2 링크만 필요한 경우에 이 기능을 사용하십시오.

GET /api/extensions/sessions/:sessionId/files/:fileId

locks:read가 필요합니다.

해당 파일은 동일한 확장자, 앱, 세션 및 잠금에 속해야 합니다.

예시 응답:

{
"file": {
"id": "file_record_id",
"signedUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"publicUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"urlExpiresAt": "2026-05-28T12:10:00.000Z",
"sizeBytes": 123456,
"originalName": "puzzle.jpg",
"mimeType": "image/webp",
"purpose": "puzzle-image",
"uploadedAt": "2026-05-28T12:00:00.000Z"
}
}

파일 업로드

POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data

locks:write가 필요합니다.

양식 입력란:

  • file: 필수 이미지 파일
  • purpose: puzzle-image 또는 preview와 같은 선택적 약식 식별자

예:

curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \

예시 응답:

{
"file": {
"id": "file_record_id",
"signedUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"publicUrl": "https://chastify.<account-id>.r2.cloudflarestorage.com/extensions/abc.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=...",
"urlExpiresAt": "2026-05-28T12:10:00.000Z",
"sizeBytes": 123456,
"originalName": "puzzle.jpg",
"mimeType": "image/webp",
"purpose": "puzzle-image",
"uploadedAt": "2026-05-28T12:00:00.000Z"
}
}

일반적인 업로드 오류:

  • extension_file_storage_disabled
  • extension_file_storage_requires_r2
  • file_too_large
  • invalid_file_type
  • extension_app_storage_quota_exceeded
  • extension_session_storage_quota_exceeded
  • extension_staged_user_app_storage_quota_exceeded

파일 삭제

DELETE /api/extensions/sessions/:sessionId/files/:fileId

locks:write가 필요합니다.

해당 파일은 동일한 확장자, 앱, 세션 및 잠금에 속해야 합니다.

iframe 브리지 액션

iframe 확장 프로그램은 런타임 파일 읽기를 위해 상위 웹 브리지를 사용할 수 있습니다. 런타임 파일 업로드 및 삭제는 세션 변경 사항이므로 앱 범위의 개발자 API 키와 x-chastify-main-token를 사용하여 백엔드에서 수행해야 합니다.

const capabilities = await bridge.request("files.capabilities", {});

const refreshed = await bridge.request("files.get", {
fileId: "file_record_id"
});

image.src = refreshed.file.signedUrl;

지지 교량 작용:

  • files.capabilities -> 파일 저장소가 활성화되어 있는지, 적용되는 할당량은 무엇인지 확인합니다.
  • files.list -> 현재 확장 세션이 소유한 파일 목록
  • files.get -> 저장된 파일 ID에서 서명된 R2 URL 하나를 새로 고칩니다.

설정 iframe은 런타임 세션이 생성되기 전에 추가 브리지 이름을 사용할 수 있습니다. 설정 모드에서 files.upload는 스테이징 파일을 생성하고, files.list/files.staged.list는 현재 사용자와 앱에 대해 만료되지 않은 스테이징 파일 목록을 표시하며, files.delete는 스테이징 파일을 삭제합니다. 설정 초기화 컨텍스트에는 storageDraftId가 포함됩니다. 저장 시 동일한 초안에서 참조되지 않은 파일을 즉시 정리하도록 Chastify를 사용하려면 저장된 구성에 해당 값을 storageDraftId로 포함하십시오.

설정 files.upload는 간단한 iframe 클라이언트의 경우 dataUrl도 허용합니다.

await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);

가능하면 File 또는 Blob 형식의 업로드를 권장합니다. dataUrl는 base64 페이로드가 메모리에서 더 큰 용량을 차지하므로 생성된 이미지 크기가 작은 경우에만 사용하십시오.

안정 파일 식별자는 id입니다.

파일을 표시하거나 다운로드하려면 signedUrl를 사용하십시오. 서명된 링크는 Chastify에서 생성된 수명이 짧은 R2 GetObject URL입니다. publicUrl는 호환성 별칭으로 유지되며 현재 동일한 서명된 URL을 포함합니다.

서명된 링크가 만료되면 저장된 파일 하나에 대한 새 링크를 받으려면 GET /api/extensions/sessions/:sessionId/files/:fileId를 호출하고, 모든 세션 파일 링크를 새로 고치려면 GET /api/extensions/sessions/:sessionId/files를 호출하십시오.

정리 라이프사이클

확장 파일은 BullMQ 기반의 정리 큐를 통해 정리됩니다.

청소는 다음과 같은 경우에 예정되어 있습니다.

  • 확장 앱이 삭제되었습니다.
  • 잠금/세션 연장 문서는 잠금 제거/아카이브 정리 중에 삭제됩니다.
  • 세션 파일 엔드포인트를 통해 파일이 명시적으로 삭제되었습니다.

큐는 비용이 많이 드는 R2 삭제 및 데이터베이스 정리 작업을 요청 경로에서 제외합니다. 큐에 요청을 넣는 데 실패하면 서버는 요청 컨텍스트가 존재하는 경우 응답 후 프로세스 내 정리를 수행하거나, 하위 수준 수명 주기 정리에서 최선을 다해 직접 정리를 수행합니다.

연주 참고 사항

  • 업로드 UI가 표시되기 전에 기능 검사가 이루어져야 합니다.
  • 업로드 용량은 파일별, 세션별, 앱별 제한이 있습니다.
  • 할당량 검사는 scope + appId, scope + sessionIdscope + lockId에 대해 색인화된 UserFile 메타데이터를 사용합니다.
  • 모든 URL을 메모리에 로드하는 대신 파일 레코드와 일치하는 스트림을 정리합니다.
  • 업로드된 래스터 이미지는 처리되어 최적화된 웹 이미지로 저장됩니다.

보안 참고 사항

  • 확장 프로그램에서 제공하는 이미지 URL을 신뢰할 수 있는 완료 증명으로 간주하지 마십시오.
  • Chastify에서 발급한 파일 레코드만 신뢰할 수 있는 확장 파일로 저장합니다.
  • 파일 레코드를 appId, sessionIdlockId에 바인딩합니다.
  • 업로드 및 삭제 시 locks:write를 강제 적용합니다.
  • MIME 유형을 검증하고 SVG를 거부합니다.
  • 일반 사용자에게 광범위한 사용을 허용하기 전에 관리자가 제어하는 ​​할당량을 유지하십시오.
  • 업로드된 파일에 의존하는 모든 워크플로에는 서버 측 유효성 검사를 사용하십시오.

직항로 vs. 교량

확장 프로그램이 Chastify 내에서 실행될 때 iframe 브리지를 사용하세요. 이렇게 하면 Chastify 인증이 상위 페이지에 유지되고 iframe에 경로 세부 정보가 노출되지 않습니다.

Chastify UI 또는 유효한 확장 세션 권한이 이미 부여된 신뢰할 수 있는 백엔드 흐름에서만 직접 세션 경로를 사용하십시오.