import { resolveFetch } from './helper' import { Fetch, FunctionsFetchError, FunctionsHttpError, FunctionsRelayError, FunctionsResponse, FunctionInvokeOptions, FunctionRegion, } from './types' export class FunctionsClient { protected url: string protected headers: Record protected region: FunctionRegion protected fetch: Fetch constructor( url: string, { headers = {}, customFetch, region = FunctionRegion.Any, }: { headers?: Record customFetch?: Fetch region?: FunctionRegion } = {} ) { this.url = url this.headers = headers this.region = region this.fetch = resolveFetch(customFetch) } /** * Updates the authorization header * @param token - the new jwt token sent in the authorisation header */ setAuth(token: string) { this.headers.Authorization = `Bearer ${token}` } /** * Invokes a function * @param functionName - The name of the Function to invoke. * @param options - Options for invoking the Function. */ async invoke( functionName: string, options: FunctionInvokeOptions = {} ): Promise> { try { const { headers, method, body: functionArgs } = options let _headers: Record = {} let { region } = options if (!region) { region = this.region } if (region && region !== 'any') { _headers['x-region'] = region } let body: any if ( functionArgs && ((headers && !Object.prototype.hasOwnProperty.call(headers, 'Content-Type')) || !headers) ) { if ( (typeof Blob !== 'undefined' && functionArgs instanceof Blob) || functionArgs instanceof ArrayBuffer ) { // will work for File as File inherits Blob // also works for ArrayBuffer as it is the same underlying structure as a Blob _headers['Content-Type'] = 'application/octet-stream' body = functionArgs } else if (typeof functionArgs === 'string') { // plain string _headers['Content-Type'] = 'text/plain' body = functionArgs } else if (typeof FormData !== 'undefined' && functionArgs instanceof FormData) { // don't set content-type headers // Request will automatically add the right boundary value body = functionArgs } else { // default, assume this is JSON _headers['Content-Type'] = 'application/json' body = JSON.stringify(functionArgs) } } const response = await this.fetch(`${this.url}/${functionName}`, { method: method || 'POST', // headers priority is (high to low): // 1. invoke-level headers // 2. client-level headers // 3. default Content-Type header headers: { ..._headers, ...this.headers, ...headers }, body, }).catch((fetchError) => { throw new FunctionsFetchError(fetchError) }) const isRelayError = response.headers.get('x-relay-error') if (isRelayError && isRelayError === 'true') { throw new FunctionsRelayError(response) } if (!response.ok) { throw new FunctionsHttpError(response) } let responseType = (response.headers.get('Content-Type') ?? 'text/plain').split(';')[0].trim() let data: any if (responseType === 'application/json') { data = await response.json() } else if (responseType === 'application/octet-stream') { data = await response.blob() } else if (responseType === 'text/event-stream') { data = response } else if (responseType === 'multipart/form-data') { data = await response.formData() } else { // default to text data = await response.text() } return { data, error: null } } catch (error) { return { data: null, error } } } }