@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
JavaScript
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 () => {