ping-openmls-sdk 0.1.2 → 0.2.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/dist/index.cjs +202 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -12
- package/dist/index.d.ts +136 -12
- package/dist/index.js +197 -13
- package/dist/index.js.map +1 -1
- package/dist/storage/indexeddb.d.cts +1 -1
- package/dist/storage/indexeddb.d.ts +1 -1
- package/dist/transport/websocket.cjs +2 -2
- package/dist/transport/websocket.cjs.map +1 -1
- package/dist/transport/websocket.d.cts +1 -1
- package/dist/transport/websocket.d.ts +1 -1
- package/dist/transport/websocket.js +2 -2
- package/dist/transport/websocket.js.map +1 -1
- package/dist/{types-jd8CLdi_.d.cts → types-DYtsj5dy.d.cts} +28 -1
- package/dist/{types-jd8CLdi_.d.ts → types-DYtsj5dy.d.ts} +28 -1
- package/dist/worker.cjs +26 -2
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js +32 -3
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/wasm/package.json +1 -1
- package/wasm/ping_wasm.d.ts +75 -7
- package/wasm/ping_wasm.js +203 -27
- package/wasm/ping_wasm_bg.wasm +0 -0
- package/wasm/ping_wasm_bg.wasm.d.ts +12 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import { M as MessageEnvelope, S as Storage, T as Transport, C as ConversationId, a as ConversationMeta, U as UserId, b as DeviceId, I as IncomingMessage,
|
|
2
|
-
export { B as Bytes,
|
|
1
|
+
import { M as MessageEnvelope, S as Storage, T as Transport, C as ConversationId, K as KeyPackageEntry, a as ConversationMeta, L as LinkingTicket, U as UserId, b as DeviceId, I as IncomingMessage, c as CatchupAppEvent, d as CatchupSnapshotView } from './types-DYtsj5dy.cjs';
|
|
2
|
+
export { B as Bytes, e as CatchupSnapshotEntry, f as DeviceInfo, D as DiscoveredDevice, H as Hlc, g as MessageKind } from './types-DYtsj5dy.cjs';
|
|
3
3
|
export { IndexedDbStorage } from './storage/indexeddb.cjs';
|
|
4
4
|
export { WebSocketTransport } from './transport/websocket.cjs';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Ping wire contract version. Bumped on incompatible profile changes.
|
|
8
|
+
*
|
|
9
|
+
* Currently `2` — CR-6 changes the `content_hash` semantics for `Application` envelopes
|
|
10
|
+
* (now hashed over plaintext, not the MLS ciphertext). Receivers run a 90-day dual-accept
|
|
11
|
+
* window per Q-ARCH-02; see [[validate]] for the per-version routing.
|
|
12
|
+
*/
|
|
13
|
+
declare const PING_WIRE_CONTRACT_VERSION = 2;
|
|
14
|
+
/** Lowest envelope version accepted by [[validate]] during the dual-accept window. */
|
|
15
|
+
declare const WIRE_VERSION_MIN_ACCEPTED = 1;
|
|
16
|
+
/** Canonical CBOR content type. Production transports SHOULD use this. */
|
|
17
|
+
declare const CONTENT_TYPE_CBOR = "application/vnd.ping.wire.v2+cbor";
|
|
10
18
|
/** JSON projection — development and tooling only. Production MUST NOT use this. */
|
|
11
|
-
declare const CONTENT_TYPE_JSON = "application/vnd.ping.wire.
|
|
19
|
+
declare const CONTENT_TYPE_JSON = "application/vnd.ping.wire.v2+json";
|
|
20
|
+
/** Legacy v1 CBOR type, accepted during the CR-6 dual-accept window. */
|
|
21
|
+
declare const CONTENT_TYPE_CBOR_LEGACY_V1 = "application/vnd.ping.wire.v1+cbor";
|
|
22
|
+
/** Legacy v1 JSON type, accepted during the CR-6 dual-accept window. */
|
|
23
|
+
declare const CONTENT_TYPE_JSON_LEGACY_V1 = "application/vnd.ping.wire.v1+json";
|
|
12
24
|
/** Header used over transports that don't surface a content type (e.g. WebSocket). */
|
|
13
25
|
declare const PING_WIRE_CONTRACT_HEADER = "X-Ping-Wire-Contract";
|
|
14
26
|
/** Header value: `ping/<VERSION>`. */
|
|
15
|
-
declare const PING_WIRE_CONTRACT_HEADER_VALUE = "ping/
|
|
27
|
+
declare const PING_WIRE_CONTRACT_HEADER_VALUE = "ping/2";
|
|
16
28
|
type ValidationErrorKind = "UnsupportedVersion" | "BadConversationIdLen" | "BadSenderDeviceLen" | "ContentHashMismatch";
|
|
17
29
|
declare class ValidationError extends Error {
|
|
18
30
|
readonly kind: ValidationErrorKind;
|
|
@@ -24,12 +36,25 @@ declare class ValidationError extends Error {
|
|
|
24
36
|
* Receivers claiming contract conformance MUST call this before applying any envelope.
|
|
25
37
|
* External consumers using the raw envelope under their own profile are not bound by
|
|
26
38
|
* these rules.
|
|
39
|
+
*
|
|
40
|
+
* CR-6 dual-accept window: `v ∈ [WIRE_VERSION_MIN_ACCEPTED, PING_WIRE_CONTRACT_VERSION]`
|
|
41
|
+
* passes the version gate. For v=2 `Application` envelopes the content_hash is over
|
|
42
|
+
* plaintext, so this function skips that check — callers MUST run
|
|
43
|
+
* [[verifyApplicationContentHash]] after MLS decrypt to close the loop.
|
|
27
44
|
*/
|
|
28
45
|
declare function validate(env: MessageEnvelope): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Verify a v=2 `Application` envelope's content_hash against the decrypted plaintext.
|
|
48
|
+
*
|
|
49
|
+
* Caller MUST run this after MLS decrypt for application envelopes whose `v >= 2`. For
|
|
50
|
+
* v=1 envelopes or handshake kinds this is a no-op (those are covered by [[validate]]).
|
|
51
|
+
*/
|
|
52
|
+
declare function verifyApplicationContentHash(env: MessageEnvelope, plaintext: Uint8Array): Promise<void>;
|
|
29
53
|
/**
|
|
30
54
|
* True if the supplied content type negotiates the ping-wire-contract.
|
|
31
55
|
*
|
|
32
|
-
*
|
|
56
|
+
* Accepts the v2 canonical types AND the v1 legacy types during the CR-6 dual-accept
|
|
57
|
+
* window. Tolerates whitespace and parameters (e.g. `; charset=utf-8`).
|
|
33
58
|
*/
|
|
34
59
|
declare function negotiates(contentType: string | null | undefined): boolean;
|
|
35
60
|
|
|
@@ -44,7 +69,9 @@ interface ClientConfig {
|
|
|
44
69
|
interface Conversation {
|
|
45
70
|
readonly id: ConversationId;
|
|
46
71
|
send(plaintext: Uint8Array): Promise<MessageEnvelope>;
|
|
47
|
-
|
|
72
|
+
/** [CR-2] Add members by (device_id, KeyPackage) pair. Hosts typically pull these
|
|
73
|
+
* straight from `transport.discoverDevices(userId)`. */
|
|
74
|
+
addMembers(entries: KeyPackageEntry[]): Promise<void>;
|
|
48
75
|
removeMembers(leafIndexes: number[]): Promise<void>;
|
|
49
76
|
meta(): ConversationMeta;
|
|
50
77
|
}
|
|
@@ -63,6 +90,30 @@ declare class MessagingClient {
|
|
|
63
90
|
private constructor();
|
|
64
91
|
/** Generate a fresh identity. The returned bytes are a secret — store them encrypted. */
|
|
65
92
|
static generateIdentity(): Promise<Uint8Array>;
|
|
93
|
+
/**
|
|
94
|
+
* HPKE-seal a `LinkingTicket` for delivery to a new device ([CR-3]).
|
|
95
|
+
*
|
|
96
|
+
* Returns CBOR-encoded bytes safe to relay through an untrusted inbox. The new device's
|
|
97
|
+
* X25519 public key (`newDevicePub`) is exchanged out-of-band — typically as part of a
|
|
98
|
+
* QR-encoded handshake on the linking screen. Pure function; runs in an ephemeral
|
|
99
|
+
* worker so no live `MessagingClient` is required on the sender side.
|
|
100
|
+
*
|
|
101
|
+
* Suite: `DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM` (matches MLS).
|
|
102
|
+
*/
|
|
103
|
+
static sealLinkingTicket(ticket: LinkingTicket, newDevicePub: Uint8Array): Promise<Uint8Array>;
|
|
104
|
+
/**
|
|
105
|
+
* HPKE-open a sealed `LinkingTicket` on the new device ([CR-3]).
|
|
106
|
+
*
|
|
107
|
+
* The receiving device hasn't booted a `MessagingClient` yet — it only has its X25519
|
|
108
|
+
* ephemeral keypair. This static method runs the open in an ephemeral worker, returning
|
|
109
|
+
* the cleartext ticket which the host then feeds to `init()` + `consumeLinkingTicket()`.
|
|
110
|
+
*
|
|
111
|
+
* On failure the error message is deliberately generic (no discriminator between "wrong
|
|
112
|
+
* key" vs "tampered ciphertext") so a probing attacker can't tell the new device's key
|
|
113
|
+
* was the issue.
|
|
114
|
+
*/
|
|
115
|
+
static openLinkingTicket(sealed: Uint8Array, newDevicePriv: Uint8Array): Promise<LinkingTicket>;
|
|
116
|
+
private static ephemeralWorkerCall;
|
|
66
117
|
static init(cfg: ClientConfig): Promise<MessagingClient>;
|
|
67
118
|
/** Internal: route an inbound envelope to the right handler. */
|
|
68
119
|
private dispatchIncoming;
|
|
@@ -83,8 +134,81 @@ declare class MessagingClient {
|
|
|
83
134
|
listConversations(): Promise<ConversationMeta[]>;
|
|
84
135
|
syncConversations(): Promise<IncomingMessage[]>;
|
|
85
136
|
processEnvelope(envelope: MessageEnvelope): Promise<IncomingMessage | null>;
|
|
86
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Build a `LinkingTicket` for a new device.
|
|
139
|
+
*
|
|
140
|
+
* [CR-13] `lastAppEvents` is host-supplied per-conversation "what you missed" data
|
|
141
|
+
* (decrypted AppEvent bytes the new device will render before sync catches up).
|
|
142
|
+
* Pass `[]` to suppress catchup data — the new device will see an empty conversation
|
|
143
|
+
* list until normal sync runs.
|
|
144
|
+
*
|
|
145
|
+
* Caller is responsible for HPKE-sealing the returned ticket via
|
|
146
|
+
* `MessagingClient.sealLinkingTicket` before transmitting (CR-3).
|
|
147
|
+
*/
|
|
148
|
+
buildLinkingTicket(newDeviceId: DeviceId, newDeviceKp: Uint8Array, lastAppEvents?: CatchupAppEvent[]): Promise<LinkingTicket>;
|
|
149
|
+
/**
|
|
150
|
+
* Decode a `LinkingTicket.catchup_snapshot` blob ([CR-13]).
|
|
151
|
+
*
|
|
152
|
+
* Pure WASM — runs in an ephemeral worker so the new device can call this before
|
|
153
|
+
* `MessagingClient.init()` completes (just like `openLinkingTicket`). Returns the
|
|
154
|
+
* structured view: per-conversation metas + last-known AppEvent bytes.
|
|
155
|
+
*
|
|
156
|
+
* Throws on malformed or oversized inputs. Empty bytes return `null` — that's the
|
|
157
|
+
* "no catchup data" case (sender passed `[]` to buildLinkingTicket).
|
|
158
|
+
*/
|
|
159
|
+
static decodeCatchupSnapshot(snapshotBytes: Uint8Array): Promise<CatchupSnapshotView | null>;
|
|
87
160
|
consumeLinkingTicket(ticket: LinkingTicket): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Revoke a device ([CR-2]).
|
|
163
|
+
*
|
|
164
|
+
* Removes `deviceId`'s leaf from every conversation where this client locally
|
|
165
|
+
* tracked the device→leaf mapping (i.e. conversations where this client itself
|
|
166
|
+
* admitted the device via `addMembers`, or where this client is the device being
|
|
167
|
+
* revoked). The SDK has already broadcast each Commit envelope via
|
|
168
|
+
* `transport.send`; the returned array is for any additional host-side handling
|
|
169
|
+
* (audit logs, UI). An empty array is a valid outcome — the device wasn't locally
|
|
170
|
+
* known anywhere.
|
|
171
|
+
*
|
|
172
|
+
* **Scope note.** Conversations where a peer admitted the device on this client's
|
|
173
|
+
* behalf are silently skipped — the leaf index isn't locally known. Host can fall
|
|
174
|
+
* back to `transport.discoverDevices` + a manual `remove_members(leafIndex)` for
|
|
175
|
+
* those, or wait for the affected user to revoke from a device that did the admit.
|
|
176
|
+
* See `docs/architecture/multi-device.md §Device removal`.
|
|
177
|
+
*/
|
|
178
|
+
revokeDevice(deviceId: DeviceId): Promise<MessageEnvelope[]>;
|
|
179
|
+
/**
|
|
180
|
+
* Export a portable MLS state snapshot for one conversation ([CR-7]).
|
|
181
|
+
*
|
|
182
|
+
* Returned bytes can be embedded in a recovery blob or shipped through a
|
|
183
|
+
* linking ticket so another device of the same user identity can re-attach to
|
|
184
|
+
* the group via [[importStateSnapshot]]. The bytes contain past epoch secrets;
|
|
185
|
+
* never log, never persist unencrypted, wipe (`.fill(0)`) after use.
|
|
186
|
+
*/
|
|
187
|
+
exportConversationStateSnapshot(conv: ConversationId): Promise<Uint8Array>;
|
|
188
|
+
/**
|
|
189
|
+
* Import a `GroupStateSnapshot` from another device of the same user identity
|
|
190
|
+
* ([CR-7]).
|
|
191
|
+
*
|
|
192
|
+
* On success, the conversation appears in [[listConversations]] and decryption
|
|
193
|
+
* of subsequent peer-originated traffic works against the imported state. For
|
|
194
|
+
* full multi-device participation, the recovered device SHOULD then issue an
|
|
195
|
+
* MLS Update Proposal to rotate onto a fresh leaf (post-v1 follow-up — see
|
|
196
|
+
* `docs/design/CR4_CR7_PERSISTENCE.md` open question #3).
|
|
197
|
+
*/
|
|
198
|
+
importStateSnapshot(snapshotBytes: Uint8Array): Promise<ConversationId>;
|
|
199
|
+
/**
|
|
200
|
+
* Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
201
|
+
*
|
|
202
|
+
* Used to seed the ephemeral channel (`ping/ephemeral`), call-media keys
|
|
203
|
+
* (`ping/calls/media/<call_id>`), and call-ephemeral framer keys
|
|
204
|
+
* (`ping/calls/ephemeral/<call_id>`). The returned bytes are a secret — never log,
|
|
205
|
+
* persist only encrypted, and call `bytes.fill(0)` after use to wipe the typed array.
|
|
206
|
+
*
|
|
207
|
+
* Same `(conversationId, current_epoch, label, context, length)` produces the same
|
|
208
|
+
* bytes across every binding (Rust core, native UniFFI, WASM/JS). Cross-platform
|
|
209
|
+
* byte-equality is enforced by `protocol/conformance/export_secret/`.
|
|
210
|
+
*/
|
|
211
|
+
exportConversationSecret(conv: ConversationId, label: string, context: Uint8Array, length: number): Promise<Uint8Array>;
|
|
88
212
|
/** Subscribe to decrypted application messages. */
|
|
89
213
|
onMessage(handler: (m: IncomingMessage) => void): () => void;
|
|
90
214
|
/** Fired after every state-changing event for a conversation (Commit, member changes). */
|
|
@@ -100,4 +224,4 @@ declare class MessagingClient {
|
|
|
100
224
|
private handleWorker;
|
|
101
225
|
}
|
|
102
226
|
|
|
103
|
-
export { CONTENT_TYPE_CBOR, CONTENT_TYPE_JSON, type ClientConfig, type Conversation, ConversationId, ConversationMeta, DeviceId, IncomingMessage, LinkingTicket, MessageEnvelope, MessagingClient, PING_WIRE_CONTRACT_HEADER, PING_WIRE_CONTRACT_HEADER_VALUE, PING_WIRE_CONTRACT_VERSION, Storage, Transport, UserId, ValidationError, negotiates, validate };
|
|
227
|
+
export { CONTENT_TYPE_CBOR, CONTENT_TYPE_CBOR_LEGACY_V1, CONTENT_TYPE_JSON, CONTENT_TYPE_JSON_LEGACY_V1, CatchupAppEvent, CatchupSnapshotView, type ClientConfig, type Conversation, ConversationId, ConversationMeta, DeviceId, IncomingMessage, KeyPackageEntry, LinkingTicket, MessageEnvelope, MessagingClient, PING_WIRE_CONTRACT_HEADER, PING_WIRE_CONTRACT_HEADER_VALUE, PING_WIRE_CONTRACT_VERSION, Storage, Transport, UserId, ValidationError, WIRE_VERSION_MIN_ACCEPTED, negotiates, validate, verifyApplicationContentHash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import { M as MessageEnvelope, S as Storage, T as Transport, C as ConversationId, a as ConversationMeta, U as UserId, b as DeviceId, I as IncomingMessage,
|
|
2
|
-
export { B as Bytes,
|
|
1
|
+
import { M as MessageEnvelope, S as Storage, T as Transport, C as ConversationId, K as KeyPackageEntry, a as ConversationMeta, L as LinkingTicket, U as UserId, b as DeviceId, I as IncomingMessage, c as CatchupAppEvent, d as CatchupSnapshotView } from './types-DYtsj5dy.js';
|
|
2
|
+
export { B as Bytes, e as CatchupSnapshotEntry, f as DeviceInfo, D as DiscoveredDevice, H as Hlc, g as MessageKind } from './types-DYtsj5dy.js';
|
|
3
3
|
export { IndexedDbStorage } from './storage/indexeddb.js';
|
|
4
4
|
export { WebSocketTransport } from './transport/websocket.js';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Ping wire contract version. Bumped on incompatible profile changes.
|
|
8
|
+
*
|
|
9
|
+
* Currently `2` — CR-6 changes the `content_hash` semantics for `Application` envelopes
|
|
10
|
+
* (now hashed over plaintext, not the MLS ciphertext). Receivers run a 90-day dual-accept
|
|
11
|
+
* window per Q-ARCH-02; see [[validate]] for the per-version routing.
|
|
12
|
+
*/
|
|
13
|
+
declare const PING_WIRE_CONTRACT_VERSION = 2;
|
|
14
|
+
/** Lowest envelope version accepted by [[validate]] during the dual-accept window. */
|
|
15
|
+
declare const WIRE_VERSION_MIN_ACCEPTED = 1;
|
|
16
|
+
/** Canonical CBOR content type. Production transports SHOULD use this. */
|
|
17
|
+
declare const CONTENT_TYPE_CBOR = "application/vnd.ping.wire.v2+cbor";
|
|
10
18
|
/** JSON projection — development and tooling only. Production MUST NOT use this. */
|
|
11
|
-
declare const CONTENT_TYPE_JSON = "application/vnd.ping.wire.
|
|
19
|
+
declare const CONTENT_TYPE_JSON = "application/vnd.ping.wire.v2+json";
|
|
20
|
+
/** Legacy v1 CBOR type, accepted during the CR-6 dual-accept window. */
|
|
21
|
+
declare const CONTENT_TYPE_CBOR_LEGACY_V1 = "application/vnd.ping.wire.v1+cbor";
|
|
22
|
+
/** Legacy v1 JSON type, accepted during the CR-6 dual-accept window. */
|
|
23
|
+
declare const CONTENT_TYPE_JSON_LEGACY_V1 = "application/vnd.ping.wire.v1+json";
|
|
12
24
|
/** Header used over transports that don't surface a content type (e.g. WebSocket). */
|
|
13
25
|
declare const PING_WIRE_CONTRACT_HEADER = "X-Ping-Wire-Contract";
|
|
14
26
|
/** Header value: `ping/<VERSION>`. */
|
|
15
|
-
declare const PING_WIRE_CONTRACT_HEADER_VALUE = "ping/
|
|
27
|
+
declare const PING_WIRE_CONTRACT_HEADER_VALUE = "ping/2";
|
|
16
28
|
type ValidationErrorKind = "UnsupportedVersion" | "BadConversationIdLen" | "BadSenderDeviceLen" | "ContentHashMismatch";
|
|
17
29
|
declare class ValidationError extends Error {
|
|
18
30
|
readonly kind: ValidationErrorKind;
|
|
@@ -24,12 +36,25 @@ declare class ValidationError extends Error {
|
|
|
24
36
|
* Receivers claiming contract conformance MUST call this before applying any envelope.
|
|
25
37
|
* External consumers using the raw envelope under their own profile are not bound by
|
|
26
38
|
* these rules.
|
|
39
|
+
*
|
|
40
|
+
* CR-6 dual-accept window: `v ∈ [WIRE_VERSION_MIN_ACCEPTED, PING_WIRE_CONTRACT_VERSION]`
|
|
41
|
+
* passes the version gate. For v=2 `Application` envelopes the content_hash is over
|
|
42
|
+
* plaintext, so this function skips that check — callers MUST run
|
|
43
|
+
* [[verifyApplicationContentHash]] after MLS decrypt to close the loop.
|
|
27
44
|
*/
|
|
28
45
|
declare function validate(env: MessageEnvelope): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Verify a v=2 `Application` envelope's content_hash against the decrypted plaintext.
|
|
48
|
+
*
|
|
49
|
+
* Caller MUST run this after MLS decrypt for application envelopes whose `v >= 2`. For
|
|
50
|
+
* v=1 envelopes or handshake kinds this is a no-op (those are covered by [[validate]]).
|
|
51
|
+
*/
|
|
52
|
+
declare function verifyApplicationContentHash(env: MessageEnvelope, plaintext: Uint8Array): Promise<void>;
|
|
29
53
|
/**
|
|
30
54
|
* True if the supplied content type negotiates the ping-wire-contract.
|
|
31
55
|
*
|
|
32
|
-
*
|
|
56
|
+
* Accepts the v2 canonical types AND the v1 legacy types during the CR-6 dual-accept
|
|
57
|
+
* window. Tolerates whitespace and parameters (e.g. `; charset=utf-8`).
|
|
33
58
|
*/
|
|
34
59
|
declare function negotiates(contentType: string | null | undefined): boolean;
|
|
35
60
|
|
|
@@ -44,7 +69,9 @@ interface ClientConfig {
|
|
|
44
69
|
interface Conversation {
|
|
45
70
|
readonly id: ConversationId;
|
|
46
71
|
send(plaintext: Uint8Array): Promise<MessageEnvelope>;
|
|
47
|
-
|
|
72
|
+
/** [CR-2] Add members by (device_id, KeyPackage) pair. Hosts typically pull these
|
|
73
|
+
* straight from `transport.discoverDevices(userId)`. */
|
|
74
|
+
addMembers(entries: KeyPackageEntry[]): Promise<void>;
|
|
48
75
|
removeMembers(leafIndexes: number[]): Promise<void>;
|
|
49
76
|
meta(): ConversationMeta;
|
|
50
77
|
}
|
|
@@ -63,6 +90,30 @@ declare class MessagingClient {
|
|
|
63
90
|
private constructor();
|
|
64
91
|
/** Generate a fresh identity. The returned bytes are a secret — store them encrypted. */
|
|
65
92
|
static generateIdentity(): Promise<Uint8Array>;
|
|
93
|
+
/**
|
|
94
|
+
* HPKE-seal a `LinkingTicket` for delivery to a new device ([CR-3]).
|
|
95
|
+
*
|
|
96
|
+
* Returns CBOR-encoded bytes safe to relay through an untrusted inbox. The new device's
|
|
97
|
+
* X25519 public key (`newDevicePub`) is exchanged out-of-band — typically as part of a
|
|
98
|
+
* QR-encoded handshake on the linking screen. Pure function; runs in an ephemeral
|
|
99
|
+
* worker so no live `MessagingClient` is required on the sender side.
|
|
100
|
+
*
|
|
101
|
+
* Suite: `DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM` (matches MLS).
|
|
102
|
+
*/
|
|
103
|
+
static sealLinkingTicket(ticket: LinkingTicket, newDevicePub: Uint8Array): Promise<Uint8Array>;
|
|
104
|
+
/**
|
|
105
|
+
* HPKE-open a sealed `LinkingTicket` on the new device ([CR-3]).
|
|
106
|
+
*
|
|
107
|
+
* The receiving device hasn't booted a `MessagingClient` yet — it only has its X25519
|
|
108
|
+
* ephemeral keypair. This static method runs the open in an ephemeral worker, returning
|
|
109
|
+
* the cleartext ticket which the host then feeds to `init()` + `consumeLinkingTicket()`.
|
|
110
|
+
*
|
|
111
|
+
* On failure the error message is deliberately generic (no discriminator between "wrong
|
|
112
|
+
* key" vs "tampered ciphertext") so a probing attacker can't tell the new device's key
|
|
113
|
+
* was the issue.
|
|
114
|
+
*/
|
|
115
|
+
static openLinkingTicket(sealed: Uint8Array, newDevicePriv: Uint8Array): Promise<LinkingTicket>;
|
|
116
|
+
private static ephemeralWorkerCall;
|
|
66
117
|
static init(cfg: ClientConfig): Promise<MessagingClient>;
|
|
67
118
|
/** Internal: route an inbound envelope to the right handler. */
|
|
68
119
|
private dispatchIncoming;
|
|
@@ -83,8 +134,81 @@ declare class MessagingClient {
|
|
|
83
134
|
listConversations(): Promise<ConversationMeta[]>;
|
|
84
135
|
syncConversations(): Promise<IncomingMessage[]>;
|
|
85
136
|
processEnvelope(envelope: MessageEnvelope): Promise<IncomingMessage | null>;
|
|
86
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Build a `LinkingTicket` for a new device.
|
|
139
|
+
*
|
|
140
|
+
* [CR-13] `lastAppEvents` is host-supplied per-conversation "what you missed" data
|
|
141
|
+
* (decrypted AppEvent bytes the new device will render before sync catches up).
|
|
142
|
+
* Pass `[]` to suppress catchup data — the new device will see an empty conversation
|
|
143
|
+
* list until normal sync runs.
|
|
144
|
+
*
|
|
145
|
+
* Caller is responsible for HPKE-sealing the returned ticket via
|
|
146
|
+
* `MessagingClient.sealLinkingTicket` before transmitting (CR-3).
|
|
147
|
+
*/
|
|
148
|
+
buildLinkingTicket(newDeviceId: DeviceId, newDeviceKp: Uint8Array, lastAppEvents?: CatchupAppEvent[]): Promise<LinkingTicket>;
|
|
149
|
+
/**
|
|
150
|
+
* Decode a `LinkingTicket.catchup_snapshot` blob ([CR-13]).
|
|
151
|
+
*
|
|
152
|
+
* Pure WASM — runs in an ephemeral worker so the new device can call this before
|
|
153
|
+
* `MessagingClient.init()` completes (just like `openLinkingTicket`). Returns the
|
|
154
|
+
* structured view: per-conversation metas + last-known AppEvent bytes.
|
|
155
|
+
*
|
|
156
|
+
* Throws on malformed or oversized inputs. Empty bytes return `null` — that's the
|
|
157
|
+
* "no catchup data" case (sender passed `[]` to buildLinkingTicket).
|
|
158
|
+
*/
|
|
159
|
+
static decodeCatchupSnapshot(snapshotBytes: Uint8Array): Promise<CatchupSnapshotView | null>;
|
|
87
160
|
consumeLinkingTicket(ticket: LinkingTicket): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Revoke a device ([CR-2]).
|
|
163
|
+
*
|
|
164
|
+
* Removes `deviceId`'s leaf from every conversation where this client locally
|
|
165
|
+
* tracked the device→leaf mapping (i.e. conversations where this client itself
|
|
166
|
+
* admitted the device via `addMembers`, or where this client is the device being
|
|
167
|
+
* revoked). The SDK has already broadcast each Commit envelope via
|
|
168
|
+
* `transport.send`; the returned array is for any additional host-side handling
|
|
169
|
+
* (audit logs, UI). An empty array is a valid outcome — the device wasn't locally
|
|
170
|
+
* known anywhere.
|
|
171
|
+
*
|
|
172
|
+
* **Scope note.** Conversations where a peer admitted the device on this client's
|
|
173
|
+
* behalf are silently skipped — the leaf index isn't locally known. Host can fall
|
|
174
|
+
* back to `transport.discoverDevices` + a manual `remove_members(leafIndex)` for
|
|
175
|
+
* those, or wait for the affected user to revoke from a device that did the admit.
|
|
176
|
+
* See `docs/architecture/multi-device.md §Device removal`.
|
|
177
|
+
*/
|
|
178
|
+
revokeDevice(deviceId: DeviceId): Promise<MessageEnvelope[]>;
|
|
179
|
+
/**
|
|
180
|
+
* Export a portable MLS state snapshot for one conversation ([CR-7]).
|
|
181
|
+
*
|
|
182
|
+
* Returned bytes can be embedded in a recovery blob or shipped through a
|
|
183
|
+
* linking ticket so another device of the same user identity can re-attach to
|
|
184
|
+
* the group via [[importStateSnapshot]]. The bytes contain past epoch secrets;
|
|
185
|
+
* never log, never persist unencrypted, wipe (`.fill(0)`) after use.
|
|
186
|
+
*/
|
|
187
|
+
exportConversationStateSnapshot(conv: ConversationId): Promise<Uint8Array>;
|
|
188
|
+
/**
|
|
189
|
+
* Import a `GroupStateSnapshot` from another device of the same user identity
|
|
190
|
+
* ([CR-7]).
|
|
191
|
+
*
|
|
192
|
+
* On success, the conversation appears in [[listConversations]] and decryption
|
|
193
|
+
* of subsequent peer-originated traffic works against the imported state. For
|
|
194
|
+
* full multi-device participation, the recovered device SHOULD then issue an
|
|
195
|
+
* MLS Update Proposal to rotate onto a fresh leaf (post-v1 follow-up — see
|
|
196
|
+
* `docs/design/CR4_CR7_PERSISTENCE.md` open question #3).
|
|
197
|
+
*/
|
|
198
|
+
importStateSnapshot(snapshotBytes: Uint8Array): Promise<ConversationId>;
|
|
199
|
+
/**
|
|
200
|
+
* Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
201
|
+
*
|
|
202
|
+
* Used to seed the ephemeral channel (`ping/ephemeral`), call-media keys
|
|
203
|
+
* (`ping/calls/media/<call_id>`), and call-ephemeral framer keys
|
|
204
|
+
* (`ping/calls/ephemeral/<call_id>`). The returned bytes are a secret — never log,
|
|
205
|
+
* persist only encrypted, and call `bytes.fill(0)` after use to wipe the typed array.
|
|
206
|
+
*
|
|
207
|
+
* Same `(conversationId, current_epoch, label, context, length)` produces the same
|
|
208
|
+
* bytes across every binding (Rust core, native UniFFI, WASM/JS). Cross-platform
|
|
209
|
+
* byte-equality is enforced by `protocol/conformance/export_secret/`.
|
|
210
|
+
*/
|
|
211
|
+
exportConversationSecret(conv: ConversationId, label: string, context: Uint8Array, length: number): Promise<Uint8Array>;
|
|
88
212
|
/** Subscribe to decrypted application messages. */
|
|
89
213
|
onMessage(handler: (m: IncomingMessage) => void): () => void;
|
|
90
214
|
/** Fired after every state-changing event for a conversation (Commit, member changes). */
|
|
@@ -100,4 +224,4 @@ declare class MessagingClient {
|
|
|
100
224
|
private handleWorker;
|
|
101
225
|
}
|
|
102
226
|
|
|
103
|
-
export { CONTENT_TYPE_CBOR, CONTENT_TYPE_JSON, type ClientConfig, type Conversation, ConversationId, ConversationMeta, DeviceId, IncomingMessage, LinkingTicket, MessageEnvelope, MessagingClient, PING_WIRE_CONTRACT_HEADER, PING_WIRE_CONTRACT_HEADER_VALUE, PING_WIRE_CONTRACT_VERSION, Storage, Transport, UserId, ValidationError, negotiates, validate };
|
|
227
|
+
export { CONTENT_TYPE_CBOR, CONTENT_TYPE_CBOR_LEGACY_V1, CONTENT_TYPE_JSON, CONTENT_TYPE_JSON_LEGACY_V1, CatchupAppEvent, CatchupSnapshotView, type ClientConfig, type Conversation, ConversationId, ConversationMeta, DeviceId, IncomingMessage, KeyPackageEntry, LinkingTicket, MessageEnvelope, MessagingClient, PING_WIRE_CONTRACT_HEADER, PING_WIRE_CONTRACT_HEADER_VALUE, PING_WIRE_CONTRACT_VERSION, Storage, Transport, UserId, ValidationError, WIRE_VERSION_MIN_ACCEPTED, negotiates, validate, verifyApplicationContentHash };
|