Stockage de fichiers d'extension
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.
Le stockage de fichiers d'extension permet à une extension activée de télécharger des fichiers image appartenant à une session d'extension de verrouillage.
Utilisez-le lorsque le JSON d'état d'extension ne suffit pas, par exemple :
- images de puzzle
- aperçus générés
- photos de défi
- médias spécifiques à l'extension qui doivent être nettoyés avec le verrouillage/la session
Ce stockage est distinct de state.*. Utilisez state.* pour les petites données JSON. Le stockage de fichiers est réservé aux données binaires.
Relation avec les points d'extrémité de l'état
L'état d'extension est destiné aux petits objets JSON à portée de session. Depuis une iframe, utilisez la commande de lecture du pont au lieu d'appeler directement l'API REST :
state.get
Cette commande de pont est acheminée vers :
GET /api/extensions/sessions/:sessionId/state
Les appels directs au backend pour écrire l'état utilisent le modèle d'authentification de l'API d'extension installée :
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Ne transmettez pas les clés API développeur au code iframe/navigateur.
Utilisez un état écrit côté serveur pour les références durables et les données d'interface utilisateur, par exemple :
{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}
Ne stockez pas de données binaires, d'images base64, d'URL blob: du navigateur ni de copies persistantes de signedUrl dans l'état. Les URL signées expirent et doivent être actualisées avec files.get lors du rendu de l'image. La taille des écritures dans l'état est limitée par le paramètre stateMaxBytes de l'application d'extension, avec une valeur par défaut de 64 Kio.
Modèle de stockage
Chastify stocke les fichiers d'extension dans le stockage R2 géré par Chastify.
Chaque fichier téléchargé est suivi grâce à :
scope: "extension"appIdsessionIdetlockIdpour les fichiers d'exécution/de sessiontemplateIdpour les fichiers revendiqués par un modèle de verrouillage enregistréstaged,draftIdetexpiresAtcorrespondent aux fichiers de configuration qui n'ont pas encore été réclamés.extensionKeypurpose
Portail d'administration et quotas
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.
Configurer les points de terminaison de préproduction
N’utilisez la phase de préparation que lorsqu’une interface utilisateur de configuration s’exécute avant l’existence d’une session d’extension.
Il s'agit d'un flux de chargement temporaire. Il est destiné aux écrans de configuration où l'utilisateur peut charger un fichier, puis fermer la fenêtre modale ou désactiver l'extension avant de créer un verrou/une session. Un fichier mis en attente n'est pas conservé tant qu'il n'est pas enregistré dans la configuration de l'extension qui y fait référence.
Les interfaces de configuration peuvent vérifier la disponibilité et l'utilisation actuelle avant d'afficher les commandes de téléchargement :
GET /api/extensions/apps/:appId/files/capabilities
La réponse inclut stagedQuota pour les fichiers intermédiaires non expirés de l'utilisateur actuel sur cette application d'extension :
{
"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
Champs de formulaire :
file: fichier image requispurpose: identifiant court facultatif tel quejigsaw-config-imagedraftId: identifiant de brouillon de configuration facultatif ; réutilisez la même valeur tant qu’une fenêtre modale de configuration est ouverte.
La réponse contient un code file.id stable et une URL signée éphémère pour un aperçu immédiat.
Exemple de réponse :
{
"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
}
}
Pour restaurer les chargements de configuration après une actualisation de la page, listez les fichiers préparés pour l'utilisateur actuel et l'application d'extension :
GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image
Paramètres de requête facultatifs :
purpose: ne renvoyer que les fichiers préparés pour un cas d’utilisation de configurationdraftId: ne renvoyer que les fichiers d'une configuration brouillon connue
Si draftId est omis, Chastify renvoie les fichiers temporaires non expirés de l'utilisateur actuel pour cette application. Ce comportement est intentionnel pour les interfaces de configuration : un utilisateur peut charger des fichiers, recharger la page ou changer d'appareil, et l'écran de configuration peut restaurer ces chargements temporaires avant l'enregistrement du verrouillage/modèle.
La réponse de la liste des sessions intermédiaires inclut également stagedQuota, qui indique l'utilisation intermédiaire non expirée de l'utilisateur actuel pour cette application :
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
Les interfaces de configuration peuvent également actualiser ou supprimer un fichier intermédiaire :
GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId
Réclamation ultérieure des dossiers mis en scène
Il n'existe pas de point de terminaison « revendication » distinct côté navigateur. Un fichier préparé est revendiqué automatiquement lorsque Chastify enregistre une configuration de verrouillage ou d'extension de modèle qui référence ce fichier.
Ces routes permettent de récupérer les fichiers intermédiaires une fois que le document de verrouillage/modèle possède un identifiant, puis d'enregistrer à nouveau la configuration de l'extension avec les références aux fichiers récupérés. En cas d'échec de la récupération, le document de verrouillage/modèle nouvellement créé est annulé afin que les fichiers intermédiaires ne restent pas associés à une configuration corrompue.
Note concernant les modèles partagés : les verrous partagés acceptés conservent l’identifiant du modèle source sur le verrou actif cloné. Les fichiers associés au modèle sont donc conservés tant qu’un verrou cloné référence ce modèle. Si le modèle source est supprimé, Chastify retarde la suppression de ses fichiers d’extension jusqu’à ce que le dernier verrou cloné référençant le modèle supprimé soit supprimé ou archivé.
Pour qu'un fichier préparé puisse être revendiqué, stockez une référence comme celle-ci dans la configuration de l'extension soumise par l'interface utilisateur d'installation :
{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}
Lors de l'enregistrement, Chastify analyse la configuration à la recherche d'enregistrements provider: "chastify_storage" avec un fileId valide, puis :
- vérifie que le fichier appartient à l'utilisateur actuel et à l'application d'extension
- rejette les fichiers intermédiaires expirés
- rejette les fichiers déjà revendiqués par un autre contexte de verrouillage/modèle
- marques référencées fichiers mis en scène comme revendiqué
- associe les fichiers revendiqués au verrou ou au modèle enregistré
- Supprime les champs temporaires
signedUrl,publicUrleturlExpiresAtavant l'enregistrement de la configuration. - supprime les fichiers intermédiaires non référencés du même
storageDraftId
Les fichiers intermédiaires abandonnés qui ne sont jamais enregistrés sont supprimés par le processus de nettoyage après leur expiration.
Pour actualiser l'aperçu de configuration d'un fichier dont l'identifiant appartient à l'utilisateur actuel et à l'application d'extension :
GET /api/extensions/apps/:appId/files/:fileId
Points de terminaison de session
Ces points de terminaison nécessitent l'autorisation et les étendues de session d'extension normales.
Chemin de base :
/api/extensions/sessions/:sessionId/files
Capacités
Vérifiez ceci avant de générer les commandes de téléchargement.
GET /api/extensions/sessions/:sessionId/files/capabilities
Nécessite locks:read.
Exemple de réponse :
{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}
Liste des fichiers
GET /api/extensions/sessions/:sessionId/files
Nécessite locks:read.
Exemple de réponse :
{
"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"
}
]
}
Obtenir un fichier
Utilisez cette option lorsque votre extension possède déjà un identifiant de fichier enregistré et qu'elle a uniquement besoin d'un lien R2 signé et récent.
GET /api/extensions/sessions/:sessionId/files/:fileId
Nécessite locks:read.
Le fichier doit appartenir à la même application d'extension, à la même session et au même verrou.
Exemple de réponse :
{
"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"
}
}
Téléverser un fichier
POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data
Nécessite locks:write.
Champs de formulaire :
file: fichier image requispurpose: identifiant court facultatif tel quepuzzle-imageoupreview
Exemple:
curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \
-F "[email protected]"
Exemple de réponse :
{
"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"
}
}
Erreurs de chargement courantes :
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
Supprimer le fichier
DELETE /api/extensions/sessions/:sessionId/files/:fileId
Nécessite locks:write.
Le fichier doit appartenir à la même application d'extension, à la même session et au même verrou.
Actions du pont Iframe
Les extensions iframe peuvent utiliser le pont web parent pour la lecture de fichiers en cours d'exécution. Le chargement et la suppression de fichiers en cours d'exécution sont des modifications de session et doivent être effectués par votre serveur avec une clé API développeur au niveau de l'application, suivie de 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;
Actions de pont prises en charge :
files.capabilities-> Vérifier si le stockage de fichiers est activé et quels quotas s'appliquent.files.list-> liste des fichiers appartenant à la session d'extension actuellefiles.get-> actualiser une URL R2 signée à partir d'un identifiant de fichier stocké
Les iframes de configuration peuvent utiliser des noms de pont supplémentaires avant l'établissement d'une session d'exécution. En mode configuration, files.upload crée des fichiers intermédiaires, files.list/files.staged.list liste les fichiers intermédiaires non expirés pour l'utilisateur et l'application en cours, et files.delete supprime un fichier intermédiaire. Le contexte d'initialisation de la configuration inclut storageDraftId ; incluez cette valeur dans votre configuration enregistrée sous la forme storageDraftId si vous souhaitez que Chastify supprime immédiatement les fichiers non référencés du même brouillon lors de l'enregistrement.
Le programme d'installation files.upload accepte également dataUrl pour les clients iframe simples :
await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);
Privilégiez les formats File ou Blob pour les chargements, lorsque cela est possible. Utilisez dataUrl uniquement pour les petites images générées, car les données base64 sont plus volumineuses en mémoire.
Liens signés
L'identifiant du fichier stable est id.
Utilisez signedUrl pour afficher ou télécharger le fichier. Les liens signés sont des URL R2 GetObject éphémères générées par Chastify. publicUrl est conservé comme alias de compatibilité et contient actuellement la même URL signée.
Lorsqu'un lien signé expire, appelez GET /api/extensions/sessions/:sessionId/files/:fileId pour recevoir un nouveau lien pour un fichier stocké, ou GET /api/extensions/sessions/:sessionId/files pour actualiser tous les liens de fichiers de session.
Cycle de vie du nettoyage
Les fichiers d'extension sont nettoyés via une file d'attente de nettoyage gérée par BullMQ.
Le nettoyage est prévu à la date suivante :
- Une application d'extension a été supprimée.
- Les documents de verrouillage/d'extension de session sont supprimés lors de la suppression du verrouillage/du nettoyage des archives.
- un fichier est explicitement supprimé via le point de terminaison de fichier de session
La file d'attente permet d'éviter les opérations coûteuses de suppression R2 et de nettoyage de la base de données lors du traitement de la requête. En cas d'échec de l'ajout à la file d'attente, le serveur effectue un nettoyage en cours de traitement après la réponse lorsqu'un contexte de requête existe, ou un nettoyage direct au mieux lors du nettoyage du cycle de vie de niveau inférieur.
Notes de performance
- Les vérifications de compatibilité doivent être effectuées avant l'affichage de l'interface de téléchargement.
- Les chargements sont limités par fichier, par session et par application.
- Les contrôles de quotas utilisent les métadonnées indexées
UserFilepourscope + appId,scope + sessionIdetscope + lockId. - Nettoyer les flux correspondant aux enregistrements de fichiers au lieu de charger toutes les URL en mémoire.
- Les images raster téléchargées sont traitées et stockées sous forme d'images web optimisées.
Notes de sécurité
- Ne considérez pas les URL d'images fournies par l'extension comme une preuve de complétion fiable.
- Ne conservez que les enregistrements de fichiers émis par Chastify comme fichiers d'extension de confiance.
- Lier les enregistrements de fichiers à
appId,sessionIdetlockId. - Appliquer le code
locks:writepour les chargements et les suppressions. - Valider le type MIME et rejeter les SVG.
- Activez les quotas contrôlés par l'administrateur avant d'autoriser une utilisation publique généralisée.
- Utilisez la validation côté serveur pour tout flux de travail dépendant de fichiers téléchargés.
Routes directes ou ponts
Utilisez le pont iframe lorsque votre extension s'exécute dans Chastify. Cela permet de conserver l'authentification Chastify sur la page parente et d'éviter d'exposer les détails de routage à l'iframe.
Utilisez uniquement les routes de session directes à partir de l'interface utilisateur Chastify de première partie ou des flux backend de confiance qui disposent déjà d'une autorisation de session d'extension valide.