Перейти к основному содержимому

Хранилище файлов расширений

Функция хранения файлов расширений позволяет включенному расширению загружать файлы изображений, относящиеся к сеансу блокировки расширения.

Используйте его, когда 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"
  • appId
  • sessionId и lockId для файлов времени выполнения/сессии
  • templateId — для файлов, защищенных сохраненным шаблоном блокировки.
  • staged, draftId и expiresAt — это номера файлов для загрузки настроек, которые еще не были востребованы.
  • extensionKey
  • purpose

Административный шлюз и квоты

Настройка тестовых конечных точек

Используйте тестовую среду настройки только в том случае, если пользовательский интерфейс настройки запускается до создания сеанса расширения.

Это временный процесс загрузки. Он существует для экранов настройки/конфигурации, где пользователь может загрузить файл, затем закрыть модальное окно или отключить расширение перед созданием блокировки/сессии. Подготовленный файл не является постоянным, пока он не будет подтвержден путем сохранения конфигурации расширения, которая ссылается на него.

В интерфейсе настройки можно проверить доступность и текущее поэтапное использование перед отображением элементов управления загрузкой:

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-image
  • draftId: необязательный идентификатор черновика настроек; используйте одно и то же значение, пока открыто одно окно настроек.

В ответе содержится стабильный код 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" \

Пример ответа:

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

Удалить файл

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 или доверенных внутренних потоков, которые уже имеют действительную авторизацию сеанса расширения.