拡張子ファイルストレージ
TLDR: store the file id, render with a signed URL.
When an extension uploads a file, Chastify returns a stable id and a short-lived signedUrl. Store the id in your extension config, state, or own database.
When your extension page loads later, call bridge.request("files.get", { fileId }) or GET /api/extensions/sessions/:sessionId/files/:fileId with the stored id. Chastify verifies that the file belongs to the same extension app, session, and lock, then returns a fresh signed R2 URL.
Render the image with <img src={file.signedUrl} />. Do not store signed URLs long-term because they expire.
拡張機能のファイルストレージ機能を使用すると、有効化された拡張機能が、ロック拡張機能セッションに属する画像ファイルをアップロードできます。
拡張状態JSONだけでは不十分な場合に使用します。例:
- パズル画像
- 生成されたプレビュー
- チャレンジ写真
- ロック/セッションでクリーンアップする必要のある、拡張子固有のメディア
このストレージはstate.*とは別です。小さなJSONデータにはstate.*を使用してください。ファイルストレージはバイナリメディアのみに使用してください。
状態エンドポイントとの関係
拡張ステートは、セッションスコープの小さなJSON用です。iframeからは、RESTを直接呼び出すのではなく、ブリッジ読み取りコマンドを使用してください。
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設定によって制限され、デフォルトは64 KiBです。
ストレージモデル
Chastifyは、拡張ファイルをChastifyが管理するR2ストレージに保存します。
アップロードされた各ファイルは、以下の方法で追跡されます。
scope: "extension"appId- ランタイム/セッションファイルの場合は、
sessionIdとlockIdを使用します。 - 保存されたロックテンプレートによって要求されたファイルの場合、
templateIdとなります。 - まだ権利が主張されていないセットアップアップロード用の
staged、draftId、およびexpiresAt extensionKeypurpose
管理者ゲートと割り当て
Admin controlled
Extension file storage is disabled by default.
Admins can enable it and configure:
- maximum bytes per file
- maximum total bytes per extension app
- maximum total bytes per active extension session
- maximum staged bytes per user and extension app
- allowed MIME prefixes
The first implementation is image-focused and should use image/* uploads. SVG is not accepted by the storage service.
If storage is disabled or R2 is not configured, upload requests fail before the server reads the multipart file body.
Staged setup uploads count toward the current user's per-app staged quota until they are claimed. Claimed files count toward the per-extension app quota. Runtime files with a session id also count toward the per-session quota.
ステージングエンドポイントの設定
セットアップ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。1つのセットアップモーダルが開いている間は同じ値を再使用します。
レスポンスには、安定した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: 1 つのセットアップユースケースに対してステージングされたファイルのみを返すdraftId: 既知のセットアップドラフトからのファイルのみを返す
draftIdを省略した場合、Chastifyは、そのアプリの現在のユーザーの有効期限が切れていないステージング済みファイルを返します。これはセットアップUIにおける意図的な動作です。ユーザーがファイルをアップロードしたり、ページを再読み込みしたり、デバイスを切り替えたりしても、セットアップ画面はロック/テンプレートが保存される前にこれらの一時的なアップロードを復元できます。
ステージングリストの応答には、stagedQuotaも含まれており、これは現在のユーザーのそのアプリに対する有効期限切れになっていないステージング使用状況を報告します。
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
セットアップUIでは、ステージングされたファイルを1つ更新または削除することもできます。
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、publicUrl、およびurlExpiresAtフィールドを削除します。 - 同じ
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"
}
]
}
ファイルを1つ取得する
拡張機能に既にファイル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" \
-F "[email protected]"
回答例:
{
"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_disabledextension_file_storage_requires_r2file_too_largeinvalid_file_typeextension_app_storage_quota_exceededextension_session_storage_quota_exceededextension_staged_user_app_storage_quota_exceeded
ファイルを削除する
DELETE /api/extensions/sessions/:sessionId/files/:fileId
locks:writeが必要です。
ファイルは、同じ拡張子のアプリ、セッション、およびロックに属している必要があります。
iframeブリッジアクション
iframe拡張機能は、実行時のファイル読み取りに親Webブリッジを使用できます。実行時のファイルアップロードと削除はセッション変更であり、アプリスコープの開発者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を1つ更新する
セットアップ 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形式でのアップロードを推奨します。Base64ペイロードはメモリ使用量が大きいため、dataUrlは生成画像の小さい場合にのみ使用してください。
署名付きリンク
安定版ファイルの識別子はidです。
ファイルを表示またはダウンロードするには、signedUrlを使用してください。署名付きリンクは、Chastifyによって生成される、有効期限の短いR2 GetObject URLです。publicUrlは互換性エイリアスとして保持されており、現在も同じ署名付きURLが含まれています。
署名付きリンクの有効期限が切れた場合は、GET /api/extensions/sessions/:sessionId/files/:fileId を呼び出すと保存されているファイル 1 つ分の新しいリンクが取得され、GET /api/extensions/sessions/:sessionId/files を呼び出すとセッションファイルリンクがすべて更新されます。
クリーンアップライフサイクル
拡張ファイルは、BullMQをバックエンドとするクリーンアップキューを通じてクリーンアップされます。
清掃は以下の場合に予定されます。
- 拡張機能アプリが削除されました
- ロック/セッション拡張ドキュメントは、ロック解除/アーカイブクリーンアップ中に削除されます。
- セッションファイルエンドポイントを介してファイルが明示的に削除されます
キューを使用することで、コストのかかるR2削除処理やデータベースクリーンアップ処理をリクエストパスから除外できます。キューへの追加が失敗した場合、サーバーはリクエストコンテキストが存在する場合はレスポンス後のプロセス内クリーンアップにフォールバックするか、下位レベルのライフサイクルクリーンアップで直接ベストエフォート型のクリーンアップを実行します。
パフォーマンスノート
- アップロードUIが表示される前に、機能チェックを行う必要があります。
- アップロードには、ファイルごと、セッションごと、アプリごとの制限があります。
- クォータチェックでは、
scope + appId、scope + sessionId、およびscope + lockIdに対して、インデックス付きのUserFileメタデータを使用します。 - すべてのURLをメモリに読み込むのではなく、ファイルレコードに一致するストリームをクリーンアップします。
- アップロードされたラスター画像は処理され、最適化されたウェブ画像として保存されます。
セキュリティに関する注意事項
- 拡張機能が提供する画像URLを、信頼できる完了証明として扱わないでください。
- Chastifyが発行したファイルレコードのみを信頼できる拡張ファイルとして保存してください。
- ファイルレコードを
appId、sessionId、およびlockIdにバインドします。 - アップロードと削除には
locks:writeを強制適用する。 - MIMEタイプを検証し、SVGを拒否する。
- 管理者による制限を有効にしてから、一般ユーザーへの広範な利用を許可してください。
- アップロードされたファイルに依存するワークフローには、サーバー側検証を使用してください。
直行ルート vs 橋梁ルート
拡張機能が Chastify 内で実行される場合は、iframe ブリッジを使用してください。これにより、Chastify の認証情報が親ページに保持され、iframe にルートの詳細が公開されるのを防ぐことができます。
直接セッションルートは、ファーストパーティのChastify UI、または既に有効な拡張セッション認証を持つ信頼できるバックエンドフローからのみ使用してください。