Archiviazione file di estensione
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.
L'archiviazione dei file dell'estensione consente a un'estensione abilitata di caricare file immagine appartenenti a una sessione di blocco dell'estensione.
Utilizzalo quando lo stato JSON dell'estensione non è sufficiente, ad esempio:
- immagini di puzzle
- anteprime generate
- foto della sfida
- media specifici dell'estensione che dovrebbero essere ripuliti con il blocco/sessione
Questo spazio di archiviazione è separato da state.*. Utilizzare state.* per piccoli dati JSON. Utilizzare l'archiviazione file solo per supporti binari.
Relazione con gli endpoint statali
Lo stato dell'estensione è per piccoli JSON con ambito di sessione. Da un iframe, utilizzare il comando di lettura del bridge invece di chiamare direttamente REST:
state.get
Quel comando bridge instrada a:
GET /api/extensions/sessions/:sessionId/state
Le chiamate dirette al backend per scrivere lo stato utilizzano il modello di autenticazione API dell'estensione installata:
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Non inviare le chiavi API per sviluppatori al codice iframe/del browser.
Utilizza lo stato scritto nel backend per i riferimenti persistenti e i dati dell'interfaccia utente, ad esempio:
{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}
Non memorizzare dati binari, immagini base64, URL blob: del browser o copie a lunga durata di signedUrl nello stato. Gli URL firmati scadono e devono essere aggiornati con files.get quando l'immagine viene visualizzata. Le scritture di stato sono limitate in termini di dimensione dall'impostazione stateMaxBytes dell'app di estensione, con un valore predefinito di 64 KiB.
Modello di archiviazione
Chastify memorizza i file di estensione nello storage R2 gestito da Chastify.
Ogni file caricato viene tracciato con:
scope: "extension"appIdsessionIdelockIdper i file di runtime/sessionetemplateIdper i file rivendicati da un modello di blocco salvatostaged,draftIdeexpiresAtper i caricamenti di configurazione non ancora rivendicatiextensionKeypurpose
Cancello amministrativo e quote
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.
Configurazione degli endpoint di staging
Utilizzare la fase di configurazione solo quando l'interfaccia utente di configurazione viene eseguita prima che esista una sessione di estensione.
Si tratta di un flusso di caricamento temporaneo. È previsto per le schermate di configurazione in cui l'utente può caricare un file, quindi chiudere la finestra modale o disabilitare l'estensione prima di creare un blocco/sessione. Un file in attesa di conferma non è permanente finché non viene rivendicato salvando la configurazione dell'estensione che lo referenzia.
Le interfacce utente di configurazione possono verificare la disponibilità e l'utilizzo corrente prima di visualizzare i controlli di caricamento:
GET /api/extensions/apps/:appId/files/capabilities
La risposta include stagedQuota per i file di staging non scaduti dell'utente corrente su quell'app di estensione:
{
"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
Campi del modulo:
file: file immagine richiestopurpose: identificatore breve opzionale comejigsaw-config-imagedraftId: ID bozza di configurazione opzionale; riutilizza lo stesso valore finché è aperta una finestra modale di configurazione.
La risposta contiene un codice file.id stabile e un URL firmato di breve durata per un'anteprima immediata.
Esempio di risposta:
{
"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
}
}
Per ripristinare i caricamenti di configurazione dopo un aggiornamento della pagina, elenca i file in attesa per l'utente corrente e l'app dell'estensione:
GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image
Parametri di query opzionali:
purpose: restituisce solo i file in fase di preparazione per un caso d'uso di configurazionedraftId: restituisce solo i file da una bozza di configurazione nota
Se draftId viene omesso, Chastify restituisce i file temporanei non scaduti dell'utente corrente per quell'app. Questo comportamento è intenzionale per le interfacce utente di configurazione: un utente può caricare file, ricaricare la pagina o cambiare dispositivo e la schermata di configurazione può ripristinare questi caricamenti temporanei prima che il blocco/modello venga salvato.
La risposta dell'elenco di fasi include anche stagedQuota, che riporta l'utilizzo di fase non ancora scaduto dell'utente corrente per quell'app:
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
Le interfacce utente di configurazione possono anche aggiornare o eliminare un file in fase di preparazione:
GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId
Richiesta di accesso ai file in fase di elaborazione in un secondo momento
Non esiste un endpoint "claim" separato lato browser. Un file in fase di staging viene rivendicato automaticamente quando Chastify salva un blocco o una configurazione di estensione del modello che fa riferimento al file in fase di staging.
Questi percorsi acquisiscono i file in fase di staging dopo che il documento di blocco/modello ha un ID, quindi salvano nuovamente la configurazione dell'estensione con i riferimenti ai file acquisiti. Se l'acquisizione fallisce, il blocco/modello appena creato viene annullato in modo che i file in fase di staging non rimangano collegati a una configurazione danneggiata.
Nota sui modelli condivisi: i blocchi condivisi accettati mantengono l'ID del modello di origine sul blocco attivo clonato. I file rivendicati dal modello vengono quindi mantenuti finché qualsiasi blocco clonato fa ancora riferimento a tale modello. Se il modello di origine viene eliminato, Chastify ritarda l'eliminazione dei relativi file di estensione fino a quando l'ultimo blocco clonato che fa riferimento al modello eliminato non viene eliminato o archiviato.
Per rendere un file in fase di preparazione riscattabile, memorizza un riferimento come questo nella configurazione dell'estensione che viene inviata dall'interfaccia utente di installazione:
{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}
Al salvataggio, Chastify analizza la configurazione alla ricerca di record provider: "chastify_storage" con un fileId valido, quindi:
- verifica che il file appartenga all'utente corrente e all'app di estensione
- rifiuta i file in fase di preparazione scaduti
- rifiuta i file già rivendicati da un altro contesto di blocco/modello
- marchi di riferimento file di staging come affermato
- collega i file rivendicati al blocco o al modello salvato
- Rimuove i campi temporanei
signedUrl,publicUrleurlExpiresAtprima che la configurazione venga salvata. - elimina i file staging non referenziati dallo stesso
storageDraftId
I file abbandonati in fase di staging che non vengono mai salvati vengono eliminati dal sistema di pulizia dopo la loro scadenza.
Per aggiornare l'anteprima di installazione per un ID file di proprietà dell'utente corrente e dell'app di estensione:
GET /api/extensions/apps/:appId/files/:fileId
Endpoint di sessione
Questi endpoint richiedono la normale autorizzazione e gli ambiti di sessione dell'estensione.
Percorso base:
/api/extensions/sessions/:sessionId/files
Capacità
Verifica questo prima di visualizzare i controlli di caricamento.
GET /api/extensions/sessions/:sessionId/files/capabilities
Richiede locks:read.
Esempio di risposta:
{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}
Elenco dei file
GET /api/extensions/sessions/:sessionId/files
Richiede locks:read.
Esempio di risposta:
{
"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"
}
]
}
Ottieni un file
Utilizza questa opzione quando la tua estensione ha già un ID file memorizzato e necessita solo di un nuovo collegamento R2 firmato.
GET /api/extensions/sessions/:sessionId/files/:fileId
Richiede locks:read.
Il file deve appartenere alla stessa estensione, sessione e blocco.
Esempio di risposta:
{
"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"
}
}
Carica file
POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data
Richiede locks:write.
Campi del modulo:
file: file immagine richiestopurpose: identificatore breve opzionale comepuzzle-imageopreview
Esempio:
curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \
-F "[email protected]"
Esempio di risposta:
{
"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"
}
}
Errori comuni durante il caricamento:
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
Elimina file
DELETE /api/extensions/sessions/:sessionId/files/:fileId
Richiede locks:write.
Il file deve appartenere alla stessa estensione, sessione e blocco.
Azioni di Iframe Bridge
Le estensioni iframe possono utilizzare il bridge web principale per la lettura dei file in fase di esecuzione. Il caricamento e l'eliminazione dei file in fase di esecuzione sono modifiche di sessione e devono essere eseguiti dal backend con una chiave API per sviluppatori con ambito app più 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;
Azioni del ponte supportate:
files.capabilities-> verifica se l'archiviazione dei file è abilitata e quali quote si applicano.files.list-> elenca i file di proprietà della sessione di estensione correntefiles.get-> aggiorna un URL R2 firmato da un ID file memorizzato
Gli iframe di configurazione possono utilizzare nomi di bridge aggiuntivi prima che esista una sessione di runtime. In modalità di configurazione, files.upload crea file in fase di staging, files.list/files.staged.list elenca i file in fase di staging non scaduti per l'utente e l'app correnti e files.delete elimina un file in fase di staging. Il contesto di inizializzazione della configurazione include storageDraftId; includi questo valore nella configurazione salvata come storageDraftId se desideri che Chastify elimini immediatamente i file non referenziati dalla stessa bozza al salvataggio.
La configurazione files.upload accetta anche dataUrl per i client iframe semplici:
await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);
Quando possibile, preferire i caricamenti con File o Blob. Utilizzare dataUrl solo per immagini generate di piccole dimensioni, poiché i payload base64 occupano più memoria.
Collegamenti firmati
L'identificativo stabile del file è id.
Utilizzare signedUrl per visualizzare o scaricare il file. I link firmati sono URL R2 GetObject di breve durata generati da Chastify. publicUrl viene mantenuto come alias di compatibilità e attualmente contiene lo stesso URL firmato.
Quando un collegamento firmato scade, chiama GET /api/extensions/sessions/:sessionId/files/:fileId per ricevere un nuovo collegamento per un file memorizzato, oppure GET /api/extensions/sessions/:sessionId/files per aggiornare tutti i collegamenti ai file della sessione.
Ciclo di vita della pulizia
I file di estensione vengono eliminati tramite una coda di pulizia basata su BullMQ.
La pulizia è programmata quando:
- un'app di estensione viene eliminata
- I documenti di blocco/estensione della sessione vengono eliminati durante la rimozione del blocco/pulizia dell'archivio.
- un file viene eliminato esplicitamente tramite l'endpoint del file di sessione
La coda impedisce che costose operazioni di eliminazione R2 e pulizia del database influiscano sul percorso della richiesta. Se l'inserimento nella coda fallisce, il server ripiega sulla pulizia in-process dopo la risposta, laddove esista un contesto di richiesta, oppure sulla pulizia diretta "best-effort" nella pulizia del ciclo di vita di livello inferiore.
Note sulle prestazioni
- Prima di mostrare l'interfaccia utente di caricamento, è necessario effettuare dei controlli di compatibilità.
- Il numero di caricamenti è soggetto a limiti per singolo file, per sessione e per singola applicazione.
- I controlli delle quote utilizzano i metadati indicizzati
UserFileperscope + appId,scope + sessionIdescope + lockId. - La pulizia dei flussi si basa sulla corrispondenza dei record dei file anziché sul caricamento di tutti gli URL in memoria.
- Le immagini raster caricate vengono elaborate e memorizzate come immagini web ottimizzate.
Note sulla sicurezza
- Non considerare gli URL delle immagini forniti dalle estensioni come prove attendibili di completamento.
- Archivia come file di estensione attendibili solo i record di file emessi con l'estensione Chastify.
- Associa i record del file a
appId,sessionIdelockId. - Applicare il codice
locks:writeper caricamenti ed eliminazioni. - Convalida il tipo MIME e rifiuta l'SVG.
- Prima di consentire l'utilizzo pubblico su larga scala, mantieni attive le quote controllate dagli amministratori.
- Utilizza la convalida lato server per qualsiasi flusso di lavoro che dipenda dal caricamento di file.
Percorsi diretti vs. ponti
Utilizza il bridge iframe quando la tua estensione è in esecuzione all'interno di Chastify. In questo modo, l'autenticazione di Chastify rimane nella pagina principale ed evita di esporre i dettagli del percorso all'iframe.
Utilizzare percorsi di sessione diretti solo dall'interfaccia utente Chastify proprietaria o da flussi backend affidabili che dispongono già di un'autorizzazione di sessione di estensione valida.