@cnhongwei/react-native-mqtt 0.0.0-stage → 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +169 -2
  3. package/README.zh-CN.md +100 -0
  4. package/ReactNativeMqtt.podspec +28 -0
  5. package/THIRD_PARTY_NOTICES.md +17 -0
  6. package/android/build.gradle +70 -0
  7. package/android/consumer-rules.pro +10 -0
  8. package/android/src/main/AndroidManifest.xml +2 -0
  9. package/android/src/main/java/com/cnhongwei/reactnativemqtt/MqttOptionsParser.kt +176 -0
  10. package/android/src/main/java/com/cnhongwei/reactnativemqtt/MqttSession.kt +1006 -0
  11. package/android/src/main/java/com/cnhongwei/reactnativemqtt/PahoLogger.java +44 -0
  12. package/android/src/main/java/com/cnhongwei/reactnativemqtt/PahoMqttTransport.kt +399 -0
  13. package/android/src/main/java/com/cnhongwei/reactnativemqtt/ReactNativeMqttModule.kt +619 -0
  14. package/android/src/main/java/com/cnhongwei/reactnativemqtt/ReactNativeMqttPackage.kt +31 -0
  15. package/ios/ReactNativeMqtt.h +15 -0
  16. package/ios/ReactNativeMqtt.mm +1184 -0
  17. package/ios/ReactNativeMqttCocoaTransport.swift +782 -0
  18. package/ios/ReactNativeMqttConnAckProbe.swift +87 -0
  19. package/ios/ReactNativeMqttSession.swift +420 -0
  20. package/lib/module/NativeReactNativeMqtt.js +5 -0
  21. package/lib/module/NativeReactNativeMqtt.js.map +1 -0
  22. package/lib/module/client.js +555 -0
  23. package/lib/module/client.js.map +1 -0
  24. package/lib/module/errors.js +20 -0
  25. package/lib/module/errors.js.map +1 -0
  26. package/lib/module/helpers.js +116 -0
  27. package/lib/module/helpers.js.map +1 -0
  28. package/lib/module/index.js +8 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/package.json +1 -0
  31. package/lib/module/types.js +2 -0
  32. package/lib/module/types.js.map +1 -0
  33. package/lib/module/validation.js +306 -0
  34. package/lib/module/validation.js.map +1 -0
  35. package/lib/typescript/package.json +1 -0
  36. package/lib/typescript/src/NativeReactNativeMqtt.d.ts +154 -0
  37. package/lib/typescript/src/NativeReactNativeMqtt.d.ts.map +1 -0
  38. package/lib/typescript/src/client.d.ts +3 -0
  39. package/lib/typescript/src/client.d.ts.map +1 -0
  40. package/lib/typescript/src/errors.d.ts +18 -0
  41. package/lib/typescript/src/errors.d.ts.map +1 -0
  42. package/lib/typescript/src/helpers.d.ts +5 -0
  43. package/lib/typescript/src/helpers.d.ts.map +1 -0
  44. package/lib/typescript/src/index.d.ts +9 -0
  45. package/lib/typescript/src/index.d.ts.map +1 -0
  46. package/lib/typescript/src/types.d.ts +161 -0
  47. package/lib/typescript/src/types.d.ts.map +1 -0
  48. package/lib/typescript/src/validation.d.ts +13 -0
  49. package/lib/typescript/src/validation.d.ts.map +1 -0
  50. package/package.json +188 -4
  51. package/react-native.config.js +1 -0
  52. package/src/NativeReactNativeMqtt.ts +173 -0
  53. package/src/client.ts +859 -0
  54. package/src/errors.ts +51 -0
  55. package/src/helpers.ts +151 -0
  56. package/src/index.ts +30 -0
  57. package/src/types.ts +184 -0
  58. package/src/validation.ts +735 -0
