@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.
- package/README.md +2 -2
- package/node/ditto.cjs.js +1 -1
- package/node/ditto.darwin-arm64.node +0 -0
- package/node/ditto.darwin-x64.node +0 -0
- package/node/ditto.linux-arm.node +0 -0
- package/node/ditto.linux-arm64.node +0 -0
- package/node/ditto.linux-x64.node +0 -0
- package/node/ditto.win32-x64.node +0 -0
- package/node/transports.darwin-arm64.node +0 -0
- package/node/transports.darwin-x64.node +0 -0
- package/package.json +10 -5
- package/web/ditto.es6.js +1 -1
- package/web/ditto.umd.js +1 -1
- package/web/ditto.wasm +0 -0
- package/DittoReactNative.podspec +0 -27
- package/react-native/android/CMakeLists.txt +0 -36
- package/react-native/android/build.gradle +0 -190
- package/react-native/android/cpp-adapter.cpp +0 -259
- package/react-native/android/gradle.properties +0 -5
- package/react-native/android/src/main/AndroidManifest.xml +0 -4
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +0 -120
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +0 -28
- package/react-native/cpp/include/Arc.hpp +0 -159
- package/react-native/cpp/include/Attachment.h +0 -20
- package/react-native/cpp/include/Authentication.h +0 -23
- package/react-native/cpp/include/Collection.h +0 -13
- package/react-native/cpp/include/ConnectionRequest.h +0 -18
- package/react-native/cpp/include/DQL.h +0 -21
- package/react-native/cpp/include/Document.h +0 -17
- package/react-native/cpp/include/FFIUtils.h +0 -16
- package/react-native/cpp/include/IO.h +0 -13
- package/react-native/cpp/include/Identity.h +0 -17
- package/react-native/cpp/include/Lifecycle.h +0 -16
- package/react-native/cpp/include/LiveQuery.h +0 -17
- package/react-native/cpp/include/Logger.h +0 -22
- package/react-native/cpp/include/Misc.h +0 -30
- package/react-native/cpp/include/Presence.h +0 -18
- package/react-native/cpp/include/SmallPeerInfo.h +0 -19
- package/react-native/cpp/include/Transports.h +0 -25
- package/react-native/cpp/include/TypedArray.hpp +0 -167
- package/react-native/cpp/include/Utils.h +0 -70
- package/react-native/cpp/include/main.h +0 -10
- package/react-native/cpp/src/Attachment.cpp +0 -272
- package/react-native/cpp/src/Authentication.cpp +0 -227
- package/react-native/cpp/src/Collection.cpp +0 -56
- package/react-native/cpp/src/ConnectionRequest.cpp +0 -123
- package/react-native/cpp/src/DQL.cpp +0 -256
- package/react-native/cpp/src/Document.cpp +0 -146
- package/react-native/cpp/src/FFIUtils.cpp +0 -122
- package/react-native/cpp/src/IO.cpp +0 -35
- package/react-native/cpp/src/Identity.cpp +0 -122
- package/react-native/cpp/src/Lifecycle.cpp +0 -93
- package/react-native/cpp/src/LiveQuery.cpp +0 -63
- package/react-native/cpp/src/Logger.cpp +0 -199
- package/react-native/cpp/src/Misc.cpp +0 -322
- package/react-native/cpp/src/Presence.cpp +0 -166
- package/react-native/cpp/src/SmallPeerInfo.cpp +0 -142
- package/react-native/cpp/src/Transports.cpp +0 -275
- package/react-native/cpp/src/TypedArray.cpp +0 -303
- package/react-native/cpp/src/Utils.cpp +0 -139
- package/react-native/cpp/src/main.cpp +0 -178
- package/react-native/dittoffi/dittoffi.h +0 -4873
- package/react-native/dittoffi/ifaddrs.cpp +0 -385
- package/react-native/dittoffi/ifaddrs.h +0 -206
- package/react-native/ios/DittoRNSDK.h +0 -7
- package/react-native/ios/DittoRNSDK.mm +0 -159
- package/react-native/ios/YeetJSIUtils.h +0 -60
- package/react-native/ios/YeetJSIUtils.mm +0 -196
- package/react-native/lib/commonjs/ditto.rn.js +0 -93
- package/react-native/lib/commonjs/ditto.rn.js.map +0 -1
- package/react-native/lib/commonjs/index.js +0 -61
- package/react-native/lib/commonjs/index.js.map +0 -1
- package/react-native/lib/module/ditto.rn.js +0 -89
- package/react-native/lib/module/ditto.rn.js.map +0 -1
- package/react-native/lib/module/index.js +0 -27
- package/react-native/lib/module/index.js.map +0 -1
- package/react-native/lib/typescript/ditto.rn.d.ts +0 -15
- package/react-native/lib/typescript/ditto.rn.d.ts.map +0 -1
- package/react-native/lib/typescript/index.d.ts +0 -1
- package/react-native/lib/typescript/index.d.ts.map +0 -1
- package/react-native/src/ditto.rn.ts +0 -123
- package/react-native/src/environment/environment.fallback.ts +0 -4
- package/react-native/src/index.ts +0 -29
- package/react-native/src/sources/@cbor-redux.ts +0 -2
- package/react-native/src/sources/@ditto.core.ts +0 -1
- package/react-native/src/sources/@environment.ts +0 -1
- package/react-native/src/sources/attachment-fetch-event.ts +0 -54
- package/react-native/src/sources/attachment-fetcher-manager.ts +0 -145
- package/react-native/src/sources/attachment-fetcher.ts +0 -265
- package/react-native/src/sources/attachment-token.ts +0 -129
- package/react-native/src/sources/attachment.ts +0 -121
- package/react-native/src/sources/augment.ts +0 -108
- package/react-native/src/sources/authenticator.ts +0 -314
- package/react-native/src/sources/base-pending-cursor-operation.ts +0 -255
- package/react-native/src/sources/base-pending-id-specific-operation.ts +0 -112
- package/react-native/src/sources/bridge.ts +0 -557
- package/react-native/src/sources/build-time-constants.ts +0 -8
- package/react-native/src/sources/cbor.ts +0 -20
- package/react-native/src/sources/collection-interface.ts +0 -73
- package/react-native/src/sources/collection.ts +0 -219
- package/react-native/src/sources/collections-event.ts +0 -99
- package/react-native/src/sources/connection-request.ts +0 -142
- package/react-native/src/sources/counter.ts +0 -82
- package/react-native/src/sources/ditto.ts +0 -991
- package/react-native/src/sources/document-id.ts +0 -163
- package/react-native/src/sources/document-path.ts +0 -308
- package/react-native/src/sources/document.ts +0 -237
- package/react-native/src/sources/epilogue.ts +0 -32
- package/react-native/src/sources/error-codes.ts +0 -114
- package/react-native/src/sources/error.ts +0 -256
- package/react-native/src/sources/essentials.ts +0 -81
- package/react-native/src/sources/ffi-error.ts +0 -134
- package/react-native/src/sources/ffi.ts +0 -2190
- package/react-native/src/sources/identity.ts +0 -163
- package/react-native/src/sources/init.ts +0 -71
- package/react-native/src/sources/internal.ts +0 -143
- package/react-native/src/sources/keep-alive.ts +0 -73
- package/react-native/src/sources/key-path.ts +0 -198
- package/react-native/src/sources/live-query-event.ts +0 -208
- package/react-native/src/sources/live-query-manager.ts +0 -110
- package/react-native/src/sources/live-query.ts +0 -167
- package/react-native/src/sources/logger.ts +0 -196
- package/react-native/src/sources/main.ts +0 -61
- package/react-native/src/sources/observer-manager.ts +0 -185
- package/react-native/src/sources/observer.ts +0 -79
- package/react-native/src/sources/pending-collections-operation.ts +0 -241
- package/react-native/src/sources/pending-cursor-operation.ts +0 -218
- package/react-native/src/sources/pending-id-specific-operation.ts +0 -218
- package/react-native/src/sources/presence-manager.ts +0 -170
- package/react-native/src/sources/presence.ts +0 -427
- package/react-native/src/sources/query-result-item.ts +0 -131
- package/react-native/src/sources/query-result.ts +0 -55
- package/react-native/src/sources/register.ts +0 -95
- package/react-native/src/sources/small-peer-info.ts +0 -166
- package/react-native/src/sources/static-tcp-client.ts +0 -8
- package/react-native/src/sources/store-observer.ts +0 -170
- package/react-native/src/sources/store.ts +0 -630
- package/react-native/src/sources/subscription-manager.ts +0 -99
- package/react-native/src/sources/subscription.ts +0 -89
- package/react-native/src/sources/sync-subscription.ts +0 -90
- package/react-native/src/sources/sync.ts +0 -561
- package/react-native/src/sources/test-helpers.ts +0 -24
- package/react-native/src/sources/transport-conditions-manager.ts +0 -104
- package/react-native/src/sources/transport-config.ts +0 -430
- package/react-native/src/sources/update-result.ts +0 -66
- package/react-native/src/sources/update-results-map.ts +0 -65
- package/react-native/src/sources/websocket-client.ts +0 -7
- package/react-native/src/sources/write-transaction-collection.ts +0 -122
- package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +0 -101
- package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +0 -74
- package/react-native/src/sources/write-transaction.ts +0 -121
- 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
|
-
}
|