@dittolive/ditto 4.5.1-experimental.aarch64-linux.1.aarch64 → 4.5.2-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DittoReactNative.podspec +25 -0
- package/README.md +2 -2
- package/node/ditto.cjs.js +1 -1
- package/node/ditto.cjs.js.map +1 -0
- package/node/ditto.cjs.pretty.js +9655 -0
- package/node/ditto.cjs.pretty.js.map +1 -0
- package/node/ditto.darwin-arm64.node +0 -0
- package/node/ditto.darwin-x64.node +0 -0
- package/node/{ditto.linux-arm64.node → ditto.linux-x64.node} +0 -0
- package/node/transports.darwin-arm64.node +0 -0
- package/node/transports.darwin-x64.node +0 -0
- package/package.json +2 -1
- package/react-native/android/CMakeLists.txt +37 -0
- package/react-native/android/build.gradle +186 -0
- package/react-native/android/cpp-adapter.cpp +254 -0
- package/react-native/android/gradle.properties +5 -0
- package/react-native/android/src/main/AndroidManifest.xml +4 -0
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +85 -0
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +28 -0
- package/react-native/cpp/include/Arc.hpp +141 -0
- package/react-native/cpp/include/Attachment.h +16 -0
- package/react-native/cpp/include/Authentication.h +23 -0
- package/react-native/cpp/include/Collection.h +13 -0
- package/react-native/cpp/include/DQL.h +21 -0
- package/react-native/cpp/include/Document.h +17 -0
- package/react-native/cpp/include/Identity.h +17 -0
- package/react-native/cpp/include/Lifecycle.h +17 -0
- package/react-native/cpp/include/LiveQuery.h +17 -0
- package/react-native/cpp/include/Logger.h +22 -0
- package/react-native/cpp/include/Misc.h +27 -0
- package/react-native/cpp/include/Presence.h +14 -0
- package/react-native/cpp/include/SmallPeerInfo.h +19 -0
- package/react-native/cpp/include/Transports.h +25 -0
- package/react-native/cpp/include/TypedArray.hpp +167 -0
- package/react-native/cpp/include/Utils.h +61 -0
- package/react-native/cpp/include/main.h +10 -0
- package/react-native/cpp/src/Attachment.cpp +86 -0
- package/react-native/cpp/src/Authentication.cpp +227 -0
- package/react-native/cpp/src/Collection.cpp +54 -0
- package/react-native/cpp/src/DQL.cpp +256 -0
- package/react-native/cpp/src/Document.cpp +146 -0
- package/react-native/cpp/src/Identity.cpp +123 -0
- package/react-native/cpp/src/Lifecycle.cpp +110 -0
- package/react-native/cpp/src/LiveQuery.cpp +63 -0
- package/react-native/cpp/src/Logger.cpp +200 -0
- package/react-native/cpp/src/Misc.cpp +283 -0
- package/react-native/cpp/src/Presence.cpp +79 -0
- package/react-native/cpp/src/SmallPeerInfo.cpp +142 -0
- package/react-native/cpp/src/Transports.cpp +270 -0
- package/react-native/cpp/src/TypedArray.cpp +303 -0
- package/react-native/cpp/src/Utils.cpp +138 -0
- package/react-native/cpp/src/main.cpp +152 -0
- package/react-native/dittoffi/dittoffi.h +4700 -0
- package/react-native/dittoffi/ifaddrs.cpp +385 -0
- package/react-native/dittoffi/ifaddrs.h +206 -0
- package/react-native/ios/DittoRNSDK.h +7 -0
- package/react-native/ios/DittoRNSDK.mm +107 -0
- package/react-native/ios/YeetJSIUtils.h +60 -0
- package/react-native/ios/YeetJSIUtils.mm +196 -0
- package/react-native/lib/commonjs/ditto.rn.js +92 -0
- package/react-native/lib/commonjs/ditto.rn.js.map +1 -0
- package/react-native/lib/commonjs/index.js +61 -0
- package/react-native/lib/commonjs/index.js.map +1 -0
- package/react-native/lib/module/ditto.rn.js +88 -0
- package/react-native/lib/module/ditto.rn.js.map +1 -0
- package/react-native/lib/module/index.js +27 -0
- package/react-native/lib/module/index.js.map +1 -0
- package/react-native/lib/typescript/ditto.rn.d.ts +15 -0
- package/react-native/lib/typescript/ditto.rn.d.ts.map +1 -0
- package/react-native/lib/typescript/index.d.ts +1 -0
- package/react-native/lib/typescript/index.d.ts.map +1 -0
- package/react-native/src/ditto.rn.ts +91 -0
- package/react-native/src/environment/environment.fallback.ts +4 -0
- package/react-native/src/index.ts +26 -0
- package/react-native/src/sources/@cbor-redux.ts +2 -0
- package/react-native/src/sources/@ditto.core.ts +1 -0
- package/react-native/src/sources/@environment.ts +1 -0
- package/react-native/src/sources/attachment-fetch-event.ts +54 -0
- package/react-native/src/sources/attachment-fetcher-manager.ts +144 -0
- package/react-native/src/sources/attachment-fetcher.ts +134 -0
- package/react-native/src/sources/attachment-token.ts +48 -0
- package/react-native/src/sources/attachment.ts +74 -0
- package/react-native/src/sources/augment.ts +101 -0
- package/react-native/src/sources/authenticator.ts +314 -0
- package/react-native/src/sources/base-pending-cursor-operation.ts +239 -0
- package/react-native/src/sources/base-pending-id-specific-operation.ts +109 -0
- package/react-native/src/sources/bridge.ts +553 -0
- package/react-native/src/sources/build-time-constants.ts +8 -0
- package/react-native/src/sources/cbor.ts +35 -0
- package/react-native/src/sources/collection-interface.ts +67 -0
- package/react-native/src/sources/collection.ts +212 -0
- package/react-native/src/sources/collections-event.ts +99 -0
- package/react-native/src/sources/counter.ts +82 -0
- package/react-native/src/sources/ditto.ts +979 -0
- package/react-native/src/sources/document-id.ts +159 -0
- package/react-native/src/sources/document-path.ts +306 -0
- package/react-native/src/sources/document.ts +193 -0
- package/react-native/src/sources/epilogue.ts +30 -0
- package/react-native/src/sources/error-codes.ts +52 -0
- package/react-native/src/sources/error.ts +208 -0
- package/react-native/src/sources/essentials.ts +53 -0
- package/react-native/src/sources/ffi-error.ts +122 -0
- package/react-native/src/sources/ffi.ts +2012 -0
- package/react-native/src/sources/identity.ts +163 -0
- package/react-native/src/sources/init.ts +71 -0
- package/react-native/src/sources/internal.ts +109 -0
- package/react-native/src/sources/keep-alive.ts +73 -0
- package/react-native/src/sources/key-path.ts +198 -0
- package/react-native/src/sources/live-query-event.ts +208 -0
- package/react-native/src/sources/live-query-manager.ts +102 -0
- package/react-native/src/sources/live-query.ts +166 -0
- package/react-native/src/sources/logger.ts +196 -0
- package/react-native/src/sources/main.ts +60 -0
- package/react-native/src/sources/observer-manager.ts +178 -0
- package/react-native/src/sources/observer.ts +79 -0
- package/react-native/src/sources/pending-collections-operation.ts +232 -0
- package/react-native/src/sources/pending-cursor-operation.ts +218 -0
- package/react-native/src/sources/pending-id-specific-operation.ts +218 -0
- package/react-native/src/sources/presence-manager.ts +161 -0
- package/react-native/src/sources/presence.ts +233 -0
- package/react-native/src/sources/query-result-item.ts +116 -0
- package/react-native/src/sources/query-result.ts +55 -0
- package/react-native/src/sources/register.ts +95 -0
- package/react-native/src/sources/small-peer-info.ts +177 -0
- package/react-native/src/sources/static-tcp-client.ts +6 -0
- package/react-native/src/sources/store-observer.ts +177 -0
- package/react-native/src/sources/store.ts +385 -0
- package/react-native/src/sources/subscription-manager.ts +99 -0
- package/react-native/src/sources/subscription.ts +89 -0
- package/react-native/src/sources/sync-subscription.ts +90 -0
- package/react-native/src/sources/sync.ts +559 -0
- package/react-native/src/sources/test-helpers.ts +24 -0
- package/react-native/src/sources/transport-conditions-manager.ts +104 -0
- package/react-native/src/sources/transport-config.ts +430 -0
- package/react-native/src/sources/update-result.ts +66 -0
- package/react-native/src/sources/update-results-map.ts +57 -0
- package/react-native/src/sources/websocket-client.ts +7 -0
- package/react-native/src/sources/write-transaction-collection.ts +122 -0
- package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +101 -0
- package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +74 -0
- package/react-native/src/sources/write-transaction.ts +121 -0
- package/react-native.config.js +9 -0
- package/types/ditto.d.ts.map +1 -0
- package/web/ditto.es6.js +1 -1
- package/web/ditto.es6.js.map +1 -0
- package/web/ditto.es6.pretty.js +12600 -0
- package/web/ditto.es6.pretty.js.map +1 -0
- package/web/ditto.umd.js +1 -1
- package/web/ditto.umd.js.map +1 -0
- package/web/ditto.umd.pretty.js +12669 -0
- package/web/ditto.umd.pretty.js.map +1 -0
- package/web/ditto.wasm +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// NOTE: proxy was originally written in pure JS rather than TypeScript. We'll
|
|
6
|
+
// gradually port it to TypeScript and until done, we've renamed the pure JS
|
|
7
|
+
// file to proxy.raw.js and introduced proxy.ts that'll contain all parts ported
|
|
8
|
+
// to TypeScript or type declarations for the JS parts.
|
|
9
|
+
|
|
10
|
+
// import * as proxyRaw from './proxy.raw'
|
|
11
|
+
|
|
12
|
+
import * as FFI from './ffi'
|
|
13
|
+
import { DocumentID } from './document-id'
|
|
14
|
+
import { AttachmentToken } from './attachment-token'
|
|
15
|
+
import { Attachment } from './attachment'
|
|
16
|
+
import { Counter } from './counter'
|
|
17
|
+
import { Register } from './register'
|
|
18
|
+
|
|
19
|
+
// Takes an annotated JSON representation of a document (see CRDT's Document
|
|
20
|
+
// class for more info about what an annotated representation is) and turns it
|
|
21
|
+
// into a JavaScript-appropriate version. This means that counters get
|
|
22
|
+
// represented by `Counter` objects and attachments get represented by
|
|
23
|
+
// `Attachment` objects.
|
|
24
|
+
export function augmentJSONValue(json, mutDoc, workingPath) {
|
|
25
|
+
if (json && typeof json === 'object') {
|
|
26
|
+
if (Array.isArray(json)) {
|
|
27
|
+
return json.map((v, idx) => augmentJSONValue(v, mutDoc, `${workingPath}[${idx}]`))
|
|
28
|
+
} else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.counter) {
|
|
29
|
+
return Counter['@ditto.create'](mutDoc, workingPath, json[FFI.DittoCRDTValueKey])
|
|
30
|
+
} else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.register) {
|
|
31
|
+
return Register['@ditto.create'](mutDoc, workingPath, json[FFI.DittoCRDTValueKey])
|
|
32
|
+
} else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.attachment) {
|
|
33
|
+
return new AttachmentToken(json)
|
|
34
|
+
} else {
|
|
35
|
+
for (const [key, value] of Object.entries(json)) {
|
|
36
|
+
json[key] = augmentJSONValue(value, mutDoc, `${workingPath}['${key}']`)
|
|
37
|
+
}
|
|
38
|
+
return json
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
return json
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Converts objects that may contain instances of classes of this SDK, i.e.
|
|
47
|
+
* `DocumentID`, `Counter`, `Register` and `Attachment`, into plain JS objects
|
|
48
|
+
* that can be passed to the FFI layer.
|
|
49
|
+
*/
|
|
50
|
+
export function desugarJSObject(jsObj: any, atRoot: boolean = false): any {
|
|
51
|
+
if (jsObj && typeof jsObj === 'object') {
|
|
52
|
+
if (Array.isArray(jsObj)) {
|
|
53
|
+
return jsObj.map((v, idx) => desugarJSObject(v, false))
|
|
54
|
+
} else if (jsObj instanceof DocumentID) {
|
|
55
|
+
return jsObj.value
|
|
56
|
+
} else if (jsObj instanceof Counter) {
|
|
57
|
+
const counterJSON = {}
|
|
58
|
+
counterJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.counter
|
|
59
|
+
counterJSON[FFI.DittoCRDTValueKey] = jsObj.value
|
|
60
|
+
return counterJSON
|
|
61
|
+
} else if (jsObj instanceof Register) {
|
|
62
|
+
const registerJSON = {}
|
|
63
|
+
registerJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.register
|
|
64
|
+
registerJSON[FFI.DittoCRDTValueKey] = jsObj.value
|
|
65
|
+
return registerJSON
|
|
66
|
+
} else if (jsObj instanceof Attachment) {
|
|
67
|
+
const attachmentJSON = {
|
|
68
|
+
_id: jsObj.token.id,
|
|
69
|
+
_len: jsObj.token.len,
|
|
70
|
+
_meta: jsObj.token.metadata,
|
|
71
|
+
}
|
|
72
|
+
attachmentJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.attachment
|
|
73
|
+
return attachmentJSON
|
|
74
|
+
} else {
|
|
75
|
+
for (const [key, value] of Object.entries(jsObj)) {
|
|
76
|
+
jsObj[key] = desugarJSObject(value, false)
|
|
77
|
+
}
|
|
78
|
+
return jsObj
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
checkForUnsupportedValues(jsObj)
|
|
82
|
+
return jsObj
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Throws an error if input is a non-finite float value.
|
|
88
|
+
*
|
|
89
|
+
* Workaround while we don't have a reliable way of receiving error messages
|
|
90
|
+
* from `dittoCore.ditto_collection_insert_value()`.
|
|
91
|
+
*
|
|
92
|
+
* See https://github.com/getditto/ditto/issues/8657 for details.
|
|
93
|
+
*
|
|
94
|
+
* @param jsObj The object to check.
|
|
95
|
+
* @throws {Error} If `jsObj` is a non-finite float value.
|
|
96
|
+
*/
|
|
97
|
+
function checkForUnsupportedValues(jsObj: any): void {
|
|
98
|
+
if (Number.isNaN(jsObj) || jsObj === Infinity || jsObj === -Infinity) {
|
|
99
|
+
throw new Error('Non-finite float values are not supported')
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FFI from './ffi'
|
|
6
|
+
|
|
7
|
+
import { Bridge } from './bridge'
|
|
8
|
+
import { Logger } from './logger'
|
|
9
|
+
import { Observer } from './observer'
|
|
10
|
+
import { ObserverManager } from './observer-manager'
|
|
11
|
+
|
|
12
|
+
import type { Ditto } from './ditto'
|
|
13
|
+
import type { KeepAlive } from './keep-alive'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Provides info about the authentication status.
|
|
20
|
+
*/
|
|
21
|
+
export type AuthenticationStatus = {
|
|
22
|
+
/**
|
|
23
|
+
* Returns `true` if authenticated, otherwise returns `false`.
|
|
24
|
+
*/
|
|
25
|
+
isAuthenticated: boolean
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* If authenticated, returns the `userID` if one was provided by the
|
|
29
|
+
* authentication service. Otherwise returns `null`.
|
|
30
|
+
*/
|
|
31
|
+
userID: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Provides feedback to the developer about Ditto authentication status.
|
|
36
|
+
*/
|
|
37
|
+
export interface AuthenticationHandler {
|
|
38
|
+
/**
|
|
39
|
+
* There is no Ditto authentication token or it has expired. Sync will not
|
|
40
|
+
* work until there is a successful login using one of the login methods on
|
|
41
|
+
* {@link Authenticator}.
|
|
42
|
+
*/
|
|
43
|
+
authenticationRequired: (authenticator: Authenticator) => void
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Warns that the Ditto authentication token is getting old.
|
|
47
|
+
*
|
|
48
|
+
* Ditto will attempt to refresh tokens periodically, starting from halfway
|
|
49
|
+
* through the token's validity period. This reduces the risk of
|
|
50
|
+
* authentication expiring while the user is offline.
|
|
51
|
+
*
|
|
52
|
+
* The refresh will happen automatically if Ditto has a suitable refresh
|
|
53
|
+
* token. If new credentials are required, such as a third-party token or a
|
|
54
|
+
* username/password, then Ditto does not cache that information and you must
|
|
55
|
+
* submit it again using one of the `login` methods on {@link Authenticator}.
|
|
56
|
+
*/
|
|
57
|
+
authenticationExpiringSoon: (authenticator: Authenticator, secondsRemaining: number) => void
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Notifies the authentication handler that the authentication status did
|
|
61
|
+
* change. Use the `authenticator`s property `status` to query for the current
|
|
62
|
+
* authentication status.
|
|
63
|
+
*
|
|
64
|
+
* This method is **optional**.
|
|
65
|
+
*/
|
|
66
|
+
authenticationStatusDidChange?: (authenticator: Authenticator) => void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// -----------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Log in to a remote authentication service, using an
|
|
73
|
+
* `OnlineWithAuthentication` or an `Online` identity.
|
|
74
|
+
*/
|
|
75
|
+
export class Authenticator {
|
|
76
|
+
/**
|
|
77
|
+
* Returns `true` if authentication is available and the login methods can be
|
|
78
|
+
* used, otherwise returns `false`. Currently, authentication is only
|
|
79
|
+
* available if Ditto was initialized with an identity of type
|
|
80
|
+
* {@link IdentityOnlineWithAuthentication | 'onlineWithAuthentication'}.
|
|
81
|
+
*/
|
|
82
|
+
readonly loginSupported: boolean
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
Returns the current authentication status.
|
|
86
|
+
*/
|
|
87
|
+
get status(): AuthenticationStatus {
|
|
88
|
+
return this._status
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Log in to Ditto with a third-party token. Throws if authentication is not
|
|
93
|
+
* available, which can be checked with {@link loginSupported}.
|
|
94
|
+
*
|
|
95
|
+
* @param token the authentication token required to log in.
|
|
96
|
+
* @param provider the name of the authentication provider.
|
|
97
|
+
*/
|
|
98
|
+
loginWithToken(token: string, provider: string): Promise<void> {
|
|
99
|
+
throw new Error(`Authenticator.loginWithToken() is abstract and must be implemented by subclasses.`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Log in to Ditto with a username and password. Throws if authentication is
|
|
104
|
+
* not available, which can be checked with {@link loginSupported}.
|
|
105
|
+
*
|
|
106
|
+
* @param username the username component of the credentials used for log in.
|
|
107
|
+
* @param password the password component of the credentials used for log in.
|
|
108
|
+
* @param provider the name of the authentication provider.
|
|
109
|
+
*/
|
|
110
|
+
loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<void> {
|
|
111
|
+
throw new Error(`Authenticator.loginWithUsernameAndPassword() is abstract and must be implemented by subclasses.`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Log out of Ditto.
|
|
116
|
+
*
|
|
117
|
+
* This will stop sync, shut down all replication sessions, and remove any
|
|
118
|
+
* cached authentication credentials. Note that this does not remove any data
|
|
119
|
+
* from the store. If you wish to delete data from the store then use the
|
|
120
|
+
* optional `cleanupFn` parameter to perform any required cleanup.
|
|
121
|
+
*
|
|
122
|
+
* @param cleanupFn An optional function that will be called with the relevant
|
|
123
|
+
* [Ditto] instance as the sole argument that allows you to perform any
|
|
124
|
+
* required cleanup of the store as part of the logout process.
|
|
125
|
+
*/
|
|
126
|
+
logout(cleanupFn?: (ditto: Ditto) => void): Promise<void> {
|
|
127
|
+
throw new Error(`Authenticator.logout() is abstract and must be implemented by subclasses.`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* Registers a callback that is called whenever the authentication status
|
|
132
|
+
* changes. Returns an `Observer` object that needs to be retained as long as
|
|
133
|
+
* you want to receive the updates.
|
|
134
|
+
*/
|
|
135
|
+
observeStatus(callback: (authenticationStatus: AuthenticationStatus) => void): Observer {
|
|
136
|
+
const token = this.observerManager.addObserver(callback)
|
|
137
|
+
return new Observer(this.observerManager, token, { stopsWhenFinalized: true })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @internal */
|
|
141
|
+
constructor(keepAlive: KeepAlive) {
|
|
142
|
+
this.keepAlive = keepAlive
|
|
143
|
+
this._status = { isAuthenticated: false, userID: null }
|
|
144
|
+
this.loginSupported = false
|
|
145
|
+
this.observerManager = new ObserverManager('AuthenticationStatusObservation', { keepAlive })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** @internal */
|
|
149
|
+
'@ditto.authenticationExpiring'(number): void {
|
|
150
|
+
throw new Error(`Authenticator['@ditto.authenticationExpiring']() is abstract and must be implemented by subclasses.`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @internal */
|
|
154
|
+
'@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): void {
|
|
155
|
+
throw new Error(`Authenticator['@ditto.authClientValidityChanged']() is abstract and must be implemented by subclasses.`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @internal */
|
|
159
|
+
close() {
|
|
160
|
+
this.observerManager.close()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** @internal */
|
|
164
|
+
protected keepAlive: KeepAlive
|
|
165
|
+
|
|
166
|
+
/** @internal */
|
|
167
|
+
protected observerManager: ObserverManager
|
|
168
|
+
|
|
169
|
+
/** @internal */
|
|
170
|
+
protected _status: AuthenticationStatus
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// -----------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
/** @internal */
|
|
176
|
+
export class OnlineAuthenticator extends Authenticator {
|
|
177
|
+
readonly loginSupported: boolean = true
|
|
178
|
+
|
|
179
|
+
async loginWithToken(token: string, provider: string): Promise<void> {
|
|
180
|
+
const ditto = this.ditto.deref()
|
|
181
|
+
if (!ditto) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
186
|
+
const dittoPointer = dittoHandle.derefOrNull()
|
|
187
|
+
if (!dittoPointer) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return ditto.deferCloseAsync(async () => {
|
|
192
|
+
await FFI.dittoAuthClientLoginWithToken(dittoPointer, token, provider)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<void> {
|
|
197
|
+
const ditto = this.ditto.deref()
|
|
198
|
+
if (!ditto) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
202
|
+
const dittoPointer = dittoHandle.derefOrNull()
|
|
203
|
+
|
|
204
|
+
if (!dittoPointer) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return ditto.deferCloseAsync(async () => {
|
|
209
|
+
await FFI.dittoAuthClientLoginWithUsernameAndPassword(dittoPointer, username, password, provider)
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async logout(cleanupFn?: (ditto: Ditto) => void): Promise<void> {
|
|
214
|
+
const ditto = this.ditto.deref()
|
|
215
|
+
if (!ditto) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
220
|
+
const dittoPointer = dittoHandle.derefOrNull()
|
|
221
|
+
|
|
222
|
+
if (!dittoPointer) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return ditto.deferCloseAsync(async () => {
|
|
227
|
+
await FFI.dittoAuthClientLogout(dittoPointer)
|
|
228
|
+
ditto.stopSync()
|
|
229
|
+
cleanupFn?.(ditto)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
readonly authenticationHandler: AuthenticationHandler
|
|
234
|
+
|
|
235
|
+
private ditto: WeakRef<Ditto>
|
|
236
|
+
|
|
237
|
+
constructor(keepAlive: KeepAlive, ditto: Ditto, authenticationHandler: AuthenticationHandler) {
|
|
238
|
+
super(keepAlive)
|
|
239
|
+
this._status = { isAuthenticated: false, userID: null }
|
|
240
|
+
this.ditto = new WeakRef<Ditto>(ditto)
|
|
241
|
+
this.authenticationHandler = authenticationHandler
|
|
242
|
+
this.updateAndNotify(false)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
'@ditto.authenticationExpiring'(secondsRemaining: number): void {
|
|
246
|
+
const authenticationHandler = this.authenticationHandler
|
|
247
|
+
if (secondsRemaining > 0) {
|
|
248
|
+
authenticationHandler.authenticationExpiringSoon(this, secondsRemaining)
|
|
249
|
+
} else {
|
|
250
|
+
authenticationHandler.authenticationRequired(this)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
'@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): void {
|
|
255
|
+
this.updateAndNotify(true)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private updateAndNotify(shouldNotify: boolean) {
|
|
259
|
+
const ditto = this.ditto.deref()
|
|
260
|
+
if (!ditto) {
|
|
261
|
+
Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.')
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
266
|
+
const dittoPointer = dittoHandle.derefOrNull()
|
|
267
|
+
if (!dittoPointer) {
|
|
268
|
+
Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.')
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const wasAuthenticated = this.status.isAuthenticated
|
|
273
|
+
const previousUserID = this.status.userID
|
|
274
|
+
|
|
275
|
+
const isAuthenticated = FFI.dittoAuthClientIsWebValid(dittoPointer)
|
|
276
|
+
const userID = FFI.dittoAuthClientUserID(dittoPointer)
|
|
277
|
+
const status = { isAuthenticated, userID }
|
|
278
|
+
|
|
279
|
+
this._status = status
|
|
280
|
+
|
|
281
|
+
if (shouldNotify) {
|
|
282
|
+
const sameStatus = !!wasAuthenticated === !!isAuthenticated && previousUserID === userID
|
|
283
|
+
if (!sameStatus) {
|
|
284
|
+
this.authenticationHandler.authenticationStatusDidChange?.call(this.authenticationHandler, this)
|
|
285
|
+
this.observerManager.notify(status)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// -----------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
/** @internal */
|
|
294
|
+
export class NotAvailableAuthenticator extends Authenticator {
|
|
295
|
+
async loginWithToken(token: string, provider: string): Promise<never> {
|
|
296
|
+
throw new Error(`Can't login, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<never> {
|
|
300
|
+
throw new Error(`Can't login, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
logout(cleanupFn?: (ditto: Ditto) => void): Promise<never> {
|
|
304
|
+
throw new Error(`Can't logout, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
'@ditto.authenticationExpiring'(secondsRemaining: number): never {
|
|
308
|
+
throw new Error(`Internal inconsistency, authentication is not available, yet the @ditto.authenticationExpiring() was called on authenticator: ${this}`)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
'@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): never {
|
|
312
|
+
throw new Error(`Internal inconsistency, authentication is not available, yet the @ditto.authClientValidityChanged() was called on authenticator: ${this}`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2023 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FFI from './ffi'
|
|
6
|
+
|
|
7
|
+
import { Document, MutableDocument } from './document'
|
|
8
|
+
import { UpdateResultsMap } from './update-results-map'
|
|
9
|
+
import { CBOR } from './cbor'
|
|
10
|
+
import { validateQuery } from './internal'
|
|
11
|
+
import { performAsyncToWorkaroundNonAsyncFFIAPI } from './internal'
|
|
12
|
+
import { Bridge } from './bridge'
|
|
13
|
+
|
|
14
|
+
import type { DocumentID } from './document-id'
|
|
15
|
+
import type { QueryArguments, SortDirection } from './essentials'
|
|
16
|
+
import type { UpdateResult } from './update-result'
|
|
17
|
+
import type { CollectionInterface } from './collection-interface'
|
|
18
|
+
|
|
19
|
+
export abstract class BasePendingCursorOperation implements PromiseLike<Document[]> {
|
|
20
|
+
/**
|
|
21
|
+
* Removes all documents that match the query generated by the preceding
|
|
22
|
+
* function chaining.
|
|
23
|
+
*
|
|
24
|
+
* @returns An array promise containing the IDs of the documents that were
|
|
25
|
+
* removed.
|
|
26
|
+
*/
|
|
27
|
+
abstract remove(): Promise<DocumentID[]>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Evicts all documents that match the query generated by the preceding
|
|
31
|
+
* function chaining.
|
|
32
|
+
*
|
|
33
|
+
* @return An array promise containing the IDs of the documents that were
|
|
34
|
+
* evicted.
|
|
35
|
+
*/
|
|
36
|
+
abstract evict(): Promise<DocumentID[]>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Updates documents that match the query generated by the preceding function
|
|
40
|
+
* chaining.
|
|
41
|
+
*
|
|
42
|
+
* @param closure A closure that gets called with all of the documents
|
|
43
|
+
* matching the query. The documents are instances of {@link MutableDocument}
|
|
44
|
+
* so you can call update-related functions on them.
|
|
45
|
+
*
|
|
46
|
+
* @returns An {@link UpdateResultsMap} promise mapping document IDs to lists
|
|
47
|
+
* of {@link UpdateResult | update results} that describe the updates that
|
|
48
|
+
* were performed for each document.
|
|
49
|
+
*/
|
|
50
|
+
abstract update(closure: (documents: MutableDocument[]) => void): Promise<UpdateResultsMap>
|
|
51
|
+
|
|
52
|
+
// ----------------------------------------------------------- Public --------
|
|
53
|
+
|
|
54
|
+
/** The query the receiver is operating with. */
|
|
55
|
+
readonly query: string
|
|
56
|
+
|
|
57
|
+
/** The named arguments for the {@link query}. */
|
|
58
|
+
readonly queryArgs: QueryArguments | null
|
|
59
|
+
|
|
60
|
+
/** The collection the receiver is operating on. */
|
|
61
|
+
readonly collection: CollectionInterface
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sorts the documents that match the query provided in the preceding
|
|
65
|
+
* `find`-like function call.
|
|
66
|
+
*
|
|
67
|
+
* @param query The query specifies the logic to be used when sorting the
|
|
68
|
+
* matching documents.
|
|
69
|
+
*
|
|
70
|
+
* @param direction Specify whether you want the sorting order to be
|
|
71
|
+
* `Ascending` or `Descending`.
|
|
72
|
+
*
|
|
73
|
+
* @return A cursor that you can chain further function calls and then either
|
|
74
|
+
* get the matching documents immediately or get updates about them over time.
|
|
75
|
+
*/
|
|
76
|
+
sort(propertyPath: string, direction: SortDirection = 'ascending'): BasePendingCursorOperation {
|
|
77
|
+
this.orderBys.push({
|
|
78
|
+
query: propertyPath,
|
|
79
|
+
direction: direction === 'ascending' ? 'Ascending' : 'Descending',
|
|
80
|
+
})
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Offsets the resulting set of matching documents.
|
|
86
|
+
*
|
|
87
|
+
* This is useful if you aren't interested in the first N matching documents
|
|
88
|
+
* for one reason or another. For example, you might already have queried the
|
|
89
|
+
* collection and obtained the first 20 matching documents and so you might
|
|
90
|
+
* want to run the same query as you did previously but ignore the first 20
|
|
91
|
+
* matching documents, and that is when you would use `offset`.
|
|
92
|
+
*
|
|
93
|
+
* @param offset The number of matching documents that you want the eventual
|
|
94
|
+
* resulting set of matching documents to be offset by (and thus not include).
|
|
95
|
+
*
|
|
96
|
+
* @return A cursor that you can chain further function calls and then either
|
|
97
|
+
* get the matching documents immediately or get updates about them over time.
|
|
98
|
+
*/
|
|
99
|
+
offset(offset: number): BasePendingCursorOperation {
|
|
100
|
+
if (offset < 0) throw new Error(`Can't offset by '${offset}', offset must be >= 0`)
|
|
101
|
+
if (!Number.isFinite(offset)) throw new Error(`Can't offset by '${offset}', offset must be a finite number`)
|
|
102
|
+
if (Number.isNaN(offset)) throw new Error(`Can't offset by '${offset}', offset must be a valid number`)
|
|
103
|
+
|
|
104
|
+
const integerOffset = Math.round(offset)
|
|
105
|
+
if (offset !== integerOffset) throw new Error(`Can't offset by '${offset}', offset must be an integer number`)
|
|
106
|
+
|
|
107
|
+
this.currentOffset = offset
|
|
108
|
+
return this
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Limits the number of documents that get returned when querying a collection
|
|
113
|
+
* for matching documents.
|
|
114
|
+
*
|
|
115
|
+
* @param limit The maximum number of documents that will be returned.
|
|
116
|
+
*
|
|
117
|
+
* @return A cursor that you can chain further function calls and then either
|
|
118
|
+
* get the matching documents immediately or get updates about them over time.
|
|
119
|
+
*/
|
|
120
|
+
limit(limit: number): BasePendingCursorOperation {
|
|
121
|
+
if (limit < -1) throw new Error(`Can't limit to '${limit}', limit must be >= -1 (where -1 means unlimited)`)
|
|
122
|
+
if (!Number.isFinite(limit)) throw new Error(`Can't limit to '${limit}', limit must be a finite number`)
|
|
123
|
+
if (Number.isNaN(limit)) throw new Error(`Can't limit to '${limit}', limit must be a valid number`)
|
|
124
|
+
|
|
125
|
+
const integerLimit = Math.round(limit)
|
|
126
|
+
if (limit !== integerLimit) throw new Error(`Can't limit to '${limit}', limit must be an integer number`)
|
|
127
|
+
|
|
128
|
+
this.currentLimit = limit
|
|
129
|
+
return this
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Executes the query generated by the preceding function chaining and return
|
|
134
|
+
* the list of matching documents.
|
|
135
|
+
*
|
|
136
|
+
* @returns An array promise containing {@link Document | documents} matching
|
|
137
|
+
* the query generated by the preceding function chaining.
|
|
138
|
+
*/
|
|
139
|
+
async exec(): Promise<Document[]> {
|
|
140
|
+
const ditto = this.collection.store.ditto
|
|
141
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
142
|
+
return ditto.deferCloseAsync(async () => {
|
|
143
|
+
const query = this.query
|
|
144
|
+
|
|
145
|
+
const documentPointers: FFI.Pointer<FFI.FFIDocument>[] = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
146
|
+
return await FFI.collectionExecQueryStr(dittoHandle.deref(), this.collection.name, null, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
return documentPointers.map((documentPointer) => {
|
|
150
|
+
return Bridge.document.bridge(documentPointer)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ----------------------------------------------------------- Internal ------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Updates documents that match the query generated by the preceding function
|
|
159
|
+
* chaining.
|
|
160
|
+
*
|
|
161
|
+
* @param closure A closure that gets called with all of the documents
|
|
162
|
+
* matching the query. The documents are instances of {@link MutableDocument}
|
|
163
|
+
* so you can call update-related functions on them.
|
|
164
|
+
* @param writeTransactionX a transaction to perform the operation in.
|
|
165
|
+
*
|
|
166
|
+
* @returns An {@link UpdateResultsMap} promise mapping document IDs to lists
|
|
167
|
+
* of {@link UpdateResult | update results} that describe the updates that
|
|
168
|
+
* were performed for each document.
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
async updateWithTransaction(closure: (documents: MutableDocument[]) => void, writeTransactionX: FFI.Pointer<FFI.FFIWriteTransaction>): Promise<UpdateResultsMap> {
|
|
172
|
+
const ditto = this.collection.store.ditto
|
|
173
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
174
|
+
return ditto.deferCloseAsync(async () => {
|
|
175
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
176
|
+
const query = this.query
|
|
177
|
+
|
|
178
|
+
const documentsX = await FFI.collectionExecQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
|
|
179
|
+
|
|
180
|
+
const mutableDocuments: MutableDocument[] = documentsX.map((documentX) => {
|
|
181
|
+
return Bridge.mutableDocument.bridge(documentX, () => new MutableDocument())
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
closure(mutableDocuments)
|
|
185
|
+
|
|
186
|
+
const updateResultsDocumentIDs = []
|
|
187
|
+
const updateResultsByDocumentIDString: { [documentID: string]: UpdateResult[] } = {}
|
|
188
|
+
|
|
189
|
+
for (const mutableDocument of mutableDocuments) {
|
|
190
|
+
const documentID = mutableDocument.id
|
|
191
|
+
const documentIDString = documentID.toString()
|
|
192
|
+
const updateResults = mutableDocument['@ditto.updateResults']
|
|
193
|
+
|
|
194
|
+
if (updateResultsByDocumentIDString[documentIDString]) {
|
|
195
|
+
throw new Error(`Internal inconsistency, update results for document ID as string already exist: ${documentIDString}`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
updateResultsDocumentIDs.push(documentID)
|
|
199
|
+
updateResultsByDocumentIDString[documentIDString] = updateResults
|
|
200
|
+
|
|
201
|
+
Bridge.mutableDocument.unregister(mutableDocument)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// NOTE: ownership of documentsX (and contained documents)
|
|
205
|
+
// is transferred to Rust at this point.
|
|
206
|
+
await FFI.collectionUpdateMultiple(dittoHandle.deref(), this.collection.name, writeTransactionX, documentsX)
|
|
207
|
+
|
|
208
|
+
return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString)
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** @internal */
|
|
214
|
+
constructor(query: string, queryArgs: QueryArguments | null, collection: CollectionInterface) {
|
|
215
|
+
this.query = validateQuery(query)
|
|
216
|
+
this.queryArgs = queryArgs ? Object.freeze({ ...queryArgs }) : null
|
|
217
|
+
this.collection = collection
|
|
218
|
+
this.queryArgsCBOR = queryArgs ? CBOR.encode(queryArgs) : null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** @internal */
|
|
222
|
+
then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2> {
|
|
223
|
+
return this.exec().then(onfulfilled, onrejected)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------- Protected ------
|
|
227
|
+
|
|
228
|
+
/** @internal */
|
|
229
|
+
protected queryArgsCBOR: Uint8Array | null
|
|
230
|
+
|
|
231
|
+
/** @internal */
|
|
232
|
+
protected currentLimit = -1
|
|
233
|
+
|
|
234
|
+
/** @internal */
|
|
235
|
+
protected currentOffset = 0
|
|
236
|
+
|
|
237
|
+
/** @internal */
|
|
238
|
+
protected orderBys: FFI.OrderBy[] = []
|
|
239
|
+
}
|