@droponair/sdk-js 0.9.0 → 0.10.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 +23 -0
- package/README.md +17 -0
- package/dist/core/messaging-client.d.ts +22 -1
- package/dist/core/messaging-client.js +57 -0
- package/dist/core/types.d.ts +38 -0
- package/dist/index.d.ts +1 -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,29 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.10.0], 2026-05-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **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).
|
|
14
|
+
- **`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.
|
|
15
|
+
- New exported `DeviceInfo` type.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- No wire-format change; PROTOCOL_VERSION stays at 5. The new surface is REST (`/v1/devices`) plus an additive event type.
|
|
20
|
+
- Any device that completes a connection is implicitly trusted. There is no explicit pairing/attestation step in this release.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [0.9.1], 2026-05-19
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`registerPushToken` / `unregisterPushToken` now exposed on the public `DropOnAirClient` type.** In 0.9.0 the methods were implemented on the `MessagingClient` class but missing from the exported `DropOnAirClient` interface, so TypeScript consumers couldn't call them through the `initialize()` return value. No wire-format change; this is a pure type-surface fix.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
9
32
|
## [0.9.0], 2026-05-19
|
|
10
33
|
|
|
11
34
|
### Added
|
package/README.md
CHANGED
|
@@ -98,6 +98,23 @@ 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
|
+
|
|
104
|
+
### Device trust
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const devices = await client.listMyDevices();
|
|
110
|
+
await client.revokeMyDevice('old-phone-device-id');
|
|
111
|
+
|
|
112
|
+
client.onEvent(e => {
|
|
113
|
+
if (e.type === 'DEVICE_REVOKED') {
|
|
114
|
+
// This device was revoked elsewhere. Clear local keys and show a re-register screen.
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
```
|
|
101
118
|
|
|
102
119
|
### Push notifications
|
|
103
120
|
|
|
@@ -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 } 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;
|
|
@@ -136,6 +136,27 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
136
136
|
unregisterPushToken(opts: {
|
|
137
137
|
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
138
138
|
}): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* List the current user's registered devices. Each device is implicitly
|
|
141
|
+
* trusted once it has completed a connection. Useful for a "Your devices"
|
|
142
|
+
* settings screen.
|
|
143
|
+
*/
|
|
144
|
+
listMyDevices(): Promise<DeviceInfo[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
147
|
+
* WebSocket session (if any) is closed immediately and all future
|
|
148
|
+
* connection attempts from that device are denied. Revoke is permanent;
|
|
149
|
+
* the user must re-register the device (which yields a fresh deviceId).
|
|
150
|
+
*/
|
|
151
|
+
revokeMyDevice(deviceId: string): Promise<DeviceInfo>;
|
|
152
|
+
/**
|
|
153
|
+
* Handles an inbound DEVICE_REVOKED event: this device has been revoked
|
|
154
|
+
* (by the user from another device, or by the app owner). Stops the
|
|
155
|
+
* reconnect loop and closes the socket. The DEVICE_REVOKED event is also
|
|
156
|
+
* surfaced through onEvent so the app can clear local key storage and
|
|
157
|
+
* show a re-register prompt.
|
|
158
|
+
*/
|
|
159
|
+
private handleDeviceRevoked;
|
|
139
160
|
sendCleartextMessage(toUserId: string, plaintext: string): Promise<{
|
|
140
161
|
messageId: string;
|
|
141
162
|
}>;
|
|
@@ -577,6 +577,59 @@ class MessagingClient {
|
|
|
577
577
|
this.ws.send(this.codec.encodePushRegistrationFrame(frame));
|
|
578
578
|
}
|
|
579
579
|
// ---------------------------------------------------------------------------
|
|
580
|
+
// Device trust (PROTOCOL_VERSION 5+ / Phase 2b)
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
/**
|
|
583
|
+
* List the current user's registered devices. Each device is implicitly
|
|
584
|
+
* trusted once it has completed a connection. Useful for a "Your devices"
|
|
585
|
+
* settings screen.
|
|
586
|
+
*/
|
|
587
|
+
async listMyDevices() {
|
|
588
|
+
const jwt = await this.getValidDropOnAirJwt(false);
|
|
589
|
+
const resp = await this.fetchFn(`${this.httpUrl}/v1/devices`, {
|
|
590
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
591
|
+
});
|
|
592
|
+
if (!resp.ok) {
|
|
593
|
+
throw new Error(`Failed to list devices: ${resp.status}`);
|
|
594
|
+
}
|
|
595
|
+
return (await resp.json());
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
599
|
+
* WebSocket session (if any) is closed immediately and all future
|
|
600
|
+
* connection attempts from that device are denied. Revoke is permanent;
|
|
601
|
+
* the user must re-register the device (which yields a fresh deviceId).
|
|
602
|
+
*/
|
|
603
|
+
async revokeMyDevice(deviceId) {
|
|
604
|
+
const jwt = await this.getValidDropOnAirJwt(false);
|
|
605
|
+
const resp = await this.fetchFn(`${this.httpUrl}/v1/devices/${encodeURIComponent(deviceId)}`, {
|
|
606
|
+
method: 'DELETE',
|
|
607
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
608
|
+
});
|
|
609
|
+
if (!resp.ok) {
|
|
610
|
+
throw new Error(`Failed to revoke device: ${resp.status}`);
|
|
611
|
+
}
|
|
612
|
+
return (await resp.json());
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Handles an inbound DEVICE_REVOKED event: this device has been revoked
|
|
616
|
+
* (by the user from another device, or by the app owner). Stops the
|
|
617
|
+
* reconnect loop and closes the socket. The DEVICE_REVOKED event is also
|
|
618
|
+
* surfaced through onEvent so the app can clear local key storage and
|
|
619
|
+
* show a re-register prompt.
|
|
620
|
+
*/
|
|
621
|
+
handleDeviceRevoked() {
|
|
622
|
+
this.shouldReconnect = false;
|
|
623
|
+
this.rateLimited = false;
|
|
624
|
+
if (this.reconnectTimer) {
|
|
625
|
+
clearTimeout(this.reconnectTimer);
|
|
626
|
+
this.reconnectTimer = null;
|
|
627
|
+
}
|
|
628
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
629
|
+
this.ws.close(4001, 'DEVICE_REVOKED');
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
580
633
|
// Cleartext messaging (no E2EE key exchange required)
|
|
581
634
|
// ---------------------------------------------------------------------------
|
|
582
635
|
async sendCleartextMessage(toUserId, plaintext) {
|
|
@@ -1272,6 +1325,10 @@ class MessagingClient {
|
|
|
1272
1325
|
if (frame.data.type === 'LIMIT_REACHED') {
|
|
1273
1326
|
this.rateLimited = true;
|
|
1274
1327
|
}
|
|
1328
|
+
if (frame.data.type === 'DEVICE_REVOKED') {
|
|
1329
|
+
this.log('device_revoked_received', { metadata: frame.data.metadata });
|
|
1330
|
+
this.handleDeviceRevoked();
|
|
1331
|
+
}
|
|
1275
1332
|
if (frame.data.type === 'ERROR' && frame.data.reason === 'JWT_EXPIRED') {
|
|
1276
1333
|
this.dropOnAirJwt = null;
|
|
1277
1334
|
this.currentUserId = null;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -132,6 +132,19 @@ export interface InitializeOptions {
|
|
|
132
132
|
*/
|
|
133
133
|
autoAckIncomingMessages?: boolean;
|
|
134
134
|
}
|
|
135
|
+
/** A registered device as returned by {@link DropOnAirClient.listMyDevices}. */
|
|
136
|
+
export interface DeviceInfo {
|
|
137
|
+
deviceId: string;
|
|
138
|
+
sdkVersion?: string;
|
|
139
|
+
protocolVersion?: string;
|
|
140
|
+
platform?: string;
|
|
141
|
+
/** ISO-8601 timestamps. */
|
|
142
|
+
firstSeenAt?: string;
|
|
143
|
+
lastSeenAt?: string;
|
|
144
|
+
/** Non-null once revoked. */
|
|
145
|
+
revokedAt?: string;
|
|
146
|
+
revokedBy?: 'END_USER' | 'APP_OWNER' | 'SYSTEM';
|
|
147
|
+
}
|
|
135
148
|
export interface DropOnAirClient {
|
|
136
149
|
connect(): Promise<void>;
|
|
137
150
|
disconnect(): void;
|
|
@@ -170,6 +183,31 @@ export interface DropOnAirClient {
|
|
|
170
183
|
onMessageEdit(callback: MessageEditCallback): () => void;
|
|
171
184
|
/** Register a listener for incoming message-delete notifications. */
|
|
172
185
|
onMessageDelete(callback: MessageDeleteCallback): () => void;
|
|
186
|
+
/**
|
|
187
|
+
* Register this device's push notification token. The platform delivers a
|
|
188
|
+
* push via APNs / FCM / Web Push when a sender attaches a pushPayload to
|
|
189
|
+
* a message and the recipient has no live WebSocket session. The token is
|
|
190
|
+
* opaque to the server. For iOS PushKit/CallKit, pass {@code voipToken}
|
|
191
|
+
* alongside the regular APNs token so incoming call invites wake the app
|
|
192
|
+
* via the VoIP push channel.
|
|
193
|
+
*/
|
|
194
|
+
registerPushToken(opts: {
|
|
195
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
196
|
+
token: string;
|
|
197
|
+
voipToken?: string;
|
|
198
|
+
}): Promise<void>;
|
|
199
|
+
/** Unregister this device's push notification token (e.g. on logout). */
|
|
200
|
+
unregisterPushToken(opts: {
|
|
201
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
202
|
+
}): Promise<void>;
|
|
203
|
+
/** List the current user's registered devices. */
|
|
204
|
+
listMyDevices(): Promise<DeviceInfo[]>;
|
|
205
|
+
/**
|
|
206
|
+
* Revoke one of the current user's devices. The revoked device's live
|
|
207
|
+
* session is closed and all future connections from it are denied.
|
|
208
|
+
* Revoke is permanent.
|
|
209
|
+
*/
|
|
210
|
+
revokeMyDevice(deviceId: string): Promise<DeviceInfo>;
|
|
173
211
|
/**
|
|
174
212
|
* Convenience: encrypts (E2EE), uploads to the customer's bucket, finalizes,
|
|
175
213
|
* 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, } 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;
|
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.10.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.10.0';
|
|
14
14
|
/**
|
|
15
15
|
* Binary encrypted-payload format version.
|
|
16
16
|
* Included as the first byte of every encrypted payload so receivers can
|