@droponair/sdk-js 0.8.0 → 0.9.1
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 +22 -0
- package/README.md +25 -0
- package/dist/core/messaging-client.d.ts +25 -0
- package/dist/core/messaging-client.js +51 -0
- package/dist/core/types.d.ts +17 -0
- package/dist/transport/protobuf-codec.d.ts +13 -0
- package/dist/transport/protobuf-codec.js +12 -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,28 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.9.1], 2026-05-19
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`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.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [0.9.0], 2026-05-19
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Push notification token registration.** New `client.registerPushToken({ platform, token, voipToken? })` and `client.unregisterPushToken({ platform })` methods. The platform delivers a push via APNs (iOS), FCM v1 (Android), or VAPID/Web Push (browser) whenever a sender supplies a `pushPayload` on a message and the recipient device has no live WebSocket session. The token is opaque to the server. For iOS VoIP/CallKit pushes, pass `voipToken` alongside the regular APNs token.
|
|
22
|
+
- **PROTOCOL_VERSION bumped to 5.** Additive only: new optional `pushPayload` field on `Envelope`, `GroupEnvelope`, `CallFrame`, `GroupCallFrame`; new `PushRegistrationFrame` for token registration. Existing 0.8.x clients keep working against the new server (proto3 ignores unknown fields).
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- E2EE invariant preserved: the push body is sender-supplied cleartext metadata only ("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.
|
|
27
|
+
- Customers configure their own APNs (.p8 + Team/Key/Bundle IDs), FCM service-account JSON, and VAPID keypair via the dashboard. Per-app, not per-environment; use two apps for dev/prod separation.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
9
31
|
## [0.8.0], 2026-05-19
|
|
10
32
|
|
|
11
33
|
### BREAKING
|
package/README.md
CHANGED
|
@@ -96,6 +96,31 @@ const client = await initialize(options);
|
|
|
96
96
|
| `onMessageDelete(callback)` | `() => void` | Listen for inbound delete tombstones for 1:1 messages |
|
|
97
97
|
| `onEvent(callback)` | `() => void` | Listen for system events (CONNECTED, DELIVERED, ERROR, etc.) |
|
|
98
98
|
| `ack(messageId)` | `Promise<void>` | Manually acknowledge a message |
|
|
99
|
+
| `registerPushToken({ platform, token, voipToken? })` | `Promise<void>` | Register this device for push notifications. `platform` is `'APNS'`, `'FCM'`, or `'WEB_PUSH'`. |
|
|
100
|
+
| `unregisterPushToken({ platform })` | `Promise<void>` | Unregister this device for push notifications (e.g. on logout). |
|
|
101
|
+
|
|
102
|
+
### Push notifications
|
|
103
|
+
|
|
104
|
+
Available since SDK `0.9.0`. Customers configure their own APNs / FCM / VAPID credentials in the dashboard. The platform delivers a push only when the sender attaches a `pushPayload` to a message and the recipient device has no live WebSocket session. See the per-product availability and limits on your dashboard's Subscription page.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// iOS, after didRegisterForRemoteNotificationsWithDeviceToken
|
|
108
|
+
await client.registerPushToken({ platform: 'APNS', token: deviceTokenHex });
|
|
109
|
+
|
|
110
|
+
// iOS + PushKit/CallKit (VoIP)
|
|
111
|
+
await client.registerPushToken({ platform: 'APNS', token: deviceTokenHex, voipToken: voipTokenHex });
|
|
112
|
+
|
|
113
|
+
// Android, after FirebaseMessaging.getInstance().token
|
|
114
|
+
await client.registerPushToken({ platform: 'FCM', token: fcmToken });
|
|
115
|
+
|
|
116
|
+
// Browser, the token is the JSON returned by PushManager.subscribe()
|
|
117
|
+
await client.registerPushToken({ platform: 'WEB_PUSH', token: JSON.stringify(subscription) });
|
|
118
|
+
|
|
119
|
+
// On logout
|
|
120
|
+
await client.unregisterPushToken({ platform: 'APNS' });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
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.
|
|
99
124
|
|
|
100
125
|
### Message Edit & Delete
|
|
101
126
|
|
|
@@ -111,6 +111,31 @@ export declare class MessagingClient implements DropOnAirClient {
|
|
|
111
111
|
deleteMessage(originalMessageId: string, toUserId: string, scope: 'FOR_EVERYONE' | 'FOR_ME'): Promise<{
|
|
112
112
|
deleteId: string;
|
|
113
113
|
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Register this device's push notification token with the DropOnAir platform.
|
|
116
|
+
* The token is opaque to the server; the platform fans out push notifications
|
|
117
|
+
* via the customer's APNs / FCM / VAPID credentials when a sender attaches a
|
|
118
|
+
* pushPayload to a message and the recipient is offline.
|
|
119
|
+
*
|
|
120
|
+
* For iOS apps using VoIP push (PushKit), pass {@code voipToken} alongside the
|
|
121
|
+
* regular APNs token. The platform will route CALL_INVITE pushes via the VoIP
|
|
122
|
+
* token and message pushes via the regular token.
|
|
123
|
+
*
|
|
124
|
+
* Idempotent: calling twice with the same (platform, deviceId) upserts the
|
|
125
|
+
* stored token and refreshes its lastSeenAt timestamp.
|
|
126
|
+
*/
|
|
127
|
+
registerPushToken(opts: {
|
|
128
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
129
|
+
token: string;
|
|
130
|
+
voipToken?: string;
|
|
131
|
+
}): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Unregister this device's push notification token (e.g. on logout). Future
|
|
134
|
+
* push fan-out for this (appId, userId, deviceId, platform) tuple is dropped.
|
|
135
|
+
*/
|
|
136
|
+
unregisterPushToken(opts: {
|
|
137
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
138
|
+
}): Promise<void>;
|
|
114
139
|
sendCleartextMessage(toUserId: string, plaintext: string): Promise<{
|
|
115
140
|
messageId: string;
|
|
116
141
|
}>;
|
|
@@ -526,6 +526,57 @@ class MessagingClient {
|
|
|
526
526
|
return { deleteId };
|
|
527
527
|
}
|
|
528
528
|
// ---------------------------------------------------------------------------
|
|
529
|
+
// Push notification token registration (PROTOCOL_VERSION 5+)
|
|
530
|
+
// ---------------------------------------------------------------------------
|
|
531
|
+
/**
|
|
532
|
+
* Register this device's push notification token with the DropOnAir platform.
|
|
533
|
+
* The token is opaque to the server; the platform fans out push notifications
|
|
534
|
+
* via the customer's APNs / FCM / VAPID credentials when a sender attaches a
|
|
535
|
+
* pushPayload to a message and the recipient is offline.
|
|
536
|
+
*
|
|
537
|
+
* For iOS apps using VoIP push (PushKit), pass {@code voipToken} alongside the
|
|
538
|
+
* regular APNs token. The platform will route CALL_INVITE pushes via the VoIP
|
|
539
|
+
* token and message pushes via the regular token.
|
|
540
|
+
*
|
|
541
|
+
* Idempotent: calling twice with the same (platform, deviceId) upserts the
|
|
542
|
+
* stored token and refreshes its lastSeenAt timestamp.
|
|
543
|
+
*/
|
|
544
|
+
async registerPushToken(opts) {
|
|
545
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
546
|
+
throw new Error('DropOnAir websocket is not connected');
|
|
547
|
+
}
|
|
548
|
+
if (!opts.token || opts.token.trim().length === 0) {
|
|
549
|
+
throw new Error('Push token must not be empty');
|
|
550
|
+
}
|
|
551
|
+
const deviceId = this.deviceId ?? await this.getOrCreateDeviceId();
|
|
552
|
+
const frame = {
|
|
553
|
+
type: 'PUSH_REGISTER',
|
|
554
|
+
platform: opts.platform,
|
|
555
|
+
token: opts.token,
|
|
556
|
+
voipToken: opts.voipToken ?? '',
|
|
557
|
+
deviceId,
|
|
558
|
+
};
|
|
559
|
+
this.ws.send(this.codec.encodePushRegistrationFrame(frame));
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Unregister this device's push notification token (e.g. on logout). Future
|
|
563
|
+
* push fan-out for this (appId, userId, deviceId, platform) tuple is dropped.
|
|
564
|
+
*/
|
|
565
|
+
async unregisterPushToken(opts) {
|
|
566
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
567
|
+
throw new Error('DropOnAir websocket is not connected');
|
|
568
|
+
}
|
|
569
|
+
const deviceId = this.deviceId ?? await this.getOrCreateDeviceId();
|
|
570
|
+
const frame = {
|
|
571
|
+
type: 'PUSH_UNREGISTER',
|
|
572
|
+
platform: opts.platform,
|
|
573
|
+
token: '',
|
|
574
|
+
voipToken: '',
|
|
575
|
+
deviceId,
|
|
576
|
+
};
|
|
577
|
+
this.ws.send(this.codec.encodePushRegistrationFrame(frame));
|
|
578
|
+
}
|
|
579
|
+
// ---------------------------------------------------------------------------
|
|
529
580
|
// Cleartext messaging (no E2EE key exchange required)
|
|
530
581
|
// ---------------------------------------------------------------------------
|
|
531
582
|
async sendCleartextMessage(toUserId, plaintext) {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -170,6 +170,23 @@ export interface DropOnAirClient {
|
|
|
170
170
|
onMessageEdit(callback: MessageEditCallback): () => void;
|
|
171
171
|
/** Register a listener for incoming message-delete notifications. */
|
|
172
172
|
onMessageDelete(callback: MessageDeleteCallback): () => void;
|
|
173
|
+
/**
|
|
174
|
+
* Register this device's push notification token. The platform delivers a
|
|
175
|
+
* push via APNs / FCM / Web Push when a sender attaches a pushPayload to
|
|
176
|
+
* a message and the recipient has no live WebSocket session. The token is
|
|
177
|
+
* opaque to the server. For iOS PushKit/CallKit, pass {@code voipToken}
|
|
178
|
+
* alongside the regular APNs token so incoming call invites wake the app
|
|
179
|
+
* via the VoIP push channel.
|
|
180
|
+
*/
|
|
181
|
+
registerPushToken(opts: {
|
|
182
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
183
|
+
token: string;
|
|
184
|
+
voipToken?: string;
|
|
185
|
+
}): Promise<void>;
|
|
186
|
+
/** Unregister this device's push notification token (e.g. on logout). */
|
|
187
|
+
unregisterPushToken(opts: {
|
|
188
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
189
|
+
}): Promise<void>;
|
|
173
190
|
/**
|
|
174
191
|
* Convenience: encrypts (E2EE), uploads to the customer's bucket, finalizes,
|
|
175
192
|
* and returns an AttachmentRef ready to pass into {@link sendMessage}.
|
|
@@ -136,6 +136,18 @@ export interface WireMessageEditFrame {
|
|
|
136
136
|
plaintextPayload?: string;
|
|
137
137
|
clientEditId?: string;
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Push token registration / unregister. Client -> server only. The server
|
|
141
|
+
* acks with a regular Ack frame of type 'PUSH_REGISTERED' or
|
|
142
|
+
* 'PUSH_UNREGISTERED' and correlation 'PUSH:<deviceId>:<platform>'.
|
|
143
|
+
*/
|
|
144
|
+
export interface WirePushRegistrationFrame {
|
|
145
|
+
type: 'PUSH_REGISTER' | 'PUSH_UNREGISTER';
|
|
146
|
+
platform: 'APNS' | 'FCM' | 'WEB_PUSH';
|
|
147
|
+
token: string;
|
|
148
|
+
voipToken?: string;
|
|
149
|
+
deviceId: string;
|
|
150
|
+
}
|
|
139
151
|
/** Tombstone frame, scope FOR_EVERYONE or FOR_ME. */
|
|
140
152
|
export interface WireMessageDeleteFrame {
|
|
141
153
|
type: string;
|
|
@@ -189,6 +201,7 @@ export declare class ProtobufCodec {
|
|
|
189
201
|
encodeGroupAck(value: WireGroupAck): Uint8Array;
|
|
190
202
|
encodeMessageEditFrame(value: WireMessageEditFrame): Uint8Array;
|
|
191
203
|
encodeMessageDeleteFrame(value: WireMessageDeleteFrame): Uint8Array;
|
|
204
|
+
encodePushRegistrationFrame(value: WirePushRegistrationFrame): Uint8Array;
|
|
192
205
|
decodeFrame(payload: Uint8Array): InboundFrame;
|
|
193
206
|
private tryDecodeEnvelope;
|
|
194
207
|
private tryDecodeAck;
|
|
@@ -208,6 +208,15 @@ const MessageDeleteFrameType = new protobuf.Type('MessageDeleteFrame')
|
|
|
208
208
|
.add(new protobuf.Field('timestamp', 7, 'int64'))
|
|
209
209
|
.add(new protobuf.Field('scope', 8, 'string'))
|
|
210
210
|
.add(new protobuf.Field('clientDeleteId', 9, 'string'));
|
|
211
|
+
// Push token registration frame (PROTOCOL_VERSION 5+). Client to server only;
|
|
212
|
+
// the server emits a PUSH_REGISTERED / PUSH_UNREGISTERED Ack back. The token
|
|
213
|
+
// is opaque to the server and stored against (appId, userId, deviceId, platform).
|
|
214
|
+
const PushRegistrationFrameType = new protobuf.Type('PushRegistrationFrame')
|
|
215
|
+
.add(new protobuf.Field('type', 1, 'string'))
|
|
216
|
+
.add(new protobuf.Field('platform', 2, 'string'))
|
|
217
|
+
.add(new protobuf.Field('token', 3, 'string'))
|
|
218
|
+
.add(new protobuf.Field('voipToken', 4, 'string'))
|
|
219
|
+
.add(new protobuf.Field('deviceId', 5, 'string'));
|
|
211
220
|
class ProtobufCodec {
|
|
212
221
|
encodeEnvelope(value) {
|
|
213
222
|
return EnvelopeType.encode(value).finish();
|
|
@@ -236,6 +245,9 @@ class ProtobufCodec {
|
|
|
236
245
|
encodeMessageDeleteFrame(value) {
|
|
237
246
|
return MessageDeleteFrameType.encode(value).finish();
|
|
238
247
|
}
|
|
248
|
+
encodePushRegistrationFrame(value) {
|
|
249
|
+
return PushRegistrationFrameType.encode(value).finish();
|
|
250
|
+
}
|
|
239
251
|
decodeFrame(payload) {
|
|
240
252
|
// Edit and delete frames must be probed BEFORE Envelope, the type
|
|
241
253
|
// discriminator at field 1 ("MESSAGE_EDIT" / "MESSAGE_DELETE") would
|
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.9.1";
|
|
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 = 5;
|
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.9.1';
|
|
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 = 5;
|