Pular para o conteúdo principal

Armazenamento de arquivos de extensão

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"
  • appId
  • sessionId e lockId para arquivos de tempo de execução/sessão
  • templateId para arquivos reivindicados por um modelo de bloqueio salvo
  • staged, draftId e expiresAt para uploads de configuração que ainda não foram reivindicados.
  • extensionKey
  • purpose

Portão Administrativo e Cotas

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ário
  • purpose: identificador curto opcional, como jigsaw-config-image
  • draftId: 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, publicUrl e urlExpiresAt antes 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ário
  • purpose: identificador curto opcional, como puzzle-image ou preview

Exemplo:

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

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_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

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 atual
  • files.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.

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 UserFile para scope + appId, scope + sessionId e scope + 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, sessionId e lockId.
  • Impor o código de acesso locks:write para 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.