UNPKG

@dittolive/ditto

Version:

Ditto is a cross-platform SDK that allows apps to sync with and even without internet connectivity.

1,118 lines (1,110 loc) 290 kB
'use strict'; // // Copyright © 2021 DittoLive Incorporated. All rights reserved. // /** @internal */ class KeepAlive { /** @internal */ get isActive() { return this.intervalID !== null; } /** @internal */ constructor() { this.countsByID = {}; this.intervalID = null; } /** @internal */ retain(id) { if (typeof this.countsByID[id] === 'undefined') this.countsByID[id] = 0; this.countsByID[id] += 1; if (this.intervalID === null) { // Keep the process alive as long as there is at least one ID being // tracked by setting a time interval with the maximum delay. This will // prevent the process from exiting. const maxDelay = 2147483647; // Signed 32 bit integer, see docs: https://developer.mozilla.org/en-US/docs/Web/API/setInterval this.intervalID = setInterval(() => { /* no-op */ }, maxDelay); KeepAlive.finalizationRegistry.register(this, this.intervalID, this); } } /** @internal */ release(id) { if (typeof this.countsByID[id] === 'undefined') { throw new Error(`Internal inconsistency, trying to release a keep-alive ID that hasn't been retained before or isn't tracked anymore: ${id}`); } this.countsByID[id] -= 1; if (this.countsByID[id] === 0) delete this.countsByID[id]; if (Object.keys(this.countsByID).length === 0) { // Nothing is tracked anymore, it's safe to clear the interval // and let the process do what it wants. KeepAlive.finalizationRegistry.unregister(this); if (this.intervalID !== null) clearInterval(this.intervalID); this.intervalID = null; } } /** @internal */ currentIDs() { return Object.keys(this.countsByID); } /** @internal */ countForID(id) { return this.countsByID[id] ?? null; } } KeepAlive.finalizationRegistry = new FinalizationRegistry(clearInterval); // // Copyright © 2021 DittoLive Incorporated. All rights reserved. // /** * Generic observer handle returned by various observation APIs. The observation * remains active until the {@link stop | stop()} method is called explicitly or * the observer instance is garbage collected. Therefore, to keep the observation * alive, you have to keep a reference to the corresponding observer. */ class Observer { /** @internal */ get token() { return this._token; } /** @internal */ constructor(observerManager, token, options = {}) { this.observerManager = observerManager; this._token = token; this.options = options; if (options.stopsWhenFinalized) { Observer.finalizationRegistry.register(this, { observerManager, token }, this); } } /** * Returns `true` if the observer has been explicitly stopped via the `stop()` * method. Otherwise returns `false`. */ get isStopped() { return (this.token !== undefined && this.observerManager.hasObserver(this.token)); } /** * Stops the observation. Calling this method multiple times has no effect. */ stop() { const token = this.token; if (token) { delete this._token; Observer.finalizationRegistry.unregister(this); this.observerManager.removeObserver(token); } } static finalize(observerManagerAndToken) { const { observerManager, token } = observerManagerAndToken; observerManager.removeObserver(token); } } Observer.finalizationRegistry = new FinalizationRegistry(Observer.finalize); const ditto = (function () { // IMPORTANT: most packagers perform _static_ analysis to identity native // Node modules to copy them into the right directory within the // final package. This only works if we use constant strings with // require(). Therefore, we use an if for all supported targets and // explicitly require each instead of dynamically building the require // path. const target = process.platform + '-' + process.arch; try { if (target === 'darwin-arm64') return require('./ditto.darwin-arm64.node') if (target === 'linux-x64') return require('./ditto.linux-x64.node') if (target === 'linux-arm64') return require('./ditto.linux-arm64.node') if (target === 'win32-x64') return require('./ditto.win32-x64.node') } catch (error) { throw new Error("Couldn't load native module 'ditto." + target + ".node' due to error:" + error.toString()) } throw new Error("No native module 'ditto." + target + ".node' found. Please check the Ditto documentation for supported platforms.") })(); function boxCBytesIntoBuffer(...args) { return ditto.boxCBytesIntoBuffer(...args) } function boxCStringIntoString(...args) { return ditto.boxCStringIntoString(...args) } function ditto_auth_client_is_web_valid(...args) { return ditto.ditto_auth_client_is_web_valid(...args) } function ditto_auth_client_login_with_token_and_feedback(...args) { return ditto.ditto_auth_client_login_with_token_and_feedback(...args) } function ditto_auth_client_logout(...args) { return ditto.ditto_auth_client_logout(...args) } function ditto_auth_client_make_login_provider(...args) { return ditto.ditto_auth_client_make_login_provider(...args) } function ditto_auth_client_user_id(...args) { return ditto.ditto_auth_client_user_id(...args) } function ditto_auth_set_login_provider(...args) { return ditto.ditto_auth_set_login_provider(...args) } function ditto_cancel_resolve_attachment(...args) { return ditto.ditto_cancel_resolve_attachment(...args) } function ditto_clear_presence_callback(...args) { return ditto.ditto_clear_presence_callback(...args) } function ditto_clear_presence_v3_callback(...args) { return ditto.ditto_clear_presence_v3_callback(...args) } function ditto_error_message(...args) { return ditto.ditto_error_message(...args) } function ditto_free(...args) { return ditto.ditto_free(...args) } function ditto_free_attachment_handle(...args) { return ditto.ditto_free_attachment_handle(...args) } function ditto_get_complete_attachment_path(...args) { return ditto.ditto_get_complete_attachment_path(...args) } function ditto_init_sdk_version(...args) { return ditto.ditto_init_sdk_version(...args) } function ditto_log(...args) { return ditto.ditto_log(...args) } function ditto_logger_enabled(...args) { return ditto.ditto_logger_enabled(...args) } function ditto_logger_enabled_get(...args) { return ditto.ditto_logger_enabled_get(...args) } function ditto_logger_init(...args) { return ditto.ditto_logger_init(...args) } function ditto_logger_minimum_log_level(...args) { return ditto.ditto_logger_minimum_log_level(...args) } function ditto_logger_minimum_log_level_get(...args) { return ditto.ditto_logger_minimum_log_level_get(...args) } function ditto_logger_set_custom_log_cb(...args) { return ditto.ditto_logger_set_custom_log_cb(...args) } function ditto_new_attachment_from_bytes(...args) { return ditto.ditto_new_attachment_from_bytes(...args) } function ditto_new_attachment_from_file(...args) { return ditto.ditto_new_attachment_from_file(...args) } function ditto_presence_v3(...args) { return ditto.ditto_presence_v3(...args) } function ditto_register_presence_v3_callback(...args) { return ditto.ditto_register_presence_v3_callback(...args) } function ditto_register_transport_condition_changed_callback(...args) { return ditto.ditto_register_transport_condition_changed_callback(...args) } function ditto_resolve_attachment(...args) { return ditto.ditto_resolve_attachment(...args) } function ditto_sdk_transports_error_free(...args) { return ditto.ditto_sdk_transports_error_free(...args) } function ditto_sdk_transports_error_new(...args) { return ditto.ditto_sdk_transports_error_new(...args) } function ditto_sdk_transports_error_value(...args) { return ditto.ditto_sdk_transports_error_value(...args) } function ditto_sdk_transports_init(...args) { return ditto.ditto_sdk_transports_init(...args) } function ditto_set_device_name(...args) { return ditto.ditto_set_device_name(...args) } function ditto_shutdown(...args) { return ditto.ditto_shutdown(...args) } function ditto_small_peer_info_get_is_enabled(...args) { return ditto.ditto_small_peer_info_get_is_enabled(...args) } function ditto_small_peer_info_get_metadata(...args) { return ditto.ditto_small_peer_info_get_metadata(...args) } function ditto_small_peer_info_set_enabled(...args) { return ditto.ditto_small_peer_info_set_enabled(...args) } function ditto_small_peer_info_set_metadata(...args) { return ditto.ditto_small_peer_info_set_metadata(...args) } function dittoffi_DEFAULT_DATABASE_ID(...args) { return ditto.dittoffi_DEFAULT_DATABASE_ID(...args) } function dittoffi_DITTO_DEVELOPMENT_PROVIDER(...args) { return ditto.dittoffi_DITTO_DEVELOPMENT_PROVIDER(...args) } function dittoffi_authentication_status_free(...args) { return ditto.dittoffi_authentication_status_free(...args) } function dittoffi_authentication_status_is_authenticated(...args) { return ditto.dittoffi_authentication_status_is_authenticated(...args) } function dittoffi_authentication_status_user_id(...args) { return ditto.dittoffi_authentication_status_user_id(...args) } function dittoffi_base64_encode(...args) { return ditto.dittoffi_base64_encode(...args) } function dittoffi_connection_request_authorize(...args) { return ditto.dittoffi_connection_request_authorize(...args) } function dittoffi_connection_request_connection_type(...args) { return ditto.dittoffi_connection_request_connection_type(...args) } function dittoffi_connection_request_free(...args) { return ditto.dittoffi_connection_request_free(...args) } function dittoffi_connection_request_identity_service_metadata_json(...args) { return ditto.dittoffi_connection_request_identity_service_metadata_json(...args) } function dittoffi_connection_request_peer_key_string(...args) { return ditto.dittoffi_connection_request_peer_key_string(...args) } function dittoffi_connection_request_peer_metadata_json(...args) { return ditto.dittoffi_connection_request_peer_metadata_json(...args) } function dittoffi_crypto_generate_secure_random_token(...args) { return ditto.dittoffi_crypto_generate_secure_random_token(...args) } function dittoffi_differ_diff(...args) { return ditto.dittoffi_differ_diff(...args) } function dittoffi_differ_free(...args) { return ditto.dittoffi_differ_free(...args) } function dittoffi_differ_new(...args) { return ditto.dittoffi_differ_new(...args) } function dittoffi_ditto_absolute_persistence_directory(...args) { return ditto.dittoffi_ditto_absolute_persistence_directory(...args) } function dittoffi_ditto_config(...args) { return ditto.dittoffi_ditto_config(...args) } function dittoffi_ditto_config_default(...args) { return ditto.dittoffi_ditto_config_default(...args) } function dittoffi_ditto_is_activated(...args) { return ditto.dittoffi_ditto_is_activated(...args) } function dittoffi_ditto_is_sync_active(...args) { return ditto.dittoffi_ditto_is_sync_active(...args) } function dittoffi_ditto_open_throws(...args) { return ditto.dittoffi_ditto_open_throws(...args) } function dittoffi_ditto_set_authentication_status_handler(...args) { return ditto.dittoffi_ditto_set_authentication_status_handler(...args) } function dittoffi_ditto_stop_sync(...args) { return ditto.dittoffi_ditto_stop_sync(...args) } function dittoffi_ditto_transport_config(...args) { return ditto.dittoffi_ditto_transport_config(...args) } function dittoffi_ditto_try_set_transport_config(...args) { return ditto.dittoffi_ditto_try_set_transport_config(...args) } function dittoffi_ditto_try_start_sync(...args) { return ditto.dittoffi_ditto_try_start_sync(...args) } function dittoffi_error_code(...args) { return ditto.dittoffi_error_code(...args) } function dittoffi_error_description(...args) { return ditto.dittoffi_error_description(...args) } function dittoffi_error_free(...args) { return ditto.dittoffi_error_free(...args) } function dittoffi_get_sdk_semver(...args) { return ditto.dittoffi_get_sdk_semver(...args) } function dittoffi_logger_try_export_to_file_async(...args) { return ditto.dittoffi_logger_try_export_to_file_async(...args) } function dittoffi_presence_peer_metadata_json(...args) { return ditto.dittoffi_presence_peer_metadata_json(...args) } function dittoffi_presence_set_connection_request_handler(...args) { return ditto.dittoffi_presence_set_connection_request_handler(...args) } function dittoffi_presence_try_set_peer_metadata_json(...args) { return ditto.dittoffi_presence_try_set_peer_metadata_json(...args) } function dittoffi_query_result_commit_id(...args) { return ditto.dittoffi_query_result_commit_id(...args) } function dittoffi_query_result_free(...args) { return ditto.dittoffi_query_result_free(...args) } function dittoffi_query_result_has_commit_id(...args) { return ditto.dittoffi_query_result_has_commit_id(...args) } function dittoffi_query_result_item_at(...args) { return ditto.dittoffi_query_result_item_at(...args) } function dittoffi_query_result_item_cbor(...args) { return ditto.dittoffi_query_result_item_cbor(...args) } function dittoffi_query_result_item_count(...args) { return ditto.dittoffi_query_result_item_count(...args) } function dittoffi_query_result_item_free(...args) { return ditto.dittoffi_query_result_item_free(...args) } function dittoffi_query_result_item_json(...args) { return ditto.dittoffi_query_result_item_json(...args) } function dittoffi_query_result_item_new(...args) { return ditto.dittoffi_query_result_item_new(...args) } function dittoffi_query_result_mutated_document_id_at(...args) { return ditto.dittoffi_query_result_mutated_document_id_at(...args) } function dittoffi_query_result_mutated_document_id_count(...args) { return ditto.dittoffi_query_result_mutated_document_id_count(...args) } function dittoffi_store_begin_transaction_async_throws(...args) { return ditto.dittoffi_store_begin_transaction_async_throws(...args) } function dittoffi_store_observer_cancel(...args) { return ditto.dittoffi_store_observer_cancel(...args) } function dittoffi_store_observer_free(...args) { return ditto.dittoffi_store_observer_free(...args) } function dittoffi_store_observer_is_cancelled(...args) { return ditto.dittoffi_store_observer_is_cancelled(...args) } function dittoffi_store_observer_query_arguments_cbor(...args) { return ditto.dittoffi_store_observer_query_arguments_cbor(...args) } function dittoffi_store_observer_query_arguments_json(...args) { return ditto.dittoffi_store_observer_query_arguments_json(...args) } function dittoffi_store_observer_query_string(...args) { return ditto.dittoffi_store_observer_query_string(...args) } function dittoffi_store_observers(...args) { return ditto.dittoffi_store_observers(...args) } function dittoffi_store_register_observer_throws(...args) { return ditto.dittoffi_store_register_observer_throws(...args) } function dittoffi_store_transactions(...args) { return ditto.dittoffi_store_transactions(...args) } function dittoffi_sync_register_subscription_throws(...args) { return ditto.dittoffi_sync_register_subscription_throws(...args) } function dittoffi_sync_subscription_cancel(...args) { return ditto.dittoffi_sync_subscription_cancel(...args) } function dittoffi_sync_subscription_free(...args) { return ditto.dittoffi_sync_subscription_free(...args) } function dittoffi_sync_subscription_is_cancelled(...args) { return ditto.dittoffi_sync_subscription_is_cancelled(...args) } function dittoffi_sync_subscription_query_arguments_cbor(...args) { return ditto.dittoffi_sync_subscription_query_arguments_cbor(...args) } function dittoffi_sync_subscription_query_arguments_json(...args) { return ditto.dittoffi_sync_subscription_query_arguments_json(...args) } function dittoffi_sync_subscription_query_string(...args) { return ditto.dittoffi_sync_subscription_query_string(...args) } function dittoffi_sync_subscriptions(...args) { return ditto.dittoffi_sync_subscriptions(...args) } function dittoffi_transaction_complete_async_throws(...args) { return ditto.dittoffi_transaction_complete_async_throws(...args) } function dittoffi_transaction_execute_async_throws(...args) { return ditto.dittoffi_transaction_execute_async_throws(...args) } function dittoffi_transaction_free(...args) { return ditto.dittoffi_transaction_free(...args) } function dittoffi_transaction_info(...args) { return ditto.dittoffi_transaction_info(...args) } function dittoffi_transport_config_new(...args) { return ditto.dittoffi_transport_config_new(...args) } function dittoffi_try_base64_decode(...args) { return ditto.dittoffi_try_base64_decode(...args) } function dittoffi_try_exec_statement(...args) { return ditto.dittoffi_try_exec_statement(...args) } function dittoffi_try_verify_license(...args) { return ditto.dittoffi_try_verify_license(...args) } function getDeadlockTimeout$1(...args) { return ditto.getDeadlockTimeout(...args) } function refCBytesIntoBuffer(...args) { return ditto.refCBytesIntoBuffer(...args) } function refCStringToString(...args) { return ditto.refCStringToString(...args) } function setDeadlockTimeout$1(...args) { return ditto.setDeadlockTimeout(...args) } const isReactNativeBuild = false; // // Copyright © 2023 DittoLive Incorporated. All rights reserved. // // This internal module contains the Ditto FFI error type and helper functions // for working with it. Except for the feature flag helper, this module should // not depend on any other parts of the Ditto Javascript SDK. /** Matches a `<...>` prefix in an FFI result error message, e.g. `<dql> ...`. **/ const PREFIX_REGEX = new RegExp(/^<.*?>\s*/); /** * Represents an exception that occurred during a call into the Ditto FFI. * * Use the {@link throwOnErrorStatus | throwOnErrorStatus()} helper to * automatically throw this error when an FFI call returns with a non-zero * return value. * * @internal */ class DittoFFIError extends Error { /** * Only call this constructor after having called `FFI.ensureInitialized()` * and `FFI.trace()`. * * @param code numerical status code returned by an FFI call or an * {@link FFIResultErrorCode} for errors returned on FFI result objects * @param messageOverride overrides the thread-local error message set in * Ditto core * @param messageFallback fallback message to use if the thread-local error * message is empty */ constructor(code, messageOverride, messageFallback) { // Call `ffiErrorMessage()` even when an override is provided to ensure that // the thread-local error message is cleared. const threadLocalErrorMessage = ffiErrorMessage(); super(messageOverride || threadLocalErrorMessage || messageFallback); this.code = code; } } /** * Throws a {@link DittoFFIError} if the given `ffiError` is not `null`. * * Removes the thread-local error message from Ditto core. If an error description * is available on the given `ffiError`, it is used as the error message of the * thrown {@link DittoFFIError}. A prefix in the error message is removed, e.g. * `<dql> ...`. * * If no error description is available, the given `ffiFunctionName` is used to * produce a fallback error message. * * @internal */ function throwOnErrorResult(ffiError, ffiFunctionName) { if (ffiError !== null) { let errorCode; let errorMsg; try { errorCode = dittoffi_error_code(ffiError); errorMsg = boxCStringIntoString(dittoffi_error_description(ffiError)); dittoffi_error_free(ffiError); } catch (err) { throw new DittoFFIError(-1, `Failed to retrieve Ditto core error message: ${err.message}`); } if (errorMsg == null) { errorMsg = `${ffiFunctionName}() failed with error code: ${errorCode}`; } else { // Remove prefix from error message, e.g. `<dql> ...`. errorMsg = errorMsg.replace(PREFIX_REGEX, ''); } throw new DittoFFIError(errorCode, errorMsg); } } /** * Retrieves last thread-local error message and removes it. * * Subsequent call to this function (if no new error message has been set) will * always return `null`. * * This function is not calling `FFI.ensureInitialized()` to avoid a circular * dependency. This is okay as long as this function is only called from a * context where `FFI.ensureInitialized()` has already been called. * * @internal */ function ffiErrorMessage() { const errorMessageCString = ditto_error_message(); return boxCStringIntoString(errorMessageCString); } // // Copyright © 2023 DittoLive Incorporated. All rights reserved. // // NOTE: This is a temporary *hand-written* shim file for glue code for the // Ditto native Node module. Mid to long term, we'll either move all of this // code to the native side or generate this file via a companion CLI tool or // something equivalent. // // IMPORTANT: please leave this file self-contained and do not import any // sources from the JS SDK. /** @internal */ const DittoCRDTTypeKey = '_ditto_internal_type_jkb12973t4b'; /** @internal */ var DittoCRDTType; (function (DittoCRDTType) { DittoCRDTType[DittoCRDTType["counter"] = 0] = "counter"; DittoCRDTType[DittoCRDTType["register"] = 1] = "register"; DittoCRDTType[DittoCRDTType["attachment"] = 2] = "attachment"; DittoCRDTType[DittoCRDTType["rga"] = 3] = "rga"; DittoCRDTType[DittoCRDTType["rwMap"] = 4] = "rwMap"; })(DittoCRDTType || (DittoCRDTType = {})); // ------------------------------------------------------------- Constants -- /** @internal */ function DITTO_DEVELOPMENT_PROVIDER() { ensureInitialized(); const providerCString = dittoffi_DITTO_DEVELOPMENT_PROVIDER(); return refCStringToString(providerCString); } /** @internal */ function DEFAULT_DATABASE_ID() { ensureInitialized(); const idCString = dittoffi_DEFAULT_DATABASE_ID(); return refCStringToString(idCString); } // ------------------------------------------------------------- Differ -------- function differNew() { ensureInitialized(); return dittoffi_differ_new(); } function differDiff(differ, items) { ensureInitialized(); return dittoffi_differ_diff(differ, items); } function differFree(differ) { ensureInitialized(); dittoffi_differ_free(differ); } // HACK: underlying safer-ffi doesn't equivalent a CDitto with dittoffi_store_t // although they are C aliases so we need to force it's type. /** @internal */ function dittoPointerToStorePointer(dittoHandle) { return { addr: dittoHandle.addr, type: 'dittoffi_store_t const *', }; } /** * This FFI can error: * - DQL parser error * - Incorrect arguments to query parameters * - Collection is not found. * * @internal */ async function tryExecStatement(ditto, query, queryArgsCBOR) { ensureInitialized(); const queryBytesPointer = bytesFromString(query); const result = await dittoffi_try_exec_statement(ditto, queryBytesPointer, queryArgsCBOR); throwOnErrorResult(result.error, 'dittoffi_try_exec_statement'); return result.success; } /** @internal */ function syncRegisterSubscriptionThrows(dittoPointer, query, queryArgsCBOR) { ensureInitialized(); const queryBuffer = bytesFromString(query); const result = dittoffi_sync_register_subscription_throws(dittoPointer, queryBuffer, queryArgsCBOR); throwOnErrorResult(result.error, 'dittoffi_sync_register_subscription_throws'); return result.success; } /** @internal */ function syncSubscriptions(dittoPointer) { ensureInitialized(); return dittoffi_sync_subscriptions(dittoPointer); } /** @internal */ function syncSubscriptionQueryString(syncSubscriptionPointer) { ensureInitialized(); const queryStringBytes = dittoffi_sync_subscription_query_string(syncSubscriptionPointer); return boxCStringIntoString(queryStringBytes); } /** @internal */ function syncSubscriptionQueryArgumentsCBOR(syncSubscriptionPointer) { ensureInitialized(); const queryArgsCBORBytes = dittoffi_sync_subscription_query_arguments_cbor(syncSubscriptionPointer); if (queryArgsCBORBytes === null) return null; return boxCBytesIntoBuffer(queryArgsCBORBytes); } /** @internal */ function syncSubscriptionQueryArgumentsJSON(syncSubscriptionPointer) { ensureInitialized(); const queryArgsJSONBytes = dittoffi_sync_subscription_query_arguments_json(syncSubscriptionPointer); if (queryArgsJSONBytes === null) return null; const jsonBuffer = boxCBytesIntoBuffer(queryArgsJSONBytes); const textDecoder = new TextDecoder(); return textDecoder.decode(jsonBuffer); } /** @internal */ function syncSubscriptionCancel(syncSubscriptionPointer) { ensureInitialized(); dittoffi_sync_subscription_cancel(syncSubscriptionPointer); } /** @internal */ function syncSubscriptionIsCancelled(syncSubscriptionPointer) { ensureInitialized(); return dittoffi_sync_subscription_is_cancelled(syncSubscriptionPointer); } /** @internal */ function syncSubscriptionFree(syncSubscriptionPointer) { ensureInitialized(); dittoffi_sync_subscription_free(syncSubscriptionPointer); } // ----------------------------------------------------------- QueryResult ------ /** * Doesn't error * * @internal */ function queryResultFree(queryResultPointer) { ensureInitialized(); dittoffi_query_result_free(queryResultPointer); } /** * Doesn't error * * @internal */ function queryResultItemFree(queryResultItemPointer) { ensureInitialized(); dittoffi_query_result_item_free(queryResultItemPointer); } /** * Can error only on internal bug. * * @internal */ function queryResultItems(queryResultPointer) { ensureInitialized(); const rv = []; const resultCount = dittoffi_query_result_item_count(queryResultPointer); for (let i = 0; i < resultCount; i++) rv.push(dittoffi_query_result_item_at(queryResultPointer, i)); return rv; } /** * Doesn't error * * @internal */ function queryResultMutatedDocumentIDs(queryResultPointer) { ensureInitialized(); const rv = []; const resultCount = dittoffi_query_result_mutated_document_id_count(queryResultPointer); for (let i = 0; i < resultCount; i++) { const cborBytes = dittoffi_query_result_mutated_document_id_at(queryResultPointer, i); rv.push(boxCBytesIntoBuffer(cborBytes)); } return rv; } /** * @internal */ function queryResultHasCommitID(queryResultPointer) { ensureInitialized(); return dittoffi_query_result_has_commit_id(queryResultPointer); } /** * @internal */ function queryResultCommitID(queryResultPointer) { ensureInitialized(); const commitId = dittoffi_query_result_commit_id(queryResultPointer); // the FFI function returns a number or BigInt, we convert it to BigInt // to simplify the API return BigInt(commitId); } /** * The result CBOR contains a map/object with fields and values. No CRDTs are * present there as they are not needed. By default only values from registers * are returned and non-register fields are ignored. * * Doesn't error * * @internal */ function queryResultItemCBOR(queryResultItemPointer) { ensureInitialized(); const cborBytes = dittoffi_query_result_item_cbor(queryResultItemPointer); return boxCBytesIntoBuffer(cborBytes); } /** * Returns JSON-encoded results given a DQL result item pointer. * * Compare for {@link queryResultItemCBOR} above. * * Doesn't error * * @internal */ function queryResultItemJSON(queryResultItemPointer) { ensureInitialized(); const jsonBytes = dittoffi_query_result_item_json(queryResultItemPointer); return boxCStringIntoString(jsonBytes); } function queryResultItemNew(jsonData) { ensureInitialized(); const result = dittoffi_query_result_item_new(jsonData); throwOnErrorResult(result.error, 'dittoffi_query_result_item_new'); return result.success; } // -------------------------------------------------------- StoreObserver ------ /** @internal */ function storeRegisterObserverThrows(dittoPointer, query, queryArgsCBOR, changeHandler) { ensureInitialized(); const queryBuffer = bytesFromString(query); const errorHandler = (err) => log('Error', `The registered store observer callback failed with ${err}`); const wrappedChangeHandler = wrapBackgroundCbForFFI(errorHandler, changeHandler); const result = dittoffi_store_register_observer_throws(dittoPointer, queryBuffer, queryArgsCBOR, wrappedChangeHandler); throwOnErrorResult(result.error, 'dittoffi_store_register_observer_throws'); return result.success; } /** @internal */ function storeObservers(dittoPointer) { ensureInitialized(); return dittoffi_store_observers(dittoPointer); } /** @internal */ function storeObserverQueryString(storeObserverPointer) { ensureInitialized(); const queryStringBytes = dittoffi_store_observer_query_string(storeObserverPointer); return boxCStringIntoString(queryStringBytes); } /** @internal */ function storeObserverQueryArgumentsCBOR(storeObserverPointer) { ensureInitialized(); const queryArgsCBORBytes = dittoffi_store_observer_query_arguments_cbor(storeObserverPointer); if (queryArgsCBORBytes === null) return null; return boxCBytesIntoBuffer(queryArgsCBORBytes); } /** @internal */ function storeObserverQueryArgumentsJSON(storeObserverPointer) { ensureInitialized(); const queryArgsJSONBytes = dittoffi_store_observer_query_arguments_json(storeObserverPointer); if (queryArgsJSONBytes === null) return null; const jsonBuffer = boxCBytesIntoBuffer(queryArgsJSONBytes); const textDecoder = new TextDecoder(); return textDecoder.decode(jsonBuffer); } /** @internal */ function storeObserverCancel(storeObserverPointer) { ensureInitialized(); dittoffi_store_observer_cancel(storeObserverPointer); } /** @internal */ function storeObserverIsCancelled(storeObserverPointer) { ensureInitialized(); return dittoffi_store_observer_is_cancelled(storeObserverPointer); } /** @internal */ function storeObserverFree(storeObserverPointer) { ensureInitialized(); dittoffi_store_observer_free(storeObserverPointer); } // --------------------------------------------------------------- Logger ------ /** @internal */ function loggerInit() { ensureInitialized(); ditto_logger_init(); } /** @internal */ async function loggerSetCustomLogCb(cb) { ensureInitialized(); if (null === cb) { await ditto_logger_set_custom_log_cb(null); } else { // IDEA: pass custom error handler here instead of null? const wrappedCallback = wrapBackgroundCbForFFI(null, (loglevel, cMsg) => { try { const msg = boxCStringIntoString(cMsg); cb(loglevel, msg); } catch (e) { log('Error', `The registered cb in \`ditto_logger_set_custom_log_cb()\` failed with: ${e}`); } }); await ditto_logger_set_custom_log_cb(wrappedCallback); } } /** @internal */ function loggerEnabled(enabled) { ensureInitialized(); ditto_logger_enabled(!!enabled); } /** @internal */ function loggerEnabledGet() { ensureInitialized(); return !!ditto_logger_enabled_get(); } /** @internal */ function loggerMinimumLogLevel(logLevel) { ensureInitialized(); ditto_logger_minimum_log_level(logLevel); } /** @internal */ function loggerMinimumLogLevelGet() { ensureInitialized(); return ditto_logger_minimum_log_level_get(); } /** @internal */ async function loggerTryExportToFile(path) { ensureInitialized(); const pathBytes = bytesFromString(path); const result = await new Promise((resolve, reject) => { const wrappedCallback = wrapBackgroundCbForFFI(reject, resolve); dittoffi_logger_try_export_to_file_async(pathBytes, wrappedCallback); }); throwOnErrorResult(result.error, 'dittoffi_logger_try_export_to_file_async'); return result.success; } /** @internal */ function log(level, message) { ensureInitialized(); const messageBuffer = bytesFromString(message); ditto_log(level, messageBuffer); } // ----------------------------------------------------------- AuthClient ------ function dittoAuthClientUserID(ditto) { ensureInitialized(); const cStr = ditto_auth_client_user_id(ditto); return boxCStringIntoString(cStr); } /** @internal */ function dittoAuthClientIsWebValid(ditto) { ensureInitialized(); return ditto_auth_client_is_web_valid(ditto) !== 0; } async function dittoAuthClientLoginWithTokenAndFeedback(ditto, token, provider) { ensureInitialized(); const tokenBytes = bytesFromString(token); const providerBytes = bytesFromString(provider); const result = await ditto_auth_client_login_with_token_and_feedback(ditto, tokenBytes, providerBytes); // Our `login_with_token_and_feedback()` API returns the `clientInfo` string // even when authentication has failed, so this function does not throw an // error when the status code is non-zero. const error = result.status_code === 0 ? null : new DittoFFIError(result.status_code, undefined, 'Ditto failed to authenticate.'); const clientInfo = result.c_string ? boxCStringIntoString(result.c_string) : null; return { error, clientInfo, }; } async function dittoAuthClientLogout(ditto) { ensureInitialized(); const errorCode = await ditto_auth_client_logout(ditto); if (errorCode !== 0) { throw new Error(errorMessage() || `Ditto failed to logout (error code: ${errorCode}).`); } } /** @internal */ function dittoSetAuthenticationStatusHandler(ditto, authenticationStatusUpdateCb, // Cb may be called in parallel at any point, so let's use an optional error handler onError) { ensureInitialized(); dittoffi_ditto_set_authentication_status_handler(ditto, wrapBackgroundCbForFFI(onError, authenticationStatusUpdateCb)); } function authenticationStatusUserID(ffiAuthenticationStatus) { ensureInitialized(); return boxCStringIntoString(dittoffi_authentication_status_user_id(ffiAuthenticationStatus)); } function authenticationStatusIsAuthenticated(ffiAuthenticationStatus) { ensureInitialized(); return dittoffi_authentication_status_is_authenticated(ffiAuthenticationStatus); } function authenticationStatusFree(ffiAuthenticationStatus) { ensureInitialized(); dittoffi_authentication_status_free(ffiAuthenticationStatus); } // --------------------------------------------------------- Transactions ------ function storeTransactions(store) { ensureInitialized(); const cborBytes = dittoffi_store_transactions(store); return boxCBytesIntoBuffer(cborBytes); } async function storeBeginTransaction(store, options) { ensureInitialized(); const ffiOptions = { is_read_only: options.isReadOnly, hint: bytesFromString(options.hint), }; return new Promise((resolve, reject) => { const callback = wrapBackgroundCbForFFI(reject, (resultObj) => { throwOnErrorResult(resultObj.error, 'dittoffi_store_begin_transaction_async_throws'); resolve(resultObj.success); }); dittoffi_store_begin_transaction_async_throws(store, ffiOptions, callback); }); } async function transactionCompleteAsync(transaction, action) { ensureInitialized(); return new Promise((resolve, reject) => { const callback = wrapBackgroundCbForFFI(reject, (resultObj) => { throwOnErrorResult(resultObj.error, 'dittoffi_transaction_complete_async_throws'); const action = resultObj.success; resolve(action); }); dittoffi_transaction_complete_async_throws(transaction, action, callback); }); } async function transactionExecuteAsync(transaction, query, queryArgsCbor) { ensureInitialized(); return new Promise((resolve, reject) => { const callback = wrapBackgroundCbForFFI(reject, (resultObj) => { throwOnErrorResult(resultObj.error, 'dittoffi_transaction_execute_async_throws'); resolve(resultObj.success); }); const queryBytes = bytesFromString(query); dittoffi_transaction_execute_async_throws(transaction, queryBytes, queryArgsCbor, callback); }); } function transactionInfo(transaction) { ensureInitialized(); const cborBytes = dittoffi_transaction_info(transaction); return boxCBytesIntoBuffer(cborBytes); } function transactionFree(transaction) { ensureInitialized(); dittoffi_transaction_free(transaction); } // --------------------------------------------------------- Ditto Config ------ /** @internal */ function dittoConfigDefault() { ensureInitialized(); const cborBytes = dittoffi_ditto_config_default(); return boxCBytesIntoBuffer(cborBytes); } /** @internal */ function dittoConfig(dittoPointer) { ensureInitialized(); const cborBytes = dittoffi_ditto_config(dittoPointer); return boxCBytesIntoBuffer(cborBytes); } /** @internal */ function dittoAbsolutePersistenceDirectory(dittoPointer) { ensureInitialized(); const cString = dittoffi_ditto_absolute_persistence_directory(dittoPointer); return boxCStringIntoString(cString); } /** @internal */ function dittoOpenThrows(configCBOR, transportConfigMode, defaultRootDirectory) { ensureInitialized(); const defaultRootDirectoryPointer = bytesFromString(defaultRootDirectory); // // Debug: Log CBOR bytes in hex format. The output can be pasted to https://cbor.me // // to visualize the CBOR structure. // const hexBytes = Array.from(configCBOR) // .map((byte) => byte.toString(16).padStart(2, '0').toUpperCase()) // .join(' '); // console.log(`DEBUG: CBOR bytes (${configCBOR.length} bytes):`); // console.log(hexBytes); const result = dittoffi_ditto_open_throws(configCBOR, transportConfigMode, defaultRootDirectoryPointer); throwOnErrorResult(result.error, 'dittoffi_ditto_open_throws'); return result.success; } /** @internal */ function dittoFree(self) { ensureInitialized(); // REFACTOR: add proper error handling. return ditto_free(self); } /** @internal */ function getDeadlockTimeout() { ensureInitialized(); return getDeadlockTimeout$1(); } /** @internal */ function setDeadlockTimeout(duration) { ensureInitialized(); setDeadlockTimeout$1(duration); } /** @internal */ function cryptoGenerateSecureRandomToken() { ensureInitialized(); const docIDString = dittoffi_crypto_generate_secure_random_token(); return boxCStringIntoString(docIDString); } /** @internal */ async function dittoClearPresenceCallback(self) { ensureInitialized(); return ditto_clear_presence_callback(self); } /** @internal */ function dittoRegisterPresenceV3Callback(self, cb) { ensureInitialized(); ditto_register_presence_v3_callback(self, wrapBackgroundCbForFFI((err) => log('Error', `The registered presence callback v3 errored with ${err}`), (cJsonStr) => { const jsonStr = refCStringToString(cJsonStr); cb(jsonStr); })); } /** @internal */ async function dittoClearPresenceV3Callback(self) { ensureInitialized(); return ditto_clear_presence_v3_callback(self); } /** @internal */ function presencePeerMetadataJSON(self) { ensureInitialized(); const result = dittoffi_presence_peer_metadata_json(self); const typedArray = boxCBytesIntoBuffer(result); const textDecoder = new TextDecoder(); return textDecoder.decode(typedArray); } /** @internal */ async function presenceTrySetPeerMetadataJSON(self, jsonString) { ensureInitialized(); const jsonDataCString = bytesFromString(jsonString); const result = await dittoffi_presence_try_set_peer_metadata_json(self, jsonDataCString); throwOnErrorResult(result.error, 'dittoffi_presence_try_set_peer_metadata_json'); } /** @internal */ function connectionRequestPeerKeyString(connectionRequest) { ensureInitialized(); const cString = dittoffi_connection_request_peer_key_string(connectionRequest); return boxCStringIntoString(cString); } /** @internal */ function connectionRequestPeerMetadataJSON(connectionRequest) { ensureInitialized(); const jsonByteRef = dittoffi_connection_request_peer_metadata_json(connectionRequest); const jsonBuffer = refCBytesIntoBuffer(jsonByteRef); const textDecoder = new TextDecoder(); return textDecoder.decode(jsonBuffer); } /** @internal */ function connectionRequestIdentityServiceMetadataJSON(connectionRequest) { ensureInitialized(); const jsonBytesRef = dittoffi_connection_request_identity_service_metadata_json(connectionRequest); const jsonBuffer = refCBytesIntoBuffer(jsonBytesRef); const textDecoder = new TextDecoder(); return textDecoder.decode(jsonBuffer); } /** @internal */ function connectionRequestConnectionType(connectionRequest) { ensureInitialized(); return dittoffi_connection_request_connection_type(connectionRequest); } /** @internal */ function connectionRequestAuthorize(connectionRequest, authorization) { ensureInitialized(); dittoffi_connection_request_authorize(connectionRequest, authorization); } /** @internal */ function connectionRequestFree(connectionRequest) { ensureInitialized(); dittoffi_connection_request_free(connectionRequest); } /** @internal */ function presenceSetConnectionRequestHandler(ditto, connectionRequestHandler, onError) { ensureInitialized(); if (connectionRequestHandler == null) { dittoffi_presence_set_connection_request_handler(ditto, null); } else { const wrappedCallback = wrapAsyncBackgroundCbForFFI(onError, connectionRequestHandler); dittoffi_presence_set_connection_request_handler(ditto, wrappedCallback); } } /** @internal */ function dittoIsActivated(ditto) { ensureInitialized(); return dittoffi_ditto_is_activated(ditto); } /** @internal */ function dittoIsSyncActive(ditto) { ensureInitialized(); return dittoffi_ditto_is_sync_active(ditto); } /** @internal */ function dittoTryStartSync(ditto) { ensureInitialized(); const result = dittoffi_ditto_try_start_sync(ditto); throwOnErrorResult(result.error, 'dittoffi_ditto_try_start_sync'); } /** @internal */ function dittoStopSync(ditto) { ensureInitialized(); return dittoffi_ditto_stop_sync(ditto); } /** @internal */ function dittoSetTransportConfig(ditto, transportConfigData) { ensureInitialized(); const result = dittoffi_ditto_try_set_transport_config(ditto, transportConfigData, true); throwOnErrorResult(result.error, 'dittoffi_ditto_try_set_transport_config'); } /** @internal */ function dittoTransportConfig(ditto) { ensureInitialized(); const cborBytes = dittoffi_ditto_transport_config(ditto); return boxCBytesIntoBuffer(cborBytes); } /** @internal */ function dittoSmallPeerInfoGetIsEnabled(dittoPointer) { ensureInitialized(); return ditto_small_peer_info_get_is_enabled(dittoPointer); } /** @internal */ function dittoSmallPeerInfoSetEnabled(dittoPointer, isEnabled) { ensureInitialized(); return ditto_small_peer_info_set_enabled(dittoPointer, isEnabled); } /** @internal */ function dittoSmallPeerInfoGetMetadata(dittoPointer) { ensureInitialized(); const cString = ditto_small_peer_info_get_metadata(dittoPointer); return boxCStringIntoString(cString); } /** @internal */ function dittoSmallPeerInfoSetMetadata(dittoPointer, metadata) { ensureInitialized(); const metadataCString = bytesFromString(metadata); const statusCode = ditto_small_peer_info_set_metadata(dittoPointer, metadataCString); switch (statusCode) { case 0: return; case -1: throw new Error('Internal inconsistency, the observability subsystem is unavailable.'); case 1: throw new Error(`Validation error, size limit exceeded: ${errorMessage() || 'metadata is too big'}`); case 2: throw new Error(`Validation error, ${errorMessage() || 'depth limit for metadata object exceeded'}`); case 3: throw new Error(`Validation error, ${errorMessage() || `'${metadata}' is not a valid JSON object`}`); default: throw new Error(errorMessage() || `Internal inconsistency, ditto_small_peer_info_set_metadata() returned an unknown error code: ${statusCode}`); } } /** @internal */ function dittoRegisterTransportConditionChangedCallback(self, cb) { ensureInitialized(); if (!cb) { ditto_register_transport_condition_changed_callback(self, null); } else { ditto_register_transport_condition_changed_callback(self, wrapBackgroundCbForFFI((err) => log('Error', `The registered "transport condition changed" callback errored with ${err}`), cb)); } } /** @internal */ function dittoSetDeviceName(dittoPointer, deviceName) { ensureInitialized(); const deviceNameCString = bytesFromString(deviceName); const truncatedDeviceNameCString = ditto_set_device_name(dittoPointer, deviceNameCString); return boxCStringIntoString(truncatedDeviceNameCString); } // Not supported on Wasm. /** @internal */ function dittoNewAttachmentFromFile(ditto, sourcePath, fileOperation) { ensureInitialized(); const sourcePathCString = bytesFromString(sourcePath); const outAttachment = {}; const errorCode = ditto_new_attachment_from_file(ditto, sourcePathCString, fileOperation, outAttachment); if (errorCode !== 0) { throw new DittoFFIError(errorCode, undefined, `ditto_new_attachment_from_file() failed with error code: ${errorCode}`); } return outAttachment; } /** @internal */ async function dittoNewAttachmentFromBytes(ditto, bytes) { ensureInitialized(); const outAttachment = {}; const errorCode = await ditto_new_attachment_from_bytes(ditto, bytes, outAttachment); if (errorCode !== 0) { throw new DittoFFIError(errorCode, undefined, `ditto_new_attachment_from_bytes() failed with error code: ${errorCode}`); } return outAttachment; } /** * @throws {@link DittoFFIError} * @internal */ async function dittoResolveAttachment(ditto, id, namedCallbacks, // Cb may be called in parallel at any point, so let's use // an optional error handler (which defaults to the ditto logger at 'Error' level). onError) { ensureInitialized(); const { onComplete, onProgress, onDelete } = namedCallbacks; const wrappedOnComplete = wrapBackgroundCbForFFI(onError, onComplete); const wrappedOnProgress = wrapBackgroundCbForFFI(onError, onProgress); const wrappedOnDelete = wrapBackgroundCbForFFI(onError, onDelete); const { status_code: errorCode, cancel_token: cancelToken } = await ditto_resolve_attachment(ditto, id, wrappedOnComplete, wrappedOnProgress, wrappedOnDelete); if (errorCode !== 0) { throw new DittoFFIError(errorCode, undefined, `ditto_resolve_attachment() failed with error code: ${errorCode}`); } return cancelToken; } /** @internal */ function dittoCancelResolveAttachment(dittoPointer, id, cancelToken) { ensureInitialized(); const errorCode = ditto_cancel_resolve_attachment(dittoPointer, id, cancelToken); if (errorCode !== 0) { throw new Error(errorMessage() || `ditto_cancel_resolve_attachment() failed with error code: ${errorCode}`); } } /** @internal */ function freeAttachmentHandle(attachmentHandlePointer) { ensureInitialized(); ditto_free_attachment_handle(attachmentHandlePointer); } /** @internal */ function dittoGetCompleteAttachmentPath(dittoPointer, attachmentHandlePointer) { ensureInitialized(); const pathCString = ditto_get_complete_attachment_path(dittoPointer, attachmentHandlePointer); return refCStringToString(pathCString); } /** @internal */ function dittoGetSDKSemver() { ensureInitialized(); const cString = dittoffi_get_sdk_semver(); return boxCStringIntoString(cString); } /** @internal */ function dittoPresenceV3(self) { ensureInitialized(); const cString = ditto_presence_v3(self); return boxCStringIntoString(cString); } /** @internal */ async function dittoShutdown(dittoPointer) { ensureInitialized(); return await ditto_shutdown(dittoPointer); } // ------------------------------------------------------------- base64 -------- /** @internal */ function base64encode(bytes, paddingMode) { const base64CString = dittoffi_base64_encode(bytes, paddingMode); return boxCStringIntoString(base64CString); } /** * @throws {@link DittoFFIError} if the base64 string is invalid * @internal */ function tryBase64Decode(base64, paddingMode) { const base64BytesPointer = bytesFromString(base64); const result = dittoffi_try_base64_decode(base64BytesPointer, paddingMode); throwOnErrorResult(result.error, 'dittoffi_try_base64_decode'); return boxCBytesIntoBuffer(result.success); } // ------------------------------------------------------------- Auth ---------- /** @internal */ async function dittoAuthSetLoginProvider(ditto, loginProvider) { ensureInitialized(); return await ditto_auth_set_login_provider(ditto, loginProvider); } /** @internal */ function dittoAuthClientMakeLoginProvider(expiringCb, // Cb may be called in parallel at any point, so let's use an optional error handler onError) { ensureInitialized(); return ditto_auth_client_make_login_provider(wrapBackgroundCbForFFI(onError, expiringCb)); } // ----------------------------------------------------------- Transports ------ /** @internal */ function transportsInit() { ensureInitialized(); const { output: wasInitialized, errorType } = withTransportsError(ditto_sdk_transports_init); if (wasInitialized === false) throw new Error(`Failed to initialize transports