ping-openmls-sdk-react-native-macos 0.2.3 → 0.6.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/Frameworks/libping_ffi.a +0 -0
- package/ios/Generated.swift +420 -25
- package/ios/PingNativeModule.m +31 -1
- package/ios/PingNativeModule.swift +139 -17
- package/ios/TypeBridge.swift +46 -0
- package/ios/pingFFI.h +69 -3
- package/package.json +1 -1
- package/src/MessagingClient.ts +140 -7
- package/src/NativePing.ts +70 -6
|
@@ -211,11 +211,14 @@ public final class PingNative: RCTEventEmitter {
|
|
|
211
211
|
/// `nowMs` is the wall-clock at the call site (Hermes can't pass UInt64 across the
|
|
212
212
|
/// bridge precisely so we accept Double and truncate; valid for the next ~285,000
|
|
213
213
|
/// years of Unix time).
|
|
214
|
-
@objc(initClient:deviceLabel:nowMs:resolver:rejecter:)
|
|
214
|
+
@objc(initClient:deviceLabel:nowMs:sqlitePath:sqliteEncryptionKeyB64:deviceSigningSecretKeyB64:resolver:rejecter:)
|
|
215
215
|
public func initClient(
|
|
216
216
|
_ identityB64: String,
|
|
217
217
|
deviceLabel: String,
|
|
218
218
|
nowMs: Double,
|
|
219
|
+
sqlitePath: String?,
|
|
220
|
+
sqliteEncryptionKeyB64: String?,
|
|
221
|
+
deviceSigningSecretKeyB64: String?,
|
|
219
222
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
220
223
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
221
224
|
) {
|
|
@@ -225,6 +228,35 @@ public final class PingNative: RCTEventEmitter {
|
|
|
225
228
|
reject("InvalidIdentity", "identity bytes failed base64 decode", nil)
|
|
226
229
|
return
|
|
227
230
|
}
|
|
231
|
+
// [CR-4] Optional SQLCipher key. Decode if supplied; reject if it
|
|
232
|
+
// decodes but isn't 32 bytes (host bug — better loud than silent).
|
|
233
|
+
var sqliteKey: Data? = nil
|
|
234
|
+
if let keyB64 = sqliteEncryptionKeyB64,
|
|
235
|
+
let keyData = Data(base64Encoded: keyB64) {
|
|
236
|
+
guard keyData.count == 32 else {
|
|
237
|
+
reject("InvalidSqliteKey", "sqliteEncryptionKey must be 32 bytes, got \(keyData.count)", nil)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
sqliteKey = keyData
|
|
241
|
+
}
|
|
242
|
+
// Optional Ed25519 device signing secret. Same 32-byte
|
|
243
|
+
// shape as the SQLCipher key; loud-reject on mismatched
|
|
244
|
+
// length so callers find the bug at init time rather
|
|
245
|
+
// than via a server-side `sender_device_mismatch`
|
|
246
|
+
// hours later.
|
|
247
|
+
var deviceSigningSecret: Data? = nil
|
|
248
|
+
if let secretB64 = deviceSigningSecretKeyB64,
|
|
249
|
+
let secretData = Data(base64Encoded: secretB64) {
|
|
250
|
+
guard secretData.count == 32 else {
|
|
251
|
+
reject(
|
|
252
|
+
"InvalidDeviceSigningKey",
|
|
253
|
+
"deviceSigningSecretKey must be 32 bytes, got \(secretData.count)",
|
|
254
|
+
nil
|
|
255
|
+
)
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
deviceSigningSecret = secretData
|
|
259
|
+
}
|
|
228
260
|
// UniFFI's `[Name=init]` constructor generates a static factory named
|
|
229
261
|
// `init` (backquoted because Swift reserves the bare identifier for
|
|
230
262
|
// designated initializers).
|
|
@@ -233,7 +265,10 @@ public final class PingNative: RCTEventEmitter {
|
|
|
233
265
|
deviceLabel: deviceLabel,
|
|
234
266
|
storage: self.storageBridge,
|
|
235
267
|
transport: self.transportBridge,
|
|
236
|
-
nowMs: UInt64(nowMs)
|
|
268
|
+
nowMs: UInt64(nowMs),
|
|
269
|
+
sqlitePath: sqlitePath,
|
|
270
|
+
sqliteEncryptionKey: sqliteKey,
|
|
271
|
+
deviceSigningSecretKey: deviceSigningSecret
|
|
237
272
|
)
|
|
238
273
|
self.client = c
|
|
239
274
|
resolve(true)
|
|
@@ -373,12 +408,13 @@ public final class PingNative: RCTEventEmitter {
|
|
|
373
408
|
}
|
|
374
409
|
}
|
|
375
410
|
|
|
376
|
-
/// Add members
|
|
377
|
-
///
|
|
378
|
-
|
|
411
|
+
/// Add members ([CR-2]). `entries` is an array of `{ deviceId: [Int], keyPackage:
|
|
412
|
+
/// [Int] }` dicts — the device-id pairing lets the SDK persist a per-conversation
|
|
413
|
+
/// device→leaf map that `revokeDeviceNative` later consumes.
|
|
414
|
+
@objc(addMembers:entries:nowMs:resolver:rejecter:)
|
|
379
415
|
public func addMembersNative(
|
|
380
416
|
_ conversationIdBytes: NSArray,
|
|
381
|
-
|
|
417
|
+
entries entriesArr: NSArray,
|
|
382
418
|
nowMs: Double,
|
|
383
419
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
384
420
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
@@ -390,13 +426,10 @@ public final class PingNative: RCTEventEmitter {
|
|
|
390
426
|
}
|
|
391
427
|
do {
|
|
392
428
|
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
393
|
-
|
|
394
|
-
for kp in keyPackagesArr {
|
|
395
|
-
kps.append(try TypeBridge.decodeBytesOrThrow(kp, field: "keyPackage"))
|
|
396
|
-
}
|
|
429
|
+
let entries = try TypeBridge.decodeKeyPackageEntryArray(entriesArr)
|
|
397
430
|
try await client.addMembers(
|
|
398
431
|
conversationId: convId,
|
|
399
|
-
|
|
432
|
+
entries: entries,
|
|
400
433
|
nowMs: UInt64(nowMs)
|
|
401
434
|
)
|
|
402
435
|
resolve(NSNull())
|
|
@@ -555,10 +588,15 @@ public final class PingNative: RCTEventEmitter {
|
|
|
555
588
|
/// Build a linking ticket on the existing device (E) for a new device (N) that
|
|
556
589
|
/// just published its KeyPackage. The ticket bundles a Welcome to E's DeviceGroup
|
|
557
590
|
/// plus a catch-up snapshot. N consumes the ticket via `consumeLinkingTicket`.
|
|
558
|
-
|
|
591
|
+
/// Build a linking ticket ([CR-13] populates `catchup_snapshot` from
|
|
592
|
+
/// `lastAppEvents`). `lastAppEvents` is an array of `{ conversationId: [Int],
|
|
593
|
+
/// appEventBytes: [Int] }` — host-supplied "what you missed" data the new
|
|
594
|
+
/// device renders before sync catches up. Empty array suppresses catchup.
|
|
595
|
+
@objc(buildLinkingTicket:newDeviceKp:lastAppEvents:nowMs:resolver:rejecter:)
|
|
559
596
|
public func buildLinkingTicketNative(
|
|
560
597
|
_ newDeviceIdBytes: NSArray,
|
|
561
598
|
newDeviceKp newDeviceKpBytes: NSArray,
|
|
599
|
+
lastAppEvents lastAppEventsArr: NSArray,
|
|
562
600
|
nowMs: Double,
|
|
563
601
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
564
602
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
@@ -571,9 +609,11 @@ public final class PingNative: RCTEventEmitter {
|
|
|
571
609
|
do {
|
|
572
610
|
let newDeviceId = try TypeBridge.decodeDeviceId(newDeviceIdBytes)
|
|
573
611
|
let newKp = try TypeBridge.decodeBytesOrThrow(newDeviceKpBytes, field: "newDeviceKp")
|
|
612
|
+
let events = try TypeBridge.decodeCatchupAppEventArray(lastAppEventsArr)
|
|
574
613
|
let ticket = try await client.buildLinkingTicket(
|
|
575
614
|
newDeviceId: newDeviceId,
|
|
576
615
|
newDeviceKp: newKp,
|
|
616
|
+
lastAppEvents: events,
|
|
577
617
|
nowMs: UInt64(nowMs)
|
|
578
618
|
)
|
|
579
619
|
resolve(TypeBridge.encodeLinkingTicket(ticket))
|
|
@@ -610,9 +650,9 @@ public final class PingNative: RCTEventEmitter {
|
|
|
610
650
|
}
|
|
611
651
|
}
|
|
612
652
|
|
|
613
|
-
/// Revoke a device
|
|
614
|
-
/// conversation the device
|
|
615
|
-
///
|
|
653
|
+
/// Revoke a device ([CR-2]). Returns the array of Commit envelopes the SDK
|
|
654
|
+
/// produced — one per conversation the device was a locally-known leaf in.
|
|
655
|
+
/// Empty array means the device wasn't locally known (scope limit per CR-2).
|
|
616
656
|
@objc(revokeDevice:nowMs:resolver:rejecter:)
|
|
617
657
|
public func revokeDeviceNative(
|
|
618
658
|
_ deviceIdBytes: NSArray,
|
|
@@ -627,14 +667,96 @@ public final class PingNative: RCTEventEmitter {
|
|
|
627
667
|
}
|
|
628
668
|
do {
|
|
629
669
|
let deviceId = try TypeBridge.decodeDeviceId(deviceIdBytes)
|
|
630
|
-
try await client.revokeDevice(deviceId: deviceId, nowMs: UInt64(nowMs))
|
|
631
|
-
resolve(
|
|
670
|
+
let envs = try await client.revokeDevice(deviceId: deviceId, nowMs: UInt64(nowMs))
|
|
671
|
+
resolve(TypeBridge.encodeEnvelopeArray(envs))
|
|
632
672
|
} catch {
|
|
633
673
|
reject("RevokeFailed", String(describing: error), error)
|
|
634
674
|
}
|
|
635
675
|
}
|
|
636
676
|
}
|
|
637
677
|
|
|
678
|
+
// MARK: - CR-8 / CR-7 helpers
|
|
679
|
+
|
|
680
|
+
/// Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
681
|
+
/// Returned bytes are a secret — the JS side is responsible for wiping them.
|
|
682
|
+
@objc(exportConversationSecret:label:context:length:resolver:rejecter:)
|
|
683
|
+
public func exportConversationSecretNative(
|
|
684
|
+
_ conversationIdBytes: NSArray,
|
|
685
|
+
label: String,
|
|
686
|
+
context contextBytes: NSArray,
|
|
687
|
+
length: NSNumber,
|
|
688
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
689
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
690
|
+
) {
|
|
691
|
+
guard let client = self.client else {
|
|
692
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
693
|
+
return
|
|
694
|
+
}
|
|
695
|
+
do {
|
|
696
|
+
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
697
|
+
let context = try TypeBridge.decodeBytesOrThrow(contextBytes, field: "context")
|
|
698
|
+
let bytes = try client.exportConversationSecret(
|
|
699
|
+
conversationId: convId,
|
|
700
|
+
label: label,
|
|
701
|
+
context: context,
|
|
702
|
+
length: length.uint32Value
|
|
703
|
+
)
|
|
704
|
+
resolve(TypeBridge.encodeBytes(bytes))
|
|
705
|
+
} catch {
|
|
706
|
+
reject("ExportSecretFailed", String(describing: error), error)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/// Export a `GroupStateSnapshot` for one conversation ([CR-7]).
|
|
711
|
+
@objc(exportConversationStateSnapshot:nowMs:resolver:rejecter:)
|
|
712
|
+
public func exportConversationStateSnapshotNative(
|
|
713
|
+
_ conversationIdBytes: NSArray,
|
|
714
|
+
nowMs: Double,
|
|
715
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
716
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
717
|
+
) {
|
|
718
|
+
guard let client = self.client else {
|
|
719
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
720
|
+
return
|
|
721
|
+
}
|
|
722
|
+
do {
|
|
723
|
+
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
724
|
+
let bytes = try client.exportConversationStateSnapshot(
|
|
725
|
+
conversationId: convId,
|
|
726
|
+
nowMs: UInt64(nowMs)
|
|
727
|
+
)
|
|
728
|
+
resolve(TypeBridge.encodeBytes(bytes))
|
|
729
|
+
} catch {
|
|
730
|
+
reject("ExportStateSnapshotFailed", String(describing: error), error)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/// Import a `GroupStateSnapshot` from another device ([CR-7]).
|
|
735
|
+
@objc(importStateSnapshot:nowMs:resolver:rejecter:)
|
|
736
|
+
public func importStateSnapshotNative(
|
|
737
|
+
_ snapshotBytes: NSArray,
|
|
738
|
+
nowMs: Double,
|
|
739
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
740
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
741
|
+
) {
|
|
742
|
+
Task {
|
|
743
|
+
guard let client = self.client else {
|
|
744
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
745
|
+
return
|
|
746
|
+
}
|
|
747
|
+
do {
|
|
748
|
+
let bytes = try TypeBridge.decodeBytesOrThrow(snapshotBytes, field: "snapshotBytes")
|
|
749
|
+
let convId = try await client.importStateSnapshot(
|
|
750
|
+
snapshotBytes: bytes,
|
|
751
|
+
nowMs: UInt64(nowMs)
|
|
752
|
+
)
|
|
753
|
+
resolve(TypeBridge.encodeConversationId(convId))
|
|
754
|
+
} catch {
|
|
755
|
+
reject("ImportStateSnapshotFailed", String(describing: error), error)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
638
760
|
// MARK: - macOS clipboard helper (stage 5 polish)
|
|
639
761
|
|
|
640
762
|
/// Write a string to the macOS pasteboard. Hermes doesn't ship `navigator.clipboard`
|
package/ios/TypeBridge.swift
CHANGED
|
@@ -294,4 +294,50 @@ enum TypeBridge {
|
|
|
294
294
|
catchupSnapshot: snapshot
|
|
295
295
|
)
|
|
296
296
|
}
|
|
297
|
+
|
|
298
|
+
// MARK: - [CR-2] KeyPackageEntry
|
|
299
|
+
|
|
300
|
+
/// Decode `{ deviceId: [Int], keyPackage: [Int] }` into UniFFI's `KeyPackageEntry`.
|
|
301
|
+
/// Used by `addMembersNative` — each entry pairs a device with its KeyPackage.
|
|
302
|
+
static func decodeKeyPackageEntry(_ value: Any?) throws -> KeyPackageEntry {
|
|
303
|
+
guard let dict = value as? [String: Any] else {
|
|
304
|
+
throw BridgeError.decodeFailure("expected KeyPackageEntry dict")
|
|
305
|
+
}
|
|
306
|
+
let deviceId = try decodeDeviceId(dict["deviceId"] ?? dict["device_id"])
|
|
307
|
+
let keyPackage = try decodeBytesOrThrow(
|
|
308
|
+
dict["keyPackage"] ?? dict["key_package"],
|
|
309
|
+
field: "keyPackage"
|
|
310
|
+
)
|
|
311
|
+
return KeyPackageEntry(deviceId: deviceId, keyPackage: keyPackage)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static func decodeKeyPackageEntryArray(_ value: Any?) throws -> [KeyPackageEntry] {
|
|
315
|
+
guard let arr = value as? [Any] else {
|
|
316
|
+
throw BridgeError.decodeFailure("expected KeyPackageEntry array")
|
|
317
|
+
}
|
|
318
|
+
return try arr.map { try decodeKeyPackageEntry($0) }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// MARK: - [CR-13] CatchupAppEvent
|
|
322
|
+
|
|
323
|
+
/// Decode `{ conversationId: [Int], appEventBytes: [Int] }` into UniFFI's
|
|
324
|
+
/// `CatchupAppEvent`. Used by `buildLinkingTicketNative`.
|
|
325
|
+
static func decodeCatchupAppEvent(_ value: Any?) throws -> CatchupAppEvent {
|
|
326
|
+
guard let dict = value as? [String: Any] else {
|
|
327
|
+
throw BridgeError.decodeFailure("expected CatchupAppEvent dict")
|
|
328
|
+
}
|
|
329
|
+
let convId = try decodeConversationId(dict["conversationId"] ?? dict["conversation_id"])
|
|
330
|
+
let bytes = try decodeBytesOrThrow(
|
|
331
|
+
dict["appEventBytes"] ?? dict["app_event_bytes"],
|
|
332
|
+
field: "appEventBytes"
|
|
333
|
+
)
|
|
334
|
+
return CatchupAppEvent(conversationId: convId, appEventBytes: bytes)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static func decodeCatchupAppEventArray(_ value: Any?) throws -> [CatchupAppEvent] {
|
|
338
|
+
guard let arr = value as? [Any] else {
|
|
339
|
+
throw BridgeError.decodeFailure("expected CatchupAppEvent array")
|
|
340
|
+
}
|
|
341
|
+
return try arr.map { try decodeCatchupAppEvent($0) }
|
|
342
|
+
}
|
|
297
343
|
}
|
package/ios/pingFFI.h
CHANGED
|
@@ -374,17 +374,17 @@ void uniffi_ping_ffi_fn_free_messagingclient(void*_Nonnull ptr, RustCallStatus *
|
|
|
374
374
|
#endif
|
|
375
375
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_CONSTRUCTOR_MESSAGINGCLIENT_INIT
|
|
376
376
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_CONSTRUCTOR_MESSAGINGCLIENT_INIT
|
|
377
|
-
uint64_t uniffi_ping_ffi_fn_constructor_messagingclient_init(RustBuffer identity_export, RustBuffer device_label, void*_Nonnull storage, void*_Nonnull transport, uint64_t now_ms
|
|
377
|
+
uint64_t uniffi_ping_ffi_fn_constructor_messagingclient_init(RustBuffer identity_export, RustBuffer device_label, void*_Nonnull storage, void*_Nonnull transport, uint64_t now_ms, RustBuffer sqlite_path, RustBuffer sqlite_encryption_key, RustBuffer device_signing_secret_key
|
|
378
378
|
);
|
|
379
379
|
#endif
|
|
380
380
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_ADD_MEMBERS
|
|
381
381
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_ADD_MEMBERS
|
|
382
|
-
uint64_t uniffi_ping_ffi_fn_method_messagingclient_add_members(void*_Nonnull ptr, RustBuffer conversation_id, RustBuffer
|
|
382
|
+
uint64_t uniffi_ping_ffi_fn_method_messagingclient_add_members(void*_Nonnull ptr, RustBuffer conversation_id, RustBuffer entries, uint64_t now_ms
|
|
383
383
|
);
|
|
384
384
|
#endif
|
|
385
385
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_BUILD_LINKING_TICKET
|
|
386
386
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_BUILD_LINKING_TICKET
|
|
387
|
-
uint64_t uniffi_ping_ffi_fn_method_messagingclient_build_linking_ticket(void*_Nonnull ptr, RustBuffer new_device_id, RustBuffer new_device_kp, uint64_t now_ms
|
|
387
|
+
uint64_t uniffi_ping_ffi_fn_method_messagingclient_build_linking_ticket(void*_Nonnull ptr, RustBuffer new_device_id, RustBuffer new_device_kp, RustBuffer last_app_events, uint64_t now_ms
|
|
388
388
|
);
|
|
389
389
|
#endif
|
|
390
390
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_CONSUME_LINKING_TICKET
|
|
@@ -407,11 +407,26 @@ RustBuffer uniffi_ping_ffi_fn_method_messagingclient_device_id(void*_Nonnull ptr
|
|
|
407
407
|
RustBuffer uniffi_ping_ffi_fn_method_messagingclient_device_info(void*_Nonnull ptr, uint64_t now_ms, RustCallStatus *_Nonnull out_status
|
|
408
408
|
);
|
|
409
409
|
#endif
|
|
410
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_SECRET
|
|
411
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_SECRET
|
|
412
|
+
RustBuffer uniffi_ping_ffi_fn_method_messagingclient_export_conversation_secret(void*_Nonnull ptr, RustBuffer conversation_id, RustBuffer label, RustBuffer context, uint32_t length, RustCallStatus *_Nonnull out_status
|
|
413
|
+
);
|
|
414
|
+
#endif
|
|
415
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_STATE_SNAPSHOT
|
|
416
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_STATE_SNAPSHOT
|
|
417
|
+
RustBuffer uniffi_ping_ffi_fn_method_messagingclient_export_conversation_state_snapshot(void*_Nonnull ptr, RustBuffer conversation_id, uint64_t now_ms, RustCallStatus *_Nonnull out_status
|
|
418
|
+
);
|
|
419
|
+
#endif
|
|
410
420
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_FRESH_KEY_PACKAGE
|
|
411
421
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_FRESH_KEY_PACKAGE
|
|
412
422
|
RustBuffer uniffi_ping_ffi_fn_method_messagingclient_fresh_key_package(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
|
|
413
423
|
);
|
|
414
424
|
#endif
|
|
425
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_IMPORT_STATE_SNAPSHOT
|
|
426
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_IMPORT_STATE_SNAPSHOT
|
|
427
|
+
uint64_t uniffi_ping_ffi_fn_method_messagingclient_import_state_snapshot(void*_Nonnull ptr, RustBuffer snapshot_bytes, uint64_t now_ms
|
|
428
|
+
);
|
|
429
|
+
#endif
|
|
415
430
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_JOIN_CONVERSATION
|
|
416
431
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_JOIN_CONVERSATION
|
|
417
432
|
uint64_t uniffi_ping_ffi_fn_method_messagingclient_join_conversation(void*_Nonnull ptr, RustBuffer welcome, uint64_t now_ms
|
|
@@ -527,10 +542,25 @@ uint64_t uniffi_ping_ffi_fn_method_transport_fetch_since(void*_Nonnull ptr, Rust
|
|
|
527
542
|
uint64_t uniffi_ping_ffi_fn_method_transport_send(void*_Nonnull ptr, RustBuffer envelope
|
|
528
543
|
);
|
|
529
544
|
#endif
|
|
545
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_DECODE_CATCHUP_SNAPSHOT
|
|
546
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_DECODE_CATCHUP_SNAPSHOT
|
|
547
|
+
RustBuffer uniffi_ping_ffi_fn_func_decode_catchup_snapshot(RustBuffer snapshot_bytes, RustCallStatus *_Nonnull out_status
|
|
548
|
+
);
|
|
549
|
+
#endif
|
|
530
550
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_GENERATE_IDENTITY_EXPORT
|
|
531
551
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_GENERATE_IDENTITY_EXPORT
|
|
532
552
|
RustBuffer uniffi_ping_ffi_fn_func_generate_identity_export(RustCallStatus *_Nonnull out_status
|
|
533
553
|
|
|
554
|
+
);
|
|
555
|
+
#endif
|
|
556
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_OPEN_LINKING_TICKET
|
|
557
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_OPEN_LINKING_TICKET
|
|
558
|
+
RustBuffer uniffi_ping_ffi_fn_func_open_linking_ticket(RustBuffer sealed, RustBuffer new_device_priv, RustCallStatus *_Nonnull out_status
|
|
559
|
+
);
|
|
560
|
+
#endif
|
|
561
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_SEAL_LINKING_TICKET
|
|
562
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_FUNC_SEAL_LINKING_TICKET
|
|
563
|
+
RustBuffer uniffi_ping_ffi_fn_func_seal_linking_ticket(RustBuffer ticket, RustBuffer new_device_pub, RustCallStatus *_Nonnull out_status
|
|
534
564
|
);
|
|
535
565
|
#endif
|
|
536
566
|
#ifndef UNIFFI_FFIDEF_FFI_PING_FFI_RUSTBUFFER_ALLOC
|
|
@@ -811,12 +841,30 @@ void ffi_ping_ffi_rust_future_free_void(uint64_t handle
|
|
|
811
841
|
#ifndef UNIFFI_FFIDEF_FFI_PING_FFI_RUST_FUTURE_COMPLETE_VOID
|
|
812
842
|
#define UNIFFI_FFIDEF_FFI_PING_FFI_RUST_FUTURE_COMPLETE_VOID
|
|
813
843
|
void ffi_ping_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
|
|
844
|
+
);
|
|
845
|
+
#endif
|
|
846
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_DECODE_CATCHUP_SNAPSHOT
|
|
847
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_DECODE_CATCHUP_SNAPSHOT
|
|
848
|
+
uint16_t uniffi_ping_ffi_checksum_func_decode_catchup_snapshot(void
|
|
849
|
+
|
|
814
850
|
);
|
|
815
851
|
#endif
|
|
816
852
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_GENERATE_IDENTITY_EXPORT
|
|
817
853
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_GENERATE_IDENTITY_EXPORT
|
|
818
854
|
uint16_t uniffi_ping_ffi_checksum_func_generate_identity_export(void
|
|
819
855
|
|
|
856
|
+
);
|
|
857
|
+
#endif
|
|
858
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_OPEN_LINKING_TICKET
|
|
859
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_OPEN_LINKING_TICKET
|
|
860
|
+
uint16_t uniffi_ping_ffi_checksum_func_open_linking_ticket(void
|
|
861
|
+
|
|
862
|
+
);
|
|
863
|
+
#endif
|
|
864
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_SEAL_LINKING_TICKET
|
|
865
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_FUNC_SEAL_LINKING_TICKET
|
|
866
|
+
uint16_t uniffi_ping_ffi_checksum_func_seal_linking_ticket(void
|
|
867
|
+
|
|
820
868
|
);
|
|
821
869
|
#endif
|
|
822
870
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGEOBSERVER_ON_APPLICATION_MESSAGE
|
|
@@ -865,12 +913,30 @@ uint16_t uniffi_ping_ffi_checksum_method_messagingclient_device_id(void
|
|
|
865
913
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_DEVICE_INFO
|
|
866
914
|
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_device_info(void
|
|
867
915
|
|
|
916
|
+
);
|
|
917
|
+
#endif
|
|
918
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_SECRET
|
|
919
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_SECRET
|
|
920
|
+
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_export_conversation_secret(void
|
|
921
|
+
|
|
922
|
+
);
|
|
923
|
+
#endif
|
|
924
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_STATE_SNAPSHOT
|
|
925
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_EXPORT_CONVERSATION_STATE_SNAPSHOT
|
|
926
|
+
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_export_conversation_state_snapshot(void
|
|
927
|
+
|
|
868
928
|
);
|
|
869
929
|
#endif
|
|
870
930
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_FRESH_KEY_PACKAGE
|
|
871
931
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_FRESH_KEY_PACKAGE
|
|
872
932
|
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_fresh_key_package(void
|
|
873
933
|
|
|
934
|
+
);
|
|
935
|
+
#endif
|
|
936
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_IMPORT_STATE_SNAPSHOT
|
|
937
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_IMPORT_STATE_SNAPSHOT
|
|
938
|
+
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_import_state_snapshot(void
|
|
939
|
+
|
|
874
940
|
);
|
|
875
941
|
#endif
|
|
876
942
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_JOIN_CONVERSATION
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ping-openmls-sdk-react-native-macos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Real MLS for React Native macOS apps — wraps the ping-openmls-sdk Rust core via UniFFI.",
|
|
5
5
|
"homepage": "https://github.com/AMP-Media-Development/ping-openmls-sdk",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/MessagingClient.ts
CHANGED
|
@@ -24,6 +24,50 @@ export interface ClientConfig {
|
|
|
24
24
|
storage: Storage;
|
|
25
25
|
/** Caller's transport backend. Wired into the native bridge during `init`. */
|
|
26
26
|
transport: Transport;
|
|
27
|
+
/**
|
|
28
|
+
* [CR-4] Absolute path to a host-managed SQLite file the SDK creates and owns
|
|
29
|
+
* for persistent MLS state. Pass when the host needs cold-restart decryption
|
|
30
|
+
* (iOS NSE, Android Service push wake, similar). The parent directory MUST exist.
|
|
31
|
+
* When omitted, the SDK uses the in-memory provider — fine for tests, not for
|
|
32
|
+
* NSE-style flows.
|
|
33
|
+
*/
|
|
34
|
+
sqlitePath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* [CR-4] 32-byte SQLCipher key. Ignored when `sqlitePath` is omitted. Source
|
|
37
|
+
* from your OS keyring (Keychain on iOS, Keystore on Android, etc.).
|
|
38
|
+
*/
|
|
39
|
+
sqliteEncryptionKey?: Uint8Array;
|
|
40
|
+
/**
|
|
41
|
+
* Optional 32-byte Ed25519 secret key the SDK adopts as its device
|
|
42
|
+
* signing key on FIRST init. When supplied, `deviceId()` returns
|
|
43
|
+
* `SHA-256(public_key_of(secret))` — deterministic from what the
|
|
44
|
+
* caller passes.
|
|
45
|
+
*
|
|
46
|
+
* Use case: align the SDK's `device_id` (which the SDK stamps into
|
|
47
|
+
* every envelope's `sender_device` field) with an externally-
|
|
48
|
+
* computed device id — typically `SHA-256(device_signing_pubkey)`
|
|
49
|
+
* in the host's auth layer, where the JWT carries that same value
|
|
50
|
+
* as its `device_id` claim. Without this alignment, a server that
|
|
51
|
+
* validates `envelope.sender_device == jwt.device_id` rejects every
|
|
52
|
+
* send with `sender_device_mismatch`.
|
|
53
|
+
*
|
|
54
|
+
* Ignored on re-init when a `LocalDevice` is already persisted in
|
|
55
|
+
* storage — the on-disk identity is authoritative for stability
|
|
56
|
+
* across restarts.
|
|
57
|
+
*/
|
|
58
|
+
deviceSigningSecretKey?: Uint8Array;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** [CR-2] One `(deviceId, keyPackage)` pair for `Conversation.addMembers`. */
|
|
62
|
+
export interface KeyPackageEntry {
|
|
63
|
+
deviceId: Uint8Array;
|
|
64
|
+
keyPackage: Uint8Array;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** [CR-13] One `(conversationId, lastAppEventBytes)` pair for `buildLinkingTicket`. */
|
|
68
|
+
export interface CatchupAppEvent {
|
|
69
|
+
conversationId: Uint8Array;
|
|
70
|
+
appEventBytes: Uint8Array;
|
|
27
71
|
}
|
|
28
72
|
|
|
29
73
|
/**
|
|
@@ -57,7 +101,20 @@ export class MessagingClient {
|
|
|
57
101
|
|
|
58
102
|
try {
|
|
59
103
|
const identityB64 = bytesToBase64(cfg.identityExport);
|
|
60
|
-
|
|
104
|
+
const sqliteKeyB64 = cfg.sqliteEncryptionKey
|
|
105
|
+
? bytesToBase64(cfg.sqliteEncryptionKey)
|
|
106
|
+
: null;
|
|
107
|
+
const signingSecretB64 = cfg.deviceSigningSecretKey
|
|
108
|
+
? bytesToBase64(cfg.deviceSigningSecretKey)
|
|
109
|
+
: null;
|
|
110
|
+
await NativePing.initClient(
|
|
111
|
+
identityB64,
|
|
112
|
+
cfg.deviceLabel,
|
|
113
|
+
Date.now(),
|
|
114
|
+
cfg.sqlitePath ?? null,
|
|
115
|
+
sqliteKeyB64,
|
|
116
|
+
signingSecretB64,
|
|
117
|
+
);
|
|
61
118
|
} catch (e) {
|
|
62
119
|
// Init failed — disconnect the bridges so we don't leak event listeners.
|
|
63
120
|
disconnectStorage();
|
|
@@ -264,14 +321,22 @@ export class MessagingClient {
|
|
|
264
321
|
* KeyPackage (`newDeviceKp`); this builds an HPKE-wrapped Welcome to the user's
|
|
265
322
|
* DeviceGroup plus a catch-up snapshot. The new device consumes via
|
|
266
323
|
* `consumeLinkingTicket`.
|
|
324
|
+
*
|
|
325
|
+
* [CR-13] `lastAppEvents` is host-supplied "what you missed" data the new device
|
|
326
|
+
* will render before sync catches up. Pass `[]` to suppress catchup data.
|
|
267
327
|
*/
|
|
268
328
|
async buildLinkingTicket(
|
|
269
329
|
newDeviceId: Uint8Array,
|
|
270
330
|
newDeviceKp: Uint8Array,
|
|
331
|
+
lastAppEvents: CatchupAppEvent[] = [],
|
|
271
332
|
): Promise<LinkingTicket> {
|
|
272
333
|
const raw = await NativePing.buildLinkingTicket(
|
|
273
334
|
Array.from(newDeviceId),
|
|
274
335
|
Array.from(newDeviceKp),
|
|
336
|
+
lastAppEvents.map((e) => ({
|
|
337
|
+
conversationId: Array.from(e.conversationId),
|
|
338
|
+
appEventBytes: Array.from(e.appEventBytes),
|
|
339
|
+
})),
|
|
275
340
|
Date.now(),
|
|
276
341
|
);
|
|
277
342
|
return decodeLinkingTicket(raw);
|
|
@@ -282,9 +347,67 @@ export class MessagingClient {
|
|
|
282
347
|
await NativePing.consumeLinkingTicket(encodeLinkingTicket(ticket), Date.now());
|
|
283
348
|
}
|
|
284
349
|
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Revoke a device ([CR-2]).
|
|
352
|
+
*
|
|
353
|
+
* Returns one Commit envelope per conversation the device was a locally-known
|
|
354
|
+
* leaf in. The SDK has already broadcast each via `transport.send`; the returned
|
|
355
|
+
* list is for any additional host-side handling. Empty array means the device
|
|
356
|
+
* wasn't locally known anywhere (CR-2 scope limit).
|
|
357
|
+
*/
|
|
358
|
+
async revokeDevice(deviceId: Uint8Array): Promise<Array<Record<string, unknown>>> {
|
|
359
|
+
return await NativePing.revokeDevice(Array.from(deviceId), Date.now());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
364
|
+
*
|
|
365
|
+
* Used to seed the ephemeral channel, call-media keys, and call-ephemeral
|
|
366
|
+
* framer keys. Returned bytes are a secret — never log; clear the typed array
|
|
367
|
+
* (`u8.fill(0)`) after use.
|
|
368
|
+
*/
|
|
369
|
+
async exportConversationSecret(
|
|
370
|
+
conversation: Uint8Array,
|
|
371
|
+
label: string,
|
|
372
|
+
context: Uint8Array,
|
|
373
|
+
length: number,
|
|
374
|
+
): Promise<Uint8Array> {
|
|
375
|
+
const arr = await NativePing.exportConversationSecret(
|
|
376
|
+
Array.from(conversation),
|
|
377
|
+
label,
|
|
378
|
+
Array.from(context),
|
|
379
|
+
length,
|
|
380
|
+
);
|
|
381
|
+
return Uint8Array.from(arr);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Export a portable MLS state snapshot for one conversation ([CR-7]).
|
|
386
|
+
*
|
|
387
|
+
* Bytes can be embedded in a recovery blob or shipped through a linking ticket
|
|
388
|
+
* so another device of the same user identity can re-attach via
|
|
389
|
+
* [importStateSnapshot]. Contains past epoch secrets — treat as a secret.
|
|
390
|
+
*/
|
|
391
|
+
async exportConversationStateSnapshot(
|
|
392
|
+
conversation: Uint8Array,
|
|
393
|
+
): Promise<Uint8Array> {
|
|
394
|
+
const arr = await NativePing.exportConversationStateSnapshot(
|
|
395
|
+
Array.from(conversation),
|
|
396
|
+
Date.now(),
|
|
397
|
+
);
|
|
398
|
+
return Uint8Array.from(arr);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Import a `GroupStateSnapshot` from another device of the same user identity
|
|
403
|
+
* ([CR-7]). On success the conversation appears in [listConversations].
|
|
404
|
+
*/
|
|
405
|
+
async importStateSnapshot(snapshotBytes: Uint8Array): Promise<Uint8Array> {
|
|
406
|
+
const arr = await NativePing.importStateSnapshot(
|
|
407
|
+
Array.from(snapshotBytes),
|
|
408
|
+
Date.now(),
|
|
409
|
+
);
|
|
410
|
+
return Uint8Array.from(arr);
|
|
288
411
|
}
|
|
289
412
|
|
|
290
413
|
/** Detach storage + transport + observer listeners. Call when you're done. */
|
|
@@ -423,11 +546,21 @@ export class Conversation {
|
|
|
423
546
|
);
|
|
424
547
|
}
|
|
425
548
|
|
|
426
|
-
/**
|
|
427
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Add members by `(deviceId, keyPackage)` pairs ([CR-2]).
|
|
551
|
+
*
|
|
552
|
+
* Hosts typically pull these straight from `transport.discoverDevices(userId)`.
|
|
553
|
+
* The pairing lets the SDK persist a per-conversation device→leaf map so
|
|
554
|
+
* `MessagingClient.revokeDevice` can later locate the leaves without a fresh
|
|
555
|
+
* directory lookup.
|
|
556
|
+
*/
|
|
557
|
+
async addMembers(entries: KeyPackageEntry[]): Promise<void> {
|
|
428
558
|
await NativePing.addMembers(
|
|
429
559
|
Array.from(this.id),
|
|
430
|
-
|
|
560
|
+
entries.map((e) => ({
|
|
561
|
+
deviceId: Array.from(e.deviceId),
|
|
562
|
+
keyPackage: Array.from(e.keyPackage),
|
|
563
|
+
})),
|
|
431
564
|
Date.now(),
|
|
432
565
|
);
|
|
433
566
|
}
|