@dittolive/ditto 4.5.1-experimental.aarch64-linux.1.aarch64 → 4.5.2-rc.2

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 (152) hide show
  1. package/DittoReactNative.podspec +25 -0
  2. package/README.md +2 -2
  3. package/node/ditto.cjs.js +1 -1
  4. package/node/ditto.cjs.js.map +1 -0
  5. package/node/ditto.cjs.pretty.js +9655 -0
  6. package/node/ditto.cjs.pretty.js.map +1 -0
  7. package/node/ditto.darwin-arm64.node +0 -0
  8. package/node/ditto.darwin-x64.node +0 -0
  9. package/node/{ditto.linux-arm64.node → ditto.linux-x64.node} +0 -0
  10. package/node/transports.darwin-arm64.node +0 -0
  11. package/node/transports.darwin-x64.node +0 -0
  12. package/package.json +2 -1
  13. package/react-native/android/CMakeLists.txt +37 -0
  14. package/react-native/android/build.gradle +186 -0
  15. package/react-native/android/cpp-adapter.cpp +254 -0
  16. package/react-native/android/gradle.properties +5 -0
  17. package/react-native/android/src/main/AndroidManifest.xml +4 -0
  18. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +85 -0
  19. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +28 -0
  20. package/react-native/cpp/include/Arc.hpp +141 -0
  21. package/react-native/cpp/include/Attachment.h +16 -0
  22. package/react-native/cpp/include/Authentication.h +23 -0
  23. package/react-native/cpp/include/Collection.h +13 -0
  24. package/react-native/cpp/include/DQL.h +21 -0
  25. package/react-native/cpp/include/Document.h +17 -0
  26. package/react-native/cpp/include/Identity.h +17 -0
  27. package/react-native/cpp/include/Lifecycle.h +17 -0
  28. package/react-native/cpp/include/LiveQuery.h +17 -0
  29. package/react-native/cpp/include/Logger.h +22 -0
  30. package/react-native/cpp/include/Misc.h +27 -0
  31. package/react-native/cpp/include/Presence.h +14 -0
  32. package/react-native/cpp/include/SmallPeerInfo.h +19 -0
  33. package/react-native/cpp/include/Transports.h +25 -0
  34. package/react-native/cpp/include/TypedArray.hpp +167 -0
  35. package/react-native/cpp/include/Utils.h +61 -0
  36. package/react-native/cpp/include/main.h +10 -0
  37. package/react-native/cpp/src/Attachment.cpp +86 -0
  38. package/react-native/cpp/src/Authentication.cpp +227 -0
  39. package/react-native/cpp/src/Collection.cpp +54 -0
  40. package/react-native/cpp/src/DQL.cpp +256 -0
  41. package/react-native/cpp/src/Document.cpp +146 -0
  42. package/react-native/cpp/src/Identity.cpp +123 -0
  43. package/react-native/cpp/src/Lifecycle.cpp +110 -0
  44. package/react-native/cpp/src/LiveQuery.cpp +63 -0
  45. package/react-native/cpp/src/Logger.cpp +200 -0
  46. package/react-native/cpp/src/Misc.cpp +283 -0
  47. package/react-native/cpp/src/Presence.cpp +79 -0
  48. package/react-native/cpp/src/SmallPeerInfo.cpp +142 -0
  49. package/react-native/cpp/src/Transports.cpp +270 -0
  50. package/react-native/cpp/src/TypedArray.cpp +303 -0
  51. package/react-native/cpp/src/Utils.cpp +138 -0
  52. package/react-native/cpp/src/main.cpp +152 -0
  53. package/react-native/dittoffi/dittoffi.h +4700 -0
  54. package/react-native/dittoffi/ifaddrs.cpp +385 -0
  55. package/react-native/dittoffi/ifaddrs.h +206 -0
  56. package/react-native/ios/DittoRNSDK.h +7 -0
  57. package/react-native/ios/DittoRNSDK.mm +107 -0
  58. package/react-native/ios/YeetJSIUtils.h +60 -0
  59. package/react-native/ios/YeetJSIUtils.mm +196 -0
  60. package/react-native/lib/commonjs/ditto.rn.js +92 -0
  61. package/react-native/lib/commonjs/ditto.rn.js.map +1 -0
  62. package/react-native/lib/commonjs/index.js +61 -0
  63. package/react-native/lib/commonjs/index.js.map +1 -0
  64. package/react-native/lib/module/ditto.rn.js +88 -0
  65. package/react-native/lib/module/ditto.rn.js.map +1 -0
  66. package/react-native/lib/module/index.js +27 -0
  67. package/react-native/lib/module/index.js.map +1 -0
  68. package/react-native/lib/typescript/ditto.rn.d.ts +15 -0
  69. package/react-native/lib/typescript/ditto.rn.d.ts.map +1 -0
  70. package/react-native/lib/typescript/index.d.ts +1 -0
  71. package/react-native/lib/typescript/index.d.ts.map +1 -0
  72. package/react-native/src/ditto.rn.ts +91 -0
  73. package/react-native/src/environment/environment.fallback.ts +4 -0
  74. package/react-native/src/index.ts +26 -0
  75. package/react-native/src/sources/@cbor-redux.ts +2 -0
  76. package/react-native/src/sources/@ditto.core.ts +1 -0
  77. package/react-native/src/sources/@environment.ts +1 -0
  78. package/react-native/src/sources/attachment-fetch-event.ts +54 -0
  79. package/react-native/src/sources/attachment-fetcher-manager.ts +144 -0
  80. package/react-native/src/sources/attachment-fetcher.ts +134 -0
  81. package/react-native/src/sources/attachment-token.ts +48 -0
  82. package/react-native/src/sources/attachment.ts +74 -0
  83. package/react-native/src/sources/augment.ts +101 -0
  84. package/react-native/src/sources/authenticator.ts +314 -0
  85. package/react-native/src/sources/base-pending-cursor-operation.ts +239 -0
  86. package/react-native/src/sources/base-pending-id-specific-operation.ts +109 -0
  87. package/react-native/src/sources/bridge.ts +553 -0
  88. package/react-native/src/sources/build-time-constants.ts +8 -0
  89. package/react-native/src/sources/cbor.ts +35 -0
  90. package/react-native/src/sources/collection-interface.ts +67 -0
  91. package/react-native/src/sources/collection.ts +212 -0
  92. package/react-native/src/sources/collections-event.ts +99 -0
  93. package/react-native/src/sources/counter.ts +82 -0
  94. package/react-native/src/sources/ditto.ts +979 -0
  95. package/react-native/src/sources/document-id.ts +159 -0
  96. package/react-native/src/sources/document-path.ts +306 -0
  97. package/react-native/src/sources/document.ts +193 -0
  98. package/react-native/src/sources/epilogue.ts +30 -0
  99. package/react-native/src/sources/error-codes.ts +52 -0
  100. package/react-native/src/sources/error.ts +208 -0
  101. package/react-native/src/sources/essentials.ts +53 -0
  102. package/react-native/src/sources/ffi-error.ts +122 -0
  103. package/react-native/src/sources/ffi.ts +2012 -0
  104. package/react-native/src/sources/identity.ts +163 -0
  105. package/react-native/src/sources/init.ts +71 -0
  106. package/react-native/src/sources/internal.ts +109 -0
  107. package/react-native/src/sources/keep-alive.ts +73 -0
  108. package/react-native/src/sources/key-path.ts +198 -0
  109. package/react-native/src/sources/live-query-event.ts +208 -0
  110. package/react-native/src/sources/live-query-manager.ts +102 -0
  111. package/react-native/src/sources/live-query.ts +166 -0
  112. package/react-native/src/sources/logger.ts +196 -0
  113. package/react-native/src/sources/main.ts +60 -0
  114. package/react-native/src/sources/observer-manager.ts +178 -0
  115. package/react-native/src/sources/observer.ts +79 -0
  116. package/react-native/src/sources/pending-collections-operation.ts +232 -0
  117. package/react-native/src/sources/pending-cursor-operation.ts +218 -0
  118. package/react-native/src/sources/pending-id-specific-operation.ts +218 -0
  119. package/react-native/src/sources/presence-manager.ts +161 -0
  120. package/react-native/src/sources/presence.ts +233 -0
  121. package/react-native/src/sources/query-result-item.ts +116 -0
  122. package/react-native/src/sources/query-result.ts +55 -0
  123. package/react-native/src/sources/register.ts +95 -0
  124. package/react-native/src/sources/small-peer-info.ts +177 -0
  125. package/react-native/src/sources/static-tcp-client.ts +6 -0
  126. package/react-native/src/sources/store-observer.ts +177 -0
  127. package/react-native/src/sources/store.ts +385 -0
  128. package/react-native/src/sources/subscription-manager.ts +99 -0
  129. package/react-native/src/sources/subscription.ts +89 -0
  130. package/react-native/src/sources/sync-subscription.ts +90 -0
  131. package/react-native/src/sources/sync.ts +559 -0
  132. package/react-native/src/sources/test-helpers.ts +24 -0
  133. package/react-native/src/sources/transport-conditions-manager.ts +104 -0
  134. package/react-native/src/sources/transport-config.ts +430 -0
  135. package/react-native/src/sources/update-result.ts +66 -0
  136. package/react-native/src/sources/update-results-map.ts +57 -0
  137. package/react-native/src/sources/websocket-client.ts +7 -0
  138. package/react-native/src/sources/write-transaction-collection.ts +122 -0
  139. package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +101 -0
  140. package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +74 -0
  141. package/react-native/src/sources/write-transaction.ts +121 -0
  142. package/react-native.config.js +9 -0
  143. package/types/ditto.d.ts.map +1 -0
  144. package/web/ditto.es6.js +1 -1
  145. package/web/ditto.es6.js.map +1 -0
  146. package/web/ditto.es6.pretty.js +12600 -0
  147. package/web/ditto.es6.pretty.js.map +1 -0
  148. package/web/ditto.umd.js +1 -1
  149. package/web/ditto.umd.js.map +1 -0
  150. package/web/ditto.umd.pretty.js +12669 -0
  151. package/web/ditto.umd.pretty.js.map +1 -0
  152. package/web/ditto.wasm +0 -0
