@droponair/sdk-js 0.29.0 → 0.30.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Added
6
+
7
+ - **Typing indicators.** `sendTyping(conversationId, isTyping)` and `onTyping(callback)`. Nothing in the platform carried this before, so an
8
+ app that wanted to show someone composing had nowhere to send it.
9
+ - Typing is never stored. It describes this moment and is worth nothing after it,
10
+ so it reaches only those connected at the time and is never replayed. Treat a
11
+ start as expiring on its own after a few seconds rather than waiting for the
12
+ matching stop, which is lost if the sender disconnects mid-sentence.
13
+ - Gated separately from read receipts, which name a message that was read and are
14
+ opted into per group. Typing says only that somebody is composing and discloses
15
+ nothing about what, so it needs no per-group opt-in.
16
+
3
17
  ## 0.29.0
4
18
 
5
19
  ### Fixed
package/README.md CHANGED
@@ -380,6 +380,22 @@ on `SERVER_RECEIVED`, which is about the relay rather than any member.
380
380
  was closed or disconnected is drained automatically when it reconnects and delivered
381
381
  through `onGroupMessage` just like a live message. There is nothing to call.
382
382
 
383
+ ### Typing indicators
384
+
385
+ Say when someone starts and stops composing, and hear it about others. Nothing is
386
+ stored: it reaches only those connected at the time and is never replayed, so
387
+ treat a start as expiring on its own rather than waiting for the stop, which is
388
+ lost if the sender disconnects mid-sentence.
389
+
390
+ ```typescript
391
+ client.sendTyping(groupId, true);
392
+ client.sendTyping(groupId, false);
393
+
394
+ client.onTyping((event) => {
395
+ // event.conversationId, event.fromUserId, event.isTyping
396
+ });
397
+ ```
398
+
383
399
  ### 1-to-1 Calls
384
400
 
385
401
  | Method | Returns | Description |
