Przechowywanie plików rozszerzeń
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.
Przechowywanie plików rozszerzeń umożliwia włączonemu rozszerzeniu przesyłanie plików graficznych należących do sesji rozszerzenia blokady.
Użyj go, gdy stan rozszerzenia JSON jest niewystarczający, na przykład:
- obrazy puzzli
- wygenerowane podglądy
- zdjęcia wyzwania
- media specyficzne dla rozszerzenia, które należy oczyścić za pomocą blokady/sesji
To miejsce przechowywania jest oddzielne od state.*. Użyj state.* dla małych danych JSON. Używaj miejsca przechowywania plików tylko dla multimediów binarnych.
Relacja do punktów końcowych stanu
Stan rozszerzenia jest przeznaczony dla małych JSON-ów o zasięgu sesji. Z poziomu ramki iframe użyj polecenia bridge read zamiast bezpośredniego wywoływania REST:
state.get
Dowództwo mostu kieruje się do:
GET /api/extensions/sessions/:sessionId/state
Bezpośrednie wywołania zaplecza służące do zapisu stanu korzystają z modelu uwierzytelniania API zainstalowanego rozszerzenia:
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Nie wysyłaj kluczy API dla programistów do kodu iframe/przeglądarki.
Użyj stanu zapisanego w zapleczu dla trwałych odniesień i danych interfejsu użytkownika, na przykład:
{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}
Nie przechowuj danych binarnych, obrazów base64, adresów URL blob: przeglądarki ani długotrwałych kopii signedUrl w stanie. Podpisane adresy URL wygasają i należy je odświeżyć za pomocą files.get podczas renderowania obrazu. Rozmiar zapisów stanu jest ograniczony przez ustawienie stateMaxBytes w aplikacji rozszerzenia, domyślnie 64 KiB.
Model pamięci masowej
Chastify przechowuje pliki rozszerzeń w pamięci masowej R2 zarządzanej przez Chastify.
Każdy przesłany plik jest śledzony za pomocą:
scope: "extension"appIdsessionIdilockIddla plików wykonawczych/sesyjnychtemplateIddla plików zgłoszonych przez zapisany szablon blokadystaged,draftIdiexpiresAtw przypadku przesłanych plików konfiguracyjnych, które nie zostały jeszcze odebraneextensionKeypurpose
Brama administracyjna i limity
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.
Konfiguracja punktów końcowych przejściowych
Użyj konfiguracji przejściowej tylko wtedy, gdy interfejs użytkownika konfiguracji zostanie uruchomiony przed utworzeniem sesji rozszerzenia.
To tymczasowy proces przesyłania. Jest on przeznaczony dla ekranów konfiguracji, gdzie użytkownik może przesłać plik, a następnie zamknąć okno modalne lub wyłączyć rozszerzenie przed utworzeniem blokady/sesji. Plik przetworzony nie jest trwały, dopóki nie zostanie przejęty przez zapisanie konfiguracji rozszerzenia, która się do niego odwołuje.
Interfejsy użytkownika konfiguracji mogą sprawdzać dostępność i bieżące wykorzystanie etapów przed renderowaniem elementów sterujących przesyłaniem:
GET /api/extensions/apps/:appId/files/capabilities
Odpowiedź zawiera kod stagedQuota dla niewygasłych plików tymczasowych bieżącego użytkownika w tej aplikacji rozszerzenia:
{
"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
Pola formularza:
file: wymagany plik obrazupurpose: opcjonalny krótki identyfikator, taki jakjigsaw-config-imagedraftId: opcjonalny identyfikator projektu konfiguracji; użyj tej samej wartości, gdy otwarte jest jedno okno konfiguracji
Odpowiedź zawiera stabilny kod file.id i krótkotrwały podpisany adres URL umożliwiający natychmiastowy podgląd.
Przykładowa odpowiedź:
{
"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
}
}
Aby przywrócić ustawienia przesyłania po odświeżeniu strony, wyświetl listę plików przygotowanych dla bieżącego użytkownika i aplikacji rozszerzenia:
GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image
Opcjonalne parametry zapytania:
purpose: zwraca tylko pliki przygotowane do instalacji w jednym przypadku użyciadraftId: zwraca tylko pliki ze znanego projektu instalacji
Jeśli parametr draftId zostanie pominięty, Chastify zwróci niewygasłe pliki przejściowe bieżącego użytkownika dla tej aplikacji. Jest to celowe w przypadku interfejsów użytkownika konfiguracji: użytkownik może przesyłać pliki, przeładować stronę lub zmienić urządzenie, a ekran konfiguracji może przywrócić te tymczasowe pliki przed zapisaniem blokady/szablonu.
Odpowiedź listy etapowej zawiera również kod stagedQuota, który raportuje bieżący czas wykorzystania etapowego danej aplikacji przez użytkownika:
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
Interfejsy użytkownika instalacji mogą również odświeżać lub usuwać jeden plik przygotowawczy:
GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId
Późniejsze zgłaszanie roszczeń do plików przygotowanych do etapu przygotowawczego
Nie ma oddzielnego punktu końcowego „deklaracji” po stronie przeglądarki. Plik przeniesiony jest deklarowany automatycznie, gdy Chastify zapisuje konfigurację blokady lub rozszerzenia szablonu, która odwołuje się do pliku przeniesionego.
Te trasy przejmują pliki przeniesione po nadaniu identyfikatora dokumentowi blokady/szablonu, a następnie ponownie zapisują konfigurację rozszerzenia z odwołaniami do przeniesionych plików. Jeśli przejęcie się nie powiedzie, nowo utworzona blokada/szablon jest wycofywana, aby pliki przeniesione nie pozostały dołączone do uszkodzonej konfiguracji.
Uwaga dotycząca współdzielonego szablonu: zaakceptowane współdzielone blokady zachowują identyfikator szablonu źródłowego dla sklonowanej aktywnej blokady. Pliki zgłoszone przez szablon są zatem zachowywane, dopóki każda sklonowana blokada nadal odwołuje się do tego szablonu. Jeśli szablon źródłowy zostanie usunięty, Chastify opóźnia usuwanie plików rozszerzeń do momentu usunięcia lub zarchiwizowania ostatniej sklonowanej blokady odwołującej się do usuniętego szablonu.
Aby plik przygotowany do przetworzenia mógł zostać uznany za możliwy do przejęcia, należy zapisać odwołanie w taki sposób w konfiguracji rozszerzenia przesłanej przez interfejs użytkownika konfiguracji:
{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}
Podczas zapisywania Chastify skanuje konfigurację w poszukiwaniu rekordów provider: "chastify_storage" z prawidłowym fileId, a następnie:
- sprawdza, czy plik należy do bieżącego użytkownika i aplikacji rozszerzenia
- odrzuca wygasłe pliki przygotowane do realizacji
- odrzuca pliki już zgłoszone przez inny kontekst blokady/szablonu
- oznacza pliki przygotowane do etapu, jak twierdzi
- wiąże zgłoszone pliki z zapisaną blokadą lub szablonem
- usuwa tymczasowe pola
signedUrl,publicUrliurlExpiresAtprzed utrwaleniem konfiguracji - usuwa nieodwołane pliki etapowe z tego samego
storageDraftId
Porzucone pliki tymczasowe, które nigdy nie zostały zapisane, są usuwane podczas czyszczenia po upływie ich ważności.
Aby odświeżyć podgląd konfiguracji dla identyfikatora pliku należącego do bieżącego użytkownika i aplikacji rozszerzenia:
GET /api/extensions/apps/:appId/files/:fileId
Punkty końcowe sesji
Te punkty końcowe wymagają standardowej autoryzacji i zakresów sesji rozszerzenia.
Ścieżka bazowa:
/api/extensions/sessions/:sessionId/files
Możliwości
Sprawdź to przed renderowaniem elementów sterujących przesyłaniem.
GET /api/extensions/sessions/:sessionId/files/capabilities
Wymagany jest locks:read.
Przykładowa odpowiedź:
{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}
Lista plików
GET /api/extensions/sessions/:sessionId/files
Wymagany jest locks:read.
Przykładowa odpowiedź:
{
"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"
}
]
}
Pobierz jeden plik
Użyj tej opcji, jeśli rozszerzenie ma już zapisany identyfikator pliku i potrzebuje tylko nowego, podpisanego łącza R2.
GET /api/extensions/sessions/:sessionId/files/:fileId
Wymagany jest locks:read.
Plik musi należeć do tej samej aplikacji rozszerzenia, sesji i blokady.
Przykładowa odpowiedź:
{
"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"
}
}
Prześlij plik
POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data
Wymagany jest locks:write.
Pola formularza:
file: wymagany plik obrazupurpose: opcjonalny krótki identyfikator, taki jakpuzzle-imagelubpreview
Przykład:
curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \
-F "[email protected]"
Przykładowa odpowiedź:
{
"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"
}
}
Typowe błędy przesyłania:
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
Usuń plik
DELETE /api/extensions/sessions/:sessionId/files/:fileId
Wymagany jest locks:write.
Plik musi należeć do tej samej aplikacji rozszerzenia, sesji i blokady.
Akcje mostu iframe
Rozszerzenia iframe mogą korzystać z nadrzędnego mostu internetowego do odczytu plików w czasie wykonywania. Przesyłanie i usuwanie plików w czasie wykonywania to mutacje sesji i powinny być wykonywane przez zaplecze za pomocą klucza API dla programistów o zasięgu aplikacji oraz kodu 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;
Obsługiwane działania mostu:
files.capabilities-> sprawdź, czy przechowywanie plików jest włączone i jakie limity obowiązująfiles.list-> wyświetl listę plików będących własnością bieżącej sesji rozszerzeniafiles.get-> odśwież jeden podpisany adres URL R2 z zapisanego identyfikatora pliku
Ramki iframe konfiguracji mogą używać dodatkowych nazw mostów przed utworzeniem sesji wykonawczej. W trybie konfiguracji polecenie files.upload tworzy pliki etapowe, polecenie files.list/files.staged.list wyświetla listę niewygasłych plików etapowych dla bieżącego użytkownika i aplikacji, a polecenie files.delete usuwa plik etapowy. Kontekst inicjalizacji konfiguracji zawiera polecenie storageDraftId; należy dodać tę wartość do zapisanej konfiguracji jako storageDraftId, jeśli polecenie Chastify ma natychmiast po zapisaniu usuwać pliki bez odniesień z tej samej wersji roboczej.
Konfiguracja files.upload akceptuje również dataUrl dla prostych klientów iframe:
await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);
W miarę możliwości preferuj przesyłanie File lub Blob. Używaj dataUrl tylko w przypadku małych generowanych obrazów, ponieważ ładunki base64 zajmują więcej pamięci.
Podpisane linki
Stabilny identyfikator pliku to id.
Użyj signedUrl, aby wyświetlić lub pobrać plik. Podpisane linki to krótkotrwałe adresy URL R2 GetObject wygenerowane przez Chastify. publicUrl jest przechowywany jako alias zgodności i obecnie zawiera ten sam podpisany adres URL.
Gdy podpisany link wygaśnie, wywołaj GET /api/extensions/sessions/:sessionId/files/:fileId, aby otrzymać nowy link dla jednego zapisanego pliku lub GET /api/extensions/sessions/:sessionId/files, aby odświeżyć wszystkie linki plików sesji.
Cykl życia czyszczenia
Pliki rozszerzeń są czyszczone za pomocą kolejki czyszczącej obsługiwanej przez BullMQ.
Sprzątanie jest planowane, gdy:
- aplikacja rozszerzenia została usunięta
- dokumenty blokady/rozszerzenia sesji są usuwane podczas usuwania blokady/czyszczenia archiwum
- plik jest jawnie usuwany za pomocą punktu końcowego pliku sesji
Kolejka utrzymuje kosztowne usuwanie R2 i czyszczenie bazy danych poza ścieżką żądania. Jeśli kolejkowanie kolejki się nie powiedzie, serwer powraca do czyszczenia w trakcie procesu po odpowiedzi, w której istnieje kontekst żądania, lub do czyszczenia z najlepszym wysiłkiem w ramach czyszczenia cyklu życia niższego poziomu.
Notatki dotyczące wydajności
- Sprawdzenie możliwości powinno nastąpić przed wyświetleniem interfejsu użytkownika umożliwiającego przesyłanie.
- Przesyłanie danych jest ograniczone limitami dotyczącymi pliku, sesji i aplikacji.
- Sprawdzanie kwot wykorzystuje indeksowane metadane
UserFiledlascope + appId,scope + sessionIdiscope + lockId. - Oczyszczaj strumienie odpowiadające rekordom plików zamiast ładować wszystkie adresy URL do pamięci.
- Przesłane obrazy rastrowe są przetwarzane i przechowywane jako zoptymalizowane obrazy internetowe.
Notatki bezpieczeństwa
- Nie traktuj adresów URL obrazów udostępnianych przez rozszerzenie jako wiarygodnego dowodu ukończenia.
- Przechowuj tylko rekordy plików wydane przez Chastify jako zaufane pliki rozszerzeń.
- Powiąż rekordy pliku z
appId,sessionIdilockId. - Wymuś
locks:writepodczas przesyłania i usuwania. - Sprawdź typ MIME i odrzuć SVG.
- Przed zezwoleniem na szerokie publiczne korzystanie z Internetu należy ustawić limity kontrolowane przez administratora.
- Korzystaj z walidacji po stronie serwera w przypadku każdego przepływu pracy zależnego od przesłanych plików.
Trasy bezpośrednie kontra mostowe
Użyj mostu iframe, gdy rozszerzenie działa w Chastify. Utrzymuje on uwierzytelnianie Chastify na stronie nadrzędnej i zapobiega ujawnianiu szczegółów trasy w ramce iframe.
Używaj bezpośrednich tras sesji wyłącznie z interfejsu użytkownika Chastify pierwszej strony lub zaufanych przepływów zaplecza, które mają już ważną autoryzację sesji rozszerzenia.