Хранилища для загрузок
Готовые загрузчики из @bloklabs/presets. Они дают Blok место для загруженных файлов, поэтому вам не нужно писать свой обработчик загрузки. Каждый подключается прямо в опцию конфигурации uploader.
| Пресет | Повторно размещает файл по URL? | Готов к продакшену? |
|---|---|---|
| Свой эндпоинт | Да | Да |
| Supabase | Нет | Да |
| Presigned-ссылки | Нет | Да |
| Cloudinary | Да | Да |
| IndexedDB | Нет | Нет |
Свой эндпоинт
Работает с бэкендом, который у вас уже есть. Каждая загрузка идёт через ваш сервер. Сервер сам решает, где в итоге окажутся байты.
Повторное размещение файла по URL
Да. Ваш эндпоинт делает запрос на стороне сервера, поэтому браузеру не нужно самому обращаться к стороннему URL.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
baseUrl | string | (required) | Адрес вашего сервиса загрузки, например "https://api.myapp.com". |
field | string | "file" | Имя multipart-поля, которое эндпоинт читает при POST /upload. |
headers | Record<string, string> | (() => Promise<Record<string, string>>) | undefined | Дополнительные заголовки для каждого запроса. Передайте функцию, чтобы выпускать свежий токен доступа на каждую загрузку. |
Настройка хранилища
- Сервер, который отвечает на POST {baseUrl}/upload (multipart) и POST {baseUrl}/upload-by-url ({ url }) JSON-объектом { url, fileName? }.
- Эндпоинт сам решает, где хранить файл: S3, дисковый том или Supabase. Пресет об этом ничего не знает.
- URL, который получает ваш эндпоинт /upload-by-url, приходит от пользователя. Проверяйте его на сервере и блокируйте внутренние и приватные адреса до того, как обращаться по нему. Иначе вы построили SSRF-вектор.
Пример использования
import { fetchStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: fetchStorage({ baseUrl: 'https://api.myapp.com' }),
});Supabase
Загружает файлы прямо в бакет Supabase Storage, используя клиент, который вы и так инициализируете.
Повторное размещение файла по URL
Нет. Повторное размещение удалённого URL требует запроса со стороны сервера, а браузер не может сделать его за Supabase. uploadByUrl остаётся undefined, поэтому Blok сохраняет URL как есть и не делает вид, что файл размещён заново.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
bucket | string | ((kind: AssetKind) => string) | "blok" | Имя бакета или функция от типа ассета, если бакеты разные для разных типов. |
path | (file: File, ctx: UploadContext) => string | random name, original extension kept | Путь к объекту внутри бакета. |
Настройка хранилища
- Создайте бакет в панели Supabase (имя по умолчанию "blok" или то, которое вернёт `bucket`).
- Сделайте бакет публичным или добавьте политику Storage, дающую роли anon права на SELECT. Пресет возвращает результат getPublicUrl() напрямую, поэтому нечитаемый объект превращается в битую картинку.
- Добавьте политику Storage на INSERT для той роли, под которой аутентифицируется клиент.
Пример использования
import { createClient } from '@supabase/supabase-js';
import { supabaseStorage } from '@bloklabs/presets';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
new Blok({ holder: 'editor', uploader: supabaseStorage(supabase, { bucket: 'blok' }) });Presigned-ссылки
Для S3 и S3-совместимых хранилищ (R2, MinIO, GCS). Ваш бэкенд выпускает короткоживущий подписанный URL. Браузер отправляет файл прямо по нему методом PUT.
Повторное размещение файла по URL
Нет. Повторное размещение удалённого файла требует запроса с сервера, а подписанный PUT-URL так не умеет. uploadByUrl остаётся undefined, поэтому Blok просто сохраняет URL как есть.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
sign | (request: SignRequest) => Promise<SignedTarget> | (required) | Вызывается с { fileName, mimeType, size, kind }. Должна вернуть { uploadUrl, publicUrl, headers? } от вашего бэкенда. |
Настройка хранилища
- Эндпоинт на бэкенде, который создаёт presigned PUT-URL (например, через S3 PutObjectCommand) и возвращает его как SignedTarget.
- Правило CORS на бакете, которое разрешает PUT с origin вашего приложения. Оно должно разрешать и заголовки, которые шлёт этот пресет, в первую очередь Content-Type. Браузер делает PUT прямо в бакет, поэтому именно на этом шаге обычно всё и застревает.
Пример использования
import { presignedStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: presignedStorage({ sign: (request) => api.sign(request) }),
});Cloudinary
Загружает файлы прямо в Cloudinary через неподписанный upload preset. Бэкенд не нужен.
Повторное размещение файла по URL
Да. Cloudinary сам запрашивает удалённый URL, как только браузер его передаст. Повторное размещение работает без вашего собственного сервера.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
cloudName | string | (required) | Ваш cloud name в Cloudinary. |
uploadPreset | string | (required) | Должен быть НЕПОДПИСАННЫЙ upload preset. Подписанному нужен сервер. |
folder | string | undefined | Необязательная папка, в которую загружать файлы. |
Настройка хранилища
- Аккаунт Cloudinary и его cloud name.
- Upload preset с режимом подписи Unsigned (Settings → Upload → Upload presets). Подписанный пресет молча падает из браузера, потому что подписать запрос некому.
- По замыслу самого Cloudinary аудио проходит через его видеоконвейер. Пресет раскладывает типы по resource types с учётом этого. Настраивать тут ничего не нужно.
Пример использования
import { cloudinaryStorage } from '@bloklabs/presets';
new Blok({
holder: 'editor',
uploader: cloudinaryStorage({ cloudName: 'my-cloud', uploadPreset: 'blok-unsigned' }),
});IndexedDB
Хранит загруженные файлы как блобы в собственном IndexedDB браузера. Ничего не покидает устройство.
Повторное размещение файла по URL
Нет. Размещать заново некуда, поэтому удалённый URL просто сохраняется как есть. У IndexedDB есть только uploadByFile.
Не для продакшена
Только для демо и прототипов. Байты лежат в браузере одного посетителя. На другом устройстве или в другом браузере их уже нет. Они исчезают, как только пользователь очистит данные сайта.
Пресет существует потому, что встроенный запасной вариант Blok (blob: URL) не переживает даже перезагрузку страницы. Из-за этого демо выглядит сломанным ещё до того, как вы настроили настоящее хранилище.
Конфигурация
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
dbName | string | "blok-assets" | Имя базы данных IndexedDB. |
Настройка хранилища
- Ничего. Пресету не нужен внешний сервис, в этом и смысл.
- Подключите экспортируемый хелпер resolveBlokObjectUrl(url) везде, где вы отрисовываете загруженный файл. uploadByFile возвращает ссылку вида blok:asset/…, а не готовый URL. Ничто не превратит её обратно в blob: URL автоматически.
- Если вы передали свой dbName в indexedDBStorage(), передайте тот же dbName в resolveBlokObjectUrl(url, { dbName }). При расхождении будет прочитана не та база. Вы получите null без ошибки, исключения не будет.
Пример использования
import { indexedDBStorage, resolveBlokObjectUrl } from '@bloklabs/presets';
new Blok({ holder: 'editor', uploader: indexedDBStorage() });
// Wherever you render a stored asset's url, e.g. an <img src>:
const displayUrl = await resolveBlokObjectUrl(asset.url);