Armazenamento de arquivos de extensão
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.
O armazenamento de arquivos de extensão permite que uma extensão habilitada carregue arquivos de imagem pertencentes a uma sessão de extensão de bloqueio.
Use-o quando o JSON do estado da extensão não for suficiente, por exemplo:
- imagens de quebra-cabeça
- pré-visualizações geradas
- fotos de desafio
- Mídia específica da extensão que deve ser limpa com o bloqueio/sessão
Este armazenamento é separado de state.*. Use state.* para dados JSON pequenos. Use o armazenamento de arquivos somente para mídias binárias.
Relação com os pontos finais do estado
O estado de extensão é para JSON de escopo de sessão pequeno. A partir de um iframe, use o comando bridge read em vez de chamar a API REST diretamente:
state.get
Esse comando de ponte encaminha para:
GET /api/extensions/sessions/:sessionId/state
As chamadas diretas ao backend para gravar o estado utilizam o modelo de autenticação da API de extensão instalada:
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Não envie chaves de API de desenvolvedor para código iframe/navegador.
Utilize o estado escrito no backend para referências persistentes e dados da interface do usuário, por exemplo:
{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}
Não armazene dados binários, imagens em base64, URLs blob: do navegador ou cópias de longa duração de signedUrl no estado. URLs assinadas expiram e devem ser atualizadas com files.get quando a imagem for renderizada. As gravações de estado são limitadas em tamanho pela configuração stateMaxBytes do aplicativo de extensão, com valor padrão de 64 KiB.
Modelo de armazenamento
Chastify armazena arquivos de extensão no armazenamento R2 gerenciado por Chastify.
Cada arquivo carregado é rastreado com:
scope: "extension"appIdsessionIdelockIdpara arquivos de tempo de execução/sessãotemplateIdpara arquivos reivindicados por um modelo de bloqueio salvostaged,draftIdeexpiresAtpara uploads de configuração que ainda não foram reivindicados.extensionKeypurpose
Portão Administrativo e Cotas
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.
Configurar pontos de extremidade de teste
Utilize a preparação de configuração somente quando a interface de configuração for executada antes da existência de uma sessão de extensão.
Este é um fluxo de upload temporário. Ele existe para telas de configuração onde o usuário pode fazer o upload de um arquivo e, em seguida, fechar a janela ou desativar a extensão antes de criar um bloqueio/sessão. Um arquivo em preparação não é permanente até que seja reivindicado ao salvar a configuração da extensão que o referencia.
As interfaces de configuração podem verificar a disponibilidade e o uso atual em fase de preparação antes de renderizar os controles de upload:
GET /api/extensions/apps/:appId/files/capabilities
A resposta inclui stagedQuota para os arquivos temporários não expirados do usuário atual nesse aplicativo de extensão:
{
"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 do formulário:
file: arquivo de imagem necessáriopurpose: identificador curto opcional, comojigsaw-config-imagedraftId: ID opcional do rascunho de configuração; reutilize o mesmo valor enquanto uma janela modal de configuração estiver aberta.
A resposta contém um código file.id estável e uma URL assinada de curta duração para visualização imediata.
Exemplo de resposta:
{
"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 os uploads de configuração após uma atualização da página, liste os arquivos preparados para o usuário atual e o aplicativo de extensão:
GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image
Parâmetros de consulta opcionais:
purpose: retornar apenas arquivos preparados para um caso de uso de configuração.draftId: retorna apenas arquivos de um rascunho de instalação conhecido
Se draftId for omitido, Chastify retorna os arquivos temporários não expirados do usuário atual para esse aplicativo. Isso é intencional para interfaces de configuração: um usuário pode enviar arquivos, recarregar a página ou trocar de dispositivo, e a tela de configuração pode restaurar esses envios temporários antes que o bloqueio/modelo seja salvo.
A resposta da lista de aplicativos armazenados em etapas também inclui stagedQuota, que informa o tempo de uso armazenado em etapas não expirado do usuário atual para esse aplicativo:
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
As interfaces de configuração também podem atualizar ou excluir um arquivo preparado:
GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId
Reivindicando arquivos encenados posteriormente
Não existe um endpoint de "reivindicação" separado no navegador. Um arquivo preparado é reivindicado automaticamente quando Chastify salva uma configuração de bloqueio ou extensão de modelo que faz referência ao arquivo preparado.
Essas rotas reivindicam arquivos em espera após o documento de bloqueio/modelo ter um ID, e então salvam a configuração da extensão novamente com as referências de arquivo reivindicadas. Se a reivindicação falhar, o bloqueio/modelo recém-criado é revertido para que os arquivos em espera não fiquem vinculados a uma configuração corrompida.
Nota sobre modelos compartilhados: os bloqueios compartilhados aceitos mantêm o ID do modelo de origem no bloqueio ativo clonado. Portanto, os arquivos reivindicados pelo modelo são mantidos enquanto qualquer bloqueio clonado ainda fizer referência a esse modelo. Se o modelo de origem for excluído, o Chastify adia a exclusão de seus arquivos de extensão até que o último bloqueio clonado que faz referência ao modelo excluído seja excluído ou arquivado.
Para tornar um arquivo em fase de preparação reivindicável, armazene uma referência como esta na configuração da extensão que é enviada pela interface de configuração:
{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}
Ao salvar, o Chastify verifica a configuração em busca de registros provider: "chastify_storage" com um fileId válido e, em seguida:
- Verifica se o arquivo pertence ao usuário atual e ao aplicativo de extensão.
- rejeita arquivos de preparação expirados
- rejeita arquivos já reivindicados por outro contexto de bloqueio/modelo
- marcas referenciadas em arquivos preparados conforme alegado
- Vincula os arquivos reivindicados ao bloqueio ou modelo salvo.
- Remove os campos temporários
signedUrl,publicUrleurlExpiresAtantes que a configuração seja persistida. - Exclui arquivos de preparação não referenciados do mesmo
storageDraftId
Arquivos temporários abandonados que nunca são salvos são excluídos pela ferramenta de limpeza após o vencimento do prazo.
Para atualizar a pré-visualização da configuração de um arquivo pertencente ao usuário atual e ao aplicativo de extensão:
GET /api/extensions/apps/:appId/files/:fileId
Pontos finais da sessão
Esses endpoints exigem a autorização e os escopos normais da sessão de extensão.
Caminho base:
/api/extensions/sessions/:sessionId/files
Capacidades
Verifique isso antes de renderizar os controles de upload.
GET /api/extensions/sessions/:sessionId/files/capabilities
Requer locks:read.
Exemplo de resposta:
{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}
Listar arquivos
GET /api/extensions/sessions/:sessionId/files
Requer locks:read.
Exemplo de resposta:
{
"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"
}
]
}
Obtenha um arquivo
Use esta opção quando sua extensão já tiver um ID de arquivo armazenado e precisar apenas de um novo link R2 assinado.
GET /api/extensions/sessions/:sessionId/files/:fileId
Requer locks:read.
O arquivo deve pertencer ao mesmo aplicativo de extensão, sessão e bloqueio.
Exemplo de resposta:
{
"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"
}
}
Carregar arquivo
POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data
Requer locks:write.
Campos do formulário:
file: arquivo de imagem necessáriopurpose: identificador curto opcional, comopuzzle-imageoupreview
Exemplo:
curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \
-F "[email protected]"
Exemplo de resposta:
{
"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"
}
}
Erros comuns de upload:
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
Excluir arquivo
DELETE /api/extensions/sessions/:sessionId/files/:fileId
Requer locks:write.
O arquivo deve pertencer ao mesmo aplicativo de extensão, sessão e bloqueio.
Ações da Ponte Iframe
As extensões de iframe podem usar a ponte web principal para leitura de arquivos em tempo de execução. O upload e a exclusão de arquivos em tempo de execução são mutações de sessão e devem ser realizados pelo seu backend com uma chave de API de desenvolvedor com escopo de aplicativo, além 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;
Ações de ponte apoiadas:
files.capabilities-> verifique se o armazenamento de arquivos está habilitado e quais cotas se aplicam.files.list-> lista os arquivos pertencentes à sessão de extensão atualfiles.get-> atualizar uma URL R2 assinada a partir de um ID de arquivo armazenado
Os iframes de configuração podem usar nomes de ponte adicionais antes que uma sessão de tempo de execução exista. No modo de configuração, files.upload cria arquivos de preparação, files.list/files.staged.list lista os arquivos de preparação não expirados para o usuário e aplicativo atuais e files.delete exclui um arquivo de preparação. O contexto de inicialização da configuração inclui storageDraftId; inclua esse valor em sua configuração salva como storageDraftId se desejar que Chastify limpe arquivos não referenciados do mesmo rascunho imediatamente após o salvamento.
A configuração files.upload também aceita dataUrl para clientes iframe simples:
await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);
Dê preferência a uploads com File ou Blob sempre que possível. Use dataUrl apenas para imagens geradas pequenas, pois os payloads em base64 ocupam mais espaço na memória.
Links assinados
O identificador de arquivo estável é id.
Use signedUrl para exibir ou baixar o arquivo. Os links assinados são URLs R2 GetObject de curta duração, geradas por Chastify. publicUrl é mantido como um alias de compatibilidade e atualmente contém a mesma URL assinada.
Quando um link assinado expirar, chame GET /api/extensions/sessions/:sessionId/files/:fileId para receber um novo link para um arquivo armazenado ou GET /api/extensions/sessions/:sessionId/files para atualizar todos os links de arquivos da sessão.
Ciclo de vida da limpeza
Os arquivos de extensão são limpos por meio de uma fila de limpeza com suporte do BullMQ.
A limpeza está agendada para quando:
- um aplicativo de extensão foi excluído
- Os documentos de bloqueio/extensão de sessão são excluídos durante a remoção do bloqueio/limpeza do arquivo.
- Um arquivo é explicitamente excluído através do endpoint de arquivo de sessão.
A fila impede que a exclusão de dados R2 e a limpeza do banco de dados, que são custosas, interfiram no fluxo da requisição. Se o enfileiramento da fila falhar, o servidor recorre à limpeza em processo após a resposta, quando houver um contexto de requisição, ou à limpeza direta, da melhor maneira possível, em etapas de limpeza do ciclo de vida de nível inferior.
Notas de desempenho
- As verificações de capacidade devem ocorrer antes que a interface de upload seja exibida.
- Os uploads são limitados por restrições de arquivo, sessão e aplicativo.
- As verificações de cotas usam metadados indexados
UserFileparascope + appId,scope + sessionIdescope + lockId. - Limpar fluxos que correspondem a registros de arquivos em vez de carregar todos os URLs na memória.
- As imagens raster carregadas são processadas e armazenadas como imagens otimizadas para a web.
Notas de segurança
- Não considere URLs de imagens fornecidas pela extensão como prova confiável de conclusão.
- Armazene apenas registros de arquivos emitidos por Chastify como arquivos de extensão confiáveis.
- Vincule os registros do arquivo aos códigos
appId,sessionIdelockId. - Impor o código de acesso
locks:writepara uploads e exclusões. - Validar tipo MIME e rejeitar SVG.
- Mantenha as quotas controladas pelo administrador ativadas antes de permitir o uso público amplo.
- Utilize a validação no servidor para qualquer fluxo de trabalho que dependa de arquivos enviados.
Rotas diretas vs. Ponte
Use a ponte iframe quando sua extensão estiver sendo executada dentro de Chastify. Isso mantém a autenticação de Chastify na página principal e evita expor os detalhes da rota ao iframe.
Utilize rotas de sessão direta somente a partir da interface de usuário Chastify de terceiros ou fluxos de backend confiáveis que já possuam autorização de sessão de extensão válida.