@omni-gate/sse-sdk-api 0.0.0-stage → 0.1.0
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 +92 -2
- package/README_CN.md +99 -0
- package/dist/SseServiceApi.d.ts +54 -0
- package/dist/SseServiceApi.d.ts.map +1 -0
- package/dist/SseServiceApi.js +100 -0
- package/dist/SseServiceApi.js.map +1 -0
- package/dist/SseTags.d.ts +17 -0
- package/dist/SseTags.d.ts.map +1 -0
- package/dist/SseTags.js +27 -0
- package/dist/SseTags.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -3
package/README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @omni-gate/sse-sdk-api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[中文](./README_CN.md)
|
|
4
|
+
|
|
5
|
+
ESM-only TypeScript SDK for OmniGate backend services to use the sse-service: issue SSE connection tickets for users and push events to users, apps or topics.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Node.js 20.19 or later (ESM only)
|
|
10
|
+
- The sse-service must be reachable on the internal network. It runs as a single instance; `/ticket` and `/notification` are internal only (blocked at the gateway), no apiKey needed
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @omni-gate/sse-sdk-api # inside the workspace use "workspace:*"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Initialization
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import SseServiceApi from '@omni-gate/sse-sdk-api';
|
|
22
|
+
|
|
23
|
+
SseServiceApi.initialize({
|
|
24
|
+
portal: 'http://omni-sse/webservice', // internal sse-service address
|
|
25
|
+
timeout: 5000 // pushes are side effects, keep it short
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const sseApi = SseServiceApi.getInstance();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
When push is optional, initialize only if an address is configured and check `SseServiceApi.isInitialized()` before calling.
|
|
32
|
+
|
|
33
|
+
## Tags
|
|
34
|
+
|
|
35
|
+
Connections receive pushes by tag, formatted `{tenantCode}|{type}|{id}`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {SseTags} from '@omni-gate/sse-sdk-api';
|
|
39
|
+
|
|
40
|
+
SseTags.user('T001', 'zhangsan'); // T001|U|zhangsan user (used by omni-portal)
|
|
41
|
+
SseTags.app('T001', 'BIG_SCREEN'); // T001|A|BIG_SCREEN app
|
|
42
|
+
SseTags.topic('T001', 'orders'); // T001|T|orders topic
|
|
43
|
+
SseTags.parse('T001|U|zhangsan'); // {tenantCode, type, id}, or null when malformed
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Tenant code and id must be non-empty and contain no spaces or `|`; otherwise an error is thrown.
|
|
47
|
+
|
|
48
|
+
## Connection tickets
|
|
49
|
+
|
|
50
|
+
After authenticating the user, a backend service requests a ticket and hands it to the browser. A ticket is valid for 120 seconds and can be used once:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const ticket = await sseApi.createUserTicket(user.tenantCode, user.accountCode);
|
|
54
|
+
return {ticket};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Browser:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const source = new EventSource(`/api/omni/sse/webservice/connection/${encodeURIComponent(ticket)}?token=${jwt}`);
|
|
61
|
+
source.addEventListener('todo-changed', e => { /* JSON.parse(e.data) */ });
|
|
62
|
+
source.onerror = () => {
|
|
63
|
+
source.close();
|
|
64
|
+
// tickets are single-use: back off, request a new ticket and reconnect (see omni-portal PortalEvents)
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Never let the browser choose the tag: the tag decides whose messages it receives and must be built on the server from the logged-in user.
|
|
69
|
+
|
|
70
|
+
## Pushing events
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const {count} = await sseApi.sendToUser('T001', 'zhangsan', 'todo-changed', {groupId: 12}); // count = 0 when offline
|
|
74
|
+
const {count: total, failed} = await sseApi.sendToUsers('T001', ['zhangsan', 'lisi'], 'message-received', {});
|
|
75
|
+
await sseApi.sendToTag('T001', SseTags.topic('T001', 'orders'), 'order-created', {orderNo: 'SO-001'});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `event` is the browser `addEventListener` name; `data` is sent as JSON.
|
|
79
|
+
- Delivery is best effort: offline users miss events and nothing is replayed. Persist anything that must arrive (e.g. inbox messages) and use the push only to tell the browser to refresh.
|
|
80
|
+
- Push after the transaction commits, and catch and log errors so they do not affect the business result.
|
|
81
|
+
|
|
82
|
+
## Error handling
|
|
83
|
+
|
|
84
|
+
HTTP errors are mapped by `@omni-gate/sdk-core`: 400 → `IllegalParameterError` (invalid ticket / tag), 503 → `ServiceUnavailableError` (connection limit reached);
|
|
85
|
+
network errors and unmapped status codes throw `ServiceApiError`.
|
|
86
|
+
|
|
87
|
+
## Build and publish
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm build
|
|
91
|
+
pnpm test:modules
|
|
92
|
+
pnpm publish:public
|
|
93
|
+
```
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @omni-gate/sse-sdk-api
|
|
2
|
+
|
|
3
|
+
[English](./README.md)
|
|
4
|
+
|
|
5
|
+
供 OmniGate 内部后端服务调用 sse-service 的纯 ESM TypeScript SDK:为用户申请 SSE 连接票据,向用户、应用或主题推送事件。
|
|
6
|
+
|
|
7
|
+
## 环境要求
|
|
8
|
+
|
|
9
|
+
- Node.js 20.19 或更高版本(纯 ESM)
|
|
10
|
+
- 能在内网访问 sse-service。sse-service 单实例部署,`/ticket`、`/notification` 只在内网开放(网关阻断),不需要 apiKey
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @omni-gate/sse-sdk-api # 工作区内使用 "workspace:*"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 初始化
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import SseServiceApi from '@omni-gate/sse-sdk-api';
|
|
22
|
+
|
|
23
|
+
SseServiceApi.initialize({
|
|
24
|
+
portal: 'http://omni-sse/webservice', // sse-service 内部地址
|
|
25
|
+
timeout: 5000 // 推送是旁路操作,建议设短一些
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const sseApi = SseServiceApi.getInstance();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
推送是可选能力时,可以只在配置了地址时初始化,调用前用 `SseServiceApi.isInitialized()` 判断。
|
|
32
|
+
|
|
33
|
+
## 标签
|
|
34
|
+
|
|
35
|
+
连接按标签接收推送,格式 `{tenantCode}|{类型}|{标识}`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {SseTags} from '@omni-gate/sse-sdk-api';
|
|
39
|
+
|
|
40
|
+
SseTags.user('T001', 'zhangsan'); // T001|U|zhangsan 用户(omni-portal 使用)
|
|
41
|
+
SseTags.app('T001', 'BIG_SCREEN'); // T001|A|BIG_SCREEN 应用
|
|
42
|
+
SseTags.topic('T001', 'orders'); // T001|T|orders 主题
|
|
43
|
+
SseTags.parse('T001|U|zhangsan'); // {tenantCode, type, id},格式不对返回 null
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
租户编码和标识不能为空,也不能包含空格或 `|`,否则抛出异常。
|
|
47
|
+
|
|
48
|
+
## 申请连接票据
|
|
49
|
+
|
|
50
|
+
业务服务在确认当前用户身份后申请票据,交给前端建立连接。票据 120 秒内有效、只能用一次:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// 例如 GET /tenant/me/sse-ticket 的实现
|
|
54
|
+
const ticket = await sseApi.createUserTicket(user.tenantCode, user.accountCode);
|
|
55
|
+
return {ticket};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
前端:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const source = new EventSource(`/api/omni/sse/webservice/connection/${encodeURIComponent(ticket)}?token=${jwt}`);
|
|
62
|
+
source.addEventListener('todo-changed', e => { /* JSON.parse(e.data) */ });
|
|
63
|
+
source.onerror = () => {
|
|
64
|
+
source.close();
|
|
65
|
+
// 票据是一次性的:退避后重新申请票据再连接(参见 omni-portal 的 PortalEvents)
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
不要让前端自己指定标签申请票据:标签决定能收到谁的消息,必须由服务端按登录用户构建。
|
|
70
|
+
|
|
71
|
+
## 推送
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// 推送给用户的所有连接;count 为成功写出的连接数,用户不在线时为 0
|
|
75
|
+
const {count} = await sseApi.sendToUser('T001', 'zhangsan', 'todo-changed', {groupId: 12});
|
|
76
|
+
|
|
77
|
+
// 推送给多个用户(去重,单个失败不影响其他人)
|
|
78
|
+
const {count: total, failed} = await sseApi.sendToUsers('T001', ['zhangsan', 'lisi'], 'message-received', {});
|
|
79
|
+
|
|
80
|
+
// 按标签推送:标签必须属于该租户
|
|
81
|
+
await sseApi.sendToTag('T001', SseTags.topic('T001', 'orders'), 'order-created', {orderNo: 'SO-001'});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- `event` 是前端 `addEventListener` 的事件名;`data` 以 JSON 发送。
|
|
85
|
+
- 推送不保证送达:离线用户收不到,也不会补发。需要可靠送达的内容(如站内消息)先落库,推送只用来提醒前端刷新。
|
|
86
|
+
- 推送通常不应影响业务结果,建议在事务提交后调用,并捕获异常只记日志。
|
|
87
|
+
|
|
88
|
+
## 错误处理
|
|
89
|
+
|
|
90
|
+
HTTP 错误按 `@omni-gate/sdk-core` 的规则映射:400 → `IllegalParameterError`(票据/标签无效),503 → `ServiceUnavailableError`(连接数已达上限),
|
|
91
|
+
网络错误和未映射的状态码抛 `ServiceApiError`。
|
|
92
|
+
|
|
93
|
+
## 构建与发布
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm build # 输出到 dist/
|
|
97
|
+
pnpm test:modules # 验证 ESM 入口
|
|
98
|
+
pnpm publish:public
|
|
99
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CommonServiceApi } from '@omni-gate/sdk-core';
|
|
2
|
+
import type { SseApiConfigOptions, SsePushResult } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* sse-service 服务端 SDK(内部服务调用)。
|
|
5
|
+
*
|
|
6
|
+
* - 申请票据:业务服务确认用户身份后,为其标签申请一次性票据(120 秒有效)交给前端,
|
|
7
|
+
* 前端用 `/connection/{ticket}` 建立 EventSource 连接;票据只能用一次,断线后要重新申请。
|
|
8
|
+
* - 推送:按用户或按标签推送事件,返回成功写出的连接数,用户不在线时为 0。
|
|
9
|
+
*
|
|
10
|
+
* sse-service 单实例部署、只在内网暴露;`/ticket`、`/notification` 不对外开放。
|
|
11
|
+
*/
|
|
12
|
+
export default class SseServiceApi extends CommonServiceApi {
|
|
13
|
+
private static instance?;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* 初始化单例(服务启动时调用一次)
|
|
17
|
+
*/
|
|
18
|
+
static initialize(config: SseApiConfigOptions): SseServiceApi;
|
|
19
|
+
static getInstance(): SseServiceApi;
|
|
20
|
+
/** 是否已初始化(推送是可选能力时,可据此跳过) */
|
|
21
|
+
static isInitialized(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 为标签申请一次性连接票据
|
|
24
|
+
* @param tag 用 SseTags 构建的标签
|
|
25
|
+
* @returns 票据
|
|
26
|
+
*/
|
|
27
|
+
createTicket(tag: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* 为用户申请连接票据
|
|
30
|
+
*/
|
|
31
|
+
createUserTicket(tenantCode: string, accountCode: string): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* 推送给用户的所有连接
|
|
34
|
+
* @param event 事件名,前端用 addEventListener(event) 接收
|
|
35
|
+
* @param data 事件数据(JSON)
|
|
36
|
+
*/
|
|
37
|
+
sendToUser(tenantCode: string, accountCode: string, event: string, data?: unknown): Promise<SsePushResult>;
|
|
38
|
+
/**
|
|
39
|
+
* 推送给多个用户;单个失败不影响其他用户
|
|
40
|
+
* @returns 成功写出的连接总数与失败的账号
|
|
41
|
+
*/
|
|
42
|
+
sendToUsers(tenantCode: string, accountCodes: Array<string>, event: string, data?: unknown): Promise<{
|
|
43
|
+
count: number;
|
|
44
|
+
failed: Array<{
|
|
45
|
+
accountCode: string;
|
|
46
|
+
error: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* 按标签推送;标签必须属于 tenantCode 的租户
|
|
51
|
+
*/
|
|
52
|
+
sendToTag(tenantCode: string, tag: string, event: string, data?: unknown): Promise<SsePushResult>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=SseServiceApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SseServiceApi.d.ts","sourceRoot":"","sources":["../src/SseServiceApi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAEnE;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,gBAAgB;IAEvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAgB;IAExC,OAAO;IAIP;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa;IAU7D,MAAM,CAAC,WAAW,IAAI,aAAa;IAOnC,6BAA6B;IAC7B,MAAM,CAAC,aAAa,IAAI,OAAO;IAI/B;;;;OAIG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQhD;;OAEG;IACG,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIhF;;;;OAIG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,OAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAMpH;;;OAGG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,OAAY,GAChG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAetF;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,OAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CAU9G"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { CommonServiceApi } from '@omni-gate/sdk-core';
|
|
2
|
+
import SseTags from './SseTags.js';
|
|
3
|
+
/**
|
|
4
|
+
* sse-service 服务端 SDK(内部服务调用)。
|
|
5
|
+
*
|
|
6
|
+
* - 申请票据:业务服务确认用户身份后,为其标签申请一次性票据(120 秒有效)交给前端,
|
|
7
|
+
* 前端用 `/connection/{ticket}` 建立 EventSource 连接;票据只能用一次,断线后要重新申请。
|
|
8
|
+
* - 推送:按用户或按标签推送事件,返回成功写出的连接数,用户不在线时为 0。
|
|
9
|
+
*
|
|
10
|
+
* sse-service 单实例部署、只在内网暴露;`/ticket`、`/notification` 不对外开放。
|
|
11
|
+
*/
|
|
12
|
+
export default class SseServiceApi extends CommonServiceApi {
|
|
13
|
+
static instance;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
super(config, 'SSE service');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 初始化单例(服务启动时调用一次)
|
|
19
|
+
*/
|
|
20
|
+
static initialize(config) {
|
|
21
|
+
if (!SseServiceApi.instance) {
|
|
22
|
+
if (!config?.portal) {
|
|
23
|
+
throw new Error('缺少 sse-service 地址 portal');
|
|
24
|
+
}
|
|
25
|
+
SseServiceApi.instance = new SseServiceApi(config);
|
|
26
|
+
}
|
|
27
|
+
return SseServiceApi.instance;
|
|
28
|
+
}
|
|
29
|
+
static getInstance() {
|
|
30
|
+
if (!SseServiceApi.instance) {
|
|
31
|
+
throw new Error('SseServiceApi has not been initialized. Call SseServiceApi.initialize() first.');
|
|
32
|
+
}
|
|
33
|
+
return SseServiceApi.instance;
|
|
34
|
+
}
|
|
35
|
+
/** 是否已初始化(推送是可选能力时,可据此跳过) */
|
|
36
|
+
static isInitialized() {
|
|
37
|
+
return SseServiceApi.instance != null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 为标签申请一次性连接票据
|
|
41
|
+
* @param tag 用 SseTags 构建的标签
|
|
42
|
+
* @returns 票据
|
|
43
|
+
*/
|
|
44
|
+
async createTicket(tag) {
|
|
45
|
+
if (!SseTags.parse(tag)) {
|
|
46
|
+
throw new Error(`无效的 SSE 标签:${tag}`);
|
|
47
|
+
}
|
|
48
|
+
const res = await this.get('/ticket/custom', { params: { tag } });
|
|
49
|
+
return res.ticket;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 为用户申请连接票据
|
|
53
|
+
*/
|
|
54
|
+
async createUserTicket(tenantCode, accountCode) {
|
|
55
|
+
return this.createTicket(SseTags.user(tenantCode, accountCode));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 推送给用户的所有连接
|
|
59
|
+
* @param event 事件名,前端用 addEventListener(event) 接收
|
|
60
|
+
* @param data 事件数据(JSON)
|
|
61
|
+
*/
|
|
62
|
+
async sendToUser(tenantCode, accountCode, event, data = {}) {
|
|
63
|
+
return this.post(`/notification/user/${encodeURIComponent(event)}`, data ?? {}, {
|
|
64
|
+
headers: { 'tenant-code': tenantCode, 'account-code': accountCode }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 推送给多个用户;单个失败不影响其他用户
|
|
69
|
+
* @returns 成功写出的连接总数与失败的账号
|
|
70
|
+
*/
|
|
71
|
+
async sendToUsers(tenantCode, accountCodes, event, data = {}) {
|
|
72
|
+
const accounts = Array.from(new Set(accountCodes.filter(a => a)));
|
|
73
|
+
const results = await Promise.allSettled(accounts.map(a => this.sendToUser(tenantCode, a, event, data)));
|
|
74
|
+
let count = 0;
|
|
75
|
+
const failed = [];
|
|
76
|
+
results.forEach((r, i) => {
|
|
77
|
+
if (r.status === 'fulfilled') {
|
|
78
|
+
count += r.value?.count ?? 0;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
failed.push({ accountCode: accounts[i], error: r.reason });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return { count, failed };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 按标签推送;标签必须属于 tenantCode 的租户
|
|
88
|
+
*/
|
|
89
|
+
async sendToTag(tenantCode, tag, event, data = {}) {
|
|
90
|
+
const info = SseTags.parse(tag);
|
|
91
|
+
if (!info || info.tenantCode !== tenantCode) {
|
|
92
|
+
throw new Error(`SSE 标签 ${tag} 无效或不属于租户 ${tenantCode}`);
|
|
93
|
+
}
|
|
94
|
+
return this.post(`/notification/tag/${encodeURIComponent(event)}`, data ?? {}, {
|
|
95
|
+
params: { tag },
|
|
96
|
+
headers: { 'tenant-code': tenantCode }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=SseServiceApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SseServiceApi.js","sourceRoot":"","sources":["../src/SseServiceApi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SseTagInfo } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* SSE 标签:`{tenantCode}|{U|A|T}|{标识}`,与 sse-service 的格式一致。
|
|
4
|
+
* 连接按标签接收推送;按标签推送时 sse-service 要求标签属于调用时传入的租户。
|
|
5
|
+
*/
|
|
6
|
+
declare const SseTags: {
|
|
7
|
+
/** 用户标签:推送给该用户的所有连接(如 omni-portal) */
|
|
8
|
+
user: (tenantCode: string, accountCode: string) => string;
|
|
9
|
+
/** 应用标签:推送给某个应用(如大屏、移动端)的连接 */
|
|
10
|
+
app: (tenantCode: string, appCode: string) => string;
|
|
11
|
+
/** 主题标签:推送给订阅某个主题的连接 */
|
|
12
|
+
topic: (tenantCode: string, topic: string) => string;
|
|
13
|
+
/** 解析标签,格式不对时返回 null */
|
|
14
|
+
parse: (tag: string) => SseTagInfo | null;
|
|
15
|
+
};
|
|
16
|
+
export default SseTags;
|
|
17
|
+
//# sourceMappingURL=SseTags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SseTags.d.ts","sourceRoot":"","sources":["../src/SseTags.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAa,MAAM,YAAY,CAAC;AAYvD;;;GAGG;AACH,QAAA,MAAM,OAAO;IACT,sCAAsC;uBACnB,MAAM,eAAe,MAAM,KAAG,MAAM;IACvD,+BAA+B;sBACb,MAAM,WAAW,MAAM,KAAG,MAAM;IAClD,wBAAwB;wBACJ,MAAM,SAAS,MAAM,KAAG,MAAM;IAClD,wBAAwB;iBACX,MAAM,KAAG,UAAU,GAAG,IAAI;CAI1C,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/dist/SseTags.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const TAG_PATTERN = /^([^|\s]+)\|([UAT])\|([^|\s]+)$/;
|
|
2
|
+
const build = (tenantCode, type, id) => {
|
|
3
|
+
const tag = `${tenantCode}|${type}|${id}`;
|
|
4
|
+
if (!TAG_PATTERN.test(tag)) {
|
|
5
|
+
throw new Error(`无效的 SSE 标签:${tag}(租户编码和标识不能为空,也不能包含空格或 "|")`);
|
|
6
|
+
}
|
|
7
|
+
return tag;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* SSE 标签:`{tenantCode}|{U|A|T}|{标识}`,与 sse-service 的格式一致。
|
|
11
|
+
* 连接按标签接收推送;按标签推送时 sse-service 要求标签属于调用时传入的租户。
|
|
12
|
+
*/
|
|
13
|
+
const SseTags = {
|
|
14
|
+
/** 用户标签:推送给该用户的所有连接(如 omni-portal) */
|
|
15
|
+
user: (tenantCode, accountCode) => build(tenantCode, 'U', accountCode),
|
|
16
|
+
/** 应用标签:推送给某个应用(如大屏、移动端)的连接 */
|
|
17
|
+
app: (tenantCode, appCode) => build(tenantCode, 'A', appCode),
|
|
18
|
+
/** 主题标签:推送给订阅某个主题的连接 */
|
|
19
|
+
topic: (tenantCode, topic) => build(tenantCode, 'T', topic),
|
|
20
|
+
/** 解析标签,格式不对时返回 null */
|
|
21
|
+
parse: (tag) => {
|
|
22
|
+
const m = TAG_PATTERN.exec(tag ?? '');
|
|
23
|
+
return m ? { tenantCode: m[1], type: m[2], id: m[3] } : null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
export default SseTags;
|
|
27
|
+
//# sourceMappingURL=SseTags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SseTags.js","sourceRoot":"","sources":["../src/SseTags.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,iCAAiC,CAAC;AAEtD,MAAM,KAAK,GAAG,CAAC,UAAkB,EAAE,IAAgB,EAAE,EAAU,EAAU,EAAE;IACvE,MAAM,GAAG,GAAG,GAAG,UAAU,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;IAC1C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,4BAA4B,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,GAAG;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,UAAkB,EAAE,WAAmB,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,WAAW,CAAC;IAC9F,+BAA+B;IAC/B,GAAG,EAAE,CAAC,UAAkB,EAAE,OAAe,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,OAAO,CAAC;IACrF,wBAAwB;IACxB,KAAK,EAAE,CAAC,UAAkB,EAAE,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,KAAK,CAAC;IACnF,wBAAwB;IACxB,KAAK,EAAE,CAAC,GAAW,EAAqB,EAAE;QACtC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,CAAC;CACJ,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as SseServiceApi } from './SseServiceApi.js';
|
|
2
|
+
export { default } from './SseServiceApi.js';
|
|
3
|
+
export { default as SseTags } from './SseTags.js';
|
|
4
|
+
export { CommonServiceApi, ServiceApiError } from '@omni-gate/sdk-core';
|
|
5
|
+
export type { ErrorResponse, BaseApiConfigOptions } from '@omni-gate/sdk-core';
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export { AppError, UnauthenticatedError, IllegalParameterError, TimeoutError, ServiceUnavailableError } from '@omni-gate/sdk-core';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,IAAI,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAC,OAAO,IAAI,OAAO,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,gBAAgB,EAAE,eAAe,EAAC,MAAM,qBAAqB,CAAC;AACtE,YAAY,EAAC,aAAa,EAAE,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAC7E,cAAc,YAAY,CAAC;AAG3B,OAAO,EACH,QAAQ,EACR,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,uBAAuB,EAC1B,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as SseServiceApi } from './SseServiceApi.js';
|
|
2
|
+
export { default } from './SseServiceApi.js';
|
|
3
|
+
export { default as SseTags } from './SseTags.js';
|
|
4
|
+
export { CommonServiceApi, ServiceApiError } from '@omni-gate/sdk-core';
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
// 异常类统一从 sdk-core 重导出
|
|
7
|
+
export { AppError, UnauthenticatedError, IllegalParameterError, TimeoutError, ServiceUnavailableError } from '@omni-gate/sdk-core';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,IAAI,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAC,OAAO,IAAI,OAAO,EAAC,MAAM,cAAc,CAAC;AAChD,OAAO,EAAC,gBAAgB,EAAE,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAEtE,cAAc,YAAY,CAAC;AAE3B,sBAAsB;AACtB,OAAO,EACH,QAAQ,EACR,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,uBAAuB,EAC1B,MAAM,qBAAqB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BaseApiConfigOptions } from '@omni-gate/sdk-core';
|
|
2
|
+
/**
|
|
3
|
+
* 连接配置:portal(sse-service 内部地址,如 http://omni-sse/webservice)、timeout 等,见 @omni-gate/sdk-core
|
|
4
|
+
*/
|
|
5
|
+
export type SseApiConfigOptions = BaseApiConfigOptions;
|
|
6
|
+
/** 标签类型:U-用户、A-应用、T-主题 */
|
|
7
|
+
export type SseTagType = 'U' | 'A' | 'T';
|
|
8
|
+
/** 解析后的标签 */
|
|
9
|
+
export interface SseTagInfo {
|
|
10
|
+
tenantCode: string;
|
|
11
|
+
type: SseTagType;
|
|
12
|
+
id: string;
|
|
13
|
+
}
|
|
14
|
+
/** 推送结果 */
|
|
15
|
+
export interface SsePushResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
/** 成功写出的连接数;目标不在线时为 0 */
|
|
18
|
+
count: number;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AAEvD,0BAA0B;AAC1B,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzC,aAAa;AACb,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACd;AAED,WAAW;AACX,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omni-gate/sse-sdk-api",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Backend SDK for the OmniGate SSE service: issue connection tickets and push events to users, apps or topics",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README_CN.md"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"omnigate",
|
|
21
|
+
"sse",
|
|
22
|
+
"server-sent-events",
|
|
23
|
+
"push",
|
|
24
|
+
"sdk",
|
|
25
|
+
"typescript"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@omni-gate/sdk-core": "^0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.8.6",
|
|
33
|
+
"typescript": "^5.7.2"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.19.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"clean": "rm -rf ./dist",
|
|
40
|
+
"prebuild": "npm run clean",
|
|
41
|
+
"build": "tsc -p tsconfig.json",
|
|
42
|
+
"test:modules": "node ./scripts/verify-modules.mjs",
|
|
43
|
+
"publish:public": "pnpm publish --access public"
|
|
44
|
+
}
|
|
6
45
|
}
|