@h-ai/reach 1.0.0-beta.13 → 1.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +25 -8
- package/README.ja.md +25 -8
- package/README.md +18 -1
- package/dist/index.d.ts +53 -2
- package/dist/index.js +149 -19
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.en.md
CHANGED
|
@@ -90,14 +90,31 @@ const config = { name: 'dev', type: 'console' }
|
|
|
90
90
|
|
|
91
91
|
#### Alibaba Cloud SMS
|
|
92
92
|
|
|
93
|
-
| Field | Type | Required | Default | Description
|
|
94
|
-
| ----------------- | -------- | -------- | ----------------------- |
|
|
95
|
-
| `name` | `string` | ✅ | — | Provider name
|
|
96
|
-
| `type` | `string` | ✅ | — | Fixed `'aliyun-sms'`
|
|
97
|
-
| `accessKeyId` | `string` | ✅ | — | AccessKey ID
|
|
98
|
-
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret
|
|
99
|
-
| `signName` | `string` | ✅ | — | SMS signature
|
|
100
|
-
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` |
|
|
93
|
+
| Field | Type | Required | Default | Description |
|
|
94
|
+
| ----------------- | -------- | -------- | ----------------------- | -------------------------- |
|
|
95
|
+
| `name` | `string` | ✅ | — | Provider name |
|
|
96
|
+
| `type` | `string` | ✅ | — | Fixed `'aliyun-sms'` |
|
|
97
|
+
| `accessKeyId` | `string` | ✅ | — | AccessKey ID |
|
|
98
|
+
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret |
|
|
99
|
+
| `signName` | `string` | ✅ | — | SMS signature |
|
|
100
|
+
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | Direct HTTPS GET endpoint |
|
|
101
|
+
| `gateway` | `object` | — | — | Optional isolation gateway |
|
|
102
|
+
|
|
103
|
+
Add `gateway` to an `aliyun-sms` provider to send a POST request to the full `url`. Alibaba Cloud signature input uses `POST`, the RPC path `/`, and the query parameters. The gateway path, headers, and body apply to the gateway hop.
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
gateway:
|
|
107
|
+
url: https://sms-gateway.example.com/dx
|
|
108
|
+
headers:
|
|
109
|
+
X-Gateway-Client: delivery
|
|
110
|
+
body: {}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`headers` accepts string values. `body` accepts a JSON object, an array, or raw text. Omit `body` for a request without content. Objects and arrays default to `application/json`, and text defaults to `text/plain; charset=utf-8`. Set `Content-Type` to override the default. HTTP on a trusted private network requires `allowInsecureHttp: true`. The signed query, phone numbers, and template variables travel in plaintext over HTTP. Redact query strings in gateway and proxy access logs.
|
|
114
|
+
|
|
115
|
+
Bind a synchronous or asynchronous server-side function with `reach.init(config, { gatewayHooks: { sms: buildGatewayFields } })`, using the configured provider name as the key. The hook receives `{ method, url, body, signal }`, with the static body already serialized, and can return `{ headers, body }`. Its headers override static values by case-insensitive name, and its body replaces the entire static body. Omitted fields inherit the static values. The HTTP client manages transport headers such as `Host` and `Content-Length`. Treat the signed URL as sensitive. The gateway snapshot in `reach.config` includes the URL and HTTP opt-in flag.
|
|
116
|
+
|
|
117
|
+
The hook and request share a 10-second deadline. Redirects are rejected. HTTP 2xx with Alibaba Cloud `Code: OK` confirms acceptance, and `BizId` can be used to check delivery. Uncertain outcomes follow the existing delivery policy, without falling back to direct delivery.
|
|
101
118
|
|
|
102
119
|
#### API callbacks
|
|
103
120
|
|
package/README.ja.md
CHANGED
|
@@ -90,14 +90,31 @@ const config = { name: 'dev', type: 'console' }
|
|
|
90
90
|
|
|
91
91
|
#### Alibaba Cloud SMS
|
|
92
92
|
|
|
93
|
-
| フィールド | 型 | 必須 | 既定値 | 説明
|
|
94
|
-
| ----------------- | -------- | ---- | ----------------------- |
|
|
95
|
-
| `name` | `string` | ✅ | — | Provider 名
|
|
96
|
-
| `type` | `string` | ✅ | — | 固定値 `'aliyun-sms'`
|
|
97
|
-
| `accessKeyId` | `string` | ✅ | — | AccessKey ID
|
|
98
|
-
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret
|
|
99
|
-
| `signName` | `string` | ✅ | — | SMS 署名
|
|
100
|
-
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` |
|
|
93
|
+
| フィールド | 型 | 必須 | 既定値 | 説明 |
|
|
94
|
+
| ----------------- | -------- | ---- | ----------------------- | ---------------------------- |
|
|
95
|
+
| `name` | `string` | ✅ | — | Provider 名 |
|
|
96
|
+
| `type` | `string` | ✅ | — | 固定値 `'aliyun-sms'` |
|
|
97
|
+
| `accessKeyId` | `string` | ✅ | — | AccessKey ID |
|
|
98
|
+
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret |
|
|
99
|
+
| `signName` | `string` | ✅ | — | SMS 署名 |
|
|
100
|
+
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | HTTPS GET の直接接続先 |
|
|
101
|
+
| `gateway` | `object` | — | — | オプションの分離ゲートウェイ |
|
|
102
|
+
|
|
103
|
+
`aliyun-sms` Provider に `gateway` を追加すると、完全な `url` へ POST します。Alibaba Cloud の署名には `POST`、RPC パス `/`、クエリパラメーターを使用します。ゲートウェイのパス、ヘッダー、ボディはゲートウェイへのリクエストに適用されます。
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
gateway:
|
|
107
|
+
url: https://sms-gateway.example.com/dx
|
|
108
|
+
headers:
|
|
109
|
+
X-Gateway-Client: delivery
|
|
110
|
+
body: {}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`headers` の値は文字列です。`body` には JSON オブジェクト、配列、またはテキストを指定でき、省略時はボディなしで送信します。既定の `Content-Type` はオブジェクトと配列が `application/json`、テキストが `text/plain; charset=utf-8` です。信頼できる内部ネットワークで HTTP を使う場合は `allowInsecureHttp: true` が必要です。HTTP では署名済みクエリ、電話番号、テンプレート変数が平文で送られるため、ゲートウェイとプロキシのアクセスログでクエリをマスクしてください。
|
|
114
|
+
|
|
115
|
+
同期または非同期の関数は、サーバー側で `reach.init(config, { gatewayHooks: { sms: buildGatewayFields } })` のように Provider 名へ紐付けます。関数は `{ method, url, body, signal }` を受け取り、`{ headers, body }` を返せます。入力の `body` はシリアライズ済みです。ヘッダー名は大文字・小文字を区別せず上書きし、ボディは全体を置き換えます。省略した項目には静的設定を使います。`Host` や `Content-Length` は HTTP クライアントが管理します。署名済み URL は機密情報として扱います。`reach.config` のゲートウェイ情報には URL と HTTP の許可フラグが含まれます。
|
|
116
|
+
|
|
117
|
+
関数と HTTP リクエストの合計期限は 10 秒で、リダイレクトは拒否します。HTTP 2xx かつ Alibaba Cloud の `Code: OK` で受理を確認し、`BizId` で配信結果を照会できます。結果が不明な場合は既存の配信ポリシーに従い、直接送信への切り替えは行いません。
|
|
101
118
|
|
|
102
119
|
#### API 通知
|
|
103
120
|
|
package/README.md
CHANGED
|
@@ -97,7 +97,24 @@ const config = { name: 'dev', type: 'console' }
|
|
|
97
97
|
| `accessKeyId` | `string` | ✅ | — | AccessKey ID |
|
|
98
98
|
| `accessKeySecret` | `string` | ✅ | — | AccessKey Secret |
|
|
99
99
|
| `signName` | `string` | ✅ | — | 短信签名 |
|
|
100
|
-
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` |
|
|
100
|
+
| `endpoint` | `string` | — | `dysmsapi.aliyuncs.com` | HTTPS GET 直连端点 |
|
|
101
|
+
| `gateway` | `object` | — | — | 可选的隔离网关 |
|
|
102
|
+
|
|
103
|
+
在 `aliyun-sms` Provider 下添加 `gateway` 后,Reach 向完整 `url` 发送 POST。阿里云签名使用 `POST`、RPC 路径 `/` 和查询参数。网关路径、请求头和请求体仅用于网关这一跳。
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
gateway:
|
|
107
|
+
url: https://sms-gateway.example.com/dx
|
|
108
|
+
headers:
|
|
109
|
+
X-Gateway-Client: delivery
|
|
110
|
+
body: {}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`headers` 接受字符串键值,`body` 可为 JSON 对象、数组或原始文本。省略 `body` 时发送无正文请求。对象和数组默认为 `application/json`,文本默认为 `text/plain; charset=utf-8`,可以用 `Content-Type` 覆盖。受信内网使用 HTTP 时须显式设 `allowInsecureHttp: true`。HTTP 下签名查询串、手机号及模板变量是明文,网关和代理的访问日志应对查询串脱敏。
|
|
114
|
+
|
|
115
|
+
在服务端通过 `reach.init(config, { gatewayHooks: { sms: buildGatewayFields } })` 绑定同步或异步函数,其中 `sms` 是启用网关的 Provider 名称。函数接收 `{ method, url, body, signal }`,可返回 `{ headers, body }`。`body` 入参是已序列化的静态正文。函数返回的头按名称忽略大小写覆盖静态头,返回的正文整段替换。省略字段时使用静态配置。`Host`、`Content-Length` 等传输头由客户端管理。函数接收的已签名 URL 仅供服务端构造请求,`reach.config` 的网关快照仅展示地址和 HTTP 开关。
|
|
116
|
+
|
|
117
|
+
函数和 HTTP 请求共用 10 秒期限,禁止重定向。HTTP 2xx 且阿里云返回 `Code: OK` 表示受理,`BizId` 可用于查询送达结果。发送结果不明时按原投递策略处理,网关分支不会自动回退直连。
|
|
101
118
|
|
|
102
119
|
#### API 回调
|
|
103
120
|
|
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,12 @@ declare const AliyunSmsProviderConfigSchema: z.ZodObject<{
|
|
|
59
59
|
accessKeySecret: z.ZodString;
|
|
60
60
|
signName: z.ZodString;
|
|
61
61
|
endpoint: z.ZodDefault<z.ZodString>;
|
|
62
|
+
gateway: z.ZodOptional<z.ZodObject<{
|
|
63
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
64
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodJSONSchema>, z.ZodRecord<z.ZodString, z.ZodJSONSchema>]>>;
|
|
65
|
+
url: z.ZodURL;
|
|
66
|
+
allowInsecureHttp: z.ZodDefault<z.ZodBoolean>;
|
|
67
|
+
}, z.core.$strict>>;
|
|
62
68
|
}, z.core.$strip>;
|
|
63
69
|
/**
|
|
64
70
|
* HTTP回调配置;idempotent默认false,只有接收端按稳定Idempotency-Key原子持久去重并覆盖旧请求迟到窗口才允许true。
|
|
@@ -115,6 +121,12 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
115
121
|
accessKeySecret: z.ZodString;
|
|
116
122
|
signName: z.ZodString;
|
|
117
123
|
endpoint: z.ZodDefault<z.ZodString>;
|
|
124
|
+
gateway: z.ZodOptional<z.ZodObject<{
|
|
125
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
126
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodJSONSchema>, z.ZodRecord<z.ZodString, z.ZodJSONSchema>]>>;
|
|
127
|
+
url: z.ZodURL;
|
|
128
|
+
allowInsecureHttp: z.ZodDefault<z.ZodBoolean>;
|
|
129
|
+
}, z.core.$strict>>;
|
|
118
130
|
}, z.core.$strip>, z.ZodObject<{
|
|
119
131
|
name: z.ZodString;
|
|
120
132
|
type: z.ZodLiteral<"api">;
|
|
@@ -344,6 +356,12 @@ declare const ReachConfigSchema: z.ZodObject<{
|
|
|
344
356
|
accessKeySecret: z.ZodString;
|
|
345
357
|
signName: z.ZodString;
|
|
346
358
|
endpoint: z.ZodDefault<z.ZodString>;
|
|
359
|
+
gateway: z.ZodOptional<z.ZodObject<{
|
|
360
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
361
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodJSONSchema>, z.ZodRecord<z.ZodString, z.ZodJSONSchema>]>>;
|
|
362
|
+
url: z.ZodURL;
|
|
363
|
+
allowInsecureHttp: z.ZodDefault<z.ZodBoolean>;
|
|
364
|
+
}, z.core.$strict>>;
|
|
347
365
|
}, z.core.$strip>, z.ZodObject<{
|
|
348
366
|
name: z.ZodString;
|
|
349
367
|
type: z.ZodLiteral<"api">;
|
|
@@ -440,6 +458,38 @@ declare const HaiReachError: {
|
|
|
440
458
|
* ```
|
|
441
459
|
*/
|
|
442
460
|
type ReachChannel = 'email' | 'sms' | 'api';
|
|
461
|
+
/**
|
|
462
|
+
* 发送前为网关提供请求头或替换请求体。URL 已包含签名和可能敏感的查询数据,函数必须在可信服务端运行。
|
|
463
|
+
* 返回 undefined 保留静态配置;字符串 body 按原样发送。signal 覆盖函数执行与 HTTP 请求的共同期限。
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* import type { ReachGatewayHook } from '@h-ai/reach'
|
|
468
|
+
* const hook: ReachGatewayHook = ({ body, signal }) => {
|
|
469
|
+
* signal.throwIfAborted()
|
|
470
|
+
* return { headers: { 'X-Client': 'delivery' }, body: body ?? '{}' }
|
|
471
|
+
* }
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
type ReachGatewayHook = (request: Readonly<{
|
|
475
|
+
method: 'POST';
|
|
476
|
+
url: string;
|
|
477
|
+
body: string | undefined;
|
|
478
|
+
signal: AbortSignal;
|
|
479
|
+
}>) => Pick<NonNullable<AliyunSmsProviderConfig['gateway']>, 'headers' | 'body'> | void | Promise<Pick<NonNullable<AliyunSmsProviderConfig['gateway']>, 'headers' | 'body'> | void>;
|
|
480
|
+
/**
|
|
481
|
+
* 服务端运行时注入选项,与可序列化的配置分离。
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```ts
|
|
485
|
+
* import type { ReachInitOptions } from '@h-ai/reach'
|
|
486
|
+
* const options: ReachInitOptions = { gatewayHooks: { sms: () => ({ headers: { 'X-Client': 'delivery' } }) } }
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
interface ReachInitOptions {
|
|
490
|
+
/** 以已配置网关的 Provider 名称绑定自定义函数。 */
|
|
491
|
+
gatewayHooks?: Readonly<Record<string, ReachGatewayHook>>;
|
|
492
|
+
}
|
|
443
493
|
/**
|
|
444
494
|
* 直接发送必须指定Provider名和内容;模板发送可由持久模板推导路由,vars做文本替换。
|
|
445
495
|
* 队列幂等键固定为持久queueId;直接调用的业务重试由应用提供稳定idempotencyKey并核验接收端契约。
|
|
@@ -1215,6 +1265,7 @@ interface ReachFunctions extends SendOperations {
|
|
|
1215
1265
|
/**
|
|
1216
1266
|
* 初始化触达模块(注册多个 Provider)
|
|
1217
1267
|
* @param config - 实际操作输入,按本方法约束校验。
|
|
1268
|
+
* @param options - 可选服务端运行时网关函数,按 Provider 名称绑定。
|
|
1218
1269
|
* @returns HaiResult;先检查success,条件提交失败不能伪装成功。
|
|
1219
1270
|
*
|
|
1220
1271
|
* @example
|
|
@@ -1244,7 +1295,7 @@ interface ReachFunctions extends SendOperations {
|
|
|
1244
1295
|
* }
|
|
1245
1296
|
* ```
|
|
1246
1297
|
*/
|
|
1247
|
-
init: (config: ReachConfigInput) => Promise<HaiResult<void>>;
|
|
1298
|
+
init: (config: ReachConfigInput, options?: ReachInitOptions) => Promise<HaiResult<void>>;
|
|
1248
1299
|
/** 当前脱敏后的配置快照(未初始化时为 null) */
|
|
1249
1300
|
readonly config: ReachConfig | null;
|
|
1250
1301
|
/** 是否已初始化 */
|
|
@@ -1338,4 +1389,4 @@ declare function migrateReach(options?: {
|
|
|
1338
1389
|
dryRun?: boolean;
|
|
1339
1390
|
}, db?: ReldbFunctions): Promise<_h_ai_core.HaiResult<_h_ai_reldb.SchemaMigrationPlan>>;
|
|
1340
1391
|
|
|
1341
|
-
export { type AliyunSmsProviderConfig, AliyunSmsProviderConfigSchema, type ApiProviderConfig, ApiProviderConfigSchema, type ConsoleProviderConfig, ConsoleProviderConfigSchema, type DndConfig, DndConfigSchema, HaiReachError, type ProviderConfig, ProviderConfigSchema, type ReachChannel, type ReachConfig, type ReachConfigInput, ReachConfigSchema, type ReachDeadRequeue, ReachDeadRequeueSchema, type ReachFunctions, type ReachMessage, type ReachQueueConfig, ReachQueueConfigSchema, type ReachQueueEntry, type ReachQueueFailure, ReachQueueIdSchema, type ReachQueueListOptions, ReachQueueListSchema, type ReachQueueOperations, type ReachQueueStatus, type ReachTemplate, type ReachTemplateRegistry, type ReachUnknownResolution, ReachUnknownResolutionSchema, type RenderedTemplate, type SendOperations, type SendResult, type SmtpProviderConfig, SmtpProviderConfigSchema, type TemplateConfig, TemplateConfigSchema, migrateReach, reach };
|
|
1392
|
+
export { type AliyunSmsProviderConfig, AliyunSmsProviderConfigSchema, type ApiProviderConfig, ApiProviderConfigSchema, type ConsoleProviderConfig, ConsoleProviderConfigSchema, type DndConfig, DndConfigSchema, HaiReachError, type ProviderConfig, ProviderConfigSchema, type ReachChannel, type ReachConfig, type ReachConfigInput, ReachConfigSchema, type ReachDeadRequeue, ReachDeadRequeueSchema, type ReachFunctions, type ReachGatewayHook, type ReachInitOptions, type ReachMessage, type ReachQueueConfig, ReachQueueConfigSchema, type ReachQueueEntry, type ReachQueueFailure, ReachQueueIdSchema, type ReachQueueListOptions, ReachQueueListSchema, type ReachQueueOperations, type ReachQueueStatus, type ReachTemplate, type ReachTemplateRegistry, type ReachUnknownResolution, ReachUnknownResolutionSchema, type RenderedTemplate, type SendOperations, type SendResult, type SmtpProviderConfig, SmtpProviderConfigSchema, type TemplateConfig, TemplateConfigSchema, migrateReach, reach };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { core, err, ok } from '@h-ai/core';
|
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import { reldb, migrateSchema, schemaColumns, BaseReldbCrudRepository } from '@h-ai/reldb';
|
|
5
5
|
import { randomUUID, createHash, createHmac } from 'crypto';
|
|
6
|
+
import { addAbortListener } from 'events';
|
|
6
7
|
import { createRequire } from 'module';
|
|
7
8
|
import { cache } from '@h-ai/cache';
|
|
8
9
|
|
|
@@ -30,6 +31,11 @@ var en_US_default = {
|
|
|
30
31
|
reach_queueInputInvalid: "Invalid queue receipt or manual resolution parameters",
|
|
31
32
|
reach_deliveryUnknown: "The external delivery outcome is unknown; manual review is required and automatic redelivery has stopped",
|
|
32
33
|
reach_smsSendFailed: "SMS send failed: {error}",
|
|
34
|
+
reach_smsGatewayFailed: "The SMS gateway request failed or timed out. Delivery is unconfirmed",
|
|
35
|
+
reach_gatewayUrlInvalid: "Use a full HTTPS gateway URL without credentials, a query, or a fragment. For HTTP on a trusted private network, explicitly enable allowInsecureHttp",
|
|
36
|
+
reach_gatewayHeadersInvalid: "Gateway headers are invalid or include a header managed by the HTTP client",
|
|
37
|
+
reach_gatewayBodyInvalid: "The gateway request body must be a JSON object, an array, or text",
|
|
38
|
+
reach_gatewayHookInvalid: "Gateway hooks must be bound to an SMS provider with a gateway configured",
|
|
33
39
|
reach_emailSendFailed: "Email send failed: {error}",
|
|
34
40
|
reach_apiSendFailed: "API callback send failed: {error}",
|
|
35
41
|
reach_configError: "Reach config validation failed: {error}",
|
|
@@ -83,6 +89,11 @@ var ja_JP_default = {
|
|
|
83
89
|
reach_queueInputInvalid: "\u30AD\u30E5\u30FC\u306E\u53D7\u9818\u8A18\u9332\u307E\u305F\u306F\u624B\u52D5\u89E3\u6C7A\u306E\u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u7121\u52B9\u3067\u3059",
|
|
84
90
|
reach_deliveryUnknown: "\u5916\u90E8\u3078\u306E\u914D\u4FE1\u7D50\u679C\u306F\u4E0D\u660E\u3067\u3059\u3002\u624B\u52D5\u78BA\u8A8D\u304C\u5FC5\u8981\u306A\u305F\u3081\u3001\u81EA\u52D5\u518D\u914D\u4FE1\u3092\u505C\u6B62\u3057\u307E\u3057\u305F",
|
|
85
91
|
reach_smsSendFailed: "SMS\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
92
|
+
reach_smsGatewayFailed: "SMS \u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u3078\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u5931\u6557\u307E\u305F\u306F\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u3001\u914D\u4FE1\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F",
|
|
93
|
+
reach_gatewayUrlInvalid: "\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u306B\u306F\u8A8D\u8A3C\u60C5\u5831\u3001\u30AF\u30A8\u30EA\u3001\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u3092\u542B\u307E\u306A\u3044\u5B8C\u5168\u306A HTTPS URL \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4FE1\u983C\u3067\u304D\u308B\u5185\u90E8\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u3067 HTTP \u3092\u4F7F\u7528\u3059\u308B\u5834\u5408\u306F allowInsecureHttp \u3092\u660E\u793A\u7684\u306B\u6709\u52B9\u306B\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
94
|
+
reach_gatewayHeadersInvalid: "\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u30D8\u30C3\u30C0\u30FC\u304C\u7121\u52B9\u304B\u3001HTTP \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304C\u7BA1\u7406\u3059\u308B\u30D8\u30C3\u30C0\u30FC\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059",
|
|
95
|
+
reach_gatewayBodyInvalid: "\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306B\u306F JSON \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3001\u914D\u5217\u3001\u307E\u305F\u306F\u30C6\u30AD\u30B9\u30C8\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
96
|
+
reach_gatewayHookInvalid: "\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u306E\u30D5\u30C3\u30AF\u306F\u3001\u30B2\u30FC\u30C8\u30A6\u30A7\u30A4\u3092\u8A2D\u5B9A\u3057\u305F SMS Provider \u306B\u5272\u308A\u5F53\u3066\u3066\u304F\u3060\u3055\u3044",
|
|
86
97
|
reach_emailSendFailed: "\u30E1\u30FC\u30EB\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
87
98
|
reach_apiSendFailed: "API\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
88
99
|
reach_configError: "\u901A\u77E5\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
@@ -136,6 +147,11 @@ var zh_CN_default = {
|
|
|
136
147
|
reach_queueInputInvalid: "\u961F\u5217\u56DE\u6267\u6216\u4EBA\u5DE5\u88C1\u5B9A\u53C2\u6570\u65E0\u6548",
|
|
137
148
|
reach_deliveryUnknown: "\u5916\u90E8\u6295\u9012\u7ED3\u679C\u672A\u77E5\uFF0C\u9700\u8981\u4EBA\u5DE5\u6838\u5BF9\uFF0C\u5DF2\u505C\u6B62\u81EA\u52A8\u91CD\u53D1",
|
|
138
149
|
reach_smsSendFailed: "\u77ED\u4FE1\u53D1\u9001\u5931\u8D25: {error}",
|
|
150
|
+
reach_smsGatewayFailed: "\u77ED\u4FE1\u7F51\u5173\u8BF7\u6C42\u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u6295\u9012\u7ED3\u679C\u672A\u786E\u8BA4",
|
|
151
|
+
reach_gatewayUrlInvalid: "\u7F51\u5173\u987B\u4F7F\u7528\u5B8C\u6574\u7684 HTTPS URL\uFF0C\u4E14\u4E0D\u80FD\u5305\u542B\u51ED\u636E\u3001\u67E5\u8BE2\u53C2\u6570\u6216\u7247\u6BB5\u3002\u53D7\u4FE1\u5185\u7F51\u4F7F\u7528 HTTP \u65F6\u987B\u663E\u5F0F\u542F\u7528 allowInsecureHttp",
|
|
152
|
+
reach_gatewayHeadersInvalid: "\u7F51\u5173\u8BF7\u6C42\u5934\u683C\u5F0F\u65E0\u6548\uFF0C\u6216\u5305\u542B\u7531 HTTP \u5BA2\u6237\u7AEF\u7BA1\u7406\u7684\u8BF7\u6C42\u5934",
|
|
153
|
+
reach_gatewayBodyInvalid: "\u7F51\u5173\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3001\u6570\u7EC4\u6216\u6587\u672C",
|
|
154
|
+
reach_gatewayHookInvalid: "\u7F51\u5173\u51FD\u6570\u5FC5\u987B\u7ED1\u5B9A\u5230\u542F\u7528\u4E86\u7F51\u5173\u7684\u77ED\u4FE1 Provider",
|
|
139
155
|
reach_emailSendFailed: "\u90AE\u4EF6\u53D1\u9001\u5931\u8D25: {error}",
|
|
140
156
|
reach_apiSendFailed: "API \u56DE\u8C03\u53D1\u9001\u5931\u8D25: {error}",
|
|
141
157
|
reach_configError: "\u89E6\u8FBE\u914D\u7F6E\u6821\u9A8C\u5931\u8D25\uFF1A{error}",
|
|
@@ -174,6 +190,33 @@ var reachM = core.i18n.createMessageGetter({
|
|
|
174
190
|
"ja-JP": ja_JP_default
|
|
175
191
|
});
|
|
176
192
|
|
|
193
|
+
// src/reach-gateway-config.ts
|
|
194
|
+
var transportHeader = /^(?:connection|content-length|host|keep-alive|proxy-connection|te|trailer|transfer-encoding|upgrade)$/i;
|
|
195
|
+
var jsonValue = z.json();
|
|
196
|
+
var gatewayFields = {
|
|
197
|
+
/** 网关自定义请求头;连接与消息长度相关的头由 HTTP 客户端管理。 */
|
|
198
|
+
headers: z.record(z.string(), z.string()).refine((headers) => {
|
|
199
|
+
try {
|
|
200
|
+
return [...new Headers(headers).keys()].every((name) => !transportHeader.test(name));
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}, reachM("reach_gatewayHeadersInvalid")).optional(),
|
|
205
|
+
/** 网关请求体;对象和数组序列化为 JSON,字符串原样发送。省略时不发请求体。 */
|
|
206
|
+
body: z.union([z.string(), z.array(jsonValue), z.record(z.string(), jsonValue)], reachM("reach_gatewayBodyInvalid")).optional()
|
|
207
|
+
};
|
|
208
|
+
var ReachGatewayFieldsSchema = z.strictObject(gatewayFields);
|
|
209
|
+
var ReachGatewayConfigSchema = z.strictObject({
|
|
210
|
+
/** 网关的完整 HTTP(S) URL;包含路径,不允许凭据、查询参数或片段。 */
|
|
211
|
+
url: z.url(reachM("reach_gatewayUrlInvalid")),
|
|
212
|
+
/** 仅在受信内网显式允许明文 HTTP。 */
|
|
213
|
+
allowInsecureHttp: z.boolean().default(false),
|
|
214
|
+
...gatewayFields
|
|
215
|
+
}).refine(({ url, allowInsecureHttp }) => {
|
|
216
|
+
const target = URL.parse(url);
|
|
217
|
+
return target !== null && /^https?:\/\//i.test(url) && !/[?#]/.test(url) && !target.username && !target.password && (target.protocol === "https:" || target.protocol === "http:" && allowInsecureHttp);
|
|
218
|
+
}, { path: ["url"], message: reachM("reach_gatewayUrlInvalid") });
|
|
219
|
+
|
|
177
220
|
// src/reach-config.ts
|
|
178
221
|
var ConsoleProviderConfigSchema = z.object({
|
|
179
222
|
/** Provider 唯一名称 */
|
|
@@ -207,8 +250,10 @@ var AliyunSmsProviderConfigSchema = z.object({
|
|
|
207
250
|
accessKeySecret: z.string().min(1, reachM("reach_config_accessKeySecretRequired")),
|
|
208
251
|
/** 短信签名 */
|
|
209
252
|
signName: z.string().min(1, reachM("reach_config_signNameRequired")),
|
|
210
|
-
/** API 端点(默认 dysmsapi.aliyuncs.com
|
|
211
|
-
endpoint: z.string().default("dysmsapi.aliyuncs.com")
|
|
253
|
+
/** 直连 API 端点(默认 dysmsapi.aliyuncs.com);配置网关时使用网关 URL。 */
|
|
254
|
+
endpoint: z.string().default("dysmsapi.aliyuncs.com"),
|
|
255
|
+
/** 可选的短信隔离网关,以 POST 请求转发已签名的查询参数。 */
|
|
256
|
+
gateway: ReachGatewayConfigSchema.optional()
|
|
212
257
|
});
|
|
213
258
|
var ApiProviderConfigSchema = z.object({
|
|
214
259
|
/** Provider 唯一名称 */
|
|
@@ -308,6 +353,9 @@ var HaiReachError = core.error.buildHaiErrorsDef("reach", ReachErrorInfo);
|
|
|
308
353
|
// src/providers/reach-provider-aliyun-sms.ts
|
|
309
354
|
var logger = core.logger.child({ module: "reach", scope: "provider-aliyun-sms" });
|
|
310
355
|
var ISO_MILLISECONDS_SUFFIX_REGEX = /\.\d{3}Z$/;
|
|
356
|
+
var RFC3986_RESERVED_CHARACTERS_REGEX = /[!'()*]/g;
|
|
357
|
+
var GATEWAY_TIMEOUT_MS = 1e4;
|
|
358
|
+
var GATEWAY_RESPONSE_LIMIT_BYTES = 64 * 1024;
|
|
311
359
|
function toReachError(error) {
|
|
312
360
|
return {
|
|
313
361
|
code: HaiReachError.SEND_FAILED.code,
|
|
@@ -318,7 +366,7 @@ function toReachError(error) {
|
|
|
318
366
|
};
|
|
319
367
|
}
|
|
320
368
|
function percentEncode(str) {
|
|
321
|
-
return encodeURIComponent(str).replace(
|
|
369
|
+
return encodeURIComponent(str).replace(RFC3986_RESERVED_CHARACTERS_REGEX, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
322
370
|
}
|
|
323
371
|
function signRequest(params, accessKeySecret, method) {
|
|
324
372
|
const sortedKeys = Object.keys(params).sort();
|
|
@@ -328,7 +376,58 @@ function signRequest(params, accessKeySecret, method) {
|
|
|
328
376
|
hmac.update(stringToSign);
|
|
329
377
|
return hmac.digest("base64");
|
|
330
378
|
}
|
|
331
|
-
function
|
|
379
|
+
async function prepareGatewayRequest(gateway, url, signal, hook) {
|
|
380
|
+
let body = gateway.body;
|
|
381
|
+
const headers = new Headers();
|
|
382
|
+
for (const [name, value] of Object.entries(gateway.headers ?? {}))
|
|
383
|
+
headers.set(name, value);
|
|
384
|
+
if (hook) {
|
|
385
|
+
signal.throwIfAborted();
|
|
386
|
+
let listener;
|
|
387
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
388
|
+
listener = addAbortListener(signal, () => reject(signal.reason));
|
|
389
|
+
});
|
|
390
|
+
try {
|
|
391
|
+
const output = await Promise.race([
|
|
392
|
+
Promise.resolve().then(() => hook({ method: "POST", url, body: typeof body === "string" ? body : JSON.stringify(body), signal })),
|
|
393
|
+
aborted
|
|
394
|
+
]);
|
|
395
|
+
const patch = ReachGatewayFieldsSchema.parse(output === void 0 ? {} : output);
|
|
396
|
+
body = patch.body ?? body;
|
|
397
|
+
for (const [name, value] of Object.entries(patch.headers ?? {}))
|
|
398
|
+
headers.set(name, value);
|
|
399
|
+
} finally {
|
|
400
|
+
listener?.[Symbol.dispose]();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (body !== void 0 && !headers.has("Content-Type"))
|
|
404
|
+
headers.set("Content-Type", typeof body === "string" ? "text/plain; charset=utf-8" : "application/json");
|
|
405
|
+
return { method: "POST", headers, body: typeof body === "string" ? body : JSON.stringify(body), redirect: "error", signal };
|
|
406
|
+
}
|
|
407
|
+
async function readGatewayResponse(response) {
|
|
408
|
+
if (response.ok && response.body) {
|
|
409
|
+
let bytes = 0;
|
|
410
|
+
const stream = response.body.pipeThrough(new TransformStream({
|
|
411
|
+
transform(chunk, controller) {
|
|
412
|
+
bytes += chunk.byteLength;
|
|
413
|
+
if (bytes > GATEWAY_RESPONSE_LIMIT_BYTES)
|
|
414
|
+
throw new Error("SMS gateway response exceeded the size limit");
|
|
415
|
+
controller.enqueue(chunk);
|
|
416
|
+
}
|
|
417
|
+
}));
|
|
418
|
+
const body = await new Response(stream).json();
|
|
419
|
+
if (body && typeof body === "object" && "Code" in body && body.Code === "OK") {
|
|
420
|
+
const messageId = "BizId" in body ? body.BizId : void 0;
|
|
421
|
+
if (messageId === void 0 || typeof messageId === "string" && messageId.length <= 255)
|
|
422
|
+
return ok({ success: true, messageId });
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
await response.body?.cancel();
|
|
426
|
+
}
|
|
427
|
+
logger.warn("SMS gateway returned an unsuccessful receipt", { status: response.status });
|
|
428
|
+
return err(HaiReachError.SEND_FAILED, reachM("reach_smsGatewayFailed"));
|
|
429
|
+
}
|
|
430
|
+
function createAliyunSmsProvider(gatewayHook) {
|
|
332
431
|
let smsConfig = null;
|
|
333
432
|
return {
|
|
334
433
|
name: "aliyun-sms",
|
|
@@ -341,7 +440,7 @@ function createAliyunSmsProvider() {
|
|
|
341
440
|
);
|
|
342
441
|
}
|
|
343
442
|
smsConfig = config;
|
|
344
|
-
logger.info("Aliyun SMS provider connected", { endpoint: config.endpoint });
|
|
443
|
+
logger.info("Aliyun SMS provider connected", config.gateway ? { gateway: true } : { endpoint: config.endpoint });
|
|
345
444
|
return ok(void 0);
|
|
346
445
|
},
|
|
347
446
|
async close() {
|
|
@@ -352,21 +451,23 @@ function createAliyunSmsProvider() {
|
|
|
352
451
|
return smsConfig !== null;
|
|
353
452
|
},
|
|
354
453
|
async send(message) {
|
|
355
|
-
|
|
454
|
+
const config = smsConfig;
|
|
455
|
+
if (!config) {
|
|
356
456
|
return err(
|
|
357
457
|
HaiReachError.NOT_INITIALIZED,
|
|
358
458
|
reachM("reach_notInitialized")
|
|
359
459
|
);
|
|
360
460
|
}
|
|
361
461
|
const templateCode = message.extra?.templateCode;
|
|
362
|
-
|
|
462
|
+
if (!config.gateway)
|
|
463
|
+
logger.debug("Sending SMS", { to: message.to, templateCode });
|
|
363
464
|
try {
|
|
364
465
|
const params = {
|
|
365
|
-
AccessKeyId:
|
|
466
|
+
AccessKeyId: config.accessKeyId,
|
|
366
467
|
Action: "SendSms",
|
|
367
468
|
Format: "JSON",
|
|
368
469
|
PhoneNumbers: message.to,
|
|
369
|
-
SignName:
|
|
470
|
+
SignName: config.signName,
|
|
370
471
|
SignatureMethod: "HMAC-SHA1",
|
|
371
472
|
SignatureNonce: randomUUID(),
|
|
372
473
|
SignatureVersion: "1.0",
|
|
@@ -377,13 +478,25 @@ function createAliyunSmsProvider() {
|
|
|
377
478
|
if (message.vars) {
|
|
378
479
|
params.TemplateParam = JSON.stringify(message.vars);
|
|
379
480
|
}
|
|
380
|
-
params.Signature = signRequest(params,
|
|
481
|
+
params.Signature = signRequest(params, config.accessKeySecret, config.gateway ? "POST" : "GET");
|
|
381
482
|
const queryString = Object.entries(params).map(([k, v]) => `${percentEncode(k)}=${percentEncode(v)}`).join("&");
|
|
382
|
-
|
|
483
|
+
if (config.gateway) {
|
|
484
|
+
const deadline = performance.now() + GATEWAY_TIMEOUT_MS;
|
|
485
|
+
const signal = AbortSignal.timeout(GATEWAY_TIMEOUT_MS);
|
|
486
|
+
const url2 = `${new URL(config.gateway.url).href}?${queryString}`;
|
|
487
|
+
const request = await prepareGatewayRequest(config.gateway, url2, signal, gatewayHook);
|
|
488
|
+
signal.throwIfAborted();
|
|
489
|
+
if (smsConfig !== config || performance.now() >= deadline)
|
|
490
|
+
return err(HaiReachError.SEND_FAILED, reachM("reach_smsGatewayFailed"));
|
|
491
|
+
if (message.sensitive && message.expiresAt !== void 0 && Date.now() >= message.expiresAt)
|
|
492
|
+
return err(HaiReachError.MESSAGE_EXPIRED, reachM("reach_messageExpired"));
|
|
493
|
+
return await readGatewayResponse(await fetch(url2, request));
|
|
494
|
+
}
|
|
495
|
+
const url = `https://${config.endpoint}/?${queryString}`;
|
|
383
496
|
const response = await fetch(url);
|
|
384
497
|
const body = await response.json();
|
|
385
498
|
if (body.Code !== "OK") {
|
|
386
|
-
logger.warn("SMS send returned non-OK status", { code: body.Code
|
|
499
|
+
logger.warn("SMS send returned non-OK status", { code: body.Code });
|
|
387
500
|
return err(
|
|
388
501
|
HaiReachError.SEND_FAILED,
|
|
389
502
|
reachM("reach_smsSendFailed", { params: { error: body.Message } })
|
|
@@ -392,6 +505,10 @@ function createAliyunSmsProvider() {
|
|
|
392
505
|
logger.info("SMS sent", { to: message.to, bizId: body.BizId });
|
|
393
506
|
return ok({ success: true, messageId: body.BizId });
|
|
394
507
|
} catch (error) {
|
|
508
|
+
if (config.gateway) {
|
|
509
|
+
logger.warn("SMS gateway request failed");
|
|
510
|
+
return err(HaiReachError.SEND_FAILED, reachM("reach_smsGatewayFailed"));
|
|
511
|
+
}
|
|
395
512
|
logger.error("SMS send failed", { to: message.to, error });
|
|
396
513
|
return err(toReachError(error));
|
|
397
514
|
}
|
|
@@ -1970,14 +2087,14 @@ var templateRepo = null;
|
|
|
1970
2087
|
var initInProgress = false;
|
|
1971
2088
|
var initCompletion = null;
|
|
1972
2089
|
var closeInProgress = null;
|
|
1973
|
-
function createProvider(config) {
|
|
2090
|
+
function createProvider(config, gatewayHook) {
|
|
1974
2091
|
switch (config.type) {
|
|
1975
2092
|
case "console":
|
|
1976
2093
|
return createConsoleProvider();
|
|
1977
2094
|
case "smtp":
|
|
1978
2095
|
return createSmtpProvider();
|
|
1979
2096
|
case "aliyun-sms":
|
|
1980
|
-
return createAliyunSmsProvider();
|
|
2097
|
+
return createAliyunSmsProvider(gatewayHook);
|
|
1981
2098
|
case "api":
|
|
1982
2099
|
return createApiProvider();
|
|
1983
2100
|
}
|
|
@@ -2047,7 +2164,7 @@ async function doClose() {
|
|
|
2047
2164
|
resetSendState();
|
|
2048
2165
|
logger7.info("Reach module closed");
|
|
2049
2166
|
}
|
|
2050
|
-
async function doInit(config) {
|
|
2167
|
+
async function doInit(config, options) {
|
|
2051
2168
|
if (providers.size > 0) {
|
|
2052
2169
|
logger7.warn("Reach module is already initialized, reinitializing");
|
|
2053
2170
|
await doClose();
|
|
@@ -2063,6 +2180,11 @@ async function doInit(config) {
|
|
|
2063
2180
|
);
|
|
2064
2181
|
}
|
|
2065
2182
|
const parsed = parseResult.data;
|
|
2183
|
+
const gatewayHooks = new Map(Object.entries(options?.gatewayHooks ?? {}));
|
|
2184
|
+
for (const [name, hook] of gatewayHooks) {
|
|
2185
|
+
if (typeof hook !== "function" || !parsed.providers.some((provider) => provider.name === name && provider.type === "aliyun-sms" && provider.gateway))
|
|
2186
|
+
return err(HaiReachError.CONFIG_ERROR, reachM("reach_gatewayHookInvalid"));
|
|
2187
|
+
}
|
|
2066
2188
|
if (core.module.requiresSharedState(process.env) && parsed.providers.some((provider) => provider.type === "console"))
|
|
2067
2189
|
return err(HaiReachError.CONFIG_ERROR, reachM("reach_productionProviderRequired"));
|
|
2068
2190
|
try {
|
|
@@ -2077,7 +2199,7 @@ async function doInit(config) {
|
|
|
2077
2199
|
}
|
|
2078
2200
|
const newProviders = /* @__PURE__ */ new Map();
|
|
2079
2201
|
for (const providerConfig of parsed.providers) {
|
|
2080
|
-
const provider = createProvider(providerConfig);
|
|
2202
|
+
const provider = createProvider(providerConfig, gatewayHooks.get(providerConfig.name));
|
|
2081
2203
|
const connectResult = await provider.connect(providerConfig);
|
|
2082
2204
|
if (!connectResult.success) {
|
|
2083
2205
|
logger7.error("Provider initialization failed", {
|
|
@@ -2193,7 +2315,7 @@ var reach = {
|
|
|
2193
2315
|
return sendLogRepo.resolveUnknown(queueId, parsed.data);
|
|
2194
2316
|
}
|
|
2195
2317
|
},
|
|
2196
|
-
async init(config) {
|
|
2318
|
+
async init(config, options) {
|
|
2197
2319
|
if (initInProgress || closeInProgress) {
|
|
2198
2320
|
logger7.warn("Reach module init already in progress, skipping");
|
|
2199
2321
|
return err(
|
|
@@ -2203,7 +2325,7 @@ var reach = {
|
|
|
2203
2325
|
}
|
|
2204
2326
|
initInProgress = true;
|
|
2205
2327
|
try {
|
|
2206
|
-
initCompletion = doInit(config);
|
|
2328
|
+
initCompletion = doInit(config, options);
|
|
2207
2329
|
return await initCompletion;
|
|
2208
2330
|
} finally {
|
|
2209
2331
|
initInProgress = false;
|
|
@@ -2221,7 +2343,15 @@ var reach = {
|
|
|
2221
2343
|
return templateRegistry;
|
|
2222
2344
|
},
|
|
2223
2345
|
get config() {
|
|
2224
|
-
|
|
2346
|
+
if (!currentConfig)
|
|
2347
|
+
return null;
|
|
2348
|
+
const safeProviders = currentConfig.providers.map((provider) => {
|
|
2349
|
+
if (provider.type !== "aliyun-sms" || !provider.gateway)
|
|
2350
|
+
return provider;
|
|
2351
|
+
const { url, allowInsecureHttp } = provider.gateway;
|
|
2352
|
+
return { ...provider, gateway: { url, allowInsecureHttp } };
|
|
2353
|
+
});
|
|
2354
|
+
return core.sanitize.sanitizeSensitiveFields({ ...currentConfig, providers: safeProviders });
|
|
2225
2355
|
},
|
|
2226
2356
|
get isInitialized() {
|
|
2227
2357
|
return providers.size > 0;
|