@@ -0,0 +1,87 @@
1
+ import CocoaMQTT
2
+ import Foundation
3
+
4
+ /// Captures the MQTT CONNACK flags at the public socket boundary.
5
+ /// CocoaMQTT 2.4.0 keeps this bit inside its private FrameConnAck, so the
6
+ /// adapter observes the already-framed header/payload callbacks and forwards
7
+ /// every callback unchanged to CocoaMQTT.
8
+ final class ReactNativeMqttConnAckProbe: NSObject, CocoaMQTTSocketProtocol, CocoaMQTTSocketDelegate {
9
+ private var socket: CocoaMQTTSocketProtocol
10
+ private weak var downstream: CocoaMQTTSocketDelegate?
11
+ private var downstreamQueue: DispatchQueue?
12
+ private var isConnAck = false
13
+ private(set) var sessionPresent: Bool?
14
+
15
+ init(socket: CocoaMQTTSocketProtocol) {
16
+ self.socket = socket
17
+ super.init()
18
+ }
19
+
20
+ var enableSSL: Bool {
21
+ get { socket.enableSSL }
22
+ set { socket.enableSSL = newValue }
23
+ }
24
+
25
+ func setDelegate(_ delegate: CocoaMQTTSocketDelegate?, delegateQueue: DispatchQueue?) {
26
+ downstream = delegate
27
+ downstreamQueue = delegateQueue
28
+ socket.setDelegate(self, delegateQueue: delegateQueue)
29
+ }
30
+
31
+ func connect(toHost host: String, onPort port: UInt16) throws {
32
+ try socket.connect(toHost: host, onPort: port)
33
+ }
34
+
35
+ func connect(toHost host: String, onPort port: UInt16, withTimeout timeout: TimeInterval) throws {
36
+ try socket.connect(toHost: host, onPort: port, withTimeout: timeout)
37
+ }
38
+
39
+ func disconnect() { socket.disconnect() }
40
+
41
+ func readData(toLength length: UInt, withTimeout timeout: TimeInterval, tag: Int) {
42
+ socket.readData(toLength: length, withTimeout: timeout, tag: tag)
43
+ }
44
+
45
+ func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int) {
46
+ socket.write(data, withTimeout: timeout, tag: tag)
47
+ }
48
+
49
+ func socketConnected(_ socket: CocoaMQTTSocketProtocol) {
50
+ forward { $0.socketConnected(self) }
51
+ }
52
+
53
+ func socket(_ socket: CocoaMQTTSocketProtocol, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void) {
54
+ forward { $0.socket(self, didReceive: trust, completionHandler: completionHandler) }
55
+ }
56
+
57
+ func socketUrlSession(_ socket: CocoaMQTTSocketProtocol, didReceiveTrust trust: SecTrust, didReceiveChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
58
+ forward { $0.socketUrlSession(self, didReceiveTrust: trust, didReceiveChallenge: challenge, completionHandler: completionHandler) }
59
+ }
60
+
61
+ func socket(_ socket: CocoaMQTTSocketProtocol, didWriteDataWithTag tag: Int) {
62
+ forward { $0.socket(self, didWriteDataWithTag: tag) }
63
+ }
64
+
65
+ func socket(_ socket: CocoaMQTTSocketProtocol, didRead data: Data, withTag tag: Int) {
66
+ if tag == 0, let header = data.first {
67
+ isConnAck = header >> 4 == 2
68
+ } else if tag == 2, isConnAck, let flags = data.first {
69
+ sessionPresent = flags & 1 == 1
70
+ isConnAck = false
71
+ }
72
+ forward { $0.socket(self, didRead: data, withTag: tag) }
73
+ }
74
+
75
+ func socketDidDisconnect(_ socket: CocoaMQTTSocketProtocol, withError error: Error?) {
76
+ forward { $0.socketDidDisconnect(self, withError: error) }
77
+ }
78
+
79
+ private func forward(_ body: @escaping (CocoaMQTTSocketDelegate) -> Void) {
80
+ guard let downstream else { return }
81
+ if let downstreamQueue {
82
+ downstreamQueue.async { body(downstream) }
83
+ } else {
84
+ body(downstream)
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,420 @@
1
+ import Foundation
2
+
3
+ enum ReactNativeMqttSessionState: String {
4
+ case idle
5
+ case connecting
6
+ case connected
7
+ case reconnecting
8
+ case disconnecting
9
+ case disconnected
10
+ case closed
11
+ }
12
+
13
+ struct ReactNativeMqttSessionSnapshot {
14
+ let state: ReactNativeMqttSessionState
15
+ let revision: UInt64
16
+ let generation: UInt64
17
+ }
18
+
19
+ protocol ReactNativeMqttSessionTransport: AnyObject {
20
+ func connect(completion: @escaping (Result<Any, Error>) -> Void)
21
+ func disconnect(completion: @escaping (Error?) -> Void)
22
+ func setDisconnectedHandler(_ handler: @escaping (Error?) -> Void)
23
+ }
24
+
25
+ /// Owns one transport lifecycle. All mutable state is accessed on `queue`.
26
+ /// Transport callbacks may arrive on arbitrary SDK queues and are immediately
27
+ /// re-enqueued, so stale generations cannot mutate a replacement connection.
28
+ final class ReactNativeMqttSession {
29
+ typealias SnapshotHandler = (ReactNativeMqttSessionSnapshot) -> Void
30
+ typealias ConnectHandler = (ReactNativeMqttSessionSnapshot, Any) -> Void
31
+
32
+ private let queue: DispatchQueue
33
+ private let transport: ReactNativeMqttSessionTransport
34
+ private let reconnectEnabled: Bool
35
+ private let reconnectInitialDelayMs: UInt64
36
+ private let reconnectMaxDelayMs: UInt64
37
+ private let reconnectJitterRatio: Double
38
+ private let connectTimeoutMs: UInt64
39
+ private let disconnectTimeoutMs: UInt64
40
+ private(set) var snapshot: ReactNativeMqttSessionSnapshot
41
+ private var connectWaiters: [(Result<Any, Error>) -> Void] = []
42
+ private var disconnectWaiters: [(Error?) -> Void] = []
43
+ private var reconnectWorkItem: DispatchWorkItem?
44
+ private var disconnectWorkItem: DispatchWorkItem?
45
+ private var reconnectAttempts: UInt64 = 0
46
+ private var lastConnectValue: Any?
47
+ private var userStopRequested = false
48
+ private var disposing = false
49
+ private var disposed = false
50
+ private let disconnectHandler: ((Error?) -> Void)?
51
+ private var snapshotHandler: SnapshotHandler?
52
+ private var connectHandler: ConnectHandler?
53
+
54
+ init(
55
+ transport: ReactNativeMqttSessionTransport,
56
+ queueLabel: String,
57
+ reconnectEnabled: Bool = true,
58
+ reconnectInitialDelayMs: UInt64 = 1_000,
59
+ reconnectMaxDelayMs: UInt64 = 30_000,
60
+ reconnectJitterRatio: Double = 0.2,
61
+ connectTimeoutMs: UInt64 = 30_000,
62
+ disconnectTimeoutMs: UInt64 = 5_000,
63
+ disconnectHandler: ((Error?) -> Void)? = nil,
64
+ snapshotHandler: SnapshotHandler? = nil
65
+ ) {
66
+ precondition(reconnectInitialDelayMs > 0 && reconnectMaxDelayMs >= reconnectInitialDelayMs)
67
+ precondition((0...1).contains(reconnectJitterRatio))
68
+ precondition(connectTimeoutMs > 0 && disconnectTimeoutMs > 0)
69
+ self.queue = DispatchQueue(label: queueLabel)
70
+ self.transport = transport
71
+ self.reconnectEnabled = reconnectEnabled
72
+ self.reconnectInitialDelayMs = reconnectInitialDelayMs
73
+ self.reconnectMaxDelayMs = reconnectMaxDelayMs
74
+ self.reconnectJitterRatio = reconnectJitterRatio
75
+ self.connectTimeoutMs = connectTimeoutMs
76
+ self.disconnectTimeoutMs = disconnectTimeoutMs
77
+ self.disconnectHandler = disconnectHandler
78
+ self.snapshot = ReactNativeMqttSessionSnapshot(state: .idle, revision: 0, generation: 0)
79
+ self.snapshotHandler = snapshotHandler
80
+ transport.setDisconnectedHandler { [weak self] error in
81
+ self?.queue.async { self?.transportDisconnected(error) }
82
+ }
83
+ }
84
+
85
+ func connect(completion: @escaping (Result<Any, Error>) -> Void) {
86
+ queue.async { [weak self] in self?.connectOnQueue(completion) }
87
+ }
88
+
89
+ func disconnect(completion: @escaping (Error?) -> Void) {
90
+ queue.async { [weak self] in self?.disconnectOnQueue(completion) }
91
+ }
92
+
93
+ func dispose(completion: @escaping (Error?) -> Void) {
94
+ queue.async { [weak self] in self?.disposeOnQueue(completion) }
95
+ }
96
+
97
+ func setSnapshotHandler(_ handler: SnapshotHandler?) {
98
+ queue.async { [weak self] in self?.snapshotHandler = handler }
99
+ }
100
+
101
+ func setConnectHandler(_ handler: ConnectHandler?) {
102
+ queue.async { [weak self] in self?.connectHandler = handler }
103
+ }
104
+
105
+ private func connectOnQueue(_ completion: @escaping (Result<Any, Error>) -> Void) {
106
+ guard !disposing && !disposed else {
107
+ completion(.failure(ReactNativeMqttSessionError.disposed))
108
+ return
109
+ }
110
+ userStopRequested = false
111
+ if snapshot.state == .connected {
112
+ if let lastConnectValue {
113
+ completion(.success(lastConnectValue))
114
+ } else {
115
+ completion(.failure(ReactNativeMqttSessionError.connectTimedOut))
116
+ }
117
+ return
118
+ }
119
+ connectWaiters.append(completion)
120
+ guard snapshot.state != .connecting && snapshot.state != .reconnecting else { return }
121
+ reconnectWorkItem?.cancel()
122
+ reconnectWorkItem = nil
123
+ beginConnectOnQueue()
124
+ }
125
+
126
+ private func beginConnectOnQueue() {
127
+ guard !disposing && !disposed else { return }
128
+ let generation = snapshot.generation &+ 1
129
+ setStateOnQueue(.connecting, generation: generation)
130
+ var completed = false
131
+ let finish: (Result<Any, Error>) -> Void = { [weak self] result in
132
+ self?.queue.async {
133
+ guard let self, !completed else { return }
134
+ completed = true
135
+ self.finishConnectOnQueue(result, generation: generation)
136
+ }
137
+ }
138
+ transport.connect(completion: finish)
139
+ let timeout = DispatchWorkItem { [weak self] in
140
+ guard let self else { return }
141
+ self.queue.async {
142
+ guard !completed, self.snapshot.generation == generation, self.snapshot.state == .connecting else { return }
143
+ completed = true
144
+ self.transport.disconnect { _ in }
145
+ self.finishConnectOnQueue(.failure(ReactNativeMqttSessionError.connectTimedOut), generation: generation)
146
+ }
147
+ }
148
+ queue.asyncAfter(deadline: .now() + .milliseconds(Int(connectTimeoutMs)), execute: timeout)
149
+ }
150
+
151
+ private func finishConnectOnQueue(_ result: Result<Any, Error>, generation: UInt64) {
152
+ guard !disposing && !disposed && snapshot.generation == generation else { return }
153
+ switch result {
154
+ case .success(let value):
155
+ lastConnectValue = value
156
+ reconnectAttempts = 0
157
+ setStateOnQueue(.connected, generation: generation)
158
+ connectHandler?(snapshot, value)
159
+ let waiters = connectWaiters
160
+ connectWaiters.removeAll()
161
+ waiters.forEach { $0(result) }
162
+ case .failure(let error):
163
+ setStateOnQueue(.disconnected, generation: generation)
164
+ let waiters = connectWaiters
165
+ connectWaiters.removeAll()
166
+ waiters.forEach { $0(.failure(error)) }
167
+ }
168
+ }
169
+
170
+ private func transportDisconnected(_ error: Error?) {
171
+ guard !disposing && !disposed else { return }
172
+ guard snapshot.state == .connected else { return }
173
+ setStateOnQueue(.reconnecting, generation: snapshot.generation)
174
+ if reconnectEnabled && !userStopRequested {
175
+ scheduleReconnectOnQueue()
176
+ } else {
177
+ setStateOnQueue(.disconnected, generation: snapshot.generation)
178
+ }
179
+ disconnectHandler?(error)
180
+ }
181
+
182
+ private func scheduleReconnectOnQueue() {
183
+ reconnectAttempts &+= 1
184
+ let exponent = min(reconnectAttempts - 1, 31)
185
+ let base = min(reconnectMaxDelayMs, reconnectInitialDelayMs << exponent)
186
+ let jitter = Double(base) * reconnectJitterRatio
187
+ let random = Double.random(in: -jitter...jitter)
188
+ let delay = UInt64(max(0, Double(base) + random))
189
+ let work = DispatchWorkItem { [weak self] in
190
+ guard let self else { return }
191
+ self.queue.async {
192
+ guard !self.userStopRequested, !self.disposing, !self.disposed, self.snapshot.state == .reconnecting else { return }
193
+ self.beginConnectOnQueue()
194
+ }
195
+ }
196
+ reconnectWorkItem = work
197
+ queue.asyncAfter(deadline: .now() + .milliseconds(Int(delay)), execute: work)
198
+ }
199
+
200
+ private func disconnectOnQueue(_ completion: @escaping (Error?) -> Void) {
201
+ userStopRequested = true
202
+ reconnectWorkItem?.cancel()
203
+ reconnectWorkItem = nil
204
+ connectWaiters.forEach { $0(.failure(ReactNativeMqttSessionError.cancelled)) }
205
+ connectWaiters.removeAll()
206
+ guard snapshot.state != .connected && snapshot.state != .connecting && snapshot.state != .reconnecting else {
207
+ disconnectWaiters.append(completion)
208
+ if snapshot.state != .disconnecting { beginDisconnectOnQueue() }
209
+ return
210
+ }
211
+ setStateOnQueue(.disconnected, generation: snapshot.generation)
212
+ completion(nil)
213
+ }
214
+
215
+ private func beginDisconnectOnQueue() {
216
+ let generation = snapshot.generation
217
+ setStateOnQueue(.disconnecting, generation: generation)
218
+ var finished = false
219
+ let finish: (Error?) -> Void = { [weak self] error in
220
+ self?.queue.async {
221
+ guard let self, !finished, self.snapshot.generation == generation else { return }
222
+ finished = true
223
+ self.disconnectWorkItem?.cancel()
224
+ self.disconnectWorkItem = nil
225
+ self.setStateOnQueue(.disconnected, generation: generation)
226
+ let waiters = self.disconnectWaiters
227
+ self.disconnectWaiters.removeAll()
228
+ waiters.forEach { $0(error) }
229
+ }
230
+ }
231
+ transport.disconnect(completion: finish)
232
+ let timeout = DispatchWorkItem { [weak self] in
233
+ guard let self else { return }
234
+ self.queue.async {
235
+ guard !finished, self.snapshot.generation == generation else { return }
236
+ finished = true
237
+ self.transport.disconnect { _ in }
238
+ self.setStateOnQueue(.disconnected, generation: generation)
239
+ let waiters = self.disconnectWaiters
240
+ self.disconnectWaiters.removeAll()
241
+ waiters.forEach { $0(ReactNativeMqttSessionError.disconnectTimedOut) }
242
+ }
243
+ }
244
+ disconnectWorkItem = timeout
245
+ queue.asyncAfter(deadline: .now() + .milliseconds(Int(disconnectTimeoutMs)), execute: timeout)
246
+ }
247
+
248
+ private func disposeOnQueue(_ completion: @escaping (Error?) -> Void) {
249
+ if disposed {
250
+ completion(nil)
251
+ return
252
+ }
253
+ disposing = true
254
+ userStopRequested = true
255
+ reconnectWorkItem?.cancel()
256
+ reconnectWorkItem = nil
257
+ connectWaiters.forEach { $0(.failure(ReactNativeMqttSessionError.disposed)) }
258
+ connectWaiters.removeAll()
259
+ if snapshot.state == .connected || snapshot.state == .connecting || snapshot.state == .reconnecting {
260
+ disconnectWaiters.append { [weak self] error in self?.closeOnQueue(error, completion: completion) }
261
+ if snapshot.state != .disconnecting { beginDisconnectOnQueue() }
262
+ } else {
263
+ closeOnQueue(nil, completion: completion)
264
+ }
265
+ }
266
+
267
+ private func closeOnQueue(_ error: Error?, completion: @escaping (Error?) -> Void) {
268
+ guard !disposed else { completion(nil); return }
269
+ disposed = true
270
+ setStateOnQueue(.closed, generation: snapshot.generation)
271
+ completion(error)
272
+ }
273
+
274
+ private func setStateOnQueue(_ state: ReactNativeMqttSessionState, generation: UInt64) {
275
+ snapshot = ReactNativeMqttSessionSnapshot(state: state, revision: snapshot.revision &+ 1, generation: generation)
276
+ snapshotHandler?(snapshot)
277
+ }
278
+ }
279
+
280
+ enum ReactNativeMqttSessionError: Error, Equatable {
281
+ case disposed
282
+ case cancelled
283
+ case connectTimedOut
284
+ case operationTimedOut
285
+ case disconnectTimedOut
286
+ }
287
+
288
+ struct ReactNativeMqttOperationToken: Equatable {
289
+ let operationId: UInt64
290
+ let generation: UInt64
291
+ let topic: String?
292
+ }
293
+
294
+ /// Serial operation ledger for SDK callbacks that do not carry an operation
295
+ /// identity. Requests sharing a topic are serialized; different topics may
296
+ /// proceed independently. Every request has one settlement gate and one
297
+ /// monotonic deadline, so late ACKs and timeout races are harmless.
298
+ final class ReactNativeMqttOperationLedger {
299
+ typealias ResultValue = Result<Any, Error>
300
+ typealias Completion = (ResultValue) -> Void
301
+ typealias Executor = (@escaping Completion) -> Void
302
+
303
+ private struct Request {
304
+ let token: ReactNativeMqttOperationToken
305
+ let deadline: DispatchTime
306
+ let execute: Executor
307
+ let completion: Completion
308
+ }
309
+
310
+ private let queue: DispatchQueue
311
+ private var nextOperationId: UInt64 = 0
312
+ private var queues: [String: [Request]] = [:]
313
+ private var active: [String: ReactNativeMqttOperationToken] = [:]
314
+ private var settled: Set<UInt64> = []
315
+
316
+ init(queueLabel: String) {
317
+ queue = DispatchQueue(label: queueLabel)
318
+ }
319
+
320
+ func submit(
321
+ topic: String?,
322
+ generation: UInt64,
323
+ timeoutMs: UInt64,
324
+ execute: @escaping Executor,
325
+ completion: @escaping Completion
326
+ ) {
327
+ let key = topic ?? "__untagged_\(UUID().uuidString)"
328
+ queue.async {
329
+ self.nextOperationId &+= 1
330
+ let token = ReactNativeMqttOperationToken(operationId: self.nextOperationId, generation: generation, topic: topic)
331
+ let request = Request(token: token, deadline: .now() + .milliseconds(Int(timeoutMs)), execute: execute, completion: completion)
332
+ self.queues[key, default: []].append(request)
333
+ self.startNextIfNeeded(key)
334
+ }
335
+ }
336
+
337
+ private func startNextIfNeeded(_ key: String) {
338
+ guard active[key] == nil, let request = queues[key]?.first else { return }
339
+ active[key] = request.token
340
+ var finished = false
341
+ let finish: Completion = { [weak self] result in
342
+ self?.queue.async {
343
+ guard let self, !finished else { return }
344
+ finished = true
345
+ self.settle(request, key: key, result: result)
346
+ }
347
+ }
348
+ request.execute(finish)
349
+ let timeout = DispatchWorkItem { [weak self] in
350
+ guard let self else { return }
351
+ self.queue.async {
352
+ guard !finished else { return }
353
+ finished = true
354
+ self.settle(request, key: key, result: .failure(ReactNativeMqttSessionError.operationTimedOut))
355
+ }
356
+ }
357
+ queue.asyncAfter(deadline: request.deadline, execute: timeout)
358
+ }
359
+
360
+ private func settle(_ request: Request, key: String, result: ResultValue) {
361
+ guard settled.insert(request.token.operationId).inserted else { return }
362
+ request.completion(result)
363
+ if queues[key]?.isEmpty == false { queues[key]?.removeFirst() }
364
+ active.removeValue(forKey: key)
365
+ if queues[key]?.isEmpty == true { queues.removeValue(forKey: key) }
366
+ else { startNextIfNeeded(key) }
367
+ }
368
+ }
369
+
370
+ /// Serial ownership for all native sessions. `invalidate` is a terminal epoch
371
+ /// barrier: after it starts no later open can create a session.
372
+ final class ReactNativeMqttSessionRegistry {
373
+ private let queue = DispatchQueue(label: "com.cnhongwei.reactnativemqtt.session-registry")
374
+ private var sessions: [String: ReactNativeMqttSession] = [:]
375
+ private(set) var epoch: UInt64 = 0
376
+ private var invalidated = false
377
+
378
+ func open(_ instanceId: String, make: @escaping () -> ReactNativeMqttSession, completion: @escaping (Result<ReactNativeMqttSession, Error>) -> Void) {
379
+ queue.async {
380
+ guard !self.invalidated else {
381
+ completion(.failure(ReactNativeMqttSessionError.disposed))
382
+ return
383
+ }
384
+ if let existing = self.sessions[instanceId] {
385
+ completion(.success(existing))
386
+ return
387
+ }
388
+ let session = make()
389
+ self.sessions[instanceId] = session
390
+ completion(.success(session))
391
+ }
392
+ }
393
+
394
+ func remove(_ instanceId: String, completion: @escaping () -> Void) {
395
+ queue.async {
396
+ guard let session = self.sessions.removeValue(forKey: instanceId) else { completion(); return }
397
+ session.dispose { _ in completion() }
398
+ }
399
+ }
400
+
401
+ func invalidate(completion: @escaping () -> Void) {
402
+ queue.async {
403
+ self.invalidated = true
404
+ self.epoch &+= 1
405
+ let sessions = Array(self.sessions.values)
406
+ self.sessions.removeAll()
407
+ guard !sessions.isEmpty else { completion(); return }
408
+ func disposeNext(_ index: Int) {
409
+ guard index < sessions.count else {
410
+ DispatchQueue.global(qos: .userInitiated).async(execute: completion)
411
+ return
412
+ }
413
+ sessions[index].dispose { _ in
414
+ self.queue.async { disposeNext(index + 1) }
415
+ }
416
+ }
417
+ disposeNext(0)
418
+ }
419
+ }
420
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.get('ReactNativeMqtt');
5
+ //# sourceMappingURL=NativeReactNativeMqtt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeReactNativeMqtt.ts"],"mappings":";;AAAA,SACEA,mBAAmB,QAGd,cAAc;AAwKrB,eAAeA,mBAAmB,CAACC,GAAG,CAAO,iBAAiB,CAAC","ignoreList":[]}