@@ -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, PushPayload } from './types';
3
+ import { BroadcastCallback, CallEventCallback, DropOnAirClient, EventCallback, CreateRoomOptions, GroupCallEventCallback, GroupInfo, GroupMessageCallback, Room, UpdateRoomOptions, SfuToken, SfuRecording, InitializeOptions, MessageCallback, MessageDeleteCallback, MessageEditCallback, TurnCredentials, DeviceInfo, ReadReceiptCallback, TypingCallback, 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;
@@ -90,6 +90,7 @@ export declare class MessagingClient implements DropOnAirClient {
90
90
  private readonly broadcastListeners;
91
91
  private readonly messageEditListeners;
92
92
  private readonly readReceiptListeners;
93
+ private readonly typingListeners;
93
94
  private readonly notificationClearListeners;
94
95
  private readonly draftSyncListeners;
95
96
  private readonly messageDeleteListeners;
@@ -146,6 +147,11 @@ export declare class MessagingClient implements DropOnAirClient {
146
147
  * reported. Fires when another device of the same user calls markRead();
147
148
  * use it to clear the unread state your app maintains for that message.
148
149
  */
150
+ /**
151
+ * Listen for someone starting or stopping typing in a conversation. Nothing is
152
+ * stored, so this only fires while both sides are connected.
153
+ */
154
+ onTyping(callback: TypingCallback): () => void;
149
155
  onReadReceipt(callback: ReadReceiptCallback): () => void;
150
156
  editMessage(originalMessageId: string, toUserId: string, newPlaintext: string): Promise<{
151
157
  editId: string;
@@ -217,6 +223,14 @@ export declare class MessagingClient implements DropOnAirClient {
217
223
  * @param conversationId optional peer userId / group id, echoed back to
218
224
  * other devices so they can bucket the receipt
219
225
  */
226
+ /**
227
+ * Say this user started or stopped composing in a conversation.
228
+ *
229
+ * Never stored, so it reaches only those connected at the time. Send `false`
230
+ * when they stop, but treat the indicator as expiring on its own: if the sender
231
+ * disconnects mid-sentence, the stop never arrives.
232
+ */
233
+ sendTyping(conversationId: string, isTyping: boolean): void;
220
234
  markRead(messageId: string, conversationId?: string): void;
221
235
  /**
222
236
  * Tell this user's other devices that the notification(s) for a
@@ -282,6 +282,7 @@ class MessagingClient {
282
282
  this.broadcastListeners = new Set();
283
283
  this.messageEditListeners = new Set();
284
284
  this.readReceiptListeners = new Set();
285
+ this.typingListeners = new Set();
285
286
  this.notificationClearListeners = new Set();
286
287
  this.draftSyncListeners = new Set();
287
288
  this.messageDeleteListeners = new Set();
@@ -519,6 +520,14 @@ class MessagingClient {
519
520
  * reported. Fires when another device of the same user calls markRead();
520
521
  * use it to clear the unread state your app maintains for that message.
521
522
  */
523
+ /**
524
+ * Listen for someone starting or stopping typing in a conversation. Nothing is
525
+ * stored, so this only fires while both sides are connected.
526
+ */
527
+ onTyping(callback) {
528
+ this.typingListeners.add(callback);
529
+ return () => this.typingListeners.delete(callback);
530
+ }
522
531
  onReadReceipt(callback) {
523
532
  this.readReceiptListeners.add(callback);
524
533
  return () => this.readReceiptListeners.delete(callback);
@@ -777,6 +786,26 @@ class MessagingClient {
777
786
  * @param conversationId optional peer userId / group id, echoed back to
778
787
  * other devices so they can bucket the receipt
779
788
  */
789
+ /**
790
+ * Say this user started or stopped composing in a conversation.
791
+ *
792
+ * Never stored, so it reaches only those connected at the time. Send `false`
793
+ * when they stop, but treat the indicator as expiring on its own: if the sender
794
+ * disconnects mid-sentence, the stop never arrives.
795
+ */
796
+ sendTyping(conversationId, isTyping) {
797
+ if (!this.transport?.isOpen()) {
798
+ return;
799
+ }
800
+ const frame = {
801
+ type: 'SYNC_TYPING',
802
+ messageId: '',
803
+ conversationId,
804
+ timestamp: Date.now(),
805
+ payload: isTyping ? 'start' : 'stop',
806
+ };
807
+ this.transport.send(this.codec.encodeSyncFrame(frame));
808
+ }
780
809
  markRead(messageId, conversationId) {
781
810
  if (!this.transport?.isOpen()) {
782
811
  throw new Error('DropOnAir websocket is not connected');
@@ -838,7 +867,24 @@ class MessagingClient {
838
867
  return () => this.draftSyncListeners.delete(callback);
839
868
  }
840
869
  handleIncomingSync(frame) {
841
- if (frame.type === 'SYNC_READ_RECEIPT') {
870
+ if (frame.type === 'SYNC_TYPING') {
871
+ if (!frame.conversationId || !frame.fromUserId) {
872
+ return;
873
+ }
874
+ const event = {
875
+ conversationId: frame.conversationId,
876
+ fromUserId: frame.fromUserId,
877
+ isTyping: frame.payload !== 'stop',
878
+ timestamp: frame.timestamp,
879
+ };
880
+ for (const listener of this.typingListeners) {
881
+ try {
882
+ listener(event);
883
+ }
884
+ catch { /* listener errors must not break the socket */ }
885
+ }
886
+ }
887
+ else if (frame.type === 'SYNC_READ_RECEIPT') {
842
888
  const event = {
843
889
  messageId: frame.messageId,
844
890
  conversationId: frame.conversationId || undefined,
@@ -109,6 +109,24 @@ export interface ReadReceiptEvent {
109
109
  fromUserId?: string;
110
110
  }
111
111
  export type ReadReceiptCallback = (event: ReadReceiptEvent) => void;
112
+ /**
113
+ * Someone in a conversation started or stopped composing.
114
+ *
115
+ * Ephemeral by design: the platform never stores it, so it arrives only while
116
+ * both sides are connected and nothing is replayed afterwards. Treat a `true` as
117
+ * expiring on its own after a few seconds rather than waiting for the matching
118
+ * `false`, which is lost if the sender disconnects mid-sentence.
119
+ */
120
+ export interface TypingEvent {
121
+ /** The group or peer the typing is happening in. */
122
+ conversationId: string;
123
+ /** Who is typing. */
124
+ fromUserId: string;
125
+ /** Whether they started or stopped. */
126
+ isTyping: boolean;
127
+ timestamp: number;
128
+ }
129
+ export type TypingCallback = (event: TypingEvent) => void;
112
130
  /**
113
131
  * Delivered to the user's OTHER devices when one device calls
114
132
  * clearNotification(). Use it to clear the conversation's notification badge.
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export { SDK_VERSION, PROTOCOL_VERSION, PAYLOAD_FORMAT_VERSION } from './version
22
22
  */
23
23
  export declare function createSecureIdentity(store?: IdentityRecordStore): Promise<WebCryptoIdentityProvider | null>;
24
24
  export declare function initialize(options: InitializeOptions): Promise<DropOnAirClient>;
25
- export type { InitializeOptions, DropOnAirClient, DropOnAirEvent, MessageCallback, EventCallback, DecryptedMessage, KeyStorageAdapter, CallEvent, CallEventType, CallEventCallback, TurnCredentials, BroadcastMessage, BroadcastCallback, GroupInfo, GroupMemberInfo, DecryptedGroupMessage, GroupMessageCallback, GroupCallEvent, GroupCallEventType, GroupCallEventCallback, Room, RoomPolicy, CreateRoomOptions, UpdateRoomOptions, SfuToken, SfuRecording, MessageEditEvent, MessageEditCallback, MessageDeleteEvent, MessageDeleteCallback, DeviceInfo, ReadReceiptEvent, ReadReceiptCallback, NotificationClearEvent, NotificationClearCallback, DraftSyncEvent, DraftSyncCallback, } from './core/types';
25
+ export type { InitializeOptions, DropOnAirClient, DropOnAirEvent, MessageCallback, EventCallback, DecryptedMessage, KeyStorageAdapter, CallEvent, CallEventType, CallEventCallback, TurnCredentials, BroadcastMessage, BroadcastCallback, GroupInfo, GroupMemberInfo, DecryptedGroupMessage, GroupMessageCallback, GroupCallEvent, GroupCallEventType, GroupCallEventCallback, Room, RoomPolicy, CreateRoomOptions, UpdateRoomOptions, SfuToken, SfuRecording, MessageEditEvent, MessageEditCallback, MessageDeleteEvent, MessageDeleteCallback, DeviceInfo, ReadReceiptEvent, ReadReceiptCallback, TypingEvent, TypingCallback, NotificationClearEvent, NotificationClearCallback, DraftSyncEvent, DraftSyncCallback, } from './core/types';
26
26
  export type { AttachmentEncryptionType, AttachmentConversationType, AttachmentRef, DeviceWrappedKey, CreateUploadSessionOptions, UploadSession, PrepareAttachmentOptions, DownloadedAttachment, } from './attachment/attachment-types';
27
27
  export { SseTransport, type SseTransportOptions, type SseFrameHandler, type SseStateHandler, } from './transport/sse-transport';
28
28
  export { WebTransportTransport, type WebTransportTransportOptions, type WTFrameHandler, type WTStateHandler, } from './transport/webtransport-transport';
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.29.0";
10
+ export declare const SDK_VERSION = "0.30.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.29.0';
13
+ exports.SDK_VERSION = '0.30.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@droponair/sdk-js",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "End-to-end encrypted messaging, voice and video calling SDK. The relay never sees your keys or message content.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",