ping-openmls-sdk-react-native-macos 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ // Type encoding/decoding helpers between UniFFI Swift types and JS-friendly JSON.
2
+ //
3
+ // RN's bridge marshals only primitives, NSDictionary, NSArray, NSString, NSNumber, and
4
+ // NSData. UniFFI types (`MessageEnvelope`, `ConversationId`, `DeviceId`, `Hlc`, etc.) are
5
+ // pure Swift structs/classes — they need to be projected into the bridge's vocabulary
6
+ // before they can cross to JS, and reconstructed from it on the way back.
7
+ //
8
+ // Encoding rule: byte fields cross as `[Int]` arrays, matching the `{_bytes: [...]}`
9
+ // projection that the existing web/relay code already uses (just unwrapped one level
10
+ // because RN doesn't need the wrapper). All structs become `[String: Any]` dictionaries
11
+ // keyed by snake_case field names — JS code that received CBOR from the relay gets the
12
+ // same shape.
13
+ //
14
+ // Stage 4a establishes the encoders/decoders. Stage 4b uses them in `MessagingClient.init`.
15
+ // Stage 4c+ uses them in every method that takes/returns UniFFI types.
16
+ //
17
+ // Spec: docs/RN_NATIVE_BINDINGS.md (stage 4).
18
+
19
+ import Foundation
20
+
21
+ enum TypeBridge {
22
+
23
+ // MARK: - Bytes
24
+
25
+ static func encodeBytes(_ data: Data) -> [Int] {
26
+ return data.map { Int($0) }
27
+ }
28
+
29
+ static func decodeBytes(_ value: Any?) -> Data? {
30
+ guard let value, !(value is NSNull) else { return nil }
31
+ if let arr = value as? [NSNumber] {
32
+ var bytes = [UInt8](); bytes.reserveCapacity(arr.count)
33
+ for n in arr { bytes.append(n.uint8Value) }
34
+ return Data(bytes)
35
+ }
36
+ if let arr = value as? [Int] {
37
+ return Data(arr.map { UInt8(truncatingIfNeeded: $0) })
38
+ }
39
+ // Relay JSON projection wraps byte fields as `{_bytes: [...]}` for browser
40
+ // DevTools inspectability — unwrap and recurse on the inner array.
41
+ if let dict = value as? [String: Any], let inner = dict["_bytes"] {
42
+ return decodeBytes(inner)
43
+ }
44
+ return nil
45
+ }
46
+
47
+ static func decodeBytesOrThrow(_ value: Any?, field: String) throws -> Data {
48
+ guard let data = decodeBytes(value) else {
49
+ throw BridgeError.decodeFailure("expected bytes for field `\(field)`, got \(type(of: value))")
50
+ }
51
+ return data
52
+ }
53
+
54
+ // MARK: - ConversationId / DeviceId / UserId
55
+ //
56
+ // The UniFFI surface declares these as dictionaries containing a `value: bytes` field.
57
+ // We project them to/from JS as bare `[Int]` arrays for ergonomics — the JS user
58
+ // doesn't need to deal with the dictionary wrapping.
59
+
60
+ static func encodeConversationId(_ id: ConversationId) -> [Int] {
61
+ return encodeBytes(id.value)
62
+ }
63
+
64
+ static func decodeConversationId(_ value: Any?) throws -> ConversationId {
65
+ let data = try decodeBytesOrThrow(value, field: "conversation_id")
66
+ return ConversationId(value: data)
67
+ }
68
+
69
+ static func encodeDeviceId(_ id: DeviceId) -> [Int] {
70
+ return encodeBytes(id.value)
71
+ }
72
+
73
+ static func decodeDeviceId(_ value: Any?) throws -> DeviceId {
74
+ let data = try decodeBytesOrThrow(value, field: "device_id")
75
+ return DeviceId(value: data)
76
+ }
77
+
78
+ static func encodeUserId(_ id: UserId) -> [Int] {
79
+ return encodeBytes(id.value)
80
+ }
81
+
82
+ static func decodeUserId(_ value: Any?) throws -> UserId {
83
+ let data = try decodeBytesOrThrow(value, field: "user_id")
84
+ return UserId(value: data)
85
+ }
86
+
87
+ // MARK: - Hlc
88
+
89
+ static func encodeHlc(_ hlc: Hlc) -> [String: Any] {
90
+ return ["wall_ms": Int(hlc.wallMs), "logical": Int(hlc.logical)]
91
+ }
92
+
93
+ static func decodeHlc(_ value: Any?) throws -> Hlc {
94
+ guard let dict = value as? [String: Any] else {
95
+ throw BridgeError.decodeFailure("expected hlc dict")
96
+ }
97
+ let wallMs = (dict["wall_ms"] as? NSNumber)?.uint64Value
98
+ ?? UInt64(dict["wall_ms"] as? Int ?? 0)
99
+ let logical = (dict["logical"] as? NSNumber)?.uint32Value
100
+ ?? UInt32(dict["logical"] as? Int ?? 0)
101
+ return Hlc(wallMs: wallMs, logical: logical)
102
+ }
103
+
104
+ // MARK: - MessageKind
105
+
106
+ static func encodeMessageKind(_ kind: MessageKind) -> String {
107
+ switch kind {
108
+ case .application: return "Application"
109
+ case .commit: return "Commit"
110
+ case .welcome: return "Welcome"
111
+ case .proposal: return "Proposal"
112
+ case .keyPackage: return "KeyPackage"
113
+ }
114
+ }
115
+
116
+ static func decodeMessageKind(_ value: Any?) throws -> MessageKind {
117
+ guard let s = value as? String else {
118
+ throw BridgeError.decodeFailure("expected MessageKind string")
119
+ }
120
+ switch s {
121
+ case "Application": return .application
122
+ case "Commit": return .commit
123
+ case "Welcome": return .welcome
124
+ case "Proposal": return .proposal
125
+ case "KeyPackage": return .keyPackage
126
+ default:
127
+ throw BridgeError.decodeFailure("unknown MessageKind: \(s)")
128
+ }
129
+ }
130
+
131
+ // MARK: - MessageEnvelope
132
+
133
+ static func encodeEnvelope(_ env: MessageEnvelope) -> [String: Any] {
134
+ return [
135
+ "v": Int(env.v),
136
+ "conversation_id": encodeConversationId(env.conversationId),
137
+ "epoch": Int(env.epoch),
138
+ "kind": encodeMessageKind(env.kind),
139
+ "sender_device": encodeDeviceId(env.senderDevice),
140
+ "seq": Int(env.seq),
141
+ "hlc": encodeHlc(env.hlc),
142
+ "payload": encodeBytes(env.payload),
143
+ "content_hash": encodeBytes(env.contentHash),
144
+ ]
145
+ }
146
+
147
+ static func decodeEnvelope(_ value: Any?) throws -> MessageEnvelope {
148
+ guard let dict = value as? [String: Any] else {
149
+ throw BridgeError.decodeFailure("expected envelope dict")
150
+ }
151
+ let v = UInt8((dict["v"] as? NSNumber)?.intValue ?? (dict["v"] as? Int ?? 1))
152
+ let convId = try decodeConversationId(dict["conversation_id"])
153
+ let epoch = (dict["epoch"] as? NSNumber)?.uint64Value
154
+ ?? UInt64(dict["epoch"] as? Int ?? 0)
155
+ let kind = try decodeMessageKind(dict["kind"])
156
+ let senderDevice = try decodeDeviceId(dict["sender_device"])
157
+ let seq = (dict["seq"] as? NSNumber)?.uint64Value
158
+ ?? UInt64(dict["seq"] as? Int ?? 0)
159
+ let hlc = try decodeHlc(dict["hlc"])
160
+ let payload = try decodeBytesOrThrow(dict["payload"], field: "payload")
161
+ let contentHash = try decodeBytesOrThrow(dict["content_hash"], field: "content_hash")
162
+ return MessageEnvelope(
163
+ v: v,
164
+ conversationId: convId,
165
+ epoch: epoch,
166
+ kind: kind,
167
+ senderDevice: senderDevice,
168
+ seq: seq,
169
+ hlc: hlc,
170
+ payload: payload,
171
+ contentHash: contentHash
172
+ )
173
+ }
174
+
175
+ static func encodeEnvelopeArray(_ envs: [MessageEnvelope]) -> [[String: Any]] {
176
+ return envs.map { encodeEnvelope($0) }
177
+ }
178
+
179
+ static func decodeEnvelopeArray(_ value: Any?) throws -> [MessageEnvelope] {
180
+ guard let arr = value as? [[String: Any]] else {
181
+ throw BridgeError.decodeFailure("expected envelope array")
182
+ }
183
+ return try arr.map { try decodeEnvelope($0) }
184
+ }
185
+
186
+ // MARK: - IncomingMessage (output-only; we encode but never decode)
187
+
188
+ static func encodeIncomingMessage(_ msg: IncomingMessage) -> [String: Any] {
189
+ return [
190
+ "conversation_id": encodeConversationId(msg.conversationId),
191
+ "sender_device": encodeDeviceId(msg.senderDevice),
192
+ "epoch": Int(msg.epoch),
193
+ "hlc": encodeHlc(msg.hlc),
194
+ "plaintext": encodeBytes(msg.plaintext),
195
+ "content_hash": encodeBytes(msg.contentHash),
196
+ ]
197
+ }
198
+
199
+ static func encodeIncomingMessageArray(_ msgs: [IncomingMessage]) -> [[String: Any]] {
200
+ return msgs.map { encodeIncomingMessage($0) }
201
+ }
202
+
203
+ // MARK: - ConversationMeta
204
+
205
+ static func encodeConversationMeta(_ m: ConversationMeta) -> [String: Any] {
206
+ return [
207
+ "id": encodeConversationId(m.id),
208
+ "name": m.name as Any? ?? NSNull(),
209
+ "epoch": Int(m.epoch),
210
+ "member_count": Int(m.memberCount),
211
+ "is_device_group": m.isDeviceGroup,
212
+ "created_at_ms": Int(m.createdAtMs),
213
+ ]
214
+ }
215
+
216
+ static func encodeConversationMetaArray(_ ms: [ConversationMeta]) -> [[String: Any]] {
217
+ return ms.map { encodeConversationMeta($0) }
218
+ }
219
+
220
+ // MARK: - DeviceInfo
221
+
222
+ static func encodeDeviceInfo(_ d: DeviceInfo) -> [String: Any] {
223
+ return [
224
+ "device_id": encodeDeviceId(d.deviceId),
225
+ "user_id": encodeUserId(d.userId),
226
+ "label": d.label,
227
+ "created_at_ms": Int(d.createdAtMs),
228
+ "last_seen_ms": Int(d.lastSeenMs),
229
+ "revoked": d.revoked,
230
+ ]
231
+ }
232
+
233
+ static func encodeDeviceInfoArray(_ ds: [DeviceInfo]) -> [[String: Any]] {
234
+ return ds.map { encodeDeviceInfo($0) }
235
+ }
236
+
237
+ // MARK: - DiscoveredDevice
238
+
239
+ static func encodeDiscoveredDevice(_ d: DiscoveredDevice) -> [String: Any] {
240
+ return [
241
+ "device_id": encodeDeviceId(d.deviceId),
242
+ "key_package": encodeBytes(d.keyPackage),
243
+ ]
244
+ }
245
+
246
+ static func decodeDiscoveredDevice(_ value: Any?) throws -> DiscoveredDevice {
247
+ guard let dict = value as? [String: Any] else {
248
+ throw BridgeError.decodeFailure("expected DiscoveredDevice dict")
249
+ }
250
+ let deviceId = try decodeDeviceId(dict["device_id"])
251
+ let keyPackage = try decodeBytesOrThrow(dict["key_package"], field: "key_package")
252
+ return DiscoveredDevice(deviceId: deviceId, keyPackage: keyPackage)
253
+ }
254
+
255
+ static func decodeDiscoveredDeviceArray(_ value: Any?) throws -> [DiscoveredDevice] {
256
+ guard let arr = value as? [[String: Any]] else {
257
+ throw BridgeError.decodeFailure("expected DiscoveredDevice array")
258
+ }
259
+ return try arr.map { try decodeDiscoveredDevice($0) }
260
+ }
261
+
262
+ // MARK: - LinkingTicket
263
+
264
+ static func encodeLinkingTicket(_ t: LinkingTicket) -> [String: Any] {
265
+ return [
266
+ "v": Int(t.v),
267
+ "user_id": encodeUserId(t.userId),
268
+ "user_pubkey": encodeBytes(t.userPubkey),
269
+ "new_device_id": encodeDeviceId(t.newDeviceId),
270
+ "device_binding_sig": encodeBytes(t.deviceBindingSig),
271
+ "device_group_welcome": encodeBytes(t.deviceGroupWelcome),
272
+ "catchup_snapshot": encodeBytes(t.catchupSnapshot),
273
+ ]
274
+ }
275
+
276
+ static func decodeLinkingTicket(_ value: Any?) throws -> LinkingTicket {
277
+ guard let dict = value as? [String: Any] else {
278
+ throw BridgeError.decodeFailure("expected LinkingTicket dict")
279
+ }
280
+ let v = UInt8((dict["v"] as? NSNumber)?.intValue ?? (dict["v"] as? Int ?? 1))
281
+ let userId = try decodeUserId(dict["user_id"])
282
+ let userPubkey = try decodeBytesOrThrow(dict["user_pubkey"], field: "user_pubkey")
283
+ let newDeviceId = try decodeDeviceId(dict["new_device_id"])
284
+ let bindingSig = try decodeBytesOrThrow(dict["device_binding_sig"], field: "device_binding_sig")
285
+ let groupWelcome = try decodeBytesOrThrow(dict["device_group_welcome"], field: "device_group_welcome")
286
+ let snapshot = try decodeBytesOrThrow(dict["catchup_snapshot"], field: "catchup_snapshot")
287
+ return LinkingTicket(
288
+ v: v,
289
+ userId: userId,
290
+ userPubkey: userPubkey,
291
+ newDeviceId: newDeviceId,
292
+ deviceBindingSig: bindingSig,
293
+ deviceGroupWelcome: groupWelcome,
294
+ catchupSnapshot: snapshot
295
+ )
296
+ }
297
+ }
@@ -0,0 +1,4 @@
1
+ module pingFFI {
2
+ header "pingFFI.h"
3
+ export *
4
+ }