@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
JavaScript
'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