mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
feat(connector): add YunPian SMS connector (#6906)
* feat(connector): add YunPian SMS connector * chore: update README and pnpm lock * chore: update SVG and error messages --------- Co-authored-by: Charles Zhao <charleszhao@silverhand.io>
This commit is contained in:
parent
3fa2b796e6
commit
3004ae9a63
12 changed files with 603 additions and 1 deletions
5
.changeset/brown-donkeys-share.md
Normal file
5
.changeset/brown-donkeys-share.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/connector-yunpian-sms": minor
|
||||
---
|
||||
|
||||
add YunPian SMS connector
|
|
@ -26,7 +26,6 @@
|
|||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:fix": "eslint --ext .ts src --fix",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"test": "vitest src",
|
||||
"test:ci": "pnpm run test --silent --coverage",
|
||||
|
|
60
packages/connectors/connector-yunpian-sms/README.md
Normal file
60
packages/connectors/connector-yunpian-sms/README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Yunpian SMS connector
|
||||
|
||||
The official Logto connector for Yunpian SMS service. [中文文档](https://github.com/logto-io/logto/tree/master/packages/connectors/connector-yunpian-sms/README.zh-CN.md)
|
||||
|
||||
**Table of contents**
|
||||
|
||||
- [Yunpian SMS connector](#yunpian-sms-connector)
|
||||
- [Get started](#get-started)
|
||||
- [Set up SMS service in Yunpian Console](#set-up-sms-service-in-yunpian-console)
|
||||
- [Create a Yunpian account](#create-a-yunpian-account)
|
||||
- [Get API KEY](#get-api-key)
|
||||
- [Configure SMS templates](#configure-sms-templates)
|
||||
- [Configure in Logto](#configure-in-logto)
|
||||
- [Notes](#notes)
|
||||
- [References](#references)
|
||||
|
||||
## Get started
|
||||
|
||||
Yunpian is a communication service provider offering various services including SMS. The Yunpian SMS Connector is a plugin provided by the Logto team to integrate with Yunpian's SMS service, enabling Logto end-users to register and sign in via SMS verification codes.
|
||||
|
||||
## Set up SMS service in Yunpian Console
|
||||
|
||||
### Create a Yunpian account
|
||||
|
||||
Visit [Yunpian's website](https://www.yunpian.com/) to register an account and complete real-name verification.
|
||||
|
||||
### Get API KEY
|
||||
|
||||
1. Log in to Yunpian Console
|
||||
2. Go to "Account Settings" -> "Sub-account Management"
|
||||
3. Find and copy the API KEY
|
||||
|
||||
### Configure SMS templates
|
||||
|
||||
1. In Yunpian Console, go to "Domestic SMS" -> "Signature Filing"
|
||||
2. Create and submit a signature, wait for carrier approval
|
||||
3. Go to "Domestic SMS" -> "Template Filing" and select "Verification Code"
|
||||
4. Create a verification code template, ensure it includes the `#code#` variable (you can also use "Common Templates" to speed up the approval process)
|
||||
5. Wait for template approval
|
||||
6. If you need to send international SMS, repeat the above steps but select "International SMS" -> "Template Filing"
|
||||
|
||||
## Configure in Logto
|
||||
|
||||
1. In Logto Console, go to "Connectors"
|
||||
2. Find and click "Yunpian SMS Service"
|
||||
3. Fill in the configuration form:
|
||||
- API KEY: The API KEY obtained from Yunpian
|
||||
- SMS templates: Configure templates according to usage, ensure they match exactly with approved templates in Yunpian
|
||||
|
||||
## Notes
|
||||
|
||||
1. SMS template content must match exactly with the approved template in Yunpian
|
||||
2. The verification code variable placeholder in Yunpian templates is `#code#`, while in the connector configuration it's `{{code}}`
|
||||
3. Yunpian automatically adds the default signature based on API KEY, no need to include signature in the template content
|
||||
4. It's recommended to test the configuration before formal use
|
||||
|
||||
## References
|
||||
|
||||
- [Yunpian Development Documentation](https://www.yunpian.com/official/document/sms/zh_CN/introduction_brief)
|
||||
- [Logto SMS Connector Guide](https://docs.logto.io/docs/recipes/configure-connectors/sms-connector/)
|
60
packages/connectors/connector-yunpian-sms/README.zh-CN.md
Normal file
60
packages/connectors/connector-yunpian-sms/README.zh-CN.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# 云片网短信连接器
|
||||
|
||||
云片网短信服务 Logto 官方连接器
|
||||
|
||||
**目录**
|
||||
|
||||
- [云片网短信连接器](#云片网短信连接器)
|
||||
- [开始使用](#开始使用)
|
||||
- [在云片网中配置](#在云片网中配置)
|
||||
- [创建云片网账号](#创建云片网账号)
|
||||
- [获取 API KEY](#获取-api-key)
|
||||
- [配置短信模板](#配置短信模板)
|
||||
- [在 Logto 中配置](#在-logto-中配置)
|
||||
- [注意事项](#注意事项)
|
||||
- [参考](#参考)
|
||||
|
||||
## 开始使用
|
||||
|
||||
云片网是一家通信服务提供商,提供包括短信在内的多种通信服务。云片网 SMS 连接器是由 Logto 团队提供的插件,用于调用云片网的短信服务,帮助 Logto 终端用户通过短信验证码进行注册和登录。
|
||||
|
||||
## 在云片网中配置
|
||||
|
||||
### 创建云片网账号
|
||||
|
||||
访问[云片网官网](https://www.yunpian.com/),注册账号并完成实名认证。
|
||||
|
||||
### 获取 API KEY
|
||||
|
||||
1. 登录云片网控制台
|
||||
2. 进入"账户设置" -> "子账户管理"
|
||||
3. 找到并复制 API KEY
|
||||
|
||||
### 配置短信模板
|
||||
|
||||
1. 在云片网控制台中进入"国内短信" -> "签名报备"
|
||||
2. 创建签名并提交,等待运营商审核通过
|
||||
3. 在云片网控制台中进入"国内短信" -> "模板报备",选择"验证码类"
|
||||
4. 创建验证码类短信模板,确保模板中包含 `#code#` 变量(也可以直接使用`常用模板`申请,加快审核速度)
|
||||
5. 等待模板审核通过
|
||||
6. 如果您需要发送国际短信,请重复上述步骤,选择"国际短信" -> "模板报备"并提交
|
||||
|
||||
## 在 Logto 中配置
|
||||
|
||||
1. 在 Logto 管理控制台中转到"连接器"
|
||||
2. 找到并点击"云片短信服务"
|
||||
3. 在配置表单中填入:
|
||||
- API KEY: 从云片网获取的 API KEY
|
||||
- 短信模板: 按照用途配置相应的模板内容,确保与云片网审核通过的模板内容一致
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 短信模板内容必须与云片网审核通过的模板完全一致
|
||||
2. 短信审核模板中的验证码变量占位符为 `#code#`,模板配置中验证码变量占位符为 `{{code}}`
|
||||
3. 云片网会自动根据 API KEY 添加默认签名,无需在发送模板内容中包含签名
|
||||
4. 建议在正式使用前进行测试,确保配置正确
|
||||
|
||||
## 参考
|
||||
|
||||
- [云片网开发文档](https://www.yunpian.com/official/document/sms/zh_CN/introduction_brief)
|
||||
- [Logto SMS 连接器指南](https://docs.logto.io/zh-CN/connectors/sms-connectors)
|
13
packages/connectors/connector-yunpian-sms/logo.svg
Normal file
13
packages/connectors/connector-yunpian-sms/logo.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||
<path fill="#3363fe"
|
||||
d="M759.96,197.79c7.49,5.01,45.89,34.47,43.09,42.21-.75,2.09-2.7,2.88-4.8,2.73-10.38-8.49-21.16-16.2-32.35-23.13-32.52-17.36-67.36-30.96-104.34-35.45-112.89-13.71-245.06,47.72-260.53,170.28-21.9-14.71-44.76-26.08-70.95-31.14-57.56-11.13-124.06,6.22-168.97,44.01-142.52,119.92-40.88,305.7,137.29,264.19,129.29-30.12,200.64-189.22,293.95-269.18,109.37-93.72,323.64-75.27,359.35,82.93,32.34,143.27-106.65,264.28-240.35,272.03-9.53.55-44.43-3.54-48.6-1.01-1.46.89-11.57,23.12-14.23,27.41-74.07,119.23-282.01,141.14-394.2,68.8-24.36-14.32-46.88-32.44-63.45-55.52-.6-.7-1.39-1.27-1.98-1.98-5.08-7.68-13.98-24.59,0-11.9,3.11,3.7,4.28,4.82,7.93,7.93,5.73,5.45,11.68,10.74,17.85,15.86,14.73,9.91,32.06,20.84,48.24,28.1,127.57,57.3,310.97,3.75,336.46-147.07,2.75-1.89,28.2,15.59,34.3,18.22,124.32,53.63,302.59-55.7,268.47-197.08-24.89-103.12-152.35-126.67-239.06-89.54-60.41,25.86-122.86,120.6-165.81,171.28-59,69.61-129.67,131.66-224.11,142.72-159.74,18.71-282.41-116.51-206.62-270.08,39.5-80.03,132.23-134.89,220.54-140.34,17.07-1.05,33.67,2.41,50.63,1.05,67.56-151.6,296.6-175.17,422.27-86.33Z" />
|
||||
<path fill="#4c6eda"
|
||||
d="M823.42,265.2c4.23,7.92,1.09,8.58-3.97,1.98-14.67-18.96-35.29-32.46-53.54-47.59.67.36,3.43-.12,5.8,1.06,6.82,3.41,19.15,16.57,24.97,18.75,3.66,1.37,3.59-2.28,2.64-4.63-4.48-11.06-32.18-25.96-39.36-36.99,6.74,1.28,13.53,7.45,18.78,11.95,19.75,16.94,32.27,32.85,44.67,55.47Z" />
|
||||
<path fill="#748ede" d="M254.33,812.47c-23.97-12.33-50.04-31.75-63.45-55.52,17.47,20.4,40.84,40.94,63.45,55.52Z" />
|
||||
<path fill="#748ede"
|
||||
d="M188.9,743.07c-1.63-1.16-4.97-3.67-5.97-.97.12,4.28,7.76,6.96,5.97,12.87-8.89-10.66-19.39-23.26-19.81-37.67.82-1.11,16.29,18.75,17.92,20.8,1.44,1.82,1.47,4.48,1.89,4.97Z" />
|
||||
<path fill="#7c91d4"
|
||||
d="M823.42,265.2c3.49,6.37,10.8,12.93,9.89,21.81-6.86-3.75-10.1-14.96-13.86-19.83,4.05,2.53,4.3,3.31,3.97-1.98Z" />
|
||||
<path fill="#748ede" d="M214.68,766.87c-5.82-3.91-16.06-9.03-17.85-15.86,5.38,4.58,14.24,8.03,17.85,15.86Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
68
packages/connectors/connector-yunpian-sms/package.json
Normal file
68
packages/connectors/connector-yunpian-sms/package.json
Normal file
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"name": "@logto/connector-yunpian-sms",
|
||||
"version": "1.0.0",
|
||||
"description": "云片网 SMS connector implementation.",
|
||||
"author": "Silverhand Inc. <contact@silverhand.io>",
|
||||
"dependencies": {
|
||||
"@logto/connector-kit": "workspace:^4.0.0",
|
||||
"@silverhand/essentials": "^2.9.1",
|
||||
"got": "^14.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
"exports": "./lib/index.js",
|
||||
"license": "MPL-2.0",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"lib",
|
||||
"docs",
|
||||
"logo.svg",
|
||||
"logo-dark.svg"
|
||||
],
|
||||
"scripts": {
|
||||
"precommit": "lint-staged",
|
||||
"check": "tsc --noEmit",
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"lint": "eslint --ext .ts src",
|
||||
"lint:report": "pnpm lint --format json --output-file report.json",
|
||||
"test": "vitest src",
|
||||
"test:ci": "pnpm run test --silent --coverage",
|
||||
"prepublishOnly": "pnpm build"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@silverhand",
|
||||
"settings": {
|
||||
"import/core-modules": [
|
||||
"@silverhand/essentials",
|
||||
"got",
|
||||
"nock",
|
||||
"snakecase-keys",
|
||||
"zod"
|
||||
]
|
||||
}
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "6.0.1",
|
||||
"@silverhand/ts-config": "6.0.0",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"eslint": "^8.56.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"nock": "^13.3.1",
|
||||
"prettier": "^3.0.0",
|
||||
"supertest": "^7.0.0",
|
||||
"tsup": "^8.3.0",
|
||||
"typescript": "^5.5.3",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
72
packages/connectors/connector-yunpian-sms/src/constant.ts
Normal file
72
packages/connectors/connector-yunpian-sms/src/constant.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import type { ConnectorMetadata } from '@logto/connector-kit';
|
||||
import { ConnectorConfigFormItemType } from '@logto/connector-kit';
|
||||
|
||||
export const endpoint = 'https://sms.yunpian.com/v2/sms/single_send.json';
|
||||
|
||||
export const defaultMetadata: ConnectorMetadata = {
|
||||
id: 'yunpian-sms',
|
||||
target: 'yunpian-sms',
|
||||
platform: null,
|
||||
name: {
|
||||
en: 'YunPian SMS Service',
|
||||
zh: '云片短信服务',
|
||||
},
|
||||
logo: './logo.svg',
|
||||
logoDark: null,
|
||||
description: {
|
||||
en: 'YunPian is a SMS service provider.',
|
||||
zh: '云片网是一家短信服务提供商。',
|
||||
},
|
||||
readme: './README.md',
|
||||
formItems: [
|
||||
{
|
||||
key: 'apikey',
|
||||
label: 'API Key',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
required: true,
|
||||
placeholder: '<api-key>',
|
||||
},
|
||||
{
|
||||
key: 'templates',
|
||||
label: 'SMS Template',
|
||||
type: ConnectorConfigFormItemType.Json,
|
||||
required: true,
|
||||
defaultValue: [
|
||||
{
|
||||
usageType: 'SignIn',
|
||||
content: '您的验证码是 {{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'Register',
|
||||
content: '您的验证码是 {{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'ForgotPassword',
|
||||
content: '您的验证码是 {{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'Generic',
|
||||
content: '您的验证码是 {{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'enableInternational',
|
||||
label: 'Enable International SMS',
|
||||
description:
|
||||
'* To enable it, you need to apply for international templates at the same time.',
|
||||
type: ConnectorConfigFormItemType.Switch,
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
key: 'unsupportedCountriesMsg',
|
||||
label: 'Unsupported Countries Error Message',
|
||||
description:
|
||||
'The message to be displayed when the phone number is not supported. If left empty, no error will be returned.',
|
||||
type: ConnectorConfigFormItemType.Text,
|
||||
required: false,
|
||||
defaultValue: 'The administrator has not enabled international SMS services.',
|
||||
},
|
||||
],
|
||||
};
|
54
packages/connectors/connector-yunpian-sms/src/index.test.ts
Normal file
54
packages/connectors/connector-yunpian-sms/src/index.test.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { TemplateType } from '@logto/connector-kit';
|
||||
|
||||
import { endpoint } from './constant.js';
|
||||
import createConnector from './index.js';
|
||||
import { mockedConfig } from './mock.js';
|
||||
|
||||
const getConfig = vi.fn().mockResolvedValue(mockedConfig);
|
||||
|
||||
describe('yunpian SMS connector', () => {
|
||||
it('init without throwing errors', async () => {
|
||||
await expect(createConnector({ getConfig })).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
describe('sendMessage()', async () => {
|
||||
const connector = await createConnector({ getConfig });
|
||||
const { sendMessage } = connector;
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('should send message successfully', async () => {
|
||||
const mockResponse = {
|
||||
code: 0,
|
||||
msg: '发送成功',
|
||||
count: 1,
|
||||
fee: 0.05,
|
||||
unit: 'RMB',
|
||||
mobile: '13800138000',
|
||||
sid: 3_310_228_982,
|
||||
};
|
||||
|
||||
nock(endpoint).post('').reply(200, mockResponse);
|
||||
|
||||
await expect(
|
||||
sendMessage({
|
||||
to: '13800138000',
|
||||
type: TemplateType.Generic,
|
||||
payload: { code: '1234' },
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
127
packages/connectors/connector-yunpian-sms/src/index.ts
Normal file
127
packages/connectors/connector-yunpian-sms/src/index.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { assert } from '@silverhand/essentials';
|
||||
import { got, RequestError } from 'got';
|
||||
|
||||
import type {
|
||||
GetConnectorConfig,
|
||||
SendMessageFunction,
|
||||
CreateConnector,
|
||||
SmsConnector,
|
||||
} from '@logto/connector-kit';
|
||||
import {
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
validateConfig,
|
||||
ConnectorType,
|
||||
replaceSendMessageHandlebars,
|
||||
} from '@logto/connector-kit';
|
||||
|
||||
import { defaultMetadata, endpoint } from './constant.js';
|
||||
import {
|
||||
yunpianSmsConfigGuard,
|
||||
type YunpianSmsPayload,
|
||||
yunpianErrorResponseGuard,
|
||||
} from './types.js';
|
||||
|
||||
const isChinaPhoneNumber = (phone: string) => {
|
||||
// Match formats:
|
||||
// 1. +86 followed by 11 digits, first digit must be 1
|
||||
// 2. 86 followed by 11 digits, first digit must be 1
|
||||
const pattern = /^(\+?86)1[3-9]\d{9}$/;
|
||||
|
||||
return pattern.test(phone);
|
||||
};
|
||||
|
||||
const formatPhoneNumber = (phoneNumber: string) => {
|
||||
const phone = phoneNumber.replaceAll(/\s/g, '');
|
||||
|
||||
if (!isChinaPhoneNumber(phone)) {
|
||||
if (!phone.startsWith('+')) {
|
||||
return `+${phone}`;
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
// If it starts with +86 or 86, truncate the last 11 digits
|
||||
if (phone.startsWith('+86')) {
|
||||
return phone.slice(3);
|
||||
}
|
||||
if (phone.startsWith('86')) {
|
||||
return phone.slice(2);
|
||||
}
|
||||
|
||||
return phone;
|
||||
};
|
||||
|
||||
const sendMessage =
|
||||
(getConfig: GetConnectorConfig): SendMessageFunction =>
|
||||
async (data, inputConfig) => {
|
||||
const { to, type, payload } = data;
|
||||
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
|
||||
validateConfig(config, yunpianSmsConfigGuard);
|
||||
const { apikey, templates, enableInternational, unsupportedCountriesMsg } = config;
|
||||
|
||||
const template = templates.find((template) => template.usageType === type);
|
||||
assert(
|
||||
template,
|
||||
new ConnectorError(
|
||||
ConnectorErrorCodes.TemplateNotFound,
|
||||
`No SMS template found for type ${type}`
|
||||
)
|
||||
);
|
||||
|
||||
const messageContent = replaceSendMessageHandlebars(template.content, payload);
|
||||
|
||||
const formattedPhone = formatPhoneNumber(to);
|
||||
|
||||
if (!enableInternational && formattedPhone.startsWith('+')) {
|
||||
if (unsupportedCountriesMsg) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.General, unsupportedCountriesMsg);
|
||||
} else {
|
||||
console.warn(`connector-yunpian-sms: unsupported phone number: ${formattedPhone}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const body: YunpianSmsPayload = {
|
||||
apikey,
|
||||
mobile: formattedPhone,
|
||||
text: messageContent,
|
||||
};
|
||||
|
||||
try {
|
||||
return await got.post(endpoint, {
|
||||
form: body,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
|
||||
Accept: 'application/json;charset=utf-8',
|
||||
},
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error instanceof RequestError &&
|
||||
error.response?.statusCode === 400 &&
|
||||
typeof error.response.body === 'string'
|
||||
) {
|
||||
const errorBody = yunpianErrorResponseGuard.parse(JSON.parse(error.response.body));
|
||||
console.warn('connector-yunpian-sms: send error', errorBody);
|
||||
|
||||
if (errorBody.msg) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.General, errorBody.msg);
|
||||
}
|
||||
}
|
||||
|
||||
console.warn('connector-yunpian-sms: send unknown error', error);
|
||||
throw new ConnectorError(ConnectorErrorCodes.General, `Unknown error: ${String(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
const createYunpianSmsConnector: CreateConnector<SmsConnector> = async ({ getConfig }) => {
|
||||
return {
|
||||
metadata: defaultMetadata,
|
||||
type: ConnectorType.Sms,
|
||||
configGuard: yunpianSmsConfigGuard,
|
||||
sendMessage: sendMessage(getConfig),
|
||||
};
|
||||
};
|
||||
|
||||
export default createYunpianSmsConnector;
|
23
packages/connectors/connector-yunpian-sms/src/mock.ts
Normal file
23
packages/connectors/connector-yunpian-sms/src/mock.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import type { YunpianSmsConfig } from './types.js';
|
||||
|
||||
export const mockedConfig: YunpianSmsConfig = {
|
||||
apikey: 'a123b456c789d0',
|
||||
templates: [
|
||||
{
|
||||
usageType: 'Generic',
|
||||
content: '您的验证码是{{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'SignIn',
|
||||
content: '您的验证码是{{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'Register',
|
||||
content: '您的验证码是{{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
{
|
||||
usageType: 'ForgotPassword',
|
||||
content: '您的验证码是{{code}}。如非本人操作,请忽略本短信',
|
||||
},
|
||||
],
|
||||
};
|
66
packages/connectors/connector-yunpian-sms/src/types.ts
Normal file
66
packages/connectors/connector-yunpian-sms/src/types.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
const templateGuard = z.object({
|
||||
usageType: z.string(),
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export const yunpianSmsConfigGuard = z.object({
|
||||
apikey: z.string(),
|
||||
templates: z
|
||||
.array(templateGuard)
|
||||
.refine(
|
||||
(templates) =>
|
||||
['Register', 'SignIn', 'ForgotPassword', 'Generic'].every((type) =>
|
||||
templates.map((template) => template.usageType).includes(type)
|
||||
),
|
||||
{
|
||||
message:
|
||||
'Must provide all required template types (Register/SignIn/ForgotPassword/Generic)',
|
||||
}
|
||||
),
|
||||
enableInternational: z.boolean().optional(),
|
||||
unsupportedCountriesMsg: z.string().optional(),
|
||||
});
|
||||
|
||||
export type YunpianSmsConfig = z.infer<typeof yunpianSmsConfigGuard>;
|
||||
|
||||
export type YunpianSmsPayload = {
|
||||
apikey: string;
|
||||
mobile: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type YunpianSmsResponse = {
|
||||
code: number;
|
||||
msg: string;
|
||||
count: number;
|
||||
fee: number;
|
||||
unit: string;
|
||||
mobile: string;
|
||||
sid: number;
|
||||
};
|
||||
|
||||
export const yunpianSmsResponseGuard = z.object({
|
||||
code: z.number(),
|
||||
msg: z.string(),
|
||||
count: z.number(),
|
||||
fee: z.number(),
|
||||
unit: z.string(),
|
||||
mobile: z.string(),
|
||||
sid: z.number(),
|
||||
});
|
||||
|
||||
export type YunpianErrorResponse = {
|
||||
http_status_code: number;
|
||||
code: number;
|
||||
msg: string;
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
export const yunpianErrorResponseGuard = z.object({
|
||||
http_status_code: z.number(),
|
||||
code: z.number(),
|
||||
msg: z.string(),
|
||||
detail: z.string().optional(),
|
||||
});
|
55
pnpm-lock.yaml
generated
55
pnpm-lock.yaml
generated
|
@ -2794,6 +2794,61 @@ importers:
|
|||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8)
|
||||
|
||||
packages/connectors/connector-yunpian-sms:
|
||||
dependencies:
|
||||
'@logto/connector-kit':
|
||||
specifier: workspace:^4.0.0
|
||||
version: link:../../toolkit/connector-kit
|
||||
'@silverhand/essentials':
|
||||
specifier: ^2.9.1
|
||||
version: 2.9.2
|
||||
got:
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.23.8
|
||||
devDependencies:
|
||||
'@silverhand/eslint-config':
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3)
|
||||
'@silverhand/ts-config':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(typescript@5.5.3)
|
||||
'@types/node':
|
||||
specifier: ^20.11.20
|
||||
version: 20.12.7
|
||||
'@types/supertest':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(vitest@2.1.8(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8))
|
||||
eslint:
|
||||
specifier: ^8.56.0
|
||||
version: 8.57.0
|
||||
lint-staged:
|
||||
specifier: ^15.0.2
|
||||
version: 15.0.2
|
||||
nock:
|
||||
specifier: ^13.3.1
|
||||
version: 13.3.1
|
||||
prettier:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
supertest:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
tsup:
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0(@swc/core@1.3.52(@swc/helpers@0.5.1))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.5.3)(yaml@2.4.5)
|
||||
typescript:
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3
|
||||
vitest:
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8)
|
||||
|
||||
packages/console:
|
||||
devDependencies:
|
||||
'@fontsource/roboto-mono':
|
||||
|
|
Loading…
Add table
Reference in a new issue