0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

refactor: use connector factories and remove enabled

This commit is contained in:
wangsijie 2022-11-28 17:48:15 +08:00 committed by Darcy Ye
parent 7ba40a7782
commit c3a361c437
No known key found for this signature in database
GPG key ID: B46F4C07EDEFC610
22 changed files with 195 additions and 187 deletions
packages
console/src
hooks
pages
ConnectorDetails
components/ConnectorTabs
index.tsx
Connectors
components
ConnectorName
ConnectorRow
CreateForm
Guide
SignInExperienceSetupNotice
index.tsx
utils
SignInExperience
components/Preview
tabs/SignUpAndSignIn/components/SocialConnectorEditBox
UserDetails/components
types
core/src
connectors
routes
schemas/src/types

View file

@ -1,10 +1,9 @@
import type { ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import { useMemo } from 'react';
import useSWR from 'swr';
import type { RequestError } from '@/hooks/use-api';
import type { ConnectorGroup } from '@/types/connector';
import { getConnectorGroups } from '@/pages/Connectors/utils';
// Group connectors by target
const useConnectorGroups = () => {
@ -15,42 +14,7 @@ const useConnectorGroups = () => {
return;
}
return data.reduce<ConnectorGroup[]>((previous, item) => {
const groupIndex = previous.findIndex(
// Only group social connectors
({ target }) => target === item.target && item.type === ConnectorType.Social
);
if (groupIndex === -1) {
return [
...previous,
{
id: item.id, // Take first connector's id as groupId, only used for indexing.
name: item.name,
logo: item.logo,
logoDark: item.logoDark,
description: item.description,
target: item.target,
type: item.type,
enabled: item.enabled,
connectors: [item],
},
];
}
return previous.map((group, index) => {
if (index !== groupIndex) {
return group;
}
return {
...group,
connectors: [...group.connectors, item],
// Group is enabled when any of its connectors is enabled.
enabled: group.enabled || item.enabled,
};
});
}, []);
return getConnectorGroups(data);
}, [data]);
return {

View file

@ -8,7 +8,7 @@ const useEnabledConnectorTypes = () => {
const { data: connectors } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
const enabledConnectorTypes = useMemo(
() => connectors?.filter(({ enabled }) => enabled).map(({ type }) => type) ?? [],
() => connectors?.map(({ type }) => type) ?? [],
[connectors]
);

View file

@ -1,7 +1,6 @@
import type { ConnectorResponse } from '@logto/schemas';
import { ConnectorPlatform } from '@logto/schemas';
import classNames from 'classnames';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import useSWR from 'swr';
@ -18,15 +17,11 @@ type Props = {
const ConnectorTabs = ({ target, connectorId }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { data } = useSWR<ConnectorResponse[]>(`/api/connectors?target=${target}`);
const { data: connectors } = useSWR<ConnectorResponse[]>(`/api/connectors?target=${target}`);
const connectors = useMemo(() => {
if (!data) {
return [];
}
return data.filter(({ enabled }) => enabled);
}, [data]);
if (!connectors) {
return null;
}
if (connectors.length === 0) {
return null;

View file

@ -67,11 +67,7 @@ const ConnectorDetails = () => {
return;
}
await api
.patch(`/api/connectors/${connectorId}/enabled`, {
json: { enabled: false },
})
.json<ConnectorResponse>();
await api.delete(`/api/connectors/${connectorId}`).json<ConnectorResponse>();
setIsDeleted(true);

View file

@ -24,8 +24,7 @@ type Props = {
const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const enabledConnectors = connectors.filter(({ enabled }) => enabled);
const connector = enabledConnectors[0];
const connector = connectors[0];
const theme = useTheme();
if (!connector) {
@ -59,7 +58,7 @@ const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
{type !== ConnectorType.Social && connector.id}
{type === ConnectorType.Social && connectors.length > 1 && (
<div className={styles.platforms}>
{enabledConnectors.map(
{connectors.map(
({ id, platform }) =>
platform && (
<div key={id} className={styles.platform}>

View file

@ -19,17 +19,17 @@ type Props = {
const ConnectorRow = ({ type, connectors, onClickSetup }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const firstEnabledConnector = connectors.find(({ enabled }) => enabled);
const inUse = useConnectorInUse(type, firstEnabledConnector?.target);
const firstConnector = connectors[0];
const inUse = useConnectorInUse(type, firstConnector?.target);
const navigate = useNavigate();
const showSetupButton = type !== ConnectorType.Social && !firstEnabledConnector;
const showSetupButton = type !== ConnectorType.Social && !firstConnector;
const handleClickRow = () => {
if (showSetupButton || !firstEnabledConnector) {
if (showSetupButton || !firstConnector) {
return;
}
navigate(`/connectors/${firstEnabledConnector.id}`);
navigate(`/connectors/${firstConnector.id}`);
};
return (

View file

@ -1,3 +1,4 @@
import type { ConnectorFactoryResponse } from '@logto/schemas';
import { useTranslation } from 'react-i18next';
import RadioGroup, { Radio } from '@/components/RadioGroup';
@ -8,7 +9,7 @@ import type { ConnectorGroup } from '@/types/connector';
import * as styles from './PlatformSelector.module.scss';
type Props = {
connectorGroup: ConnectorGroup;
connectorGroup: ConnectorGroup<ConnectorFactoryResponse & { added: boolean }>;
connectorId?: string;
onConnectorIdChange: (value: string) => void;
};
@ -28,13 +29,13 @@ const PlatformSelector = ({ connectorGroup, connectorId, onConnectorIdChange }:
</div>
<RadioGroup type="plain" name="connector" value={connectorId} onChange={onConnectorIdChange}>
{connectorGroup.connectors.map(
({ platform, id, enabled }) =>
({ platform, id, added }) =>
platform && (
<Radio
key={id}
value={id}
title={connectorPlatformLabel[platform]}
isDisabled={enabled}
isDisabled={added}
/>
)
)}

View file

@ -1,15 +1,18 @@
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import classNames from 'classnames';
import { useMemo, useState } from 'react';
import Modal from 'react-modal';
import useSWR from 'swr';
import Button from '@/components/Button';
import ModalLayout from '@/components/ModalLayout';
import RadioGroup, { Radio } from '@/components/RadioGroup';
import UnnamedTrans from '@/components/UnnamedTrans';
import useConnectorGroups from '@/hooks/use-connector-groups';
import type { RequestError } from '@/hooks/use-api';
import * as modalStyles from '@/scss/modal.module.scss';
import { getConnectorGroups } from '../../utils';
import Guide from '../Guide';
import PlatformSelector from './PlatformSelector';
import * as styles from './index.module.scss';
@ -21,25 +24,44 @@ type Props = {
};
const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
const { data: allGroups, connectors, error } = useConnectorGroups();
const isLoading = !allGroups && !connectors && !error;
const { data: connectors, error: connectorsError } = useSWR<ConnectorResponse[], RequestError>(
'/api/connectors'
);
const { data: factories, error: factoriesError } = useSWR<
ConnectorFactoryResponse[],
RequestError
>('/api/connector-factories');
const isLoading = !factories && !connectors && !connectorsError && !factoriesError;
const [activeGroupId, setActiveGroupId] = useState<string>();
const [activeConnectorId, setActiveConnectorId] = useState<string>();
const [activeFactoryId, setActiveFactoryId] = useState<string>();
const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false);
const groups = useMemo(
() => allGroups?.filter((group) => group.type === type),
[allGroups, type]
);
const groups = useMemo(() => {
if (!factories || !connectors) {
return [];
}
const allGroups = getConnectorGroups<ConnectorFactoryResponse>(
factories.filter(({ type: factoryType }) => factoryType === type)
);
return allGroups.map((group) => ({
...group,
connectors: group.connectors.map((connector) => ({
...connector,
added: connectors.some(({ connectorId }) => connector.id === connectorId),
})),
}));
}, [factories, type, connectors]);
const activeGroup = useMemo(
() => groups?.find(({ id }) => id === activeGroupId),
() => groups.find(({ id }) => id === activeGroupId),
[activeGroupId, groups]
);
const activeConnector = useMemo(
() => connectors?.find(({ id }) => id === activeConnectorId),
[activeConnectorId, connectors]
const activeFactory = useMemo(
() => factories?.find(({ id }) => id === activeFactoryId),
[activeFactoryId, factories]
);
const cardTitle = useMemo(() => {
@ -55,10 +77,6 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
}, [type]);
const handleGroupChange = (groupId: string) => {
if (!groups) {
return;
}
setActiveGroupId(groupId);
const group = groups.find(({ id }) => id === groupId);
@ -67,20 +85,20 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
return;
}
const firstAvailableConnector = group.connectors.find(({ enabled }) => !enabled);
const firstAvailableConnector = group.connectors.find(({ added }) => !added);
setActiveConnectorId(firstAvailableConnector?.id);
setActiveFactoryId(firstAvailableConnector?.id);
};
const closeModal = () => {
setIsGetStartedModalOpen(false);
onClose?.(activeConnectorId);
onClose?.(activeFactoryId);
setActiveGroupId(undefined);
setActiveConnectorId(undefined);
setActiveFactoryId(undefined);
};
const modalSize = useMemo(() => {
if (!groups || groups.length <= 2) {
if (groups.length <= 2) {
return 'medium';
}
@ -103,7 +121,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
<Button
title="general.next"
type="primary"
disabled={!activeConnectorId}
disabled={!activeFactoryId}
onClick={() => {
setIsGetStartedModalOpen(true);
}}
@ -114,53 +132,48 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
onClose={onClose}
>
{isLoading && 'Loading...'}
{error?.message}
{groups && (
<RadioGroup
name="group"
value={activeGroupId}
type="card"
className={classNames(styles.connectorGroup, styles[modalSize])}
onChange={handleGroupChange}
>
{groups.map(({ id, name, logo, description, connectors }) => {
const isDisabled = connectors.every(({ enabled }) => enabled);
{factoriesError?.message ?? connectorsError?.message}
<RadioGroup
name="group"
value={activeGroupId}
type="card"
className={classNames(styles.connectorGroup, styles[modalSize])}
onChange={handleGroupChange}
>
{groups.map(({ id, name, logo, description, connectors }) => {
const isDisabled = connectors.every(({ added }) => added);
return (
<Radio key={id} value={id} isDisabled={isDisabled} disabledLabel="general.added">
<div className={styles.connector}>
<div className={styles.logo}>
<img src={logo} alt="logo" />
return (
<Radio key={id} value={id} isDisabled={isDisabled} disabledLabel="general.added">
<div className={styles.connector}>
<div className={styles.logo}>
<img src={logo} alt="logo" />
</div>
<div className={styles.content}>
<div
className={classNames(styles.name, isDisabled && styles.nameWithRightPadding)}
>
<UnnamedTrans resource={name} />
</div>
<div className={styles.content}>
<div
className={classNames(
styles.name,
isDisabled && styles.nameWithRightPadding
)}
>
<UnnamedTrans resource={name} />
</div>
<div className={styles.description}>
<UnnamedTrans resource={description} />
</div>
<div className={styles.description}>
<UnnamedTrans resource={description} />
</div>
</div>
</Radio>
);
})}
</RadioGroup>
)}
</div>
</Radio>
);
})}
</RadioGroup>
{activeGroup && (
<PlatformSelector
connectorGroup={activeGroup}
connectorId={activeConnectorId}
onConnectorIdChange={setActiveConnectorId}
connectorId={activeFactoryId}
onConnectorIdChange={setActiveFactoryId}
/>
)}
{activeConnector && (
{activeFactory && (
<Modal isOpen={isGetStartedModalOpen} className={modalStyles.fullScreen}>
<Guide connector={activeConnector} onClose={closeModal} />
<Guide connector={activeFactory} onClose={closeModal} />
</Modal>
)}
</ModalLayout>

View file

@ -1,5 +1,5 @@
import { isLanguageTag } from '@logto/language-kit';
import type { ConnectorResponse } from '@logto/schemas';
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import { conditional } from '@silverhand/essentials';
import i18next from 'i18next';
@ -23,7 +23,7 @@ import { safeParseJson } from '@/utilities/json';
import * as styles from './index.module.scss';
type Props = {
connector: ConnectorResponse;
connector: ConnectorFactoryResponse;
onClose: () => void;
};
@ -57,11 +57,10 @@ const Guide = ({ connector, onClose }: Props) => {
return;
}
const { id: connectorId } = connector;
await api
.patch(`/api/connectors/${connectorId}`, { json: { config: result.data } })
.json<ConnectorResponse>();
await api
.patch(`/api/connectors/${connectorId}/enabled`, { json: { enabled: true } })
.post('/api/connectors', { json: { config: result.data, connectorId } })
.json<ConnectorResponse>();
await updateSettings({

View file

@ -16,13 +16,7 @@ const SignInExperienceSetupNotice = () => {
update,
} = useUserPreferences();
if (!connectors || connectorSieNoticeConfirmed) {
return null;
}
const hasSetupConnector = connectors.some(({ enabled }) => enabled);
if (!hasSetupConnector) {
if (!connectors || connectors.length > 0 || connectorSieNoticeConfirmed) {
return null;
}

View file

@ -35,17 +35,13 @@ const Connectors = () => {
const isLightMode = theme === AppearanceMode.LightMode;
const emailConnector = useMemo(() => {
const emailConnectorGroup = data?.find(
({ enabled, type }) => enabled && type === ConnectorType.Email
);
const emailConnectorGroup = data?.find(({ type }) => type === ConnectorType.Email);
return emailConnectorGroup?.connectors[0];
}, [data]);
const smsConnector = useMemo(() => {
const smsConnectorGroup = data?.find(
({ enabled, type }) => enabled && type === ConnectorType.Sms
);
const smsConnectorGroup = data?.find(({ type }) => type === ConnectorType.Sms);
return smsConnectorGroup?.connectors[0];
}, [data]);
@ -55,7 +51,7 @@ const Connectors = () => {
return;
}
return data?.filter(({ enabled, type }) => enabled && type === ConnectorType.Social);
return data?.filter(({ type }) => type === ConnectorType.Social);
}, [data, isSocial]);
return (

View file

@ -0,0 +1,44 @@
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import type { ConnectorGroup } from '@/types/connector';
export const getConnectorGroups = <
T extends ConnectorResponse | ConnectorFactoryResponse = ConnectorResponse
>(
connectors: T[]
) => {
return connectors.reduce<Array<ConnectorGroup<T>>>((previous, item) => {
const groupIndex = previous.findIndex(
// Only group social connectors
({ target }) => target === item.target && item.type === ConnectorType.Social
);
if (groupIndex === -1) {
return [
...previous,
{
id: item.id, // Take first connector's id as groupId, only used for indexing.
name: item.name,
logo: item.logo,
logoDark: item.logoDark,
description: item.description,
target: item.target,
type: item.type,
connectors: [item],
},
];
}
return previous.map((group, index) => {
if (index !== groupIndex) {
return group;
}
return {
...group,
connectors: [...group.connectors, item],
};
});
}, []);
};

View file

@ -88,18 +88,14 @@ const Preview = ({ signInExperience, className }: Props) => {
>(
(previous, connectorTarget) => [
...previous,
...allConnectors.filter(({ target, enabled }) => target === connectorTarget && enabled),
...allConnectors.filter(({ target }) => target === connectorTarget),
],
[]
);
const hasEmailConnector = allConnectors.some(
({ type, enabled }) => enabled && type === ConnectorType.Email
);
const hasEmailConnector = allConnectors.some(({ type }) => type === ConnectorType.Email);
const hasSmsConnector = allConnectors.some(
({ type, enabled }) => enabled && type === ConnectorType.Sms
);
const hasSmsConnector = allConnectors.some(({ type }) => type === ConnectorType.Sms);
return {
signInExperience: {

View file

@ -54,13 +54,11 @@ const AddButton = ({ options, onSelected, hasSelectedConnectors }: Props) => {
<img src={logo} alt={target} className={styles.logo} />
<UnnamedTrans resource={name} className={styles.name} />
{connectors.length > 1 &&
connectors
.filter(({ enabled }) => enabled)
.map(({ platform }) => (
<div key={platform} className={styles.icon}>
{platform && <ConnectorPlatformIcon platform={platform} />}
</div>
))}
connectors.map(({ platform }) => (
<div key={platform} className={styles.icon}>
{platform && <ConnectorPlatformIcon platform={platform} />}
</div>
))}
</div>
</DropdownItem>
))}

View file

@ -20,13 +20,11 @@ const SelectedConnectorItem = ({ data: { logo, target, name, connectors }, onDel
<img src={logo} alt={target} className={styles.logo} />
<UnnamedTrans resource={name} className={styles.name} />
{connectors.length > 1 &&
connectors
.filter(({ enabled }) => enabled)
.map(({ platform }) => (
<div key={platform} className={styles.icon}>
{platform && <ConnectorPlatformIcon platform={platform} />}
</div>
))}
connectors.map(({ platform }) => (
<div key={platform} className={styles.icon}>
{platform && <ConnectorPlatformIcon platform={platform} />}
</div>
))}
</div>
<IconButton
onClick={() => {

View file

@ -54,8 +54,7 @@ const SocialConnectorEditBox = ({ value, onChange }: Props) => {
.filter((item): item is ConnectorGroup => Boolean(item));
const connectorOptions = connectorData.filter(
({ target, type, enabled }) =>
!value.includes(target) && type === ConnectorType.Social && enabled
({ target, type }) => !value.includes(target) && type === ConnectorType.Social
);
return (

View file

@ -1,15 +1,18 @@
import type { Identities, ConnectorResponse } from '@logto/schemas';
import type { Optional } from '@silverhand/essentials';
import { conditional } from '@silverhand/essentials';
import { useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import useSWR from 'swr';
import Button from '@/components/Button';
import CopyToClipboard from '@/components/CopyToClipboard';
import DeleteConfirmModal from '@/components/DeleteConfirmModal';
import TableError from '@/components/Table/TableError';
import UnnamedTrans from '@/components/UnnamedTrans';
import type { RequestError } from '@/hooks/use-api';
import useApi from '@/hooks/use-api';
import useConnectorGroups from '@/hooks/use-connector-groups';
import { getConnectorGroups } from '@/pages/Connectors/utils';
import * as styles from './UserConnectors.module.scss';
@ -24,8 +27,9 @@ type DisplayConnector = Pick<ConnectorResponse, 'target' | 'logo' | 'name'> & {
const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
const api = useApi();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { data: connectorGroups, error, mutate } = useConnectorGroups();
const { data, error, mutate } = useSWR<ConnectorResponse[], RequestError>('/api/connectors');
const [deletingConnector, setDeletingConnector] = useState<DisplayConnector>();
const connectorGroups = conditional(data && getConnectorGroups(data));
const isLoading = !connectorGroups && !error;
const [isSubmitting, setIsSubmitting] = useState(false);

View file

@ -1,10 +1,9 @@
import type { ConnectorResponse } from '@logto/schemas';
export type ConnectorGroup = Pick<
export type ConnectorGroup<T = ConnectorResponse> = Pick<
ConnectorResponse,
'name' | 'logo' | 'logoDark' | 'target' | 'type' | 'description'
> & {
id: string;
enabled: boolean;
connectors: ConnectorResponse[];
connectors: T[];
};

View file

@ -15,7 +15,7 @@ import { findAllConnectors } from '#src/queries/connector.js';
import { defaultConnectorMethods } from './consts.js';
import { metaUrl } from './meta-url.js';
import type { ConnectorFactory, LogtoConnector } from './types.js';
import { getConnectorConfig, readUrl, validateConnectorModule } from './utilities/index.js';
import { getConnectorConfig, parseMetadata, validateConnectorModule } from './utilities/index.js';
const currentDirname = path.dirname(fileURLToPath(metaUrl));
@ -52,7 +52,7 @@ export const loadConnectorFactories = async () => {
validateConnectorModule(rawConnector);
return {
metadata: rawConnector.metadata,
metadata: await parseMetadata(rawConnector.metadata, packagePath),
type: rawConnector.type,
createConnector,
path: packagePath,
@ -106,22 +106,13 @@ export const getLogtoConnectors = async (): Promise<LogtoConnector[]> => {
},
});
validateConnectorModule(rawConnector);
const rawMetadata = await parseMetadata(rawConnector.metadata, packagePath);
const connector: AllConnector = {
...defaultConnectorMethods,
...rawConnector,
metadata: {
...rawConnector.metadata,
logo: await readUrl(rawConnector.metadata.logo, packagePath, 'svg'),
logoDark:
rawConnector.metadata.logoDark &&
(await readUrl(rawConnector.metadata.logoDark, packagePath, 'svg')),
readme: await readUrl(rawConnector.metadata.readme, packagePath, 'text'),
configTemplate: await readUrl(
rawConnector.metadata.configTemplate,
packagePath,
'text'
),
...rawMetadata,
...metadata,
},
};

View file

@ -2,7 +2,7 @@ import { existsSync } from 'fs';
import { readFile } from 'fs/promises';
import path from 'path';
import type { BaseConnector } from '@logto/connector-kit';
import type { AllConnector, BaseConnector } from '@logto/connector-kit';
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
import RequestError from '#src/errors/RequestError/index.js';
@ -59,3 +59,13 @@ export const readUrl = async (
return readFile(path.join(baseUrl, url), 'utf8');
};
export const parseMetadata = async (metadata: AllConnector['metadata'], packagePath: string) => {
return {
...metadata,
logo: await readUrl(metadata.logo, packagePath, 'svg'),
logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'svg')),
readme: await readUrl(metadata.readme, packagePath, 'text'),
configTemplate: await readUrl(metadata.configTemplate, packagePath, 'text'),
};
};

View file

@ -1,6 +1,6 @@
import { MessageTypes } from '@logto/connector-kit';
import { emailRegEx, phoneRegEx } from '@logto/core-kit';
import type { ConnectorResponse } from '@logto/schemas';
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
import { arbitraryObjectGuard, Connectors, ConnectorType } from '@logto/schemas';
import { buildIdGenerator } from '@logto/shared';
import { object, string } from 'zod';
@ -71,7 +71,13 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
router.get('/connector-factories', async (ctx, next) => {
const connectorFactories = await loadConnectorFactories();
ctx.body = connectorFactories.map(({ metadata, type }) => ({ type, ...metadata }));
const formatedFactories: ConnectorFactoryResponse[] = connectorFactories.map(
({ metadata, type }) => ({
type,
...metadata,
})
);
ctx.body = formatedFactories;
return next();
});

View file

@ -8,3 +8,9 @@ export { ConnectorType, ConnectorPlatform } from '@logto/connector-kit';
export type ConnectorResponse = Omit<Connector, 'metadata'> &
Omit<BaseConnector<ConnectorType>, 'configGuard' | 'metadata'> &
ConnectorMetadata;
export type ConnectorFactoryResponse = Omit<
BaseConnector<ConnectorType>,
'configGuard' | 'metadata'
> &
ConnectorMetadata;