@droponair/sdk-js 0.33.0 → 0.34.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 +26 -0
- package/README.md +2 -0
- package/dist/attachment/attachment-client.d.ts +2 -0
- package/dist/attachment/attachment-client.js +23 -0
- package/dist/attachment/attachment-types.d.ts +11 -0
- package/dist/core/messaging-client.js +19 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.34.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A file was deleted before the last recipient could download it.** With
|
|
8
|
+
`deleteWhenEveryoneHasIt`, the platform counted a recipient as having the file the
|
|
9
|
+
moment it issued their download link, so the final recipient got a link to a file
|
|
10
|
+
already gone. The SDK now confirms receipt only after the download and decrypt
|
|
11
|
+
succeed, and the platform deletes only on those confirmations. Nothing to change
|
|
12
|
+
in an app: it happens inside `downloadAttachment`.
|
|
13
|
+
|
|
14
|
+
## 0.34.0
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Files in group messages.** `sendGroupMessage(groupId, text, memberUserIds, { attachments })`
|
|
19
|
+
now carries attachment references, and a received `DecryptedGroupMessage` carries
|
|
20
|
+
`attachments`. Each member is handed the pointer and only the file keys wrapped for
|
|
21
|
+
their own devices. A file sent while a member was away arrives with the message when
|
|
22
|
+
they catch up, which previously carried nothing.
|
|
23
|
+
- **A lifetime per file.** `retentionSeconds` on an upload session asks for one file to
|
|
24
|
+
live a set time. Never longer than the app's own retention.
|
|
25
|
+
- **`deleteWhenEveryoneHasIt`.** The platform deletes the stored bytes as soon as every
|
|
26
|
+
recipient has fetched them, and answers a later request with "no longer available"
|
|
27
|
+
rather than a broken link.
|
|
28
|
+
|
|
3
29
|
## 0.33.0
|
|
4
30
|
|
|
5
31
|
### Added
|
package/README.md
CHANGED
|
@@ -346,6 +346,8 @@ client.onMessage(async (msg) => {
|
|
|
346
346
|
| `prepareAttachmentAndUpload(bytes, options)` | `Promise<AttachmentRef>` | Encrypt (E2EE), upload, finalize, return ref |
|
|
347
347
|
| `createUploadSession(options)` | `Promise<UploadSession>` | Low-level: presigned PUT URL only |
|
|
348
348
|
| `finalizeAttachment(attachmentId, sha256)` | `Promise<void>` | Low-level: commit integrity hash |
|
|
349
|
+
| `retentionSeconds` (upload option) | | How long this one file should live. Never granted longer than your own retention. |
|
|
350
|
+
| `deleteWhenEveryoneHasIt` (upload option) | | Delete the stored bytes as soon as every recipient has fetched them. A later request is answered "no longer available". |
|
|
349
351
|
| `downloadAttachment(ref)` | `Promise<DownloadedAttachment>` | Get presigned URL + download + decrypt |
|
|
350
352
|
| `revokeAttachment(attachmentId \| ref)` | `Promise<void>` | Revoke an attachment you sent; recipients get `ATTACHMENT_REVOKED` |
|
|
351
353
|
| `sendMessage(toUserId, text, { attachments })` | `Promise<{ messageId }>` | Send with attachments |
|
|
@@ -63,6 +63,8 @@ export declare class AttachmentClient {
|
|
|
63
63
|
* current device, unwraps it, then decrypts the stored bytes.
|
|
64
64
|
*/
|
|
65
65
|
downloadAttachment(ref: AttachmentRef): Promise<DownloadedAttachment>;
|
|
66
|
+
/** Best effort: a missed confirmation only means the copy waits for its own expiry. */
|
|
67
|
+
private confirmReceived;
|
|
66
68
|
/** Convert public AttachmentRef into the wire-format type for proto encoding. */
|
|
67
69
|
toWire(ref: AttachmentRef): WireAttachmentRef;
|
|
68
70
|
/** Convert a wire AttachmentRef (from a received message) into the public type. */
|
|
@@ -46,6 +46,10 @@ class AttachmentClient {
|
|
|
46
46
|
conversationId: opts.conversationId ?? '',
|
|
47
47
|
recipientUserIds: opts.recipientUserIds ?? [],
|
|
48
48
|
};
|
|
49
|
+
if (opts.retentionSeconds !== undefined)
|
|
50
|
+
body.retentionSeconds = opts.retentionSeconds;
|
|
51
|
+
if (opts.deleteWhenEveryoneHasIt)
|
|
52
|
+
body.deleteWhenEveryoneHasIt = true;
|
|
49
53
|
const resp = await this.deps.fetchFn(`${this.deps.httpUrl}/v1/attachments/upload-session`, {
|
|
50
54
|
method: 'POST',
|
|
51
55
|
headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
|
|
@@ -194,6 +198,7 @@ class AttachmentClient {
|
|
|
194
198
|
const arrayBuf = await resp.arrayBuffer();
|
|
195
199
|
let stored = new Uint8Array(arrayBuf);
|
|
196
200
|
if (ref.encryptionType === 'CLEARTEXT') {
|
|
201
|
+
await this.confirmReceived(ref.attachmentId);
|
|
197
202
|
return { attachmentId: ref.attachmentId, mimeType: info.mimeType, sizeBytes: info.sizeBytes, sha256: info.sha256, bytes: stored };
|
|
198
203
|
}
|
|
199
204
|
const myDeviceId = await this.deps.getCurrentDeviceId();
|
|
@@ -210,6 +215,10 @@ class AttachmentClient {
|
|
|
210
215
|
const ciphertext = stored.slice(12);
|
|
211
216
|
const aesKey = await crypto.subtle.importKey('raw', asBufferSource(fileKey), { name: 'AES-GCM' }, false, ['decrypt']);
|
|
212
217
|
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: asBufferSource(fileNonce), tagLength: 128 }, aesKey, asBufferSource(ciphertext));
|
|
218
|
+
// Only now, with the bytes downloaded and readable, does this device say it has
|
|
219
|
+
// the file. A download link is not a file, so the platform deletes the stored
|
|
220
|
+
// copy on this confirmation, never on the link.
|
|
221
|
+
await this.confirmReceived(ref.attachmentId);
|
|
213
222
|
return {
|
|
214
223
|
attachmentId: ref.attachmentId,
|
|
215
224
|
mimeType: info.mimeType,
|
|
@@ -218,6 +227,20 @@ class AttachmentClient {
|
|
|
218
227
|
bytes: new Uint8Array(plain),
|
|
219
228
|
};
|
|
220
229
|
}
|
|
230
|
+
/** Best effort: a missed confirmation only means the copy waits for its own expiry. */
|
|
231
|
+
async confirmReceived(attachmentId) {
|
|
232
|
+
try {
|
|
233
|
+
const jwt = await this.deps.getValidDropOnAirJwt();
|
|
234
|
+
await this.deps.fetchFn(`${this.deps.httpUrl}/v1/attachments/${encodeURIComponent(attachmentId)}/received`, {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
|
|
237
|
+
body: '{}',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// The download itself succeeded; nothing to surface.
|
|
242
|
+
}
|
|
243
|
+
}
|
|
221
244
|
/** Convert public AttachmentRef into the wire-format type for proto encoding. */
|
|
222
245
|
toWire(ref) {
|
|
223
246
|
return {
|
|
@@ -44,6 +44,17 @@ export interface CreateUploadSessionOptions {
|
|
|
44
44
|
conversationId?: string;
|
|
45
45
|
/** Recipient user IDs (used by the server for download authorization). */
|
|
46
46
|
recipientUserIds?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* How long this one file should live, in seconds. Optional, and never granted
|
|
49
|
+
* longer than the app's own retention. An app that works out a window per file
|
|
50
|
+
* sets it here instead of living with one number for everything.
|
|
51
|
+
*/
|
|
52
|
+
retentionSeconds?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Delete the stored bytes as soon as every recipient has fetched them. The
|
|
55
|
+
* file then exists only on the devices it was sent to.
|
|
56
|
+
*/
|
|
57
|
+
deleteWhenEveryoneHasIt?: boolean;
|
|
47
58
|
}
|
|
48
59
|
/** Result of reserving an upload session. */
|
|
49
60
|
export interface UploadSession {
|
|
@@ -1863,6 +1863,7 @@ class MessagingClient {
|
|
|
1863
1863
|
fromUserId: notif.fromUserId,
|
|
1864
1864
|
timestamp: notif.timestamp,
|
|
1865
1865
|
plaintext,
|
|
1866
|
+
attachments: (notif.attachments ?? []).map(a => attachment_client_1.AttachmentClient.fromWire(a)),
|
|
1866
1867
|
};
|
|
1867
1868
|
for (const listener of this.groupMessageListeners) {
|
|
1868
1869
|
listener(msg);
|
|
@@ -2643,6 +2644,24 @@ class MessagingClient {
|
|
|
2643
2644
|
encryptedPayload: (0, bytes_1.fromBase64)(dp.encryptedPayloadBase64),
|
|
2644
2645
|
senderPublicKey: (0, bytes_1.fromBase64)(dp.senderPublicKeyBase64),
|
|
2645
2646
|
})),
|
|
2647
|
+
// Files travel with a late message too. Without this, somebody who was
|
|
2648
|
+
// away came back to a message that mentioned nothing at all: the live
|
|
2649
|
+
// path carried the pointer and the catch-up did not.
|
|
2650
|
+
attachments: (offline.attachments ?? []).map(a => ({
|
|
2651
|
+
attachmentId: a.attachmentId,
|
|
2652
|
+
storageHint: a.storageHint ?? '',
|
|
2653
|
+
mimeType: a.mimeType ?? '',
|
|
2654
|
+
sizeBytes: a.sizeBytes ?? 0,
|
|
2655
|
+
sha256: a.sha256 ?? '',
|
|
2656
|
+
encryptionType: a.encryptionType === 'CLEARTEXT' ? 1 : 0,
|
|
2657
|
+
thumbnailAttachmentId: a.thumbnailAttachmentId ?? '',
|
|
2658
|
+
wrappedKeys: (a.wrappedKeys ?? []).map(k => ({
|
|
2659
|
+
deviceId: k.deviceId,
|
|
2660
|
+
wrappedKey: (0, bytes_1.fromBase64)(k.wrappedKeyBase64 ?? ''),
|
|
2661
|
+
senderPublicKey: (0, bytes_1.fromBase64)(k.senderPublicKeyBase64 ?? ''),
|
|
2662
|
+
nonce: (0, bytes_1.fromBase64)(k.nonceBase64 ?? ''),
|
|
2663
|
+
})),
|
|
2664
|
+
})),
|
|
2646
2665
|
};
|
|
2647
2666
|
// The same path a live message takes, so decryption, de-duplication and
|
|
2648
2667
|
// the callback behave identically.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -222,6 +222,12 @@ export interface DecryptedGroupMessage {
|
|
|
222
222
|
fromUserId: string;
|
|
223
223
|
timestamp: number;
|
|
224
224
|
plaintext: string;
|
|
225
|
+
/**
|
|
226
|
+
* Files this message points at, if any. The bytes are fetched separately with
|
|
227
|
+
* `downloadAttachment`; what arrives here is the pointer and the file key
|
|
228
|
+
* wrapped for this device.
|
|
229
|
+
*/
|
|
230
|
+
attachments?: import('../attachment/attachment-types').AttachmentRef[];
|
|
225
231
|
}
|
|
226
232
|
export type GroupMessageCallback = (message: DecryptedGroupMessage) => void;
|
|
227
233
|
export type GroupCallEventType = 'GROUP_CALL_INVITE' | 'GROUP_CALL_RINGING' | 'GROUP_CALL_JOIN' | 'GROUP_CALL_LEAVE' | 'GROUP_CALL_END' | 'GROUP_CALL_ENDED' | 'GROUP_CALL_PARTICIPANT_JOINED' | 'GROUP_CALL_PARTICIPANT_LEFT' | 'GROUP_CALL_PARTICIPANT_REMOVED' | 'GROUP_CALL_ALREADY_ACTIVE' | 'GROUP_CALL_SDP_OFFER' | 'GROUP_CALL_SDP_ANSWER' | 'GROUP_CALL_ICE_CANDIDATE' | 'GROUP_CALL_SCREEN_SHARE_STARTED' | 'GROUP_CALL_SCREEN_SHARE_STOPPED' | 'GROUP_CALL_HOST_TRANSFER' | 'GROUP_CALL_COHOST_APPOINT' | 'GROUP_CALL_COHOST_REVOKE' | 'GROUP_CALL_ROLE_CHANGED' | 'GROUP_CALL_MUTE_PARTICIPANT' | 'GROUP_CALL_REMOVE_PARTICIPANT' | 'GROUP_CALL_WAITING_ROOM_REQUEST' | 'GROUP_CALL_WAITING_ROOM_JOINED' | 'GROUP_CALL_WAITING_ROOM_ADMIT' | 'GROUP_CALL_WAITING_ROOM_ADMITTED' | 'GROUP_CALL_WAITING_ROOM_REJECT' | 'GROUP_CALL_WAITING_ROOM_REJECTED' | 'GROUP_CALL_JOINED' | 'GROUP_CALL_WAITING_ROOM_PENDING' | 'GROUP_CALL_HOST_REQUIRED' | 'GROUP_CALL_ROOM_CLOSED' | 'GROUP_CALL_STAGE_HAND_RAISED' | 'GROUP_CALL_STAGE_HAND_LOWERED' | 'GROUP_CALL_STAGE_PROMOTE' | 'GROUP_CALL_STAGE_DEMOTE' | 'GROUP_CALL_STAGE_QUESTION' | 'GROUP_CALL_RECORDING_STARTED' | 'GROUP_CALL_RECORDING_STOPPED' | 'GROUP_CALL_RECORDING_AVAILABLE';
|
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.34.1";
|
|
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.34.1';
|
|
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