Saltar al contenido principal

Almacenamiento de archivos de extensión

El almacenamiento de archivos de extensión permite que una extensión habilitada cargue archivos de imagen que pertenecen a una sesión de extensión de bloqueo.

Úselo cuando el JSON del estado de la extensión no sea suficiente, por ejemplo:

  • imágenes de rompecabezas
  • previsualizaciones generadas
  • fotos del reto
  • medios específicos de la extensión que deben limpiarse con el bloqueo/sesión

Este almacenamiento es independiente de state.*. Utilice state.* para datos JSON pequeños. Utilice el almacenamiento de archivos únicamente para medios binarios.

Relación con los puntos finales de estado

El estado de extensión es para JSON pequeños con ámbito de sesión. Desde un iframe, utilice el comando de lectura bridge en lugar de llamar directamente a REST:

state.get

Esa ruta de comando del puente apunta a:

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

Las llamadas directas al backend para escribir el estado utilizan el modelo de autenticación de la API de extensión instalada:

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

No envíe claves de API para desarrolladores al código del iframe/navegador.

Utilice el estado escrito en el backend para referencias duraderas y datos de la interfaz de usuario, por ejemplo:

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

No almacene datos binarios, imágenes base64, URL del navegador (blob:) ni copias de larga duración de signedUrl en el estado. Las URL firmadas caducan y deben actualizarse con files.get cuando se renderiza la imagen. El tamaño de las escrituras de estado está limitado por la configuración stateMaxBytes de la aplicación de extensión, con un valor predeterminado de 64 KiB.

Modelo de almacenamiento

Chastify almacena archivos de extensión en el almacenamiento R2 administrado por Chastify.

Cada archivo subido se rastrea con:

  • scope: "extension"
  • appId
  • sessionId y lockId para archivos de tiempo de ejecución/sesión
  • templateId para archivos reclamados por una plantilla de bloqueo guardada
  • staged, draftId y expiresAt para cargas de configuración que aún no han sido reclamadas
  • extensionKey
  • purpose

Puerta de administración y cuotas

Configurar puntos finales de prueba

Utilice la configuración preliminar únicamente cuando se ejecute una interfaz de usuario de configuración antes de que exista una sesión de extensión.

Este es un flujo de carga temporal. Se utiliza en pantallas de configuración donde el usuario puede cargar un archivo, cerrar la ventana modal o deshabilitar la extensión antes de crear un bloqueo o sesión. Un archivo almacenado temporalmente no es permanente hasta que se reclama guardando la configuración de la extensión que lo referencia.

Las interfaces de configuración pueden comprobar la disponibilidad y el uso actual previsto antes de renderizar los controles de carga:

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

La respuesta incluye stagedQuota para los archivos temporales no caducados del usuario actual en esa aplicación de extensión:

{
"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

Campos del formulario:

  • file: archivo de imagen requerido
  • purpose: identificador corto opcional como jigsaw-config-image
  • draftId: ID de borrador de configuración opcional; reutilice el mismo valor mientras haya una ventana modal de configuración abierta.

La respuesta contiene un código file.id estable y una URL firmada de corta duración para una vista previa inmediata.

Ejemplo de respuesta:

{
"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
}
}

Para restaurar las cargas de configuración después de actualizar la página, enumere los archivos preparados para el usuario actual y la aplicación de extensión:

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

Parámetros de consulta opcionales:

  • purpose: devolver solo archivos preparados para un caso de uso de configuración.
  • draftId: devolver solo archivos de un borrador de configuración conocido

Si se omite draftId, Chastify devuelve los archivos temporales no caducados del usuario actual para esa aplicación. Esto es intencional para las interfaces de configuración: un usuario puede cargar archivos, recargar la página o cambiar de dispositivo, y la pantalla de configuración puede restaurar esas cargas temporales antes de que se guarde el bloqueo/plantilla.

