@unifyport/sdk-node 0.1.1 → 0.1.3
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.md +107 -91
- package/README.zh-CN.md +306 -0
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,47 +1,51 @@
|
|
|
1
1
|
# `@unifyport/sdk-node`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`UnifyPortDeviceClient`、统一的成功响应与错误模型,以及 timeout、安全重试、取消和 cursor
|
|
5
|
-
分页能力。
|
|
3
|
+
[English](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.md) | [简体中文](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.zh-CN.md) | [Website](https://www.unifyport.ai)
|
|
6
4
|
|
|
7
|
-
SDK
|
|
8
|
-
`UnifyPortDeviceClient
|
|
5
|
+
The UnifyPort Device API SDK for Node.js and TypeScript. It provides the type-safe
|
|
6
|
+
`UnifyPortDeviceClient`, consistent success response and error models, timeouts, safe retries,
|
|
7
|
+
cancellation, and cursor pagination.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
The SDK uses a fixed `X-Api-Key` authentication boundary. Operations under `/v1/accounts/...` belong to
|
|
10
|
+
the Device API and are all available through `UnifyPortDeviceClient`.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Features
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
- provider 账号资源、授权流程与运行状态;
|
|
16
|
-
- 会话、联系人和群组;
|
|
17
|
-
- 消息发送与消息动作;
|
|
18
|
-
- API Key 与 webhook endpoint 管理。
|
|
14
|
+
Every operation in the current public contract has a same-named SDK method. Major areas include:
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
- workspace and provider region queries;
|
|
17
|
+
- provider account resources, authorization flows, and runtime status;
|
|
18
|
+
- conversations, contacts, and groups;
|
|
19
|
+
- message sending and message actions;
|
|
20
|
+
- API key and webhook endpoint management.
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
Method names match their OpenAPI `operationId` values. See the
|
|
23
|
+
[API Reference](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-reference/README.md)
|
|
24
|
+
for each operation's parameters, request body, response fields, and TypeScript example. See
|
|
25
|
+
[API coverage](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/api-coverage.md) for
|
|
26
|
+
HTTP paths, retry behavior, and MCP policies.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
26
29
|
|
|
27
30
|
- Node.js `>=22.12.0`
|
|
28
|
-
- TypeScript
|
|
31
|
+
- A TypeScript project or a Node.js project with ESM support
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
This package provides only ESM `import` exports and TypeScript declarations. It does not provide a
|
|
34
|
+
CommonJS `require()` entry point.
|
|
31
35
|
|
|
32
|
-
##
|
|
36
|
+
## Installation
|
|
33
37
|
|
|
34
38
|
```bash
|
|
35
39
|
npm install @unifyport/sdk-node
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
Or with pnpm:
|
|
39
43
|
|
|
40
44
|
```bash
|
|
41
45
|
pnpm add @unifyport/sdk-node
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
##
|
|
48
|
+
## Quick start
|
|
45
49
|
|
|
46
50
|
```ts
|
|
47
51
|
import { UnifyPortDeviceClient } from "@unifyport/sdk-node";
|
|
@@ -54,7 +58,7 @@ function requiredEnv(name: string): string {
|
|
|
54
58
|
return value;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
//
|
|
61
|
+
// Read credentials only from the runtime environment so they never enter source or build artifacts.
|
|
58
62
|
const device = new UnifyPortDeviceClient({
|
|
59
63
|
baseUrl: requiredEnv("UNIFYPORT_DEVICE_API_BASE_URL"),
|
|
60
64
|
apiKey: requiredEnv("UNIFYPORT_DEVICE_API_KEY")
|
|
@@ -66,9 +70,10 @@ console.log(result.data.data.name);
|
|
|
66
70
|
console.log(result.status, result.requestId);
|
|
67
71
|
```
|
|
68
72
|
|
|
69
|
-
`ApiResult.data`
|
|
73
|
+
`ApiResult.data` contains the complete API JSON envelope, so resource data is usually available at
|
|
74
|
+
`result.data.data`.
|
|
70
75
|
|
|
71
|
-
## Client
|
|
76
|
+
## Client configuration
|
|
72
77
|
|
|
73
78
|
```ts
|
|
74
79
|
const device = new UnifyPortDeviceClient({
|
|
@@ -84,29 +89,29 @@ const device = new UnifyPortDeviceClient({
|
|
|
84
89
|
});
|
|
85
90
|
```
|
|
86
91
|
|
|
87
|
-
|
|
|
88
|
-
| ------------------- |
|
|
89
|
-
| `baseUrl` |
|
|
90
|
-
| `apiKey` |
|
|
91
|
-
| `fetch` |
|
|
92
|
-
| `timeoutMs` |
|
|
93
|
-
| `maxResponseBytes` |
|
|
94
|
-
| `retry` |
|
|
95
|
-
| `allowInsecureHttp` |
|
|
92
|
+
| Option | Required | Description |
|
|
93
|
+
| ------------------- | -------- | -------------------------------------------------------------------------------- |
|
|
94
|
+
| `baseUrl` | Yes | Absolute Device API URL; remote URLs must use HTTPS |
|
|
95
|
+
| `apiKey` | Yes | A string or a synchronous/asynchronous provider that returns a string |
|
|
96
|
+
| `fetch` | No | Custom `fetch` implementation with `(request: Request) => Promise<Response>` |
|
|
97
|
+
| `timeoutMs` | No | Defaults to `30000`; total timeout for each operation |
|
|
98
|
+
| `maxResponseBytes` | No | Defaults to 8 MiB, maximum 64 MiB; limits both wire body and normalized JSON |
|
|
99
|
+
| `retry` | No | Retry count and full-jitter backoff settings for safe operations |
|
|
100
|
+
| `allowInsecureHttp` | No | Defaults to `false`; allows only explicitly enabled loopback HTTP in development |
|
|
96
101
|
|
|
97
|
-
`baseUrl`
|
|
98
|
-
|
|
102
|
+
`baseUrl` must not contain a username, password, query, or fragment. The client injects the API key
|
|
103
|
+
centrally, and an individual operation cannot override the authentication header.
|
|
99
104
|
|
|
100
|
-
##
|
|
105
|
+
## Calling operations
|
|
101
106
|
|
|
102
|
-
|
|
107
|
+
Every method uses `(request?, execution?)`:
|
|
103
108
|
|
|
104
|
-
-
|
|
105
|
-
- path
|
|
106
|
-
- JSON
|
|
107
|
-
-
|
|
109
|
+
- Omit `request` for operations without parameters.
|
|
110
|
+
- Put path and query parameters in `request.params`.
|
|
111
|
+
- Put a JSON request body in `request.body`.
|
|
112
|
+
- Put per-call timeout, tighter retry settings, and the cancellation signal in `execution`.
|
|
108
113
|
|
|
109
|
-
###
|
|
114
|
+
### No-parameter reads
|
|
110
115
|
|
|
111
116
|
```ts
|
|
112
117
|
const workspace = await device.getWorkspace();
|
|
@@ -118,7 +123,7 @@ for (const account of accounts.data.data) {
|
|
|
118
123
|
}
|
|
119
124
|
```
|
|
120
125
|
|
|
121
|
-
### Path
|
|
126
|
+
### Path parameters
|
|
122
127
|
|
|
123
128
|
```ts
|
|
124
129
|
const regions = await device.listProviderRegions({
|
|
@@ -132,7 +137,7 @@ for (const region of regions.data.data.regions) {
|
|
|
132
137
|
}
|
|
133
138
|
```
|
|
134
139
|
|
|
135
|
-
### Path
|
|
140
|
+
### Path and query parameters
|
|
136
141
|
|
|
137
142
|
```ts
|
|
138
143
|
const contacts = await device.listContacts({
|
|
@@ -146,7 +151,7 @@ console.log(contacts.data.data.items);
|
|
|
146
151
|
console.log(contacts.data.data.next_cursor);
|
|
147
152
|
```
|
|
148
153
|
|
|
149
|
-
### JSON
|
|
154
|
+
### JSON request body
|
|
150
155
|
|
|
151
156
|
```ts
|
|
152
157
|
const message = await device.sendMessage({
|
|
@@ -160,22 +165,24 @@ const message = await device.sendMessage({
|
|
|
160
165
|
console.log(message.data.data.message_id, message.data.data.status);
|
|
161
166
|
```
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
A write operation does not become retryable because of its HTTP method or caller configuration. Do not
|
|
169
|
+
write sensitive inputs such as message bodies to logs.
|
|
164
170
|
|
|
165
|
-
##
|
|
171
|
+
## Successful responses
|
|
166
172
|
|
|
167
|
-
|
|
173
|
+
Every operation returns `ApiResult<T>`:
|
|
168
174
|
|
|
169
|
-
|
|
|
170
|
-
| ----------- |
|
|
171
|
-
| `data` |
|
|
172
|
-
| `status` | HTTP status
|
|
173
|
-
| `requestId` |
|
|
174
|
-
| `response` |
|
|
175
|
+
| Field | Description |
|
|
176
|
+
| ----------- | ------------------------------------------------------------------------- |
|
|
177
|
+
| `data` | Complete API JSON envelope parsed according to the public contract |
|
|
178
|
+
| `status` | HTTP status |
|
|
179
|
+
| `requestId` | Optional server request ID |
|
|
180
|
+
| `response` | Underlying `Response` for headers and other successful transport metadata |
|
|
175
181
|
|
|
176
|
-
`response`
|
|
182
|
+
The SDK has already parsed the `response` body; do not depend on reading it again. Use the typed `data`
|
|
183
|
+
for application data.
|
|
177
184
|
|
|
178
|
-
##
|
|
185
|
+
## Timeouts, cancellation, and retries
|
|
179
186
|
|
|
180
187
|
```ts
|
|
181
188
|
const controller = new AbortController();
|
|
@@ -190,20 +197,22 @@ const workspace = await device.getWorkspace(
|
|
|
190
197
|
);
|
|
191
198
|
```
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
A per-call `execution.retry` setting can only disable or tighten client-level retries:
|
|
194
201
|
|
|
195
202
|
```ts
|
|
196
203
|
await device.getWorkspace({}, { retry: false });
|
|
197
204
|
await device.getWorkspace({}, { retry: { maxRetries: 0 } });
|
|
198
205
|
```
|
|
199
206
|
|
|
200
|
-
|
|
201
|
-
`
|
|
202
|
-
|
|
207
|
+
Automatic retries apply only to safe operations explicitly allowed by policy. They handle network
|
|
208
|
+
failures and temporary statuses such as `408`, `429`, `502`, `503`, and `504`, and honor a valid
|
|
209
|
+
`Retry-After` value. Ordinary writes, account authorization submissions, and one-time secret operations
|
|
210
|
+
are not retried automatically.
|
|
203
211
|
|
|
204
|
-
## Cursor
|
|
212
|
+
## Cursor pagination
|
|
205
213
|
|
|
206
|
-
`paginateCursor`
|
|
214
|
+
`paginateCursor` iterates page items and supports a maximum page count, repeated cursor detection, and
|
|
215
|
+
`AbortSignal`:
|
|
207
216
|
|
|
208
217
|
```ts
|
|
209
218
|
import { paginateCursor } from "@unifyport/sdk-node";
|
|
@@ -213,7 +222,7 @@ const controller = new AbortController();
|
|
|
213
222
|
|
|
214
223
|
for await (const contact of paginateCursor(
|
|
215
224
|
async (cursor, pageSignal) => {
|
|
216
|
-
// exactOptionalPropertyTypes
|
|
225
|
+
// With exactOptionalPropertyTypes, do not assign undefined to an optional query field.
|
|
217
226
|
const query = cursor === undefined ? { limit: 100 } : { cursor, limit: 100 };
|
|
218
227
|
const result = await device.listContacts(
|
|
219
228
|
{
|
|
@@ -232,12 +241,14 @@ for await (const contact of paginateCursor(
|
|
|
232
241
|
}
|
|
233
242
|
```
|
|
234
243
|
|
|
235
|
-
|
|
244
|
+
The pagination helper reads at most 10,000 pages by default. Application code should normally set a
|
|
245
|
+
smaller `maxPages` value appropriate to its workload.
|
|
236
246
|
|
|
237
|
-
##
|
|
247
|
+
## Error handling
|
|
238
248
|
|
|
239
|
-
|
|
240
|
-
`operationId
|
|
249
|
+
All public errors extend `UnifyPortError`. Record only reviewed fields such as `status`, `code`,
|
|
250
|
+
`requestId`, `api`, and `operationId`. Do not serialize complete errors, requests, responses, or
|
|
251
|
+
headers.
|
|
241
252
|
|
|
242
253
|
```ts
|
|
243
254
|
import { UnifyPortAbortError, UnifyPortApiError, UnifyPortTimeoutError } from "@unifyport/sdk-node";
|
|
@@ -262,19 +273,19 @@ try {
|
|
|
262
273
|
}
|
|
263
274
|
```
|
|
264
275
|
|
|
265
|
-
|
|
|
266
|
-
| ----------------------------- |
|
|
267
|
-
| `UnifyPortConfigurationError` | client
|
|
268
|
-
| `UnifyPortNetworkError` |
|
|
269
|
-
| `UnifyPortTimeoutError` |
|
|
270
|
-
| `UnifyPortAbortError` |
|
|
271
|
-
| `UnifyPortApiError` | API
|
|
272
|
-
| `UnifyPortResponseParseError` |
|
|
273
|
-
| `UnifyPortPaginationError` | cursor
|
|
276
|
+
| Error type | Scenario |
|
|
277
|
+
| ----------------------------- | --------------------------------------------------------------------- |
|
|
278
|
+
| `UnifyPortConfigurationError` | Invalid client configuration |
|
|
279
|
+
| `UnifyPortNetworkError` | Network or custom credential provider failure |
|
|
280
|
+
| `UnifyPortTimeoutError` | Operation timeout |
|
|
281
|
+
| `UnifyPortAbortError` | Caller cancellation through `AbortSignal` |
|
|
282
|
+
| `UnifyPortApiError` | API returned a non-success HTTP status |
|
|
283
|
+
| `UnifyPortResponseParseError` | Successful response could not be parsed safely |
|
|
284
|
+
| `UnifyPortPaginationError` | Missing/repeated cursor, page limit exceeded, or pagination cancelled |
|
|
274
285
|
|
|
275
|
-
## TypeScript
|
|
286
|
+
## TypeScript types
|
|
276
287
|
|
|
277
|
-
|
|
288
|
+
The package root also exports the public contract types:
|
|
278
289
|
|
|
279
290
|
```ts
|
|
280
291
|
import type { DeviceApiComponents, DeviceApiOperations, DeviceApiPaths } from "@unifyport/sdk-node";
|
|
@@ -284,21 +295,26 @@ type SendMessageOperation = DeviceApiOperations["sendMessage"];
|
|
|
284
295
|
type DevicePaths = DeviceApiPaths;
|
|
285
296
|
```
|
|
286
297
|
|
|
287
|
-
|
|
288
|
-
|
|
298
|
+
To avoid JavaScript precision loss, the SDK preserves integers outside the safe range as strings.
|
|
299
|
+
Fields modeled like `uint64` or `int64` may therefore be `number | string`. Do not call `Number(...)`
|
|
300
|
+
unconditionally unless the value's range is guaranteed.
|
|
289
301
|
|
|
290
|
-
##
|
|
302
|
+
## Security considerations
|
|
291
303
|
|
|
292
|
-
- API key
|
|
293
|
-
-
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
304
|
+
- Provide the API key through a secret manager or runtime environment; never hard-code or commit it.
|
|
305
|
+
- Require HTTPS in production. `allowInsecureHttp` accepts only explicitly enabled loopback addresses.
|
|
306
|
+
- Do not log authentication headers, authorization codes/passwords/sessions, message bodies, or
|
|
307
|
+
one-time secrets.
|
|
308
|
+
- Do not override `baseUrl` dynamically from external input. The client confines credentials to the
|
|
309
|
+
origin and path established during initialization.
|
|
310
|
+
- Design application-level idempotency and failure recovery for writes. Do not apply read-operation
|
|
311
|
+
retry assumptions to writes.
|
|
297
312
|
|
|
298
|
-
|
|
313
|
+
For more detail, see the
|
|
314
|
+
[security boundaries](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/security.md).
|
|
299
315
|
|
|
300
|
-
##
|
|
316
|
+
## Project and license
|
|
301
317
|
|
|
302
|
-
- [GitHub
|
|
303
|
-
- [
|
|
318
|
+
- [GitHub repository](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs)
|
|
319
|
+
- [Project README](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs#readme)
|
|
304
320
|
- [MIT License](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# `@unifyport/sdk-node`
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.md) | [简体中文](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/packages/sdk/README.zh-CN.md) | [官方网站](https://www.unifyport.ai)
|
|
4
|
+
|
|
5
|
+
面向 Node.js/TypeScript 的 UnifyPort Device API SDK。它提供类型安全的
|
|
6
|
+
`UnifyPortDeviceClient`、统一的成功响应与错误模型,以及 timeout、安全重试、取消和 cursor
|
|
7
|
+
分页能力。
|
|
8
|
+
|
|
9
|
+
SDK 使用固定的 `X-Api-Key` 认证边界。`/v1/accounts/...` operation 属于 Device API,统一由
|
|
10
|
+
`UnifyPortDeviceClient` 提供。
|
|
11
|
+
|
|
12
|
+
## 功能范围
|
|
13
|
+
|
|
14
|
+
当前公开契约中的全部 operation 都有同名 SDK 方法,主要覆盖:
|
|
15
|
+
|
|
16
|
+
- workspace 与 provider region 查询;
|
|
17
|
+
- provider 账号资源、授权流程与运行状态;
|
|
18
|
+
- 会话、联系人和群组;
|
|
19
|
+
- 消息发送与消息动作;
|
|
20
|
+
- API Key 与 webhook endpoint 管理。
|
|
21
|
+
|
|
22
|
+
方法名与 OpenAPI `operationId` 保持一致。每个 operation 的参数、请求体、返回字段和 TypeScript
|
|
23
|
+
示例见 [API Reference](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/api-reference/README.md);
|
|
24
|
+
HTTP path、重试和 MCP 策略见
|
|
25
|
+
[API 覆盖表](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/api-coverage.md)。
|
|
26
|
+
|
|
27
|
+
## 环境要求
|
|
28
|
+
|
|
29
|
+
- Node.js `>=22.12.0`
|
|
30
|
+
- TypeScript 项目或支持 ESM 的 Node.js 项目
|
|
31
|
+
|
|
32
|
+
该 package 只提供 ESM `import` 和 TypeScript 声明文件,不提供 CommonJS `require()` 入口。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @unifyport/sdk-node
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
也可以使用 pnpm:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @unifyport/sdk-node
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 快速开始
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { UnifyPortDeviceClient } from "@unifyport/sdk-node";
|
|
50
|
+
|
|
51
|
+
function requiredEnv(name: string): string {
|
|
52
|
+
const value = process.env[name];
|
|
53
|
+
if (!value) {
|
|
54
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 凭据只从运行环境读取,避免进入源码、构建产物或版本控制。
|
|
60
|
+
const device = new UnifyPortDeviceClient({
|
|
61
|
+
baseUrl: requiredEnv("UNIFYPORT_DEVICE_API_BASE_URL"),
|
|
62
|
+
apiKey: requiredEnv("UNIFYPORT_DEVICE_API_KEY")
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await device.getWorkspace();
|
|
66
|
+
|
|
67
|
+
console.log(result.data.data.name);
|
|
68
|
+
console.log(result.status, result.requestId);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`ApiResult.data` 是完整 API JSON envelope,因此资源数据通常位于 `result.data.data`。
|
|
72
|
+
|
|
73
|
+
## Client 配置
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const device = new UnifyPortDeviceClient({
|
|
77
|
+
baseUrl: "https://device.example.com",
|
|
78
|
+
apiKey: () => requiredEnv("UNIFYPORT_DEVICE_API_KEY"),
|
|
79
|
+
timeoutMs: 15_000,
|
|
80
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
81
|
+
retry: {
|
|
82
|
+
maxRetries: 2,
|
|
83
|
+
baseDelayMs: 200,
|
|
84
|
+
maxDelayMs: 5_000
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| 配置项 | 必填 | 说明 |
|
|
90
|
+
| ------------------- | ---- | --------------------------------------------------------------------- |
|
|
91
|
+
| `baseUrl` | 是 | Device API 的绝对 URL;远程地址必须使用 HTTPS |
|
|
92
|
+
| `apiKey` | 是 | 字符串,或返回字符串的同步/异步 provider |
|
|
93
|
+
| `fetch` | 否 | 自定义 `fetch` 实现,签名为 `(request: Request) => Promise<Response>` |
|
|
94
|
+
| `timeoutMs` | 否 | 默认 `30000`;每次 operation 的总超时 |
|
|
95
|
+
| `maxResponseBytes` | 否 | 默认 8 MiB,最大 64 MiB;同时限制 wire body 与规范化后的 JSON |
|
|
96
|
+
| `retry` | 否 | 安全 operation 的重试次数和 full-jitter 退避配置 |
|
|
97
|
+
| `allowInsecureHttp` | 否 | 默认 `false`;仅允许显式开启的 loopback HTTP 开发环境 |
|
|
98
|
+
|
|
99
|
+
`baseUrl` 不应包含 username、password、query 或 fragment。API key 由 client 统一注入,单次 operation
|
|
100
|
+
不能覆盖认证 header。
|
|
101
|
+
|
|
102
|
+
## 调用 operation
|
|
103
|
+
|
|
104
|
+
方法统一使用 `(request?, execution?)`:
|
|
105
|
+
|
|
106
|
+
- 没有参数的 operation 可以省略 `request`;
|
|
107
|
+
- path 和 query 参数放在 `request.params`;
|
|
108
|
+
- JSON 请求体放在 `request.body`;
|
|
109
|
+
- 单次 timeout、重试收紧和取消信号放在 `execution`。
|
|
110
|
+
|
|
111
|
+
### 无参数读取
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const workspace = await device.getWorkspace();
|
|
115
|
+
console.log(workspace.data.data);
|
|
116
|
+
|
|
117
|
+
const accounts = await device.listAccounts();
|
|
118
|
+
for (const account of accounts.data.data) {
|
|
119
|
+
console.log(account.id, account.provider, account.status);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Path 参数
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const regions = await device.listProviderRegions({
|
|
127
|
+
params: {
|
|
128
|
+
path: { provider: "whatsapp" }
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
for (const region of regions.data.data.regions) {
|
|
133
|
+
console.log(region.region, region.supported, region.allocatable);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Path 与 query 参数
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const contacts = await device.listContacts({
|
|
141
|
+
params: {
|
|
142
|
+
path: { account_id: "acc_xxx" },
|
|
143
|
+
query: { limit: 100, q: "Alice" }
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
console.log(contacts.data.data.items);
|
|
148
|
+
console.log(contacts.data.data.next_cursor);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### JSON 请求体
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const message = await device.sendMessage({
|
|
155
|
+
body: {
|
|
156
|
+
account_id: "acc_xxx",
|
|
157
|
+
to: { id: "recipient_xxx", type: "user" },
|
|
158
|
+
message: { type: "text", text: "Hello from UnifyPort" }
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log(message.data.data.message_id, message.data.data.status);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
写操作不会因为 HTTP method 或调用方配置而自动获得重试能力。消息正文等敏感输入不应写入日志。
|
|
166
|
+
|
|
167
|
+
## 成功响应
|
|
168
|
+
|
|
169
|
+
所有 operation 都返回 `ApiResult<T>`:
|
|
170
|
+
|
|
171
|
+
| 字段 | 说明 |
|
|
172
|
+
| ----------- | ------------------------------------------------------- |
|
|
173
|
+
| `data` | 按公开契约解析后的完整 API JSON envelope |
|
|
174
|
+
| `status` | HTTP status |
|
|
175
|
+
| `requestId` | 可选的服务端 request ID |
|
|
176
|
+
| `response` | 底层 `Response`,用于读取正常响应的 header 等传输元数据 |
|
|
177
|
+
|
|
178
|
+
`response` 的 body 已由 SDK 解析,不应依赖再次读取;业务数据使用类型化的 `data`。
|
|
179
|
+
|
|
180
|
+
## Timeout、取消与重试
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
|
|
185
|
+
const workspace = await device.getWorkspace(
|
|
186
|
+
{},
|
|
187
|
+
{
|
|
188
|
+
timeoutMs: 5_000,
|
|
189
|
+
retry: { maxRetries: 1 },
|
|
190
|
+
signal: controller.signal
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
单次 `execution.retry` 只能关闭或收紧 client 级重试:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
await device.getWorkspace({}, { retry: false });
|
|
199
|
+
await device.getWorkspace({}, { retry: { maxRetries: 0 } });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
自动重试只适用于策略明确允许的安全 operation,处理网络失败和 `408`、`429`、`502`、`503`、
|
|
203
|
+
`504` 等临时状态,并遵守合法的 `Retry-After`。普通写操作、账号授权提交和一次性 secret operation
|
|
204
|
+
不会自动重试。
|
|
205
|
+
|
|
206
|
+
## Cursor 分页
|
|
207
|
+
|
|
208
|
+
`paginateCursor` 会逐项迭代分页数据,并提供最大页数、重复 cursor 检测与 `AbortSignal` 支持:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { paginateCursor } from "@unifyport/sdk-node";
|
|
212
|
+
|
|
213
|
+
const accountId = "acc_xxx";
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
|
|
216
|
+
for await (const contact of paginateCursor(
|
|
217
|
+
async (cursor, pageSignal) => {
|
|
218
|
+
// exactOptionalPropertyTypes 下不把 undefined 写入可选 query 字段。
|
|
219
|
+
const query = cursor === undefined ? { limit: 100 } : { cursor, limit: 100 };
|
|
220
|
+
const result = await device.listContacts(
|
|
221
|
+
{
|
|
222
|
+
params: {
|
|
223
|
+
path: { account_id: accountId },
|
|
224
|
+
query
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
pageSignal === undefined ? {} : { signal: pageSignal }
|
|
228
|
+
);
|
|
229
|
+
return result.data.data;
|
|
230
|
+
},
|
|
231
|
+
{ maxPages: 100, signal: controller.signal }
|
|
232
|
+
)) {
|
|
233
|
+
console.log(contact.id, contact.display_name);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
分页 helper 默认最多读取 10,000 页。业务代码通常应根据自身任务量设置更小的 `maxPages`。
|
|
238
|
+
|
|
239
|
+
## 错误处理
|
|
240
|
+
|
|
241
|
+
所有公开错误都继承 `UnifyPortError`。建议只记录经过筛选的 `status`、`code`、`requestId`、`api` 和
|
|
242
|
+
`operationId`,不要直接序列化完整错误、请求、响应或 header。
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { UnifyPortAbortError, UnifyPortApiError, UnifyPortTimeoutError } from "@unifyport/sdk-node";
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await device.getWorkspace();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof UnifyPortApiError) {
|
|
251
|
+
console.error({
|
|
252
|
+
status: error.status,
|
|
253
|
+
code: error.code,
|
|
254
|
+
requestId: error.requestId,
|
|
255
|
+
operationId: error.operationId
|
|
256
|
+
});
|
|
257
|
+
} else if (error instanceof UnifyPortTimeoutError) {
|
|
258
|
+
console.error({ timeoutMs: error.timeoutMs, operationId: error.operationId });
|
|
259
|
+
} else if (error instanceof UnifyPortAbortError) {
|
|
260
|
+
console.error({ aborted: true, operationId: error.operationId });
|
|
261
|
+
} else {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
| 错误类型 | 场景 |
|
|
268
|
+
| ----------------------------- | --------------------------------------- |
|
|
269
|
+
| `UnifyPortConfigurationError` | client 配置无效 |
|
|
270
|
+
| `UnifyPortNetworkError` | 网络或自定义 credential provider 失败 |
|
|
271
|
+
| `UnifyPortTimeoutError` | operation 超时 |
|
|
272
|
+
| `UnifyPortAbortError` | 调用方通过 `AbortSignal` 取消 |
|
|
273
|
+
| `UnifyPortApiError` | API 返回非成功 HTTP status |
|
|
274
|
+
| `UnifyPortResponseParseError` | 成功响应无法安全解析 |
|
|
275
|
+
| `UnifyPortPaginationError` | cursor 缺失、重复、超出页数或分页被取消 |
|
|
276
|
+
|
|
277
|
+
## TypeScript 类型
|
|
278
|
+
|
|
279
|
+
根入口同时导出公开契约类型:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import type { DeviceApiComponents, DeviceApiOperations, DeviceApiPaths } from "@unifyport/sdk-node";
|
|
283
|
+
|
|
284
|
+
type Workspace = DeviceApiComponents["schemas"]["Workspace"];
|
|
285
|
+
type SendMessageOperation = DeviceApiOperations["sendMessage"];
|
|
286
|
+
type DevicePaths = DeviceApiPaths;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
为避免 JavaScript 精度丢失,SDK 会把超出安全范围的整数保留为字符串。因此 `uint64`/`int64` 风格
|
|
290
|
+
字段可能是 `number | string`;不要在没有范围保证时无条件调用 `Number(...)`。
|
|
291
|
+
|
|
292
|
+
## 安全注意事项
|
|
293
|
+
|
|
294
|
+
- API key 应从 secret manager 或运行环境提供,不要硬编码或提交到版本控制;
|
|
295
|
+
- 生产环境必须使用 HTTPS;`allowInsecureHttp` 只接受显式开启的 loopback 地址;
|
|
296
|
+
- 不要记录认证 header、授权 code/password/session、消息正文或一次性 secret;
|
|
297
|
+
- 不要从外部输入动态覆盖 `baseUrl`;client 会把凭据限制在初始化时确定的 origin/path;
|
|
298
|
+
- 对写操作自行设计业务幂等和失败恢复,不要把读取 operation 的重试假设套用到写入。
|
|
299
|
+
|
|
300
|
+
更多设计细节见[安全边界](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/docs/zh-CN/security.md)。
|
|
301
|
+
|
|
302
|
+
## 项目与许可证
|
|
303
|
+
|
|
304
|
+
- [GitHub 仓库](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs)
|
|
305
|
+
- [项目 README](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/README.zh-CN.md)
|
|
306
|
+
- [MIT License](https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unifyport/sdk-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Type-safe Node.js SDK for the UnifyPort Device API.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/Unify-Port/UnifyPort-SDK-Nodejs.git",
|
|
10
10
|
"directory": "packages/sdk"
|
|
11
11
|
},
|
|
12
|
+
"homepage": "https://www.unifyport.ai",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Unify-Port/UnifyPort-SDK-Nodejs/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"unifyport",
|
|
18
|
+
"sdk",
|
|
19
|
+
"device-api",
|
|
20
|
+
"nodejs",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
12
23
|
"publishConfig": {
|
|
13
24
|
"access": "public",
|
|
14
25
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -28,7 +39,8 @@
|
|
|
28
39
|
},
|
|
29
40
|
"files": [
|
|
30
41
|
"dist",
|
|
31
|
-
"README.md"
|
|
42
|
+
"README.md",
|
|
43
|
+
"README.zh-CN.md"
|
|
32
44
|
],
|
|
33
45
|
"sideEffects": false,
|
|
34
46
|
"scripts": {
|