@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,979 @@
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 { TransportConfig, transportConfigToSerializable } from './transport-config'
9
+
10
+ import { Authenticator, OnlineAuthenticator, NotAvailableAuthenticator } from './authenticator'
11
+ import { IdentityTypesRequiringOfflineLicenseToken } from './identity'
12
+ import { Bridge } from './bridge'
13
+ import { CBOR } from './cbor'
14
+ import { Store } from './store'
15
+ import { Logger } from './logger'
16
+ import { KeepAlive } from './keep-alive'
17
+
18
+ import { Observer } from './observer'
19
+ import { ObserverManager } from './observer-manager'
20
+
21
+ import { Presence } from './presence'
22
+ import { LiveQueryManager } from './live-query-manager'
23
+ import { PresenceManager } from './presence-manager'
24
+ import { TransportConditionsManager } from './transport-conditions-manager'
25
+ import { Sync } from './sync'
26
+
27
+ import { defaultAuthURL } from './internal'
28
+ import { SubscriptionManager } from './subscription-manager'
29
+ import { AttachmentFetcherManager } from './attachment-fetcher-manager'
30
+ import { SmallPeerInfo } from './small-peer-info'
31
+
32
+ import type { Identity } from './identity'
33
+ // -----------------------------------------------------------------------------
34
+
35
+ /** Types of connections that can be established between two peers. */
36
+ export type TransportCondition = 'Unknown' | 'OK' | 'GenericFailure' | 'AppInBackground' | 'MDNSFailure' | 'TCPListenFailure' | 'NoBLECentralPermission' | 'NoBLEPeripheralPermission' | 'CannotEstablishConnection' | 'BLEDisabled' | 'NoBLEHardware' | 'WiFiDisabled' | 'TemporarilyUnavailable'
37
+
38
+ /** The source for a transport condition. */
39
+ export type ConditionSource = 'BLE' | 'TCP' | 'AWDL' | 'MDNS'
40
+
41
+ // -----------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Types of connections that can be established between two peers.
45
+ * @deprecated replaced by {@link ConnectionType}.
46
+ */
47
+ export type PresenceConnectionType = 'WiFi' | 'WebSocket' | 'AWDL' | 'BLE'
48
+
49
+ /**
50
+ * A peer object with information about an observed peer.
51
+ * @deprecated replaced by {@link Peer}.
52
+ */
53
+ export type RemotePeer = {
54
+ networkID: string
55
+ deviceName: string
56
+ rssi?: number
57
+ approximateDistanceInMeters?: number
58
+ connections: PresenceConnectionType[]
59
+ }
60
+
61
+ // -----------------------------------------------------------------------------
62
+
63
+ // The name of the directory, relative to cwd, that will be used for persistence
64
+ // if no other directory is specified.
65
+ const DEFAULT_PERSISTENCE_DIRECTORY = 'ditto'
66
+
67
+ /**
68
+ * Ditto is the entry point for accessing Ditto-related functionality.
69
+ */
70
+ export class Ditto {
71
+ /**
72
+ * A string containing the semantic version of the Ditto SDK. Example: 4.4.3
73
+ */
74
+ static get VERSION(): string {
75
+ // Requires having called FFI.initSDKVersion() first.
76
+ return FFI.dittoGetSDKSemver()
77
+ }
78
+
79
+ /**
80
+ * Configure a custom identifier for the current device.
81
+ *
82
+ * When using {@link Presence.observe | presence.observe()}, each remote peer is
83
+ * represented by a short UTF-8 "device name". By default this will be a
84
+ * truncated version of the device's hostname. It does not need to be unique
85
+ * among peers. Configure the device name before calling
86
+ * {@link startSync | startSync()}. If it is too long it may be truncated.
87
+ */
88
+ deviceName: string
89
+
90
+ /** Returns a string identifying the version of the Ditto SDK. */
91
+ get sdkVersion() {
92
+ const dittoHandle = Bridge.ditto.handleFor(this)
93
+ return this.deferClose(() => {
94
+ return FFI.dittoGetSDKVersion(dittoHandle.deref())
95
+ })
96
+ }
97
+
98
+ /**
99
+ * The (validated) identity this Ditto instance was initialized with.
100
+ */
101
+ readonly identity: Identity
102
+
103
+ /**
104
+ * The path this Ditto instance was initialized with, if no path was given at
105
+ * construction time, the default value is returned (see constructor).
106
+ *
107
+ * @deprecated `Ditto.path` is deprecated. Please update your code to use the
108
+ * new 'Ditto.persistenceDirectory' property instead.
109
+ */
110
+ get path(): string {
111
+ Logger.warning("⚠️ Deprecation Warning: The 'Ditto.path' property is deprecated. Please update your code to use the new 'Ditto.persistenceDirectory' property instead.")
112
+ return this.persistenceDirectory
113
+ }
114
+
115
+ /**
116
+ * Path to the local directory used for persistent data storage.
117
+ *
118
+ * Defaults to 'ditto'. In environments without file system access, such as
119
+ * browsers, this is used as a namespace for the internal data store.
120
+ */
121
+ readonly persistenceDirectory: string
122
+
123
+ /**
124
+ * Provides access to the SDK's store functionality.
125
+ */
126
+ readonly store: Store
127
+
128
+ /**
129
+ * Provides access to the SDK's sync functionality.
130
+ */
131
+ readonly sync: Sync
132
+
133
+ /**
134
+ * Provides access to the SDK's presence functionality.
135
+ */
136
+ readonly presence: Presence
137
+
138
+ /**
139
+ * Provides access to authentication methods for logging on to Ditto Cloud.
140
+ */
141
+ readonly auth: Authenticator
142
+
143
+ /**
144
+ * The site ID that the instance of `Ditto` is using as part of its identity.
145
+ */
146
+ readonly siteID: number | BigInt
147
+
148
+ /**
149
+ * Provides access to the SDK's small peer info functionality.
150
+ */
151
+ readonly smallPeerInfo: SmallPeerInfo
152
+
153
+ /**
154
+ * Returns `true` if an offline license token has been set, otherwise returns `false`.
155
+ *
156
+ * @see {@link setOfflineOnlyLicenseToken | setOfflineOnlyLicenseToken()}
157
+ */
158
+ get isActivated() {
159
+ return this._isActivated ?? false
160
+ }
161
+
162
+ /**
163
+ * `true` once {@link close | Ditto.close()} has been called, otherwise
164
+ * `false`.
165
+ */
166
+ get isClosed() {
167
+ return this._isClosed ?? false
168
+ }
169
+
170
+ /**
171
+ * Returns `true` if sync is active, otherwise returns `false`. Use
172
+ * {@link startSync | startSync()} to activate and
173
+ * {@link stopSync | stopSync()} to deactivate sync.
174
+ */
175
+ get isSyncActive() {
176
+ return this._isSyncActive ?? false
177
+ }
178
+
179
+ /**
180
+ * Application ID associated with the {@link Identity | identity} used by this
181
+ * Ditto instance.
182
+ * */
183
+ readonly appID: string
184
+
185
+ /**
186
+ * Initializes a new `Ditto` instance.
187
+ *
188
+ * **NOTE**: The `sharedKey` identity is only supported for Node environments,
189
+ * using this to create a Ditto instance in the web browser will throw an
190
+ * exception.
191
+ *
192
+ * @param identity - Identity for the new Ditto instance, defaults to
193
+ * `offlinePlayground` with `appID` being the empty string `''`.
194
+ *
195
+ * @param persistenceDirectory optional string containing a directory path
196
+ * that Ditto will use for persistence. Defaults to `"ditto"`. On Windows,
197
+ * the path will be automatically normalized.
198
+ *
199
+ * @see {@link Ditto.identity}
200
+ * @see {@link Ditto.persistenceDirectory}
201
+ * @throws {Error} when the current environment is not supported by this SDK.
202
+ * @throws {Error} for other failures during initialization of Ditto and
203
+ * validation of the provided identity.
204
+ */
205
+ constructor(identity?: Identity, persistenceDirectory?: string) {
206
+ if (!Ditto.isEnvironmentSupported()) {
207
+ throw new Error('Ditto does not support this JavaScript environment. Please consult the Ditto JavaScript documentation for a list of supported environments and browsers. You can use `Ditto.isEnvironmentSupported()` to run this check anytime.')
208
+ }
209
+
210
+ this.persistenceDirectory = Ditto.initPersistenceDirectory(persistenceDirectory)
211
+
212
+ const identityOrDefault = identity ?? { type: 'offlinePlayground', appID: '' }
213
+
214
+ const validIdentity = Object.freeze(this.validateIdentity(identityOrDefault))
215
+
216
+ this.identity = Object.freeze(validIdentity)
217
+
218
+ // Check if device name stays the same on sdk and core levels (#10729).
219
+
220
+ if (Environment.isWebBuild) {
221
+ this.deviceName = navigator.userAgent
222
+ }
223
+
224
+ if (Environment.isReactNativeBuild) {
225
+ this.deviceName = FFI.getDeviceName() as string
226
+ }
227
+
228
+ this.keepAlive = new KeepAlive()
229
+
230
+ // NOTE: some behavior not implemented yet as compared to the ObjC/Swift
231
+ // version:
232
+ //
233
+ // 1. Optional `identity` parameter falling back to a default one.
234
+ //
235
+ // 2. Optional siteID of the identity, falling back to a random one if
236
+ // newly created or to the stored one if already persisted.
237
+
238
+ const uninitializedDittoX = FFI.uninitializedDittoMake(this.persistenceDirectory)
239
+
240
+ let secondsRemainingUntilAuthenticationExpires: number | null = null
241
+
242
+ const weakThis = new WeakRef(this)
243
+ const identityConfig = (() => {
244
+ if (validIdentity.type === 'offlinePlayground') {
245
+ if (Environment.isReactNativeBuild) {
246
+ throw new Error('OfflinePlayground Identity is currently not implemented for React Native.')
247
+ }
248
+ return FFI.dittoIdentityConfigMakeOfflinePlayground(validIdentity.appID, validIdentity.siteID ?? 0)
249
+ }
250
+
251
+ if (validIdentity.type === 'manual') {
252
+ if (Environment.isReactNativeBuild) {
253
+ throw new Error('Manual Identify is currently not implemented for React Native.')
254
+ }
255
+ return FFI.dittoIdentityConfigMakeManual(validIdentity.certificate)
256
+ }
257
+
258
+ if (validIdentity.type === 'sharedKey') {
259
+ if (Environment.isReactNativeBuild) {
260
+ throw new Error('Shared Key Identity is currently not implemented for React Native.')
261
+ }
262
+ return FFI.dittoIdentityConfigMakeSharedKey(validIdentity.appID, validIdentity.sharedKey, validIdentity.siteID)
263
+ }
264
+
265
+ if (validIdentity.type === 'onlinePlayground') {
266
+ const authURL = validIdentity.customAuthURL ?? defaultAuthURL(validIdentity.appID)
267
+ return FFI.dittoIdentityConfigMakeOnlinePlayground(validIdentity.appID, validIdentity.token, authURL)
268
+ }
269
+
270
+ if (validIdentity.type === 'onlineWithAuthentication') {
271
+ const authURL = validIdentity.customAuthURL ?? defaultAuthURL(validIdentity.appID)
272
+ return FFI.dittoIdentityConfigMakeOnlineWithAuthentication(validIdentity.appID, authURL)
273
+ }
274
+
275
+ throw new Error(`Can't create Ditto, unsupported identity type: ${validIdentity}`)
276
+ })()
277
+
278
+ const dittoPointer = FFI.dittoMake(uninitializedDittoX, identityConfig)
279
+
280
+ FFI.dittoAuthClientSetValidityListener(dittoPointer, function (...args) {
281
+ const ditto = weakThis.deref()
282
+ if (ditto == null || ditto.isClosed) {
283
+ Logger.info('Ditto is closed, ignoring auth client validity change')
284
+ } else {
285
+ ditto.authClientValidityChanged(...args)
286
+ }
287
+ })
288
+
289
+ const isWebValid = FFI.dittoAuthClientIsWebValid(dittoPointer)
290
+ const isX509Valid = FFI.dittoAuthClientIsX509Valid(dittoPointer)
291
+
292
+ const appID = FFI.dittoAuthClientGetAppID(dittoPointer)
293
+ const siteID = FFI.dittoAuthClientGetSiteID(dittoPointer)
294
+
295
+ Bridge.ditto.bridge(dittoPointer, this)
296
+
297
+ // IMPORTANT: Keeping the auth client around accumulates run-times and
298
+ // resources which becomes a problem specifically in tests (where we use one
299
+ // Ditto instance per test). We therefore keep it only if needed, i.e.
300
+ // _only_ for the onlineWithAuthentication and online identities and
301
+ // transfer ownership of it to the authenticator.
302
+ if (validIdentity.type === 'onlineWithAuthentication') {
303
+ this.auth = new OnlineAuthenticator(this.keepAlive, this, validIdentity.authHandler)
304
+
305
+ const loginProviderX = FFI.dittoAuthClientMakeLoginProvider(function (secondsRemaining) {
306
+ const strongThis = weakThis.deref()
307
+ if (!strongThis) {
308
+ Logger.warning(`Internal inconsistency, LoginProvider callback fired after the corresponding Ditto instance has been deallocated.`)
309
+ return
310
+ }
311
+
312
+ if (strongThis.auth) {
313
+ strongThis.auth['@ditto.authenticationExpiring'](secondsRemaining)
314
+ } else {
315
+ secondsRemainingUntilAuthenticationExpires = secondsRemaining
316
+ }
317
+ })
318
+ // We don't need to worry about awaiting the result of this call because
319
+ // auth all happens in the background and so there are no guarantees we
320
+ // need to uphold by making sure things are in a certain state before the
321
+ // constructor finishes
322
+ FFI.dittoAuthSetLoginProvider(dittoPointer, loginProviderX)
323
+ } else if (validIdentity.type === 'onlinePlayground') {
324
+ this.auth = new OnlineAuthenticator(this.keepAlive, this, {
325
+ authenticationRequired: function (authenticator: Authenticator) {
326
+ // No-op.
327
+ },
328
+
329
+ authenticationExpiringSoon: function (authenticator: Authenticator, secondsRemaining: number) {
330
+ // No-op.
331
+ },
332
+ })
333
+ } else {
334
+ this.auth = new NotAvailableAuthenticator(this.keepAlive)
335
+ }
336
+
337
+ const transportConfig = this.makeDefaultTransportConfig().freeze()
338
+
339
+ //
340
+ // Assign instance properties.
341
+ //
342
+
343
+ this.appID = appID
344
+ this.siteID = siteID
345
+
346
+ this.isX509Valid = isX509Valid
347
+ this.isWebValid = isWebValid
348
+
349
+ this.sync = new Sync(this)
350
+ this.sync.update({ isSyncActive: false, isX509Valid, isWebValid, identity: validIdentity, ditto: this, transportConfig })
351
+
352
+ // We don't need a license when running in the browser, so we
353
+ // mark the instance as activated from the get-go in that case.
354
+ this._isActivated = Environment.isWebBuild || !IdentityTypesRequiringOfflineLicenseToken.includes(validIdentity.type)
355
+
356
+ this.store = new Store(this)
357
+ this.smallPeerInfo = new SmallPeerInfo(this)
358
+ this.presence = new Presence(this)
359
+ this.presenceManager = new PresenceManager(this)
360
+ this.liveQueryManager = new LiveQueryManager(this, this.keepAlive)
361
+ this.attachmentFetcherManager = new AttachmentFetcherManager(this)
362
+ this.transportConditionsManager = new TransportConditionsManager(this)
363
+ this.subscriptionManager = new SubscriptionManager(this)
364
+
365
+ disableDeadlockTimeoutWhenDebugging()
366
+
367
+ if (secondsRemainingUntilAuthenticationExpires != null) {
368
+ this.auth['@ditto.authenticationExpiring'](secondsRemainingUntilAuthenticationExpires)
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Don't terminate the process when callbacks are pending for a long time.
374
+ *
375
+ * Some methods in the Ditto library accept asynchronous functions as callback
376
+ * parameters. If these asynchronous functions do not resolve within a certain
377
+ * period of time after having been invoked by Ditto, deadlock detection gets
378
+ * triggered, resulting in the termination of the process.
379
+ *
380
+ * When Ditto is executed in a Node.js environment with an interactive
381
+ * debugger attached, this deadlock detection might get activated upon
382
+ * encountering a breakpoint. Calling `Ditto.disableDeadlockDetection()`
383
+ * disables this behavior, thus allowing the use of an interactive debugger
384
+ * without triggering the deadlock detection.
385
+ *
386
+ * This feature is not available in the browser.
387
+ */
388
+ static disableDeadlockDetection(): void {
389
+ if (Environment.isNodeBuild) {
390
+ const current = FFI.getDeadlockTimeout()
391
+ if (current !== 0) {
392
+ try {
393
+ FFI.setDeadlockTimeout(0)
394
+ } catch (e: any) {
395
+ throw new Error(`Failed to disable deadlock detection: ${e?.message}`)
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Returns `true` if deadlock detection is enabled.
403
+ *
404
+ * See
405
+ * {@link Ditto.disableDeadlockDetection | Ditto.disableDeadlockDetection()}
406
+ * for more information.
407
+ *
408
+ * This method always returns `false` in the browser where deadlock detection
409
+ * is not available.
410
+ *
411
+ * @returns `true` if deadlock detection is enabled
412
+ */
413
+ static hasDeadlockDetection(): boolean {
414
+ if (Environment.isNodeBuild) {
415
+ return FFI.getDeadlockTimeout() !== 0
416
+ }
417
+ return false
418
+ }
419
+
420
+ /**
421
+ * Check if the current environment supports running Ditto.
422
+ *
423
+ * Required APIs include:
424
+ *
425
+ * - `BigInt`
426
+ * - `FinalizationRegistry`
427
+ * - `WeakRef`
428
+ *
429
+ * Internet Explorer is not supported.
430
+ *
431
+ * @returns `true` if the environment is supported
432
+ */
433
+ static isEnvironmentSupported(): boolean {
434
+ // From https://stackoverflow.com/questions/21825157/internet-explorer-11-detection
435
+ let isIE = false
436
+ if (typeof window !== 'undefined' && window.navigator && window.navigator.userAgent && window.navigator.appVersion) {
437
+ isIE = window.navigator.userAgent.indexOf('MSIE') !== -1 || window.navigator.appVersion.indexOf('Trident/') > -1
438
+ }
439
+
440
+ let hasRequiredAPIs: boolean
441
+ try {
442
+ hasRequiredAPIs = checkAPIs()
443
+ } catch (error) {
444
+ throw new Error(`Error checking environment support: ${error}`)
445
+ }
446
+
447
+ return !isIE && hasRequiredAPIs
448
+ }
449
+
450
+ /**
451
+ * Validates and creates the given directory and returns its path.
452
+ *
453
+ * Any string containing non-whitespace characters is considered valid.
454
+ * Defaults to `ditto` if no path or an invalid path is given. In web
455
+ * environments, the given path is returned as-is if it is valid.
456
+ *
457
+ * @param path optional string containing a writable directory path
458
+ * @returns validated path
459
+ * @internal
460
+ */
461
+ static initPersistenceDirectory(path?: string): string {
462
+ let validatedPath: string
463
+ if (path == null) {
464
+ validatedPath = DEFAULT_PERSISTENCE_DIRECTORY
465
+ } else if (path.trim().length === 0) {
466
+ throw new Error(`Invalid argument for path parameter: '${path}'`)
467
+ } else {
468
+ validatedPath = path
469
+ }
470
+
471
+ if (Environment.isReactNativeBuild) {
472
+ validatedPath = FFI.createDirectory(validatedPath) as string
473
+ }
474
+ return validatedPath
475
+ }
476
+
477
+ /**
478
+ * Activate a `Ditto` instance by setting an offline only license token. You
479
+ * cannot initiate sync with `Ditto` before you have activated it. The offline
480
+ * license token is only valid for identities of type `development`, `manual`,
481
+ * `offlinePlayground`, and `sharedKey`.
482
+ *
483
+ * @param licenseToken the license token to activate the `Ditto` instance
484
+ * with. You can find yours on the [Ditto portal](https://portal.ditto.live).
485
+ *
486
+ * @throws {Error} if called in a React Native environment.
487
+ */
488
+ setOfflineOnlyLicenseToken(licenseToken: string) {
489
+ if (Environment.isWebBuild) {
490
+ // We don't need a license when running in the browser. Web builds mark the instance as activated
491
+ // inside the constructor.
492
+ Logger.info('Offline license token are ignored on web builds. Token validation will be skipped.')
493
+ }
494
+ if (Environment.isReactNativeBuild) {
495
+ throw new Error('Offline license tokens are currently not implemented for the React Native SDK.')
496
+ } else {
497
+ if (IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type)) {
498
+ const { result, errorMessage } = FFI.verifyLicense(licenseToken)
499
+ if (result !== 'LicenseOk') {
500
+ this._isActivated = false
501
+ throw new Error(errorMessage)
502
+ } else {
503
+ this._isActivated = true
504
+ }
505
+ } else {
506
+ throw new Error('Offline license tokens should only be used for manual, sharedKey or offlinePlayground identities')
507
+ }
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Returns the current transport configuration, frozen. If you want to modify
513
+ * the transport config, make a {@link TransportConfig.copy | copy} first. Or
514
+ * use the {@link updateTransportConfig | updateTransportConfig()}
515
+ * convenience method. By default peer-to-peer transports (Bluetooth, WiFi,
516
+ * and AWDL) are enabled if available in the current environment
517
+ * (Web, Node, OS, etc.).
518
+ *
519
+ * @see {@link setTransportConfig | setTransportConfig()}
520
+ * @see {@link updateTransportConfig | updateTransportConfig()}
521
+ */
522
+ get transportConfig(): TransportConfig {
523
+ return this.sync.parameters.transportConfig
524
+ }
525
+
526
+ /**
527
+ * Assigns a new transports configuration. By default peer-to-peer transports
528
+ * (Bluetooth, WiFi, and AWDL) are enabled. You may use this method to alter
529
+ * the configuration at any time, however sync will not begin until
530
+ * {@link startSync | startSync()} is called.
531
+ *
532
+ * @see {@link transportConfig}
533
+ * @see {@link updateTransportConfig | updateTransportConfig()}
534
+ */
535
+ setTransportConfig(transportConfig: TransportConfig) {
536
+ const transportConfigNew = transportConfig.copy().freeze()
537
+
538
+ const identity = this.identity
539
+ const isWebValid = this.isWebValid
540
+ const isX509Valid = this.isX509Valid
541
+
542
+ this.sync.update({ transportConfig: transportConfigNew, identity, isWebValid, isX509Valid, isSyncActive: this.isSyncActive, ditto: this })
543
+
544
+ const configSerializeReady = transportConfigToSerializable(transportConfigNew)
545
+ const configCBOR = CBOR.encode(configSerializeReady)
546
+ const dittoHandle = Bridge.ditto.handleFor(this)
547
+ this.deferClose(() => {
548
+ FFI.dittoSmallPeerInfoCollectionSetTransportConfigData(dittoHandle.deref(), configCBOR)
549
+ })
550
+ }
551
+
552
+ /**
553
+ * Convenience method for updating the transport config. Creates a copy of the
554
+ * current transport config, passes that copy to the `update` closure,
555
+ * allowing it to mutate as needed, and sets that updated copy afterwards.
556
+ */
557
+ updateTransportConfig(update: (transportConfig: TransportConfig) => void): Ditto {
558
+ const transportConfig = this.transportConfig.copy()
559
+ update(transportConfig)
560
+ this.setTransportConfig(transportConfig)
561
+ return this
562
+ }
563
+
564
+ /**
565
+ * Starts the network transports. Ditto will connect to other devices.
566
+ *
567
+ * By default Ditto will enable all peer-to-peer transport types. On **Node**,
568
+ * this means BluetoothLE, WiFi/LAN, and AWDL. On the **Web**, only connecting
569
+ * via Websockets is supported. The network configuration can be
570
+ * customized with {@link updateTransportConfig | updateTransportConfig()}
571
+ * or replaced entirely with {@link setTransportConfig | setTransportConfig()}.
572
+ *
573
+ *
574
+ * Ditto will prevent the process from exiting until sync is stopped (not
575
+ * relevant when running in the browser).
576
+ *
577
+ * **NOTE**: the BluetoothLE transport on Linux is experimental, this
578
+ * method panics if no BluetoothLE hardware is available. Therefore, contrary
579
+ * to the above, the BluetoothLE transport is temporarily disabled by default
580
+ * on Linux.
581
+ *
582
+ * @see {@link isSyncActive}
583
+ * @see {@link stopSync | stopSync()}
584
+ */
585
+ startSync() {
586
+ this.setSyncActive(true)
587
+ }
588
+
589
+ /**
590
+ * Stops all network transports.
591
+ *
592
+ * You may continue to use the database locally but no data will sync to or
593
+ * from other devices.
594
+ *
595
+ * @see {@link isSyncActive}
596
+ * @see {@link startSync | startSync()}
597
+ */
598
+ stopSync() {
599
+ this.setSyncActive(false)
600
+ }
601
+
602
+ /**
603
+ * Registers an observer for info about Ditto peers in range of this device.
604
+ *
605
+ * Ditto will prevent the process from exiting as long as there are active
606
+ * peers observers (not relevant when running in the browser).
607
+ *
608
+ * @param callback called immediately with the current state of peers
609
+ * in range and whenever that state changes. Then it will be invoked
610
+ * repeatedly when Ditto devices come and go, or the active connections to
611
+ * them change.
612
+ *
613
+ * @deprecated please use {@link Presence.observe | presence.observe()} instead.
614
+ */
615
+ observePeers(callback: (peersData: RemotePeer[]) => void): Observer {
616
+ Logger.warning('`ditto.observePeers()` is deprecated, please use `ditto.presence.observe()` instead.')
617
+ const token = this.presenceManager.addObserver(callback)
618
+ return new Observer(this.presenceManager, token, { stopsWhenFinalized: true })
619
+ }
620
+
621
+ /**
622
+ * Register observer for changes of underlying transport conditions.
623
+ *
624
+ * Ditto will prevent the process from exiting as long as there are active
625
+ * transport conditions observers (not relevant when running in the browser).
626
+ *
627
+ * @param callback called when underlying transport conditions change with
628
+ * the changed `condition` and its `source`.
629
+ */
630
+ observeTransportConditions(callback: (condition: TransportCondition, source: ConditionSource) => void): Observer {
631
+ const token = this.transportConditionsManager.addObserver(callback)
632
+ return new Observer(this.transportConditionsManager, token, { stopsWhenFinalized: true })
633
+ }
634
+
635
+ /**
636
+ * Removes all sync metadata for any remote peers which aren't currently
637
+ * connected. This method shouldn't usually be called. Manually running
638
+ * garbage collection often will result in slower sync times. Ditto
639
+ * automatically runs a garbage a collection process in the background at
640
+ * optimal times.
641
+ *
642
+ * Manually running garbage collection is typically only useful during testing
643
+ * if large amounts of data are being generated. Alternatively, if an entire
644
+ * data set is to be evicted and it's clear that maintaining this metadata
645
+ * isn't necessary, then garbage collection could be run after evicting the
646
+ * old data.
647
+ *
648
+ * Only available in Node environments at the moment, no-op in the browser.
649
+ */
650
+ runGarbageCollection() {
651
+ const dittoHandle = Bridge.ditto.handleFor(this)
652
+ return this.deferClose(() => {
653
+ FFI.dittoRunGarbageCollection(dittoHandle.deref())
654
+ })
655
+ }
656
+
657
+ /**
658
+ * Explicitly opt-in to disabling the ability to sync with Ditto peers running
659
+ * any version of the SDK in the v3 (or lower) series of releases.
660
+ *
661
+ * Assuming this succeeds then this peer will only be able to sync with other
662
+ * peers using SDKs in the v4 (or higher) series of releases. Note that this
663
+ * disabling of sync spreads to peers that sync with a peer that has disabled,
664
+ * or has (transitively) had disabled, syncing with v3 SDK peers.
665
+ *
666
+ * @throws {Error} if called in a React Native environment.
667
+ */
668
+ disableSyncWithV3() {
669
+ if (Environment.isReactNativeBuild) {
670
+ throw new Error('Disabling sync with V3 is not supported in a React Native environment.')
671
+ }
672
+ const dittoHandle = Bridge.ditto.handleFor(this)
673
+ return this.deferClose(() => {
674
+ FFI.dittoDisableSyncWithV3(dittoHandle.deref())
675
+ })
676
+ }
677
+
678
+ /**
679
+ * Shut down Ditto and release all resources.
680
+ *
681
+ * Must be called before recreating a Ditto instance that uses the same
682
+ * persistence directory.
683
+ */
684
+ async close() {
685
+ if (this.isClosed) {
686
+ return
687
+ }
688
+
689
+ this._isClosed = true
690
+
691
+ this.stopSync()
692
+
693
+ this.store.close()
694
+ this.presence.close()
695
+ this.auth.close()
696
+ this.sync.close()
697
+ this.presenceManager.close()
698
+ this.liveQueryManager.close()
699
+ this.attachmentFetcherManager.close()
700
+ this.transportConditionsManager.close()
701
+ this.subscriptionManager.close()
702
+
703
+ if (this.keepAlive.isActive) {
704
+ throw new Error('Internal inconsistency, still kept alive after the Ditto object has been close()-ed. ')
705
+ }
706
+
707
+ // Await all pending operations before closing. Rejected promises are
708
+ // ignored because they are handled at the original call site.
709
+ do {
710
+ await Promise.allSettled(this.pendingOperations)
711
+
712
+ } while (this.pendingOperations.size > 0)
713
+ this.deferCloseAllowed = false
714
+
715
+ await Bridge.ditto.close(this)
716
+ }
717
+
718
+ // ------------------------------------------------------------ Internal -----
719
+
720
+ /** @internal */
721
+ keepAlive: KeepAlive
722
+
723
+ /** @internal */
724
+ liveQueryManager: LiveQueryManager
725
+
726
+ /** @internal */
727
+ subscriptionManager: SubscriptionManager
728
+
729
+ /** @internal */
730
+ attachmentFetcherManager: AttachmentFetcherManager
731
+
732
+ /**
733
+ * The number of operations pending before the Ditto instance can be closed.
734
+ *
735
+ * For testing purposes only.
736
+ * @internal */
737
+ get numPendingOperations(): number {
738
+ return this.pendingOperations.size
739
+ }
740
+
741
+ /**
742
+ * Makes sure that the closure is executed only if the Ditto instance hasn't
743
+ * been closed yet.
744
+ *
745
+ * @param closure the synchronous closure to execute.
746
+ * @returns the result of the closure.
747
+ * @throws if the Ditto instance was closed before calling this method.
748
+ * @internal */
749
+ deferClose<T>(closure: () => T): T {
750
+ if (!this.deferCloseAllowed) {
751
+ throw new Error(`Can't perform operation using a Ditto instance that has been closed.`)
752
+ }
753
+
754
+ return closure()
755
+ }
756
+
757
+ /**
758
+ * Makes sure that the closure is executed to completion before the Ditto
759
+ * instance is closed.
760
+ *
761
+ * Any calls to {@link close | `Ditto.close()`} will wait until the closure
762
+ * has completed before closing the Ditto instance.
763
+ *
764
+ * @param closure the asynchronous closure to execute.
765
+ * @returns the result of the closure.
766
+ * @throws if the Ditto instance was closed before calling this method.
767
+ * @internal */
768
+ async deferCloseAsync<T>(closure: () => Promise<T>): Promise<T> {
769
+ if (!this.deferCloseAllowed) {
770
+ throw new Error(`Can't perform operation using a Ditto instance that has been closed.`)
771
+ }
772
+
773
+ const pendingOperation = closure()
774
+ this.pendingOperations.add(pendingOperation)
775
+
776
+ let result: T
777
+ try {
778
+ result = await pendingOperation
779
+ } finally {
780
+ // Remove the promise from the set of pending operations even if it
781
+ // rejected.
782
+ this.pendingOperations.delete(pendingOperation)
783
+ }
784
+
785
+ return result
786
+ }
787
+
788
+ // ------------------------------------------------------------ Private ------
789
+
790
+ private deferCloseAllowed: boolean = true
791
+ private isWebValid: boolean = false
792
+ private isX509Valid: boolean = false
793
+
794
+ private presenceManager: PresenceManager
795
+ private transportConditionsManager: ObserverManager
796
+
797
+ private _isActivated: boolean
798
+ private _isSyncActive: boolean = false
799
+ private _isClosed: boolean = false
800
+
801
+ /** Set of pending operations that need to complete before the Ditto instance can be closed in a safe manner. */
802
+ private pendingOperations = new Set<Promise<any>>()
803
+
804
+ private authClientValidityChanged(isWebValid: boolean, isX509Valid: boolean) {
805
+ const transportConfig = this.transportConfig
806
+ const identity = this.identity
807
+
808
+ const isSyncActive = this.isSyncActive
809
+ const wasX509Valid = this.isX509Valid
810
+ const wasWebValid = this.isWebValid
811
+
812
+ this.isX509Valid = isX509Valid
813
+ this.isWebValid = isWebValid
814
+
815
+ this.auth['@ditto.authClientValidityChanged'](isWebValid, isX509Valid)
816
+ this.sync.update({ transportConfig, identity, isWebValid, isX509Valid, isSyncActive, ditto: this })
817
+ }
818
+
819
+ private validateIdentity(identity: Identity): Identity {
820
+ const validIdentity = { ...identity }
821
+
822
+ // @ts-expect-error we validate the existence of the value below.
823
+ const appID = identity.appID
824
+
825
+ if (!['offlinePlayground', 'sharedKey', 'manual', 'onlinePlayground', 'onlineWithAuthentication'].includes(identity.type)) {
826
+ throw new Error(`Can't create Ditto instance, unknown identity type: ${identity.type}`)
827
+ }
828
+
829
+ if ((identity.type === 'offlinePlayground' || identity.type === 'sharedKey' || identity.type === 'onlinePlayground' || identity.type === 'onlineWithAuthentication') && typeof appID === 'undefined') {
830
+ throw new Error(`Property .appID must be given for identity, but isn't.`)
831
+ }
832
+
833
+ if (typeof appID !== 'undefined' && typeof appID !== 'string') {
834
+ throw new Error(`Property .appID must be be of type string, but is of type '${typeof appID}': ${appID}`)
835
+ }
836
+
837
+ if ((identity.type === 'offlinePlayground' || identity.type === 'sharedKey') && typeof identity.siteID !== 'undefined') {
838
+ const siteID = identity.siteID
839
+ const isSiteIDNumberOrBigInt = typeof siteID === 'number' || typeof siteID === 'bigint'
840
+
841
+ if (!isSiteIDNumberOrBigInt) throw new Error("Can't create Ditto instance, siteID must be a number or BigInt")
842
+ if (siteID < 0) throw new Error("Can't create Ditto instance, siteID must be >= 0")
843
+ if (siteID > BigInt('0xffffffffffffffff')) throw new Error("Can't create Ditto instance, siteID must be < 2^64")
844
+ }
845
+
846
+ if (identity.type === 'sharedKey') {
847
+ // No validations yet.
848
+ }
849
+
850
+ if (identity.type === 'manual') {
851
+ // No validations yet.
852
+ }
853
+
854
+ if (identity.type === 'onlinePlayground') {
855
+ const token = identity.token
856
+
857
+ if (typeof token === 'undefined') {
858
+ throw new Error(`Property .token must be given for identity but isn't. You can find the corresponding token on the Ditto Portal.`)
859
+ }
860
+
861
+ if (typeof token !== 'undefined' && typeof token !== 'string') {
862
+ throw new Error(`Property .token of identity must be be of type string, but is of type '${typeof token}': ${token}`)
863
+ }
864
+ }
865
+
866
+ if (identity.type === 'onlineWithAuthentication') {
867
+ // No validations yet.
868
+ }
869
+
870
+ return validIdentity
871
+ }
872
+
873
+ private setSyncActive(flag: boolean) {
874
+ const dittoHandle = Bridge.ditto.handleFor(this)
875
+ this.deferClose(() => {
876
+ if (flag && IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type) && !this.isActivated) {
877
+ throw new Error('Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `setOfflineOnlyLicenseToken`. If you need to obtain a license token then please visit https://portal.ditto.live.')
878
+ }
879
+
880
+ if (!this.isSyncActive && flag) {
881
+ this.keepAlive.retain('sync')
882
+ }
883
+
884
+ if (this.isSyncActive && !flag) {
885
+ this.keepAlive.release('sync')
886
+ }
887
+
888
+ this._isSyncActive = flag
889
+
890
+ const isWebValid = this.isWebValid
891
+ const isX509Valid = this.isX509Valid
892
+
893
+ const identity = this.identity
894
+ const transportConfig = this.transportConfig
895
+
896
+ FFI.dittoSetDeviceName(dittoHandle.deref(), this.deviceName)
897
+ this.sync.update({ identity, transportConfig, isWebValid, isX509Valid, isSyncActive: !!flag, ditto: this })
898
+ })
899
+ }
900
+
901
+ private makeDefaultTransportConfig(): TransportConfig {
902
+ const dittoHandle = Bridge.ditto.handleFor(this)
903
+ return this.deferClose(() => {
904
+ const transportConfig = new TransportConfig()
905
+
906
+ if (FFI.transportsBLEIsAvailable(dittoHandle.deref())) {
907
+ transportConfig.peerToPeer.bluetoothLE.isEnabled = true
908
+ }
909
+
910
+ if (FFI.transportsAWDLIsAvailable(dittoHandle.deref())) {
911
+ transportConfig.peerToPeer.awdl.isEnabled = true
912
+ }
913
+
914
+ if (FFI.transportsLANIsAvailable(dittoHandle.deref())) {
915
+ transportConfig.peerToPeer.lan.isEnabled = true
916
+ }
917
+
918
+ return transportConfig.freeze()
919
+ })
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Returns true if the current JS environment supports all required APIs.
925
+ *
926
+ * @param _globalObject optional global object to test this function without
927
+ * having to mock `global`.
928
+ * @returns `true` iff all required APIs exist on `global`.
929
+ * @internal
930
+ */
931
+ export const checkAPIs = (_globalObject?: typeof globalThis): boolean => {
932
+ const requiredBrowserAPIs = ['BigInt', 'WeakRef', 'FinalizationRegistry'] as const
933
+ const globalObject = _globalObject || globalThis || global || window
934
+ return requiredBrowserAPIs.every((apiName) => !!globalObject[apiName])
935
+ }
936
+
937
+ /**
938
+ * Disable deadlock timeout when Node.js is running with `--inspect` parameter.
939
+ *
940
+ * This heuristic is not failsafe as debugging mode can also be enabled by
941
+ * sending a `SIGUSR1` signal to the process.
942
+ *
943
+ * @internal
944
+ */
945
+ export const disableDeadlockTimeoutWhenDebugging = () => {
946
+ if (Environment.isNodeBuild) {
947
+ const hasInspector = process && process?.execArgv?.some((arg) => arg.includes('--inspect') || arg.includes('--debug'))
948
+
949
+ // @ts-ignore - v8debug may be undefined
950
+ const hasLegacyDebugMode = (process && process?.execArgv?.some((arg) => arg.includes('--debug'))) || typeof v8debug === 'object'
951
+
952
+ if (hasInspector || hasLegacyDebugMode) {
953
+ try {
954
+ FFI.setDeadlockTimeout(0)
955
+ Logger.warning('Detected Node running with inspector enabled, disabling deadlock timeout.')
956
+ } catch (e: any) {
957
+ Logger.error(`Detected Node running with inspector enabled but failed to disable deadlock timeout: ${e?.message}`)
958
+ }
959
+ }
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Return true if we have read and write permissions for the given directory.
965
+ *
966
+ * Always returns `true` in the browser.
967
+ *
968
+ * Uses `fs.accessSync()` on all platforms except Windows, where ACLs are not
969
+ * checked by that method [1]. On Windows, we try writing and removing a temp
970
+ * file to the given path instead.
971
+ *
972
+ * [1]:
973
+ * https://nodejs.org/docs/latest-v18.x/api/fs.html#fsaccesspath-mode-callback
974
+ *
975
+ * @internal
976
+ */
977
+ const isDirectoryWritable = (directoryPath: string): boolean => {
978
+ return true
979
+ }