diff --git a/packages/connector-aliyun-dm/src/index.ts b/packages/connector-aliyun-dm/src/index.ts index 87a7a221a..62fc944b3 100644 --- a/packages/connector-aliyun-dm/src/index.ts +++ b/packages/connector-aliyun-dm/src/index.ts @@ -32,10 +32,12 @@ export default class AliyunDmConnector implements EmailConnector { } }; - public sendMessage: EmailSendMessageFunction = async (address, type, data) => { - const config = await this.getConfig(this.metadata.id); - await this.validateConfig(config); - const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = config; + // eslint-disable-next-line complexity + public sendMessage: EmailSendMessageFunction = async (address, type, data, config) => { + const emailConfig = + (config as AliyunDmConfig | undefined) ?? (await this.getConfig(this.metadata.id)); + await this.validateConfig(emailConfig); + const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = emailConfig; const template = templates.find((template) => template.usageType === type); assert( diff --git a/packages/connector-aliyun-sms/src/index.ts b/packages/connector-aliyun-sms/src/index.ts index f0bdd32e6..9cfbae81a 100644 --- a/packages/connector-aliyun-sms/src/index.ts +++ b/packages/connector-aliyun-sms/src/index.ts @@ -26,10 +26,11 @@ export default class AliyunSmsConnector implements SmsConnector { } }; - public sendMessage: SmsSendMessageFunction = async (phone, type, { code }) => { - const config = await this.getConfig(this.metadata.id); - await this.validateConfig(config); - const { accessKeyId, accessKeySecret, signName, templates } = config; + public sendMessage: SmsSendMessageFunction = async (phone, type, { code }, config) => { + const smsConfig = + (config as AliyunSmsConfig | undefined) ?? (await this.getConfig(this.metadata.id)); + await this.validateConfig(smsConfig); + const { accessKeyId, accessKeySecret, signName, templates } = smsConfig; const template = templates.find(({ usageType }) => usageType === type); assert( diff --git a/packages/connector-types/src/index.ts b/packages/connector-types/src/index.ts index 8d7b3c190..538220da3 100644 --- a/packages/connector-types/src/index.ts +++ b/packages/connector-types/src/index.ts @@ -64,13 +64,15 @@ export type SmsMessageTypes = EmailMessageTypes; export type EmailSendMessageFunction = ( address: string, type: keyof EmailMessageTypes, - payload: EmailMessageTypes[typeof type] + payload: EmailMessageTypes[typeof type], + config?: Record ) => Promise; export type SmsSendMessageFunction = ( phone: string, type: keyof SmsMessageTypes, - payload: SmsMessageTypes[typeof type] + payload: SmsMessageTypes[typeof type], + config?: Record ) => Promise; export interface BaseConnector { diff --git a/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx index 1a997dd71..dd29ae73e 100644 --- a/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx +++ b/packages/console/src/pages/ConnectorDetails/components/SenderTester/index.tsx @@ -1,7 +1,6 @@ import { ConnectorType } from '@logto/schemas'; import { phoneRegEx, emailRegEx } from '@logto/shared'; import classNames from 'classnames'; -import ky from 'ky'; import React, { useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -10,18 +9,21 @@ import Button from '@/components/Button'; import FormField from '@/components/FormField'; import TextInput from '@/components/TextInput'; import Tooltip from '@/components/Tooltip'; +import useApi from '@/hooks/use-api'; import * as styles from './index.module.scss'; type Props = { connectorType: Exclude; + config?: string; + className?: string; }; type FormData = { sendTo: string; }; -const SenderTester = ({ connectorType }: Props) => { +const SenderTester = ({ connectorType, config, className }: Props) => { const buttonPosReference = useRef(null); const [showTooltip, setShowTooltip] = useState(false); const [isSubmitting, seIsSubmitting] = useState(false); @@ -33,6 +35,7 @@ const SenderTester = ({ connectorType }: Props) => { }, } = useForm(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const api = useApi(); const isSms = connectorType === ConnectorType.SMS; useEffect(() => { @@ -52,12 +55,13 @@ const SenderTester = ({ connectorType }: Props) => { const onSubmit = handleSubmit(async (formData) => { const { sendTo } = formData; + const configJson = config ? (JSON.parse(config) as JSON) : undefined; seIsSubmitting(true); - const data = isSms ? { phone: sendTo } : { email: sendTo }; + const data = { config: configJson, ...(isSms ? { phone: sendTo } : { email: sendTo }) }; try { - await ky + await api .post(`/api/connectors/test/${connectorType.toLowerCase()}`, { json: data, }) @@ -70,7 +74,7 @@ const SenderTester = ({ connectorType }: Props) => { }); return ( -
+
{
{showTooltip && ( diff --git a/packages/console/src/pages/ConnectorDetails/index.module.scss b/packages/console/src/pages/ConnectorDetails/index.module.scss index 1c7575ea2..8cadc188d 100644 --- a/packages/console/src/pages/ConnectorDetails/index.module.scss +++ b/packages/console/src/pages/ConnectorDetails/index.module.scss @@ -93,3 +93,7 @@ .resetIcon { color: var(--color-icon); } + +.readme { + margin-top: _.unit(-6); +} diff --git a/packages/console/src/pages/ConnectorDetails/index.tsx b/packages/console/src/pages/ConnectorDetails/index.tsx index aa8a7dfbf..b4dad0b62 100644 --- a/packages/console/src/pages/ConnectorDetails/index.tsx +++ b/packages/console/src/pages/ConnectorDetails/index.tsx @@ -210,7 +210,9 @@ const ConnectorDetails = () => { }} /> - {data.type !== ConnectorType.Social && } + {data.type !== ConnectorType.Social && ( + + )} {saveError &&
{saveError}
}
diff --git a/packages/core/src/routes/connector.test.ts b/packages/core/src/routes/connector.test.ts index c75e5cb6c..c96e343f6 100644 --- a/packages/core/src/routes/connector.test.ts +++ b/packages/core/src/routes/connector.test.ts @@ -123,11 +123,16 @@ describe('connector route', () => { const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendMessage'); const response = await connectorRequest .post('/connectors/test/email') - .send({ email: 'test@email.com' }); + .send({ email: 'test@email.com', config: { test: 123 } }); expect(sendMessageSpy).toHaveBeenCalledTimes(1); - expect(sendMessageSpy).toHaveBeenCalledWith('test@email.com', 'Test', { - code: 'email-test', - }); + expect(sendMessageSpy).toHaveBeenCalledWith( + 'test@email.com', + 'Test', + { + code: 'email-test', + }, + { test: 123 } + ); expect(response).toHaveProperty('statusCode', 204); }); @@ -166,11 +171,16 @@ describe('connector route', () => { const sendMessageSpy = jest.spyOn(mockedSmsConnectorInstance, 'sendMessage'); const response = await connectorRequest .post('/connectors/test/sms') - .send({ phone: '12345678901' }); + .send({ phone: '12345678901', config: { test: 123 } }); expect(sendMessageSpy).toHaveBeenCalledTimes(1); - expect(sendMessageSpy).toHaveBeenCalledWith('12345678901', 'Test', { - code: '123456', - }); + expect(sendMessageSpy).toHaveBeenCalledWith( + '12345678901', + 'Test', + { + code: '123456', + }, + { test: 123 } + ); expect(response).toHaveProperty('statusCode', 204); }); diff --git a/packages/core/src/routes/connector.ts b/packages/core/src/routes/connector.ts index ebaf15ac7..46ac14043 100644 --- a/packages/core/src/routes/connector.ts +++ b/packages/core/src/routes/connector.ts @@ -1,4 +1,4 @@ -import { ConnectorDTO, Connectors, ConnectorType } from '@logto/schemas'; +import { arbitraryObjectGuard, ConnectorDTO, Connectors, ConnectorType } from '@logto/schemas'; import { emailRegEx, phoneRegEx } from '@logto/shared'; import { object, string } from 'zod'; @@ -153,15 +153,16 @@ export default function connectorRoutes(router: T) { koaGuard({ body: object({ email: string().regex(emailRegEx), + config: arbitraryObjectGuard.optional(), }), }), async (ctx, next) => { - const { email } = ctx.guard.body; + const { email, config } = ctx.guard.body; const connectorInstances = await getConnectorInstances(); const connector = connectorInstances.find( (connector): connector is EmailConnectorInstance => - connector.connector.enabled && connector.metadata.type === ConnectorType.Email + connector.metadata.type === ConnectorType.Email ); assertThat( @@ -169,9 +170,18 @@ export default function connectorRoutes(router: T) { new RequestError({ code: 'connector.not_found', type: ConnectorType.Email }) ); - await connector.sendMessage(email, 'Test', { - code: 'email-test', - }); + if (config) { + await connector.validateConfig(config); + } + + await connector.sendMessage( + email, + 'Test', + { + code: 'email-test', + }, + config + ); ctx.status = 204; @@ -184,15 +194,16 @@ export default function connectorRoutes(router: T) { koaGuard({ body: object({ phone: string().regex(phoneRegEx), + config: arbitraryObjectGuard.optional(), }), }), async (ctx, next) => { - const { phone } = ctx.guard.body; + const { phone, config } = ctx.guard.body; const connectorInstances = await getConnectorInstances(); const connector = connectorInstances.find( (connector): connector is SmsConnectorInstance => - connector.connector.enabled && connector.metadata.type === ConnectorType.SMS + connector.metadata.type === ConnectorType.SMS ); assertThat( @@ -200,9 +211,14 @@ export default function connectorRoutes(router: T) { new RequestError({ code: 'connector.not_found', type: ConnectorType.SMS }) ); - await connector.sendMessage(phone, 'Test', { - code: '123456', - }); + await connector.sendMessage( + phone, + 'Test', + { + code: '123456', + }, + config + ); ctx.status = 204;