import { LibraryModel } from '../models/libraries.js' export type FetchOptions = Parameters[1] type FetchPreflight = (url: string, options?: FetchOptions) => Promise export interface TransportOptions { backend: string cdn: string frontend: string formsBackend?: string inventory: string inventoryEnv: string sitecorePostfix: string accessToken: string apiKey: string preflight: FetchPreflight library?: LibraryModel verbose?: boolean useProxy?: boolean onError: (e?: Error, message?: string) => any } export interface Transport extends TransportOptions {} export let nanoid = (t = 21) => Transport.crypto .getRandomValues(new Uint8Array(t)) .reduce( (t: string, e: number) => (t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? '-' : '_'), '' ) const nodeVer = typeof process !== 'undefined' && process.versions?.node export async function getCrypto(): Promise { if (typeof crypto != 'undefined') return crypto try { const packageName = 'node:crypto' var { webcrypto } = require(packageName) return webcrypto } catch (e) { try { const { webcrypto } = await import(/* @vite-ignore */ /* webpackIgnore: true */ 'node'.substring(0) + ':crypto') return webcrypto as typeof crypto } catch (e) { console.log('Crypto is not found. Update to node that has node:crypto ', e) } } return crypto } export async function getFetch(): Promise { if (typeof fetch != 'undefined') { return fetch } try { const packageName = 'node-fetch' return require(packageName) } catch (e) { try { const { default: nodeFetch } = await import( /* @vite-ignore */ /* webpackIgnore: true */ 'node'.substring(0) + '-fetch' ) return nodeFetch as unknown as typeof fetch } catch (e) { throw new Error('fetch() is not available. Install `node-fetch` or update node to v17+') } } } export class HTTPError extends Error { status: number cause: Error | string constructor(message: string, details: { cause?: any; status: number }) { super(message, details) this.status = details.status } } export class Transport implements TransportOptions { static cryptoPromise: Promise static fetchPromise: Promise static crypto: typeof crypto static fetch: typeof fetch static nanoid = nanoid static defaults: Partial = { cdn: `https://feaas.blob.core.windows.net`, backend: 'https://components.sitecorecloud.io/api', frontend: 'https://components.sitecorecloud.io', formsBackend: 'https://forms-entities-{region}.sitecorecloud.io', inventoryEnv: 'production', sitecorePostfix: 'sitecorecloud.io', inventory: 'https://platform-inventory.sitecorecloud.io' } nanoid = nanoid constructor(options?: Partial) { Object.assign(this, Transport.defaults) Object.assign(this, options) } async checkFetchResponse(response: Response) { if (!response.ok) { const isJsonResponse = response.headers.get('content-type')?.includes('application/json') if (isJsonResponse) { const json = await response.json() throw new HTTPError(json.message, { status: response.status }) } else { throw new HTTPError(`Request failed with ${response.status} status`, { status: response.status }) } } return response } parseObjectDates = (object: any): any => { if (object == null || typeof object != 'object') { return object } if (Array.isArray(object)) { return object.map(this.parseObjectDates, this) } var result: any = {} for (var property in object) { if (property.endsWith('At') && object[property] != null) { result[property] = new Date(object[property]) } else { result[property] = this.parseObjectDates(object[property]) } } return result } log(...args: any[]) { if (this.verbose) { console.log(...args) } } async fetch(url: string, options: FetchOptions = {}): Promise { if (this.preflight) options = await this.preflight(url, options) const absoluteUrl = url.startsWith('/') ? this.backend + url : url this.log(options.method?.toUpperCase() || 'GET', ' ' + absoluteUrl) const apiKeyHeader = this.apiKey || this.library?.apiKey ? { 'x-api-key': this.apiKey || this.library?.apiKey } : {} const authorizationHeader = this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {} const initOptions = { ...options, headers: { 'Content-Type': 'application/json', ...authorizationHeader, ...apiKeyHeader, ...options.headers } } const fetch = Transport.fetch || (await Transport.fetchPromise) return fetch(absoluteUrl, initOptions).then(this.checkFetchResponse) } async fetchJSON(url: string, options: FetchOptions = {}): Promise { return this.fetch(url, options) .then((res) => { return res.json() }) .then(this.parseObjectDates) .then((object: Object | Object[]) => { if (object) { if (Array.isArray(object)) { this.log(' > ', object.length, ' items') } else { this.log( ' > #', (object as any)['id'], (object as any)['version'] ? '(v' + (object as any)['revision'] + ')' : '' ) } } return object }) } async fetchExternalJSON(url: string, options: FetchOptions = {}): Promise { if (this.useProxy) { return this.proxyJSON(url, options) } else { return this.fetchJSON(url, options) } } async proxyJSON(url: string, options?: FetchOptions) { return this.fetchJSON(`/proxy/fetch`, { method: 'POST', body: JSON.stringify({ url, options }) }) } async proxy(url: string, options?: FetchOptions): Promise { return this.fetch(`/proxy/fetch`, { method: 'POST', body: JSON.stringify({ url, options }) }) } async signUpload(category: string) { return this.fetchJSON(`/files/generateSAS`, { method: 'POST', body: JSON.stringify({ category }) }) } async uploadBlob( category: 'uploads' | 'images' | 'thumbnails' | 'webcomponents', name: string, blob: Buffer | Blob | ArrayBufferView | ArrayBuffer, options: FetchOptions = {} ) { const { url } = await this.signUpload(category) const domain = url.split('?')[0] const blobUrl = `${domain}/${category}/${name}` const uploadUrl = url.replace('?', `/${category}/${name}?`) const fetch = Transport.fetch || (await Transport.fetchPromise) return await fetch(uploadUrl, { method: 'PUT', body: blob, ...options, headers: { accept: 'application/xml', 'x-ms-blob-type': 'BlockBlob', 'x-ms-revision': '2021-08-06', ...options.headers } }).then(() => { return { url: blobUrl } }) //return Transport.fetch(url) } reportError(e: Error, message?: string) { this.onError?.(e, message) } } if (typeof crypto != 'undefined') Transport.crypto = crypto if (typeof fetch != 'undefined') Transport.fetch = fetch Transport.fetchPromise = getFetch().then((fetch) => (Transport.fetch = fetch)) Transport.cryptoPromise = getCrypto().then((crypto) => (Transport.crypto = crypto)) export default Transport