ping-openmls-sdk-react-native-macos 0.2.3 → 0.3.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 +27 -1
- package/ios/PingNativeModule.swift +119 -17
- package/ios/TypeBridge.swift +46 -0
- package/ios/pingFFI.h +69 -3
- package/package.json +1 -1
- package/src/MessagingClient.ts +117 -7
- package/src/NativePing.ts +65 -6
|
@@ -211,11 +211,13 @@ 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: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?,
|
|
219
221
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
220
222
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
221
223
|
) {
|
|
@@ -225,6 +227,17 @@ public final class PingNative: RCTEventEmitter {
|
|
|
225
227
|
reject("InvalidIdentity", "identity bytes failed base64 decode", nil)
|
|
226
228
|
return
|
|
227
229
|
}
|
|
230
|
+
// [CR-4] Optional SQLCipher key. Decode if supplied; reject if it
|
|
231
|
+
// decodes but isn't 32 bytes (host bug — better loud than silent).
|
|
232
|
+
var sqliteKey: Data? = nil
|
|
233
|
+
if let keyB64 = sqliteEncryptionKeyB64,
|
|
234
|
+
let keyData = Data(base64Encoded: keyB64) {
|
|
235
|
+
guard keyData.count == 32 else {
|
|
236
|
+
reject("InvalidSqliteKey", "sqliteEncryptionKey must be 32 bytes, got \(keyData.count)", nil)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
sqliteKey = keyData
|
|
240
|
+
}
|
|
228
241
|
// UniFFI's `[Name=init]` constructor generates a static factory named
|
|
229
242
|
// `init` (backquoted because Swift reserves the bare identifier for
|
|
230
243
|
// designated initializers).
|
|
@@ -233,7 +246,9 @@ public final class PingNative: RCTEventEmitter {
|
|
|
233
246
|
deviceLabel: deviceLabel,
|
|
234
247
|
storage: self.storageBridge,
|
|
235
248
|
transport: self.transportBridge,
|
|
236
|
-
nowMs: UInt64(nowMs)
|
|
249
|
+
nowMs: UInt64(nowMs),
|
|
250
|
+
sqlitePath: sqlitePath,
|
|
251
|
+
sqliteEncryptionKey: sqliteKey
|
|
237
252
|
)
|
|
238
253
|
self.client = c
|
|
239
254
|
resolve(true)
|
|
@@ -373,12 +388,13 @@ public final class PingNative: RCTEventEmitter {
|
|
|
373
388
|
}
|
|
374
389
|
}
|
|
375
390
|
|
|
376
|
-
/// Add members
|
|
377
|
-
///
|
|
378
|
-
|
|
391
|
+
/// Add members ([CR-2]). `entries` is an array of `{ deviceId: [Int], keyPackage:
|
|
392
|
+
/// [Int] }` dicts — the device-id pairing lets the SDK persist a per-conversation
|
|
393
|
+
/// device→leaf map that `revokeDeviceNative` later consumes.
|
|
394
|
+
@objc(addMembers:entries:nowMs:resolver:rejecter:)
|
|
379
395
|
public func addMembersNative(
|
|
380
396
|
_ conversationIdBytes: NSArray,
|
|
381
|
-
|
|
397
|
+
entries entriesArr: NSArray,
|
|
382
398
|
nowMs: Double,
|
|
383
399
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
384
400
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
@@ -390,13 +406,10 @@ public final class PingNative: RCTEventEmitter {
|
|
|
390
406
|
}
|
|
391
407
|
do {
|
|
392
408
|
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
393
|
-
|
|
394
|
-
for kp in keyPackagesArr {
|
|
395
|
-
kps.append(try TypeBridge.decodeBytesOrThrow(kp, field: "keyPackage"))
|
|
396
|
-
}
|
|
409
|
+
let entries = try TypeBridge.decodeKeyPackageEntryArray(entriesArr)
|
|
397
410
|
try await client.addMembers(
|
|
398
411
|
conversationId: convId,
|
|
399
|
-
|
|
412
|
+
entries: entries,
|
|
400
413
|
nowMs: UInt64(nowMs)
|
|
401
414
|
)
|
|
402
415
|
resolve(NSNull())
|
|
@@ -555,10 +568,15 @@ public final class PingNative: RCTEventEmitter {
|
|
|
555
568
|
/// Build a linking ticket on the existing device (E) for a new device (N) that
|
|
556
569
|
/// just published its KeyPackage. The ticket bundles a Welcome to E's DeviceGroup
|
|
557
570
|
/// plus a catch-up snapshot. N consumes the ticket via `consumeLinkingTicket`.
|
|
558
|
-
|
|
571
|
+
/// Build a linking ticket ([CR-13] populates `catchup_snapshot` from
|
|
572
|
+
/// `lastAppEvents`). `lastAppEvents` is an array of `{ conversationId: [Int],
|
|
573
|
+
/// appEventBytes: [Int] }` — host-supplied "what you missed" data the new
|
|
574
|
+
/// device renders before sync catches up. Empty array suppresses catchup.
|
|
575
|
+
@objc(buildLinkingTicket:newDeviceKp:lastAppEvents:nowMs:resolver:rejecter:)
|
|
559
576
|
public func buildLinkingTicketNative(
|
|
560
577
|
_ newDeviceIdBytes: NSArray,
|
|
561
578
|
newDeviceKp newDeviceKpBytes: NSArray,
|
|
579
|
+
lastAppEvents lastAppEventsArr: NSArray,
|
|
562
580
|
nowMs: Double,
|
|
563
581
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
564
582
|
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
@@ -571,9 +589,11 @@ public final class PingNative: RCTEventEmitter {
|
|
|
571
589
|
do {
|
|
572
590
|
let newDeviceId = try TypeBridge.decodeDeviceId(newDeviceIdBytes)
|
|
573
591
|
let newKp = try TypeBridge.decodeBytesOrThrow(newDeviceKpBytes, field: "newDeviceKp")
|
|
592
|
+
let events = try TypeBridge.decodeCatchupAppEventArray(lastAppEventsArr)
|
|
574
593
|
let ticket = try await client.buildLinkingTicket(
|
|
575
594
|
newDeviceId: newDeviceId,
|
|
576
595
|
newDeviceKp: newKp,
|
|
596
|
+
lastAppEvents: events,
|
|
577
597
|
nowMs: UInt64(nowMs)
|
|
578
598
|
)
|
|
579
599
|
resolve(TypeBridge.encodeLinkingTicket(ticket))
|
|
@@ -610,9 +630,9 @@ public final class PingNative: RCTEventEmitter {
|
|
|
610
630
|
}
|
|
611
631
|
}
|
|
612
632
|
|
|
613
|
-
/// Revoke a device
|
|
614
|
-
/// conversation the device
|
|
615
|
-
///
|
|
633
|
+
/// Revoke a device ([CR-2]). Returns the array of Commit envelopes the SDK
|
|
634
|
+
/// produced — one per conversation the device was a locally-known leaf in.
|
|
635
|
+
/// Empty array means the device wasn't locally known (scope limit per CR-2).
|
|
616
636
|
@objc(revokeDevice:nowMs:resolver:rejecter:)
|
|
617
637
|
public func revokeDeviceNative(
|
|
618
638
|
_ deviceIdBytes: NSArray,
|
|
@@ -627,14 +647,96 @@ public final class PingNative: RCTEventEmitter {
|
|
|
627
647
|
}
|
|
628
648
|
do {
|
|
629
649
|
let deviceId = try TypeBridge.decodeDeviceId(deviceIdBytes)
|
|
630
|
-
try await client.revokeDevice(deviceId: deviceId, nowMs: UInt64(nowMs))
|
|
631
|
-
resolve(
|
|
650
|
+
let envs = try await client.revokeDevice(deviceId: deviceId, nowMs: UInt64(nowMs))
|
|
651
|
+
resolve(TypeBridge.encodeEnvelopeArray(envs))
|
|
632
652
|
} catch {
|
|
633
653
|
reject("RevokeFailed", String(describing: error), error)
|
|
634
654
|
}
|
|
635
655
|
}
|
|
636
656
|
}
|
|
637
657
|
|
|
658
|
+
// MARK: - CR-8 / CR-7 helpers
|
|
659
|
+
|
|
660
|
+
/// Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
661
|
+
/// Returned bytes are a secret — the JS side is responsible for wiping them.
|
|
662
|
+
@objc(exportConversationSecret:label:context:length:resolver:rejecter:)
|
|
663
|
+
public func exportConversationSecretNative(
|
|
664
|
+
_ conversationIdBytes: NSArray,
|
|
665
|
+
label: String,
|
|
666
|
+
context contextBytes: NSArray,
|
|
667
|
+
length: NSNumber,
|
|
668
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
669
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
670
|
+
) {
|
|
671
|
+
guard let client = self.client else {
|
|
672
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
673
|
+
return
|
|
674
|
+
}
|
|
675
|
+
do {
|
|
676
|
+
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
677
|
+
let context = try TypeBridge.decodeBytesOrThrow(contextBytes, field: "context")
|
|
678
|
+
let bytes = try client.exportConversationSecret(
|
|
679
|
+
conversationId: convId,
|
|
680
|
+
label: label,
|
|
681
|
+
context: context,
|
|
682
|
+
length: length.uint32Value
|
|
683
|
+
)
|
|
684
|
+
resolve(TypeBridge.encodeBytes(bytes))
|
|
685
|
+
} catch {
|
|
686
|
+
reject("ExportSecretFailed", String(describing: error), error)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/// Export a `GroupStateSnapshot` for one conversation ([CR-7]).
|
|
691
|
+
@objc(exportConversationStateSnapshot:nowMs:resolver:rejecter:)
|
|
692
|
+
public func exportConversationStateSnapshotNative(
|
|
693
|
+
_ conversationIdBytes: NSArray,
|
|
694
|
+
nowMs: Double,
|
|
695
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
696
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
697
|
+
) {
|
|
698
|
+
guard let client = self.client else {
|
|
699
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
700
|
+
return
|
|
701
|
+
}
|
|
702
|
+
do {
|
|
703
|
+
let convId = try TypeBridge.decodeConversationId(conversationIdBytes)
|
|
704
|
+
let bytes = try client.exportConversationStateSnapshot(
|
|
705
|
+
conversationId: convId,
|
|
706
|
+
nowMs: UInt64(nowMs)
|
|
707
|
+
)
|
|
708
|
+
resolve(TypeBridge.encodeBytes(bytes))
|
|
709
|
+
} catch {
|
|
710
|
+
reject("ExportStateSnapshotFailed", String(describing: error), error)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/// Import a `GroupStateSnapshot` from another device ([CR-7]).
|
|
715
|
+
@objc(importStateSnapshot:nowMs:resolver:rejecter:)
|
|
716
|
+
public func importStateSnapshotNative(
|
|
717
|
+
_ snapshotBytes: NSArray,
|
|
718
|
+
nowMs: Double,
|
|
719
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
720
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
721
|
+
) {
|
|
722
|
+
Task {
|
|
723
|
+
guard let client = self.client else {
|
|
724
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
do {
|
|
728
|
+
let bytes = try TypeBridge.decodeBytesOrThrow(snapshotBytes, field: "snapshotBytes")
|
|
729
|
+
let convId = try await client.importStateSnapshot(
|
|
730
|
+
snapshotBytes: bytes,
|
|
731
|
+
nowMs: UInt64(nowMs)
|
|
732
|
+
)
|
|
733
|
+
resolve(TypeBridge.encodeConversationId(convId))
|
|
734
|
+
} catch {
|
|
735
|
+
reject("ImportStateSnapshotFailed", String(describing: error), error)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
638
740
|
// MARK: - macOS clipboard helper (stage 5 polish)
|
|
639
741
|
|
|
640
742
|
/// 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
|
|
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.3.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,31 @@ 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
|
+
|
|
42
|
+
/** [CR-2] One `(deviceId, keyPackage)` pair for `Conversation.addMembers`. */
|
|
43
|
+
export interface KeyPackageEntry {
|
|
44
|
+
deviceId: Uint8Array;
|
|
45
|
+
keyPackage: Uint8Array;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** [CR-13] One `(conversationId, lastAppEventBytes)` pair for `buildLinkingTicket`. */
|
|
49
|
+
export interface CatchupAppEvent {
|
|
50
|
+
conversationId: Uint8Array;
|
|
51
|
+
appEventBytes: Uint8Array;
|
|
27
52
|
}
|
|
28
53
|
|
|
29
54
|
/**
|
|
@@ -57,7 +82,16 @@ export class MessagingClient {
|
|
|
57
82
|
|
|
58
83
|
try {
|
|
59
84
|
const identityB64 = bytesToBase64(cfg.identityExport);
|
|
60
|
-
|
|
85
|
+
const sqliteKeyB64 = cfg.sqliteEncryptionKey
|
|
86
|
+
? bytesToBase64(cfg.sqliteEncryptionKey)
|
|
87
|
+
: null;
|
|
88
|
+
await NativePing.initClient(
|
|
89
|
+
identityB64,
|
|
90
|
+
cfg.deviceLabel,
|
|
91
|
+
Date.now(),
|
|
92
|
+
cfg.sqlitePath ?? null,
|
|
93
|
+
sqliteKeyB64,
|
|
94
|
+
);
|
|
61
95
|
} catch (e) {
|
|
62
96
|
// Init failed — disconnect the bridges so we don't leak event listeners.
|
|
63
97
|
disconnectStorage();
|
|
@@ -264,14 +298,22 @@ export class MessagingClient {
|
|
|
264
298
|
* KeyPackage (`newDeviceKp`); this builds an HPKE-wrapped Welcome to the user's
|
|
265
299
|
* DeviceGroup plus a catch-up snapshot. The new device consumes via
|
|
266
300
|
* `consumeLinkingTicket`.
|
|
301
|
+
*
|
|
302
|
+
* [CR-13] `lastAppEvents` is host-supplied "what you missed" data the new device
|
|
303
|
+
* will render before sync catches up. Pass `[]` to suppress catchup data.
|
|
267
304
|
*/
|
|
268
305
|
async buildLinkingTicket(
|
|
269
306
|
newDeviceId: Uint8Array,
|
|
270
307
|
newDeviceKp: Uint8Array,
|
|
308
|
+
lastAppEvents: CatchupAppEvent[] = [],
|
|
271
309
|
): Promise<LinkingTicket> {
|
|
272
310
|
const raw = await NativePing.buildLinkingTicket(
|
|
273
311
|
Array.from(newDeviceId),
|
|
274
312
|
Array.from(newDeviceKp),
|
|
313
|
+
lastAppEvents.map((e) => ({
|
|
314
|
+
conversationId: Array.from(e.conversationId),
|
|
315
|
+
appEventBytes: Array.from(e.appEventBytes),
|
|
316
|
+
})),
|
|
275
317
|
Date.now(),
|
|
276
318
|
);
|
|
277
319
|
return decodeLinkingTicket(raw);
|
|
@@ -282,9 +324,67 @@ export class MessagingClient {
|
|
|
282
324
|
await NativePing.consumeLinkingTicket(encodeLinkingTicket(ticket), Date.now());
|
|
283
325
|
}
|
|
284
326
|
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Revoke a device ([CR-2]).
|
|
329
|
+
*
|
|
330
|
+
* Returns one Commit envelope per conversation the device was a locally-known
|
|
331
|
+
* leaf in. The SDK has already broadcast each via `transport.send`; the returned
|
|
332
|
+
* list is for any additional host-side handling. Empty array means the device
|
|
333
|
+
* wasn't locally known anywhere (CR-2 scope limit).
|
|
334
|
+
*/
|
|
335
|
+
async revokeDevice(deviceId: Uint8Array): Promise<Array<Record<string, unknown>>> {
|
|
336
|
+
return await NativePing.revokeDevice(Array.from(deviceId), Date.now());
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Export a derived secret from a conversation's MLS exporter ([CR-8]).
|
|
341
|
+
*
|
|
342
|
+
* Used to seed the ephemeral channel, call-media keys, and call-ephemeral
|
|
343
|
+
* framer keys. Returned bytes are a secret — never log; clear the typed array
|
|
344
|
+
* (`u8.fill(0)`) after use.
|
|
345
|
+
*/
|
|
346
|
+
async exportConversationSecret(
|
|
347
|
+
conversation: Uint8Array,
|
|
348
|
+
label: string,
|
|
349
|
+
context: Uint8Array,
|
|
350
|
+
length: number,
|
|
351
|
+
): Promise<Uint8Array> {
|
|
352
|
+
const arr = await NativePing.exportConversationSecret(
|
|
353
|
+
Array.from(conversation),
|
|
354
|
+
label,
|
|
355
|
+
Array.from(context),
|
|
356
|
+
length,
|
|
357
|
+
);
|
|
358
|
+
return Uint8Array.from(arr);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Export a portable MLS state snapshot for one conversation ([CR-7]).
|
|
363
|
+
*
|
|
364
|
+
* Bytes can be embedded in a recovery blob or shipped through a linking ticket
|
|
365
|
+
* so another device of the same user identity can re-attach via
|
|
366
|
+
* [importStateSnapshot]. Contains past epoch secrets — treat as a secret.
|
|
367
|
+
*/
|
|
368
|
+
async exportConversationStateSnapshot(
|
|
369
|
+
conversation: Uint8Array,
|
|
370
|
+
): Promise<Uint8Array> {
|
|
371
|
+
const arr = await NativePing.exportConversationStateSnapshot(
|
|
372
|
+
Array.from(conversation),
|
|
373
|
+
Date.now(),
|
|
374
|
+
);
|
|
375
|
+
return Uint8Array.from(arr);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Import a `GroupStateSnapshot` from another device of the same user identity
|
|
380
|
+
* ([CR-7]). On success the conversation appears in [listConversations].
|
|
381
|
+
*/
|
|
382
|
+
async importStateSnapshot(snapshotBytes: Uint8Array): Promise<Uint8Array> {
|
|
383
|
+
const arr = await NativePing.importStateSnapshot(
|
|
384
|
+
Array.from(snapshotBytes),
|
|
385
|
+
Date.now(),
|
|
386
|
+
);
|
|
387
|
+
return Uint8Array.from(arr);
|
|
288
388
|
}
|
|
289
389
|
|
|
290
390
|
/** Detach storage + transport + observer listeners. Call when you're done. */
|
|
@@ -423,11 +523,21 @@ export class Conversation {
|
|
|
423
523
|
);
|
|
424
524
|
}
|
|
425
525
|
|
|
426
|
-
/**
|
|
427
|
-
|
|
526
|
+
/**
|
|
527
|
+
* Add members by `(deviceId, keyPackage)` pairs ([CR-2]).
|
|
528
|
+
*
|
|
529
|
+
* Hosts typically pull these straight from `transport.discoverDevices(userId)`.
|
|
530
|
+
* The pairing lets the SDK persist a per-conversation device→leaf map so
|
|
531
|
+
* `MessagingClient.revokeDevice` can later locate the leaves without a fresh
|
|
532
|
+
* directory lookup.
|
|
533
|
+
*/
|
|
534
|
+
async addMembers(entries: KeyPackageEntry[]): Promise<void> {
|
|
428
535
|
await NativePing.addMembers(
|
|
429
536
|
Array.from(this.id),
|
|
430
|
-
|
|
537
|
+
entries.map((e) => ({
|
|
538
|
+
deviceId: Array.from(e.deviceId),
|
|
539
|
+
keyPackage: Array.from(e.keyPackage),
|
|
540
|
+
})),
|
|
431
541
|
Date.now(),
|
|
432
542
|
);
|
|
433
543
|
}
|