@droponair/sdk-js 0.9.1 → 0.11.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 +30 -0
- package/README.md +33 -0
- package/dist/core/messaging-client.d.ts +44 -1
- package/dist/core/messaging-client.js +114 -0
- package/dist/core/types.d.ts +41 -0
- package/dist/index.d.ts +1 -1
- package/dist/transport/protobuf-codec.d.ts +15 -0
- package/dist/transport/protobuf-codec.js +28 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,36 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.11.0], 2026-05-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **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.
|
|
14
|
+
- New exported `ReadReceiptEvent` type and `ReadReceiptCallback`.
|
|
15
|
+
- **PROTOCOL_VERSION bumped to 6.** Additive only: new `SyncFrame` wire type. Existing 0.10.x clients keep working against the new server.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [0.10.0], 2026-05-20
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **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 — re-registering yields a fresh deviceId).
|
|
29
|
+
- **`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.
|
|
30
|
+
- New exported `DeviceInfo` type.
|
|
31
|
+
|
|
32
|
+
### Notes
|
|
33
|
+
|
|
34
|
+
- No wire-format change; PROTOCOL_VERSION stays at 5. The new surface is REST (`/v1/devices`) plus an additive event type.
|
|
35
|
+
- Any device that completes a connection is implicitly trusted. There is no explicit pairing/attestation step in this release.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
9
39
|
## [0.9.1], 2026-05-19
|
|
10
40
|
|
|
11
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -98,6 +98,39 @@ const client = await initialize(options);
|
|
|
98
98
|
| `ack(messageId)` | `Promise<void>` | Manually acknowledge a message |
|
|
99
99
|
| `registerPushToken({ platform, token, voipToken? })` | `Promise<void>` | Register this device for push notifications. `platform` is `'APNS'`, `'FCM'`, or `'WEB_PUSH'`. |
|
|
100
100
|
| `unregisterPushToken({ platform })` | `Promise<void>` | Unregister this device for push notifications (e.g. on logout). |
|
|
101
|
+
| `listMyDevices()` | `Promise<DeviceInfo[]>` | List the current user's registered devices. |
|
|
102
|
+
| `revokeMyDevice(deviceId)` | `Promise<DeviceInfo>` | Revoke one of the current user's devices. Permanent. |
|
|
103
|
+
| `markRead(messageId, conversationId?)` | `void` | Mark a message as read; relays a receipt to the user's other devices. |
|
|
104
|
+
| `onReadReceipt(callback)` | `() => void` | Listen for read receipts from the user's other devices. |
|
|
105
|
+
|
|
106
|
+
### Cross-device read receipts
|
|
107
|
+
|
|
108
|
+
Available since SDK `0.11.0`. **Your app decides when a message is read** — 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.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// When your UI decides the message has been read
|
|
112
|
+
client.markRead(messageId, peerUserId);
|
|
113
|
+
|
|
114
|
+
// On the user's other devices
|
|
115
|
+
client.onReadReceipt(e => {
|
|
116
|
+
// e.messageId was read elsewhere — clear your unread state for it
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Device trust
|
|
121
|
+
|
|
122
|
+
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.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const devices = await client.listMyDevices();
|
|
126
|
+
await client.revokeMyDevice('old-phone-device-id');
|
|
127
|
+
|
|
128
|
+
client.onEvent(e => {
|
|
129
|
+
if (e.type === 'DEVICE_REVOKED') {
|
|
130
|
+
// This device was revoked elsewhere. Clear local keys and show a re-register screen.
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
```
|
|
101
134
|
|
|
102
135
|
### Push notifications
|
|
103
136
|
|
|
@@ -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, GroupCallEventCallback, GroupInfo, GroupMessageCallback, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials } from './types';
|
|
3
|
+
import { BroadcastCallback, CallEventCallback, DropOnAirClient, EventCallback, GroupCallEventCallback, GroupInfo, GroupMessageCallback, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials, DeviceInfo, ReadReceiptCallback } 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;
|
|
@@ -71,6 +71,7 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
71
71
|
private readonly eventListeners;
|
|
72
72
|
private readonly broadcastListeners;
|
|
73
73
|
private readonly messageEditListeners;
|
|
74
|
+
private readonly readReceiptListeners;
|
|
74
75
|
private readonly messageDeleteListeners;
|
|
75
76
|
/** ------------------------------------------------------------------
|
|
76
77
|
* Lightweight structured logger. Only active when options.debug === true.
|
|
@@ -102,6 +103,12 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
102
103
|
onEvent(callback: EventCallback): () => void;
|
|
103
104
|
onMessageEdit(callback: MessageEditCallback): () => void;
|
|
104
105
|
onMessageDelete(callback: MessageDeleteCallback): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Register a listener for read receipts that this user's OTHER devices
|
|
108
|
+
* reported. Fires when another device of the same user calls markRead();
|
|
109
|
+
* use it to clear the unread state your app maintains for that message.
|
|
110
|
+
*/
|
|
111
|
+
onReadReceipt(callback: ReadReceiptCallback): () => void;
|
|
105
112
|
editMessage(originalMessageId: string, toUserId: string, newPlaintext: string): Promise<{
|
|
106
113
|
editId: string;
|
|
107
114
|
}>;
|
|
@@ -136,6 +143,42 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
136
143
|
unregisterPushToken(opts: {
|
|
137
144
|
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
138
145
|
}): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* List the current user's registered devices. Each device is implicitly
|
|
148
|
+
* trusted once it has completed a connection. Useful for a "Your devices"
|
|
149
|
+
* settings screen.
|
|
150
|
+
*/
|
|
151
|
+
listMyDevices(): Promise<DeviceInfo[]>;
|
|
152
|
+
/**
|
|
153
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
154
|
+
* WebSocket session (if any) is closed immediately and all future
|
|
155
|
+
* connection attempts from that device are denied. Revoke is permanent;
|
|
156
|
+
* the user must re-register the device (which yields a fresh deviceId).
|
|
157
|
+
*/
|
|
158
|
+
revokeMyDevice(deviceId: string): Promise<DeviceInfo>;
|
|
159
|
+
/**
|
|
160
|
+
* Handles an inbound DEVICE_REVOKED event: this device has been revoked
|
|
161
|
+
* (by the user from another device, or by the app owner). Stops the
|
|
162
|
+
* reconnect loop and closes the socket. The DEVICE_REVOKED event is also
|
|
163
|
+
* surfaced through onEvent so the app can clear local key storage and
|
|
164
|
+
* show a re-register prompt.
|
|
165
|
+
*/
|
|
166
|
+
private handleDeviceRevoked;
|
|
167
|
+
/**
|
|
168
|
+
* Mark a message as read. Call this when YOUR app decides a message has
|
|
169
|
+
* been read - the platform never infers read state for you. The receipt
|
|
170
|
+
* is relayed to this user's other devices so they can clear their unread
|
|
171
|
+
* UI for the same message; it is not delivered to the message's sender.
|
|
172
|
+
*
|
|
173
|
+
* If the app owner has disabled read receipts in the dashboard, the server
|
|
174
|
+
* silently drops the frame.
|
|
175
|
+
*
|
|
176
|
+
* @param messageId the message that was read
|
|
177
|
+
* @param conversationId optional peer userId / group id, echoed back to
|
|
178
|
+
* other devices so they can bucket the receipt
|
|
179
|
+
*/
|
|
180
|
+
markRead(messageId: string, conversationId?: string): void;
|
|
181
|
+
private handleIncomingSync;
|
|
139
182
|
sendCleartextMessage(toUserId: string, plaintext: string): Promise<{
|
|
140
183
|
messageId: string;
|
|
141
184
|
}>;
|
|
@@ -220,6 +220,7 @@ class MessagingClient {
|
|
|
220
220
|
this.eventListeners = new Set();
|
|
221
221
|
this.broadcastListeners = new Set();
|
|
222
222
|
this.messageEditListeners = new Set();
|
|
223
|
+
this.readReceiptListeners = new Set();
|
|
223
224
|
this.messageDeleteListeners = new Set();
|
|
224
225
|
this.wsUrl = options.messagingWsUrl ?? 'wss://sdk.droponair.com/ws';
|
|
225
226
|
this.httpUrl = options.messagingHttpUrl ?? 'https://sdk.droponair.com';
|
|
@@ -410,6 +411,15 @@ class MessagingClient {
|
|
|
410
411
|
this.messageDeleteListeners.add(callback);
|
|
411
412
|
return () => this.messageDeleteListeners.delete(callback);
|
|
412
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Register a listener for read receipts that this user's OTHER devices
|
|
416
|
+
* reported. Fires when another device of the same user calls markRead();
|
|
417
|
+
* use it to clear the unread state your app maintains for that message.
|
|
418
|
+
*/
|
|
419
|
+
onReadReceipt(callback) {
|
|
420
|
+
this.readReceiptListeners.add(callback);
|
|
421
|
+
return () => this.readReceiptListeners.delete(callback);
|
|
422
|
+
}
|
|
413
423
|
// ---------------------------------------------------------------------------
|
|
414
424
|
// Message edit and delete (PROTOCOL_VERSION 3+)
|
|
415
425
|
// ---------------------------------------------------------------------------
|
|
@@ -577,6 +587,102 @@ class MessagingClient {
|
|
|
577
587
|
this.ws.send(this.codec.encodePushRegistrationFrame(frame));
|
|
578
588
|
}
|
|
579
589
|
// ---------------------------------------------------------------------------
|
|
590
|
+
// Device trust (PROTOCOL_VERSION 5+ / Phase 2b)
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
/**
|
|
593
|
+
* List the current user's registered devices. Each device is implicitly
|
|
594
|
+
* trusted once it has completed a connection. Useful for a "Your devices"
|
|
595
|
+
* settings screen.
|
|
596
|
+
*/
|
|
597
|
+
async listMyDevices() {
|
|
598
|
+
const jwt = await this.getValidDropOnAirJwt(false);
|
|
599
|
+
const resp = await this.fetchFn(`${this.httpUrl}/v1/devices`, {
|
|
600
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
601
|
+
});
|
|
602
|
+
if (!resp.ok) {
|
|
603
|
+
throw new Error(`Failed to list devices: ${resp.status}`);
|
|
604
|
+
}
|
|
605
|
+
return (await resp.json());
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
609
|
+
* WebSocket session (if any) is closed immediately and all future
|
|
610
|
+
* connection attempts from that device are denied. Revoke is permanent;
|
|
611
|
+
* the user must re-register the device (which yields a fresh deviceId).
|
|
612
|
+
*/
|
|
613
|
+
async revokeMyDevice(deviceId) {
|
|
614
|
+
const jwt = await this.getValidDropOnAirJwt(false);
|
|
615
|
+
const resp = await this.fetchFn(`${this.httpUrl}/v1/devices/${encodeURIComponent(deviceId)}`, {
|
|
616
|
+
method: 'DELETE',
|
|
617
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
618
|
+
});
|
|
619
|
+
if (!resp.ok) {
|
|
620
|
+
throw new Error(`Failed to revoke device: ${resp.status}`);
|
|
621
|
+
}
|
|
622
|
+
return (await resp.json());
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Handles an inbound DEVICE_REVOKED event: this device has been revoked
|
|
626
|
+
* (by the user from another device, or by the app owner). Stops the
|
|
627
|
+
* reconnect loop and closes the socket. The DEVICE_REVOKED event is also
|
|
628
|
+
* surfaced through onEvent so the app can clear local key storage and
|
|
629
|
+
* show a re-register prompt.
|
|
630
|
+
*/
|
|
631
|
+
handleDeviceRevoked() {
|
|
632
|
+
this.shouldReconnect = false;
|
|
633
|
+
this.rateLimited = false;
|
|
634
|
+
if (this.reconnectTimer) {
|
|
635
|
+
clearTimeout(this.reconnectTimer);
|
|
636
|
+
this.reconnectTimer = null;
|
|
637
|
+
}
|
|
638
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
639
|
+
this.ws.close(4001, 'DEVICE_REVOKED');
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
// Cross-device read receipts (PROTOCOL_VERSION 6+ / Phase 2c)
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
/**
|
|
646
|
+
* Mark a message as read. Call this when YOUR app decides a message has
|
|
647
|
+
* been read - the platform never infers read state for you. The receipt
|
|
648
|
+
* is relayed to this user's other devices so they can clear their unread
|
|
649
|
+
* UI for the same message; it is not delivered to the message's sender.
|
|
650
|
+
*
|
|
651
|
+
* If the app owner has disabled read receipts in the dashboard, the server
|
|
652
|
+
* silently drops the frame.
|
|
653
|
+
*
|
|
654
|
+
* @param messageId the message that was read
|
|
655
|
+
* @param conversationId optional peer userId / group id, echoed back to
|
|
656
|
+
* other devices so they can bucket the receipt
|
|
657
|
+
*/
|
|
658
|
+
markRead(messageId, conversationId) {
|
|
659
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
660
|
+
throw new Error('DropOnAir websocket is not connected');
|
|
661
|
+
}
|
|
662
|
+
const frame = {
|
|
663
|
+
type: 'SYNC_READ_RECEIPT',
|
|
664
|
+
messageId,
|
|
665
|
+
conversationId: conversationId ?? '',
|
|
666
|
+
timestamp: Date.now(),
|
|
667
|
+
};
|
|
668
|
+
this.ws.send(this.codec.encodeSyncFrame(frame));
|
|
669
|
+
}
|
|
670
|
+
handleIncomingSync(frame) {
|
|
671
|
+
if (frame.type === 'SYNC_READ_RECEIPT') {
|
|
672
|
+
const event = {
|
|
673
|
+
messageId: frame.messageId,
|
|
674
|
+
conversationId: frame.conversationId || undefined,
|
|
675
|
+
timestamp: frame.timestamp,
|
|
676
|
+
};
|
|
677
|
+
for (const listener of this.readReceiptListeners) {
|
|
678
|
+
try {
|
|
679
|
+
listener(event);
|
|
680
|
+
}
|
|
681
|
+
catch { /* listener errors must not break the socket */ }
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// ---------------------------------------------------------------------------
|
|
580
686
|
// Cleartext messaging (no E2EE key exchange required)
|
|
581
687
|
// ---------------------------------------------------------------------------
|
|
582
688
|
async sendCleartextMessage(toUserId, plaintext) {
|
|
@@ -1272,6 +1378,10 @@ class MessagingClient {
|
|
|
1272
1378
|
if (frame.data.type === 'LIMIT_REACHED') {
|
|
1273
1379
|
this.rateLimited = true;
|
|
1274
1380
|
}
|
|
1381
|
+
if (frame.data.type === 'DEVICE_REVOKED') {
|
|
1382
|
+
this.log('device_revoked_received', { metadata: frame.data.metadata });
|
|
1383
|
+
this.handleDeviceRevoked();
|
|
1384
|
+
}
|
|
1275
1385
|
if (frame.data.type === 'ERROR' && frame.data.reason === 'JWT_EXPIRED') {
|
|
1276
1386
|
this.dropOnAirJwt = null;
|
|
1277
1387
|
this.currentUserId = null;
|
|
@@ -1332,6 +1442,10 @@ class MessagingClient {
|
|
|
1332
1442
|
this.handleIncomingMessageDelete(frame.data);
|
|
1333
1443
|
return;
|
|
1334
1444
|
}
|
|
1445
|
+
if (frame.kind === 'sync') {
|
|
1446
|
+
this.handleIncomingSync(frame.data);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1335
1449
|
if (frame.kind === 'envelope') {
|
|
1336
1450
|
await this.handleIncomingEnvelope(frame.data);
|
|
1337
1451
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -48,6 +48,18 @@ export interface MessageDeleteEvent {
|
|
|
48
48
|
}
|
|
49
49
|
export type MessageEditCallback = (event: MessageEditEvent) => void;
|
|
50
50
|
export type MessageDeleteCallback = (event: MessageDeleteEvent) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Delivered to the user's OTHER devices when one device calls markRead().
|
|
53
|
+
* Use it to clear whatever unread state your app keeps for that message.
|
|
54
|
+
* It is NOT delivered to the original sender of the message.
|
|
55
|
+
*/
|
|
56
|
+
export interface ReadReceiptEvent {
|
|
57
|
+
messageId: string;
|
|
58
|
+
/** Optional peer userId / group id, echoed from markRead() for bucketing. */
|
|
59
|
+
conversationId?: string;
|
|
60
|
+
timestamp: number;
|
|
61
|
+
}
|
|
62
|
+
export type ReadReceiptCallback = (event: ReadReceiptEvent) => void;
|
|
51
63
|
export interface BroadcastMessage {
|
|
52
64
|
broadcastId: string;
|
|
53
65
|
channelId: string;
|
|
@@ -132,6 +144,19 @@ export interface InitializeOptions {
|
|
|
132
144
|
*/
|
|
133
145
|
autoAckIncomingMessages?: boolean;
|
|
134
146
|
}
|
|
147
|
+
/** A registered device as returned by {@link DropOnAirClient.listMyDevices}. */
|
|
148
|
+
export interface DeviceInfo {
|
|
149
|
+
deviceId: string;
|
|
150
|
+
sdkVersion?: string;
|
|
151
|
+
protocolVersion?: string;
|
|
152
|
+
platform?: string;
|
|
153
|
+
/** ISO-8601 timestamps. */
|
|
154
|
+
firstSeenAt?: string;
|
|
155
|
+
lastSeenAt?: string;
|
|
156
|
+
/** Non-null once revoked. */
|
|
157
|
+
revokedAt?: string;
|
|
158
|
+
revokedBy?: 'END_USER' | 'APP_OWNER' | 'SYSTEM';
|
|
159
|
+
}
|
|
135
160
|
export interface DropOnAirClient {
|
|
136
161
|
connect(): Promise<void>;
|
|
137
162
|
disconnect(): void;
|
|
@@ -170,6 +195,14 @@ export interface DropOnAirClient {
|
|
|
170
195
|
onMessageEdit(callback: MessageEditCallback): () => void;
|
|
171
196
|
/** Register a listener for incoming message-delete notifications. */
|
|
172
197
|
onMessageDelete(callback: MessageDeleteCallback): () => void;
|
|
198
|
+
/**
|
|
199
|
+
* Mark a message as read. The integrating app decides when this is called;
|
|
200
|
+
* the platform never infers read state. The receipt is relayed to the
|
|
201
|
+
* user's other devices only, never to the message's sender.
|
|
202
|
+
*/
|
|
203
|
+
markRead(messageId: string, conversationId?: string): void;
|
|
204
|
+
/** Register a listener for read receipts reported by this user's other devices. */
|
|
205
|
+
onReadReceipt(callback: ReadReceiptCallback): () => void;
|
|
173
206
|
/**
|
|
174
207
|
* Register this device's push notification token. The platform delivers a
|
|
175
208
|
* push via APNs / FCM / Web Push when a sender attaches a pushPayload to
|
|
@@ -187,6 +220,14 @@ export interface DropOnAirClient {
|
|
|
187
220
|
unregisterPushToken(opts: {
|
|
188
221
|
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
189
222
|
}): Promise<void>;
|
|
223
|
+
/** List the current user's registered devices. */
|
|
224
|
+
listMyDevices(): Promise<DeviceInfo[]>;
|
|
225
|
+
/**
|
|
226
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
227
|
+
* session is closed and all future connections from it are denied.
|
|
228
|
+
* Revoke is permanent.
|
|
229
|
+
*/
|
|
230
|
+
revokeMyDevice(deviceId: string): Promise<DeviceInfo>;
|
|
190
231
|
/**
|
|
191
232
|
* Convenience: encrypts (E2EE), uploads to the customer's bucket, finalizes,
|
|
192
233
|
* and returns an AttachmentRef ready to pass into {@link sendMessage}.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InitializeOptions, DropOnAirClient } from './core/types';
|
|
2
2
|
export { SDK_VERSION, PROTOCOL_VERSION, PAYLOAD_FORMAT_VERSION } from './version';
|
|
3
3
|
export declare function initialize(options: InitializeOptions): Promise<DropOnAirClient>;
|
|
4
|
-
export type { InitializeOptions, DropOnAirClient, DropOnAirEvent, MessageCallback, EventCallback, DecryptedMessage, KeyStorageAdapter, CallEvent, CallEventType, CallEventCallback, TurnCredentials, BroadcastMessage, BroadcastCallback, GroupInfo, GroupMemberInfo, DecryptedGroupMessage, GroupMessageCallback, GroupCallEvent, GroupCallEventType, GroupCallEventCallback, MessageEditEvent, MessageEditCallback, MessageDeleteEvent, MessageDeleteCallback, } from './core/types';
|
|
4
|
+
export type { InitializeOptions, DropOnAirClient, DropOnAirEvent, MessageCallback, EventCallback, DecryptedMessage, KeyStorageAdapter, CallEvent, CallEventType, CallEventCallback, TurnCredentials, BroadcastMessage, BroadcastCallback, GroupInfo, GroupMemberInfo, DecryptedGroupMessage, GroupMessageCallback, GroupCallEvent, GroupCallEventType, GroupCallEventCallback, MessageEditEvent, MessageEditCallback, MessageDeleteEvent, MessageDeleteCallback, DeviceInfo, ReadReceiptEvent, ReadReceiptCallback, } from './core/types';
|
|
5
5
|
export type { AttachmentEncryptionType, AttachmentConversationType, AttachmentRef, DeviceWrappedKey, CreateUploadSessionOptions, UploadSession, PrepareAttachmentOptions, DownloadedAttachment, } from './attachment/attachment-types';
|
|
6
6
|
declare const _default: {
|
|
7
7
|
initialize: typeof initialize;
|
|
@@ -148,6 +148,16 @@ export interface WirePushRegistrationFrame {
|
|
|
148
148
|
voipToken?: string;
|
|
149
149
|
deviceId: string;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Cross-device state sync frame (PROTOCOL_VERSION 6+). Same shape sent
|
|
153
|
+
* client -> server and relayed server -> the user's other devices.
|
|
154
|
+
*/
|
|
155
|
+
export interface WireSyncFrame {
|
|
156
|
+
type: string;
|
|
157
|
+
messageId: string;
|
|
158
|
+
conversationId?: string;
|
|
159
|
+
timestamp: number;
|
|
160
|
+
}
|
|
151
161
|
/** Tombstone frame, scope FOR_EVERYONE or FOR_ME. */
|
|
152
162
|
export interface WireMessageDeleteFrame {
|
|
153
163
|
type: string;
|
|
@@ -190,6 +200,9 @@ export type InboundFrame = {
|
|
|
190
200
|
} | {
|
|
191
201
|
kind: 'messageDelete';
|
|
192
202
|
data: WireMessageDeleteFrame;
|
|
203
|
+
} | {
|
|
204
|
+
kind: 'sync';
|
|
205
|
+
data: WireSyncFrame;
|
|
193
206
|
};
|
|
194
207
|
export declare class ProtobufCodec {
|
|
195
208
|
encodeEnvelope(value: WireEnvelope): Uint8Array;
|
|
@@ -202,6 +215,7 @@ export declare class ProtobufCodec {
|
|
|
202
215
|
encodeMessageEditFrame(value: WireMessageEditFrame): Uint8Array;
|
|
203
216
|
encodeMessageDeleteFrame(value: WireMessageDeleteFrame): Uint8Array;
|
|
204
217
|
encodePushRegistrationFrame(value: WirePushRegistrationFrame): Uint8Array;
|
|
218
|
+
encodeSyncFrame(value: WireSyncFrame): Uint8Array;
|
|
205
219
|
decodeFrame(payload: Uint8Array): InboundFrame;
|
|
206
220
|
private tryDecodeEnvelope;
|
|
207
221
|
private tryDecodeAck;
|
|
@@ -211,6 +225,7 @@ export declare class ProtobufCodec {
|
|
|
211
225
|
private tryDecodeGroupMessageNotification;
|
|
212
226
|
private tryDecodeGroupAck;
|
|
213
227
|
private tryDecodeGroupCallFrame;
|
|
228
|
+
private tryDecodeSyncFrame;
|
|
214
229
|
private tryDecodeMessageEditFrame;
|
|
215
230
|
private tryDecodeMessageDeleteFrame;
|
|
216
231
|
}
|
|
@@ -217,6 +217,14 @@ const PushRegistrationFrameType = new protobuf.Type('PushRegistrationFrame')
|
|
|
217
217
|
.add(new protobuf.Field('token', 3, 'string'))
|
|
218
218
|
.add(new protobuf.Field('voipToken', 4, 'string'))
|
|
219
219
|
.add(new protobuf.Field('deviceId', 5, 'string'));
|
|
220
|
+
// Cross-device state sync frame (PROTOCOL_VERSION 6+). Same shape both
|
|
221
|
+
// directions: client -> server (request) and server -> the user's other
|
|
222
|
+
// devices (notification). Discriminator is `type` at field 1 ("SYNC_").
|
|
223
|
+
const SyncFrameType = new protobuf.Type('SyncFrame')
|
|
224
|
+
.add(new protobuf.Field('type', 1, 'string'))
|
|
225
|
+
.add(new protobuf.Field('messageId', 2, 'string'))
|
|
226
|
+
.add(new protobuf.Field('conversationId', 3, 'string'))
|
|
227
|
+
.add(new protobuf.Field('timestamp', 4, 'int64'));
|
|
220
228
|
class ProtobufCodec {
|
|
221
229
|
encodeEnvelope(value) {
|
|
222
230
|
return EnvelopeType.encode(value).finish();
|
|
@@ -248,6 +256,9 @@ class ProtobufCodec {
|
|
|
248
256
|
encodePushRegistrationFrame(value) {
|
|
249
257
|
return PushRegistrationFrameType.encode(value).finish();
|
|
250
258
|
}
|
|
259
|
+
encodeSyncFrame(value) {
|
|
260
|
+
return SyncFrameType.encode(value).finish();
|
|
261
|
+
}
|
|
251
262
|
decodeFrame(payload) {
|
|
252
263
|
// Edit and delete frames must be probed BEFORE Envelope, the type
|
|
253
264
|
// discriminator at field 1 ("MESSAGE_EDIT" / "MESSAGE_DELETE") would
|
|
@@ -260,6 +271,11 @@ class ProtobufCodec {
|
|
|
260
271
|
if (asDelete) {
|
|
261
272
|
return { kind: 'messageDelete', data: asDelete };
|
|
262
273
|
}
|
|
274
|
+
// SyncFrame: discriminator "SYNC_" at field 1, probed before Envelope.
|
|
275
|
+
const asSync = this.tryDecodeSyncFrame(payload);
|
|
276
|
+
if (asSync) {
|
|
277
|
+
return { kind: 'sync', data: asSync };
|
|
278
|
+
}
|
|
263
279
|
const asEnvelope = this.tryDecodeEnvelope(payload);
|
|
264
280
|
if (asEnvelope) {
|
|
265
281
|
return { kind: 'envelope', data: asEnvelope };
|
|
@@ -446,6 +462,18 @@ class ProtobufCodec {
|
|
|
446
462
|
return null;
|
|
447
463
|
}
|
|
448
464
|
}
|
|
465
|
+
tryDecodeSyncFrame(payload) {
|
|
466
|
+
try {
|
|
467
|
+
const decoded = SyncFrameType.decode(payload);
|
|
468
|
+
if (!decoded.type || !decoded.type.startsWith('SYNC_')) {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
return { ...decoded, timestamp: Number(decoded.timestamp) };
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
449
477
|
tryDecodeMessageEditFrame(payload) {
|
|
450
478
|
try {
|
|
451
479
|
const decoded = MessageEditFrameType.decode(payload);
|
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.11.0";
|
|
11
11
|
/**
|
|
12
12
|
* Binary encrypted-payload format version.
|
|
13
13
|
* Included as the first byte of every encrypted payload so receivers can
|
|
@@ -19,4 +19,4 @@ export declare const PAYLOAD_FORMAT_VERSION = 1;
|
|
|
19
19
|
* Increment when adding required proto fields or changing frame semantics.
|
|
20
20
|
* The server advertises its supported range via GET /api/info.
|
|
21
21
|
*/
|
|
22
|
-
export declare const PROTOCOL_VERSION =
|
|
22
|
+
export declare const PROTOCOL_VERSION = 6;
|
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.11.0';
|
|
14
14
|
/**
|
|
15
15
|
* Binary encrypted-payload format version.
|
|
16
16
|
* Included as the first byte of every encrypted payload so receivers can
|
|
@@ -22,4 +22,4 @@ exports.PAYLOAD_FORMAT_VERSION = 1;
|
|
|
22
22
|
* Increment when adding required proto fields or changing frame semantics.
|
|
23
23
|
* The server advertises its supported range via GET /api/info.
|
|
24
24
|
*/
|
|
25
|
-
exports.PROTOCOL_VERSION =
|
|
25
|
+
exports.PROTOCOL_VERSION = 6;
|