@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,265 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as FFI from './ffi'
|
|
6
|
-
import { Bridge } from './bridge'
|
|
7
|
-
import { Attachment } from './attachment'
|
|
8
|
-
import { AttachmentToken } from './attachment-token'
|
|
9
|
-
import { DittoError, mapFFIErrorsAsync } from './error'
|
|
10
|
-
import { generateEphemeralToken, step } from './internal'
|
|
11
|
-
import { Logger } from './logger'
|
|
12
|
-
|
|
13
|
-
import type { AttachmentFetchEvent } from './attachment-fetch-event'
|
|
14
|
-
import type { Ditto } from './ditto'
|
|
15
|
-
import type { AttachmentFetcherManager } from './attachment-fetcher-manager'
|
|
16
|
-
import type { Store } from './store'
|
|
17
|
-
import type { Collection } from './collection'
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* These objects are returned by calls to
|
|
21
|
-
* {@link Store.fetchAttachment | ditto.store.fetchAttachment()}
|
|
22
|
-
* and allow you to stop an in-flight attachment fetch.
|
|
23
|
-
*/
|
|
24
|
-
export class AttachmentFetcher implements PromiseLike<Attachment | null> {
|
|
25
|
-
/**
|
|
26
|
-
* Returns a promise for the attachment that you can `await`.
|
|
27
|
-
*
|
|
28
|
-
* The promise is rejected if an error occurs during the fetch. Note that the
|
|
29
|
-
* `AttachmentFetcher` itself implements `PromiseLike`, so you can `await` it
|
|
30
|
-
* directly.
|
|
31
|
-
*
|
|
32
|
-
* If this attachment fetcher has been initiated through {@link
|
|
33
|
-
* Collection.fetchAttachment | `Collection.fetchAttachment()`}, the promise
|
|
34
|
-
* resolves to `null` if the attachment could not be found instead of being
|
|
35
|
-
* rejected.
|
|
36
|
-
*/
|
|
37
|
-
readonly attachment: Promise<Attachment | null>
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Stops fetching the associated attachment and cleans up any associated
|
|
41
|
-
* resources.
|
|
42
|
-
*
|
|
43
|
-
* Note that you are not required to call `stop()` once your attachment fetch
|
|
44
|
-
* operation has finished. The method primarily exists to allow you to cancel
|
|
45
|
-
* an attachment fetch request while it is ongoing if you no longer wish for
|
|
46
|
-
* the attachment to be made available locally to the device.
|
|
47
|
-
*/
|
|
48
|
-
stop() {
|
|
49
|
-
// No need for synchronicity here: we let the "stop promise" float / run in
|
|
50
|
-
// a detached fashion since there is no point in awaiting the "stop
|
|
51
|
-
// signaling" itself.
|
|
52
|
-
|
|
53
|
-
if (this.manager == null) {
|
|
54
|
-
// Case where the fetcher was started from `Store.fetchAttachment()`.
|
|
55
|
-
if (!this.isStopped) {
|
|
56
|
-
this.rejectPendingFetch()
|
|
57
|
-
this.rejectPendingFetch = null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this.ditto.store.removeAttachmentFetcher(this)
|
|
61
|
-
|
|
62
|
-
const dittoHandle = Bridge.ditto.handleFor(this.ditto)
|
|
63
|
-
void this.ditto.deferCloseAsync(async () => {
|
|
64
|
-
// Cancel the fetcher if it is still running.
|
|
65
|
-
const cancelToken = await this.cancelTokenPromise
|
|
66
|
-
if (cancelToken) {
|
|
67
|
-
FFI.dittoCancelResolveAttachment(dittoHandle.deref(), this.token.idBytes, cancelToken)
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
} else {
|
|
71
|
-
// Legacy case where the fetcher was started from `Collection.fetchAttachment()`.
|
|
72
|
-
|
|
73
|
-
void step(async () => {
|
|
74
|
-
await this.manager.stopAttachmentFetcher(this)
|
|
75
|
-
if (this.rejectPendingFetch != null) {
|
|
76
|
-
this.rejectPendingFetch()
|
|
77
|
-
this.rejectPendingFetch = null
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// --------------------------------------- Internal ------------------------
|
|
84
|
-
|
|
85
|
-
/** @internal */
|
|
86
|
-
readonly cancelTokenPromise: Promise<number | BigInt | null> | null = null
|
|
87
|
-
|
|
88
|
-
/** @internal */
|
|
89
|
-
readonly ditto: Ditto
|
|
90
|
-
|
|
91
|
-
/** @internal */
|
|
92
|
-
readonly id: string
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Defined when the fetcher was started from `Collection.fetchAttachment()`.
|
|
96
|
-
* Otherwise, the fetcher was started from `Store.fetchAttachment()`.
|
|
97
|
-
*
|
|
98
|
-
* @internal
|
|
99
|
-
*/
|
|
100
|
-
readonly manager?: AttachmentFetcherManager
|
|
101
|
-
|
|
102
|
-
/** @internal */
|
|
103
|
-
readonly token: AttachmentToken
|
|
104
|
-
|
|
105
|
-
/** @internal */
|
|
106
|
-
then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2> {
|
|
107
|
-
return this.attachment.then(onfulfilled, onrejected)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** @internal */
|
|
111
|
-
constructor(ditto: Ditto, token: AttachmentToken, manager?: AttachmentFetcherManager, eventHandler?: (attachmentFetchEvent: AttachmentFetchEvent) => void) {
|
|
112
|
-
this.ditto = ditto
|
|
113
|
-
this.token = token
|
|
114
|
-
this.manager = manager
|
|
115
|
-
this.id = generateEphemeralToken()
|
|
116
|
-
|
|
117
|
-
const eventHandlerOrNoOp = eventHandler || function () {}
|
|
118
|
-
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
119
|
-
|
|
120
|
-
this.attachment = new Promise((resolve, reject) => {
|
|
121
|
-
// REFACTOR: The callbacks hold quite a bunch of objects with most
|
|
122
|
-
// probably lead to retain cycles and therefore memory leaks. This needs
|
|
123
|
-
// to be
|
|
124
|
-
// fixed.
|
|
125
|
-
const onComplete = (attachmentHandlePointer: FFI.Pointer<FFI.AttachmentHandle>) => {
|
|
126
|
-
const attachment = new Attachment(this.ditto, this.token)
|
|
127
|
-
Bridge.attachment.bridge(attachmentHandlePointer, () => attachment)
|
|
128
|
-
|
|
129
|
-
eventHandlerOrNoOp({ type: 'Completed', attachment })
|
|
130
|
-
|
|
131
|
-
this.rejectPendingFetch = null
|
|
132
|
-
resolve(attachment)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const onProgress = (downloaded: number | BigInt, toDownload: number | BigInt) => {
|
|
136
|
-
eventHandlerOrNoOp({ type: 'Progress', totalBytes: toDownload, downloadedBytes: downloaded })
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const onDelete = () => {
|
|
140
|
-
eventHandlerOrNoOp({ type: 'Deleted' })
|
|
141
|
-
|
|
142
|
-
if (this.manager != null) {
|
|
143
|
-
// Legacy behavior: when the attachment is deleted while being fetched
|
|
144
|
-
// the fetcher is stopped and the promise is resolved to `null`.
|
|
145
|
-
this.rejectPendingFetch = null
|
|
146
|
-
resolve(null)
|
|
147
|
-
} else {
|
|
148
|
-
this.rejectPendingFetch = null
|
|
149
|
-
reject(new DittoError('store/attachment-not-found', 'The attachment was deleted while being fetched.'))
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const onError = () => {
|
|
154
|
-
;(err: any) => {
|
|
155
|
-
this.rejectPendingFetch = null
|
|
156
|
-
reject(err)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// The core doesn't call any of the handlers defined above when a fetch is
|
|
161
|
-
// cancelled through `this.stop()` so we use this function to reject the
|
|
162
|
-
// promise from the outside.
|
|
163
|
-
this.rejectPendingFetch = () => {
|
|
164
|
-
const err = this.manager != null ? new Error('Attachment fetch was canceled') : new DittoError('store/failed-to-fetch-attachment', 'Attachment fetch was canceled')
|
|
165
|
-
reject(err)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// `cancelTokenPromise` resolves once the fetcher has been initialised in
|
|
169
|
-
// core. This constructor is sync and thus can't await the promise. That
|
|
170
|
-
// only happens when the cancel token is used, either in `stop()` (for
|
|
171
|
-
// fetchers started from `Store.fetchAttachment()`) or in
|
|
172
|
-
// `AttachmentFetcherManager.stopWithContextInfo()` (for fetchers started
|
|
173
|
-
// from `Collection.fetchAttachment()`).
|
|
174
|
-
//
|
|
175
|
-
// In addition, we want to be able to intercept errors that occur after
|
|
176
|
-
// this constructor has returned. For that, the whole creation of the
|
|
177
|
-
// cancel token is wrapped in an inline async function that catches any
|
|
178
|
-
// errors and then doesn't reject the cancel token promise, but the
|
|
179
|
-
// `attachment` promise and through that the `AttachmentFetcher` itself.
|
|
180
|
-
const weakThis = new WeakRef(this)
|
|
181
|
-
|
|
182
|
-
// @ts-expect-error setting readonly property
|
|
183
|
-
this.cancelTokenPromise = (async () => {
|
|
184
|
-
try {
|
|
185
|
-
return await mapFFIErrorsAsync(async () => FFI.dittoResolveAttachment(dittoHandle.deref(), token.idBytes, { onComplete, onProgress, onDelete }, onError), {
|
|
186
|
-
1: ['store/failed-to-fetch-attachment', 'Failed to fetch the attachment.'],
|
|
187
|
-
2: ['store/attachment-token-invalid', 'The attachment token was invalid.'],
|
|
188
|
-
3: ['store/attachment-not-found', 'The attachment was not found.'],
|
|
189
|
-
})
|
|
190
|
-
} catch (e) {
|
|
191
|
-
let isDeleted = false
|
|
192
|
-
|
|
193
|
-
// Legacy behavior: when the attachment is deleted before the fetch is
|
|
194
|
-
// started, the fetcher is stopped and the promise is resolved to
|
|
195
|
-
// `null`.
|
|
196
|
-
if (e instanceof DittoError && e.code === 'store/attachment-not-found') {
|
|
197
|
-
isDeleted = true
|
|
198
|
-
eventHandlerOrNoOp({ type: 'Deleted' })
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
Logger.error(e.message)
|
|
202
|
-
|
|
203
|
-
const strongThis = weakThis.deref()
|
|
204
|
-
if (strongThis == null) {
|
|
205
|
-
// The fetcher was already gc'd, so it's not possible to reject the
|
|
206
|
-
// `attachment` promise anymore. We stop here as the error has been
|
|
207
|
-
// logged.
|
|
208
|
-
return null
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// When this is called from legacy `Collection.fetchAttachment()`, we
|
|
212
|
-
// convert the DittoError to a regular Error.
|
|
213
|
-
if (strongThis.manager != null && e instanceof DittoError) {
|
|
214
|
-
e = new Error(e.message)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
strongThis.rejectPendingFetch = null
|
|
218
|
-
|
|
219
|
-
// Reject the `attachment` promise to signal that the fetch has
|
|
220
|
-
// failed, unless this is a legacy fetcher and the attachment was
|
|
221
|
-
// deleted.
|
|
222
|
-
if (strongThis.manager != null && isDeleted) {
|
|
223
|
-
resolve(null)
|
|
224
|
-
} else {
|
|
225
|
-
reject(e)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Set cancelTokenPromise to null to indicate that there is nothing to
|
|
229
|
-
// cancel anymore.
|
|
230
|
-
return null
|
|
231
|
-
}
|
|
232
|
-
})()
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
if (manager == null) {
|
|
236
|
-
// Remove the attachment fetcher once it is done.
|
|
237
|
-
this.attachment
|
|
238
|
-
.then(() => {
|
|
239
|
-
this.rejectPendingFetch = null
|
|
240
|
-
this.ditto.store.removeAttachmentFetcher(this)
|
|
241
|
-
})
|
|
242
|
-
.catch(() => {
|
|
243
|
-
this.rejectPendingFetch = null
|
|
244
|
-
this.ditto.store.removeAttachmentFetcher(this)
|
|
245
|
-
})
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* `true` if the fetcher has completed or was stopped.
|
|
251
|
-
*
|
|
252
|
-
* @internal
|
|
253
|
-
*/
|
|
254
|
-
get isStopped(): boolean {
|
|
255
|
-
return this.rejectPendingFetch == null
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* This function is defined while a fetch is in progress and is used to reject
|
|
260
|
-
* the promise `this.attachment` when the fetch is canceled.
|
|
261
|
-
*
|
|
262
|
-
* @internal
|
|
263
|
-
*/
|
|
264
|
-
private rejectPendingFetch: (() => void) | null = null
|
|
265
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as FFI from './ffi'
|
|
6
|
-
import { mapFFIErrors } from './error'
|
|
7
|
-
|
|
8
|
-
import type { AttachmentMetadata } from './attachment'
|
|
9
|
-
import type { Store } from './store'
|
|
10
|
-
|
|
11
|
-
/** @internal */
|
|
12
|
-
export type UntypedAttachmentToken = { id: string; len: number | BigInt; metadata: AttachmentMetadata }
|
|
13
|
-
|
|
14
|
-
/** @internal */
|
|
15
|
-
export type TypedAttachmentToken = { [FFI.DittoCRDTTypeKey]?: FFI.DittoCRDTType.attachment; _id: Uint8Array; _len: number | BigInt; _meta: AttachmentMetadata }
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Serves as a token for a specific attachment that you can pass to a call to
|
|
19
|
-
* {@link Store.fetchAttachment | ditto.store.fetchAttachment()}.
|
|
20
|
-
*/
|
|
21
|
-
export class AttachmentToken {
|
|
22
|
-
/** The attachment's ID. */
|
|
23
|
-
// This ID is a _non-padded_ base64-encoded version of `idBytes`.
|
|
24
|
-
readonly id: string
|
|
25
|
-
|
|
26
|
-
/** The attachment's size given as number of bytes. */
|
|
27
|
-
readonly len: number | BigInt
|
|
28
|
-
|
|
29
|
-
/** The attachment's metadata. */
|
|
30
|
-
readonly metadata: AttachmentMetadata
|
|
31
|
-
|
|
32
|
-
// -------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
/** @internal */
|
|
35
|
-
constructor(jsObj: UntypedAttachmentToken | TypedAttachmentToken) {
|
|
36
|
-
// There are two representations of attachment tokens:
|
|
37
|
-
// 1. The legacy typed representation is an internal format that is used by
|
|
38
|
-
// the query builder API. It can be identified by the presence of the
|
|
39
|
-
// [FFI.DittoCRDTTypeKey] field.
|
|
40
|
-
// 2. The untyped representation is used in our public API and was first
|
|
41
|
-
// introduced in the HTTP API. It is now used by the DQL API as well. It
|
|
42
|
-
// uses a non-padded base64-encoded ID.
|
|
43
|
-
let id: Uint8Array, len: number | BigInt, meta: AttachmentMetadata
|
|
44
|
-
if (jsObj[FFI.DittoCRDTTypeKey] != null) {
|
|
45
|
-
;({ id, len, meta } = AttachmentToken.validateTypedInput(jsObj as TypedAttachmentToken))
|
|
46
|
-
} else {
|
|
47
|
-
;({ id, len, meta } = AttachmentToken.validateUntypedInput(jsObj as UntypedAttachmentToken))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// base64-encode string and remove padding
|
|
51
|
-
this.id = mapFFIErrors(() => FFI.base64encode(id, 'Unpadded'))
|
|
52
|
-
this.idBytes = id
|
|
53
|
-
this.len = len
|
|
54
|
-
this.metadata = meta
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @internal */
|
|
58
|
-
readonly idBytes: Uint8Array
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Validate an input value that has a field `[FFI.DittoCRDTTypeKey]` and
|
|
62
|
-
* return its contents.
|
|
63
|
-
*
|
|
64
|
-
* @throws {Error} If the input is invalid.
|
|
65
|
-
* @returns {object} binary id, len and metadata of the attachment token
|
|
66
|
-
*/
|
|
67
|
-
private static validateTypedInput(jsObj: TypedAttachmentToken): { id: Uint8Array; len: number | BigInt; meta: AttachmentMetadata } {
|
|
68
|
-
const type = jsObj[FFI.DittoCRDTTypeKey]
|
|
69
|
-
if (type !== FFI.DittoCRDTType.attachment) {
|
|
70
|
-
throw new Error('Invalid attachment token')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const id = jsObj['_id']
|
|
74
|
-
if (!(id instanceof Uint8Array)) {
|
|
75
|
-
throw new Error('Invalid attachment token id')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const len = jsObj['_len']
|
|
79
|
-
if ((typeof len !== 'number' && typeof len !== 'bigint') || len < 0) {
|
|
80
|
-
throw new Error('Invalid attachment token length, must be a non-negative number or bigint')
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const meta = jsObj['_meta']
|
|
84
|
-
if (typeof meta !== 'object') {
|
|
85
|
-
throw new Error('Invalid attachment token meta')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { id, len, meta }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Validate an untyped input value and return its contents.
|
|
93
|
-
*
|
|
94
|
-
* Converts _unpadded_ base64-encoded ID in input to _padded_ base64-encoded
|
|
95
|
-
* ID before returning it as `Uint8Array`.
|
|
96
|
-
*
|
|
97
|
-
* @throws {@link DittoError} `store/attachment-token-invalid` If the input id
|
|
98
|
-
* is not a valid base64 string.
|
|
99
|
-
* @returns {object} binary id, len and metadata of the attachment token
|
|
100
|
-
*/
|
|
101
|
-
private static validateUntypedInput(jsObj: UntypedAttachmentToken): { id: Uint8Array; len: number | BigInt; meta: AttachmentMetadata } {
|
|
102
|
-
const idBase64 = jsObj['id']
|
|
103
|
-
if (typeof idBase64 !== 'string') {
|
|
104
|
-
throw new Error('Invalid attachment token id')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const id = mapFFIErrors(
|
|
108
|
-
() => FFI.tryBase64Decode(idBase64, 'Unpadded'),
|
|
109
|
-
{
|
|
110
|
-
Base64Invalid: ['store/attachment-token-invalid', 'Failed to decode attachment token id from base64 input'],
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
attachmentTokenID: idBase64,
|
|
114
|
-
},
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
const len = jsObj['len']
|
|
118
|
-
if ((typeof len !== 'number' && typeof len !== 'bigint') || len < 0) {
|
|
119
|
-
throw new Error('Invalid attachment token length, must be a non-negative number or bigint')
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const meta = jsObj['metadata']
|
|
123
|
-
if (typeof meta !== 'object') {
|
|
124
|
-
throw new Error('Invalid attachment token meta')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { id, len, meta }
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -1,121 +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
|
-
import { Bridge } from './bridge'
|
|
8
|
-
import { DittoError } from './error'
|
|
9
|
-
|
|
10
|
-
import type { Ditto } from './ditto'
|
|
11
|
-
import type { AttachmentToken } from './attachment-token'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A key-value map of user-defined metadata for an attachment.
|
|
15
|
-
*/
|
|
16
|
-
export type AttachmentMetadata = { [key: string]: string }
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Represents an attachment and can be used to insert the associated attachment
|
|
20
|
-
* into a document at a specific key-path. You can't instantiate an attachment
|
|
21
|
-
* directly, please use the
|
|
22
|
-
* {@link Store.newAttachment | ditto.store.newAttachment()} method instead.
|
|
23
|
-
*/
|
|
24
|
-
export class Attachment {
|
|
25
|
-
/** @internal */
|
|
26
|
-
readonly ditto: Ditto
|
|
27
|
-
|
|
28
|
-
/** @internal */
|
|
29
|
-
readonly token: AttachmentToken
|
|
30
|
-
|
|
31
|
-
/** The attachment's ID. */
|
|
32
|
-
get id(): string {
|
|
33
|
-
return this.token.id
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** The attachment's size given as number of bytes. */
|
|
37
|
-
get len(): number | BigInt {
|
|
38
|
-
return this.token.len
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** The attachment's metadata. */
|
|
42
|
-
get metadata(): { [key: string]: string } {
|
|
43
|
-
return this.token.metadata
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Returns the attachment's data.
|
|
48
|
-
*/
|
|
49
|
-
data(): Promise<Uint8Array> {
|
|
50
|
-
const ditto = this.ditto
|
|
51
|
-
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
52
|
-
return this.ditto.deferCloseAsync(async () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (Environment.isReactNativeBuild) {
|
|
56
|
-
const attachmentHandle = Bridge.attachment.handleFor(this)
|
|
57
|
-
const attachmentPath = FFI.dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref())
|
|
58
|
-
return await FFI.readFile(attachmentPath)
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Returns the attachment's data.
|
|
65
|
-
*
|
|
66
|
-
* @deprecated Use `data()` instead.
|
|
67
|
-
*/
|
|
68
|
-
getData(): Promise<Uint8Array> {
|
|
69
|
-
return this.data()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Copies the attachment to the specified file path. Node-only,
|
|
74
|
-
* throws in the browser.
|
|
75
|
-
*
|
|
76
|
-
* @param path The path that the attachment should be copied to.
|
|
77
|
-
*/
|
|
78
|
-
copyToPath(path: string): Promise<void> {
|
|
79
|
-
const ditto = this.ditto
|
|
80
|
-
const dittoHandle = Bridge.ditto.handleFor(ditto)
|
|
81
|
-
return this.ditto.deferCloseAsync(async () => {
|
|
82
|
-
|
|
83
|
-
if (Environment.isReactNativeBuild) {
|
|
84
|
-
const attachmentHandle = Bridge.attachment.handleFor(this)
|
|
85
|
-
const attachmentPath = FFI.dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref())
|
|
86
|
-
// If the file already exists, we fail. This is the same behavior as
|
|
87
|
-
// for the Swift/ObjC SDK.
|
|
88
|
-
return await FFI.copyFile(attachmentPath, path, ditto.persistenceDirectory)
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** @internal */
|
|
94
|
-
constructor(ditto: Ditto, token: AttachmentToken) {
|
|
95
|
-
this.ditto = ditto
|
|
96
|
-
this.token = token
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Validates the given attachment metadata. Metadata must be a flat object with
|
|
102
|
-
* string values.
|
|
103
|
-
*
|
|
104
|
-
* This should really happen in core to make sure we use the same validation
|
|
105
|
-
* logic across SDKs but we decided to postpone that for the next iteration on
|
|
106
|
-
* attachments.
|
|
107
|
-
*
|
|
108
|
-
* @throws {@link DittoError} 'store/failed-to-create-attachment'
|
|
109
|
-
* @internal
|
|
110
|
-
*/
|
|
111
|
-
export function validateAttachmentMetadata(metadata: AttachmentMetadata): void {
|
|
112
|
-
if (typeof metadata !== 'object') {
|
|
113
|
-
throw new DittoError('store/failed-to-create-attachment', `Invalid attachment metadata: expected a value of type object but got ${typeof metadata}.`)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (const key in metadata) {
|
|
117
|
-
if (typeof metadata[key] !== 'string') {
|
|
118
|
-
throw new DittoError('store/failed-to-create-attachment', `Invalid attachment metadata: metadata values must be strings but key '${key}' has a value of type ${typeof metadata[key]}.`)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
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
|
-
* WARNING: For attachments in the input, the output can contain `BigInt`
|
|
51
|
-
* values, which are not JSON-compatible.
|
|
52
|
-
*
|
|
53
|
-
* @throws {Error} If `jsObj` contains a non-finite float value.
|
|
54
|
-
*/
|
|
55
|
-
export function desugarJSObject(jsObj: any): any {
|
|
56
|
-
if (jsObj && typeof jsObj === 'object') {
|
|
57
|
-
if (Array.isArray(jsObj)) {
|
|
58
|
-
return jsObj.map((v, idx) => desugarJSObject(v))
|
|
59
|
-
} else if (jsObj instanceof DocumentID) {
|
|
60
|
-
return jsObj.value
|
|
61
|
-
} else if (jsObj instanceof Counter) {
|
|
62
|
-
const counterJSON = {}
|
|
63
|
-
counterJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.counter
|
|
64
|
-
counterJSON[FFI.DittoCRDTValueKey] = jsObj.value
|
|
65
|
-
return counterJSON
|
|
66
|
-
} else if (jsObj instanceof Register) {
|
|
67
|
-
const registerJSON = {}
|
|
68
|
-
registerJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.register
|
|
69
|
-
registerJSON[FFI.DittoCRDTValueKey] = jsObj.value
|
|
70
|
-
return registerJSON
|
|
71
|
-
} else if (jsObj instanceof Attachment) {
|
|
72
|
-
const attachmentJSON = {
|
|
73
|
-
_id: jsObj.token.idBytes,
|
|
74
|
-
_len: jsObj.token.len, // may be a BigInt
|
|
75
|
-
_meta: jsObj.token.metadata,
|
|
76
|
-
}
|
|
77
|
-
attachmentJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.attachment
|
|
78
|
-
return attachmentJSON
|
|
79
|
-
} else {
|
|
80
|
-
// Create a copy to not mutate the original object
|
|
81
|
-
const jsObjJSON = {}
|
|
82
|
-
for (const [key, value] of Object.entries(jsObj)) {
|
|
83
|
-
jsObjJSON[key] = desugarJSObject(value)
|
|
84
|
-
}
|
|
85
|
-
return jsObjJSON
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
checkForUnsupportedValues(jsObj)
|
|
89
|
-
return jsObj
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Throws an error if input is a non-finite float value.
|
|
95
|
-
*
|
|
96
|
-
* Workaround while we don't have a reliable way of receiving error messages
|
|
97
|
-
* from `dittoCore.ditto_collection_insert_value()`.
|
|
98
|
-
*
|
|
99
|
-
* See https://github.com/getditto/ditto/issues/8657 for details.
|
|
100
|
-
*
|
|
101
|
-
* @param jsObj The object to check.
|
|
102
|
-
* @throws {Error} If `jsObj` is a non-finite float value.
|
|
103
|
-
*/
|
|
104
|
-
function checkForUnsupportedValues(jsObj: any): void {
|
|
105
|
-
if (Number.isNaN(jsObj) || jsObj === Infinity || jsObj === -Infinity) {
|
|
106
|
-
throw new Error('Non-finite float values are not supported')
|
|
107
|
-
}
|
|
108
|
-
}
|