UNPKG

@dev-fastn-ai/react-core

Version:

React hooks and components for integrating Fastn AI connector marketplace into your applications. Built on top of @fastn-ai/core with React Query for optimal performance.

1,287 lines (1,265 loc) 77.3 kB
import { jsx } from 'react/jsx-runtime'; import { createContext, useMemo, useContext, useEffect, useState, useCallback } from 'react'; import { QueryClientProvider, QueryClient, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query'; export * from '@dev-fastn-ai/core'; const REQUEST_TRACE_ID_HEADER_KEY = 'x-fastn-request-trace-id'; const PROJECT_ID_HEADER_KEY = 'x-fastn-space-id'; const TENANT_ID_HEADER_KEY = 'x-fastn-space-tenantid'; const REALM = 'fastn'; const DEFAULT_BASE_URL = 'https://live.fastn.ai/api'; const PROD_OAUTH_REDIRECT_URL = 'https://oauth.live.fastn.ai'; const CUSTOM_AUTH_CONTEXT_HEADER_KEY = 'x-fastn-custom-auth-context'; const CUSTOM_AUTH_HEADER_KEY = 'x-fastn-custom-auth'; const GOOGLE_FILES_PICKER_API_KEY = "AIzaSyClOJ0PYR0NJJkE79VLpKGOjgABbhjj9x4"; const ACTIVATE_CONNECTOR_ACCESS_KEY = "7e9456eb-5fa1-44ca-8464-d63eb4a1c9a8"; const ACTIVATE_CONNECTOR_PROJECT_ID = "e9289c72-9c15-42af-98e5-8bc6114a362f"; const ACTIVATE_CONNECTOR_URL = "/api/v1/ActivateConnector"; let config; function setConfig(userConfig) { var _a; config = { baseUrl: userConfig.baseUrl || DEFAULT_BASE_URL, environment: userConfig.environment || 'DRAFT', flowsEnvironment: userConfig.flowsEnvironment || 'LIVE', authToken: userConfig.authToken, tenantId: userConfig.tenantId, spaceId: userConfig.spaceId, customAuth: (_a = userConfig.customAuth) !== null && _a !== void 0 ? _a : true, }; } function getConfig() { if (!config) throw new Error('Fastn not initialized.'); return config; } // Unique ID creation requires a high quality random # generator. In the browser we therefore // require the crypto API and do not support built-in fallback to lower quality random number // generators (like Math.random()). let getRandomValues; const rnds8 = new Uint8Array(16); function rng() { // lazy load so that environments that need to polyfill have a chance to do so if (!getRandomValues) { // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); if (!getRandomValues) { throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); } } return getRandomValues(rnds8); } /** * Convert array of 16 byte values to UUID string format of the form: * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX */ const byteToHex = []; for (let i = 0; i < 256; ++i) { byteToHex.push((i + 0x100).toString(16).slice(1)); } function unsafeStringify(arr, offset = 0) { // Note: Be careful editing this code! It's been tuned for performance // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]; } const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto); var native = { randomUUID }; function v4(options, buf, offset) { if (native.randomUUID && true && !options) { return native.randomUUID(); } options = options || {}; const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = rnds[6] & 0x0f | 0x40; rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided return unsafeStringify(rnds); } /** * Supported field types for connector forms. */ var ConnectorFieldType; (function (ConnectorFieldType) { ConnectorFieldType["TEXT"] = "text"; ConnectorFieldType["NUMBER"] = "number"; ConnectorFieldType["CHECKBOX"] = "checkbox"; ConnectorFieldType["DATE"] = "date"; ConnectorFieldType["DATETIME"] = "datetime"; ConnectorFieldType["TIME"] = "time"; ConnectorFieldType["DATETIME_LOCAL"] = "datetime-local"; ConnectorFieldType["MULTI_SELECT"] = "multi-select"; ConnectorFieldType["SELECT"] = "select"; ConnectorFieldType["GOOGLE_FILES_PICKER_SELECT"] = "google-files-picker-select"; ConnectorFieldType["GOOGLE_FILES_PICKER_MULTI_SELECT"] = "google-files-picker-multi-select"; })(ConnectorFieldType || (ConnectorFieldType = {})); /** * Supported action types for connectors and configurations. */ var ConnectorActionType; (function (ConnectorActionType) { ConnectorActionType["ACTIVATION"] = "ACTIVATION"; ConnectorActionType["DEACTIVATION"] = "DEACTIVATION"; ConnectorActionType["NONE"] = "NONE"; ConnectorActionType["ENABLE"] = "ENABLE"; ConnectorActionType["DISABLE"] = "DISABLE"; ConnectorActionType["DELETE"] = "DELETE"; })(ConnectorActionType || (ConnectorActionType = {})); /** * Status of a connector. */ var ConnectorStatus; (function (ConnectorStatus) { ConnectorStatus["ACTIVE"] = "ACTIVE"; ConnectorStatus["INACTIVE"] = "INACTIVE"; ConnectorStatus["ALL"] = "ALL"; })(ConnectorStatus || (ConnectorStatus = {})); /** * Supported resource actions for connectors and configurations. */ var ResourceAction; (function (ResourceAction) { ResourceAction["ACTIVATION"] = "ACTIVATION"; ResourceAction["DEACTIVATION"] = "DEACTIVATION"; ResourceAction["CONFIGURE"] = "CONFIGURE"; ResourceAction["UNCONFIGURE"] = "UNCONFIGURE"; ResourceAction["FLOW_EXECUTION"] = "FLOW_EXECUTION"; ResourceAction["QUERY"] = "QUERY"; ResourceAction["ENABLE_CONFIGURATION"] = "ENABLE_CONFIGURATION"; ResourceAction["UPDATE_CONFIGURATION"] = "UPDATE_CONFIGURATION"; ResourceAction["DISABLE_CONFIGURATION"] = "DISABLE_CONFIGURATION"; ResourceAction["DELETE_CONFIGURATION"] = "DELETE_CONFIGURATION"; })(ResourceAction || (ResourceAction = {})); const getCustomAuthContextValue = (operationName, variables) => { var _a; try { if (!operationName) return ""; return btoa(JSON.stringify(getCustomAuthContext(operationName, (_a = variables === null || variables === void 0 ? void 0 : variables.input) !== null && _a !== void 0 ? _a : {}))); } catch (error) { console.error("Error getting custom auth context value:", error); return ""; } }; const getCustomAuthContext = (operationName, input) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; if (!operationName) return {}; try { let action = ResourceAction.QUERY; let resourceId = ""; switch (operationName) { case "metadata": case "connectors": case "api": case "tenantflow": case "generateaccesstoken": break; case "deactivateconnector": action = ResourceAction.DEACTIVATION; resourceId = (_a = input.connectorId) !== null && _a !== void 0 ? _a : ""; break; case "configuretenantflow": action = ResourceAction.CONFIGURE; resourceId = (_b = input.connectorId) !== null && _b !== void 0 ? _b : ""; break; case "deletetenantconfiguration": action = ResourceAction.UNCONFIGURE; resourceId = (_c = input.connectorId) !== null && _c !== void 0 ? _c : ""; break; case "createtenantconfiguration": action = ResourceAction.ENABLE_CONFIGURATION; resourceId = (_d = input.connectorId) !== null && _d !== void 0 ? _d : ""; break; case "updatetenantconfiguration": action = ResourceAction.UPDATE_CONFIGURATION; resourceId = (_e = input.connectorId) !== null && _e !== void 0 ? _e : ""; break; case "disabletenantconfig": action = ResourceAction.DISABLE_CONFIGURATION; resourceId = (_f = input.connectorId) !== null && _f !== void 0 ? _f : ""; break; case "deletetenantconfig": action = ResourceAction.DELETE_CONFIGURATION; resourceId = (_g = input.connectorId) !== null && _g !== void 0 ? _g : ""; break; default: resourceId = (_j = (_h = input.clientId) !== null && _h !== void 0 ? _h : input.projectId) !== null && _j !== void 0 ? _j : ""; } return { action, resourceId }; } catch (error) { console.error("Error parsing custom auth context:", error); return {}; } }; const addCustomAuthContextHeader = (headers, context) => { try { const contextString = JSON.stringify(context); const encodedContext = btoa(contextString); headers[CUSTOM_AUTH_CONTEXT_HEADER_KEY] = encodedContext; } catch (error) { console.warn("Error adding custom auth context header:", error); } return headers; }; const getOauthPopUpDimensions = () => { const screenWidth = window.innerWidth; const screenHeight = window.innerHeight; const width = Math.min(screenWidth - 100, 900); const height = Math.min(screenHeight - 100, 900); const top = Math.max((screenHeight - height) / 2, 100); const left = Math.max((screenWidth - width) / 2, 50); return { width, height, top, left }; }; function encodeState(state) { const { domain, uuid } = state; return btoa(`${domain}#${uuid}`); } const templateObjectValues = (obj, values) => { try { let objString = JSON.stringify(obj); Object.keys(values).forEach((key) => { objString = objString.replace(new RegExp(`{{${key}}}`, "g"), values[key]); }); return JSON.parse(objString); } catch (error) { console.warn("Error templating object values", error); return undefined; } }; const generateAuthUrl = ({ oauthDetails, formData = {}, }) => { var _a, _b; try { const templatedDetails = (_a = templateObjectValues(oauthDetails, formData)) !== null && _a !== void 0 ? _a : oauthDetails; const params = Object.assign({}, ((_b = templatedDetails === null || templatedDetails === void 0 ? void 0 : templatedDetails.params) !== null && _b !== void 0 ? _b : {})); if (templatedDetails.clientId) params.client_id = templatedDetails.clientId; params.redirect_uri = PROD_OAUTH_REDIRECT_URL; params.state = encodeState({ domain: "widget", uuid: "none" }); const paramsString = Object.entries(params) .map(([key, val]) => `${key}=${encodeURIComponent(val)}`) .join("&"); return templatedDetails.baseUrl ? `${templatedDetails.baseUrl}?${paramsString}` : ""; } catch (error) { console.error("Error generating auth URL:", error); return ""; } }; const getParams = (paramsString = window.location.search) => { try { const searchParams = new URLSearchParams(paramsString); const params = {}; for (const [key, value] of searchParams.entries()) { params[key] = value; } return params; } catch (_a) { return {}; } }; const getFieldsFromContract = (contract) => { return Object.keys(contract) .map((key) => { var _a, _b, _c, _d, _e, _f, _g, _h; const field = contract[key]; if (typeof field !== "object") return null; return { name: key, label: (_a = field.description) !== null && _a !== void 0 ? _a : "", initialValue: (_d = (_c = (_b = field.default) !== null && _b !== void 0 ? _b : field.defaultValue) !== null && _c !== void 0 ? _c : field.initialValue) !== null && _d !== void 0 ? _d : field.v, required: (_e = field.required) !== null && _e !== void 0 ? _e : false, type: (_f = field.type) !== null && _f !== void 0 ? _f : "text", hidden: (_g = field.hidden) !== null && _g !== void 0 ? _g : false, disabled: (_h = field.disabled) !== null && _h !== void 0 ? _h : false, }; }) .filter(Boolean); }; const inputContractToFormData = (inputContract) => { var _a, _b; try { const fields = getFieldsFromContract(inputContract !== null && inputContract !== void 0 ? inputContract : {}); if (!fields.length) return null; return { fields, description: (_a = inputContract.description) !== null && _a !== void 0 ? _a : "", submitButtonLabel: (_b = inputContract.submitLabel) !== null && _b !== void 0 ? _b : "Connect", }; } catch (error) { console.warn("Error converting input contract to form data:", error); return null; } }; const convertUiCodeFormDataToFormData = (formData) => { try { const { target, targetType } = formData; if (targetType === "object" && typeof target === "object" && target !== null) { const data = {}; Object.keys(target).forEach((key) => { data[key] = convertUiCodeFormDataToFormData(target[key]); }); return data; } else if (targetType === "array" && Array.isArray(target)) { return target.map((item) => convertUiCodeFormDataToFormData(item)); } else { return target; } } catch (_a) { return formData; } }; const safeParse = (jsonString) => { try { if (typeof jsonString === "object") return jsonString; return JSON.parse(jsonString); } catch (_a) { return {}; } }; const safeStringify = (json) => { try { return typeof json === "string" ? json : JSON.stringify(json); } catch (_a) { return ""; } }; function formatApolloErrors(error) { var _a; if (!error) return ""; const code = (_a = error === null || error === void 0 ? void 0 : error.cause) === null || _a === void 0 ? void 0 : _a.code; try { switch (code) { case "RESOURCE_NOT_FOUND": case "resource_not_found": return "The requested resource could not be found"; case "RESOURCE_ALREADY_EXISTS": case "resource_already_exists": return "The resource you are trying to create already exists"; case "UNAUTHORIZED": case "unauthorized": case "UNAUTHORIZED_ERROR": return "You are not authorized to perform this operation"; case "UNAUTHENTICATED": case "unauthenticated": case "UNAUTHENTICATED_ERROR": return "You are not authenticated to perform this operation"; case "INTERNAL_SERVER_ERROR": case "internal_server_error": return "An unexpected error occurred on the server. Please try again later"; default: return (error === null || error === void 0 ? void 0 : error.message) || "An unknown error occurred. Please try again."; } } catch (err) { return "An error occurred while processing the error. Please try again."; } } const getFieldType = (field) => { if (field.targetType === "array" && field.configs.selection.enable) return ConnectorFieldType.MULTI_SELECT; if (field.targetType === "object" && field.configs.selection.enable) return ConnectorFieldType.SELECT; if (field.targetType === "string") return ConnectorFieldType.TEXT; if (field.targetType === "number") return ConnectorFieldType.NUMBER; if (field.targetType === "boolean") return ConnectorFieldType.CHECKBOX; if (field.targetType === "date") return ConnectorFieldType.DATE; if (field.targetType === "datetime") return ConnectorFieldType.DATETIME; if (field.targetType === "time") return ConnectorFieldType.TIME; if (field.targetType === "datetime-local") return ConnectorFieldType.DATETIME_LOCAL; return ConnectorFieldType.TEXT; }; const uiCodeToFormFields = (uiCodeString, options) => { try { const parsed = safeParse(uiCodeString); const { targetType, target } = parsed; const values = convertUiCodeFormDataToFormData(parsed); if (targetType !== 'object' || typeof target !== 'object' || target === null) { return []; } return Object.entries(target).map(([key, rawField]) => { var _a; const { configs = {}, default: defaultValue, } = rawField; const { label = key, description = '', required = false, placeholder = '', hideOption = {}, selection, } = configs; const field = { key, name: key, label: label !== null && label !== void 0 ? label : key, description, initialValue: (_a = values[key]) !== null && _a !== void 0 ? _a : defaultValue, type: getFieldType(rawField), required: Boolean(required), hidden: Boolean((hideOption === null || hideOption === void 0 ? void 0 : hideOption.enable) || key === "connectorId" || key === "id"), disabled: Boolean((hideOption === null || hideOption === void 0 ? void 0 : hideOption.enable) || key === "connectorId" || key === "id"), placeholder, }; if (selection === null || selection === void 0 ? void 0 : selection.enable) { if (selection.type === 'CUSTOM' && selection.customSelector === 'GOOGLE_FILES_PICKER') { return Object.assign(Object.assign({}, field), { type: field.type === ConnectorFieldType.MULTI_SELECT ? ConnectorFieldType.GOOGLE_FILES_PICKER_MULTI_SELECT : ConnectorFieldType.GOOGLE_FILES_PICKER_SELECT, optionsSource: { type: 'GOOGLE_FILES_PICKER', openGoogleFilesPicker: options.openGoogleFilesPicker, } }); } else if (selection.flowId) { const pagination = Object.assign(Object.assign({ sourceId: selection.flowId, sourceProject: selection.isSameProject ? 'self' : 'community', limit: selection.limit, type: selection.type }, (selection.type === 'OFFSET' && selection.offset != null ? { offset: selection.offset } : {})), (selection.type === 'CURSOR' && selection.cursor != null ? { cursor: selection.cursor } : {})); return Object.assign(Object.assign({}, field), { optionsSource: { type: 'DYNAMIC', pagination, getOptions: options.getOptions, loadMore: options.loadMore, refresh: options.refresh, searchOptions: options.searchOptions, } }); } else if (Array.isArray(selection.list)) { return Object.assign(Object.assign({}, field), { optionsSource: { type: 'STATIC', staticOptions: selection.list.map((item) => ({ label: item.title, value: item.value, })), } }); } } return field; }); } catch (error) { console.warn('Error parsing UI code to form fields:', error); return []; } }; function populateFormDataInUiCode(uiCodeString, formData) { const uiCode = safeParse(uiCodeString) || {}; const { target, targetType } = uiCode; if (!target || !targetType) return uiCode; if (targetType === "object") { const updatedTarget = Object.keys(target).reduce((acc, key) => { var _a; const value = formData[key]; if (value !== undefined) { acc[key] = Object.assign(Object.assign({}, target[key]), { target: (_a = convertFormDataToUiCodeFormData(value)) === null || _a === void 0 ? void 0 : _a.target }); } else { acc[key] = target[key]; } return acc; }, {}); return Object.assign(Object.assign({}, uiCode), { target: updatedTarget }); } else if (targetType === "array" && Array.isArray(target)) { const updatedTarget = target.map((item, index) => { const updatedItem = Object.assign({}, item); // Assuming item has a unique key (e.g., id or name) to match formData fields Object.keys(item).forEach((key) => { var _a, _b; const formValue = (_a = formData[`${index}.${key}`]) !== null && _a !== void 0 ? _a : formData[key]; if (formValue !== undefined) { updatedItem[key] = Object.assign(Object.assign({}, item[key]), { target: (_b = convertFormDataToUiCodeFormData(formValue)) === null || _b === void 0 ? void 0 : _b.target }); } }); return updatedItem; }); return Object.assign(Object.assign({}, uiCode), { target: updatedTarget }); } return uiCode; } const convertFormDataToUiCodeFormData = (formData) => { if (Array.isArray(formData)) { return { actionType: "map", targetType: "array", target: formData.map((item) => convertFormDataToUiCodeFormData(item)), }; } else if (formData !== null && typeof formData === "object") { const target = {}; for (const key in formData) { target[key] = convertFormDataToUiCodeFormData(formData[key]); } return { actionType: "map", targetType: "object", target, }; } else if (typeof formData === "string") { return { actionType: "map", targetType: "string", target: formData, }; } else if (typeof formData === "number") { return { actionType: "map", targetType: "number", target: formData, }; } else if (typeof formData === "boolean") { return { actionType: "map", targetType: "boolean", target: formData, }; } else { return { actionType: "map", targetType: "string", target: formData, }; } }; /** * Recursively removes __typename fields from an object or array. */ function stripTypename(obj) { if (Array.isArray(obj)) { return obj.map(stripTypename); } else if (obj && typeof obj === 'object') { const newObj = {}; for (const key in obj) { if (key === '__typename') continue; newObj[key] = stripTypename(obj[key]); } return newObj; } return obj; } // GraphQL endpoint (customize as needed) const GRAPHQL_ENDPOINT = "/graphql"; // Utility to perform GraphQL queries/mutations using fetch async function fetchGraphQL({ query, variables, operationName }) { const config = getConfig(); const response = await fetch(config.baseUrl + GRAPHQL_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${config.authToken}`, realm: REALM, [CUSTOM_AUTH_HEADER_KEY]: String(config.customAuth), [PROJECT_ID_HEADER_KEY]: config.spaceId, [TENANT_ID_HEADER_KEY]: config.tenantId, [REQUEST_TRACE_ID_HEADER_KEY]: `react-ai-core-${v4()}`, [CUSTOM_AUTH_CONTEXT_HEADER_KEY]: getCustomAuthContextValue(operationName, variables), }, body: JSON.stringify({ query, variables }), }); if (!response.ok) { switch (response.status) { case 401: throw new Error("Unauthorized: Please check your auth token"); case 403: throw new Error("Forbidden: Please check your access"); case 404: throw new Error("Not Found: Please check your request"); default: throw new Error("Failed to fetch: Please check your request"); } } const result = await response.json(); if (result.errors) { throw new Error(result.errors.map((e) => e.message).join("; ")); } return { data: result.data, errors: result.errors }; } // GraphQL Queries & Mutations (as plain strings) const GET_WIDGET_CONNECTORS = ` query Connectors($input: GetConnectorsListInput!) { connectors(input: $input) { name id imageUri description content actions { name handler actionType activatedStateLabel content shows handlerPayload multiConnectorWidgetConnectors { name id imageUri description content connectionId actions { name handler actionType activatedStateLabel content shows handlerPayload } events { name eventId content payload } connectedConnectors { id imageUrl clientId name enableActivate activateStatus authMethods { title inputContract type details isDefault } } status } } events { name eventId content payload } connectedConnectors { id imageUrl clientId name enableActivate activateStatus authMethods { title inputContract type details isDefault } } status } } `; const SAVE_ACTIVATE_CONNECTOR_STATUS = ` mutation SaveActivateConnectorStatus( $input: SaveWidgetConnectorStatusInput! ) { saveWidgetConnectorStatus(input: $input) } `; const DEACTIVATE_CONNECTOR = ` mutation DeactivateConnector($input: DeactivateConnectorInput!) { deactivateConnector(input: $input) } `; const GET_FIELD_DATA_QUERY = ` query executeGetFieldDataFlow($input: ApiPrimaryResolverInvocationInput!) { executeGetFieldDataFlow(input: $input) { data } } `; const GET_TENANT_CONFIGURATIONS = ` query GetConfigurationSubscription($input: ConfigurationSubscriptionInput!) { getConfigurationSubscription(input: $input) { connector { id name description imageUri status active actions { name handler actionType } connectedConnectors { id name clientId imageUrl enableActivate activateStatus authMethods { title inputContract type details isDefault } } } status metaData } } `; const GET_TENANT_CONFIGURATIONS_BY_ID = ` query GetConfigurationSubscription($input: GetTenantConfigurationsInput) { tenantConfigurationsById(input: $input) { uiCode flowId stepId status configurations configuredStepSetting { keyIsEditable addButton { label isDisabled fieldsLimit } description label validation { fieldValidation submitValidation modelId jsonSchema submitValidationFunction } } } } `; const CREATE_TENANT_CONFIGURATION = ` mutation CreateTenantConfiguration($input: TenantConfigurationsInput!) { createTenantConfiguration(input: $input) { id } } `; const UPDATE_TENANT_CONFIGURATION = ` mutation UpdateTenantConfiguration($input: TenantConfigurationsInput!) { updateTenantConfiguration(input: $input) { id } } `; const DELETE_TENANT_CONFIG = ` mutation DeleteTenantConfig($input: GetTenantConfigurationsInput!) { deleteTenantConfig(input: $input) { id } } `; const DISABLE_TENANT_CONFIG = ` mutation DisableTenantConfig($input: GetTenantConfigurationsInput!) { disableTenantConfig(input: $input) { id } } `; const GENERATE_ACCESS_TOKEN = ` query GenerateAccessToken($input: ConnectorConnectionInput!) { connectorConnection(input: $input) } `; // Refactored functions using fetchGraphQL async function getConnectors$1(variables) { return fetchGraphQL({ query: GET_WIDGET_CONNECTORS, variables, operationName: "connectors" }); } async function saveActivateConnectorStatus(variables) { return fetchGraphQL({ query: SAVE_ACTIVATE_CONNECTOR_STATUS, variables, operationName: "saveactivatconnectorstatus" }); } async function deactivateConnector$1(variables) { return fetchGraphQL({ query: DEACTIVATE_CONNECTOR, variables, operationName: "deactivateConnector" }); } async function getFieldData(variables) { return fetchGraphQL({ query: GET_FIELD_DATA_QUERY, variables, operationName: "executeGetFieldDataFlow" }); } async function getConfigurationSubscriptions(variables) { return fetchGraphQL({ query: GET_TENANT_CONFIGURATIONS, variables, operationName: "getconfigurationsubscription" }); } async function getConfigurationSubscriptionById(variables) { return fetchGraphQL({ query: GET_TENANT_CONFIGURATIONS_BY_ID, variables, operationName: "getconfigurationsubscriptionbyid" }); } async function createTenantConfiguration(variables) { return fetchGraphQL({ query: CREATE_TENANT_CONFIGURATION, variables, operationName: "createtenantconfiguration" }); } async function updateTenantConfiguration(variables) { return fetchGraphQL({ query: UPDATE_TENANT_CONFIGURATION, variables, operationName: "updatetenantconfiguration" }); } async function deleteTenantConfig(variables) { return fetchGraphQL({ query: DELETE_TENANT_CONFIG, variables, operationName: "deletetenantconfig" }); } async function disableTenantConfig(variables) { return fetchGraphQL({ query: DISABLE_TENANT_CONFIG, variables, operationName: "disabletenantconfig" }); } async function generateAccessToken(variables) { return fetchGraphQL({ query: GENERATE_ACCESS_TOKEN, variables, operationName: "generateaccesstoken" }); } /** * Returns the backend URL for flow execution, appending /v1 if needed. */ const getBackendUrl = () => { var _a; const config = getConfig(); return ((_a = config.baseUrl) === null || _a === void 0 ? void 0 : _a.endsWith("/api")) ? config.baseUrl + "/v1" : `${config.baseUrl}/api/v1`; }; /** * Executes a backend flow with the given path, input, and headers. * @returns {Promise<any>} The response data from the backend. */ const executeFLow = async ({ path, input = {}, headers = {} }) => { try { const config = getConfig(); const backendUrl = getBackendUrl(); const response = await fetch(`${backendUrl}/${path}`, { method: "POST", headers: Object.assign({ "Content-Type": "application/json", [PROJECT_ID_HEADER_KEY]: config.spaceId, [TENANT_ID_HEADER_KEY]: config.tenantId, authorization: `${config.authToken}`, stage: config.flowsEnvironment }, headers), body: JSON.stringify({ "input": input }) }); if (!response.ok) { throw new Error(`Failed to execute flow: ${response.statusText}`); } const data = await response.json(); return data; } catch (error) { console.error("Error in executeFLow:", error); throw error; } }; /** * Builds the request input for connector activation. */ const buildRequestInput = (dependencyConnector, authMethod, formData) => { var _a; const config = getConfig(); return { fastn_connection: { projectId: config === null || config === void 0 ? void 0 : config.spaceId, domain: (_a = config.baseUrl) === null || _a === void 0 ? void 0 : _a.replace("https://", "").replace("/api", "").replace("api", ""), redirectUri: PROD_OAUTH_REDIRECT_URL }, oauth: { connector: { id: dependencyConnector === null || dependencyConnector === void 0 ? void 0 : dependencyConnector.id, clientId: dependencyConnector === null || dependencyConnector === void 0 ? void 0 : dependencyConnector.clientId, name: dependencyConnector === null || dependencyConnector === void 0 ? void 0 : dependencyConnector.name, isOauth: (authMethod === null || authMethod === void 0 ? void 0 : authMethod.type) === "OAUTH", oauth: authMethod === null || authMethod === void 0 ? void 0 : authMethod.details, input: formData, }, response: {} } }; }; /** * Calls the backend to activate a connector. */ const callActivateConnector = async (input, headers) => { const config = getConfig(); const url = config.baseUrl + ACTIVATE_CONNECTOR_URL; const response = await fetch(url, { method: "POST", headers: Object.assign(Object.assign({ "Content-Type": "application/json" }, headers), { "x-fastn-api-key": ACTIVATE_CONNECTOR_ACCESS_KEY, "x-fastn-space-id": ACTIVATE_CONNECTOR_PROJECT_ID, "x-fastn-space-client-id": config.spaceId, 'stage': "LIVE" }), body: JSON.stringify({ input }) }); if (!response.ok) { throw new Error(`Failed to activate connector: ${response.statusText}`); } const responseData = await response.json(); return responseData.data; }; /** * Handles the OAuth flow for connector activation. */ const handleOauthFlow = async (authMethod, formData, input) => { const config = getConfig(); const authUrl = generateAuthUrl({ oauthDetails: authMethod.details, formData }); const { width, height, top, left } = getOauthPopUpDimensions(); const oAuthPopUpWindow = window.open(authUrl, "OAuthPopup", `width=${width},height=${height},top=${top},left=${left}`); return new Promise((resolve, reject) => { let params = null; const messageListener = async (event) => { var _a; if (event.origin === PROD_OAUTH_REDIRECT_URL && ((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === "oauth_complete" && !params) { params = getParams(event.data.params); window.removeEventListener("message", messageListener); clearInterval(closePopupInterval); try { input.oauth.response = params; const data = await callActivateConnector(input, { [TENANT_ID_HEADER_KEY]: config === null || config === void 0 ? void 0 : config.tenantId, [CUSTOM_AUTH_HEADER_KEY]: String(config === null || config === void 0 ? void 0 : config.customAuth), authorization: config === null || config === void 0 ? void 0 : config.authToken }); resolve({ data, status: "SUCCESS" }); } catch (error) { console.error("Error in handleOauthFlow", error); reject(error); } finally { } } }; const closePopupInterval = setInterval(() => { if (oAuthPopUpWindow.closed) { clearInterval(closePopupInterval); if (!params) { window.removeEventListener("message", messageListener); resolve({ data: { message: "Popup closed without completing OAuth" }, status: "CANCELLED" }); } } }, 1000); window.addEventListener("message", messageListener); }); }; /** * Framework-agnostic activateConnector utility. * @param args - Activation arguments (connectorId, action, dependencyConnector, authMethod, input, saveStatusFn, executeActionHandlerFn, config) * @returns {Promise<{ status: string; data: any }>} */ const activateConnector = async ({ connectorId, action, dependencyConnector, authMethod, input = {}, }) => { try { const config = getConfig(); // Activate the connector let response = await activateConnectorCore({ dependencyConnector, authMethod, formData: input, }); if ((response === null || response === void 0 ? void 0 : response.status) === "CANCELLED") { return response; } // Execute the action handler response = await executeActionHandler(action === null || action === void 0 ? void 0 : action.handler, addCustomAuthContextHeader({}, { resourceId: connectorId, action: ResourceAction.ACTIVATION })); // Save connector status await saveActivateConnectorStatus({ input: { projectId: config.spaceId, tenantId: config.tenantId, connectorId, isDependencyConnector: false, } }); return { status: "SUCCESS", data: response, }; } catch (error) { console.error("Error in activateConnector:", error); throw error; } }; /** * Framework-agnostic deactivateConnector utility. * @param args - Deactivation arguments (connectorId, action, deactivateConnectorFn, executeActionHandlerFn, config) * @returns {Promise<{ status: string; data: any }>} */ const deactivateConnector = async ({ connectorId, action, }) => { try { const config = getConfig(); // Execute the action handler let response = {}; response = await executeActionHandler(action === null || action === void 0 ? void 0 : action.handler, addCustomAuthContextHeader({}, { resourceId: connectorId, action: ResourceAction.DEACTIVATION })); // Deactivate the connector await deactivateConnector$1({ input: { projectId: config.spaceId, tenantId: config.tenantId, connectorId, } }); return { status: "SUCCESS", data: response, }; } catch (error) { console.error("Error in deactivateConnector:", error); throw error; } }; /** * Framework-agnostic executeActionHandler utility. * @param handler - The handler path or function * @param headers - Optional headers * @returns {Promise<any>} */ const executeActionHandler = async (handler, headers = {}) => { try { if (!handler) { return { status: "SUCCESS", data: {}, }; } const response = await executeFLow({ path: handler, headers }); return response; } catch (error) { console.warn("Error executing action handler:", error); throw error; } }; // Core activation logic for internal use const activateConnectorCore = async ({ dependencyConnector, authMethod, formData }) => { try { const input = buildRequestInput(dependencyConnector, authMethod, formData); const isOauth = (authMethod === null || authMethod === void 0 ? void 0 : authMethod.type) === "OAUTH"; if (isOauth) { return await handleOauthFlow(authMethod, formData, input); } const config = getConfig(); const data = await callActivateConnector(input, { [TENANT_ID_HEADER_KEY]: config === null || config === void 0 ? void 0 : config.tenantId, [CUSTOM_AUTH_HEADER_KEY]: String(config === null || config === void 0 ? void 0 : config.customAuth), authorization: config === null || config === void 0 ? void 0 : config.authToken }); return { data, status: "SUCCESS" }; } catch (error) { console.error("Error in activateConnectorCore", error); throw error; } }; // eventBus.ts // Internal listener registry const listeners = { 'REFETCH_CONNECTORS': new Set(), 'REFETCH_CONFIGURATIONS': new Set(), 'REFRESH_CONFIGURATION_FORM': new Set(), 'INVALIDATE_CONFIGURATION_FORM': new Set(), 'INVALIDATE_CONFIGURATIONS': new Set(), 'INVALIDATE_CONNECTORS': new Set(), }; // Send a cross-context-safe event const sendEvent = (event) => { window.postMessage({ __eventBus: true, event }, '*'); }; // Register a callback for a specific event const onEvent = (event, callback) => { var _a; (_a = listeners[event]) === null || _a === void 0 ? void 0 : _a.add(callback); }; // Unregister a callback const offEvent = (event, callback) => { var _a; (_a = listeners[event]) === null || _a === void 0 ? void 0 : _a.delete(callback); }; // Internal postMessage listener const handleMessage = (e) => { const data = e.data; if (!data || !data.__eventBus || !data.event) return; const callbacks = listeners[data.event]; callbacks === null || callbacks === void 0 ? void 0 : callbacks.forEach((cb) => cb()); }; // Register listener once window.addEventListener('message', handleMessage); /** * Fetches connectors and maps their actions for use in the application. * Updates the global state store. * @returns {Promise<Connector[]>} */ async function getConnectors({ disabled = false, status = "ALL", refetch } = {}) { try { const config = getConfig(); const { data } = await getConnectors$1({ input: { projectId: config.spaceId, tenantId: config.tenantId, onlyActive: !disabled, environment: config.environment, }, }); const connectors = data === null || data === void 0 ? void 0 : data.connectors; const mappedConnectors = connectors.map((connector) => { const actions = (connector.actions || []) .filter((action) => { // Only allow ACTIVATION, DEACTIVATION, or NONE if (action.actionType !== "ACTIVATION" && action.actionType !== "DEACTIVATION" && action.actionType !== "NONE") { return false; } if (!action.shows || action.shows === "ALWAYS") { return true; } if (action.shows === "WHEN_ACTIVE" && connector.status === "ACTIVE") { return true; } if (action.shows === "WHEN_INACTIVE" && connector.status === "INACTIVE") { return true; } return false; }) .map((action) => { var _a, _b, _c, _d; const handler = async (formData = {}) => { var _a, _b, _c, _d; try { let response = null; if ((action === null || action === void 0 ? void 0 : action.actionType) === "ACTIVATION") { response = await activateConnector({ connectorId: connector.id, action, dependencyConnector: (_a = connector === null || connector === void 0 ? void 0 : connector.connectedConnectors) === null || _a === void 0 ? void 0 : _a[0], authMethod: (_d = (_c = (_b = connector === null || connector === void 0 ? void 0 : connector.connectedConnectors) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.authMethods) === null || _d === void 0 ? void 0 : _d[0], input: formData, }); } else if ((action === null || action === void 0 ? void 0 : action.actionType) === "DEACTIVATION") { response = await deactivateConnector({ connectorId: connector.id, action, }); await (refetch === null || refetch === void 0 ? void 0 : refetch()); } else { response = await executeActionHandler(action === null || action === void 0 ? void 0 : action.handler); } sendEvent("REFETCH_CONFIGURATIONS"); sendEvent("REFETCH_CONNECTORS"); return response; } catch (e) { return { data: { error: e instanceof Error ? e.message : "Unknown error", }, status: "ERROR", }; } }; const form = inputContractToFormData((_d = (_c = (_b = (_a = connector === null || connector === void 0 ? void 0 : connector.connectedConnectors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.authMethods) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.inputContract); // Construct the action object with all properties at creation return Object.assign({ name: action.name, actionType: action.actionType, form }, (form ? { onSubmit: async (formData) => await handler(formData) } : { onClick: async () => await handler() })); }); return { id: connector.id, name: connector.name, description: connector.description, imageUri: connector === null || connector === void 0 ? void 0 : connector.imageUri, status: connector.status, actions, }; }).filter((connector) => { if (status === "ALL") return true; if (status === "ACTIVE" && connector.status === "ACTIVE") return true; if (status === "INACTIVE" && connector.status === "INACTIVE") return true; return false; }); return mappedConnectors; } catch (error) { throw new Error(formatApolloErrors(error)); } } const handleDisableConfiguration = async ({ id, connectorId, }) => { try { const config = getConfig(); await disableTenantConfig({ input: { clientId: config.spaceId, tenantId: config.tenantId, id: id, connectorId, widgetConnectorId: connectorId, }, }); sendEvent("REFETCH_CONFIGURATIONS"); } catch (error) { throw new Error(formatApolloErrors(error)); } }; const handleDeleteConfiguration = async ({ id, connectorId, }) => { try { const config = getConfig(); await deleteTenantConfig({ input: { clientId: config.spaceId, tenantId: config.tenantId, id: id, connectorId, widgetConnectorId: connectorId, }, }); sendEvent("REFETCH_CONFIGURATIONS"); } catch (error) { throw new Error(formatApolloErrors(error)); } }; async function getConfigurations({ configurationId, status = "ALL", }) { try { const config = getConfig(); const { data } = await getConfigurationSubscriptions({ input: { clientId: config.spaceId, tenantId: config.tenantId, id: configurationId, status, } }); const configurations = (data.getConfigurationSubscription || []) .map((configSubscription) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const configureAction = (_b = (_a = configSubscription === null || configSubscription === void 0 ? void 0 : configSubscription.connector) === null || _a === void 0 ? void 0 : _a.actions) === null || _b === void 0 ? void 0 : _b.find((action) => action.actionType === "CONFIGURATION"); if (!configureAction || ((_c = configSubscription === null || configSubscription === void 0 ? void 0 : configSubscription.connector) === null || _c === void 0 ? void 0 : _c.active) === false) return null; const actions = []; if ((configSubscription === null || configSubscription === void 0 ? void 0 : configSubscription.status) === "ENABLED") { actions.push({ name: "Disable", actionType: ConnectorActionType.DISABLE, onClick: async () => { await handleDisableConfiguration({ connectorId: configSubscription.connector.id, id: configurationId }); return { data: null, status: "SUCCESS" }; }, }); actions.push({ name: "Delete", actionType: ConnectorActionType.DELETE, onClick: async () => {