La respuesta de la lista de etapas también incluye stagedQuota, que informa sobre el uso de etapas no caducado del usuario actual para esa aplicación:

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

Las interfaces de configuración también pueden actualizar o eliminar un archivo provisional:

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

Reclamar archivos preparados posteriormente

No existe un punto final de "reclamación" independiente del navegador. Un archivo preparado se reclama automáticamente cuando Chastify guarda una configuración de bloqueo o extensión de plantilla que hace referencia a dicho archivo.

Estas rutas reclaman los archivos preparados una vez que el documento de bloqueo/plantilla tiene un ID, y luego guardan la configuración de la extensión con las referencias de archivo reclamadas. Si la reclamación falla, el bloqueo/plantilla recién creado se revierte para evitar que los archivos preparados queden vinculados a una configuración defectuosa.

Nota sobre plantillas compartidas: los bloqueos compartidos aceptados conservan el ID de la plantilla de origen en el bloqueo activo clonado. Por lo tanto, los archivos vinculados a la plantilla se conservan mientras cualquier bloqueo clonado siga haciendo referencia a ella. Si se elimina la plantilla de origen, Chastify retrasa la eliminación de sus archivos de extensión hasta que se elimine o archive el último bloqueo clonado que haga referencia a la plantilla eliminada.

Para que un archivo en preparación sea reclamable, almacene una referencia como esta en la configuración de la extensión que se envía a través de la interfaz de usuario de configuración:

{
"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 guardar, Chastify escanea la configuración en busca de registros provider: "chastify_storage" con un fileId válido y, a continuación:

  • verifica que el archivo pertenezca al usuario actual y a la aplicación de extensión.
  • rechaza archivos caducados en la etapa de preparación
  • rechaza archivos que ya están siendo reclamados por otro contexto de bloqueo/plantilla
  • marcas referenciadas archivos preparados según se afirma
  • vincula los archivos reclamados al bloqueo o plantilla guardados.
  • Elimina los campos temporales signedUrl, publicUrl y urlExpiresAt antes de que se guarde la configuración.
  • elimina los archivos preparados sin referencia del mismo storageDraftId

Los archivos temporales abandonados que nunca se guardan se eliminan mediante la limpieza una vez que expiran.

Para actualizar la vista previa de configuración de un ID de archivo propiedad del usuario actual y la aplicación de extensión:

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

Puntos finales de sesión

Estos puntos finales requieren la autorización y los ámbitos de sesión de extensión normales.

Ruta base:

/api/extensions/sessions/:sessionId/files

Capacidades

Verifique esto antes de renderizar los controles de carga.

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

Requiere locks:read.

Ejemplo de respuesta:

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

Listar archivos

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

Requiere locks:read.

Ejemplo de respuesta:

{
"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"
}
]
}

Obtén un archivo

Utilice esta opción cuando su extensión ya tenga un ID de archivo almacenado y solo necesite un nuevo enlace R2 firmado.

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

Requiere locks:read.

El archivo debe pertenecer a la misma extensión de aplicación, sesión y bloqueo.

Ejemplo de respuesta:

{
"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"
}
}

Subir archivo

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

Requiere locks:write.

Campos del formulario:

  • file: archivo de imagen requerido
  • purpose: identificador corto opcional como puzzle-image o preview

Ejemplo:

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

Ejemplo de respuesta:

{
"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"
}
}

Errores comunes al subir archivos:

  • 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

Eliminar archivo

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

Requiere locks:write.

El archivo debe pertenecer a la misma extensión de aplicación, sesión y bloqueo.

Acciones de puente de iframe

Las extensiones de iframe pueden usar el puente web principal para leer archivos en tiempo de ejecución. La carga y eliminación de archivos en tiempo de ejecución son mutaciones de sesión y deben ser realizadas por su backend con una clave API de desarrollador con ámbito de aplicación más 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;

