UNPKG

@formbricks/api

Version:

Formbricks-api is an api wrapper for the Formbricks client API

1 lines 20.7 kB
{"version":3,"file":"index.umd.cjs","sources":["../../types/error-handlers.ts","../src/utils/make-request.ts","../src/api/client/attribute.ts","../src/api/client/display.ts","../src/api/client/response.ts","../src/api/client/storage.ts","../src/api/client/index.ts","../src/index.ts"],"sourcesContent":["export type Result<T, E = Error> = { ok: true; data: T } | { ok: false; error: E };\n\nexport const ok = <T, E>(data: T): Result<T, E> => ({ ok: true, data });\n\nexport const okVoid = <E>(): Result<void, E> => ({ ok: true, data: undefined });\n\nexport const err = <E = Error>(error: E): Result<never, E> => ({\n ok: false,\n error,\n});\n\n// Applies the given function `fn` to the data property of the input `result` object\n// and returns a new `Result` object with the transformed data property.\n//\n// T - The type of the input data.\n// R - The type of the output data.\n//\n// fn - The function to apply to the data property of the input `result` object.\n// Returns a new function that takes in a `Result<T>` object and returns a new `Result<R>` object.\n//\n//\n// Example:\n// const divideByTwo = (num: number): Result<number> => {\n// if (num === 0) {\n// return { ok: false, error: \"Cannot divide zero\" };\n// }\n//\n// return { ok: true, data: num / 2 };\n// }\n//\n// const wrappedDivideByTwo = wrap(divideByTwo);\n//\n// const result1: Result<number> = { ok: true, data: 10 };\n// const result2: Result<number> = { ok: false, error: \"Invalid input\" };\n// const result3: Result<number> = { ok: true, data: 0 };\n//\n// console.log(wrappedDivideByTwo(result1)); // { ok: true, data: 5 }\n// console.log(wrappedDivideByTwo(result2)); // { ok: false, error: \"Invalid input\" }\n// console.log(wrappedDivideByTwo(result3)); // { ok: false, error: \"Cannot divide zero\" }\nexport const wrap =\n <T, R>(fn: (value: T) => R) =>\n (result: Result<T>): Result<R> =>\n result.ok ? { ok: true, data: fn(result.data) } : result;\n\n// Matches the given `result` object against its `ok` property and invokes the `onSuccess` function\n// if `ok` is `true`, or the `onError` function if `ok` is `false`. Returns the result of the invoked function.\n//\n// TSuccess - Type of the success value.\n// TError - Type of the error value.\n// TReturn - Type of the return value.\n//\n// result - The `Result` object to match against.\n// onSuccess - The function to invoke if `result.ok` is `true`.\n// onError - The function to invoke if `result.ok` is `false`.\n//\n// Returns the result of the invoked function.\n//\n// Example:\n// const test = (): Result<string, Error> => {\n// return err(new Error(\"error happened\"));\n// }\n//\n// const result = test();\n//\n// match(result, (value) => {\n// console.log(value); // never run with this example\n// }, (error) => {\n// console.log(error); // Error: error happened\n// });\nexport const match = <TSuccess, TError, TReturn>(\n result: Result<TSuccess, TError>,\n onSuccess: (value: TSuccess) => TReturn,\n onError: (error: TError) => TReturn\n): TReturn => (result.ok ? onSuccess(result.data) : onError(result.error));\n\n// Wraps a function `fn` that may throw an error into a new function that returns a `Result` object.\n// If the wrapped function throws an error, the returned `Result` object will have an `ok` property of `false`\n// and an `error` property containing the thrown error. Otherwise, the returned `Result` object will have an\n// `ok` property of `true` and a `data` property containing the result of the wrapped function.\n//\n// T - The type of the result value.\n// A - An array of the types of the arguments expected by the wrapped function.\n//\n// fn - The function to wrap.\n// Returns a new function that returns a `Result` object.\n//\n// Example:\n// function divideByTwo(num: number): number {\n// if (num === 0) {\n// throw new Error(\"Cannot divide zero\");\n// }\n// return num / 2;\n// }\n//\n// const wrappedDivideByTwo = wrapThrows(divideByTwo);\n//\n// const result1: Result<number> = wrappedDivideByTwo(10); // { ok: true, data: 5 }\n// const result2: Result<number> = wrappedDivideByTwo(0); // { ok: false, error: Error(\"Cannot divide zero\") }\nexport const wrapThrows =\n <T, A extends unknown[]>(fn: (...args: A) => T): ((...args: A) => Result<T>) =>\n (...args: A): Result<T> => {\n try {\n return {\n ok: true,\n data: fn(...args),\n };\n } catch (error) {\n return {\n ok: false,\n error: error as Error,\n };\n }\n };\n\n// Wraps an asynchronous function `fn` that may throw an error into a new function that returns a `Result` object.\n// If the wrapped function throws an error, the returned `Result` object will have an `ok` property of `false`\n// and an `error` property containing the thrown error. Otherwise, the returned `Result` object will have an\n// `ok` property of `true` and a `data` property containing the result of the wrapped function.\n//\n// T - The type of the result value.\n// A - An array of the types of the arguments expected by the wrapped function.\n//\n// fn - The asynchronous function to wrap.\n// Returns a new function that returns a `Result` object.\n//\n// Example:\n// async function fetchData(url: string): Promise<string> {\n// const response = await fetch(url);\n// if (!response.ok) {\n// throw new Error(\"Network response was not ok\");\n// }\n// return response.text();\n// }\n//\n// const wrappedFetchData = wrapThrowsAsync(fetchData);\n//\n// const result1: Result<string> = await wrappedFetchData(\"https://example.com\"); // { ok: true, data: \"...\" }\n// const result2: Result<string> = await wrappedFetchData(\"https://bad-url.com\"); // { ok: false, error: Error(\"Network response was not ok\") }\nexport const wrapThrowsAsync =\n <T, A extends unknown[]>(fn: (...args: A) => Promise<T>) =>\n async (...args: A): Promise<Result<T>> => {\n try {\n return {\n ok: true,\n data: await fn(...args),\n };\n } catch (error) {\n return {\n ok: false,\n error: error as Error,\n };\n }\n };\n","import { type Result, err, ok, wrapThrowsAsync } from \"@formbricks/types/error-handlers\";\nimport { type ForbiddenError, type NetworkError } from \"@formbricks/types/errors\";\nimport type { ApiErrorResponse, ApiResponse, ApiSuccessResponse } from \"../types\";\n\nexport const makeRequest = async <T>(\n apiHost: string,\n endpoint: string,\n method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\",\n data?: unknown\n): Promise<Result<T, NetworkError | Error | ForbiddenError>> => {\n const url = new URL(apiHost + endpoint);\n const body = data ? JSON.stringify(data) : undefined;\n\n const res = await wrapThrowsAsync(fetch)(url.toString(), {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body,\n });\n\n if (!res.ok) return err(res.error);\n\n const response = res.data;\n const json = (await response.json()) as ApiResponse;\n\n if (!response.ok) {\n const errorResponse = json as ApiErrorResponse;\n return err({\n code: errorResponse.code === \"forbidden\" ? \"forbidden\" : \"network_error\",\n status: response.status,\n message: errorResponse.message || \"Something went wrong\",\n url,\n ...(Object.keys(errorResponse.details).length > 0 && { details: errorResponse.details }),\n });\n }\n\n const successResponse = json as ApiSuccessResponse<T>;\n return ok(successResponse.data);\n};\n","import { type TAttributeUpdateInput } from \"@formbricks/types/attributes\";\nimport { type Result } from \"@formbricks/types/error-handlers\";\nimport { type ForbiddenError, type NetworkError } from \"@formbricks/types/errors\";\nimport { makeRequest } from \"../../utils/make-request\";\n\nexport class AttributeAPI {\n private apiHost: string;\n private environmentId: string;\n\n constructor(apiHost: string, environmentId: string) {\n this.apiHost = apiHost;\n this.environmentId = environmentId;\n }\n\n async update(\n attributeUpdateInput: Omit<TAttributeUpdateInput, \"environmentId\">\n ): Promise<\n Result<\n { changed: boolean; message: string; details?: Record<string, string> },\n NetworkError | Error | ForbiddenError\n >\n > {\n // transform all attributes to string if attributes are present into a new attributes copy\n const attributes: Record<string, string> = {};\n for (const key in attributeUpdateInput.attributes) {\n attributes[key] = String(attributeUpdateInput.attributes[key]);\n }\n\n return makeRequest(\n this.apiHost,\n `/api/v1/client/${this.environmentId}/contacts/${attributeUpdateInput.userId}/attributes`,\n \"PUT\",\n { attributes }\n );\n }\n}\n","import { type TDisplayCreateInput } from \"@formbricks/types/displays\";\nimport { type Result } from \"@formbricks/types/error-handlers\";\nimport { type ForbiddenError, type NetworkError } from \"@formbricks/types/errors\";\nimport { makeRequest } from \"../../utils/make-request\";\n\nexport class DisplayAPI {\n private apiHost: string;\n private environmentId: string;\n\n constructor(baseUrl: string, environmentId: string) {\n this.apiHost = baseUrl;\n this.environmentId = environmentId;\n }\n\n async create(\n displayInput: Omit<TDisplayCreateInput, \"environmentId\">\n ): Promise<Result<{ id: string }, ForbiddenError | NetworkError | Error>> {\n return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/displays`, \"POST\", displayInput);\n }\n}\n","import { type Result } from \"@formbricks/types/error-handlers\";\nimport { type ForbiddenError, type NetworkError } from \"@formbricks/types/errors\";\nimport { type TResponseInput, type TResponseUpdateInput } from \"@formbricks/types/responses\";\nimport { makeRequest } from \"../../utils/make-request\";\n\ntype TResponseUpdateInputWithResponseId = TResponseUpdateInput & { responseId: string };\n\nexport class ResponseAPI {\n private apiHost: string;\n private environmentId: string;\n\n constructor(apiHost: string, environmentId: string) {\n this.apiHost = apiHost;\n this.environmentId = environmentId;\n }\n\n async create(\n responseInput: Omit<TResponseInput, \"environmentId\">\n ): Promise<Result<{ id: string }, ForbiddenError | NetworkError | Error>> {\n return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/responses`, \"POST\", responseInput);\n }\n\n async update({\n responseId,\n finished,\n endingId,\n data,\n ttc,\n variables,\n language,\n }: TResponseUpdateInputWithResponseId): Promise<Result<object, NetworkError | Error | ForbiddenError>> {\n return makeRequest(this.apiHost, `/api/v1/client/${this.environmentId}/responses/${responseId}`, \"PUT\", {\n finished,\n endingId,\n data,\n ttc,\n variables,\n language,\n });\n }\n}\n","/* eslint-disable no-console -- used for error logging */\nimport type { TUploadFileConfig, TUploadFileResponse } from \"@formbricks/types/storage\";\n\nexport class StorageAPI {\n private apiHost: string;\n private environmentId: string;\n\n constructor(apiHost: string, environmentId: string) {\n this.apiHost = apiHost;\n this.environmentId = environmentId;\n }\n\n async uploadFile(\n file: {\n type: string;\n name: string;\n base64: string;\n },\n { allowedFileExtensions, surveyId }: TUploadFileConfig | undefined = {}\n ): Promise<string> {\n if (!file.name || !file.type || !file.base64) {\n throw new Error(`Invalid file object`);\n }\n\n const payload = {\n fileName: file.name,\n fileType: file.type,\n allowedFileExtensions,\n surveyId,\n };\n\n const response = await fetch(`${this.apiHost}/api/v1/client/${this.environmentId}/storage`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`Upload failed with status: ${String(response.status)}`);\n }\n\n const json = (await response.json()) as TUploadFileResponse;\n\n const { data } = json;\n\n const { signedUrl, fileUrl, signingData, presignedFields, updatedFileName } = data;\n\n let requestHeaders: Record<string, string> = {};\n\n if (signingData) {\n const { signature, timestamp, uuid } = signingData;\n\n requestHeaders = {\n \"X-File-Type\": file.type,\n \"X-File-Name\": encodeURIComponent(updatedFileName),\n \"X-Survey-ID\": surveyId ?? \"\",\n \"X-Signature\": signature,\n \"X-Timestamp\": String(timestamp),\n \"X-UUID\": uuid,\n };\n }\n\n const formData: Record<string, string> = {};\n const formDataForS3 = new FormData();\n\n if (presignedFields) {\n Object.entries(presignedFields).forEach(([key, value]) => {\n formDataForS3.append(key, value);\n });\n\n try {\n const binaryString = atob(file.base64.split(\",\")[1]);\n const uint8Array = Uint8Array.from([...binaryString].map((char) => char.charCodeAt(0)));\n const blob = new Blob([uint8Array], { type: file.type });\n\n formDataForS3.append(\"file\", blob);\n } catch (err) {\n console.error(err);\n throw new Error(\"Error uploading file\");\n }\n }\n\n formData.fileBase64String = file.base64;\n\n let uploadResponse: Response = {} as Response;\n\n const signedUrlCopy = signedUrl.replace(\"http://localhost:3000\", this.apiHost);\n\n try {\n uploadResponse = await fetch(signedUrlCopy, {\n method: \"POST\",\n ...(signingData\n ? {\n headers: {\n ...requestHeaders,\n },\n }\n : {}),\n body: presignedFields ? formDataForS3 : JSON.stringify(formData),\n });\n } catch (err) {\n console.error(\"Error uploading file\", err);\n }\n\n if (!uploadResponse.ok) {\n // if local storage is used, we'll use the json response:\n if (signingData) {\n const uploadJson = (await uploadResponse.json()) as { message: string };\n const error = new Error(uploadJson.message);\n error.name = \"FileTooLargeError\";\n throw error;\n }\n\n // if s3 is used, we'll use the text response:\n const errorText = await uploadResponse.text();\n if (presignedFields && errorText.includes(\"EntityTooLarge\")) {\n const error = new Error(\"File size exceeds the size limit for your plan\");\n error.name = \"FileTooLargeError\";\n throw error;\n }\n\n throw new Error(`Upload failed with status: ${String(uploadResponse.status)}`);\n }\n\n return fileUrl;\n }\n}\n","import { type ApiConfig } from \"../../types\";\nimport { AttributeAPI } from \"./attribute\";\nimport { DisplayAPI } from \"./display\";\nimport { ResponseAPI } from \"./response\";\nimport { StorageAPI } from \"./storage\";\n\nexport class Client {\n response: ResponseAPI;\n display: DisplayAPI;\n storage: StorageAPI;\n attribute: AttributeAPI;\n\n constructor(options: ApiConfig) {\n const { apiHost, environmentId } = options;\n\n this.response = new ResponseAPI(apiHost, environmentId);\n this.display = new DisplayAPI(apiHost, environmentId);\n this.attribute = new AttributeAPI(apiHost, environmentId);\n this.storage = new StorageAPI(apiHost, environmentId);\n }\n}\n","import { Client } from \"./api/client\";\nimport { type ApiConfig } from \"./types/index\";\n\nexport class FormbricksAPI {\n client: Client;\n\n constructor(options: ApiConfig) {\n this.client = new Client(options);\n }\n}\n"],"names":["err","error","ok","makeRequest","async","apiHost","endpoint","method","data","url","URL","body","JSON","stringify","res","fn","fetch","args","toString","headers","response","json","errorResponse","code","status","message","Object","keys","details","length","AttributeAPI","constructor","environmentId","__publicField","this","update","attributeUpdateInput","attributes","key","String","userId","DisplayAPI","baseUrl","create","displayInput","ResponseAPI","responseInput","responseId","finished","endingId","ttc","variables","language","StorageAPI","uploadFile","file","allowedFileExtensions","surveyId","name","type","base64","Error","payload","fileName","fileType","signedUrl","fileUrl","signingData","presignedFields","updatedFileName","requestHeaders","signature","timestamp","uuid","encodeURIComponent","formData","formDataForS3","FormData","entries","forEach","value","append","binaryString","atob","split","uint8Array","Uint8Array","from","map","char","charCodeAt","blob","Blob","console","fileBase64String","uploadResponse","signedUrlCopy","replace","uploadJson","errorText","text","includes","Client","options","display","attribute","storage","client"],"mappings":"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"}