ping-openmls-sdk-react-native-macos 0.7.4 → 0.7.6
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 +268 -0
- package/ios/PingNativeModule.m +19 -0
- package/ios/PingNativeModule.swift +59 -0
- package/ios/TypeBridge.swift +49 -0
- package/ios/pingFFI.h +11 -0
- package/package.json +1 -1
- package/src/MessagingClient.ts +116 -1
- package/src/NativePing.ts +35 -0
- package/src/transport-bridge.ts +11 -0
package/Frameworks/libping_ffi.a
CHANGED
|
Binary file
|
package/ios/Generated.swift
CHANGED
|
@@ -726,6 +726,8 @@ public func FfiConverterTypeMessageObserver_lower(_ value: MessageObserver) -> U
|
|
|
726
726
|
public protocol MessagingClientProtocol: AnyObject {
|
|
727
727
|
func addMembers(conversationId: ConversationId, entries: [KeyPackageEntry], nowMs: UInt64) async throws
|
|
728
728
|
|
|
729
|
+
func admitDeviceToChats(newDeviceId: DeviceId, entries: [AdmitChatEntry], nowMs: UInt64) async throws -> [AdmitChatOutcome]
|
|
730
|
+
|
|
729
731
|
func buildLinkingTicket(newDeviceId: DeviceId, newDeviceKp: Data, lastAppEvents: [CatchupAppEvent], nowMs: UInt64) async throws -> LinkingTicket
|
|
730
732
|
|
|
731
733
|
func consumeLinkingTicket(ticket: LinkingTicket, nowMs: UInt64) async throws
|
|
@@ -845,6 +847,23 @@ open class MessagingClient:
|
|
|
845
847
|
)
|
|
846
848
|
}
|
|
847
849
|
|
|
850
|
+
open func admitDeviceToChats(newDeviceId: DeviceId, entries: [AdmitChatEntry], nowMs: UInt64) async throws -> [AdmitChatOutcome] {
|
|
851
|
+
return
|
|
852
|
+
try await uniffiRustCallAsync(
|
|
853
|
+
rustFutureFunc: {
|
|
854
|
+
uniffi_ping_ffi_fn_method_messagingclient_admit_device_to_chats(
|
|
855
|
+
self.uniffiClonePointer(),
|
|
856
|
+
FfiConverterTypeDeviceId.lower(newDeviceId), FfiConverterSequenceTypeAdmitChatEntry.lower(entries), FfiConverterUInt64.lower(nowMs)
|
|
857
|
+
)
|
|
858
|
+
},
|
|
859
|
+
pollFunc: ffi_ping_ffi_rust_future_poll_rust_buffer,
|
|
860
|
+
completeFunc: ffi_ping_ffi_rust_future_complete_rust_buffer,
|
|
861
|
+
freeFunc: ffi_ping_ffi_rust_future_free_rust_buffer,
|
|
862
|
+
liftFunc: FfiConverterSequenceTypeAdmitChatOutcome.lift,
|
|
863
|
+
errorHandler: FfiConverterTypePingError.lift
|
|
864
|
+
)
|
|
865
|
+
}
|
|
866
|
+
|
|
848
867
|
open func buildLinkingTicket(newDeviceId: DeviceId, newDeviceKp: Data, lastAppEvents: [CatchupAppEvent], nowMs: UInt64) async throws -> LinkingTicket {
|
|
849
868
|
return
|
|
850
869
|
try await uniffiRustCallAsync(
|
|
@@ -1811,6 +1830,144 @@ public func FfiConverterTypeTransport_lower(_ value: Transport) -> UnsafeMutable
|
|
|
1811
1830
|
return FfiConverterTypeTransport.lower(value)
|
|
1812
1831
|
}
|
|
1813
1832
|
|
|
1833
|
+
public struct AdmitChatEntry {
|
|
1834
|
+
public var conversationId: ConversationId
|
|
1835
|
+
public var keyPackage: Data
|
|
1836
|
+
|
|
1837
|
+
/// Default memberwise initializers are never public by default, so we
|
|
1838
|
+
/// declare one manually.
|
|
1839
|
+
public init(conversationId: ConversationId, keyPackage: Data) {
|
|
1840
|
+
self.conversationId = conversationId
|
|
1841
|
+
self.keyPackage = keyPackage
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
extension AdmitChatEntry: Equatable, Hashable {
|
|
1846
|
+
public static func == (lhs: AdmitChatEntry, rhs: AdmitChatEntry) -> Bool {
|
|
1847
|
+
if lhs.conversationId != rhs.conversationId {
|
|
1848
|
+
return false
|
|
1849
|
+
}
|
|
1850
|
+
if lhs.keyPackage != rhs.keyPackage {
|
|
1851
|
+
return false
|
|
1852
|
+
}
|
|
1853
|
+
return true
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
public func hash(into hasher: inout Hasher) {
|
|
1857
|
+
hasher.combine(conversationId)
|
|
1858
|
+
hasher.combine(keyPackage)
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
#if swift(>=5.8)
|
|
1863
|
+
@_documentation(visibility: private)
|
|
1864
|
+
#endif
|
|
1865
|
+
public struct FfiConverterTypeAdmitChatEntry: FfiConverterRustBuffer {
|
|
1866
|
+
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AdmitChatEntry {
|
|
1867
|
+
return
|
|
1868
|
+
try AdmitChatEntry(
|
|
1869
|
+
conversationId: FfiConverterTypeConversationId.read(from: &buf),
|
|
1870
|
+
keyPackage: FfiConverterData.read(from: &buf)
|
|
1871
|
+
)
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
public static func write(_ value: AdmitChatEntry, into buf: inout [UInt8]) {
|
|
1875
|
+
FfiConverterTypeConversationId.write(value.conversationId, into: &buf)
|
|
1876
|
+
FfiConverterData.write(value.keyPackage, into: &buf)
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
#if swift(>=5.8)
|
|
1881
|
+
@_documentation(visibility: private)
|
|
1882
|
+
#endif
|
|
1883
|
+
public func FfiConverterTypeAdmitChatEntry_lift(_ buf: RustBuffer) throws -> AdmitChatEntry {
|
|
1884
|
+
return try FfiConverterTypeAdmitChatEntry.lift(buf)
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
#if swift(>=5.8)
|
|
1888
|
+
@_documentation(visibility: private)
|
|
1889
|
+
#endif
|
|
1890
|
+
public func FfiConverterTypeAdmitChatEntry_lower(_ value: AdmitChatEntry) -> RustBuffer {
|
|
1891
|
+
return FfiConverterTypeAdmitChatEntry.lower(value)
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
public struct AdmitChatOutcome {
|
|
1895
|
+
public var conversationId: ConversationId
|
|
1896
|
+
public var status: AdmitChatStatus
|
|
1897
|
+
public var reason: String?
|
|
1898
|
+
public var error: String?
|
|
1899
|
+
|
|
1900
|
+
/// Default memberwise initializers are never public by default, so we
|
|
1901
|
+
/// declare one manually.
|
|
1902
|
+
public init(conversationId: ConversationId, status: AdmitChatStatus, reason: String?, error: String?) {
|
|
1903
|
+
self.conversationId = conversationId
|
|
1904
|
+
self.status = status
|
|
1905
|
+
self.reason = reason
|
|
1906
|
+
self.error = error
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
extension AdmitChatOutcome: Equatable, Hashable {
|
|
1911
|
+
public static func == (lhs: AdmitChatOutcome, rhs: AdmitChatOutcome) -> Bool {
|
|
1912
|
+
if lhs.conversationId != rhs.conversationId {
|
|
1913
|
+
return false
|
|
1914
|
+
}
|
|
1915
|
+
if lhs.status != rhs.status {
|
|
1916
|
+
return false
|
|
1917
|
+
}
|
|
1918
|
+
if lhs.reason != rhs.reason {
|
|
1919
|
+
return false
|
|
1920
|
+
}
|
|
1921
|
+
if lhs.error != rhs.error {
|
|
1922
|
+
return false
|
|
1923
|
+
}
|
|
1924
|
+
return true
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
public func hash(into hasher: inout Hasher) {
|
|
1928
|
+
hasher.combine(conversationId)
|
|
1929
|
+
hasher.combine(status)
|
|
1930
|
+
hasher.combine(reason)
|
|
1931
|
+
hasher.combine(error)
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
#if swift(>=5.8)
|
|
1936
|
+
@_documentation(visibility: private)
|
|
1937
|
+
#endif
|
|
1938
|
+
public struct FfiConverterTypeAdmitChatOutcome: FfiConverterRustBuffer {
|
|
1939
|
+
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AdmitChatOutcome {
|
|
1940
|
+
return
|
|
1941
|
+
try AdmitChatOutcome(
|
|
1942
|
+
conversationId: FfiConverterTypeConversationId.read(from: &buf),
|
|
1943
|
+
status: FfiConverterTypeAdmitChatStatus.read(from: &buf),
|
|
1944
|
+
reason: FfiConverterOptionString.read(from: &buf),
|
|
1945
|
+
error: FfiConverterOptionString.read(from: &buf)
|
|
1946
|
+
)
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
public static func write(_ value: AdmitChatOutcome, into buf: inout [UInt8]) {
|
|
1950
|
+
FfiConverterTypeConversationId.write(value.conversationId, into: &buf)
|
|
1951
|
+
FfiConverterTypeAdmitChatStatus.write(value.status, into: &buf)
|
|
1952
|
+
FfiConverterOptionString.write(value.reason, into: &buf)
|
|
1953
|
+
FfiConverterOptionString.write(value.error, into: &buf)
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
#if swift(>=5.8)
|
|
1958
|
+
@_documentation(visibility: private)
|
|
1959
|
+
#endif
|
|
1960
|
+
public func FfiConverterTypeAdmitChatOutcome_lift(_ buf: RustBuffer) throws -> AdmitChatOutcome {
|
|
1961
|
+
return try FfiConverterTypeAdmitChatOutcome.lift(buf)
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
#if swift(>=5.8)
|
|
1965
|
+
@_documentation(visibility: private)
|
|
1966
|
+
#endif
|
|
1967
|
+
public func FfiConverterTypeAdmitChatOutcome_lower(_ value: AdmitChatOutcome) -> RustBuffer {
|
|
1968
|
+
return FfiConverterTypeAdmitChatOutcome.lower(value)
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1814
1971
|
public struct CatchupAppEvent {
|
|
1815
1972
|
public var conversationId: ConversationId
|
|
1816
1973
|
public var appEventBytes: Data
|
|
@@ -2852,6 +3009,64 @@ public func FfiConverterTypeUserId_lower(_ value: UserId) -> RustBuffer {
|
|
|
2852
3009
|
// Note that we don't yet support `indirect` for enums.
|
|
2853
3010
|
// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
|
|
2854
3011
|
|
|
3012
|
+
public enum AdmitChatStatus {
|
|
3013
|
+
case admitted
|
|
3014
|
+
case skipped
|
|
3015
|
+
case failed
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
#if swift(>=5.8)
|
|
3019
|
+
@_documentation(visibility: private)
|
|
3020
|
+
#endif
|
|
3021
|
+
public struct FfiConverterTypeAdmitChatStatus: FfiConverterRustBuffer {
|
|
3022
|
+
typealias SwiftType = AdmitChatStatus
|
|
3023
|
+
|
|
3024
|
+
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AdmitChatStatus {
|
|
3025
|
+
let variant: Int32 = try readInt(&buf)
|
|
3026
|
+
switch variant {
|
|
3027
|
+
case 1: return .admitted
|
|
3028
|
+
|
|
3029
|
+
case 2: return .skipped
|
|
3030
|
+
|
|
3031
|
+
case 3: return .failed
|
|
3032
|
+
|
|
3033
|
+
default: throw UniffiInternalError.unexpectedEnumCase
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
public static func write(_ value: AdmitChatStatus, into buf: inout [UInt8]) {
|
|
3038
|
+
switch value {
|
|
3039
|
+
case .admitted:
|
|
3040
|
+
writeInt(&buf, Int32(1))
|
|
3041
|
+
|
|
3042
|
+
case .skipped:
|
|
3043
|
+
writeInt(&buf, Int32(2))
|
|
3044
|
+
|
|
3045
|
+
case .failed:
|
|
3046
|
+
writeInt(&buf, Int32(3))
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
#if swift(>=5.8)
|
|
3052
|
+
@_documentation(visibility: private)
|
|
3053
|
+
#endif
|
|
3054
|
+
public func FfiConverterTypeAdmitChatStatus_lift(_ buf: RustBuffer) throws -> AdmitChatStatus {
|
|
3055
|
+
return try FfiConverterTypeAdmitChatStatus.lift(buf)
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
#if swift(>=5.8)
|
|
3059
|
+
@_documentation(visibility: private)
|
|
3060
|
+
#endif
|
|
3061
|
+
public func FfiConverterTypeAdmitChatStatus_lower(_ value: AdmitChatStatus) -> RustBuffer {
|
|
3062
|
+
return FfiConverterTypeAdmitChatStatus.lower(value)
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
extension AdmitChatStatus: Equatable, Hashable {}
|
|
3066
|
+
|
|
3067
|
+
// Note that we don't yet support `indirect` for enums.
|
|
3068
|
+
// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
|
|
3069
|
+
|
|
2855
3070
|
public enum MessageKind {
|
|
2856
3071
|
case application
|
|
2857
3072
|
case commit
|
|
@@ -3150,6 +3365,56 @@ private struct FfiConverterSequenceString: FfiConverterRustBuffer {
|
|
|
3150
3365
|
}
|
|
3151
3366
|
}
|
|
3152
3367
|
|
|
3368
|
+
#if swift(>=5.8)
|
|
3369
|
+
@_documentation(visibility: private)
|
|
3370
|
+
#endif
|
|
3371
|
+
private struct FfiConverterSequenceTypeAdmitChatEntry: FfiConverterRustBuffer {
|
|
3372
|
+
typealias SwiftType = [AdmitChatEntry]
|
|
3373
|
+
|
|
3374
|
+
static func write(_ value: [AdmitChatEntry], into buf: inout [UInt8]) {
|
|
3375
|
+
let len = Int32(value.count)
|
|
3376
|
+
writeInt(&buf, len)
|
|
3377
|
+
for item in value {
|
|
3378
|
+
FfiConverterTypeAdmitChatEntry.write(item, into: &buf)
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [AdmitChatEntry] {
|
|
3383
|
+
let len: Int32 = try readInt(&buf)
|
|
3384
|
+
var seq = [AdmitChatEntry]()
|
|
3385
|
+
seq.reserveCapacity(Int(len))
|
|
3386
|
+
for _ in 0 ..< len {
|
|
3387
|
+
try seq.append(FfiConverterTypeAdmitChatEntry.read(from: &buf))
|
|
3388
|
+
}
|
|
3389
|
+
return seq
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
#if swift(>=5.8)
|
|
3394
|
+
@_documentation(visibility: private)
|
|
3395
|
+
#endif
|
|
3396
|
+
private struct FfiConverterSequenceTypeAdmitChatOutcome: FfiConverterRustBuffer {
|
|
3397
|
+
typealias SwiftType = [AdmitChatOutcome]
|
|
3398
|
+
|
|
3399
|
+
static func write(_ value: [AdmitChatOutcome], into buf: inout [UInt8]) {
|
|
3400
|
+
let len = Int32(value.count)
|
|
3401
|
+
writeInt(&buf, len)
|
|
3402
|
+
for item in value {
|
|
3403
|
+
FfiConverterTypeAdmitChatOutcome.write(item, into: &buf)
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [AdmitChatOutcome] {
|
|
3408
|
+
let len: Int32 = try readInt(&buf)
|
|
3409
|
+
var seq = [AdmitChatOutcome]()
|
|
3410
|
+
seq.reserveCapacity(Int(len))
|
|
3411
|
+
for _ in 0 ..< len {
|
|
3412
|
+
try seq.append(FfiConverterTypeAdmitChatOutcome.read(from: &buf))
|
|
3413
|
+
}
|
|
3414
|
+
return seq
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3153
3418
|
#if swift(>=5.8)
|
|
3154
3419
|
@_documentation(visibility: private)
|
|
3155
3420
|
#endif
|
|
@@ -3532,6 +3797,9 @@ private var initializationResult: InitializationResult = {
|
|
|
3532
3797
|
if uniffi_ping_ffi_checksum_method_messagingclient_add_members() != 2224 {
|
|
3533
3798
|
return InitializationResult.apiChecksumMismatch
|
|
3534
3799
|
}
|
|
3800
|
+
if uniffi_ping_ffi_checksum_method_messagingclient_admit_device_to_chats() != 22080 {
|
|
3801
|
+
return InitializationResult.apiChecksumMismatch
|
|
3802
|
+
}
|
|
3535
3803
|
if uniffi_ping_ffi_checksum_method_messagingclient_build_linking_ticket() != 21631 {
|
|
3536
3804
|
return InitializationResult.apiChecksumMismatch
|
|
3537
3805
|
}
|
package/ios/PingNativeModule.m
CHANGED
|
@@ -96,6 +96,16 @@ RCT_EXTERN_METHOD(addMembers: (NSArray *)conversationId
|
|
|
96
96
|
resolver: (RCTPromiseResolveBlock)resolve
|
|
97
97
|
rejecter: (RCTPromiseRejectBlock)reject)
|
|
98
98
|
|
|
99
|
+
// Post-link reconciliation: admit a freshly-linked device to every chat in
|
|
100
|
+
// `entries`. `entries` is an array of `{ conversationId: [Int], keyPackage: [Int] }`
|
|
101
|
+
// dicts. Returns an array of `{ conversationId: [Int], status: String,
|
|
102
|
+
// reason?: String, error?: String }`.
|
|
103
|
+
RCT_EXTERN_METHOD(admitDeviceToChats: (NSArray *)newDeviceId
|
|
104
|
+
entries: (NSArray *)entries
|
|
105
|
+
nowMs: (double)nowMs
|
|
106
|
+
resolver: (RCTPromiseResolveBlock)resolve
|
|
107
|
+
rejecter: (RCTPromiseRejectBlock)reject)
|
|
108
|
+
|
|
99
109
|
RCT_EXTERN_METHOD(removeMembers: (NSArray *)conversationId
|
|
100
110
|
leafIndexes: (NSArray *)leafIndexes
|
|
101
111
|
nowMs: (double)nowMs
|
|
@@ -136,6 +146,15 @@ RCT_EXTERN_METHOD(consumeLinkingTicket: (NSDictionary *)ticket
|
|
|
136
146
|
resolver: (RCTPromiseResolveBlock)resolve
|
|
137
147
|
rejecter: (RCTPromiseRejectBlock)reject)
|
|
138
148
|
|
|
149
|
+
// [CR-3] HPKE-open a sealed LinkingTicket on the new device. Free
|
|
150
|
+
// function (not on the client instance) — the new device is pre-init
|
|
151
|
+
// and doesn't have a MessagingClient yet at the time of the open call.
|
|
152
|
+
// Returns the same dict shape `consumeLinkingTicket` accepts as input.
|
|
153
|
+
RCT_EXTERN_METHOD(openLinkingTicket: (NSArray *)sealed
|
|
154
|
+
newDevicePriv: (NSArray *)newDevicePriv
|
|
155
|
+
resolver: (RCTPromiseResolveBlock)resolve
|
|
156
|
+
rejecter: (RCTPromiseRejectBlock)reject)
|
|
157
|
+
|
|
139
158
|
RCT_EXTERN_METHOD(revokeDevice: (NSArray *)deviceId
|
|
140
159
|
nowMs: (double)nowMs
|
|
141
160
|
resolver: (RCTPromiseResolveBlock)resolve
|
|
@@ -439,6 +439,40 @@ public final class PingNative: RCTEventEmitter {
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
/// Admit a freshly-linked device to every chat in `entries`. Mirrors the SDK's
|
|
443
|
+
/// `MessagingClient.admit_device_to_chats` — one Commit + Welcome per chat,
|
|
444
|
+
/// per-chat outcomes returned so a single bad KP doesn't strand the user on
|
|
445
|
+
/// the other chats. `entries` is `{ conversationId: [Int], keyPackage: [Int] }[]`.
|
|
446
|
+
/// Result is `{ conversationId: [Int], status: "admitted"|"skipped"|"failed",
|
|
447
|
+
/// reason?: String, error?: String }[]`.
|
|
448
|
+
@objc(admitDeviceToChats:entries:nowMs:resolver:rejecter:)
|
|
449
|
+
public func admitDeviceToChatsNative(
|
|
450
|
+
_ newDeviceIdBytes: NSArray,
|
|
451
|
+
entries entriesArr: NSArray,
|
|
452
|
+
nowMs: Double,
|
|
453
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
454
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
455
|
+
) {
|
|
456
|
+
Task {
|
|
457
|
+
guard let client = self.client else {
|
|
458
|
+
reject("NotInitialised", "MessagingClient not initialised", nil)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
do {
|
|
462
|
+
let deviceId = try TypeBridge.decodeDeviceId(newDeviceIdBytes)
|
|
463
|
+
let entries = try TypeBridge.decodeAdmitChatEntryArray(entriesArr)
|
|
464
|
+
let outcomes = try await client.admitDeviceToChats(
|
|
465
|
+
newDeviceId: deviceId,
|
|
466
|
+
entries: entries,
|
|
467
|
+
nowMs: UInt64(nowMs)
|
|
468
|
+
)
|
|
469
|
+
resolve(TypeBridge.encodeAdmitChatOutcomeArray(outcomes))
|
|
470
|
+
} catch {
|
|
471
|
+
reject("AdmitDeviceToChatsFailed", String(describing: error), error)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
442
476
|
/// Remove members by leaf index. Leaf indexes come from `MlsGroup::members()` in
|
|
443
477
|
/// stage 4e's `listConversations` result; for now they're caller-supplied integers.
|
|
444
478
|
@objc(removeMembers:leafIndexes:nowMs:resolver:rejecter:)
|
|
@@ -650,6 +684,31 @@ public final class PingNative: RCTEventEmitter {
|
|
|
650
684
|
}
|
|
651
685
|
}
|
|
652
686
|
|
|
687
|
+
/// HPKE-open a sealed `LinkingTicket` on the new device ([CR-3]).
|
|
688
|
+
/// Free function (not on `client`) — the new device is pre-init and
|
|
689
|
+
/// doesn't have a `MessagingClient` yet when the open happens. Mirrors
|
|
690
|
+
/// the wasm SDK's `MessagingClient.openLinkingTicket(sealed, priv)`
|
|
691
|
+
/// static so the desktop link host can decode the ticket before
|
|
692
|
+
/// calling `init` + `consumeLinkingTicket`.
|
|
693
|
+
@objc(openLinkingTicket:newDevicePriv:resolver:rejecter:)
|
|
694
|
+
public func openLinkingTicketNative(
|
|
695
|
+
_ sealedBytes: NSArray,
|
|
696
|
+
newDevicePriv newDevicePrivBytes: NSArray,
|
|
697
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
698
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
699
|
+
) {
|
|
700
|
+
do {
|
|
701
|
+
let sealed = try TypeBridge.decodeBytesOrThrow(sealedBytes, field: "sealed")
|
|
702
|
+
let priv = try TypeBridge.decodeBytesOrThrow(newDevicePrivBytes, field: "newDevicePriv")
|
|
703
|
+
// `open_linking_ticket` is a UniFFI top-level free function;
|
|
704
|
+
// Swift exposes it camelCased.
|
|
705
|
+
let ticket = try openLinkingTicket(sealed: sealed, newDevicePriv: priv)
|
|
706
|
+
resolve(TypeBridge.encodeLinkingTicket(ticket))
|
|
707
|
+
} catch {
|
|
708
|
+
reject("OpenLinkingFailed", String(describing: error), error)
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
653
712
|
/// Revoke a device ([CR-2]). Returns the array of Commit envelopes the SDK
|
|
654
713
|
/// produced — one per conversation the device was a locally-known leaf in.
|
|
655
714
|
/// Empty array means the device wasn't locally known (scope limit per CR-2).
|
package/ios/TypeBridge.swift
CHANGED
|
@@ -318,6 +318,55 @@ enum TypeBridge {
|
|
|
318
318
|
return try arr.map { try decodeKeyPackageEntry($0) }
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// MARK: - AdmitChat (post-link device admission)
|
|
322
|
+
|
|
323
|
+
/// Decode `{ conversationId: [Int], keyPackage: [Int] }` into UniFFI's
|
|
324
|
+
/// `AdmitChatEntry`. Used by `admitDeviceToChatsNative` — one entry per
|
|
325
|
+
/// chat the existing device is admitting the new device into.
|
|
326
|
+
static func decodeAdmitChatEntry(_ value: Any?) throws -> AdmitChatEntry {
|
|
327
|
+
guard let dict = value as? [String: Any] else {
|
|
328
|
+
throw BridgeError.decodeFailure("expected AdmitChatEntry dict")
|
|
329
|
+
}
|
|
330
|
+
let convId = try decodeConversationId(dict["conversationId"] ?? dict["conversation_id"])
|
|
331
|
+
let keyPackage = try decodeBytesOrThrow(
|
|
332
|
+
dict["keyPackage"] ?? dict["key_package"],
|
|
333
|
+
field: "keyPackage"
|
|
334
|
+
)
|
|
335
|
+
return AdmitChatEntry(conversationId: convId, keyPackage: keyPackage)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
static func decodeAdmitChatEntryArray(_ value: Any?) throws -> [AdmitChatEntry] {
|
|
339
|
+
guard let arr = value as? [Any] else {
|
|
340
|
+
throw BridgeError.decodeFailure("expected AdmitChatEntry array")
|
|
341
|
+
}
|
|
342
|
+
return try arr.map { try decodeAdmitChatEntry($0) }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// Encode an `AdmitChatStatus` as one of `"admitted" | "skipped" | "failed"`.
|
|
346
|
+
/// Matches the web SDK's `AdmitChatOutcome.status` discriminator so a host
|
|
347
|
+
/// app sharing logic across web/desktop can switch on a single string.
|
|
348
|
+
static func encodeAdmitChatStatus(_ status: AdmitChatStatus) -> String {
|
|
349
|
+
switch status {
|
|
350
|
+
case .admitted: return "admitted"
|
|
351
|
+
case .skipped: return "skipped"
|
|
352
|
+
case .failed: return "failed"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
static func encodeAdmitChatOutcome(_ outcome: AdmitChatOutcome) -> [String: Any] {
|
|
357
|
+
var dict: [String: Any] = [
|
|
358
|
+
"conversationId": encodeConversationId(outcome.conversationId),
|
|
359
|
+
"status": encodeAdmitChatStatus(outcome.status),
|
|
360
|
+
]
|
|
361
|
+
if let reason = outcome.reason { dict["reason"] = reason }
|
|
362
|
+
if let error = outcome.error { dict["error"] = error }
|
|
363
|
+
return dict
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static func encodeAdmitChatOutcomeArray(_ outcomes: [AdmitChatOutcome]) -> [[String: Any]] {
|
|
367
|
+
return outcomes.map(encodeAdmitChatOutcome)
|
|
368
|
+
}
|
|
369
|
+
|
|
321
370
|
// MARK: - [CR-13] CatchupAppEvent
|
|
322
371
|
|
|
323
372
|
/// Decode `{ conversationId: [Int], appEventBytes: [Int] }` into UniFFI's
|
package/ios/pingFFI.h
CHANGED
|
@@ -382,6 +382,11 @@ uint64_t uniffi_ping_ffi_fn_constructor_messagingclient_init(RustBuffer identity
|
|
|
382
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
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_ADMIT_DEVICE_TO_CHATS
|
|
386
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_ADMIT_DEVICE_TO_CHATS
|
|
387
|
+
uint64_t uniffi_ping_ffi_fn_method_messagingclient_admit_device_to_chats(void*_Nonnull ptr, RustBuffer new_device_id, RustBuffer entries, uint64_t now_ms
|
|
388
|
+
);
|
|
389
|
+
#endif
|
|
385
390
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_BUILD_LINKING_TICKET
|
|
386
391
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_FN_METHOD_MESSAGINGCLIENT_BUILD_LINKING_TICKET
|
|
387
392
|
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
|
|
@@ -883,6 +888,12 @@ uint16_t uniffi_ping_ffi_checksum_method_messageobserver_on_conversation_updated
|
|
|
883
888
|
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_ADD_MEMBERS
|
|
884
889
|
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_add_members(void
|
|
885
890
|
|
|
891
|
+
);
|
|
892
|
+
#endif
|
|
893
|
+
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_ADMIT_DEVICE_TO_CHATS
|
|
894
|
+
#define UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_ADMIT_DEVICE_TO_CHATS
|
|
895
|
+
uint16_t uniffi_ping_ffi_checksum_method_messagingclient_admit_device_to_chats(void
|
|
896
|
+
|
|
886
897
|
);
|
|
887
898
|
#endif
|
|
888
899
|
#ifndef UNIFFI_FFIDEF_UNIFFI_PING_FFI_CHECKSUM_METHOD_MESSAGINGCLIENT_BUILD_LINKING_TICKET
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ping-openmls-sdk-react-native-macos",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
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
|
@@ -70,6 +70,24 @@ export interface CatchupAppEvent {
|
|
|
70
70
|
appEventBytes: Uint8Array;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/** One per-chat KP for `MessagingClient.admitDeviceToChats`. Hosts claim these
|
|
74
|
+
* from the auth-layer's per-account KP pool after the newly-linked device's
|
|
75
|
+
* bootstrap has uploaded its KP batch. */
|
|
76
|
+
export interface AdmitChatEntry {
|
|
77
|
+
conversationId: Uint8Array;
|
|
78
|
+
keyPackage: Uint8Array;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Per-chat result returned by `MessagingClient.admitDeviceToChats`. */
|
|
82
|
+
export interface AdmitChatOutcome {
|
|
83
|
+
conversationId: Uint8Array;
|
|
84
|
+
status: "admitted" | "skipped" | "failed";
|
|
85
|
+
/** Set when `status === "skipped"`. Diagnostic only. */
|
|
86
|
+
reason?: string;
|
|
87
|
+
/** Set when `status === "failed"`. The underlying MLS / transport error. */
|
|
88
|
+
error?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
/**
|
|
74
92
|
* High-level facade over the native MessagingClient. Methods marshal arguments + return
|
|
75
93
|
* values across the JS↔Swift bridge; the actual MLS work happens in Rust.
|
|
@@ -87,6 +105,14 @@ export class MessagingClient {
|
|
|
87
105
|
private constructor(
|
|
88
106
|
private readonly disconnectStorage: () => void,
|
|
89
107
|
private readonly disconnectTransport: () => void,
|
|
108
|
+
/**
|
|
109
|
+
* Keep a reference to the host transport so post-link reconciliation
|
|
110
|
+
* can prime its Welcome-recipient map before the native SDK emits
|
|
111
|
+
* Welcomes. The bridge connection (above) is what forwards `send` /
|
|
112
|
+
* `fetchSince` / `discoverDevices` calls — we keep this around purely
|
|
113
|
+
* for the host-side primitives (e.g. `setNextWelcomeRecipients`).
|
|
114
|
+
*/
|
|
115
|
+
private readonly transport: Transport,
|
|
90
116
|
) {}
|
|
91
117
|
|
|
92
118
|
/**
|
|
@@ -122,7 +148,7 @@ export class MessagingClient {
|
|
|
122
148
|
throw e;
|
|
123
149
|
}
|
|
124
150
|
|
|
125
|
-
const client = new MessagingClient(disconnectStorage, disconnectTransport);
|
|
151
|
+
const client = new MessagingClient(disconnectStorage, disconnectTransport, cfg.transport);
|
|
126
152
|
|
|
127
153
|
// Install the observer bridge so native can fire application-message and
|
|
128
154
|
// conversation-updated events. Failure here is non-fatal — the client still works
|
|
@@ -347,6 +373,95 @@ export class MessagingClient {
|
|
|
347
373
|
await NativePing.consumeLinkingTicket(encodeLinkingTicket(ticket), Date.now());
|
|
348
374
|
}
|
|
349
375
|
|
|
376
|
+
/**
|
|
377
|
+
* [CR-3] HPKE-open a sealed linking ticket on the new device. Static —
|
|
378
|
+
* the new device hasn't initialised a `MessagingClient` yet at the
|
|
379
|
+
* time of the open call (it does `openLinkingTicket` first to extract
|
|
380
|
+
* the auth-layer fields, hands them to `/v1/auth/link-verify` to
|
|
381
|
+
* mint tokens, THEN `init`s and `consumeLinkingTicket`s).
|
|
382
|
+
*
|
|
383
|
+
* Mirrors the wasm SDK's `MessagingClient.openLinkingTicket` so the
|
|
384
|
+
* desktop link host can share code shape with the web link host.
|
|
385
|
+
*
|
|
386
|
+
* @param sealed - HPKE-sealed bytes from `getLinkingTicket(ticketId)`
|
|
387
|
+
* (the BE relays exactly what the existing device
|
|
388
|
+
* `sealLinkingTicket`'d).
|
|
389
|
+
* @param newDevicePriv - 32-byte X25519 private key the new device
|
|
390
|
+
* generated for this pairing session.
|
|
391
|
+
*/
|
|
392
|
+
static async openLinkingTicket(
|
|
393
|
+
sealed: Uint8Array,
|
|
394
|
+
newDevicePriv: Uint8Array,
|
|
395
|
+
): Promise<LinkingTicket> {
|
|
396
|
+
const raw = await NativePing.openLinkingTicket(
|
|
397
|
+
Array.from(sealed),
|
|
398
|
+
Array.from(newDevicePriv),
|
|
399
|
+
);
|
|
400
|
+
return decodeLinkingTicket(raw);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Admit a freshly-linked device to every chat in `entries` — one Commit +
|
|
405
|
+
* Welcome per chat, with per-chat outcomes. Host calls this AFTER
|
|
406
|
+
* `consumeLinkingTicket` on the new device side (and after the new device
|
|
407
|
+
* has uploaded its KeyPackage batch so the existing device can claim per-chat
|
|
408
|
+
* KPs).
|
|
409
|
+
*
|
|
410
|
+
* Welcome routing: this method primes the host transport's
|
|
411
|
+
* `setNextWelcomeRecipients` for each chat BEFORE the native call so the
|
|
412
|
+
* BE's `POST /v1/messages?recipient=` query param lands correctly. Hosts
|
|
413
|
+
* that implement Transport.setNextWelcomeRecipients (PingTransport on web
|
|
414
|
+
* and desktop both do) need no further coordination. Hosts that don't see
|
|
415
|
+
* a silent no-op — Welcomes will still send, but Welcome routing depends
|
|
416
|
+
* entirely on the transport's existing recipient priming.
|
|
417
|
+
*/
|
|
418
|
+
async admitDeviceToChats(
|
|
419
|
+
newDeviceId: Uint8Array,
|
|
420
|
+
entries: AdmitChatEntry[],
|
|
421
|
+
): Promise<AdmitChatOutcome[]> {
|
|
422
|
+
// Pre-prime the transport's recipient map for every entry. Each entry's
|
|
423
|
+
// Welcome will consume its own slot on send; pre-priming the whole batch
|
|
424
|
+
// is safe because chat ids are unique within `entries`. We tolerate
|
|
425
|
+
// hosts without `setNextWelcomeRecipients` (e.g. tests) by treating the
|
|
426
|
+
// missing method as a no-op.
|
|
427
|
+
if (this.transport.setNextWelcomeRecipients) {
|
|
428
|
+
for (const entry of entries) {
|
|
429
|
+
try {
|
|
430
|
+
await this.transport.setNextWelcomeRecipients(
|
|
431
|
+
entry.conversationId,
|
|
432
|
+
[newDeviceId],
|
|
433
|
+
);
|
|
434
|
+
} catch {
|
|
435
|
+
// Priming is best-effort; the per-chat send will surface a 400
|
|
436
|
+
// from the BE if it actually needed the recipient.
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const raw = await NativePing.admitDeviceToChats(
|
|
442
|
+
Array.from(newDeviceId),
|
|
443
|
+
entries.map((e) => ({
|
|
444
|
+
conversationId: Array.from(e.conversationId),
|
|
445
|
+
keyPackage: Array.from(e.keyPackage),
|
|
446
|
+
})),
|
|
447
|
+
Date.now(),
|
|
448
|
+
);
|
|
449
|
+
return raw.map((o: {
|
|
450
|
+
conversationId: number[];
|
|
451
|
+
status: "admitted" | "skipped" | "failed";
|
|
452
|
+
reason?: string;
|
|
453
|
+
error?: string;
|
|
454
|
+
}) => {
|
|
455
|
+
const outcome: AdmitChatOutcome = {
|
|
456
|
+
conversationId: Uint8Array.from(o.conversationId),
|
|
457
|
+
status: o.status,
|
|
458
|
+
};
|
|
459
|
+
if (o.reason !== undefined) outcome.reason = o.reason;
|
|
460
|
+
if (o.error !== undefined) outcome.error = o.error;
|
|
461
|
+
return outcome;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
350
465
|
/**
|
|
351
466
|
* Revoke a device ([CR-2]).
|
|
352
467
|
*
|
package/src/NativePing.ts
CHANGED
|
@@ -117,6 +117,28 @@ export interface Spec extends TurboModule {
|
|
|
117
117
|
nowMs: number,
|
|
118
118
|
): Promise<null>;
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Admit a freshly-linked device to every chat in `entries`. The new device
|
|
122
|
+
* gets its own MLS leaf (so per-device PCS still works) and the BE-side
|
|
123
|
+
* `conversation_members` row is materialised as a side effect of each
|
|
124
|
+
* Welcome. Returns per-chat outcomes — `status` is one of `"admitted"`,
|
|
125
|
+
* `"skipped"`, `"failed"`. On `"failed"`, `error` carries the underlying
|
|
126
|
+
* MLS / transport error. On `"skipped"`, `reason` carries the diagnostic
|
|
127
|
+
* (e.g. `"device_group"` when a DG slipped into the entries).
|
|
128
|
+
*/
|
|
129
|
+
admitDeviceToChats(
|
|
130
|
+
newDeviceId: number[],
|
|
131
|
+
entries: { conversationId: number[]; keyPackage: number[] }[],
|
|
132
|
+
nowMs: number,
|
|
133
|
+
): Promise<
|
|
134
|
+
{
|
|
135
|
+
conversationId: number[];
|
|
136
|
+
status: "admitted" | "skipped" | "failed";
|
|
137
|
+
reason?: string;
|
|
138
|
+
error?: string;
|
|
139
|
+
}[]
|
|
140
|
+
>;
|
|
141
|
+
|
|
120
142
|
/** Remove members by leaf index in the conversation's ratchet tree. */
|
|
121
143
|
removeMembers(
|
|
122
144
|
conversationId: number[],
|
|
@@ -178,6 +200,19 @@ export interface Spec extends TurboModule {
|
|
|
178
200
|
nowMs: number,
|
|
179
201
|
): Promise<null>;
|
|
180
202
|
|
|
203
|
+
/**
|
|
204
|
+
* [CR-3] HPKE-open a sealed linking ticket on the new device. Free
|
|
205
|
+
* function — runs BEFORE `initClient` so the host can decode the
|
|
206
|
+
* ticket fields (`user_pubkey`, `new_device_id`, `device_binding_sig`)
|
|
207
|
+
* and submit them to the auth layer's `/v1/auth/link-verify` before
|
|
208
|
+
* spinning up the messaging client. Returns the same dict shape
|
|
209
|
+
* `consumeLinkingTicket` accepts.
|
|
210
|
+
*/
|
|
211
|
+
openLinkingTicket(
|
|
212
|
+
sealed: number[],
|
|
213
|
+
newDevicePriv: number[],
|
|
214
|
+
): Promise<Record<string, unknown>>;
|
|
215
|
+
|
|
181
216
|
/**
|
|
182
217
|
* Revoke a device ([CR-2]). Returns one Commit envelope per conversation the
|
|
183
218
|
* device was a locally-known leaf in. Empty array means the device wasn't locally
|
package/src/transport-bridge.ts
CHANGED
|
@@ -26,6 +26,17 @@ export interface Transport {
|
|
|
26
26
|
* handle.
|
|
27
27
|
*/
|
|
28
28
|
subscribe?(onEvent: (envelopeJson: Record<string, unknown>) => void): { unsubscribe(): void };
|
|
29
|
+
/**
|
|
30
|
+
* Optional. Prime the host with recipient device ids for the NEXT Welcome on
|
|
31
|
+
* `conversationId`. Called by `MessagingClient.admitDeviceToChats` before
|
|
32
|
+
* each per-chat Welcome send so the BE's `POST /v1/messages?recipient=`
|
|
33
|
+
* query param lands correctly. Hosts that route Welcomes differently (or
|
|
34
|
+
* don't need per-recipient routing) may leave this unimplemented.
|
|
35
|
+
*/
|
|
36
|
+
setNextWelcomeRecipients?(
|
|
37
|
+
conversationId: Uint8Array,
|
|
38
|
+
recipientDeviceIds: Uint8Array[],
|
|
39
|
+
): Promise<void>;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
interface TransportCallEvent {
|