UNPKG

@nhost/hasura-storage-js

Version:

Hasura-storage client

731 lines (730 loc) 21.8 kB
var C = Object.defineProperty; var $ = (t, e, r) => e in t ? C(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r; var f = (t, e, r) => $(t, typeof e != "symbol" ? e + "" : e, r); import M from "fetch-ponyfill"; import w from "form-data"; import { createMachine as H, assign as c, spawn as N, send as P, actions as j } from "xstate"; let k = globalThis.fetch; const E = async (t, e, { accessToken: r, name: s, fileId: a, bucketId: i, adminSecret: o, onUploadProgress: d, headers: l = {} } = {}) => { var L; const h = { ...l }; i && e.append("bucket-id", i), o && (h["x-hasura-admin-secret"] = o), r && (h.Authorization = `Bearer ${r}`); const T = `${t}/files`; if (typeof XMLHttpRequest == "undefined") try { e instanceof w && (k = M().fetch); const u = await k(T, { method: "POST", headers: h, body: e // * https://github.com/form-data/form-data/issues/513 }), n = await u.json(); return u.ok ? { fileMetadata: n, error: null } : { error: { status: u.status, message: ((L = n == null ? void 0 : n.error) == null ? void 0 : L.message) || u.statusText, // * errors from hasura-storage are not codified error: u.statusText }, fileMetadata: null }; } catch (u) { return { error: { status: 0, message: u.message, error: u.message }, fileMetadata: null }; } return new Promise((u) => { let n = new XMLHttpRequest(); n.responseType = "json", n.onload = () => { var p, g, S, F, R, _, x, I; if (n.status < 200 || n.status >= 300) { const b = { error: (R = (F = (g = (p = n.response) == null ? void 0 : p.error) == null ? void 0 : g.message) != null ? F : (S = n.response) == null ? void 0 : S.error) != null ? R : n.response, message: (I = (x = (_ = n.response) == null ? void 0 : _.error) == null ? void 0 : x.message) != null ? I : n.response, status: n.status }; return u({ fileMetadata: null, error: b }); } return u({ fileMetadata: n.response, error: null }); }, n.onerror = () => { const p = { error: n.statusText, message: n.statusText, status: n.status }; return u({ fileMetadata: null, error: p }); }, d && n.upload.addEventListener("progress", d, !1), n.open("POST", T, !0), Object.entries(h).forEach(([p, g]) => { n.setRequestHeader(p, g); }), n.send(e); }); }; function D(t, e) { if (!e || Object.keys(e).length === 0) return t; const r = new URL(t), s = Object.entries(e).reduce( (a, [i, o]) => ({ ...a, [i.charAt(0)]: o }), {} ); return Object.entries(s).forEach(([a, i]) => { i && r.searchParams.set(a, i); }), r.toString(); } let m; typeof m == "undefined" && (m = M().fetch); class z { constructor({ url: e }) { f(this, "url"); f(this, "accessToken"); f(this, "adminSecret"); f(this, "headers", {}); this.url = e; } async uploadFormData({ formData: e, bucketId: r, headers: s }) { const { error: a, fileMetadata: i } = await E(this.url, e, { bucketId: r, headers: { ...this.headers, // global nhost storage client headers to be sent with all `uploadFormData` calls ...s // extra headers to be sent with a specific call }, accessToken: this.accessToken, adminSecret: this.adminSecret }); return a ? { fileMetadata: null, error: a } : i && !("processedFiles" in i) ? { fileMetadata: { processedFiles: [i] }, error: null } : { fileMetadata: i, error: null }; } async uploadFile({ file: e, bucketId: r, id: s, name: a, headers: i }) { const o = typeof window == "undefined" ? new w() : new FormData(); o.append("file[]", e), o.append("metadata[]", JSON.stringify({ id: s, name: a })); const { error: d, fileMetadata: l } = await E(this.url, o, { accessToken: this.accessToken, adminSecret: this.adminSecret, bucketId: r, fileId: s, name: a, headers: { ...this.headers, // global nhost storage client headers to be sent with all `uploadFile` calls ...i // extra headers to be sent with a specific call } }); return d ? { fileMetadata: null, error: d } : l && "processedFiles" in l ? { fileMetadata: l.processedFiles[0], error: null } : { fileMetadata: l, error: null }; } async downloadFile(e) { try { const { fileId: r, headers: s, ...a } = e, i = D( `${this.url}/files/${r}`, a ), o = await m(i, { method: "GET", headers: { ...this.generateAuthHeaders(), ...this.headers, // global nhost storage client headers to be sent with all `downloadFile` calls ...s // extra headers to be sent with a specific call } }); if (!o.ok) throw new Error(await o.text()); return { file: await o.blob(), error: null }; } catch (r) { return { file: null, error: r }; } } async getPresignedUrl(e) { try { const { fileId: r, headers: s } = e, a = await m(`${this.url}/files/${r}/presignedurl`, { method: "GET", headers: { ...this.generateAuthHeaders(), ...this.headers, // global nhost storage client headers to be sent with all `getPresignedUrl` calls ...s // extra headers to be sent with a specific call } }); if (!a.ok) throw new Error(await a.text()); return { presignedUrl: await a.json(), error: null }; } catch (r) { return { presignedUrl: null, error: r }; } } async delete(e) { try { const { fileId: r, headers: s } = e, a = await m(`${this.url}/files/${r}`, { method: "DELETE", headers: { ...this.generateAuthHeaders(), ...this.headers, // global nhost storage client headers to be sent with all `delete` calls ...s // extra headers to be sent with a specific call } }); if (!a.ok) throw new Error(await a.text()); return { error: null }; } catch (r) { return { error: r }; } } /** * Set the access token to use for authentication. * * @param accessToken Access token * @returns Hasura Storage API instance */ setAccessToken(e) { return this.accessToken = e, this; } /** * Set the admin secret to use for authentication. * * @param adminSecret Hasura admin secret * @returns Hasura Storage API instance */ setAdminSecret(e) { return this.adminSecret = e, this; } /** * Get global headers sent with all requests. * * @returns Record<string, string> */ getHeaders() { return this.headers; } /** * Set global headers to be sent with all requests. * * @param headers a key value pair headers object * @returns Hasura Storage API instance */ setHeaders(e) { return e ? (this.headers = { ...this.headers, ...e }, this) : this; } /** * Remove global headers sent with all requests, except for the role header to preserve * the role set by 'setRole' method. * * @returns {HasuraStorageApi} - Hasura Storage API instance. */ unsetHeaders() { const e = this.headers["x-hasura-role"]; return this.headers = e ? { "x-hasura-role": e } : {}, this; } generateAuthHeaders() { if (!(!this.adminSecret && !this.accessToken)) return this.adminSecret ? { "x-hasura-admin-secret": this.adminSecret } : { Authorization: `Bearer ${this.accessToken}` }; } } class W { constructor({ url: e, adminSecret: r }) { f(this, "url"); f(this, "api"); this.url = e, this.api = new z({ url: e }), this.setAdminSecret(r); } async upload(e) { return "file" in e ? this.api.uploadFile(e) : this.api.uploadFormData(e); } /** * Use `nhost.storage.getPublicUrl` to get the public URL of a file. The public URL can be used for un-authenticated users to access files. To access public files the `public` role must have permissions to select the file in the `storage.files` table. * * @example * ```ts * const publicUrl = nhost.storage.getPublicUrl({ fileId: '<File-ID>' }) * ``` * * @docs https://docs.nhost.io/reference/javascript/storage/get-public-url */ getPublicUrl(e) { const { fileId: r, ...s } = e; return D( `${this.url}/files/${r}`, s ); } /** * Use `nhost.storage.getPresignedUrl` to get a presigned URL of a file. To get a presigned URL the user must have permission to select the file in the `storage.files` table. * * @example * ```ts * const { presignedUrl, error} = await nhost.storage.getPresignedUrl({ fileId: '<File-ID>' }) * * if (error) { * throw error; * } * * console.log('url: ', presignedUrl.url) * console.log('expiration: ', presignedUrl.expiration) * ``` * * @docs https://docs.nhost.io/reference/javascript/storage/get-presigned-url */ async getPresignedUrl(e) { const { fileId: r, headers: s, ...a } = e, { presignedUrl: i, error: o } = await this.api.getPresignedUrl(e); if (o) return { presignedUrl: null, error: o }; if (!i) return { presignedUrl: null, error: new Error("Invalid file id") }; const d = D( i.url, a ); return { presignedUrl: { ...i, url: d }, error: null }; } /** * Use `nhost.storage.download` to download a file. To download a file the user must have permission to select the file in the `storage.files` table. * * @example * ```ts * const { file, error} = await nhost.storage.download({ fileId: '<File-ID>' }) * ``` * * @docs https://docs.nhost.io/reference/javascript/storage/download */ async download(e) { const { file: r, error: s } = await this.api.downloadFile(e); return s ? { file: null, error: s } : r ? { file: r, error: null } : { file: null, error: new Error("File does not exist") }; } /** * Use `nhost.storage.delete` to delete a file. To delete a file the user must have permissions to delete the file in the `storage.files` table. Deleting the file using `nhost.storage.delete()` will delete both the file and its metadata. * * @example * ```ts * const { error } = await nhost.storage.delete({ fileId: 'uuid' }) * ``` * * @docs https://docs.nhost.io/reference/javascript/storage/delete */ async delete(e) { const { error: r } = await this.api.delete(e); return r ? { error: r } : { error: null }; } /** * Use `nhost.storage.setAccessToken` to a set an access token to be used in subsequent storage requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically. * * @example * ```ts * nhost.storage.setAccessToken('some-access-token') * ``` * * @param accessToken Access token * * @docs https://docs.nhost.io/reference/javascript/storage/set-access-token */ setAccessToken(e) { return this.api.setAccessToken(e), this; } /** * Use `nhost.storage.adminSecret` to set the admin secret to be used for subsequent storage requests. This is useful if you want to run storage in "admin mode". * * @example * ```ts * nhost.storage.setAdminSecret('some-admin-secret') * ``` * * @param adminSecret Hasura admin secret * * @docs https://docs.nhost.io/reference/javascript/storage/set-admin-secret */ setAdminSecret(e) { return this.api.setAdminSecret(e), this; } /** * Use `nhost.storage.getHeaders` to get global headers sent with all storage requests. * * @example * ```ts * nhost.storage.getHeaders() * ``` * * @docs https://docs.nhost.io/reference/javascript/storage/get-headers */ getHeaders() { return this.api.getHeaders(); } /** * Use `nhost.storage.setHeaders` to set global headers to be sent for all subsequent storage requests. * * @example * ```ts * nhost.storage.setHeaders({ * 'x-hasura-role': 'admin' * }) * ``` * * @param headers key value headers object * * @docs https://docs.nhost.io/reference/javascript/storage/set-headers */ setHeaders(e) { return this.api.setHeaders(e), this; } /** * Use `nhost.storage.unsetHeaders` to remove the global headers sent for all subsequent storage requests. * * @example * ```ts * nhost.storage.unsetHeaders() * ``` * * @param headers key value headers object * * @docs https://docs.nhost.io/reference/javascript/storage/unset-headers */ unsetHeaders() { return this.api.unsetHeaders(), this; } } let O; typeof O == "undefined" && (O = w); const U = { progress: null, loaded: 0, error: null, bucketId: void 0, file: void 0, id: void 0 }, G = () => H( { predictableActionArguments: !0, schema: { context: {}, events: {} }, tsTypes: {}, context: { ...U }, initial: "idle", on: { DESTROY: { actions: "sendDestroy", target: "stopped" } }, states: { idle: { on: { ADD: { actions: "addFile" }, UPLOAD: { cond: "hasFile", target: "uploading" } } }, uploading: { entry: "resetProgress", on: { UPLOAD_PROGRESS: { actions: ["incrementProgress", "sendProgress"] }, UPLOAD_DONE: "uploaded", UPLOAD_ERROR: "error", CANCEL: "idle" }, invoke: { src: "uploadFile" } }, uploaded: { entry: ["setFileMetadata", "sendDone"], on: { ADD: { actions: "addFile", target: "idle" }, UPLOAD: { actions: "resetContext", target: "uploading" } } }, error: { entry: ["setError", "sendError"], on: { ADD: { actions: "addFile", target: "idle" }, UPLOAD: { actions: "resetContext", target: "uploading" } } }, stopped: { type: "final" } } }, { guards: { hasFile: (t, e) => !!t.file || !!e.file }, actions: { incrementProgress: c({ loaded: (t, { loaded: e }) => e, progress: (t, { progress: e }) => e }), setFileMetadata: c({ id: (t, { id: e }) => e, bucketId: (t, { bucketId: e }) => e, progress: (t) => 100 }), setError: c({ error: (t, { error: e }) => e }), sendProgress: () => { }, sendError: () => { }, sendDestroy: () => { }, sendDone: () => { }, resetProgress: c({ progress: (t) => null, loaded: (t) => 0 }), resetContext: c((t) => U), addFile: c({ file: (t, { file: e }) => e, bucketId: (t, { bucketId: e }) => e, id: (t, { id: e }) => e }) }, services: { uploadFile: (t, e) => (r) => { const s = e.file || t.file, a = new O(); a.append("file[]", s); let i = 0; return E(e.url, a, { fileId: e.id || t.id, bucketId: e.bucketId || t.bucketId, accessToken: e.accessToken, adminSecret: e.adminSecret, name: e.name || s.name, onUploadProgress: (o) => { const d = o.total ? Math.round(o.loaded * s.size / o.total) : 0, l = d - i; i = d, r({ type: "UPLOAD_PROGRESS", progress: o.total ? Math.round(d * 100 / o.total) : 0, loaded: d, additions: l }); } }).then(({ fileMetadata: o, error: d }) => { if (d && r({ type: "UPLOAD_ERROR", error: d }), o && !("processedFiles" in o)) { const { id: l, bucketId: h } = o; r({ type: "UPLOAD_DONE", id: l, bucketId: h }); } if (o && "processedFiles" in o) { const { id: l, bucketId: h } = o.processedFiles[0]; r({ type: "UPLOAD_DONE", id: l, bucketId: h }); } }), () => { }; } } } ), { pure: y, sendParent: A } = j, Y = () => H( { id: "files-list", schema: { context: {}, events: {} }, tsTypes: {}, predictableActionArguments: !0, context: { progress: null, files: [], loaded: 0, total: 0 }, initial: "idle", on: { UPLOAD: { cond: "hasFileToDownload", actions: "addItem", target: "uploading" }, ADD: { actions: "addItem" }, REMOVE: { actions: "removeItem" } }, states: { idle: { entry: ["resetProgress", "resetLoaded", "resetTotal"], on: { CLEAR: { actions: "clearList", target: "idle" } } }, uploading: { entry: ["upload", "startProgress", "resetLoaded", "resetTotal"], on: { UPLOAD_PROGRESS: { actions: ["incrementProgress"] }, UPLOAD_DONE: [ { cond: "isAllUploaded", target: "uploaded" }, { cond: "isAllUploadedOrError", target: "error" } ], UPLOAD_ERROR: [ { cond: "isAllUploaded", target: "uploaded" }, { cond: "isAllUploadedOrError", target: "error" } ], CANCEL: { actions: "cancel", target: "idle" } } }, uploaded: { entry: "setUploaded", on: { CLEAR: { actions: "clearList", target: "idle" } } }, error: { on: { CLEAR: { actions: "clearList", target: "idle" } } } } }, { guards: { hasFileToDownload: (t, e) => t.files.some((r) => r.getSnapshot().matches("idle")) || !!e.files, isAllUploaded: (t) => t.files.every((e) => { var r; return (r = e.getSnapshot()) == null ? void 0 : r.matches("uploaded"); }), isAllUploadedOrError: (t) => t.files.every((e) => { const r = e.getSnapshot(); return (r == null ? void 0 : r.matches("error")) || (r == null ? void 0 : r.matches("uploaded")); }) }, actions: { incrementProgress: c((t, e) => { const r = t.loaded + e.additions, s = Math.round(r * 100 / t.total); return { ...t, loaded: r, progress: s }; }), setUploaded: c({ progress: (t) => 100, loaded: ({ files: t }) => t.map((e) => e.getSnapshot()).filter((e) => e.matches("uploaded")).reduce((e, r) => { var s; return e + ((s = r.context.file) == null ? void 0 : s.size); }, 0) }), resetTotal: c({ total: ({ files: t }) => t.map((e) => e.getSnapshot()).filter((e) => !e.matches("uploaded")).reduce((e, r) => { var s; return e + ((s = r.context.file) == null ? void 0 : s.size); }, 0) }), resetLoaded: c({ loaded: (t) => 0 }), startProgress: c({ progress: (t) => 0 }), resetProgress: c({ progress: (t) => null }), addItem: c((t, { files: e, bucketId: r }) => { const s = e ? Array.isArray(e) ? e : "item" in e ? Array.from(e) : [e] : [], a = t.total + s.reduce((o, d) => o + d.size, 0), i = Math.round(t.loaded * 100 / a); return { files: [ ...t.files, ...s.map( (o) => N( G().withConfig({ actions: { sendProgress: A((d, { additions: l }) => ({ type: "UPLOAD_PROGRESS", additions: l })), sendDone: A("UPLOAD_DONE"), sendError: A("UPLOAD_ERROR"), sendDestroy: A("REMOVE") } }).withContext({ ...U, file: o, bucketId: r }), { sync: !0 } ) ) ], total: a, loaded: t.loaded, progress: i }; }), removeItem: c({ files: (t) => t.files.filter((e) => { var s, a; const r = (s = e.getSnapshot()) == null ? void 0 : s.matches("stopped"); return r && ((a = e.stop) == null || a.call(e)), !r; }) }), clearList: y( (t) => t.files.map((e) => P({ type: "DESTROY" }, { to: e.id })) ), upload: y((t, e) => t.files.map((r) => P(e, { to: r.id }))), cancel: y( (t) => t.files.map((e) => P({ type: "CANCEL" }, { to: e.id })) ) } } ), J = async (t, e) => new Promise((r) => { e.send({ type: "UPLOAD", ...t }), e.subscribe((s) => { var a; s.matches("error") ? r({ error: s.context.error, isError: !0, isUploaded: !1 }) : s.matches("uploaded") && r({ error: null, isError: !1, isUploaded: !0, id: s.context.id, bucketId: s.context.id, name: (a = s.context.file) == null ? void 0 : a.name }); }); }), K = async (t, e) => new Promise((r) => { e.send({ type: "UPLOAD", ...t, files: t.files }), e.onTransition((s) => { s.matches("error") ? r({ errors: s.context.files.filter((a) => { var i; return (i = a.getSnapshot()) == null ? void 0 : i.context.error; }), isError: !0, files: [] }) : s.matches("uploaded") && r({ errors: [], isError: !1, files: s.context.files }); }); }); export { z as HasuraStorageApi, W as HasuraStorageClient, U as INITIAL_FILE_CONTEXT, D as appendImageTransformationParameters, G as createFileUploadMachine, Y as createMultipleFilesUploadMachine, J as uploadFilePromise, K as uploadMultipleFilesPromise }; //# sourceMappingURL=index.esm.js.map