Acciones de puente compatibles:

  • files.capabilities -> compruebe si el almacenamiento de archivos está habilitado y qué cuotas se aplican.
  • files.list -> listar archivos propiedad de la sesión de extensión actual
  • files.get -> actualiza una URL R2 firmada desde un ID de archivo almacenado

Los iframes de configuración pueden usar nombres de puente adicionales antes de que exista una sesión de tiempo de ejecución. En el modo de configuración, files.upload crea archivos preparados, files.list/files.staged.list enumera los archivos preparados no caducados para el usuario y la aplicación actuales, y files.delete elimina un archivo preparado. El contexto de inicialización de la configuración incluye storageDraftId; incluya ese valor en su configuración guardada como storageDraftId si desea que Chastify limpie los archivos sin referencia del mismo borrador inmediatamente después de guardar.

La configuración files.upload también acepta dataUrl para clientes iframe simples:

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

Siempre que sea posible, dé preferencia a las cargas con File o Blob. Use dataUrl solo para imágenes generadas pequeñas, ya que las cargas útiles base64 ocupan más memoria.

El identificador de archivo estable es id.

Utilice signedUrl para visualizar o descargar el archivo. Los enlaces firmados son URL R2 GetObject de corta duración generadas por Chastify. publicUrl se mantiene como un alias de compatibilidad y actualmente contiene la misma URL firmada.

Cuando un enlace firmado caduca, llame a GET /api/extensions/sessions/:sessionId/files/:fileId para recibir un nuevo enlace para un archivo almacenado, o a GET /api/extensions/sessions/:sessionId/files para actualizar todos los enlaces de archivos de la sesión.

Ciclo de vida de la limpieza

Los archivos de extensión se eliminan mediante una cola de limpieza respaldada por BullMQ.

La limpieza está programada para cuando:

  • Se elimina una aplicación de extensión
  • Los documentos de bloqueo/extensión de sesión se eliminan durante la eliminación del bloqueo/limpieza del archivo.
  • Un archivo se elimina explícitamente a través del punto final del archivo de sesión.

La cola evita que la costosa eliminación de R2 y la limpieza de la base de datos entren en la ruta de la solicitud. Si falla la inserción en la cola, el servidor recurre a la limpieza en proceso después de la respuesta donde existe un contexto de solicitud, o a la limpieza directa con el mejor esfuerzo posible en la limpieza del ciclo de vida de nivel inferior.

Notas de interpretación

  • Las comprobaciones de capacidad deben realizarse antes de que se muestre la interfaz de usuario de carga.
  • Las cargas están sujetas a límites por archivo, por sesión y por aplicación.
  • Las comprobaciones de cuotas utilizan metadatos indexados UserFile para scope + appId, scope + sessionId y scope + lockId.
  • Limpia los flujos de datos que coinciden con los registros de archivos en lugar de cargar todas las URL en la memoria.
  • Las imágenes rasterizadas subidas se procesan y almacenan como imágenes web optimizadas.

Notas de seguridad

  • No considere las URL de imágenes proporcionadas por la extensión como prueba fehaciente de que la solicitud se ha completado correctamente.
  • Almacene únicamente los registros de archivos emitidos por Chastify como archivos de extensión de confianza.
  • Vincular los registros de archivos a appId, sessionId y lockId.
  • Aplicar el código locks:write para cargas y eliminaciones.
  • Validar el tipo MIME y rechazar el SVG.
  • Mantenga habilitadas las cuotas controladas por el administrador antes de permitir un uso público generalizado.
  • Utilice la validación del lado del servidor para cualquier flujo de trabajo que dependa de archivos cargados.

Rutas directas frente a rutas puente

Utilice el puente iframe cuando su extensión se ejecute dentro de Chastify. Esto mantiene la autenticación de Chastify en la página principal y evita exponer los detalles de la ruta al iframe.

Utilice rutas de sesión directas únicamente desde la interfaz de usuario Chastify de primera parte o flujos de backend de confianza que ya tengan una autorización de sesión de extensión válida.