Хранилище файлов расширений
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.
Функция хранения файлов расширений позволяет включенному расширению загружать файлы изображений, относящиеся к сеансу блокировки расширения.
Используйте его, когда JSON-файла состояния расширения недостаточно, например:
- изображения-головоломки
- сгенерированные предварительные просмотры
- фотографии для конкурса
- Медиафайлы, специфичные для расширения, которые следует удалить вместе с блокировкой/сессией.
Это хранилище отделено от state.*. Используйте state.* для небольших JSON-данных. Используйте файловое хранилище только для бинарных носителей.
Связь с конечными точками состояния
Расширение состояния предназначено для небольших JSON-файлов, ограниченных областью действия сессии. В iframe используйте команду чтения моста вместо прямого вызова REST:
state.get
Эта команда моста направляет запрос по следующему пути:
GET /api/extensions/sessions/:sessionId/state
Для записи состояния напрямую в бэкэнд используются методы аутентификации API установленных расширений:
Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
Не отправляйте ключи API разработчика в код iframe/браузера.
Используйте состояние, записываемое в бэкэнд, для обеспечения надежных ссылок и хранения данных пользовательского интерфейса, например:
{
"puzzleImageFileId": "file_record_id",
"selectedImageIds": ["file_record_id"],
"lastOpenedTab": "images"
}
Не храните в состоянии двоичные данные, изображения в формате base64, URL-адреса браузера blob: или долговременные копии signedUrl. Подписанные URL-адреса истекают и должны обновляться значением files.get при отрисовке изображения. Размер записей в состояние ограничен параметром stateMaxBytes приложения расширения и по умолчанию составляет 64 КиБ.
Модель хранения
Chastify хранит файлы расширений в управляемом хранилище R2, находящемся под управлением Chastify.
Каждый загруженный файл отслеживается с помощью:
scope: "extension"appIdsessionIdиlockIdдля файлов времени выполнения/сессииtemplateId— для файлов, защищенных сохраненным шаблоном блокировки.staged,draftIdиexpiresAt— это номера файлов для загрузки настроек, которые еще не были востребованы.extensionKeypurpose
Административный шлюз и квоты
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.
Настройка тестовых конечных точек
Используйте тестовую среду настройки только в том случае, если пользовательский интерфейс настройки запускается до создания сеанса расширения.
Это временный процесс загрузки. Он существует для экранов настройки/конфигурации, где пользователь может загрузить файл, затем закрыть модальное окно или отключить расширение перед созданием блокировки/сессии. Подготовленный файл не является постоянным, пока он не будет подтвержден путем сохранения конфигурации расширения, которая ссылается на него.
В интерфейсе настройки можно проверить доступность и текущее поэтапное использование перед отображением элементов управления загрузкой:
GET /api/extensions/apps/:appId/files/capabilities
В ответе содержится код stagedQuota, указывающий на наличие у текущего пользователя непросроченных файлов, находящихся в процессе подготовки к использованию в этом расширении:
{
"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
Поля формы:
file: необходимый файл изображенияpurpose: необязательный короткий идентификатор, например,jigsaw-config-imagedraftId: необязательный идентификатор черновика настроек; используйте одно и то же значение, пока открыто одно окно настроек.
В ответе содержится стабильный код file.id и кратковременный подписанный URL-адрес для немедленного предварительного просмотра.
Пример ответа:
{
"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
}
}
Чтобы восстановить загруженные файлы установки после обновления страницы, отобразите список файлов, подготовленных для текущего пользователя и приложения расширения:
GET /api/extensions/apps/:appId/files/staged?purpose=jigsaw-config-image
Дополнительные параметры запроса:
purpose: возвращать только файлы, находящиеся в процессе подготовки, для одного варианта использования настройки.draftId: возвращать только файлы из известного черновика конфигурации.
Если параметр draftId опущен, Chastify возвращает список незагруженных файлов текущего пользователя для данного приложения. Это сделано намеренно для пользовательских интерфейсов настройки: пользователь может загружать файлы, перезагружать страницу или переключать устройства, и экран настройки может восстановить эти временные загрузки до сохранения блокировки/шаблона.
В ответе с поэтапным списком также содержится код stagedQuota, который сообщает о неистекшем периоде использования приложения текущим пользователем:
{
"items": [],
"stagedQuota": {
"bytesUsed": 123456,
"fileCount": 1,
"maxBytes": 10485760,
"remainingBytes": 10362304
}
}
В интерфейсе настройки также можно обновить или удалить один из подготовленных файлов:
GET /api/extensions/apps/:appId/files/:fileId
DELETE /api/extensions/apps/:appId/files/staged/:fileId
Получение доступа к подготовленным файлам позже.
Отдельная точка доступа для подтверждения запроса на стороне браузера отсутствует. Файл, находящийся в процессе подготовки, автоматически подтверждается при сохранении конфигурации блокировки или расширения шаблона, ссылающейся на этот файл.
Эти маршруты запрашивают файлы из подготовленного хранилища после того, как документ блокировки/шаблона получит идентификатор, а затем снова сохраняют конфигурацию расширения с запрошенными ссылками на файлы. Если запрос не удается, вновь созданный документ блокировки/шаблона откатывается, чтобы подготовленные файлы не оставались привязанными к неработающей конфигурации.
Примечание о разделяемых блокировках: принятые разделяемые блокировки сохраняют идентификатор исходного шаблона в клонированной активной блокировке. Таким образом, файлы, на которые претендует шаблон, сохраняются до тех пор, пока любая клонированная блокировка продолжает ссылаться на этот шаблон. Если исходный шаблон удален, Chastify откладывает удаление его файлов расширения до тех пор, пока не будет удалена или заархивирована последняя клонированная блокировка, ссылающаяся на удаленный шаблон.
Чтобы сделать файл, находящийся в процессе загрузки, доступным для активации, сохраните в конфигурации расширения, отправляемой через пользовательский интерфейс настройки, ссылку следующего вида:
{
"storageDraftId": "draft_123",
"images": [
{
"id": "file_record_id",
"provider": "chastify_storage",
"fileId": "file_record_id",
"url": "extension-file:file_record_id",
"title": "Puzzle image"
}
]
}
При сохранении Chastify сканирует конфигурацию на наличие записей provider: "chastify_storage" с действительным значением fileId, после чего:
- Проверяет, принадлежит ли файл текущему пользователю и приложению расширения.
- отклоняет просроченные файлы, находящиеся на стадии подготовки.
- отклоняет файлы, уже занятые другим контекстом блокировки/шаблона.
- отмечены указанные файлы, находящиеся на стадии подготовки, как и заявлено.
- привязывает заявленные файлы к сохраненному блокировочному файлу или шаблону.
- Удаляет временные поля
signedUrl,publicUrlиurlExpiresAtперед сохранением конфигурации. - удаляет неиспользуемые файлы, находящиеся в процессе подготовки, из того же
storageDraftId
Заброшенные файлы, находящиеся в процессе подготовки и так и не сохраненные, удаляются в процессе очистки после истечения срока их действия.
Чтобы обновить предварительный просмотр настроек для файла с идентификатором, принадлежащим текущему пользователю и приложению расширения:
GET /api/extensions/apps/:appId/files/:fileId
Конечные точки сессии
Для доступа к этим конечным точкам требуется стандартная авторизация сеанса расширения и соответствующие области действия.
Базовый путь:
/api/extensions/sessions/:sessionId/files
Возможности
Проверьте это перед отображением элементов управления загрузкой.
GET /api/extensions/sessions/:sessionId/files/capabilities
Требуется locks:read.
Пример ответа:
{
"enabled": true,
"provider": "r2",
"r2Configured": true,
"settings": {
"enabled": true,
"maxFileBytes": 10485760,
"maxBytesPerApp": 524288000,
"maxBytesPerSession": 52428800,
"maxStagedBytesPerUserPerApp": 10485760,
"allowedMimePrefixes": ["image/"]
}
}
Список файлов
GET /api/extensions/sessions/:sessionId/files
Требуется locks:read.
Пример ответа:
{
"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"
}
]
}
Получить один файл
Используйте это, если ваше расширение уже имеет сохраненный идентификатор файла и вам нужна только новая подписанная ссылка R2.
GET /api/extensions/sessions/:sessionId/files/:fileId
Требуется locks:read.
Файл должен принадлежать одному и тому же расширению приложения, сессии и блокировке.
Пример ответа:
{
"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"
}
}
Загрузить файл
POST /api/extensions/sessions/:sessionId/files
Content-Type: multipart/form-data
Требуется locks:write.
Поля формы:
file: необходимый файл изображенияpurpose: необязательный короткий идентификатор, напримерpuzzle-imageилиpreview
Пример:
curl "https://chastify.net/api/extensions/sessions/SESSION_ID/files" \
-X POST \
-F "purpose=puzzle-image" \
-F "[email protected]"
Пример ответа:
{
"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"
}
}
Распространённые ошибки при загрузке:
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
Удалить файл
DELETE /api/extensions/sessions/:sessionId/files/:fileId
Требуется locks:write.
Файл должен принадлежать одному и тому же расширению приложения, сессии и блокировке.
Действия моста Iframe
Расширения iframe могут использовать родительский веб-мост для чтения файлов во время выполнения. Загрузка и удаление файлов во время выполнения являются изменениями сессии и должны выполняться вашим бэкэндом с использованием ключа API разработчика, ограниченного областью действия приложения, плюс 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;
Поддерживаемые действия по строительству мостов:
files.capabilities-> проверьте, включено ли хранилище файлов и какие квоты применяются.files.list-> список файлов, принадлежащих текущей сессии расширенияfiles.get-> обновить один подписанный URL-адрес R2 из сохраненного идентификатора файла
В настройках iframe можно использовать дополнительные имена мостов до создания сеанса выполнения. В режиме настройки files.upload создает подготовленные файлы, files.list/files.staged.list отображает список непросроченных подготовленных файлов для текущего пользователя и приложения, а files.delete удаляет подготовленный файл. Контекст инициализации настройки включает storageDraftId; укажите это значение в сохраненной конфигурации как storageDraftId, если вы хотите, чтобы Chastify немедленно удалял неиспользуемые файлы из того же черновика при сохранении.
В настройках files.upload также принимается dataUrl для простых iframe-клиентов:
await bridge.request("files.upload", {
dataUrl: canvas.toDataURL("image/webp", 0.9),
filename: "preview.webp",
purpose: "preview"
}, 60000);
По возможности отдавайте предпочтение загрузке файлов с кодами File или Blob. Используйте dataUrl только для небольших сгенерированных изображений, поскольку полезные данные в формате base64 занимают больше места в памяти.
Подписанные ссылки
Идентификатор стабильного файла — id.
Используйте signedUrl для отображения или загрузки файла. Подписанные ссылки представляют собой кратковременные URL-адреса R2 GetObject, сгенерированные Chastify. publicUrl сохранен как псевдоним совместимости и в настоящее время содержит тот же подписанный URL-адрес.
Когда срок действия подписанной ссылки истекает, вызовите GET /api/extensions/sessions/:sessionId/files/:fileId, чтобы получить новую ссылку для одного сохраненного файла, или GET /api/extensions/sessions/:sessionId/files, чтобы обновить все ссылки на файлы сессии.
Жизненный цикл очистки
Файлы расширений удаляются с помощью очереди очистки, поддерживаемой BullMQ.
Уборка запланирована на следующее время:
- приложение-расширение удалено
- Документы, касающиеся блокировки/продления сессии, удаляются в процессе снятия блокировки/очистки архива.
- Файл удаляется явным образом через конечную точку файла сессии.
Очередь исключает дорогостоящие операции удаления R2 и очистки базы данных из пути обработки запроса. Если добавление в очередь не удается, сервер переключается на внутрипроцессную очистку после получения ответа, если существует контекст запроса, или на прямую очистку с наилучшими усилиями на более низком уровне жизненного цикла.
Примечания к производительности
- Проверка работоспособности должна выполняться до отображения интерфейса загрузки.
- На загрузку распространяются ограничения по количеству файлов, сессий и приложений.
- Для проверки квоты используются индексированные метаданные
UserFileдляscope + appId,scope + sessionIdиscope + lockId. - Очистка потоков, соответствующих записям файлов, вместо загрузки всех URL-адресов в память.
- Загруженные растровые изображения обрабатываются и сохраняются как оптимизированные веб-изображения.
Примечания по безопасности
- Не следует рассматривать предоставленные расширением URL-адреса изображений как надежное подтверждение завершения ввода.
- Сохраняйте в качестве файлов с доверенным расширением только записи, выданные с помощью Chastify.
- Привяжите записи файлов к
appId,sessionIdиlockId. - Для загрузки и удаления файлов необходимо установить значение
locks:write. - Проверить MIME-тип и отклонить SVG.
- Перед тем как разрешить широкое использование сервиса, оставьте включенными квоты, контролируемые администратором.
- Для любых рабочих процессов, зависящих от загруженных файлов, используйте проверку на стороне сервера.
Прямые маршруты против мостов
Используйте мост iframe, если ваше расширение работает внутри Chastify. Это позволит сохранить аутентификацию Chastify на родительской странице и избежать раскрытия сведений о маршруте iframe.
Используйте прямые маршруты сеансов только из пользовательского интерфейса Chastify или доверенных внутренних потоков, которые уже имеют действительную авторизацию сеанса расширения.