@droponair/sdk-js 0.25.0 → 0.26.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 +59 -15
- package/README.md +46 -7
- package/dist/attachment/attachment-client.d.ts +1 -1
- package/dist/attachment/attachment-client.js +1 -1
- package/dist/core/messaging-client.d.ts +12 -3
- package/dist/core/messaging-client.js +38 -10
- package/dist/core/types.d.ts +59 -4
- package/dist/index.js +3 -3
- package/dist/transport/auto-select.d.ts +1 -1
- package/dist/transport/auto-select.js +1 -1
- package/dist/transport/protobuf-codec.d.ts +25 -1
- package/dist/transport/protobuf-codec.js +25 -3
- package/dist/transport/sse-transport.d.ts +1 -1
- package/dist/transport/sse-transport.js +1 -1
- package/dist/transport/webtransport-transport.d.ts +3 -3
- package/dist/transport/webtransport-transport.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Push payloads on outgoing messages.** `sendMessage` and `sendGroupMessage` accept
|
|
8
|
+
`options.push`, and `defaultPushPayload` on the client config attaches one to every
|
|
9
|
+
message that does not carry its own. Setting the default once is the recommended
|
|
10
|
+
route: passing a payload per send means forgetting it once stops push with no error
|
|
11
|
+
to notice.
|
|
12
|
+
- **`PushDelivery` chooses how a push behaves** on the recipient's device, and the
|
|
13
|
+
platform translates it per transport rather than deciding for you:
|
|
14
|
+
- `alert` shows title and body as supplied. Suits apps sending cleartext.
|
|
15
|
+
- `mutable` shows them as a placeholder and lets your app replace the text before it
|
|
16
|
+
appears, which is how an end-to-end encrypted app puts real content on a lock
|
|
17
|
+
screen without the relay seeing it. On iOS this is also the reliable way to be
|
|
18
|
+
woken, because the system throttles silent pushes.
|
|
19
|
+
- `silent` wakes the app and shows nothing.
|
|
20
|
+
- `voip` is the call invite path, superseding the `voip` boolean, which is still honoured.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
|
|
24
|
+
- Push previously could not be triggered at all: no SDK exposed a way to attach a
|
|
25
|
+
payload, and the server only pushes when the sender supplies one.
|
|
26
|
+
- Never put message content in `title` or `body`. Every field is readable by the relay,
|
|
27
|
+
which is why it can act on them, and putting plaintext there leaks it through the one
|
|
28
|
+
path the platform promises never sees it.
|
|
29
|
+
- Additive and backward compatible. Messages sent without a payload behave exactly as
|
|
30
|
+
before, and an absent or unrecognised delivery mode is treated as `alert`.
|
|
31
|
+
|
|
32
|
+
## 0.25.1
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Documentation only, no behaviour or API change. Comments and notes that shipped in the
|
|
37
|
+
package described the platform's own internals rather than the integration surface: some
|
|
38
|
+
named components a consumer never talks to, and some carried planning tags that mean nothing
|
|
39
|
+
outside our own backlog. They are rewritten in terms of what the code does and what the
|
|
40
|
+
platform offers.
|
|
41
|
+
- Where availability used to be described as a specific plan flag, it now points at the pricing
|
|
42
|
+
page and the app's subscription, which are the two places that stay correct as plans change.
|
|
43
|
+
- `PROTOCOL_VERSION` references are untouched. That is the contract a consumer integrates
|
|
44
|
+
against and it remains the way to tell what a relay supports.
|
|
45
|
+
- Em dashes are gone from the shipped text, per the house style. Each was judged in place
|
|
46
|
+
rather than swapped mechanically, so clauses read as sentences rather than comma splices.
|
|
47
|
+
|
|
3
48
|
## 0.25.0
|
|
4
49
|
|
|
5
50
|
### Added
|
|
@@ -43,7 +88,7 @@
|
|
|
43
88
|
|
|
44
89
|
## 0.24.2
|
|
45
90
|
|
|
46
|
-
- Fix: E2EE group message decryption failed because the decrypt AAD bound `recipientId` to the group id instead of the receiving user's own id (which is what the sender encrypts against). Now matches the sender and the other SDKs. Requires
|
|
91
|
+
- Fix: E2EE group message decryption failed because the decrypt AAD bound `recipientId` to the group id instead of the receiving user's own id (which is what the sender encrypts against). Now matches the sender and the other SDKs. Requires a relay that preserves the sender timestamp on group notifications.
|
|
47
92
|
|
|
48
93
|
## 0.24.1
|
|
49
94
|
|
|
@@ -221,7 +266,7 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
221
266
|
|
|
222
267
|
### Changed
|
|
223
268
|
|
|
224
|
-
- **`revokeAttachment` accepts an `AttachmentRef`.** Calling `revokeAttachment(ref)` now also revokes the linked preview thumbnail (`ref.thumbnailAttachmentId`) in the same call. `revokeAttachment(attachmentId)` with a plain id string is unchanged and revokes only that one attachment. Additive
|
|
269
|
+
- **`revokeAttachment` accepts an `AttachmentRef`.** Calling `revokeAttachment(ref)` now also revokes the linked preview thumbnail (`ref.thumbnailAttachmentId`) in the same call. `revokeAttachment(attachmentId)` with a plain id string is unchanged and revokes only that one attachment. Additive, so no breaking change.
|
|
225
270
|
|
|
226
271
|
---
|
|
227
272
|
|
|
@@ -230,13 +275,13 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
230
275
|
### Added
|
|
231
276
|
|
|
232
277
|
- **Group read receipts.** When a group has `readReceiptsVisibleToGroup` enabled, a member's `markRead(messageId, groupId)` is fanned to every other member, not just the reader's own devices. The inbound `ReadReceiptEvent` now carries `fromUserId` so you know which member read the message. New `client.updateGroup(groupId, { name?, readReceiptsVisibleToGroup? })` controls the per-group flag (it is a group-level choice; off by default). Broadcasts stay own-device only by design.
|
|
233
|
-
- **Attachment revoke.** New `client.revokeAttachment(attachmentId)
|
|
234
|
-
- **Attachment preview thumbnails.** `prepareAttachmentAndUpload` accepts an optional `thumbnail` (and `thumbnailMimeType`). Entirely optional
|
|
278
|
+
- **Attachment revoke.** New `client.revokeAttachment(attachmentId)`. The platform stops issuing download URLs for it and recipients with a live connection get an `ATTACHMENT_REVOKED` event via `onEvent` (metadata = attachmentId). Bytes already downloaded cannot be recalled.
|
|
279
|
+
- **Attachment preview thumbnails.** `prepareAttachmentAndUpload` accepts an optional `thumbnail` (and `thumbnailMimeType`). Entirely optional: if provided, the SDK uploads it as a separate attachment encrypted the same way and links it via `AttachmentRef.thumbnailAttachmentId`; download it like any attachment. If omitted, there is simply no thumbnail.
|
|
235
280
|
|
|
236
281
|
### Notes
|
|
237
282
|
|
|
238
283
|
- Download authorization for GROUP attachments is now checked against *current* group membership: a user removed from a group can no longer download its attachments even if they were a recipient at upload time.
|
|
239
|
-
- No PROTOCOL_VERSION change
|
|
284
|
+
- No PROTOCOL_VERSION change: additive proto fields (`SyncFrame.fromUserId`, `AttachmentRef.thumbnailAttachmentId`) and an additive event type.
|
|
240
285
|
|
|
241
286
|
---
|
|
242
287
|
|
|
@@ -244,14 +289,14 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
244
289
|
|
|
245
290
|
### Added
|
|
246
291
|
|
|
247
|
-
- **Notification clear sync.** `client.clearNotification(conversationId)` tells the user's other devices a conversation's notifications were dismissed; `client.onNotificationCleared(cb)` listens for it. Always available (no opt-out)
|
|
292
|
+
- **Notification clear sync.** `client.clearNotification(conversationId)` tells the user's other devices a conversation's notifications were dismissed; `client.onNotificationCleared(cb)` listens for it. Always available (no opt-out), since it is plain own-device hygiene.
|
|
248
293
|
- **Draft sync.** `client.syncDraft(conversationId, draftText)` pushes a conversation draft to the user's other devices; `client.onDraftSync(cb)` listens for it. **Opt-in:** the app owner enables it in the dashboard, and the draft text crosses the relay in cleartext (fanned out, never stored). When disabled the server drops the frame.
|
|
249
294
|
- New exported types: `NotificationClearEvent` / `NotificationClearCallback`, `DraftSyncEvent` / `DraftSyncCallback`.
|
|
250
295
|
|
|
251
296
|
### Notes
|
|
252
297
|
|
|
253
298
|
- Both ride the existing `SyncFrame` wire type (own-device fan-out only, never delivered to a different user). No PROTOCOL_VERSION change.
|
|
254
|
-
- Your app decides when to call `clearNotification()` / `syncDraft()
|
|
299
|
+
- Your app decides when to call `clearNotification()` / `syncDraft()`. The platform never infers dismissal or tracks drafts.
|
|
255
300
|
|
|
256
301
|
---
|
|
257
302
|
|
|
@@ -259,13 +304,13 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
259
304
|
|
|
260
305
|
### Added
|
|
261
306
|
|
|
262
|
-
- **Cross-device read receipts.** New `client.markRead(messageId, conversationId?)` reports a message as read; the receipt is relayed to the user's *other* devices (never to the message's sender). New `client.onReadReceipt(callback)` listens for receipts reported by the user's other devices
|
|
307
|
+
- **Cross-device read receipts.** New `client.markRead(messageId, conversationId?)` reports a message as read; the receipt is relayed to the user's *other* devices (never to the message's sender). New `client.onReadReceipt(callback)` listens for receipts reported by the user's other devices. Use it to clear unread state your app maintains.
|
|
263
308
|
- New exported `ReadReceiptEvent` type and `ReadReceiptCallback`.
|
|
264
309
|
- **PROTOCOL_VERSION bumped to 6.** Additive only: new `SyncFrame` wire type. Existing 0.10.x clients keep working against the new server.
|
|
265
310
|
|
|
266
311
|
### Notes
|
|
267
312
|
|
|
268
|
-
- The platform is deliberately unopinionated: **your app decides when `markRead()` is called
|
|
313
|
+
- The platform is deliberately unopinionated: **your app decides when `markRead()` is called**. The server never infers read state, and unread counts stay client-side. The app owner can disable read receipts entirely from the dashboard, in which case the server silently drops the frame.
|
|
269
314
|
- This release is own-device sync only: the *sender* of a message is not told when a recipient read it. Sender-side read notifications are a separate future capability.
|
|
270
315
|
|
|
271
316
|
---
|
|
@@ -274,7 +319,7 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
274
319
|
|
|
275
320
|
### Added
|
|
276
321
|
|
|
277
|
-
- **Device trust.** New `client.listMyDevices()` returns the current user's registered devices; `client.revokeMyDevice(deviceId)` revokes one of them. A revoked device's live session is closed immediately and all future connections from it are denied (revoke is permanent
|
|
322
|
+
- **Device trust.** New `client.listMyDevices()` returns the current user's registered devices; `client.revokeMyDevice(deviceId)` revokes one of them. A revoked device's live session is closed immediately and all future connections from it are denied (revoke is permanent, and re-registering yields a fresh deviceId).
|
|
278
323
|
- **`DEVICE_REVOKED` event.** When this device is revoked (from another of the user's devices, or by the app owner), the SDK stops its reconnect loop, closes the socket, and surfaces a `DEVICE_REVOKED` event through `onEvent` so the app can clear local key storage and show a re-register prompt.
|
|
279
324
|
- New exported `DeviceInfo` type.
|
|
280
325
|
|
|
@@ -326,7 +371,7 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
326
371
|
### Notes
|
|
327
372
|
|
|
328
373
|
- Wire protocol unchanged (PROTOCOL_VERSION stays 4). The proto schema was already correct; only the JS hand-rolled codec was out of sync.
|
|
329
|
-
- `protoFieldNumbers` for the group types are now documented
|
|
374
|
+
- `protoFieldNumbers` for the group types are now documented alongside the shared protocol test vectors, for cross-platform reference.
|
|
330
375
|
|
|
331
376
|
---
|
|
332
377
|
|
|
@@ -395,9 +440,8 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
395
440
|
`getDisplayMedia()` (web) / `MediaProjection` (Android) / `ReplayKit` (iOS)
|
|
396
441
|
in the app, add the track to the existing peer connection, then call
|
|
397
442
|
`startScreenShare(...)` to notify the peer.
|
|
398
|
-
-
|
|
399
|
-
|
|
400
|
-
side, on the customer's backend).
|
|
443
|
+
- Availability depends on the app's plan; see the pricing page or the app's
|
|
444
|
+
subscription in the dashboard.
|
|
401
445
|
|
|
402
446
|
---
|
|
403
447
|
|
|
@@ -468,7 +512,7 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
468
512
|
### Added
|
|
469
513
|
- **SDK versioning infrastructure:** `SDK_VERSION`, `PROTOCOL_VERSION`, and `PAYLOAD_FORMAT_VERSION` exported from `src/version.ts`
|
|
470
514
|
- **Version telemetry:** `X-SDK-Version` header sent on token exchange; `sdkVersion` and `protocolVersion` query params on WebSocket handshake
|
|
471
|
-
- **Server info endpoint:**
|
|
515
|
+
- **Server info endpoint:** the platform now exposes `GET /api/info` (no auth) returning `protocolVersion`, `minSdkVersion`, and supported `features`
|
|
472
516
|
- **Multi-device E2EE:** Per-device encrypted payloads (`devicePayloads[]` in Envelope proto), sender encrypts once per recipient device + self-sync to own other devices
|
|
473
517
|
- **Self-sync:** Sent messages are encrypted for the sender's other devices so all devices see the conversation in real time
|
|
474
518
|
- **Legacy fallback:** When recipient has no device keys, SDK falls back to single `encryptedPayload` path for backward compat with older clients
|
package/README.md
CHANGED
|
@@ -154,7 +154,7 @@ unchanged, so peers still address the same identity and existing conversations k
|
|
|
154
154
|
|
|
155
155
|
### Cross-device read receipts
|
|
156
156
|
|
|
157
|
-
Available since SDK `0.11.0`. **Your app decides when a message is read
|
|
157
|
+
Available since SDK `0.11.0`. **Your app decides when a message is read**, and the platform never infers it. Call `markRead()` at that moment; the receipt syncs to the user's *other* devices so they can clear their unread UI. It is not sent to the message's sender. The app owner can switch read receipts off entirely from the dashboard.
|
|
158
158
|
|
|
159
159
|
```typescript
|
|
160
160
|
// When your UI decides the message has been read
|
|
@@ -162,11 +162,11 @@ client.markRead(messageId, peerUserId);
|
|
|
162
162
|
|
|
163
163
|
// On the user's other devices
|
|
164
164
|
client.onReadReceipt(e => {
|
|
165
|
-
// e.messageId was read elsewhere
|
|
165
|
+
// e.messageId was read elsewhere, so clear your unread state for it
|
|
166
166
|
});
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
**Group read receipts.** Since SDK `0.13.0`, a group can opt into sharing read receipts with every member. Call `updateGroup(groupId, { readReceiptsVisibleToGroup: true })` (owner/admin); after that, a member's `markRead()` on a group message is broadcast to every other member, and `onReadReceipt` fires with `e.fromUserId` set to the member who read it. It is **opt-in per group
|
|
169
|
+
**Group read receipts.** Since SDK `0.13.0`, a group can opt into sharing read receipts with every member. Call `updateGroup(groupId, { readReceiptsVisibleToGroup: true })` (owner/admin); after that, a member's `markRead()` on a group message is broadcast to every other member, and `onReadReceipt` fires with `e.fromUserId` set to the member who read it. It is **opt-in per group**. The platform never enables it for you, and it has no effect unless read receipts are also enabled for your app.
|
|
170
170
|
|
|
171
171
|
```typescript
|
|
172
172
|
await client.updateGroup(groupId, { readReceiptsVisibleToGroup: true });
|
|
@@ -178,7 +178,7 @@ client.onReadReceipt(e => {
|
|
|
178
178
|
|
|
179
179
|
### Notification clear & draft sync
|
|
180
180
|
|
|
181
|
-
Available since SDK `0.12.0`, both own-device only. `clearNotification()` tells your user's other devices a conversation's notifications were dismissed
|
|
181
|
+
Available since SDK `0.12.0`, both own-device only. `clearNotification()` tells your user's other devices a conversation's notifications were dismissed, and is always available. `syncDraft()` pushes a draft so the user can keep typing on another device, and is **opt-in** (the app owner enables it in the dashboard) and the draft text crosses the relay in cleartext.
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
184
|
client.clearNotification(conversationId);
|
|
@@ -190,7 +190,7 @@ client.onDraftSync(e => { /* pre-fill composer with e.draftText */ });
|
|
|
190
190
|
|
|
191
191
|
### Device trust
|
|
192
192
|
|
|
193
|
-
Available since SDK `0.10.0`. Any device that completes a connection is implicitly trusted. `listMyDevices()` powers a "Your devices" screen; `revokeMyDevice()` cuts a device off immediately. When the *current* device is revoked, the SDK stops reconnecting and emits a `DEVICE_REVOKED` event via `onEvent
|
|
193
|
+
Available since SDK `0.10.0`. Any device that completes a connection is implicitly trusted. `listMyDevices()` powers a "Your devices" screen; `revokeMyDevice()` cuts a device off immediately. When the *current* device is revoked, the SDK stops reconnecting and emits a `DEVICE_REVOKED` event via `onEvent`. Listen for it to clear local key storage and prompt re-registration.
|
|
194
194
|
|
|
195
195
|
```typescript
|
|
196
196
|
const devices = await client.listMyDevices();
|
|
@@ -224,6 +224,45 @@ await client.registerPushToken({ platform: 'WEB_PUSH', token: JSON.stringify(sub
|
|
|
224
224
|
await client.unregisterPushToken({ platform: 'APNS' });
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
#### Attaching a payload
|
|
228
|
+
|
|
229
|
+
A push is only sent when the sender attaches a payload, so set a default once and
|
|
230
|
+
every message carries it. Passing one on each send also works, but forgetting once
|
|
231
|
+
stops push with no error to notice.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const client = await initialize({
|
|
235
|
+
appId, publicApiKey, getUserJwt,
|
|
236
|
+
defaultPushPayload: { title: 'New message', delivery: 'mutable' },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Override for a single message
|
|
240
|
+
await client.sendMessage(userId, text, {
|
|
241
|
+
push: { title: 'New photo', delivery: 'mutable', threadId: conversationId },
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Choosing how the push behaves
|
|
246
|
+
|
|
247
|
+
`delivery` says what the recipient's device should do. The platform translates it
|
|
248
|
+
per transport, so you pick what suits your app rather than adopting somebody else's
|
|
249
|
+
model.
|
|
250
|
+
|
|
251
|
+
| `delivery` | What happens |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `'alert'` | Title and body are shown exactly as supplied. Suits apps sending cleartext. |
|
|
254
|
+
| `'mutable'` | Shown as a placeholder, then your app replaces the text before it appears. This is how an end-to-end encrypted app shows real content without the relay seeing it. On iOS it needs a notification service extension; on Android it arrives as a data message. |
|
|
255
|
+
| `'silent'` | The app is woken and nothing is shown. **iOS throttles these and does not guarantee delivery**, so use `'mutable'` when being woken matters. |
|
|
256
|
+
| `'voip'` | Call invite: PushKit on iOS, high priority data on Android. Requires a registered VoIP token. |
|
|
257
|
+
|
|
258
|
+
Omit `delivery` and it defaults to `'alert'`.
|
|
259
|
+
|
|
260
|
+
**Never put message content in `title` or `body`.** Every field in the payload is
|
|
261
|
+
readable by the relay, which is exactly why it can act on them, so putting plaintext
|
|
262
|
+
there leaks it through the one path that otherwise never sees it. Send a placeholder
|
|
263
|
+
and use `'mutable'` to replace it on the device.
|
|
264
|
+
|
|
265
|
+
|
|
227
266
|
E2EE invariant: the push body is sender-supplied cleartext metadata only (e.g. "1 new message from Alice"), never the encrypted message contents. The recipient SDK decrypts the real message after the push wakes the device and the WebSocket reconnects.
|
|
228
267
|
|
|
229
268
|
### Message Edit & Delete
|
|
@@ -479,7 +518,7 @@ await wt.connect();
|
|
|
479
518
|
await wt.sendEnvelope(envelopeBytes);
|
|
480
519
|
```
|
|
481
520
|
|
|
482
|
-
Uses the browser-native `WebTransport` API; opens a single bidirectional stream and rides raw bytes both ways. Browser support: Chromium 97+ (stable), Firefox 125+ (stable), Safari Technology Preview only, Node 22+ behind experimental flags. The
|
|
521
|
+
Uses the browser-native `WebTransport` API; opens a single bidirectional stream and rides raw bytes both ways. Browser support: Chromium 97+ (stable), Firefox 125+ (stable), Safari Technology Preview only, Node 22+ behind experimental flags. The endpoint advertises `transports: ["webtransport"]` only where the lane is available; check `GET /api/info.transports` and `features.transport_webtransport` to detect it. Bring your own protobuf decoder. WebSocket remains the default; this primitive is purely opt-in.
|
|
483
522
|
|
|
484
523
|
### Transport auto-select
|
|
485
524
|
|
|
@@ -500,7 +539,7 @@ Foundation for an `init({ transport: 'auto' })` shortcut once full `MessagingCli
|
|
|
500
539
|
|
|
501
540
|
### Call Recording
|
|
502
541
|
|
|
503
|
-
Available since SDK `0.16.0`. The SDK **signals** recording state on a group or room call; your app does the actual media capture (`MediaRecorder`) and uploads the file to your own storage
|
|
542
|
+
Available since SDK `0.16.0`. The SDK **signals** recording state on a group or room call; your app does the actual media capture (`MediaRecorder`) and uploads the file to your own storage. The platform never holds the media. The recording signal is broadcast to every participant (including anyone who joins later); that transparency is enforced server-side.
|
|
504
543
|
|
|
505
544
|
| Method | Description |
|
|
506
545
|
|--------|-------------|
|
|
@@ -44,7 +44,7 @@ export declare class AttachmentClient {
|
|
|
44
44
|
*/
|
|
45
45
|
prepareAttachmentAndUpload(input: Uint8Array, opts: PrepareAttachmentOptions): Promise<AttachmentRef>;
|
|
46
46
|
/**
|
|
47
|
-
* Revoke an attachment you sent
|
|
47
|
+
* Revoke an attachment you sent. After revoke the platform
|
|
48
48
|
* refuses to issue further download URLs and recipients with a live
|
|
49
49
|
* connection get an ATTACHMENT_REVOKED event. Bytes a recipient already
|
|
50
50
|
* downloaded cannot be recalled.
|
|
@@ -147,7 +147,7 @@ class AttachmentClient {
|
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
|
-
* Revoke an attachment you sent
|
|
150
|
+
* Revoke an attachment you sent. After revoke the platform
|
|
151
151
|
* refuses to issue further download URLs and recipients with a live
|
|
152
152
|
* connection get an ATTACHMENT_REVOKED event. Bytes a recipient already
|
|
153
153
|
* downloaded cannot be recalled.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CryptoService } from '../crypto/crypto-service';
|
|
2
2
|
import { SessionManager } from './session-manager';
|
|
3
|
-
import { BroadcastCallback, CallEventCallback, DropOnAirClient, EventCallback, CreateRoomOptions, GroupCallEventCallback, GroupInfo, GroupMessageCallback, Room, UpdateRoomOptions, SfuToken, SfuRecording, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials, DeviceInfo, ReadReceiptCallback, NotificationClearCallback, DraftSyncCallback, KeyCustody } from './types';
|
|
3
|
+
import { BroadcastCallback, CallEventCallback, DropOnAirClient, EventCallback, CreateRoomOptions, GroupCallEventCallback, GroupInfo, GroupMessageCallback, Room, UpdateRoomOptions, SfuToken, SfuRecording, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials, DeviceInfo, ReadReceiptCallback, NotificationClearCallback, DraftSyncCallback, KeyCustody, PushPayload } from './types';
|
|
4
4
|
import { AttachmentRef, CreateUploadSessionOptions, DownloadedAttachment, PrepareAttachmentOptions, UploadSession } from '../attachment/attachment-types';
|
|
5
5
|
export declare class MessagingClient implements DropOnAirClient {
|
|
6
6
|
private readonly options;
|
|
@@ -11,6 +11,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
11
11
|
private readonly httpUrl;
|
|
12
12
|
private readonly tokenExchangeEndpoint;
|
|
13
13
|
private readonly keyDirectoryEndpoint;
|
|
14
|
+
private readonly defaultPushPayload?;
|
|
14
15
|
private readonly fetchFn;
|
|
15
16
|
private readonly codec;
|
|
16
17
|
private attachmentClient;
|
|
@@ -29,6 +30,12 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
29
30
|
private visibilityChangeHandler;
|
|
30
31
|
private readonly autoAckIncomingMessages;
|
|
31
32
|
private static readonly DEVICE_KEYS_CACHE_TTL_MS;
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the payload for a send: what the caller passed, otherwise the
|
|
35
|
+
* client default, otherwise none at all. Returns undefined when there is
|
|
36
|
+
* nothing to send, so no push is requested rather than an empty one.
|
|
37
|
+
*/
|
|
38
|
+
private resolvePushPayload;
|
|
32
39
|
private readonly deviceKeysCache;
|
|
33
40
|
private readonly callListeners;
|
|
34
41
|
/** Pending startCall resolver, only one outgoing call can be in-flight at a time. */
|
|
@@ -39,7 +46,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
39
46
|
/** Pending startGroupCall resolver. */
|
|
40
47
|
private pendingGroupInviteResolve;
|
|
41
48
|
private pendingGroupInviteReject;
|
|
42
|
-
/** Pending joinRoom resolver
|
|
49
|
+
/** Pending joinRoom resolver, keyed by roomId. */
|
|
43
50
|
private readonly pendingRoomJoins;
|
|
44
51
|
private reconnectDelayMs;
|
|
45
52
|
/** Schedule a single reconnect, replacing any pending one (never stack timers). */
|
|
@@ -112,6 +119,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
112
119
|
sendMessage(toUserId: string, plaintextMessage: string, options?: {
|
|
113
120
|
attachments?: AttachmentRef[];
|
|
114
121
|
clientMessageId?: string;
|
|
122
|
+
push?: PushPayload;
|
|
115
123
|
}): Promise<{
|
|
116
124
|
messageId: string;
|
|
117
125
|
}>;
|
|
@@ -238,7 +246,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
238
246
|
getGroup(groupId: string): Promise<GroupInfo>;
|
|
239
247
|
/**
|
|
240
248
|
* Update a group. Any omitted field is left unchanged. `readReceiptsVisibleToGroup`
|
|
241
|
-
*
|
|
249
|
+
* controls whether members' group read receipts are visible to
|
|
242
250
|
* the whole group or stay own-device only.
|
|
243
251
|
*/
|
|
244
252
|
updateGroup(groupId: string, update: {
|
|
@@ -282,6 +290,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
282
290
|
*/
|
|
283
291
|
sendGroupMessage(groupId: string, plaintext: string, memberUserIds: string[], options?: {
|
|
284
292
|
attachments?: AttachmentRef[];
|
|
293
|
+
push?: PushPayload;
|
|
285
294
|
}): Promise<{
|
|
286
295
|
messageId: string;
|
|
287
296
|
}>;
|
|
@@ -11,6 +11,30 @@ const version_1 = require("../version");
|
|
|
11
11
|
const attachment_client_1 = require("../attachment/attachment-client");
|
|
12
12
|
const STORAGE_DEVICE_ID = 'droponair.device.id.v1';
|
|
13
13
|
class MessagingClient {
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the payload for a send: what the caller passed, otherwise the
|
|
16
|
+
* client default, otherwise none at all. Returns undefined when there is
|
|
17
|
+
* nothing to send, so no push is requested rather than an empty one.
|
|
18
|
+
*/
|
|
19
|
+
resolvePushPayload(override) {
|
|
20
|
+
const payload = override ?? this.defaultPushPayload;
|
|
21
|
+
if (!payload)
|
|
22
|
+
return undefined;
|
|
23
|
+
const deliveryCodes = { alert: 0, mutable: 1, silent: 2, voip: 3 };
|
|
24
|
+
const delivery = deliveryCodes[payload.delivery ?? 'alert'] ?? 0;
|
|
25
|
+
return {
|
|
26
|
+
title: payload.title ?? '',
|
|
27
|
+
body: payload.body ?? '',
|
|
28
|
+
badge: payload.badge,
|
|
29
|
+
sound: payload.sound,
|
|
30
|
+
category: payload.category,
|
|
31
|
+
threadId: payload.threadId,
|
|
32
|
+
customJson: payload.customJson,
|
|
33
|
+
delivery,
|
|
34
|
+
// The server still reads the older boolean, so keep it consistent.
|
|
35
|
+
voip: delivery === 3 ? true : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
14
38
|
reconnectDelayMs() {
|
|
15
39
|
// Exponential backoff with jitter: ~2s, 4s, 8s, 16s, 32s, 60s (capped), +/-20%.
|
|
16
40
|
// Jitter avoids many clients reconnecting in lockstep after a server blip.
|
|
@@ -246,7 +270,7 @@ class MessagingClient {
|
|
|
246
270
|
/** Pending startGroupCall resolver. */
|
|
247
271
|
this.pendingGroupInviteResolve = null;
|
|
248
272
|
this.pendingGroupInviteReject = null;
|
|
249
|
-
/** Pending joinRoom resolver
|
|
273
|
+
/** Pending joinRoom resolver, keyed by roomId. */
|
|
250
274
|
this.pendingRoomJoins = new Map();
|
|
251
275
|
this.messageListeners = new Set();
|
|
252
276
|
this.eventListeners = new Set();
|
|
@@ -260,6 +284,7 @@ class MessagingClient {
|
|
|
260
284
|
this.httpUrl = options.messagingHttpUrl ?? 'https://sdk.droponair.com';
|
|
261
285
|
this.tokenExchangeEndpoint = options.tokenExchangeEndpoint ?? '/api/messaging/token-exchange';
|
|
262
286
|
this.keyDirectoryEndpoint = options.keyDirectoryEndpoint ?? '/api/messaging/keys';
|
|
287
|
+
this.defaultPushPayload = options.defaultPushPayload;
|
|
263
288
|
const providedFetch = options.fetchFn;
|
|
264
289
|
const globalFetch = typeof globalThis !== 'undefined' ? globalThis.fetch : undefined;
|
|
265
290
|
const resolvedFetch = providedFetch ?? globalFetch;
|
|
@@ -419,6 +444,7 @@ class MessagingClient {
|
|
|
419
444
|
if (options?.attachments && options.attachments.length > 0) {
|
|
420
445
|
envelope.attachments = options.attachments.map(a => this.attachmentClient.toWire(a));
|
|
421
446
|
}
|
|
447
|
+
envelope.pushPayload = this.resolvePushPayload(options?.push);
|
|
422
448
|
this.sendFrame(this.codec.encodeEnvelope(envelope));
|
|
423
449
|
}
|
|
424
450
|
else {
|
|
@@ -442,6 +468,7 @@ class MessagingClient {
|
|
|
442
468
|
if (options?.attachments && options.attachments.length > 0) {
|
|
443
469
|
envelope.attachments = options.attachments.map(a => this.attachmentClient.toWire(a));
|
|
444
470
|
}
|
|
471
|
+
envelope.pushPayload = this.resolvePushPayload(options?.push);
|
|
445
472
|
this.sendFrame(this.codec.encodeEnvelope(envelope));
|
|
446
473
|
}
|
|
447
474
|
return { messageId };
|
|
@@ -658,7 +685,7 @@ class MessagingClient {
|
|
|
658
685
|
this.transport.send(this.codec.encodePushRegistrationFrame(frame));
|
|
659
686
|
}
|
|
660
687
|
// ---------------------------------------------------------------------------
|
|
661
|
-
// Device trust (PROTOCOL_VERSION 5+
|
|
688
|
+
// Device trust (PROTOCOL_VERSION 5+)
|
|
662
689
|
// ---------------------------------------------------------------------------
|
|
663
690
|
/**
|
|
664
691
|
* List the current user's registered devices. Each device is implicitly
|
|
@@ -711,7 +738,7 @@ class MessagingClient {
|
|
|
711
738
|
}
|
|
712
739
|
}
|
|
713
740
|
// ---------------------------------------------------------------------------
|
|
714
|
-
// Cross-device read receipts (PROTOCOL_VERSION 6+
|
|
741
|
+
// Cross-device read receipts (PROTOCOL_VERSION 6+)
|
|
715
742
|
// ---------------------------------------------------------------------------
|
|
716
743
|
/**
|
|
717
744
|
* Mark a message as read. Call this when YOUR app decides a message has
|
|
@@ -950,7 +977,7 @@ class MessagingClient {
|
|
|
950
977
|
}
|
|
951
978
|
/**
|
|
952
979
|
* Update a group. Any omitted field is left unchanged. `readReceiptsVisibleToGroup`
|
|
953
|
-
*
|
|
980
|
+
* controls whether members' group read receipts are visible to
|
|
954
981
|
* the whole group or stay own-device only.
|
|
955
982
|
*/
|
|
956
983
|
async updateGroup(groupId, update) {
|
|
@@ -995,7 +1022,7 @@ class MessagingClient {
|
|
|
995
1022
|
throw new Error(`deleteGroup failed (HTTP ${res.status})`);
|
|
996
1023
|
}
|
|
997
1024
|
// ---------------------------------------------------------------------------
|
|
998
|
-
// Rooms (
|
|
1025
|
+
// Rooms (scheduled & persistent meeting rooms)
|
|
999
1026
|
// ---------------------------------------------------------------------------
|
|
1000
1027
|
async createRoom(options) {
|
|
1001
1028
|
const jwt = await this.getValidDropOnAirJwt(false);
|
|
@@ -1223,6 +1250,7 @@ class MessagingClient {
|
|
|
1223
1250
|
encryptionType: 0, // E2EE
|
|
1224
1251
|
memberPayloads,
|
|
1225
1252
|
};
|
|
1253
|
+
frame.pushPayload = this.resolvePushPayload(options?.push);
|
|
1226
1254
|
if (options?.attachments && options.attachments.length > 0) {
|
|
1227
1255
|
frame.attachments = options.attachments.map(a => this.attachmentClient.toWire(a));
|
|
1228
1256
|
}
|
|
@@ -1325,7 +1353,7 @@ class MessagingClient {
|
|
|
1325
1353
|
return () => this.groupCallListeners.delete(callback);
|
|
1326
1354
|
}
|
|
1327
1355
|
// Group call moderation + waiting room. The server validates host/co-host
|
|
1328
|
-
// authority and
|
|
1356
|
+
// authority and whether moderation is available on the app's plan; on denial it
|
|
1329
1357
|
// emits a LIMIT_REACHED event with reason="MODERATION_NOT_ENABLED".
|
|
1330
1358
|
transferHost(callId, groupId, newHostUserId) {
|
|
1331
1359
|
this.sendGroupCallFrame({
|
|
@@ -1399,7 +1427,7 @@ class MessagingClient {
|
|
|
1399
1427
|
payload: JSON.stringify({ userId }),
|
|
1400
1428
|
});
|
|
1401
1429
|
}
|
|
1402
|
-
// Live stage controls
|
|
1430
|
+
// Live stage controls. These act on a room call running in
|
|
1403
1431
|
// stage mode; pass the callId from joinRoom(). raiseHand / lowerHand and
|
|
1404
1432
|
// submitStageQuestion are open to any participant; promote / demote are
|
|
1405
1433
|
// host / co-host authority. Roles surface via GROUP_CALL_ROLE_CHANGED.
|
|
@@ -1428,7 +1456,7 @@ class MessagingClient {
|
|
|
1428
1456
|
submitStageQuestion(callId, text) {
|
|
1429
1457
|
this.sendGroupCallFrame({ type: 'GROUP_CALL_STAGE_QUESTION', callId, groupId: '', payload: text });
|
|
1430
1458
|
}
|
|
1431
|
-
// Call recording
|
|
1459
|
+
// Call recording. These SIGNAL recording state; the actual
|
|
1432
1460
|
// media capture + upload to your storage is your app's job (same split as
|
|
1433
1461
|
// screen sharing). The platform broadcasts the signal to every participant.
|
|
1434
1462
|
startRecording(callId) {
|
|
@@ -1589,7 +1617,7 @@ class MessagingClient {
|
|
|
1589
1617
|
plaintext = await this.cryptoService.decrypt(myPayload.encryptedPayload, sharedKey, {
|
|
1590
1618
|
messageId: notif.messageId,
|
|
1591
1619
|
senderId: notif.fromUserId,
|
|
1592
|
-
// AAD recipientId is the receiving user's own id
|
|
1620
|
+
// AAD recipientId is the receiving user's own id, and this MUST match the
|
|
1593
1621
|
// sender's encrypt AAD, which binds recipientId to the target member's
|
|
1594
1622
|
// userId (memberId). Using notif.groupId here (the previous value) never
|
|
1595
1623
|
// matched, so E2EE group messages failed GCM auth on decrypt. android /
|
|
@@ -1634,7 +1662,7 @@ class MessagingClient {
|
|
|
1634
1662
|
this.pendingGroupInviteReject = null;
|
|
1635
1663
|
reject(new Error(`GROUP_CALL_ALREADY_ACTIVE:${wire.payload ?? ''}`));
|
|
1636
1664
|
}
|
|
1637
|
-
// Resolve / reject a pending joinRoom()
|
|
1665
|
+
// Resolve / reject a pending joinRoom(). Room-call frames
|
|
1638
1666
|
// carry roomId; the join outcome is one of these four types.
|
|
1639
1667
|
if (wire.roomId) {
|
|
1640
1668
|
const pending = this.pendingRoomJoins.get(wire.roomId);
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a push should behave on the recipient's device.
|
|
3
|
+
*
|
|
4
|
+
* The platform translates this per transport; it does not decide for you. Pick
|
|
5
|
+
* the one that matches your app, not the one that matches ours.
|
|
6
|
+
*/
|
|
7
|
+
export type PushDelivery =
|
|
8
|
+
/** Show title and body exactly as supplied. Suits apps sending cleartext. */
|
|
9
|
+
'alert'
|
|
10
|
+
/**
|
|
11
|
+
* Show them as a placeholder, then replace the text on the device before it
|
|
12
|
+
* appears. This is how an end-to-end encrypted app shows real content on a
|
|
13
|
+
* lock screen without the relay ever seeing it. On iOS it is also the reliable
|
|
14
|
+
* way to be woken, because silent pushes are throttled by the system.
|
|
15
|
+
*/
|
|
16
|
+
| 'mutable'
|
|
17
|
+
/** Wake the app, show nothing. Delivery is not guaranteed on iOS. */
|
|
18
|
+
| 'silent'
|
|
19
|
+
/** Call invite. PushKit on iOS, high priority data on Android. */
|
|
20
|
+
| 'voip';
|
|
21
|
+
/**
|
|
22
|
+
* Cleartext metadata that wakes a recipient's device when they have no live
|
|
23
|
+
* connection.
|
|
24
|
+
*
|
|
25
|
+
* Every field here is readable by the relay, which is precisely why it can act on
|
|
26
|
+
* them. Never put message content in `title` or `body`: it would leak through the
|
|
27
|
+
* one path the platform promises never sees it. A placeholder such as
|
|
28
|
+
* "New message" is the point, and `mutable` lets the device replace it.
|
|
29
|
+
*/
|
|
30
|
+
export interface PushPayload {
|
|
31
|
+
title?: string;
|
|
32
|
+
body?: string;
|
|
33
|
+
badge?: number;
|
|
34
|
+
sound?: string;
|
|
35
|
+
/** iOS UNNotificationCategory, Android channel id. */
|
|
36
|
+
category?: string;
|
|
37
|
+
/** Groups related notifications together. */
|
|
38
|
+
threadId?: string;
|
|
39
|
+
/** App-defined JSON passed through to the provider payload untouched. */
|
|
40
|
+
customJson?: string;
|
|
41
|
+
/** Defaults to 'alert'. */
|
|
42
|
+
delivery?: PushDelivery;
|
|
43
|
+
}
|
|
1
44
|
export type DropOnAirEventType = 'SERVER_RECEIVED' | 'DELIVERED' | 'PROCESSED' | 'LIMIT_REACHED' | 'IMPERSONATION_DETECTED' | 'ERROR' | 'CONNECTED' | 'DISCONNECTED' | 'RECONNECTING';
|
|
2
45
|
export interface DropOnAirEvent {
|
|
3
46
|
type: DropOnAirEventType | string;
|
|
@@ -117,7 +160,7 @@ export interface GroupInfo {
|
|
|
117
160
|
members: GroupMemberInfo[];
|
|
118
161
|
createdAt: number;
|
|
119
162
|
/**
|
|
120
|
-
*
|
|
163
|
+
* When true, a member's group read receipt is fanned to all other
|
|
121
164
|
* members; when false it stays own-device only. Controlled via updateGroup().
|
|
122
165
|
*/
|
|
123
166
|
readReceiptsVisibleToGroup?: boolean;
|
|
@@ -140,7 +183,7 @@ export interface GroupCallEvent {
|
|
|
140
183
|
type: GroupCallEventType | string;
|
|
141
184
|
callId: string;
|
|
142
185
|
groupId: string;
|
|
143
|
-
/** Set instead of groupId when the event belongs to a room call
|
|
186
|
+
/** Set instead of groupId when the event belongs to a room call. */
|
|
144
187
|
roomId?: string;
|
|
145
188
|
targetUserId?: string;
|
|
146
189
|
payload?: string;
|
|
@@ -160,7 +203,7 @@ export interface RoomPolicy {
|
|
|
160
203
|
/** When true, the room flips to CLOSED once the last participant leaves the call. */
|
|
161
204
|
autoCloseWhenEmpty?: boolean;
|
|
162
205
|
/**
|
|
163
|
-
* When true, the room's live call runs in stage mode
|
|
206
|
+
* When true, the room's live call runs in stage mode: hosts
|
|
164
207
|
* join as speakers, everyone else as receive-only audience who can raise a
|
|
165
208
|
* hand to be promoted. Mesh-scale - suited to panels and small stages.
|
|
166
209
|
*/
|
|
@@ -191,7 +234,7 @@ export interface SfuToken {
|
|
|
191
234
|
expiresAt: number;
|
|
192
235
|
}
|
|
193
236
|
/**
|
|
194
|
-
* A server-side recording of an SFU-mode room
|
|
237
|
+
* A server-side recording of an SFU-mode room. The platform
|
|
195
238
|
* never holds the recorded bytes - LiveKit Egress uploads the finalized file
|
|
196
239
|
* directly to the destination configured in the panel under `destinationId`.
|
|
197
240
|
*/
|
|
@@ -311,6 +354,14 @@ export interface InitializeOptions {
|
|
|
311
354
|
messagingHttpUrl?: string;
|
|
312
355
|
tokenExchangeEndpoint?: string;
|
|
313
356
|
keyDirectoryEndpoint?: string;
|
|
357
|
+
/**
|
|
358
|
+
* Attached to every message that does not carry its own.
|
|
359
|
+
*
|
|
360
|
+
* Set this once and push works; the alternative is remembering to pass a
|
|
361
|
+
* payload on every send, where forgetting once means push silently stops with
|
|
362
|
+
* no error to notice. Omit it entirely and no push is ever requested.
|
|
363
|
+
*/
|
|
364
|
+
defaultPushPayload?: PushPayload;
|
|
314
365
|
fetchFn?: typeof fetch;
|
|
315
366
|
storage?: KeyStorageAdapter;
|
|
316
367
|
/**
|
|
@@ -395,6 +446,8 @@ export interface DropOnAirClient {
|
|
|
395
446
|
sendMessage(toUserId: string, plaintextMessage: string, options?: {
|
|
396
447
|
attachments?: import('../attachment/attachment-types').AttachmentRef[];
|
|
397
448
|
clientMessageId?: string;
|
|
449
|
+
/** Overrides `defaultPushPayload` for this message. */
|
|
450
|
+
push?: PushPayload;
|
|
398
451
|
}): Promise<{
|
|
399
452
|
messageId: string;
|
|
400
453
|
}>;
|
|
@@ -565,6 +618,8 @@ export interface DropOnAirClient {
|
|
|
565
618
|
*/
|
|
566
619
|
sendGroupMessage(groupId: string, plaintext: string, memberUserIds: string[], options?: {
|
|
567
620
|
attachments?: import('../attachment/attachment-types').AttachmentRef[];
|
|
621
|
+
/** Overrides `defaultPushPayload` for this message. */
|
|
622
|
+
push?: PushPayload;
|
|
568
623
|
}): Promise<{
|
|
569
624
|
messageId: string;
|
|
570
625
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -59,13 +59,13 @@ async function initialize(options) {
|
|
|
59
59
|
}
|
|
60
60
|
return client;
|
|
61
61
|
}
|
|
62
|
-
//
|
|
62
|
+
// HTTP fallback lane primitive for restrictive networks.
|
|
63
63
|
var sse_transport_1 = require("./transport/sse-transport");
|
|
64
64
|
Object.defineProperty(exports, "SseTransport", { enumerable: true, get: function () { return sse_transport_1.SseTransport; } });
|
|
65
|
-
//
|
|
65
|
+
// WebTransport (HTTP/3) lane primitive.
|
|
66
66
|
var webtransport_transport_1 = require("./transport/webtransport-transport");
|
|
67
67
|
Object.defineProperty(exports, "WebTransportTransport", { enumerable: true, get: function () { return webtransport_transport_1.WebTransportTransport; } });
|
|
68
|
-
//
|
|
68
|
+
// transport auto-select - intersects runtime + platform support.
|
|
69
69
|
var auto_select_1 = require("./transport/auto-select");
|
|
70
70
|
Object.defineProperty(exports, "selectTransport", { enumerable: true, get: function () { return auto_select_1.selectTransport; } });
|
|
71
71
|
/**
|
|
@@ -22,6 +22,26 @@ export interface WireAttachmentRef {
|
|
|
22
22
|
wrappedKeys?: WireDeviceWrappedKey[];
|
|
23
23
|
thumbnailAttachmentId?: string;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Cleartext metadata that wakes a recipient's device. Never message content: the
|
|
27
|
+
* relay can read every field here, which is the whole reason it can act on them.
|
|
28
|
+
*
|
|
29
|
+
* `delivery` says how the device should treat the push. The platform translates
|
|
30
|
+
* it per transport rather than deciding for the app.
|
|
31
|
+
*/
|
|
32
|
+
export interface WirePushPayload {
|
|
33
|
+
title?: string;
|
|
34
|
+
body?: string;
|
|
35
|
+
badge?: number;
|
|
36
|
+
sound?: string;
|
|
37
|
+
category?: string;
|
|
38
|
+
threadId?: string;
|
|
39
|
+
customJson?: string;
|
|
40
|
+
/** Superseded by `delivery: 3`; still honoured by the server. */
|
|
41
|
+
voip?: boolean;
|
|
42
|
+
/** 0 ALERT, 1 MUTABLE, 2 SILENT, 3 VOIP. */
|
|
43
|
+
delivery?: number;
|
|
44
|
+
}
|
|
25
45
|
export interface WireEnvelope {
|
|
26
46
|
messageId: string;
|
|
27
47
|
appId: string;
|
|
@@ -40,6 +60,8 @@ export interface WireEnvelope {
|
|
|
40
60
|
plaintextPayload?: string;
|
|
41
61
|
/** Zero or more attachment pointers (PROTOCOL_VERSION 4+). */
|
|
42
62
|
attachments?: WireAttachmentRef[];
|
|
63
|
+
/** Wakes the recipient when they have no live session (PROTOCOL_VERSION 5+). */
|
|
64
|
+
pushPayload?: WirePushPayload;
|
|
43
65
|
}
|
|
44
66
|
export interface WireAck {
|
|
45
67
|
messageId: string;
|
|
@@ -93,6 +115,8 @@ export interface WireGroupEnvelope {
|
|
|
93
115
|
plaintextPayload?: string;
|
|
94
116
|
memberPayloads: WireGroupMemberPayload[];
|
|
95
117
|
attachments?: WireAttachmentRef[];
|
|
118
|
+
/** Wakes offline members (PROTOCOL_VERSION 5+). */
|
|
119
|
+
pushPayload?: WirePushPayload;
|
|
96
120
|
}
|
|
97
121
|
export interface WireGroupMessageNotification {
|
|
98
122
|
messageId: string;
|
|
@@ -116,7 +140,7 @@ export interface WireGroupCallFrame {
|
|
|
116
140
|
groupId: string;
|
|
117
141
|
targetUserId?: string;
|
|
118
142
|
payload?: string;
|
|
119
|
-
/** Set instead of groupId when the multi-party call belongs to a Room
|
|
143
|
+
/** Set instead of groupId when the multi-party call belongs to a Room. */
|
|
120
144
|
roomId?: string;
|
|
121
145
|
}
|
|
122
146
|
/**
|
|
@@ -70,10 +70,29 @@ function buildAttachmentRefType() {
|
|
|
70
70
|
.add(new protobuf.Field('wrappedKeys', 7, 'DeviceWrappedKey', 'repeated'))
|
|
71
71
|
.add(new protobuf.Field('thumbnailAttachmentId', 8, 'string'));
|
|
72
72
|
}
|
|
73
|
+
// Same rule as AttachmentRef above: Envelope and GroupEnvelope each nest
|
|
74
|
+
// PushPayload, so each needs its own instance. Sharing one would re-parent it
|
|
75
|
+
// onto whichever added it last and orphan it from the other, which is how the
|
|
76
|
+
// AttachmentRef outage happened. The Delivery enum nests inside it and is
|
|
77
|
+
// rebuilt per call for the same reason.
|
|
78
|
+
function buildPushPayloadType() {
|
|
79
|
+
return new protobuf.Type('PushPayload')
|
|
80
|
+
.add(new protobuf.Enum('Delivery', { ALERT: 0, MUTABLE: 1, SILENT: 2, VOIP: 3 }))
|
|
81
|
+
.add(new protobuf.Field('title', 1, 'string'))
|
|
82
|
+
.add(new protobuf.Field('body', 2, 'string'))
|
|
83
|
+
.add(new protobuf.Field('badge', 3, 'int32'))
|
|
84
|
+
.add(new protobuf.Field('sound', 4, 'string'))
|
|
85
|
+
.add(new protobuf.Field('category', 5, 'string'))
|
|
86
|
+
.add(new protobuf.Field('threadId', 6, 'string'))
|
|
87
|
+
.add(new protobuf.Field('customJson', 7, 'string'))
|
|
88
|
+
.add(new protobuf.Field('voip', 8, 'bool'))
|
|
89
|
+
.add(new protobuf.Field('delivery', 9, 'Delivery'));
|
|
90
|
+
}
|
|
73
91
|
const EnvelopeType = new protobuf.Type('Envelope')
|
|
74
92
|
.add(EncryptionTypeEnum)
|
|
75
93
|
.add(DeviceEncryptedPayloadType) // nested type must be added first
|
|
76
94
|
.add(buildAttachmentRefType())
|
|
95
|
+
.add(buildPushPayloadType())
|
|
77
96
|
.add(new protobuf.Field('messageId', 1, 'string'))
|
|
78
97
|
.add(new protobuf.Field('appId', 2, 'string'))
|
|
79
98
|
.add(new protobuf.Field('fromUserId', 3, 'string'))
|
|
@@ -85,7 +104,8 @@ const EnvelopeType = new protobuf.Type('Envelope')
|
|
|
85
104
|
.add(new protobuf.Field('senderDeviceId', 9, 'string'))
|
|
86
105
|
.add(new protobuf.Field('encryptionType', 10, 'EncryptionType'))
|
|
87
106
|
.add(new protobuf.Field('plaintextPayload', 11, 'string'))
|
|
88
|
-
.add(new protobuf.Field('attachments', 12, 'AttachmentRef', 'repeated'))
|
|
107
|
+
.add(new protobuf.Field('attachments', 12, 'AttachmentRef', 'repeated'))
|
|
108
|
+
.add(new protobuf.Field('pushPayload', 13, 'PushPayload'));
|
|
89
109
|
const AckType = new protobuf.Type('Ack')
|
|
90
110
|
.add(new protobuf.Field('messageId', 1, 'string'))
|
|
91
111
|
.add(new protobuf.Field('type', 2, 'string'));
|
|
@@ -138,6 +158,7 @@ const GroupEnvelopeType = new protobuf.Type('GroupEnvelope')
|
|
|
138
158
|
.add(GroupEnvelopeEncryptionTypeEnum)
|
|
139
159
|
.add(GroupMemberPayloadType)
|
|
140
160
|
.add(buildAttachmentRefType())
|
|
161
|
+
.add(buildPushPayloadType())
|
|
141
162
|
.add(new protobuf.Field('messageId', 1, 'string'))
|
|
142
163
|
.add(new protobuf.Field('appId', 2, 'string'))
|
|
143
164
|
.add(new protobuf.Field('groupId', 3, 'string'))
|
|
@@ -149,7 +170,8 @@ const GroupEnvelopeType = new protobuf.Type('GroupEnvelope')
|
|
|
149
170
|
.add(new protobuf.Field('plaintextPayload', 9, 'string'))
|
|
150
171
|
.add(new protobuf.Field('memberPayloads', 10, 'GroupMemberPayload', 'repeated'))
|
|
151
172
|
// field 11 reserved upstream
|
|
152
|
-
.add(new protobuf.Field('attachments', 12, 'AttachmentRef', 'repeated'))
|
|
173
|
+
.add(new protobuf.Field('attachments', 12, 'AttachmentRef', 'repeated'))
|
|
174
|
+
.add(new protobuf.Field('pushPayload', 13, 'PushPayload'));
|
|
153
175
|
const GroupNotifEncryptionTypeEnum = new protobuf.Enum('EncryptionType', { E2EE: 0, CLEARTEXT: 1 });
|
|
154
176
|
const GroupNotifDeviceType = new protobuf.Type('DeviceEncryptedPayload')
|
|
155
177
|
.add(new protobuf.Field('deviceId', 1, 'string'))
|
|
@@ -404,7 +426,7 @@ class ProtobufCodec {
|
|
|
404
426
|
// Event.type is a SCREAMING_SNAKE_CASE enum string (e.g. LIMIT_REACHED).
|
|
405
427
|
// A GroupMessageNotification, BroadcastNotification, or Ack decoded here
|
|
406
428
|
// carries a UUID (messageId / broadcastId) in field 1, which never matches
|
|
407
|
-
// this shape
|
|
429
|
+
// this shape, so this reliably rejects those overlapping frame types and
|
|
408
430
|
// lets Event be probed before them.
|
|
409
431
|
if (!decoded.type || !/^[A-Z][A-Z0-9_]*$/.test(decoded.type)) {
|
|
410
432
|
return null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WebTransport (HTTP/3) lane primitive
|
|
2
|
+
* WebTransport (HTTP/3) lane primitive.
|
|
3
3
|
*
|
|
4
|
-
* Standalone client for the platform's WebTransport endpoint,
|
|
5
|
-
* the
|
|
4
|
+
* Standalone client for the platform's WebTransport endpoint, which carries
|
|
5
|
+
* the same session as the WebSocket lane.
|
|
6
6
|
* Browser-native `WebTransport` API; no polyfill ships with the SDK.
|
|
7
7
|
*
|
|
8
8
|
* Browser support as of 2026-05:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* WebTransport (HTTP/3) lane primitive
|
|
3
|
+
* WebTransport (HTTP/3) lane primitive.
|
|
4
4
|
*
|
|
5
|
-
* Standalone client for the platform's WebTransport endpoint,
|
|
6
|
-
* the
|
|
5
|
+
* Standalone client for the platform's WebTransport endpoint, which carries
|
|
6
|
+
* the same session as the WebSocket lane.
|
|
7
7
|
* Browser-native `WebTransport` API; no polyfill ships with the SDK.
|
|
8
8
|
*
|
|
9
9
|
* Browser support as of 2026-05:
|
package/dist/version.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* MINOR, additive feature (e.g. multi-device payloads, new call event type)
|
|
8
8
|
* PATCH, bug-fix / perf improvement with no wire or API change
|
|
9
9
|
*/
|
|
10
|
-
export declare const SDK_VERSION = "0.
|
|
10
|
+
export declare const SDK_VERSION = "0.26.0";
|
|
11
11
|
/**
|
|
12
12
|
* Binary encrypted-payload format version.
|
|
13
13
|
* Included as the first byte of every encrypted payload so receivers can
|
package/dist/version.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.PROTOCOL_VERSION = exports.PAYLOAD_FORMAT_VERSION = exports.SDK_VERSION
|
|
|
10
10
|
* MINOR, additive feature (e.g. multi-device payloads, new call event type)
|
|
11
11
|
* PATCH, bug-fix / perf improvement with no wire or API change
|
|
12
12
|
*/
|
|
13
|
-
exports.SDK_VERSION = '0.
|
|
13
|
+
exports.SDK_VERSION = '0.26.0';
|
|
14
14
|
/**
|
|
15
15
|
* Binary encrypted-payload format version.
|
|
16
16
|
* Included as the first byte of every encrypted payload so receivers can
|
package/package.json
CHANGED