UNPKG

@supabase/storage-js

Version:

Isomorphic storage client for Supabase.

532 lines 23.5 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { isStorageError, StorageError, StorageUnknownError } from '../lib/errors'; import { get, head, post, put, remove } from '../lib/fetch'; import { recursiveToCamel, resolveFetch } from '../lib/helpers'; const DEFAULT_SEARCH_OPTIONS = { limit: 100, offset: 0, sortBy: { column: 'name', order: 'asc', }, }; const DEFAULT_FILE_OPTIONS = { cacheControl: '3600', contentType: 'text/plain;charset=UTF-8', upsert: false, }; export default class StorageFileApi { constructor(url, headers = {}, bucketId, fetch) { this.url = url; this.headers = headers; this.bucketId = bucketId; this.fetch = resolveFetch(fetch); } /** * Uploads a file to an existing bucket or replaces an existing file at the specified path with a new one. * * @param method HTTP method. * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param fileBody The body of the file to be stored in the bucket. */ uploadOrUpdate(method, path, fileBody, fileOptions) { return __awaiter(this, void 0, void 0, function* () { try { let body; const options = Object.assign(Object.assign({}, DEFAULT_FILE_OPTIONS), fileOptions); let headers = Object.assign(Object.assign({}, this.headers), (method === 'POST' && { 'x-upsert': String(options.upsert) })); const metadata = options.metadata; if (typeof Blob !== 'undefined' && fileBody instanceof Blob) { body = new FormData(); body.append('cacheControl', options.cacheControl); if (metadata) { body.append('metadata', this.encodeMetadata(metadata)); } body.append('', fileBody); } else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) { body = fileBody; body.append('cacheControl', options.cacheControl); if (metadata) { body.append('metadata', this.encodeMetadata(metadata)); } } else { body = fileBody; headers['cache-control'] = `max-age=${options.cacheControl}`; headers['content-type'] = options.contentType; if (metadata) { headers['x-metadata'] = this.toBase64(this.encodeMetadata(metadata)); } } if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) { headers = Object.assign(Object.assign({}, headers), fileOptions.headers); } const cleanPath = this._removeEmptyFolders(path); const _path = this._getFinalPath(cleanPath); const data = yield (method == 'PUT' ? put : post)(this.fetch, `${this.url}/object/${_path}`, body, Object.assign({ headers }, ((options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}))); return { data: { path: cleanPath, id: data.Id, fullPath: data.Key }, error: null, }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Uploads a file to an existing bucket. * * @param path The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param fileBody The body of the file to be stored in the bucket. */ upload(path, fileBody, fileOptions) { return __awaiter(this, void 0, void 0, function* () { return this.uploadOrUpdate('POST', path, fileBody, fileOptions); }); } /** * Upload a file with a token generated from `createSignedUploadUrl`. * @param path The file path, including the file name. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload. * @param token The token generated from `createSignedUploadUrl` * @param fileBody The body of the file to be stored in the bucket. */ uploadToSignedUrl(path, token, fileBody, fileOptions) { return __awaiter(this, void 0, void 0, function* () { const cleanPath = this._removeEmptyFolders(path); const _path = this._getFinalPath(cleanPath); const url = new URL(this.url + `/object/upload/sign/${_path}`); url.searchParams.set('token', token); try { let body; const options = Object.assign({ upsert: DEFAULT_FILE_OPTIONS.upsert }, fileOptions); const headers = Object.assign(Object.assign({}, this.headers), { 'x-upsert': String(options.upsert) }); if (typeof Blob !== 'undefined' && fileBody instanceof Blob) { body = new FormData(); body.append('cacheControl', options.cacheControl); body.append('', fileBody); } else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) { body = fileBody; body.append('cacheControl', options.cacheControl); } else { body = fileBody; headers['cache-control'] = `max-age=${options.cacheControl}`; headers['content-type'] = options.contentType; } const data = yield put(this.fetch, url.toString(), body, { headers }); return { data: { path: cleanPath, fullPath: data.Key }, error: null, }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Creates a signed upload URL. * Signed upload URLs can be used to upload files to the bucket without further authentication. * They are valid for 2 hours. * @param path The file path, including the current file name. For example `folder/image.png`. * @param options.upsert If set to true, allows the file to be overwritten if it already exists. */ createSignedUploadUrl(path, options) { return __awaiter(this, void 0, void 0, function* () { try { let _path = this._getFinalPath(path); const headers = Object.assign({}, this.headers); if (options === null || options === void 0 ? void 0 : options.upsert) { headers['x-upsert'] = 'true'; } const data = yield post(this.fetch, `${this.url}/object/upload/sign/${_path}`, {}, { headers }); const url = new URL(this.url + data.url); const token = url.searchParams.get('token'); if (!token) { throw new StorageError('No token returned by API'); } return { data: { signedUrl: url.toString(), path, token }, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Replaces an existing file at the specified path with a new one. * * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to update. * @param fileBody The body of the file to be stored in the bucket. */ update(path, fileBody, fileOptions) { return __awaiter(this, void 0, void 0, function* () { return this.uploadOrUpdate('PUT', path, fileBody, fileOptions); }); } /** * Moves an existing file to a new path in the same bucket. * * @param fromPath The original file path, including the current file name. For example `folder/image.png`. * @param toPath The new file path, including the new file name. For example `folder/image-new.png`. * @param options The destination options. */ move(fromPath, toPath, options) { return __awaiter(this, void 0, void 0, function* () { try { const data = yield post(this.fetch, `${this.url}/object/move`, { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath, destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket, }, { headers: this.headers }); return { data, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Copies an existing file to a new path in the same bucket. * * @param fromPath The original file path, including the current file name. For example `folder/image.png`. * @param toPath The new file path, including the new file name. For example `folder/image-copy.png`. * @param options The destination options. */ copy(fromPath, toPath, options) { return __awaiter(this, void 0, void 0, function* () { try { const data = yield post(this.fetch, `${this.url}/object/copy`, { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath, destinationBucket: options === null || options === void 0 ? void 0 : options.destinationBucket, }, { headers: this.headers }); return { data: { path: data.Key }, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Creates a signed URL. Use a signed URL to share a file for a fixed amount of time. * * @param path The file path, including the current file name. For example `folder/image.png`. * @param expiresIn The number of seconds until the signed URL expires. For example, `60` for a URL which is valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. */ createSignedUrl(path, expiresIn, options) { return __awaiter(this, void 0, void 0, function* () { try { let _path = this._getFinalPath(path); let data = yield post(this.fetch, `${this.url}/object/sign/${_path}`, Object.assign({ expiresIn }, ((options === null || options === void 0 ? void 0 : options.transform) ? { transform: options.transform } : {})), { headers: this.headers }); const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? '' : options.download}` : ''; const signedUrl = encodeURI(`${this.url}${data.signedURL}${downloadQueryParam}`); data = { signedUrl }; return { data, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Creates multiple signed URLs. Use a signed URL to share a file for a fixed amount of time. * * @param paths The file paths to be downloaded, including the current file names. For example `['folder/image.png', 'folder2/image2.png']`. * @param expiresIn The number of seconds until the signed URLs expire. For example, `60` for URLs which are valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. */ createSignedUrls(paths, expiresIn, options) { return __awaiter(this, void 0, void 0, function* () { try { const data = yield post(this.fetch, `${this.url}/object/sign/${this.bucketId}`, { expiresIn, paths }, { headers: this.headers }); const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? '' : options.download}` : ''; return { data: data.map((datum) => (Object.assign(Object.assign({}, datum), { signedUrl: datum.signedURL ? encodeURI(`${this.url}${datum.signedURL}${downloadQueryParam}`) : null }))), error: null, }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Downloads a file from a private bucket. For public buckets, make a request to the URL returned from `getPublicUrl` instead. * * @param path The full path and file name of the file to be downloaded. For example `folder/image.png`. * @param options.transform Transform the asset before serving it to the client. */ download(path, options) { return __awaiter(this, void 0, void 0, function* () { const wantsTransformation = typeof (options === null || options === void 0 ? void 0 : options.transform) !== 'undefined'; const renderPath = wantsTransformation ? 'render/image/authenticated' : 'object'; const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : options.transform) || {}); const queryString = transformationQuery ? `?${transformationQuery}` : ''; try { const _path = this._getFinalPath(path); const res = yield get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString}`, { headers: this.headers, noResolveJson: true, }); const data = yield res.blob(); return { data, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Retrieves the details of an existing file. * @param path */ info(path) { return __awaiter(this, void 0, void 0, function* () { const _path = this._getFinalPath(path); try { const data = yield get(this.fetch, `${this.url}/object/info/${_path}`, { headers: this.headers, }); return { data: recursiveToCamel(data), error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Checks the existence of a file. * @param path */ exists(path) { return __awaiter(this, void 0, void 0, function* () { const _path = this._getFinalPath(path); try { yield head(this.fetch, `${this.url}/object/${_path}`, { headers: this.headers, }); return { data: true, error: null }; } catch (error) { if (isStorageError(error) && error instanceof StorageUnknownError) { const originalError = error.originalError; if ([400, 404].includes(originalError === null || originalError === void 0 ? void 0 : originalError.status)) { return { data: false, error }; } } throw error; } }); } /** * A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset. * This function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset. * * @param path The path and name of the file to generate the public URL for. For example `folder/image.png`. * @param options.download Triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. */ getPublicUrl(path, options) { const _path = this._getFinalPath(path); const _queryString = []; const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `download=${options.download === true ? '' : options.download}` : ''; if (downloadQueryParam !== '') { _queryString.push(downloadQueryParam); } const wantsTransformation = typeof (options === null || options === void 0 ? void 0 : options.transform) !== 'undefined'; const renderPath = wantsTransformation ? 'render/image' : 'object'; const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : options.transform) || {}); if (transformationQuery !== '') { _queryString.push(transformationQuery); } let queryString = _queryString.join('&'); if (queryString !== '') { queryString = `?${queryString}`; } return { data: { publicUrl: encodeURI(`${this.url}/${renderPath}/public/${_path}${queryString}`) }, }; } /** * Deletes files within the same bucket * * @param paths An array of files to delete, including the path and file name. For example [`'folder/image.png'`]. */ remove(paths) { return __awaiter(this, void 0, void 0, function* () { try { const data = yield remove(this.fetch, `${this.url}/object/${this.bucketId}`, { prefixes: paths }, { headers: this.headers }); return { data, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } /** * Get file metadata * @param id the file id to retrieve metadata */ // async getMetadata( // id: string // ): Promise< // | { // data: Metadata // error: null // } // | { // data: null // error: StorageError // } // > { // try { // const data = await get(this.fetch, `${this.url}/metadata/${id}`, { headers: this.headers }) // return { data, error: null } // } catch (error) { // if (isStorageError(error)) { // return { data: null, error } // } // throw error // } // } /** * Update file metadata * @param id the file id to update metadata * @param meta the new file metadata */ // async updateMetadata( // id: string, // meta: Metadata // ): Promise< // | { // data: Metadata // error: null // } // | { // data: null // error: StorageError // } // > { // try { // const data = await post( // this.fetch, // `${this.url}/metadata/${id}`, // { ...meta }, // { headers: this.headers } // ) // return { data, error: null } // } catch (error) { // if (isStorageError(error)) { // return { data: null, error } // } // throw error // } // } /** * Lists all the files within a bucket. * @param path The folder path. */ list(path, options, parameters) { return __awaiter(this, void 0, void 0, function* () { try { const body = Object.assign(Object.assign(Object.assign({}, DEFAULT_SEARCH_OPTIONS), options), { prefix: path || '' }); const data = yield post(this.fetch, `${this.url}/object/list/${this.bucketId}`, body, { headers: this.headers }, parameters); return { data, error: null }; } catch (error) { if (isStorageError(error)) { return { data: null, error }; } throw error; } }); } encodeMetadata(metadata) { return JSON.stringify(metadata); } toBase64(data) { if (typeof Buffer !== 'undefined') { return Buffer.from(data).toString('base64'); } return btoa(data); } _getFinalPath(path) { return `${this.bucketId}/${path}`; } _removeEmptyFolders(path) { return path.replace(/^\/|\/$/g, '').replace(/\/+/g, '/'); } transformOptsToQueryString(transform) { const params = []; if (transform.width) { params.push(`width=${transform.width}`); } if (transform.height) { params.push(`height=${transform.height}`); } if (transform.resize) { params.push(`resize=${transform.resize}`); } if (transform.format) { params.push(`format=${transform.format}`); } if (transform.quality) { params.push(`quality=${transform.quality}`); } return params.join('&'); } } //# sourceMappingURL=StorageFileApi.js.map