@droponair/sdk-js 0.11.0 → 0.12.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 +15 -0
- package/README.md +16 -0
- package/dist/core/messaging-client.d.ts +22 -1
- package/dist/core/messaging-client.js +75 -0
- package/dist/core/types.d.ts +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/transport/protobuf-codec.d.ts +1 -0
- package/dist/transport/protobuf-codec.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.12.0], 2026-05-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **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) — it is plain own-device hygiene.
|
|
14
|
+
- **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.
|
|
15
|
+
- New exported types: `NotificationClearEvent` / `NotificationClearCallback`, `DraftSyncEvent` / `DraftSyncCallback`.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- Both ride the existing `SyncFrame` wire type (own-device fan-out only, never delivered to a different user). No PROTOCOL_VERSION change.
|
|
20
|
+
- Your app decides when to call `clearNotification()` / `syncDraft()` — the platform never infers dismissal or tracks drafts.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
9
24
|
## [0.11.0], 2026-05-20
|
|
10
25
|
|
|
11
26
|
### Added
|
package/README.md
CHANGED
|
@@ -102,6 +102,10 @@ const client = await initialize(options);
|
|
|
102
102
|
| `revokeMyDevice(deviceId)` | `Promise<DeviceInfo>` | Revoke one of the current user's devices. Permanent. |
|
|
103
103
|
| `markRead(messageId, conversationId?)` | `void` | Mark a message as read; relays a receipt to the user's other devices. |
|
|
104
104
|
| `onReadReceipt(callback)` | `() => void` | Listen for read receipts from the user's other devices. |
|
|
105
|
+
| `clearNotification(conversationId)` | `void` | Tell the user's other devices a conversation's notifications were dismissed. |
|
|
106
|
+
| `onNotificationCleared(callback)` | `() => void` | Listen for notification-clear syncs from the user's other devices. |
|
|
107
|
+
| `syncDraft(conversationId, draftText)` | `void` | Push a conversation draft to the user's other devices (opt-in, cleartext). |
|
|
108
|
+
| `onDraftSync(callback)` | `() => void` | Listen for draft syncs from the user's other devices. |
|
|
105
109
|
|
|
106
110
|
### Cross-device read receipts
|
|
107
111
|
|
|
@@ -117,6 +121,18 @@ client.onReadReceipt(e => {
|
|
|
117
121
|
});
|
|
118
122
|
```
|
|
119
123
|
|
|
124
|
+
### Notification clear & draft sync
|
|
125
|
+
|
|
126
|
+
Available since SDK `0.12.0`, both own-device only. `clearNotification()` tells your user's other devices a conversation's notifications were dismissed — always available. `syncDraft()` pushes a draft so the user can keep typing on another device — **opt-in** (the app owner enables it in the dashboard) and the draft text crosses the relay in cleartext.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
client.clearNotification(conversationId);
|
|
130
|
+
client.onNotificationCleared(e => { /* clear badge for e.conversationId */ });
|
|
131
|
+
|
|
132
|
+
client.syncDraft(conversationId, composerText);
|
|
133
|
+
client.onDraftSync(e => { /* pre-fill composer with e.draftText */ });
|
|
134
|
+
```
|
|
135
|
+
|
|
120
136
|
### Device trust
|
|
121
137
|
|
|
122
138
|
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.
|
|
@@ -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, DeviceInfo, ReadReceiptCallback } from './types';
|
|
3
|
+
import { BroadcastCallback, CallEventCallback, DropOnAirClient, EventCallback, GroupCallEventCallback, GroupInfo, GroupMessageCallback, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials, DeviceInfo, ReadReceiptCallback, NotificationClearCallback, DraftSyncCallback } 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;
|
|
@@ -72,6 +72,8 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
72
72
|
private readonly broadcastListeners;
|
|
73
73
|
private readonly messageEditListeners;
|
|
74
74
|
private readonly readReceiptListeners;
|
|
75
|
+
private readonly notificationClearListeners;
|
|
76
|
+
private readonly draftSyncListeners;
|
|
75
77
|
private readonly messageDeleteListeners;
|
|
76
78
|
/** ------------------------------------------------------------------
|
|
77
79
|
* Lightweight structured logger. Only active when options.debug === true.
|
|
@@ -178,6 +180,25 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
178
180
|
* other devices so they can bucket the receipt
|
|
179
181
|
*/
|
|
180
182
|
markRead(messageId: string, conversationId?: string): void;
|
|
183
|
+
/**
|
|
184
|
+
* Tell this user's other devices that the notification(s) for a
|
|
185
|
+
* conversation have been dismissed - call this when YOUR app dismisses a
|
|
186
|
+
* notification or the user opens the conversation. The other devices clear
|
|
187
|
+
* the matching badge. The relay never decides what "dismissed" means.
|
|
188
|
+
*/
|
|
189
|
+
clearNotification(conversationId: string): void;
|
|
190
|
+
/**
|
|
191
|
+
* Push the current draft text for a conversation to this user's other
|
|
192
|
+
* devices so the user can continue typing on another device. Draft sync is
|
|
193
|
+
* opt-in: the app owner must enable it in the dashboard, and the draft text
|
|
194
|
+
* crosses the relay in cleartext (it is fanned out, never stored). If the
|
|
195
|
+
* feature is disabled the server silently drops the frame.
|
|
196
|
+
*/
|
|
197
|
+
syncDraft(conversationId: string, draftText: string): void;
|
|
198
|
+
/** Register a listener for notification-clear syncs from the user's other devices. */
|
|
199
|
+
onNotificationCleared(callback: NotificationClearCallback): () => void;
|
|
200
|
+
/** Register a listener for draft syncs from the user's other devices. */
|
|
201
|
+
onDraftSync(callback: DraftSyncCallback): () => void;
|
|
181
202
|
private handleIncomingSync;
|
|
182
203
|
sendCleartextMessage(toUserId: string, plaintext: string): Promise<{
|
|
183
204
|
messageId: string;
|
|
@@ -221,6 +221,8 @@ class MessagingClient {
|
|
|
221
221
|
this.broadcastListeners = new Set();
|
|
222
222
|
this.messageEditListeners = new Set();
|
|
223
223
|
this.readReceiptListeners = new Set();
|
|
224
|
+
this.notificationClearListeners = new Set();
|
|
225
|
+
this.draftSyncListeners = new Set();
|
|
224
226
|
this.messageDeleteListeners = new Set();
|
|
225
227
|
this.wsUrl = options.messagingWsUrl ?? 'wss://sdk.droponair.com/ws';
|
|
226
228
|
this.httpUrl = options.messagingHttpUrl ?? 'https://sdk.droponair.com';
|
|
@@ -667,6 +669,54 @@ class MessagingClient {
|
|
|
667
669
|
};
|
|
668
670
|
this.ws.send(this.codec.encodeSyncFrame(frame));
|
|
669
671
|
}
|
|
672
|
+
/**
|
|
673
|
+
* Tell this user's other devices that the notification(s) for a
|
|
674
|
+
* conversation have been dismissed - call this when YOUR app dismisses a
|
|
675
|
+
* notification or the user opens the conversation. The other devices clear
|
|
676
|
+
* the matching badge. The relay never decides what "dismissed" means.
|
|
677
|
+
*/
|
|
678
|
+
clearNotification(conversationId) {
|
|
679
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
680
|
+
throw new Error('DropOnAir websocket is not connected');
|
|
681
|
+
}
|
|
682
|
+
const frame = {
|
|
683
|
+
type: 'SYNC_CLEAR_NOTIFICATION',
|
|
684
|
+
messageId: '',
|
|
685
|
+
conversationId,
|
|
686
|
+
timestamp: Date.now(),
|
|
687
|
+
};
|
|
688
|
+
this.ws.send(this.codec.encodeSyncFrame(frame));
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Push the current draft text for a conversation to this user's other
|
|
692
|
+
* devices so the user can continue typing on another device. Draft sync is
|
|
693
|
+
* opt-in: the app owner must enable it in the dashboard, and the draft text
|
|
694
|
+
* crosses the relay in cleartext (it is fanned out, never stored). If the
|
|
695
|
+
* feature is disabled the server silently drops the frame.
|
|
696
|
+
*/
|
|
697
|
+
syncDraft(conversationId, draftText) {
|
|
698
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
699
|
+
throw new Error('DropOnAir websocket is not connected');
|
|
700
|
+
}
|
|
701
|
+
const frame = {
|
|
702
|
+
type: 'SYNC_DRAFT',
|
|
703
|
+
messageId: '',
|
|
704
|
+
conversationId,
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
payload: draftText,
|
|
707
|
+
};
|
|
708
|
+
this.ws.send(this.codec.encodeSyncFrame(frame));
|
|
709
|
+
}
|
|
710
|
+
/** Register a listener for notification-clear syncs from the user's other devices. */
|
|
711
|
+
onNotificationCleared(callback) {
|
|
712
|
+
this.notificationClearListeners.add(callback);
|
|
713
|
+
return () => this.notificationClearListeners.delete(callback);
|
|
714
|
+
}
|
|
715
|
+
/** Register a listener for draft syncs from the user's other devices. */
|
|
716
|
+
onDraftSync(callback) {
|
|
717
|
+
this.draftSyncListeners.add(callback);
|
|
718
|
+
return () => this.draftSyncListeners.delete(callback);
|
|
719
|
+
}
|
|
670
720
|
handleIncomingSync(frame) {
|
|
671
721
|
if (frame.type === 'SYNC_READ_RECEIPT') {
|
|
672
722
|
const event = {
|
|
@@ -681,6 +731,31 @@ class MessagingClient {
|
|
|
681
731
|
catch { /* listener errors must not break the socket */ }
|
|
682
732
|
}
|
|
683
733
|
}
|
|
734
|
+
else if (frame.type === 'SYNC_CLEAR_NOTIFICATION') {
|
|
735
|
+
const event = {
|
|
736
|
+
conversationId: frame.conversationId || '',
|
|
737
|
+
timestamp: frame.timestamp,
|
|
738
|
+
};
|
|
739
|
+
for (const listener of this.notificationClearListeners) {
|
|
740
|
+
try {
|
|
741
|
+
listener(event);
|
|
742
|
+
}
|
|
743
|
+
catch { /* listener errors must not break the socket */ }
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
else if (frame.type === 'SYNC_DRAFT') {
|
|
747
|
+
const event = {
|
|
748
|
+
conversationId: frame.conversationId || '',
|
|
749
|
+
draftText: frame.payload || '',
|
|
750
|
+
timestamp: frame.timestamp,
|
|
751
|
+
};
|
|
752
|
+
for (const listener of this.draftSyncListeners) {
|
|
753
|
+
try {
|
|
754
|
+
listener(event);
|
|
755
|
+
}
|
|
756
|
+
catch { /* listener errors must not break the socket */ }
|
|
757
|
+
}
|
|
758
|
+
}
|
|
684
759
|
}
|
|
685
760
|
// ---------------------------------------------------------------------------
|
|
686
761
|
// Cleartext messaging (no E2EE key exchange required)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -60,6 +60,26 @@ export interface ReadReceiptEvent {
|
|
|
60
60
|
timestamp: number;
|
|
61
61
|
}
|
|
62
62
|
export type ReadReceiptCallback = (event: ReadReceiptEvent) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Delivered to the user's OTHER devices when one device calls
|
|
65
|
+
* clearNotification(). Use it to clear the conversation's notification badge.
|
|
66
|
+
*/
|
|
67
|
+
export interface NotificationClearEvent {
|
|
68
|
+
conversationId: string;
|
|
69
|
+
timestamp: number;
|
|
70
|
+
}
|
|
71
|
+
export type NotificationClearCallback = (event: NotificationClearEvent) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Delivered to the user's OTHER devices when one device calls syncDraft().
|
|
74
|
+
* Use it to pre-fill the message composer for that conversation. Draft sync
|
|
75
|
+
* is opt-in and the text crosses the relay in cleartext.
|
|
76
|
+
*/
|
|
77
|
+
export interface DraftSyncEvent {
|
|
78
|
+
conversationId: string;
|
|
79
|
+
draftText: string;
|
|
80
|
+
timestamp: number;
|
|
81
|
+
}
|
|
82
|
+
export type DraftSyncCallback = (event: DraftSyncEvent) => void;
|
|
63
83
|
export interface BroadcastMessage {
|
|
64
84
|
broadcastId: string;
|
|
65
85
|
channelId: string;
|
|
@@ -203,6 +223,20 @@ export interface DropOnAirClient {
|
|
|
203
223
|
markRead(messageId: string, conversationId?: string): void;
|
|
204
224
|
/** Register a listener for read receipts reported by this user's other devices. */
|
|
205
225
|
onReadReceipt(callback: ReadReceiptCallback): () => void;
|
|
226
|
+
/**
|
|
227
|
+
* Tell the user's other devices a conversation's notifications were
|
|
228
|
+
* dismissed. The app decides what "dismissed" means.
|
|
229
|
+
*/
|
|
230
|
+
clearNotification(conversationId: string): void;
|
|
231
|
+
/** Listen for notification-clear syncs from the user's other devices. */
|
|
232
|
+
onNotificationCleared(callback: NotificationClearCallback): () => void;
|
|
233
|
+
/**
|
|
234
|
+
* Push a conversation draft to the user's other devices. Opt-in; the draft
|
|
235
|
+
* text crosses the relay in cleartext and is never stored.
|
|
236
|
+
*/
|
|
237
|
+
syncDraft(conversationId: string, draftText: string): void;
|
|
238
|
+
/** Listen for draft syncs from the user's other devices. */
|
|
239
|
+
onDraftSync(callback: DraftSyncCallback): () => void;
|
|
206
240
|
/**
|
|
207
241
|
* Register this device's push notification token. The platform delivers a
|
|
208
242
|
* push via APNs / FCM / Web Push when a sender attaches a pushPayload to
|
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, DeviceInfo, ReadReceiptEvent, ReadReceiptCallback, } 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, NotificationClearEvent, NotificationClearCallback, DraftSyncEvent, DraftSyncCallback, } 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;
|
|
@@ -224,7 +224,8 @@ const SyncFrameType = new protobuf.Type('SyncFrame')
|
|
|
224
224
|
.add(new protobuf.Field('type', 1, 'string'))
|
|
225
225
|
.add(new protobuf.Field('messageId', 2, 'string'))
|
|
226
226
|
.add(new protobuf.Field('conversationId', 3, 'string'))
|
|
227
|
-
.add(new protobuf.Field('timestamp', 4, 'int64'))
|
|
227
|
+
.add(new protobuf.Field('timestamp', 4, 'int64'))
|
|
228
|
+
.add(new protobuf.Field('payload', 5, 'string'));
|
|
228
229
|
class ProtobufCodec {
|
|
229
230
|
encodeEnvelope(value) {
|
|
230
231
|
return EnvelopeType.encode(value).finish();
|
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.12.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.12.0';
|
|
14
14
|
/**
|
|
15
15
|
* Binary encrypted-payload format version.
|
|
16
16
|
* Included as the first byte of every encrypted payload so receivers can
|