@@ -0,0 +1,559 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+ import * as Environment from './@environment'
7
+
8
+ import { Bridge } from './bridge'
9
+ import { CBOR, documentIDReplacer } from './cbor'
10
+ import { defaultDittoCloudURL } from './internal'
11
+ import { SyncSubscription } from './sync-subscription'
12
+ import { TransportConfig } from './transport-config'
13
+ import { DittoError, mapFFIErrors } from './error'
14
+
15
+ import type { Ditto } from './ditto'
16
+ import type { Identity } from './identity'
17
+ import type { DQLQueryArguments } from './essentials'
18
+
19
+ /** @internal */
20
+ export type SyncParameters = {
21
+ readonly isSyncActive: boolean
22
+ readonly isX509Valid: boolean
23
+ readonly isWebValid: boolean
24
+ readonly ditto: Ditto
25
+ readonly identity: Identity
26
+ readonly transportConfig: TransportConfig
27
+ }
28
+
29
+ /** @internal */
30
+ export type SyncState = {
31
+ // NOTE: we can get away with just an effective transport config for now,
32
+ // but this is meant to carry more state if need be. Whenever possible,
33
+ // prefer putting logic into the `stateFrom()` function together with some
34
+ // flags here and keep the `updateXXX()` to just diffing and updating the
35
+ // low-level transports machinery.
36
+ readonly underlyingSyncParameters: SyncParameters
37
+ readonly effectiveTransportConfig: TransportConfig
38
+ }
39
+
40
+ /**
41
+ * Provides access to sync related functionality of Ditto.
42
+ *
43
+ * Access this object via {@link Ditto.sync | Ditto.sync} on any Ditto instance.
44
+ */
45
+ export class Sync {
46
+ /**
47
+ * The {@link Ditto} instance managed by this sync object.
48
+ */
49
+ readonly ditto: Ditto
50
+
51
+ /**
52
+ * All currently active sync subscriptions.
53
+ *
54
+ * **Note:** Manage sync subscriptions using
55
+ * {@link registerSubscription | registerSubscription()} to register a new
56
+ * sync subscription and
57
+ * {@link SyncSubscription.cancel | SyncSubscription.cancel()} to remove an
58
+ * existing sync subscription.
59
+ */
60
+ readonly subscriptions: Readonly<Array<SyncSubscription>> = Object.freeze([])
61
+
62
+ /**
63
+ * Installs and returns a sync subscription for a query, configuring Ditto to
64
+ * receive updates from other peers for documents matching that query. The
65
+ * passed in query must be a `SELECT` query, otherwise an error is thrown.
66
+ *
67
+ * @param query a string containing a valid query expressed in DQL.
68
+ * @param queryArguments an object containing the arguments for the query.
69
+ * Example: `{mileage: 123}` for a query with `:mileage` placeholder.
70
+ * @returns An active `SyncSubscription` for the passed in query and
71
+ * arguments. It will remain active until it is
72
+ * {@link SyncSubscription.cancel | cancelled} or the {@link Ditto} instance
73
+ * managing the sync subscription has been closed.
74
+ * @throws {@link DittoError} `query/invalid`: if `query` argument is not a
75
+ * string or not valid DQL.
76
+ * @throws {@link DittoError} `query/arguments-invalid`: if `queryArguments`
77
+ * argument is invalid (e.g. contains unsupported types).
78
+ * @throws {@link DittoError} `query/unsupported`: if the query is not a
79
+ * `SELECT` query.
80
+ * @throws {@link DittoError} may throw other errors.
81
+ */
82
+ registerSubscription(query: string, queryArguments?: DQLQueryArguments): SyncSubscription {
83
+ if (typeof query !== 'string') {
84
+ throw new DittoError('query/invalid', `Expected parameter 'query' to be of type 'string', found: ${typeof query}`)
85
+ }
86
+
87
+ let queryArgumentsCBOR: Uint8Array | null = null
88
+ if (queryArguments != null) {
89
+ try {
90
+ queryArgumentsCBOR = CBOR.encode(queryArguments, documentIDReplacer)
91
+ } catch (error: any) {
92
+ throw new DittoError('query/arguments-invalid', `Unable to encode query arguments: ${error.message}`)
93
+ }
94
+ }
95
+
96
+ const errorContext = { query, queryArguments }
97
+ const dittoHandle = Bridge.ditto.handleFor(this.ditto)
98
+ this.ditto.deferClose(() => {
99
+ mapFFIErrors(() => FFI.tryExperimentalAddDQLSubscription(dittoHandle.deref(), query, queryArgumentsCBOR), undefined, errorContext)
100
+ })
101
+
102
+ const subscription = new SyncSubscription(this.ditto, query, queryArguments || null, queryArgumentsCBOR)
103
+
104
+ // @ts-expect-error modifying readonly property
105
+ this.subscriptions = Object.freeze([...this.subscriptions, subscription])
106
+
107
+ return subscription
108
+ }
109
+
110
+ // ---------------------------------------------------------- Internal ------
111
+
112
+ /** @internal */
113
+ get parameters(): SyncParameters {
114
+ return this.state.underlyingSyncParameters
115
+ }
116
+
117
+ /** @internal */
118
+ constructor(ditto: Ditto) {
119
+ const identity = ditto.identity
120
+ const transportConfig = new TransportConfig()
121
+ const parameters = { identity, transportConfig, ditto, isWebValid: false, isX509Valid: false, isSyncActive: false }
122
+
123
+ this.ditto = ditto
124
+ this.state = stateFrom(parameters)
125
+
126
+ FFI.transportsInit()
127
+ }
128
+
129
+ /**
130
+ * Removes the passed in `syncSubscription`, configuring Ditto to not receive
131
+ * updates for documents matching the corresponding query anymore. No-op if
132
+ * the passed in `syncSubscription` has already been removed.
133
+ *
134
+ * This must only be called by the sync subscription itself.
135
+ *
136
+ * @param syncSubscription the sync subscription to remove
137
+ * @returns `true` if the passed in sync subscription could be found and has
138
+ * been removed, otherwise returns `false`.
139
+ * @throws {@link DittoError} `internal`: if the replication subscription does not
140
+ * belong to this store
141
+ * @throws {@link DittoError} `internal`: if the replication subscription has not
142
+ * been cancelled yet
143
+ * @internal
144
+ */
145
+ unregisterSubscription(syncSubscription: SyncSubscription): boolean {
146
+ if (syncSubscription.ditto !== this.ditto) {
147
+ throw new DittoError('internal', `Can't remove replication subscription that does not belong to this store`)
148
+ }
149
+
150
+ if (!syncSubscription.isCancelled) {
151
+ throw new DittoError('internal', "Internal inconsistency, can't remove replication subscription that has not been cancelled")
152
+ }
153
+
154
+ const indexToDelete = this.subscriptions.findIndex((s) => s === syncSubscription)
155
+ if (indexToDelete === -1) {
156
+ return false
157
+ }
158
+
159
+ const newSyncSubscriptions = [...this.subscriptions]
160
+ newSyncSubscriptions.splice(indexToDelete, 1)
161
+ // @ts-expect-error modifying readonly property
162
+ this.subscriptions = Object.freeze(newSyncSubscriptions)
163
+
164
+ const dittoHandle = Bridge.ditto.handleFor(this.ditto)
165
+ this.ditto.deferClose(() => {
166
+ // prettier-ignore
167
+ mapFFIErrors(
168
+ () => FFI.tryExperimentalRemoveDQLSubscription(dittoHandle.deref(), syncSubscription.queryString, syncSubscription.queryArgumentsCBOR)
169
+ )
170
+ })
171
+ return true
172
+ }
173
+
174
+ /** @internal */
175
+ update(parameters: SyncParameters) {
176
+ // NOTE: updating is a two-step process. Given all parameters, we
177
+ // first compute the final "state" we want the transports stuff to
178
+ // be in via the `stateFrom()` function. We then take that "desired" state
179
+ // and feed it into the various update methods, which essentially perform
180
+ // a diff on the state before and after, and update the low-level transports
181
+ // machinery accordingly.
182
+
183
+ const stateOld = this.state
184
+ const stateNew = stateFrom(parameters)
185
+
186
+ this.updatePeerToPeerBluetoothLE(stateOld, stateNew)
187
+ this.updatePeerToPeerAWDL(stateOld, stateNew)
188
+ this.updatePeerToPeerLAN(stateOld, stateNew)
189
+ this.updateListenTCP(stateOld, stateNew)
190
+ this.updateListenHTTP(stateOld, stateNew)
191
+ this.updateConnectTCPServers(stateOld, stateNew)
192
+ this.updateConnectWebsocketURLs(stateOld, stateNew)
193
+ this.updateGlobal(stateOld, stateNew)
194
+ this.updateConnectRetryInterval(stateOld, stateNew)
195
+
196
+ this.state = stateNew
197
+ }
198
+
199
+ /** @internal */
200
+ close() {
201
+ if (this.parameters.isSyncActive) {
202
+ throw new Error(`Internal inconsistency, can't close sync object while sync is active, please 'stopSync()' first.`)
203
+ }
204
+
205
+ for (const subscription of this.subscriptions) {
206
+ subscription.cancel()
207
+ }
208
+
209
+ // Nothing to do, when sync is stopped, this object should be
210
+ // already be cleaned up properly.
211
+ }
212
+
213
+ // ------------------------------------------------------------ Private ------
214
+
215
+ private state: SyncState
216
+
217
+ private bluetoothLETransportPointer: FFI.Pointer<'DittoTransportsBLE_t'> | null = null
218
+ private awdlTransportPointer: FFI.Pointer<'DittoTransportsAWDL_t'> | null = null
219
+ private lanTransportPointer: FFI.Pointer<'DittoTransportsLAN_t'> | null = null
220
+ private mdnsClientTransportPointer: FFI.Pointer<'DittoTransportsMdnsClient_t'> | null = null
221
+ private mdnsServerAdvertiserPointer: FFI.Pointer<'DittoTransportsMdnsServer_t'> | null = null
222
+
223
+ private updatePeerToPeerBluetoothLE(stateOld: SyncState, stateNew: SyncState) {
224
+ // Updating BLE transport is a no-op outside of Node.js & React Native.
225
+ if (!Environment.isNodeBuild && !Environment.isReactNativeBuild) return
226
+
227
+ const ditto = this.ditto
228
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
229
+ ditto.deferClose(() => {
230
+ const bluetoothLEOld = stateOld.effectiveTransportConfig.peerToPeer.bluetoothLE
231
+ const bluetoothLENew = stateNew.effectiveTransportConfig.peerToPeer.bluetoothLE
232
+
233
+ const shouldStart = !bluetoothLEOld.isEnabled && bluetoothLENew.isEnabled
234
+ const shouldStop = bluetoothLEOld.isEnabled && !bluetoothLENew.isEnabled
235
+
236
+ if (shouldStart && this.bluetoothLETransportPointer) throw new Error(`Internal inconsistency, when starting BLE transport, no BLE transport pointer should exist.`)
237
+ if (shouldStop && !this.bluetoothLETransportPointer) throw new Error(`Internal inconsistency, when stopping BLE transport, a BLE transport pointer should exist.`)
238
+
239
+ if (process.platform === 'linux' || process.platform === 'win32') {
240
+ if (shouldStart) {
241
+ const clientTransport = FFI.dittoAddInternalBLEClientTransport(dittoHandle.deref())
242
+ const serverTransport = FFI.dittoAddInternalBLEServerTransport(dittoHandle.deref())
243
+ const blePlatform = { clientTransport, serverTransport } as any
244
+ this.bluetoothLETransportPointer = blePlatform
245
+ }
246
+
247
+ if (shouldStop) {
248
+ const blePlatform = this.bluetoothLETransportPointer as any
249
+ const { clientTransport, serverTransport } = blePlatform
250
+ FFI.bleServerFreeHandle(serverTransport)
251
+ FFI.bleClientFreeHandle(clientTransport)
252
+ this.bluetoothLETransportPointer = null
253
+ }
254
+
255
+ return
256
+ }
257
+
258
+ if (shouldStart) {
259
+ this.bluetoothLETransportPointer = FFI.transportsBLECreate(dittoHandle.deref()) as FFI.Pointer<FFI.TransportBluetooth> | null
260
+ }
261
+
262
+ if (shouldStop) {
263
+ // null check is performed above
264
+ FFI.transportsBLEDestroy(this.bluetoothLETransportPointer as FFI.Pointer<FFI.TransportBluetooth>)
265
+ this.bluetoothLETransportPointer = null
266
+ }
267
+ })
268
+ }
269
+
270
+ private updatePeerToPeerAWDL(stateOld: SyncState, stateNew: SyncState) {
271
+ // Updating AWDL transport is a no-op outside of Node.js & React Native.
272
+ if (!Environment.isNodeBuild && !Environment.isReactNativeBuild) return
273
+
274
+ const ditto = this.ditto
275
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
276
+ ditto.deferClose(() => {
277
+ const awdlOld = stateOld.effectiveTransportConfig.peerToPeer.awdl
278
+ const awdlNew = stateNew.effectiveTransportConfig.peerToPeer.awdl
279
+
280
+ const shouldStart = !awdlOld.isEnabled && awdlNew.isEnabled
281
+ const shouldStop = awdlOld.isEnabled && !awdlNew.isEnabled
282
+
283
+ if (shouldStart && this.awdlTransportPointer) throw new Error(`Internal inconsistency, when starting AWDL transport, no AWDL transport pointer should exist.`)
284
+ if (shouldStop && !this.awdlTransportPointer) throw new Error(`Internal inconsistency, when stopping AWDL transport, an AWDL transport pointer should exist.`)
285
+
286
+ if (shouldStart) {
287
+ this.awdlTransportPointer = FFI.transportsAWDLCreate(dittoHandle.deref()) as FFI.Pointer<FFI.TransportAWDL> | null
288
+ }
289
+
290
+ if (shouldStop) {
291
+ // null check is performed above
292
+ FFI.transportsAWDLDestroy(this.awdlTransportPointer as FFI.Pointer<FFI.TransportAWDL>)
293
+ this.awdlTransportPointer = null
294
+ }
295
+ })
296
+ }
297
+
298
+ private updatePeerToPeerLAN(stateOld: SyncState, stateNew: SyncState) {
299
+ // Updating LAN transport currently only works with Node.js & React Native.
300
+ if (!Environment.isNodeBuild && !Environment.isReactNativeBuild) return
301
+
302
+ const ditto = this.ditto
303
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
304
+ ditto.deferClose(() => {
305
+ const lanOld = stateOld.effectiveTransportConfig.peerToPeer.lan
306
+ const lanNew = stateNew.effectiveTransportConfig.peerToPeer.lan
307
+
308
+ if (process.platform === 'win32' || process.platform === 'linux') {
309
+ if (lanOld.isEnabled) {
310
+ if (lanOld.isMdnsEnabled) {
311
+ if (this.mdnsClientTransportPointer == null) throw new Error(`Internal inconsistency, when stopping LAN transport, a LAN transport pointer should exist.`)
312
+ FFI.mdnsClientFreeHandle(this.mdnsClientTransportPointer)
313
+ this.mdnsClientTransportPointer = null
314
+
315
+ if (this.mdnsServerAdvertiserPointer == null) throw new Error(`Internal inconsistency, when stopping LAN transport, a LAN transport pointer should exist.`)
316
+ FFI.mdnsServerFreeHandle(this.mdnsServerAdvertiserPointer)
317
+ this.mdnsServerAdvertiserPointer = null
318
+ }
319
+
320
+ if (lanOld.isMulticastEnabled) {
321
+ FFI.dittoRemoveMulticastTransport(dittoHandle.deref())
322
+ }
323
+ }
324
+
325
+ if (lanNew.isEnabled) {
326
+ if (lanNew.isMdnsEnabled) {
327
+ this.mdnsClientTransportPointer = FFI.dittoAddInternalMdnsTransport(dittoHandle.deref())
328
+ this.mdnsServerAdvertiserPointer = FFI.dittoAddInternalMdnsAdvertiser(dittoHandle.deref())
329
+ }
330
+
331
+ if (lanNew.isMulticastEnabled) {
332
+ FFI.dittoAddMulticastTransport(dittoHandle.deref())
333
+ }
334
+ }
335
+
336
+ return
337
+ }
338
+
339
+ // IDEA: move the logic for this dance into stateFrom() signal
340
+ // via some additional state signaling the actions here.
341
+
342
+ if (lanOld.isEnabled) {
343
+ if (lanOld.isMdnsEnabled) {
344
+ if (this.lanTransportPointer == null) throw new Error(`Internal inconsistency, when stopping LAN transport, a LAN transport pointer should exist.`)
345
+ FFI.transportsLANDestroy(this.lanTransportPointer as FFI.Pointer<FFI.TransportLAN>)
346
+ this.lanTransportPointer = null
347
+ }
348
+
349
+ if (lanOld.isMulticastEnabled) {
350
+ FFI.dittoRemoveMulticastTransport(dittoHandle.deref())
351
+ }
352
+ }
353
+
354
+ if (lanNew.isEnabled) {
355
+ if (lanNew.isMdnsEnabled) {
356
+ if (this.lanTransportPointer != null) throw new Error(`Internal inconsistency, when starting LAN transport, no LAN transport pointer should exist.`)
357
+ this.lanTransportPointer = FFI.transportsLANCreate(dittoHandle.deref()) as FFI.Pointer<FFI.TransportLAN>
358
+ }
359
+
360
+ if (lanNew.isMulticastEnabled) {
361
+ FFI.dittoAddMulticastTransport(dittoHandle.deref())
362
+ }
363
+ }
364
+ })
365
+ }
366
+
367
+ private updateListenTCP(stateOld: SyncState, stateNew: SyncState) {
368
+ const ditto = this.ditto
369
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
370
+ ditto.deferClose(() => {
371
+ const tcpOld = stateOld.effectiveTransportConfig.listen.tcp
372
+ const tcpNew = stateNew.effectiveTransportConfig.listen.tcp
373
+
374
+ if (TransportConfig.areListenTCPsEqual(tcpNew, tcpOld)) return
375
+
376
+ if (tcpOld.isEnabled) FFI.dittoStopTCPServer(dittoHandle.deref())
377
+ if (tcpNew.isEnabled) FFI.dittoStartTCPServer(dittoHandle.deref(), `${tcpNew.interfaceIP}:${tcpNew.port}`)
378
+ })
379
+ }
380
+
381
+ private updateListenHTTP(stateOld: SyncState, stateNew: SyncState) {
382
+ const ditto = this.ditto
383
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
384
+ ditto.deferClose(() => {
385
+ const httpOld = stateOld.effectiveTransportConfig.listen.http
386
+ const httpNew = stateNew.effectiveTransportConfig.listen.http
387
+
388
+ if (TransportConfig.areListenHTTPsEqual(httpOld, httpNew)) return
389
+ if (httpOld.isEnabled) FFI.dittoStopHTTPServer(dittoHandle.deref())
390
+
391
+ /* eslint-disable */
392
+ if (httpNew.isEnabled) {
393
+ FFI.dittoStartHTTPServer(dittoHandle.deref(), `${httpNew.interfaceIP}:${httpNew.port}`, httpNew.staticContentPath || null, httpNew.websocketSync ? 'Enabled' : 'Disabled', httpNew.tlsCertificatePath || null, httpNew.tlsKeyPath || null)
394
+ }
395
+ /* eslint-enable */
396
+ })
397
+ }
398
+
399
+ private updateConnectTCPServers(stateOld: SyncState, stateNew: SyncState) {
400
+ const ditto = this.ditto
401
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
402
+ ditto.deferClose(() => {
403
+ const tcpServers = stateNew.effectiveTransportConfig.connect.tcpServers
404
+ FFI.dittoSetStaticTCPClients(dittoHandle.deref(), tcpServers)
405
+ })
406
+ }
407
+
408
+ private updateConnectWebsocketURLs(stateOld: SyncState, stateNew: SyncState) {
409
+ const ditto = this.ditto
410
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
411
+ ditto.deferClose(() => {
412
+ const websocketURLs = stateNew.effectiveTransportConfig.connect.websocketURLs
413
+ const routingHint = stateNew.effectiveTransportConfig.global.routingHint
414
+ FFI.dittoSetStaticWebsocketClients(dittoHandle.deref(), websocketURLs, routingHint)
415
+ })
416
+ }
417
+
418
+ private updateGlobal(stateOld: SyncState, stateNew: SyncState) {
419
+ const ditto = this.ditto
420
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
421
+ ditto.deferClose(() => {
422
+ if (stateOld.effectiveTransportConfig.global.syncGroup !== stateNew.effectiveTransportConfig.global.syncGroup) {
423
+ FFI.dittoSetSyncGroup(dittoHandle.deref(), stateNew.effectiveTransportConfig.global.syncGroup)
424
+ }
425
+ })
426
+ }
427
+
428
+ private updateConnectRetryInterval(stateOld: SyncState, stateNew: SyncState) {
429
+ const ditto = this.ditto
430
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
431
+ ditto.deferClose(() => {
432
+ FFI.dittoSetConnectRetryInterval(dittoHandle.deref(), stateNew.effectiveTransportConfig.connect.retryInterval)
433
+ })
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Computes the effective transport config given a desired transport config and
439
+ * some additional state.
440
+ *
441
+ * Disables all transports that are unavailable due to the runtime environment
442
+ * or authentication requirements. Adds websocket URL for Ditto Cloud if
443
+ * enabled.
444
+ *
445
+ * Throws if the target transport config enables sync but contains transports
446
+ * that are not available in the current environment. Does not throw when
447
+ * transports are enabled but not available due to authentication requirements.
448
+ *
449
+ * @private
450
+ */
451
+ function stateFrom(parameters: SyncParameters): SyncState {
452
+ // IMPORTANT: this function maps a set of sync parameters to an effective
453
+ // transport config plus some extra state where need be. Make sure
454
+ // to keep this function free of side-effects.
455
+
456
+ const identity = parameters.identity
457
+
458
+ const isSyncActive = parameters.isSyncActive
459
+ const isX509Valid = parameters.isX509Valid
460
+ const isWebValid = parameters.isWebValid
461
+
462
+ let appID
463
+ let customDittoCloudURL
464
+ let isDittoCloudSyncEnabled = false
465
+
466
+ if (identity.type === 'onlinePlayground' || identity.type === 'onlineWithAuthentication') {
467
+ // NOTE: enableDittoCloudSync is true by default for any online identity.
468
+ appID = identity.appID
469
+ customDittoCloudURL = identity.customDittoCloudURL ?? null
470
+ isDittoCloudSyncEnabled = identity.enableDittoCloudSync ?? true
471
+ }
472
+
473
+ validateEnabledTransportsAvailable(parameters.ditto, parameters.transportConfig)
474
+
475
+ const transportConfig = parameters.transportConfig.copy()
476
+
477
+ if (!isSyncActive || !isX509Valid) {
478
+ transportConfig.peerToPeer.bluetoothLE.isEnabled = false
479
+ transportConfig.peerToPeer.awdl.isEnabled = false
480
+ transportConfig.peerToPeer.lan.isEnabled = false
481
+ transportConfig.listen.tcp.isEnabled = false
482
+ transportConfig.connect.tcpServers = []
483
+ }
484
+
485
+ if (!isSyncActive || !isWebValid) {
486
+ transportConfig.connect.websocketURLs = []
487
+ }
488
+
489
+ if (!isSyncActive) {
490
+ transportConfig.listen.http.isEnabled = false
491
+ }
492
+
493
+ // NOTE: we have to smuggle in an additional Ditto Cloud websocket URL to
494
+ // connect to if cloud sync is enabled.
495
+ if (isSyncActive && isWebValid && isDittoCloudSyncEnabled) {
496
+ // @ts-ignore To fix this type error, refactor using a switch
497
+ // statement, c.f.
498
+ // https://github.com/getditto/ditto/blob/d35b6c1cb280cdd6368d14137c97609b9b9b9296/js/sources/sync.ts#L488
499
+ const dittoCloudURL = customDittoCloudURL ?? defaultDittoCloudURL(appID)
500
+ transportConfig.connect.websocketURLs.push(dittoCloudURL)
501
+ }
502
+
503
+ return {
504
+ underlyingSyncParameters: parameters,
505
+ effectiveTransportConfig: transportConfig.freeze(),
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Validate that all enabled transports are available in the current environment
511
+ * and throw an error if not.
512
+ *
513
+ * @private
514
+ */
515
+ function validateEnabledTransportsAvailable(ditto: Ditto, transportConfig: TransportConfig) {
516
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
517
+ return ditto.deferClose(() => {
518
+ // - BLE
519
+ // - AWDL
520
+ // - LAN / mDNS
521
+
522
+ const unavailableButEnabledP2PTransports = []
523
+
524
+ const isBLEAvailable = FFI.transportsBLEIsAvailable(dittoHandle.deref())
525
+ const isAWDLAvailable = FFI.transportsAWDLIsAvailable(dittoHandle.deref())
526
+ const isLANAvailable = FFI.transportsLANIsAvailable(dittoHandle.deref())
527
+
528
+ if (transportConfig.peerToPeer.bluetoothLE.isEnabled && !isBLEAvailable) {
529
+ unavailableButEnabledP2PTransports.push('BluetoothLE')
530
+ }
531
+
532
+ if (transportConfig.peerToPeer.awdl.isEnabled && !isAWDLAvailable) {
533
+ unavailableButEnabledP2PTransports.push('AWDL')
534
+ }
535
+
536
+ if (transportConfig.peerToPeer.lan.isEnabled && !isLANAvailable) {
537
+ unavailableButEnabledP2PTransports.push('LAN')
538
+ }
539
+
540
+ if (unavailableButEnabledP2PTransports.length > 0) {
541
+ throw new Error(`The following P2P transports are enabled in the transport config but are not supported in the current environment: ${unavailableButEnabledP2PTransports.join(', ')}`)
542
+ }
543
+
544
+ // - Websocket / HTTP
545
+ // - TCP
546
+
547
+ if (transportConfig.connect.tcpServers.length > 0 && Environment.isWebBuild) {
548
+ throw new Error(`The transport config contains TCP servers, but this transport is not supported in web environments`)
549
+ }
550
+
551
+ if (transportConfig.listen.http.isEnabled && Environment.isWebBuild) {
552
+ throw new Error(`The transport config contains an HTTP listener, which is not supported in web environments`)
553
+ }
554
+
555
+ if (transportConfig.listen.tcp.isEnabled && Environment.isWebBuild) {
556
+ throw new Error(`The transport config contains a TCP listener, which is not supported in web environments`)
557
+ }
558
+ })
559
+ }
@@ -0,0 +1,24 @@
1
+ import { Bridge } from './bridge'
2
+
3
+ /**
4
+ * Get a count of bridged objects binned by bridge type.
5
+ *
6
+ * Use this in testing to ensure that all objects are properly garbage collected at the end of tests.
7
+ *
8
+ * @returns an object with a key per bridge type, set to the count of registered
9
+ * objects.
10
+ */
11
+ export function getBridgeLoad(): { [bridgeName: string]: number } {
12
+ const countsByType: { [bridgeName: string]: number } = {}
13
+ Bridge.all.map((bridgeWeakRef) => {
14
+ const bridge = bridgeWeakRef.deref()
15
+ if (!bridge) {
16
+ return null
17
+ }
18
+ if (bridge.count === 0) {
19
+ return null
20
+ }
21
+ countsByType[bridge.type.name] = bridge.count
22
+ })
23
+ return countsByType
24
+ }
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+
7
+ import { Bridge } from './bridge'
8
+ import { ObserverManager } from './observer-manager'
9
+
10
+ import type { Ditto, TransportCondition, ConditionSource } from './ditto'
11
+
12
+ /** @internal */
13
+ export class TransportConditionsManager extends ObserverManager {
14
+ readonly ditto: Ditto
15
+
16
+ constructor(ditto: Ditto) {
17
+ const keepAlive = ditto.keepAlive
18
+ super('TransportConditionsObservation', { keepAlive })
19
+ this.ditto = ditto
20
+ }
21
+
22
+ protected register(callback: (...args: any[]) => void) {
23
+ const ditto = this.ditto
24
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
25
+ return ditto.deferClose(() => {
26
+ return FFI.dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), callback)
27
+ })
28
+ }
29
+
30
+ protected unregister() {
31
+ const ditto = this.ditto
32
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
33
+ return ditto.deferClose(() => {
34
+ return FFI.dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), null)
35
+ })
36
+ }
37
+
38
+ protected process(conditionSource: FFI.ConditionSource, transportCondition: FFI.TransportCondition): [TransportCondition, ConditionSource] {
39
+ /* eslint-disable */
40
+ let apiConditionSource: ConditionSource
41
+ switch (conditionSource) {
42
+ case 'Bluetooth':
43
+ apiConditionSource = 'BLE'
44
+ break
45
+ case 'Tcp':
46
+ apiConditionSource = 'TCP'
47
+ break
48
+ case 'Awdl':
49
+ apiConditionSource = 'AWDL'
50
+ break
51
+ case 'Mdns':
52
+ apiConditionSource = 'MDNS'
53
+ break
54
+ }
55
+ /* eslint-enable */
56
+
57
+ /* eslint-disable */
58
+ let apiTransportCondition: TransportCondition
59
+ switch (transportCondition) {
60
+ case 'Unknown':
61
+ apiTransportCondition = 'Unknown'
62
+ break
63
+ case 'Ok':
64
+ apiTransportCondition = 'OK'
65
+ break
66
+ case 'GenericFailure':
67
+ apiTransportCondition = 'GenericFailure'
68
+ break
69
+ case 'AppInBackground':
70
+ apiTransportCondition = 'AppInBackground'
71
+ break
72
+ case 'MdnsFailure':
73
+ apiTransportCondition = 'MDNSFailure'
74
+ break
75
+ case 'TcpListenFailure':
76
+ apiTransportCondition = 'TCPListenFailure'
77
+ break
78
+ case 'NoBleCentralPermission':
79
+ apiTransportCondition = 'NoBLECentralPermission'
80
+ break
81
+ case 'NoBlePeripheralPermission':
82
+ apiTransportCondition = 'NoBLEPeripheralPermission'
83
+ break
84
+ case 'CannotEstablishConnection':
85
+ apiTransportCondition = 'CannotEstablishConnection'
86
+ break
87
+ case 'BleDisabled':
88
+ apiTransportCondition = 'BLEDisabled'
89
+ break
90
+ case 'NoBleHardware':
91
+ apiTransportCondition = 'NoBLEHardware'
92
+ break
93
+ case 'WifiDisabled':
94
+ apiTransportCondition = 'WiFiDisabled'
95
+ break
96
+ case 'TemporarilyUnavailable':
97
+ apiTransportCondition = 'TemporarilyUnavailable'
98
+ break
99
+ }
100
+ /* eslint-enable */
101
+
102
+ return [apiTransportCondition, apiConditionSource]
103
+ }
104
+ }