@oxpulse/chat-sdk 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/CHANGELOG.md +19 -0
- package/LICENSE +661 -0
- package/README.md +142 -0
- package/dist/client.d.ts +98 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +639 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +31 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +79 -0
- package/dist/utils.js.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @oxpulse/chat-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript client for the OxPulse encrypted chat message log API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
> Publishing to npm is deferred to W8 (embed widget wave). Until then use a
|
|
8
|
+
> local path dependency or build from source.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# After W8 publish:
|
|
12
|
+
npm install @oxpulse/chat-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { SDKChatClient } from '@oxpulse/chat-sdk';
|
|
19
|
+
|
|
20
|
+
// JWT obtained from POST /api/sdk/tokens (server-side mint).
|
|
21
|
+
const client = new SDKChatClient({
|
|
22
|
+
baseUrl: 'https://chat.example.com',
|
|
23
|
+
jwt: 'raw-jwt-here', // do NOT include "Bearer " prefix
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Send a sealed (E2EE) message.
|
|
27
|
+
const { seq, msgId } = await client.send('room-123', {
|
|
28
|
+
senderUid: 'user-1',
|
|
29
|
+
sealed: ciphertextArrayBuffer,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// List historical messages.
|
|
33
|
+
const rows = await client.list('room-123', { afterSeq: 0, limit: 100 });
|
|
34
|
+
|
|
35
|
+
// Subscribe to live messages via SSE.
|
|
36
|
+
// Auth uses a short-lived ticket (RFC 6750 compliant — no JWT in URL).
|
|
37
|
+
const teardown = client.subscribe('room-123', {
|
|
38
|
+
onMessage: (row) => {
|
|
39
|
+
// row.sealed — ciphertext as ArrayBuffer; pass to your E2EE decrypt function.
|
|
40
|
+
console.log('new message seq=%d', row.seq);
|
|
41
|
+
},
|
|
42
|
+
onError: (err) => console.error('SSE error', err),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Stop subscribing.
|
|
46
|
+
teardown();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Error model
|
|
50
|
+
|
|
51
|
+
All failures throw `SDKChatError` with a typed `code` field:
|
|
52
|
+
|
|
53
|
+
| Code | When |
|
|
54
|
+
|------|------|
|
|
55
|
+
| `unauthorized` | 401 — invalid or expired JWT / ticket |
|
|
56
|
+
| `forbidden` | 403 — missing scope |
|
|
57
|
+
| `not_found` | 404 |
|
|
58
|
+
| `rate_limited` | 429 |
|
|
59
|
+
| `invalid_args` | 400–4xx (other than above) |
|
|
60
|
+
| `server_error` | 5xx |
|
|
61
|
+
| `network` | fetch/network-level failure |
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
AGPL-3.0-or-later. See root [LICENSE](../../LICENSE).
|
|
66
|
+
|
|
67
|
+
## Compression (optional)
|
|
68
|
+
|
|
69
|
+
By default all outgoing `POST /api/sdk/messages` requests use plain JSON
|
|
70
|
+
(`compression: 'none'`). Enable zstd compression to reduce payload size:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { SDKChatClient } from '@oxpulse/chat-sdk';
|
|
74
|
+
|
|
75
|
+
const client = new SDKChatClient({
|
|
76
|
+
baseUrl: 'https://chat.example.com',
|
|
77
|
+
jwt: 'jwt...',
|
|
78
|
+
compression: 'auto', // zstd dictless (0xC6) when payload ≥ 256 B
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Or use a shared dictionary for better compression on RU/FA/EN content:
|
|
82
|
+
const client2 = new SDKChatClient({
|
|
83
|
+
baseUrl: 'https://chat.example.com',
|
|
84
|
+
jwt: 'jwt...',
|
|
85
|
+
compression: 'dict',
|
|
86
|
+
dictBaseUrl: '/dicts', // served at /dicts/zstd-dict-ru-v1.zstd etc.
|
|
87
|
+
dictHint: 'zstd-dict-ru-v1',
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
See `@oxpulse/wire-codec` README for codec internals, dict management, and
|
|
92
|
+
the server-side frame format.
|
|
93
|
+
|
|
94
|
+
## CSP Compatibility
|
|
95
|
+
|
|
96
|
+
`@oxpulse/chat-sdk` is **strict-CSP-safe by design** — zero `eval()`, zero
|
|
97
|
+
`new Function()`. Tested against:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
script-src 'self' 'wasm-unsafe-eval' 'nonce-...' 'strict-dynamic'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
(no `'unsafe-eval'`, no `'unsafe-inline'`).
|
|
104
|
+
|
|
105
|
+
The SDK does NOT use zod internally — types are exported but runtime
|
|
106
|
+
validation is left to the integrator. This means no zod `Function()` probe
|
|
107
|
+
in the SDK bundle itself.
|
|
108
|
+
|
|
109
|
+
### If you bundle the SDK with eval-using libraries
|
|
110
|
+
|
|
111
|
+
If your own bundle pulls in libraries that call `Function()`, you'll hit CSP
|
|
112
|
+
violations unrelated to the SDK. Common culprits and mitigations:
|
|
113
|
+
|
|
114
|
+
- **zod v4 (default mode):** calls `new Function(...)` at schema-build time
|
|
115
|
+
to JIT-compile validators. Set:
|
|
116
|
+
```ts
|
|
117
|
+
// app boot script — BEFORE any module-level z.object() executes:
|
|
118
|
+
globalThis.__zod_globalConfig = { jitless: true };
|
|
119
|
+
```
|
|
120
|
+
Putting `zod.config({ jitless: true })` inside an instance file is too
|
|
121
|
+
late — module-level `z.object()` already ran during import.
|
|
122
|
+
|
|
123
|
+
- **cbor-x (default import):** JIT-compiles decoders via `new Function`.
|
|
124
|
+
Import from the no-eval subpath instead:
|
|
125
|
+
```ts
|
|
126
|
+
import { Encoder, decode } from 'cbor-x/index-no-eval';
|
|
127
|
+
```
|
|
128
|
+
`@oxpulse/wire-codec` (a transitive dep of this SDK) already uses
|
|
129
|
+
`cbor-x/index-no-eval` internally — you only need this fix if you import
|
|
130
|
+
`cbor-x` directly in your application code.
|
|
131
|
+
|
|
132
|
+
### Verifying your bundle
|
|
133
|
+
|
|
134
|
+
Scan your built output before shipping:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
grep -E 'new Function|eval\(' dist/**/*.js
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A build-time test that asserts the SDK bundle stays clean lives at
|
|
141
|
+
`packages/chat-sdk/src/__tests__/csp-cleanliness.test.ts`. Run it after
|
|
142
|
+
`npm run build` to catch regressions introduced by dependency updates.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDKChatClient — standalone npm package implementation.
|
|
3
|
+
*
|
|
4
|
+
* This is a standalone copy of the HTTP client for use by third-party
|
|
5
|
+
* integrations that import @oxpulse/chat-sdk directly. The SvelteKit
|
|
6
|
+
* front-end uses web/src/lib/api/sdkChat.ts (same semantics, same wire
|
|
7
|
+
* protocol) — kept separate to avoid pulling in SvelteKit build tooling.
|
|
8
|
+
*
|
|
9
|
+
* W4 skeleton: full implementation mirrors web/src/lib/api/sdkChat.ts.
|
|
10
|
+
* Publishing to npmjs.org is deferred to W8 (embed widget wave).
|
|
11
|
+
*/
|
|
12
|
+
import type { SDKChatClientOptions, SendArgs, ListArgs, MessageRow, SubscribeArgs, Room, Member, CreateRoomArgs, UpdateRoomArgs, PresenceUser } from './types.js';
|
|
13
|
+
export declare class SDKChatClient {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(opts: SDKChatClientOptions);
|
|
16
|
+
/**
|
|
17
|
+
* Encode a payload to the bytes that would be sent on the wire, without sending.
|
|
18
|
+
* Mirrors the encoding _encodeBody applies to POST /api/sdk/messages.
|
|
19
|
+
*/
|
|
20
|
+
encodeEnvelope(payload: unknown): Promise<string | Uint8Array>;
|
|
21
|
+
/**
|
|
22
|
+
* Decode bytes received from the server back to the JSON payload.
|
|
23
|
+
* Server responses are always plain JSON; this also handles wire-codec
|
|
24
|
+
* compressed frames for completeness (round-trip with encodeEnvelope).
|
|
25
|
+
*
|
|
26
|
+
* Note: #compression governs OUTGOING encoding only. A server may send a
|
|
27
|
+
* compressed frame (0xC6/0xC7) regardless of the client's compression setting.
|
|
28
|
+
* decodeEnvelope checks the first byte directly and initializes zstd if needed.
|
|
29
|
+
*/
|
|
30
|
+
decodeEnvelope(bytes: Uint8Array | string): Promise<unknown>;
|
|
31
|
+
send(roomId: string, args: SendArgs): Promise<{
|
|
32
|
+
seq: number;
|
|
33
|
+
msgId: string;
|
|
34
|
+
}>;
|
|
35
|
+
list(roomId: string, args?: ListArgs): Promise<MessageRow[]>;
|
|
36
|
+
subscribe(roomId: string, args: SubscribeArgs): () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Broadcast a typing indicator for roomId.
|
|
39
|
+
* Wire-contract: POST /api/sdk/rooms/:room_id/typing
|
|
40
|
+
* Body: { ttl_secs? }
|
|
41
|
+
*/
|
|
42
|
+
sendTyping(roomId: string, ttlSecs?: number): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Send a presence heartbeat for roomId.
|
|
45
|
+
* Wire-contract: POST /api/sdk/rooms/:room_id/presence
|
|
46
|
+
* Body: {}
|
|
47
|
+
*/
|
|
48
|
+
sendPresence(roomId: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Fetch presence snapshot for roomId.
|
|
51
|
+
* Wire-contract: GET /api/sdk/rooms/:room_id/presence
|
|
52
|
+
* Returns: Array<PresenceUser>
|
|
53
|
+
*/
|
|
54
|
+
getPresence(roomId: string): Promise<PresenceUser[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Mark messages up to seq as read (monotonic — cannot regress).
|
|
57
|
+
* Wire-contract: POST /api/sdk/rooms/:room_id/read?seq=N
|
|
58
|
+
*/
|
|
59
|
+
markRead(roomId: string, seq: number): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Create a room. Idempotent per roomId.
|
|
62
|
+
* Wire-contract: POST /api/sdk/rooms
|
|
63
|
+
*/
|
|
64
|
+
createRoom(args?: CreateRoomArgs): Promise<Room>;
|
|
65
|
+
/**
|
|
66
|
+
* Fetch room metadata + active members.
|
|
67
|
+
* Wire-contract: GET /api/sdk/rooms/:room_id
|
|
68
|
+
*/
|
|
69
|
+
getRoom(roomId: string): Promise<Room>;
|
|
70
|
+
/**
|
|
71
|
+
* Update room title / metadata. Owner-only.
|
|
72
|
+
* Wire-contract: PATCH /api/sdk/rooms/:room_id
|
|
73
|
+
*/
|
|
74
|
+
updateRoom(roomId: string, args: UpdateRoomArgs): Promise<Room>;
|
|
75
|
+
/**
|
|
76
|
+
* List active members of a room (fetches room and returns members).
|
|
77
|
+
*/
|
|
78
|
+
listMembers(roomId: string): Promise<Member[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Add a user to a room.
|
|
81
|
+
* Wire-contract: POST /api/sdk/rooms/:room_id/members
|
|
82
|
+
* Body: { user_id, role? }
|
|
83
|
+
*/
|
|
84
|
+
addMember(roomId: string, userId: string, role?: string): Promise<Member>;
|
|
85
|
+
/**
|
|
86
|
+
* Remove a user from a room (soft-delete).
|
|
87
|
+
* Wire-contract: DELETE /api/sdk/rooms/:room_id/members/:user_id
|
|
88
|
+
*/
|
|
89
|
+
removeMember(roomId: string, userId: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Fetch all reply messages in a thread.
|
|
92
|
+
* Wire-contract: GET /api/sdk/rooms/:room_id/threads/:root_msg_id
|
|
93
|
+
* Returns: Array<MessageRow> sorted by seq ascending (server-side ORDER BY seq).
|
|
94
|
+
* Requires scope: chat:read:<room_id>.
|
|
95
|
+
*/
|
|
96
|
+
getThread(roomId: string, rootMsgId: string): Promise<MessageRow[]>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,OAAO,KAAK,EACV,oBAAoB,EACpB,QAAQ,EACR,QAAQ,EACR,UAAU,EAEV,aAAa,EACb,IAAI,EACJ,MAAM,EACN,cAAc,EACd,cAAc,EACd,YAAY,EACb,MAAM,YAAY,CAAC;AAgEpB,qBAAa,aAAa;;gBAQZ,IAAI,EAAE,oBAAoB;IAoDtC;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC;IAIpE;;;;;;;;OAQG;IACG,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB5D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAuC7E,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,QAAa,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAwEtE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,IAAI;IAwI1D;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BjE;;;;OAIG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BjD;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA+B1D;;;OAGG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB1D;;;OAGG;IACG,UAAU,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C1D;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB5C;;;OAGG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BrE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKpD;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAoCjF;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBjE;;;;;OAKG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CA2C1E"}
|