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