@dittolive/ditto 4.0.3-alpha.linux-ble-fixes-2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/node/ditto.cjs.js +1676 -966
- package/node/ditto.darwin-arm64.node +0 -0
- package/node/ditto.darwin-x64.node +0 -0
- package/node/ditto.linux-arm.node +0 -0
- package/node/ditto.linux-x64.node +0 -0
- package/node/ditto.win32-x64.node +0 -0
- package/node/transports.darwin-arm64.node +0 -0
- package/node/transports.darwin-x64.node +0 -0
- package/package.json +1 -1
- package/types/ditto.d.ts +700 -536
- package/web/ditto.es6.js +1 -1
- package/web/ditto.umd.js +1 -1
- package/web/ditto.wasm +0 -0
package/node/ditto.cjs.js
CHANGED
|
@@ -84,7 +84,7 @@ class Observer {
|
|
|
84
84
|
* method. Otherwise returns `false`.
|
|
85
85
|
*/
|
|
86
86
|
get isStopped() {
|
|
87
|
-
return
|
|
87
|
+
return this.observerManager.hasObserver(this.token);
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
90
|
* Stops the observation. Calling this method multiple times has no effect.
|
|
@@ -292,21 +292,16 @@ function ditto_add_multicast_transport(...args) { return ditto.ditto_add_multica
|
|
|
292
292
|
function ditto_add_static_tcp_client(...args) { return ditto.ditto_add_static_tcp_client(...args) }
|
|
293
293
|
function ditto_add_subscription(...args) { return ditto.ditto_add_subscription(...args) }
|
|
294
294
|
function ditto_add_websocket_client(...args) { return ditto.ditto_add_websocket_client(...args) }
|
|
295
|
-
function ditto_auth_client_free(...args) { return ditto.ditto_auth_client_free(...args) }
|
|
296
295
|
function ditto_auth_client_get_site_id(...args) { return ditto.ditto_auth_client_get_site_id(...args) }
|
|
297
296
|
function ditto_auth_client_is_web_valid(...args) { return ditto.ditto_auth_client_is_web_valid(...args) }
|
|
298
297
|
function ditto_auth_client_is_x509_valid(...args) { return ditto.ditto_auth_client_is_x509_valid(...args) }
|
|
299
298
|
function ditto_auth_client_login_with_credentials(...args) { return ditto.ditto_auth_client_login_with_credentials(...args) }
|
|
300
299
|
function ditto_auth_client_login_with_token(...args) { return ditto.ditto_auth_client_login_with_token(...args) }
|
|
301
300
|
function ditto_auth_client_logout(...args) { return ditto.ditto_auth_client_logout(...args) }
|
|
302
|
-
function ditto_auth_client_make_anonymous_client(...args) { return ditto.ditto_auth_client_make_anonymous_client(...args) }
|
|
303
|
-
function ditto_auth_client_make_for_development(...args) { return ditto.ditto_auth_client_make_for_development(...args) }
|
|
304
301
|
function ditto_auth_client_make_login_provider(...args) { return ditto.ditto_auth_client_make_login_provider(...args) }
|
|
305
|
-
function ditto_auth_client_make_with_shared_key(...args) { return ditto.ditto_auth_client_make_with_shared_key(...args) }
|
|
306
|
-
function ditto_auth_client_make_with_static_x509(...args) { return ditto.ditto_auth_client_make_with_static_x509(...args) }
|
|
307
|
-
function ditto_auth_client_make_with_web(...args) { return ditto.ditto_auth_client_make_with_web(...args) }
|
|
308
302
|
function ditto_auth_client_set_validity_listener(...args) { return ditto.ditto_auth_client_set_validity_listener(...args) }
|
|
309
303
|
function ditto_auth_client_user_id(...args) { return ditto.ditto_auth_client_user_id(...args) }
|
|
304
|
+
function ditto_auth_set_login_provider(...args) { return ditto.ditto_auth_set_login_provider(...args) }
|
|
310
305
|
function ditto_cancel_resolve_attachment(...args) { return ditto.ditto_cancel_resolve_attachment(...args) }
|
|
311
306
|
function ditto_clear_presence_callback(...args) { return ditto.ditto_clear_presence_callback(...args) }
|
|
312
307
|
function ditto_clear_presence_v3_callback(...args) { return ditto.ditto_clear_presence_v3_callback(...args) }
|
|
@@ -337,6 +332,11 @@ function ditto_free_attachment_handle(...args) { return ditto.ditto_free_attachm
|
|
|
337
332
|
function ditto_get_collection_names(...args) { return ditto.ditto_get_collection_names(...args) }
|
|
338
333
|
function ditto_get_complete_attachment_path(...args) { return ditto.ditto_get_complete_attachment_path(...args) }
|
|
339
334
|
function ditto_get_sdk_version(...args) { return ditto.ditto_get_sdk_version(...args) }
|
|
335
|
+
function ditto_identity_config_make_manual_v0(...args) { return ditto.ditto_identity_config_make_manual_v0(...args) }
|
|
336
|
+
function ditto_identity_config_make_offline_playground(...args) { return ditto.ditto_identity_config_make_offline_playground(...args) }
|
|
337
|
+
function ditto_identity_config_make_online_playground(...args) { return ditto.ditto_identity_config_make_online_playground(...args) }
|
|
338
|
+
function ditto_identity_config_make_online_with_authentication(...args) { return ditto.ditto_identity_config_make_online_with_authentication(...args) }
|
|
339
|
+
function ditto_identity_config_make_shared_key(...args) { return ditto.ditto_identity_config_make_shared_key(...args) }
|
|
340
340
|
function ditto_init_sdk_version(...args) { return ditto.ditto_init_sdk_version(...args) }
|
|
341
341
|
function ditto_live_query_register_str_detached(...args) { return ditto.ditto_live_query_register_str_detached(...args) }
|
|
342
342
|
function ditto_live_query_signal_available_next(...args) { return ditto.ditto_live_query_signal_available_next(...args) }
|
|
@@ -370,6 +370,7 @@ function ditto_run_garbage_collection(...args) { return ditto.ditto_run_garbage_
|
|
|
370
370
|
function ditto_set_connect_retry_interval(...args) { return ditto.ditto_set_connect_retry_interval(...args) }
|
|
371
371
|
function ditto_set_device_name(...args) { return ditto.ditto_set_device_name(...args) }
|
|
372
372
|
function ditto_set_sync_group(...args) { return ditto.ditto_set_sync_group(...args) }
|
|
373
|
+
function ditto_shutdown(...args) { return ditto.ditto_shutdown(...args) }
|
|
373
374
|
function ditto_start_http_server(...args) { return ditto.ditto_start_http_server(...args) }
|
|
374
375
|
function ditto_start_tcp_server(...args) { return ditto.ditto_start_tcp_server(...args) }
|
|
375
376
|
function ditto_stop_http_server(...args) { return ditto.ditto_stop_http_server(...args) }
|
|
@@ -537,7 +538,7 @@ async function collectionGet(ditto, collectionName, documentID, readTransaction)
|
|
|
537
538
|
return document;
|
|
538
539
|
}
|
|
539
540
|
/** @internal */
|
|
540
|
-
async function collectionInsertValue(ditto, collectionName, doc_cbor,
|
|
541
|
+
async function collectionInsertValue(ditto, collectionName, doc_cbor, writeStrategy, writeTransaction) {
|
|
541
542
|
ensureInitialized();
|
|
542
543
|
// REFACTOR: add proper error handling.
|
|
543
544
|
const collectionNameX = bytesFromString(collectionName);
|
|
@@ -555,7 +556,7 @@ async function collectionInsertValue(ditto, collectionName, doc_cbor, doc_id, wr
|
|
|
555
556
|
default:
|
|
556
557
|
throw new Error('Invalid write strategy provided');
|
|
557
558
|
}
|
|
558
|
-
const { status_code: errorCode, id } = await ditto_collection_insert_value(ditto, collectionNameX, doc_cbor,
|
|
559
|
+
const { status_code: errorCode, id } = await ditto_collection_insert_value(ditto, collectionNameX, doc_cbor, strategy, null, writeTransaction !== null && writeTransaction !== void 0 ? writeTransaction : null);
|
|
559
560
|
if (errorCode !== 0)
|
|
560
561
|
throw new Error(errorMessage() || `ditto_collection_insert_value() failed with error code: ${errorCode}`);
|
|
561
562
|
return boxCBytesIntoBuffer(id);
|
|
@@ -823,103 +824,94 @@ function log(level, message) {
|
|
|
823
824
|
}
|
|
824
825
|
// ----------------------------------------------------------- AuthClient ------
|
|
825
826
|
/** @internal */
|
|
826
|
-
function
|
|
827
|
+
function dittoIdentityConfigMakeOnlinePlayground(appID, sharedToken, baseURL) {
|
|
827
828
|
ensureInitialized();
|
|
828
|
-
const pathX = bytesFromString(path);
|
|
829
829
|
const appIDX = bytesFromString(appID);
|
|
830
830
|
const sharedTokenX = bytesFromString(sharedToken);
|
|
831
831
|
const baseURLX = bytesFromString(baseURL);
|
|
832
|
-
const { status_code: errorCode,
|
|
832
|
+
const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_online_playground(appIDX, sharedTokenX, baseURLX);
|
|
833
833
|
if (errorCode !== 0)
|
|
834
|
-
throw new Error(errorMessage() || `
|
|
835
|
-
return
|
|
834
|
+
throw new Error(errorMessage() || `ditto_identity_config_make_online_playground() failed with error code: ${errorCode}`);
|
|
835
|
+
return identityConfig;
|
|
836
836
|
}
|
|
837
837
|
/** @internal */
|
|
838
|
-
function
|
|
838
|
+
function dittoIdentityConfigMakeOnlineWithAuthentication(appID, baseURL) {
|
|
839
839
|
ensureInitialized();
|
|
840
|
-
const pathX = bytesFromString(path);
|
|
841
840
|
const appIDX = bytesFromString(appID);
|
|
842
841
|
const baseURLX = bytesFromString(baseURL);
|
|
843
|
-
const { status_code: errorCode,
|
|
842
|
+
const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_online_with_authentication(appIDX, baseURLX);
|
|
844
843
|
if (errorCode !== 0)
|
|
845
|
-
throw new Error(errorMessage() || `
|
|
846
|
-
return
|
|
844
|
+
throw new Error(errorMessage() || `ditto_identity_config_make_online_with_authentication() failed with error code: ${errorCode}`);
|
|
845
|
+
return identityConfig;
|
|
847
846
|
}
|
|
848
847
|
/** @internal */
|
|
849
|
-
function
|
|
848
|
+
function dittoIdentityConfigMakeOfflinePlayground(appId, siteID) {
|
|
850
849
|
ensureInitialized();
|
|
851
|
-
const pathX = bytesFromString(path);
|
|
852
850
|
const appIdX = bytesFromString(appId);
|
|
853
851
|
const siteIDX = Number(siteID);
|
|
854
|
-
const { status_code: errorCode,
|
|
852
|
+
const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_offline_playground(appIdX, siteIDX);
|
|
855
853
|
if (errorCode !== 0)
|
|
856
|
-
throw new Error(errorMessage() || `
|
|
857
|
-
return
|
|
854
|
+
throw new Error(errorMessage() || `ditto_identity_config_make_offline_playground() failed with error code: ${errorCode}`);
|
|
855
|
+
return identityConfig;
|
|
858
856
|
}
|
|
859
857
|
/** @internal */
|
|
860
|
-
function
|
|
858
|
+
function dittoIdentityConfigMakeSharedKey(appId, sharedKey, siteID) {
|
|
861
859
|
ensureInitialized();
|
|
862
|
-
const pathX = bytesFromString(path);
|
|
863
860
|
const appIdX = bytesFromString(appId);
|
|
864
861
|
const sharedKeyX = bytesFromString(sharedKey);
|
|
865
862
|
const siteIDX = Number(siteID);
|
|
866
|
-
const { status_code: errorCode,
|
|
863
|
+
const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_shared_key(appIdX, sharedKeyX, siteIDX);
|
|
867
864
|
if (errorCode !== 0)
|
|
868
|
-
throw new Error(errorMessage() || `
|
|
869
|
-
return
|
|
865
|
+
throw new Error(errorMessage() || `ditto_identity_config_make_shared_key() failed with error code: ${errorCode}`);
|
|
866
|
+
return identityConfig;
|
|
870
867
|
}
|
|
871
868
|
/** @internal */
|
|
872
|
-
function
|
|
869
|
+
function dittoIdentityConfigMakeManual(configCBORBase64) {
|
|
873
870
|
ensureInitialized();
|
|
874
871
|
const configCBORBase64X = bytesFromString(configCBORBase64);
|
|
875
|
-
const { status_code: errorCode,
|
|
872
|
+
const { status_code: errorCode, identity_config: identityConfig } = ditto_identity_config_make_manual_v0(configCBORBase64X);
|
|
876
873
|
if (errorCode !== 0)
|
|
877
|
-
throw new Error(errorMessage() || `
|
|
878
|
-
return
|
|
874
|
+
throw new Error(errorMessage() || `ditto_identity_config_make_manual_v0() failed with error code: ${errorCode}`);
|
|
875
|
+
return identityConfig;
|
|
879
876
|
}
|
|
880
877
|
/** @internal */
|
|
881
|
-
function dittoAuthClientGetSiteID(
|
|
878
|
+
function dittoAuthClientGetSiteID(ditto) {
|
|
882
879
|
ensureInitialized();
|
|
883
|
-
return ditto_auth_client_get_site_id(
|
|
880
|
+
return ditto_auth_client_get_site_id(ditto);
|
|
884
881
|
}
|
|
885
|
-
|
|
886
|
-
function dittoAuthClientFree(authClient) {
|
|
887
|
-
ensureInitialized();
|
|
888
|
-
return ditto_auth_client_free(authClient);
|
|
889
|
-
}
|
|
890
|
-
function dittoAuthClientIsWebValid(authClient) {
|
|
882
|
+
function dittoAuthClientIsWebValid(ditto) {
|
|
891
883
|
ensureInitialized();
|
|
892
|
-
return ditto_auth_client_is_web_valid(
|
|
884
|
+
return ditto_auth_client_is_web_valid(ditto) !== 0;
|
|
893
885
|
}
|
|
894
|
-
function dittoAuthClientUserID(
|
|
886
|
+
function dittoAuthClientUserID(ditto) {
|
|
895
887
|
ensureInitialized();
|
|
896
|
-
const cStr = ditto_auth_client_user_id(
|
|
888
|
+
const cStr = ditto_auth_client_user_id(ditto);
|
|
897
889
|
return boxCStringIntoString(cStr);
|
|
898
890
|
}
|
|
899
|
-
function dittoAuthClientIsX509Valid(
|
|
891
|
+
function dittoAuthClientIsX509Valid(ditto) {
|
|
900
892
|
ensureInitialized();
|
|
901
|
-
return ditto_auth_client_is_x509_valid(
|
|
893
|
+
return ditto_auth_client_is_x509_valid(ditto) !== 0;
|
|
902
894
|
}
|
|
903
|
-
async function dittoAuthClientLoginWithToken(
|
|
895
|
+
async function dittoAuthClientLoginWithToken(ditto, token, provider) {
|
|
904
896
|
ensureInitialized();
|
|
905
897
|
const tokenBytes = bytesFromString(token);
|
|
906
898
|
const providerBytes = bytesFromString(provider);
|
|
907
|
-
const errorCode = await ditto_auth_client_login_with_token(
|
|
899
|
+
const errorCode = await ditto_auth_client_login_with_token(ditto, tokenBytes, providerBytes);
|
|
908
900
|
if (errorCode !== 0)
|
|
909
901
|
throw new Error(errorMessage() || `Ditto failed to authenticate (error code: ${errorCode}).`);
|
|
910
902
|
}
|
|
911
|
-
async function dittoAuthClientLoginWithUsernameAndPassword(
|
|
903
|
+
async function dittoAuthClientLoginWithUsernameAndPassword(ditto, username, password, provider) {
|
|
912
904
|
ensureInitialized();
|
|
913
905
|
const usernameBytes = bytesFromString(username);
|
|
914
906
|
const passwordBytes = bytesFromString(password);
|
|
915
907
|
const providerBytes = bytesFromString(provider);
|
|
916
|
-
const errorCode = await ditto_auth_client_login_with_credentials(
|
|
908
|
+
const errorCode = await ditto_auth_client_login_with_credentials(ditto, usernameBytes, passwordBytes, providerBytes);
|
|
917
909
|
if (errorCode !== 0)
|
|
918
910
|
throw new Error(errorMessage() || `Ditto failed to authenticate (error code: ${errorCode}).`);
|
|
919
911
|
}
|
|
920
|
-
async function dittoAuthClientLogout(
|
|
912
|
+
async function dittoAuthClientLogout(ditto) {
|
|
921
913
|
ensureInitialized();
|
|
922
|
-
const errorCode = await ditto_auth_client_logout(
|
|
914
|
+
const errorCode = await ditto_auth_client_logout(ditto);
|
|
923
915
|
if (errorCode !== 0)
|
|
924
916
|
throw new Error(errorMessage() || `Ditto failed to logout (error code: ${errorCode}).`);
|
|
925
917
|
}
|
|
@@ -931,9 +923,9 @@ function uninitializedDittoMake(path) {
|
|
|
931
923
|
return uninitialized_ditto_make(pathX);
|
|
932
924
|
}
|
|
933
925
|
/** @internal */
|
|
934
|
-
function dittoMake(uninitializedDitto,
|
|
926
|
+
function dittoMake(uninitializedDitto, identityConfig) {
|
|
935
927
|
ensureInitialized();
|
|
936
|
-
return ditto_make(uninitializedDitto,
|
|
928
|
+
return ditto_make(uninitializedDitto, identityConfig, 'Disabled');
|
|
937
929
|
}
|
|
938
930
|
/** @internal */
|
|
939
931
|
async function dittoGetCollectionNames(self) {
|
|
@@ -958,15 +950,15 @@ function dittoFree(self) {
|
|
|
958
950
|
/** @internal */
|
|
959
951
|
async function dittoRegisterPresenceV1Callback(self, cb) {
|
|
960
952
|
ensureInitialized();
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
953
|
+
ditto_register_presence_v1_callback(self, wrapBackgroundCbForFFI((err) => console.error(`The registered presence callback v1 errored with ${err}`), (cJsonStr) => {
|
|
954
|
+
const jsonStr = refCStringToString(cJsonStr);
|
|
955
|
+
cb(jsonStr);
|
|
956
|
+
}));
|
|
957
|
+
}
|
|
958
|
+
/** @internal */
|
|
959
|
+
async function dittoClearPresenceCallback(self) {
|
|
960
|
+
ensureInitialized();
|
|
961
|
+
await ditto_clear_presence_callback(self);
|
|
970
962
|
}
|
|
971
963
|
/** @internal */
|
|
972
964
|
async function dittoRegisterPresenceV3Callback(self, cb) {
|
|
@@ -982,11 +974,6 @@ async function dittoClearPresenceV3Callback(self) {
|
|
|
982
974
|
ditto_clear_presence_v3_callback(self);
|
|
983
975
|
}
|
|
984
976
|
/** @internal */
|
|
985
|
-
async function dittoClearPresenceCallback(self) {
|
|
986
|
-
ensureInitialized();
|
|
987
|
-
await ditto_clear_presence_callback(self);
|
|
988
|
-
}
|
|
989
|
-
/** @internal */
|
|
990
977
|
function dittoRegisterTransportConditionChangedCallback(self, cb) {
|
|
991
978
|
ensureInitialized();
|
|
992
979
|
if (!cb) {
|
|
@@ -1100,6 +1087,11 @@ function dittoStopTCPServer(dittoPointer) {
|
|
|
1100
1087
|
return ditto_stop_tcp_server(dittoPointer);
|
|
1101
1088
|
}
|
|
1102
1089
|
/** @internal */
|
|
1090
|
+
async function dittoShutdown(dittoPointer) {
|
|
1091
|
+
ensureInitialized();
|
|
1092
|
+
return await ditto_shutdown(dittoPointer);
|
|
1093
|
+
}
|
|
1094
|
+
/** @internal */
|
|
1103
1095
|
function dittoAddMulticastTransport(dittoPointer) {
|
|
1104
1096
|
ensureInitialized();
|
|
1105
1097
|
return ditto_add_multicast_transport(dittoPointer);
|
|
@@ -1154,6 +1146,11 @@ function documentsHashMnemonic(documents) {
|
|
|
1154
1146
|
return boxCStringIntoString(c_string);
|
|
1155
1147
|
}
|
|
1156
1148
|
/** @internal */
|
|
1149
|
+
async function dittoAuthSetLoginProvider(ditto, loginProvider) {
|
|
1150
|
+
ensureInitialized();
|
|
1151
|
+
return await ditto_auth_set_login_provider(ditto, loginProvider);
|
|
1152
|
+
}
|
|
1153
|
+
/** @internal */
|
|
1157
1154
|
function dittoAuthClientMakeLoginProvider(expiringCb,
|
|
1158
1155
|
// Cb may be called in parallel at any point, so let's use an optional error handler
|
|
1159
1156
|
onError) {
|
|
@@ -1161,14 +1158,14 @@ onError) {
|
|
|
1161
1158
|
return ditto_auth_client_make_login_provider(wrapBackgroundCbForFFI(onError, expiringCb));
|
|
1162
1159
|
}
|
|
1163
1160
|
/** @internal */
|
|
1164
|
-
function dittoAuthClientSetValidityListener(
|
|
1161
|
+
function dittoAuthClientSetValidityListener(ditto, validityUpdateCb,
|
|
1165
1162
|
// Cb may be called in parallel at any point, so let's use an optional error handler
|
|
1166
1163
|
onError) {
|
|
1167
1164
|
ensureInitialized();
|
|
1168
1165
|
const validityUpdateRawCb = wrapBackgroundCbForFFI(onError, function (isWebValidInt, isX509ValidInt) {
|
|
1169
1166
|
return validityUpdateCb(isWebValidInt === 1, isX509ValidInt === 1);
|
|
1170
1167
|
});
|
|
1171
|
-
return ditto_auth_client_set_validity_listener(
|
|
1168
|
+
return ditto_auth_client_set_validity_listener(ditto, validityUpdateRawCb);
|
|
1172
1169
|
}
|
|
1173
1170
|
// ---------------------------------------------------------------- Other ------
|
|
1174
1171
|
/** @internal */
|
|
@@ -1442,7 +1439,7 @@ function awdlDestroy(awdl) {
|
|
|
1442
1439
|
|
|
1443
1440
|
// NOTE: this is patched up with the actual build version by Jake task
|
|
1444
1441
|
// build:package and has to be a valid semantic version as defined here: https://semver.org.
|
|
1445
|
-
const fullBuildVersionString = '4.0
|
|
1442
|
+
const fullBuildVersionString = '4.1.0';
|
|
1446
1443
|
|
|
1447
1444
|
//
|
|
1448
1445
|
/**
|
|
@@ -2220,19 +2217,111 @@ function validateDocumentIDCBOR(idCBOR) {
|
|
|
2220
2217
|
const DEBUG_TYPE_NAMES = [];
|
|
2221
2218
|
const DEBUG_ALL_TYPES = false;
|
|
2222
2219
|
/**
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2220
|
+
* A handle serves as a safe wrapper around a pointer to a native object.
|
|
2221
|
+
*
|
|
2222
|
+
* A bridge keeps track of all handles that have been created on its
|
|
2223
|
+
* {@link Bridge.handlesByAddress | `handlesByAddress`} property, which allows
|
|
2224
|
+
* enumerating all objects that are currently managed by the bridge.
|
|
2225
2225
|
*
|
|
2226
2226
|
* @internal */
|
|
2227
|
-
class
|
|
2228
|
-
/**
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2227
|
+
class Handle {
|
|
2228
|
+
/**
|
|
2229
|
+
* Warning: Do not call this constructor directly. Use
|
|
2230
|
+
* {@link Bridge.handleFor | `Bridge.<type>.handleFor()`} instead.
|
|
2231
|
+
*
|
|
2232
|
+
* @internal */
|
|
2233
|
+
constructor(bridge, object) {
|
|
2234
|
+
this.isClosed = false;
|
|
2235
|
+
this.bridge = bridge;
|
|
2236
|
+
this.objectWeakRef = new WeakRef(object);
|
|
2237
|
+
}
|
|
2238
|
+
/** The type of this handle's bridge */
|
|
2239
|
+
get type() {
|
|
2240
|
+
return this.bridge.type;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Returns the pointer associated with this handle.
|
|
2244
|
+
*
|
|
2245
|
+
* @throws {Error} if the object has already been closed, garbage collected,
|
|
2246
|
+
* or unregistered from the bridge.
|
|
2247
|
+
*/
|
|
2248
|
+
deref() {
|
|
2249
|
+
const object = this.objectWeakRef.deref();
|
|
2250
|
+
if (object == null) {
|
|
2251
|
+
throw new Error(`Bridging error: can't get pointer for an object that has been garbage collected.`);
|
|
2252
|
+
}
|
|
2253
|
+
if (this.isClosed) {
|
|
2254
|
+
throw new Error(`Bridging error: can't get pointer for an object that has been closed.`);
|
|
2255
|
+
}
|
|
2256
|
+
// Instead of storing the pointer in the handle, we look it up in the
|
|
2257
|
+
// bridge's pointer map to avoid storing the pointer twice.
|
|
2258
|
+
const pointer = this.bridge.rawPointerFor(object);
|
|
2259
|
+
if (pointer == null) {
|
|
2260
|
+
throw new Error(`Bridging error: ${this.type.name} object is not currently registered in this bridge.`);
|
|
2261
|
+
}
|
|
2262
|
+
return pointer;
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Returns the pointer associated with this handle or `null` if the object
|
|
2266
|
+
* has been closed, garbage collected, or unregistered.
|
|
2267
|
+
*/
|
|
2268
|
+
derefOrNull() {
|
|
2269
|
+
const object = this.objectWeakRef.deref();
|
|
2270
|
+
if (object == null) {
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
if (this.isClosed) {
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
2276
|
+
const pointer = this.bridge.rawPointerFor(object);
|
|
2277
|
+
return pointer !== null && pointer !== void 0 ? pointer : null;
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Returns the object associated with this handle.
|
|
2281
|
+
*
|
|
2282
|
+
* @throws {Error} if the object has already been garbage collected,
|
|
2283
|
+
* closed or unregistered.
|
|
2284
|
+
*/
|
|
2285
|
+
object() {
|
|
2286
|
+
const object = this.objectWeakRef.deref();
|
|
2287
|
+
if (object == null) {
|
|
2288
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has already been garbage collected.`);
|
|
2289
|
+
}
|
|
2290
|
+
if (this.isClosed) {
|
|
2291
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has already been closed.`);
|
|
2292
|
+
}
|
|
2293
|
+
return object;
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Returns the object associated with this handle or `null` if the object
|
|
2297
|
+
* has already been garbage collected.
|
|
2298
|
+
*/
|
|
2299
|
+
objectOrNull() {
|
|
2300
|
+
var _b;
|
|
2301
|
+
if (this.isClosed) {
|
|
2302
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has already been closed.`);
|
|
2303
|
+
}
|
|
2304
|
+
return (_b = this.objectWeakRef.deref()) !== null && _b !== void 0 ? _b : null;
|
|
2233
2305
|
}
|
|
2234
2306
|
toString() {
|
|
2235
|
-
|
|
2307
|
+
const pointer = this.derefOrNull();
|
|
2308
|
+
return `{ Handle | type: ${this.bridge.type}, object: ${this.objectWeakRef.deref()}, FFI address: ${pointer === null || pointer === void 0 ? void 0 : pointer.addr}, FFI type: ${pointer === null || pointer === void 0 ? void 0 : pointer.type} }`;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Use this for passing arrays of pointers to the FFI.
|
|
2313
|
+
*/
|
|
2314
|
+
// REFACTOR: Use an iterator instead of an array to deref handles lazily.
|
|
2315
|
+
class Handles {
|
|
2316
|
+
/**
|
|
2317
|
+
* @throws {Error} if any of the objects are not registered in the bridge.
|
|
2318
|
+
* @throws {Error} if any of the objects have already been garbage collected.
|
|
2319
|
+
*/
|
|
2320
|
+
constructor(bridge, objects) {
|
|
2321
|
+
this.handles = objects.map((object) => bridge.handleFor(object));
|
|
2322
|
+
}
|
|
2323
|
+
deref() {
|
|
2324
|
+
return this.handles.map((handle) => handle.deref());
|
|
2236
2325
|
}
|
|
2237
2326
|
}
|
|
2238
2327
|
/**
|
|
@@ -2254,9 +2343,9 @@ class Meta {
|
|
|
2254
2343
|
* - {@link StaticTCPClient}: `Bridge.staticTCPClient`
|
|
2255
2344
|
* - {@link WebsocketClient}: `Bridge.websocketClient`
|
|
2256
2345
|
*
|
|
2257
|
-
* Use `Bridge.<type>.
|
|
2258
|
-
* `Bridge.<type>.bridge()` to get or create the matching
|
|
2259
|
-
* address.
|
|
2346
|
+
* Use `Bridge.<type>.handleFor()` to obtain a handle, which is a wrapper around
|
|
2347
|
+
* the raw pointer, and `Bridge.<type>.bridge()` to get or create the matching
|
|
2348
|
+
* object for a memory address.
|
|
2260
2349
|
*
|
|
2261
2350
|
* @internal */
|
|
2262
2351
|
class Bridge {
|
|
@@ -2279,12 +2368,12 @@ class Bridge {
|
|
|
2279
2368
|
/**
|
|
2280
2369
|
* Look up a pointer given its object.
|
|
2281
2370
|
*
|
|
2282
|
-
* As `WeakMap` does not allow for enumeration, use `this.
|
|
2371
|
+
* As `WeakMap` does not allow for enumeration, use `this.handlesByAddress` to
|
|
2283
2372
|
* iterate over all pointers.
|
|
2284
2373
|
*/
|
|
2285
2374
|
this.pointerByObject = new WeakMap();
|
|
2286
2375
|
this.release = release;
|
|
2287
|
-
this.
|
|
2376
|
+
this.handlesByAddress = {};
|
|
2288
2377
|
this.finalizationRegistry = new FinalizationRegistry(this.finalize.bind(this));
|
|
2289
2378
|
Bridge.all.push(new WeakRef(this));
|
|
2290
2379
|
}
|
|
@@ -2314,20 +2403,32 @@ class Bridge {
|
|
|
2314
2403
|
this.internalType = value;
|
|
2315
2404
|
}
|
|
2316
2405
|
/**
|
|
2317
|
-
* Returns the
|
|
2406
|
+
* Returns the handle for a bridged object.
|
|
2318
2407
|
*
|
|
2319
|
-
*
|
|
2320
|
-
*
|
|
2321
|
-
*
|
|
2408
|
+
* Use `handle.deref()` to get the pointer for the object at the time of use.
|
|
2409
|
+
*
|
|
2410
|
+
* @throws {Error} if the object is not registered.
|
|
2322
2411
|
*
|
|
2323
2412
|
* @internal
|
|
2324
2413
|
*/
|
|
2325
|
-
|
|
2326
|
-
const
|
|
2327
|
-
if (
|
|
2414
|
+
handleFor(object) {
|
|
2415
|
+
const pointer = this.pointerByObject.get(object);
|
|
2416
|
+
if (pointer == null) {
|
|
2328
2417
|
throw new Error(`Bridging error: ${this.type.name} object is not currently registered in this bridge.`);
|
|
2329
2418
|
}
|
|
2330
|
-
|
|
2419
|
+
const handle = this.handlesByAddress[pointer.addr];
|
|
2420
|
+
if (handle == null) {
|
|
2421
|
+
throw new Error(`Internal inconsistency, found a pointer for an object whose handle is undefined: ${pointer}`);
|
|
2422
|
+
}
|
|
2423
|
+
return handle;
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Returns a `Handles` instance for an array of objects.
|
|
2427
|
+
*
|
|
2428
|
+
* @internal
|
|
2429
|
+
*/
|
|
2430
|
+
handlesFor(objects) {
|
|
2431
|
+
return new Handles(this, objects);
|
|
2331
2432
|
}
|
|
2332
2433
|
/**
|
|
2333
2434
|
* Convenience method, returns the object for the FFI `pointer` if registered,
|
|
@@ -2337,15 +2438,14 @@ class Bridge {
|
|
|
2337
2438
|
* @internal
|
|
2338
2439
|
*/
|
|
2339
2440
|
objectFor(pointer) {
|
|
2340
|
-
const
|
|
2341
|
-
if (!
|
|
2441
|
+
const handle = this.handlesByAddress[pointer.addr];
|
|
2442
|
+
if (!handle)
|
|
2342
2443
|
return undefined;
|
|
2343
|
-
if (
|
|
2344
|
-
throw new Error(`Can't return object for pointer, pointer is associated with an object of type ${
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
return object;
|
|
2444
|
+
if (handle.type !== this.type)
|
|
2445
|
+
throw new Error(`Can't return object for pointer, pointer is associated with an object of type ${handle.type} but this bridge is configured for ${this.type}`);
|
|
2446
|
+
// This throws an error if the object has been garbage collected but the
|
|
2447
|
+
// finalizer has not been called yet.
|
|
2448
|
+
return handle.object();
|
|
2349
2449
|
}
|
|
2350
2450
|
/**
|
|
2351
2451
|
* Returns the object for the FFI `pointer` if registered. Otherwise, calls
|
|
@@ -2356,6 +2456,7 @@ class Bridge {
|
|
|
2356
2456
|
*
|
|
2357
2457
|
* @param pointer reference to the FFi instance for the object
|
|
2358
2458
|
* @param objectOrCreate can either be the JS object, or a function that returns the instance when called. If undefined, an object is created based on the Bridge type.
|
|
2459
|
+
* @throws {Error} if `objectOrCreate` is a function that returns an object that is not an instance of the bridge's type.
|
|
2359
2460
|
* @internal
|
|
2360
2461
|
*/
|
|
2361
2462
|
bridge(pointer, objectOrCreate) {
|
|
@@ -2393,26 +2494,49 @@ class Bridge {
|
|
|
2393
2494
|
* @private */
|
|
2394
2495
|
register(object, pointer) {
|
|
2395
2496
|
const objectType = object.constructor;
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
throw new Error(`Can't register, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`);
|
|
2497
|
+
if (objectType !== this.type)
|
|
2498
|
+
throw new Error(`Can't register, bridge is configured for type ${this.type.name} but passed in object is of type ${objectType.name}`);
|
|
2399
2499
|
const existingPointer = this.pointerByObject.get(object);
|
|
2400
|
-
const
|
|
2401
|
-
// Check that both pointer and
|
|
2402
|
-
if (existingPointer != null &&
|
|
2403
|
-
throw new Error(`Can't register, an object for the passed in pointer has previously been registered: ${
|
|
2404
|
-
if (existingPointer != null &&
|
|
2405
|
-
throw new Error(`Internal inconsistency, trying to register an object which has an associated pointer but no
|
|
2406
|
-
if (existingPointer == null &&
|
|
2407
|
-
throw new Error(`Internal inconsistency, trying to register an object which has a
|
|
2408
|
-
const
|
|
2409
|
-
this.
|
|
2500
|
+
const existingHandle = this.handlesByAddress[pointer.addr];
|
|
2501
|
+
// Check that both pointer and handle are undefined at this point.
|
|
2502
|
+
if (existingPointer != null && existingHandle != null)
|
|
2503
|
+
throw new Error(`Can't register, an object for the passed in pointer has previously been registered: ${existingHandle.object()}`);
|
|
2504
|
+
if (existingPointer != null && existingHandle == null)
|
|
2505
|
+
throw new Error(`Internal inconsistency, trying to register an object which has an associated pointer but no handle entry: ${objectType.name} at ${existingPointer.type} ${existingPointer.addr}`);
|
|
2506
|
+
if (existingPointer == null && existingHandle != null)
|
|
2507
|
+
throw new Error(`Internal inconsistency, trying to register an object which has a handle entry but no associated pointer: ${objectType.name} ${object}`);
|
|
2508
|
+
const handle = new Handle(this, object);
|
|
2509
|
+
this.handlesByAddress[pointer.addr] = handle;
|
|
2410
2510
|
this.pointerByObject.set(object, pointer);
|
|
2411
2511
|
this.finalizationRegistry.register(object, pointer, object);
|
|
2412
2512
|
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
2413
|
-
console.log(`[VERBOSE] Bridge REGISTERED a new instance of ${this.type.name}, current count: ${Object.keys(this.
|
|
2513
|
+
console.log(`[VERBOSE] Bridge REGISTERED a new instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
|
|
2414
2514
|
}
|
|
2415
2515
|
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Marks the object as closed while still keeping it registered in the
|
|
2518
|
+
* {@link FinalizationRegistry}, such that it still gets finalized by GC.
|
|
2519
|
+
* Otherwise, the behavior is exactly as if the object would've been
|
|
2520
|
+
* unregistered.
|
|
2521
|
+
*
|
|
2522
|
+
* @internal */
|
|
2523
|
+
markAsClosed(object) {
|
|
2524
|
+
const objectType = object.constructor;
|
|
2525
|
+
const bridgeType = this.type;
|
|
2526
|
+
if (objectType !== bridgeType)
|
|
2527
|
+
throw new Error(`Can't unregister, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`);
|
|
2528
|
+
const pointer = this.pointerByObject.get(object);
|
|
2529
|
+
if (pointer == null)
|
|
2530
|
+
throw new Error(`Can't unregister, object that has not been registered before: ${object}`);
|
|
2531
|
+
const handle = this.handlesByAddress[pointer.addr];
|
|
2532
|
+
if (handle == null)
|
|
2533
|
+
throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no handle: ${object}`);
|
|
2534
|
+
if (handle.type !== bridgeType)
|
|
2535
|
+
throw new Error(`Internal inconsistency, trying to unregister an object that has a handle with a different type than that of the bridge: ${handle}`);
|
|
2536
|
+
if (handle.objectOrNull() !== object)
|
|
2537
|
+
throw new Error(`Internal inconsistency, trying to unregister an object whose associated handle holds a different object: ${handle}`);
|
|
2538
|
+
handle['isClosed'] = true;
|
|
2539
|
+
}
|
|
2416
2540
|
/**
|
|
2417
2541
|
* Removes an instance from this bridge's {@link FinalizationRegistry}.
|
|
2418
2542
|
*
|
|
@@ -2429,20 +2553,18 @@ class Bridge {
|
|
|
2429
2553
|
const pointer = this.pointerByObject.get(object);
|
|
2430
2554
|
if (pointer == null)
|
|
2431
2555
|
throw new Error(`Can't unregister, object that has not been registered before: ${object}`);
|
|
2432
|
-
const
|
|
2433
|
-
if (
|
|
2434
|
-
throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no
|
|
2435
|
-
if (
|
|
2436
|
-
throw new Error(`Internal inconsistency, trying to unregister an object that has a
|
|
2437
|
-
if (
|
|
2438
|
-
throw new Error(`Internal inconsistency, trying to unregister an object
|
|
2439
|
-
if (meta.object.deref() !== object)
|
|
2440
|
-
throw new Error(`Internal inconsistency, trying to unregister an object that has a meta entry holding a different object: ${meta}`);
|
|
2556
|
+
const handle = this.handlesByAddress[pointer.addr];
|
|
2557
|
+
if (handle == null)
|
|
2558
|
+
throw new Error(`Internal inconsistency, trying to unregister an object which has an associated pointer but no handle: ${object}`);
|
|
2559
|
+
if (handle.type !== bridgeType)
|
|
2560
|
+
throw new Error(`Internal inconsistency, trying to unregister an object that has a handle with a different type than that of the bridge: ${handle}`);
|
|
2561
|
+
if (handle.objectOrNull() !== object)
|
|
2562
|
+
throw new Error(`Internal inconsistency, trying to unregister an object whose associated handle holds a different object: ${handle}`);
|
|
2441
2563
|
this.finalizationRegistry.unregister(object);
|
|
2442
|
-
delete this.
|
|
2564
|
+
delete this.handlesByAddress[pointer.addr];
|
|
2443
2565
|
this.pointerByObject.delete(object);
|
|
2444
2566
|
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
2445
|
-
console.log(`[VERBOSE] Bridge UNREGISTERED an instance of ${this.type.name}, current count: ${Object.keys(this.
|
|
2567
|
+
console.log(`[VERBOSE] Bridge UNREGISTERED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
|
|
2446
2568
|
}
|
|
2447
2569
|
}
|
|
2448
2570
|
/** @internal */
|
|
@@ -2450,8 +2572,8 @@ class Bridge {
|
|
|
2450
2572
|
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
2451
2573
|
console.log(`[VERBOSE] Unregistering ALL bridged instances of type ${this.type.name}.`);
|
|
2452
2574
|
}
|
|
2453
|
-
for (const
|
|
2454
|
-
const object =
|
|
2575
|
+
for (const handle of Object.values(this.handlesByAddress)) {
|
|
2576
|
+
const object = handle.object();
|
|
2455
2577
|
if (object) {
|
|
2456
2578
|
this.unregister(object);
|
|
2457
2579
|
}
|
|
@@ -2459,16 +2581,20 @@ class Bridge {
|
|
|
2459
2581
|
}
|
|
2460
2582
|
/** @internal */
|
|
2461
2583
|
get count() {
|
|
2462
|
-
return Object.keys(this.
|
|
2584
|
+
return Object.keys(this.handlesByAddress).length;
|
|
2585
|
+
}
|
|
2586
|
+
/* @internal */
|
|
2587
|
+
rawPointerFor(object) {
|
|
2588
|
+
return this.pointerByObject.get(object);
|
|
2463
2589
|
}
|
|
2464
2590
|
finalize(pointer) {
|
|
2465
|
-
const
|
|
2466
|
-
if (!
|
|
2467
|
-
throw new Error(`Internal inconsistency, tried to finalize an instance for a pointer, that has no
|
|
2468
|
-
delete this.
|
|
2469
|
-
this.release(pointer);
|
|
2591
|
+
const handle = this.handlesByAddress[pointer.addr];
|
|
2592
|
+
if (!handle)
|
|
2593
|
+
throw new Error(`Internal inconsistency, tried to finalize an instance for a pointer, that has no handle: ${pointer}`);
|
|
2594
|
+
delete this.handlesByAddress[pointer.addr];
|
|
2595
|
+
this.release(pointer, handle);
|
|
2470
2596
|
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
2471
|
-
console.log(`[VERBOSE] Bridge FINALIZED an instance of ${this.type.name}, current count: ${Object.keys(this.
|
|
2597
|
+
console.log(`[VERBOSE] Bridge FINALIZED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`);
|
|
2472
2598
|
}
|
|
2473
2599
|
}
|
|
2474
2600
|
}
|
|
@@ -2490,11 +2616,14 @@ Bridge.staticTCPClient = new Bridge(staticTCPClientFreeHandle);
|
|
|
2490
2616
|
/** @internal */
|
|
2491
2617
|
Bridge.websocketClient = new Bridge(websocketClientFreeHandle);
|
|
2492
2618
|
/** @internal */
|
|
2493
|
-
Bridge.ditto = new Bridge((dittoPointer) => {
|
|
2619
|
+
Bridge.ditto = new Bridge(async (dittoPointer, handle) => {
|
|
2494
2620
|
// HACK: quick and dirty, clear all presence callbacks. This covers presence
|
|
2495
2621
|
// v1 and v2 callbacks. v3 should be cleared properly by the `Presence`
|
|
2496
2622
|
// class itself.
|
|
2497
2623
|
dittoClearPresenceCallback(dittoPointer);
|
|
2624
|
+
if (!handle.isClosed) {
|
|
2625
|
+
await dittoShutdown(dittoPointer);
|
|
2626
|
+
}
|
|
2498
2627
|
dittoFree(dittoPointer);
|
|
2499
2628
|
});
|
|
2500
2629
|
|
|
@@ -2551,13 +2680,16 @@ class Attachment {
|
|
|
2551
2680
|
* Returns the attachment's data.
|
|
2552
2681
|
*/
|
|
2553
2682
|
getData() {
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2683
|
+
const ditto = this.ditto;
|
|
2684
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
2685
|
+
return this.ditto.deferCloseAsync(async () => {
|
|
2686
|
+
{
|
|
2687
|
+
const attachmentHandle = Bridge.attachment.handleFor(this);
|
|
2688
|
+
const attachmentPath = dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref());
|
|
2689
|
+
const fs = require('fs/promises');
|
|
2690
|
+
return await fs.readFile(attachmentPath);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2561
2693
|
}
|
|
2562
2694
|
/**
|
|
2563
2695
|
* Copies the attachment to the specified file path. Node-only,
|
|
@@ -2566,15 +2698,18 @@ class Attachment {
|
|
|
2566
2698
|
* @param path The path that the attachment should be copied to.
|
|
2567
2699
|
*/
|
|
2568
2700
|
copyToPath(path) {
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2701
|
+
const ditto = this.ditto;
|
|
2702
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
2703
|
+
return this.ditto.deferCloseAsync(async () => {
|
|
2704
|
+
{
|
|
2705
|
+
const attachmentHandle = Bridge.attachment.handleFor(this);
|
|
2706
|
+
const attachmentPath = dittoGetCompleteAttachmentPath(dittoHandle.deref(), attachmentHandle.deref());
|
|
2707
|
+
const fs = require('fs/promises');
|
|
2708
|
+
// If the file already exists, we fail. This is the same behavior as
|
|
2709
|
+
// for the Swift/ObjC SDK.
|
|
2710
|
+
return await fs.copyFile(attachmentPath, path, fs.COPYFILE_EXCL);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2578
2713
|
}
|
|
2579
2714
|
/** @internal */
|
|
2580
2715
|
constructor(ditto, token) {
|
|
@@ -2719,7 +2854,10 @@ function validateQuery(query, options = {}) {
|
|
|
2719
2854
|
return validatedQuery;
|
|
2720
2855
|
}
|
|
2721
2856
|
// -------------------------------------------------------------- Helpers ------
|
|
2722
|
-
/**
|
|
2857
|
+
/**
|
|
2858
|
+
* Generate a random hex-encoded token using the WebCrypto API.
|
|
2859
|
+
*
|
|
2860
|
+
* @internal */
|
|
2723
2861
|
function generateEphemeralToken() {
|
|
2724
2862
|
let webcrypto = undefined;
|
|
2725
2863
|
{
|
|
@@ -2738,9 +2876,9 @@ function sleep(milliseconds) {
|
|
|
2738
2876
|
});
|
|
2739
2877
|
}
|
|
2740
2878
|
/** @internal use this to asyncify chunks of code. */
|
|
2741
|
-
async function step(
|
|
2879
|
+
async function step(closure) {
|
|
2742
2880
|
await sleep(0);
|
|
2743
|
-
return
|
|
2881
|
+
return closure();
|
|
2744
2882
|
}
|
|
2745
2883
|
/** @internal */
|
|
2746
2884
|
// WORKAROUND: the corresponding FFI function(s) is not async at the
|
|
@@ -2999,8 +3137,8 @@ class DocumentPath {
|
|
|
2999
3137
|
underlyingValueForPathType(pathType) {
|
|
3000
3138
|
const path = this.path;
|
|
3001
3139
|
const document = this.document;
|
|
3002
|
-
const
|
|
3003
|
-
const cborPathResult = documentGetCBORWithPathType(
|
|
3140
|
+
const documentHandle = Bridge.document.handleFor(document);
|
|
3141
|
+
const cborPathResult = documentGetCBORWithPathType(documentHandle.deref(), path, pathType);
|
|
3004
3142
|
return cborPathResult.cbor !== null ? CBOR.decode(cborPathResult.cbor) : undefined;
|
|
3005
3143
|
}
|
|
3006
3144
|
}
|
|
@@ -3103,8 +3241,8 @@ class MutableDocumentPath {
|
|
|
3103
3241
|
underlyingValueForPathType(pathType) {
|
|
3104
3242
|
const path = this.path;
|
|
3105
3243
|
const document = this.mutableDocument;
|
|
3106
|
-
const
|
|
3107
|
-
const cborPathResult = documentGetCBORWithPathType(
|
|
3244
|
+
const documentHandle = Bridge.mutableDocument.handleFor(document);
|
|
3245
|
+
const cborPathResult = documentGetCBORWithPathType(documentHandle.deref(), path, pathType);
|
|
3108
3246
|
return cborPathResult.cbor !== null ? CBOR.decode(cborPathResult.cbor) : undefined;
|
|
3109
3247
|
}
|
|
3110
3248
|
/** @internal */
|
|
@@ -3112,21 +3250,21 @@ class MutableDocumentPath {
|
|
|
3112
3250
|
// REFACTOR: this body should probably move to
|
|
3113
3251
|
// `MutableCounter.incrementBy()`. Keeping as-is for now until we implement
|
|
3114
3252
|
// more explicit CRDT types.
|
|
3115
|
-
const
|
|
3116
|
-
documentIncrementCounter(
|
|
3253
|
+
const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
|
|
3254
|
+
documentIncrementCounter(documentHandle.deref(), this.path, amount);
|
|
3117
3255
|
const updateResult = UpdateResult.incremented(this.mutableDocument.id, this.path, amount);
|
|
3118
3256
|
this.recordUpdateResult(updateResult);
|
|
3119
3257
|
}
|
|
3120
3258
|
/** @internal */
|
|
3121
3259
|
'@ditto.set'(value, isDefault) {
|
|
3122
|
-
const
|
|
3260
|
+
const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
|
|
3123
3261
|
const valueJSON = desugarJSObject(value, false);
|
|
3124
3262
|
const valueCBOR = CBOR.encode(valueJSON);
|
|
3125
3263
|
if (isDefault) {
|
|
3126
|
-
documentSetCBORWithTimestamp(
|
|
3264
|
+
documentSetCBORWithTimestamp(documentHandle.deref(), this.path, valueCBOR, 0);
|
|
3127
3265
|
}
|
|
3128
3266
|
else {
|
|
3129
|
-
documentSetCBOR(
|
|
3267
|
+
documentSetCBOR(documentHandle.deref(), this.path, valueCBOR);
|
|
3130
3268
|
}
|
|
3131
3269
|
// HACK: we need a copy of value because the original value can be mutated
|
|
3132
3270
|
// later on and an update result needs the state of the value at this
|
|
@@ -3139,8 +3277,8 @@ class MutableDocumentPath {
|
|
|
3139
3277
|
}
|
|
3140
3278
|
/** @internal */
|
|
3141
3279
|
'@ditto.remove'() {
|
|
3142
|
-
const
|
|
3143
|
-
documentRemove(
|
|
3280
|
+
const documentHandle = Bridge.mutableDocument.handleFor(this.mutableDocument);
|
|
3281
|
+
documentRemove(documentHandle.deref(), this.path);
|
|
3144
3282
|
this.updateInMemory((container, lastPathComponent) => {
|
|
3145
3283
|
if (Array.isArray(container) && typeof lastPathComponent === 'number') {
|
|
3146
3284
|
container.splice(lastPathComponent, 1);
|
|
@@ -3178,8 +3316,8 @@ class Document {
|
|
|
3178
3316
|
*/
|
|
3179
3317
|
static hash(documentOrMany) {
|
|
3180
3318
|
const documents = documentsFrom(documentOrMany);
|
|
3181
|
-
const
|
|
3182
|
-
return documentsHash(
|
|
3319
|
+
const documentHandles = documents.map((doc) => Bridge.document.handleFor(doc));
|
|
3320
|
+
return documentsHash(documentHandles.map((handle) => handle.deref()));
|
|
3183
3321
|
}
|
|
3184
3322
|
/**
|
|
3185
3323
|
* Returns a pattern of words that together create a mnemonic, which
|
|
@@ -3187,8 +3325,8 @@ class Document {
|
|
|
3187
3325
|
*/
|
|
3188
3326
|
static hashMnemonic(documentOrMany) {
|
|
3189
3327
|
const documents = documentsFrom(documentOrMany);
|
|
3190
|
-
const
|
|
3191
|
-
return documentsHashMnemonic(
|
|
3328
|
+
const documentHandles = Bridge.document.handlesFor(documents);
|
|
3329
|
+
return documentsHashMnemonic(documentHandles.deref());
|
|
3192
3330
|
}
|
|
3193
3331
|
/**
|
|
3194
3332
|
* Returns the document ID.
|
|
@@ -3196,8 +3334,8 @@ class Document {
|
|
|
3196
3334
|
get id() {
|
|
3197
3335
|
let id = this['@ditto.id'];
|
|
3198
3336
|
if (typeof id === 'undefined') {
|
|
3199
|
-
const
|
|
3200
|
-
const documentIDCBOR = documentID(
|
|
3337
|
+
const documentHandle = Bridge.document.handleFor(this);
|
|
3338
|
+
const documentIDCBOR = documentID(documentHandle.deref());
|
|
3201
3339
|
id = new DocumentID(documentIDCBOR, true);
|
|
3202
3340
|
this['@ditto.id'] = id;
|
|
3203
3341
|
}
|
|
@@ -3233,8 +3371,8 @@ class Document {
|
|
|
3233
3371
|
// TEMPORARY: helpers to deal with non-canonical IDs.
|
|
3234
3372
|
/** @internal */
|
|
3235
3373
|
static idCBOR(document) {
|
|
3236
|
-
const
|
|
3237
|
-
return documentID(
|
|
3374
|
+
const documentHandle = Bridge.document.handleFor(document);
|
|
3375
|
+
return documentID(documentHandle.deref());
|
|
3238
3376
|
}
|
|
3239
3377
|
/** @internal */
|
|
3240
3378
|
static canonicalizedIDCBOR(idCBOR) {
|
|
@@ -3269,8 +3407,8 @@ class MutableDocument {
|
|
|
3269
3407
|
get id() {
|
|
3270
3408
|
let id = this['@ditto.id'];
|
|
3271
3409
|
if (typeof id === 'undefined') {
|
|
3272
|
-
const
|
|
3273
|
-
const documentIDCBOR = documentID(
|
|
3410
|
+
const documentHandle = Bridge.mutableDocument.handleFor(this);
|
|
3411
|
+
const documentIDCBOR = documentID(documentHandle.deref());
|
|
3274
3412
|
id = new DocumentID(documentIDCBOR, true);
|
|
3275
3413
|
this['@ditto.id'] = id;
|
|
3276
3414
|
}
|
|
@@ -3302,8 +3440,8 @@ class MutableDocument {
|
|
|
3302
3440
|
// TEMPORARY: helpers to deal with non-canonical IDs.
|
|
3303
3441
|
/** @internal */
|
|
3304
3442
|
static idCBOR(mutableDocument) {
|
|
3305
|
-
const
|
|
3306
|
-
return documentID(
|
|
3443
|
+
const documentHandle = Bridge.mutableDocument.handleFor(mutableDocument);
|
|
3444
|
+
return documentID(documentHandle.deref());
|
|
3307
3445
|
}
|
|
3308
3446
|
}
|
|
3309
3447
|
/** @internal */
|
|
@@ -3454,13 +3592,16 @@ class BasePendingCursorOperation {
|
|
|
3454
3592
|
* the query generated by the preceding function chaining.
|
|
3455
3593
|
*/
|
|
3456
3594
|
async exec() {
|
|
3457
|
-
const
|
|
3458
|
-
const
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3595
|
+
const ditto = this.collection.store.ditto;
|
|
3596
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3597
|
+
return ditto.deferCloseAsync(async () => {
|
|
3598
|
+
const query = this.query;
|
|
3599
|
+
const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
3600
|
+
return await collectionExecQueryStr(dittoHandle.deref(), this.collection.name, null, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
|
|
3601
|
+
});
|
|
3602
|
+
return documentPointers.map((documentPointer) => {
|
|
3603
|
+
return Bridge.document.bridge(documentPointer);
|
|
3604
|
+
});
|
|
3464
3605
|
});
|
|
3465
3606
|
}
|
|
3466
3607
|
// ----------------------------------------------------------- Internal ------
|
|
@@ -3479,37 +3620,40 @@ class BasePendingCursorOperation {
|
|
|
3479
3620
|
* @internal
|
|
3480
3621
|
*/
|
|
3481
3622
|
async updateWithTransaction(closure, writeTransactionX) {
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
const
|
|
3494
|
-
const
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3623
|
+
const ditto = this.collection.store.ditto;
|
|
3624
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3625
|
+
return ditto.deferCloseAsync(async () => {
|
|
3626
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
3627
|
+
const query = this.query;
|
|
3628
|
+
const documentsX = await collectionExecQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
|
|
3629
|
+
const mutableDocuments = documentsX.map((documentX) => {
|
|
3630
|
+
return Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
|
|
3631
|
+
});
|
|
3632
|
+
closure(mutableDocuments);
|
|
3633
|
+
const updateResultsDocumentIDs = [];
|
|
3634
|
+
const updateResultsByDocumentIDString = {};
|
|
3635
|
+
for (const mutableDocument of mutableDocuments) {
|
|
3636
|
+
const documentID = mutableDocument.id;
|
|
3637
|
+
const documentIDString = documentID.toString();
|
|
3638
|
+
const updateResults = mutableDocument['@ditto.updateResults'];
|
|
3639
|
+
if (updateResultsByDocumentIDString[documentIDString]) {
|
|
3640
|
+
// HACK: in theory, 2 different document IDs can have the exact
|
|
3641
|
+
// same string representation at the time of this writing. There is
|
|
3642
|
+
// already a REFACTOR comment in `UpdateResultsMap` to implement a
|
|
3643
|
+
// proper and correct data structure for holding these update results.
|
|
3644
|
+
// Until then, we'll leave this check here to see how often we hit
|
|
3645
|
+
// this edge case.
|
|
3646
|
+
throw new Error(`Internal inconsistency, update results for document ID as string already exist: ${documentIDString}`);
|
|
3647
|
+
}
|
|
3648
|
+
updateResultsDocumentIDs.push(documentID);
|
|
3649
|
+
updateResultsByDocumentIDString[documentIDString] = updateResults;
|
|
3650
|
+
Bridge.mutableDocument.unregister(mutableDocument);
|
|
3504
3651
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
// is transferred to Rust at this point.
|
|
3511
|
-
await collectionUpdateMultiple(dittoX, this.collection.name, writeTransactionX, documentsX);
|
|
3512
|
-
return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString);
|
|
3652
|
+
// NOTE: ownership of documentsX (and contained documents)
|
|
3653
|
+
// is transferred to Rust at this point.
|
|
3654
|
+
await collectionUpdateMultiple(dittoHandle.deref(), this.collection.name, writeTransactionX, documentsX);
|
|
3655
|
+
return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString);
|
|
3656
|
+
});
|
|
3513
3657
|
});
|
|
3514
3658
|
}
|
|
3515
3659
|
/** @internal */
|
|
@@ -3551,15 +3695,18 @@ class BasePendingIDSpecificOperation {
|
|
|
3551
3695
|
* not found.
|
|
3552
3696
|
*/
|
|
3553
3697
|
async exec() {
|
|
3554
|
-
const
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
document =
|
|
3561
|
-
|
|
3562
|
-
|
|
3698
|
+
const ditto = this.collection.store.ditto;
|
|
3699
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3700
|
+
return ditto.deferCloseAsync(async () => {
|
|
3701
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
3702
|
+
const readTransactionX = await readTransaction(dittoHandle.deref());
|
|
3703
|
+
const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
|
|
3704
|
+
let document = undefined;
|
|
3705
|
+
if (documentX)
|
|
3706
|
+
document = Bridge.document.bridge(documentX);
|
|
3707
|
+
readTransactionFree(readTransactionX);
|
|
3708
|
+
return document;
|
|
3709
|
+
});
|
|
3563
3710
|
});
|
|
3564
3711
|
}
|
|
3565
3712
|
// ----------------------------------------------------------- Internal ------
|
|
@@ -3591,6 +3738,7 @@ class ObserverManager {
|
|
|
3591
3738
|
const process = (_d = options.process) !== null && _d !== void 0 ? _d : null;
|
|
3592
3739
|
this.id = id;
|
|
3593
3740
|
this.keepAlive = keepAlive;
|
|
3741
|
+
this.isClosed = false;
|
|
3594
3742
|
this.isRegistered = false;
|
|
3595
3743
|
this.callbacksByToken = {};
|
|
3596
3744
|
if (register !== null) {
|
|
@@ -3606,6 +3754,11 @@ class ObserverManager {
|
|
|
3606
3754
|
/** @internal */
|
|
3607
3755
|
addObserver(callback) {
|
|
3608
3756
|
var _a;
|
|
3757
|
+
if (this.isClosed) {
|
|
3758
|
+
// REFACTOR: throw a catchable error here, such that calling code
|
|
3759
|
+
// can be more specific when forwarding it to the user.
|
|
3760
|
+
throw new Error(`Internal inconsistency, can't add '${this.id}' observer, observer mananger close()-ed.`);
|
|
3761
|
+
}
|
|
3609
3762
|
this.registerIfNeeded();
|
|
3610
3763
|
const token = generateEphemeralToken();
|
|
3611
3764
|
this.callbacksByToken[token] = callback;
|
|
@@ -3615,18 +3768,45 @@ class ObserverManager {
|
|
|
3615
3768
|
/** @internal */
|
|
3616
3769
|
removeObserver(token) {
|
|
3617
3770
|
var _a;
|
|
3618
|
-
|
|
3771
|
+
const callback = this.callbacksByToken[token];
|
|
3772
|
+
if (typeof callback === 'undefined') {
|
|
3773
|
+
throw new Error(`Can't remove '${this.id}' observer, token '${token}' has never been registered before.`);
|
|
3774
|
+
}
|
|
3775
|
+
if (callback === null) {
|
|
3776
|
+
// Observer has already been removed, no-op.
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
// REFACTOR: not deleting the token here will keep eating up
|
|
3780
|
+
// memory over long periods of time. We actually need to track
|
|
3781
|
+
// the observer objects themselves and remove it from the table
|
|
3782
|
+
// as soon as the observer object is garbage collected.
|
|
3783
|
+
this.callbacksByToken[token] = null;
|
|
3619
3784
|
(_a = this.keepAlive) === null || _a === void 0 ? void 0 : _a.release(`${this.id}.${token}`);
|
|
3620
3785
|
this.unregisterIfNeeded();
|
|
3621
3786
|
}
|
|
3787
|
+
hasObserver(token) {
|
|
3788
|
+
return typeof this.callbacksByToken[token] != 'undefined';
|
|
3789
|
+
}
|
|
3622
3790
|
/** @internal */
|
|
3623
3791
|
notify(...args) {
|
|
3792
|
+
if (this.isClosed) {
|
|
3793
|
+
// NOTE: we don't notify observers after closing and just swallow
|
|
3794
|
+
// the event.
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3624
3797
|
const processedArgs = this.process(...args);
|
|
3625
3798
|
for (const token in this.callbacksByToken) {
|
|
3626
3799
|
const callback = this.callbacksByToken[token];
|
|
3627
3800
|
callback(...processedArgs);
|
|
3628
3801
|
}
|
|
3629
3802
|
}
|
|
3803
|
+
/** @internal */
|
|
3804
|
+
close() {
|
|
3805
|
+
this.isClosed = true;
|
|
3806
|
+
for (const token in this.callbacksByToken) {
|
|
3807
|
+
this.removeObserver(token);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3630
3810
|
/**
|
|
3631
3811
|
* Can be injected and replaced via constructor options.
|
|
3632
3812
|
*
|
|
@@ -3676,9 +3856,6 @@ class ObserverManager {
|
|
|
3676
3856
|
this.unregister();
|
|
3677
3857
|
}
|
|
3678
3858
|
}
|
|
3679
|
-
async finalize(token) {
|
|
3680
|
-
await this.removeObserver(token);
|
|
3681
|
-
}
|
|
3682
3859
|
}
|
|
3683
3860
|
|
|
3684
3861
|
//
|
|
@@ -3748,39 +3925,65 @@ class Authenticator {
|
|
|
3748
3925
|
'@ditto.authClientValidityChanged'(isWebValid, isX509Valid) {
|
|
3749
3926
|
throw new Error(`Authenticator['@ditto.authClientValidityChanged']() is abstract and must be implemented by subclasses.`);
|
|
3750
3927
|
}
|
|
3928
|
+
/** @internal */
|
|
3929
|
+
close() {
|
|
3930
|
+
this.observerManager.close();
|
|
3931
|
+
}
|
|
3751
3932
|
}
|
|
3752
3933
|
// -----------------------------------------------------------------------------
|
|
3753
3934
|
/** @internal */
|
|
3754
3935
|
class OnlineAuthenticator extends Authenticator {
|
|
3755
3936
|
async loginWithToken(token, provider) {
|
|
3756
|
-
|
|
3937
|
+
const ditto = this.ditto.deref();
|
|
3938
|
+
if (!ditto) {
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3942
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
3943
|
+
if (!dittoPointer) {
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3946
|
+
return ditto.deferCloseAsync(async () => {
|
|
3947
|
+
await dittoAuthClientLoginWithToken(dittoPointer, token, provider);
|
|
3948
|
+
});
|
|
3757
3949
|
}
|
|
3758
3950
|
async loginWithUsernameAndPassword(username, password, provider) {
|
|
3759
|
-
|
|
3951
|
+
const ditto = this.ditto.deref();
|
|
3952
|
+
if (!ditto) {
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3956
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
3957
|
+
if (!dittoPointer) {
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
return ditto.deferCloseAsync(async () => {
|
|
3961
|
+
await dittoAuthClientLoginWithUsernameAndPassword(dittoPointer, username, password, provider);
|
|
3962
|
+
});
|
|
3760
3963
|
}
|
|
3761
3964
|
async logout(cleanupFn) {
|
|
3762
3965
|
const ditto = this.ditto.deref();
|
|
3763
|
-
if (ditto) {
|
|
3764
|
-
|
|
3765
|
-
ditto.stopSync();
|
|
3766
|
-
cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(this.ditto);
|
|
3966
|
+
if (!ditto) {
|
|
3967
|
+
return;
|
|
3767
3968
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3969
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
3970
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
3971
|
+
if (!dittoPointer) {
|
|
3972
|
+
return;
|
|
3770
3973
|
}
|
|
3974
|
+
return ditto.deferCloseAsync(async () => {
|
|
3975
|
+
await dittoAuthClientLogout(dittoPointer);
|
|
3976
|
+
ditto.stopSync();
|
|
3977
|
+
cleanupFn === null || cleanupFn === void 0 ? void 0 : cleanupFn(this.ditto);
|
|
3978
|
+
});
|
|
3771
3979
|
}
|
|
3772
|
-
constructor(keepAlive,
|
|
3980
|
+
constructor(keepAlive, ditto, authenticationHandler) {
|
|
3773
3981
|
super(keepAlive);
|
|
3774
3982
|
this['loginSupported'] = true;
|
|
3775
3983
|
this['status'] = { isAuthenticated: false, userID: null };
|
|
3776
|
-
this.authClientPointer = authClientPointer;
|
|
3777
3984
|
this.ditto = new WeakRef(ditto);
|
|
3778
3985
|
this.authenticationHandler = authenticationHandler;
|
|
3779
3986
|
this.updateAndNotify(false);
|
|
3780
|
-
// NOTE: The ownership of the auth client is transferred to us (from Ditto),
|
|
3781
|
-
// so we have to free the auth client once this instance is garbage
|
|
3782
|
-
// collected.
|
|
3783
|
-
OnlineAuthenticator.finalizationRegistry.register(this, authClientPointer);
|
|
3784
3987
|
}
|
|
3785
3988
|
'@ditto.authenticationExpiring'(secondsRemaining) {
|
|
3786
3989
|
const authenticationHandler = this.authenticationHandler;
|
|
@@ -3796,10 +3999,21 @@ class OnlineAuthenticator extends Authenticator {
|
|
|
3796
3999
|
}
|
|
3797
4000
|
updateAndNotify(shouldNotify) {
|
|
3798
4001
|
var _a;
|
|
4002
|
+
const ditto = this.ditto.deref();
|
|
4003
|
+
if (!ditto) {
|
|
4004
|
+
Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.');
|
|
4005
|
+
return;
|
|
4006
|
+
}
|
|
4007
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4008
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
4009
|
+
if (!dittoPointer) {
|
|
4010
|
+
Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.');
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
3799
4013
|
const wasAuthenticated = this.status.isAuthenticated;
|
|
3800
4014
|
const previousUserID = this.status.userID;
|
|
3801
|
-
const isAuthenticated = dittoAuthClientIsWebValid(
|
|
3802
|
-
const userID = dittoAuthClientUserID(
|
|
4015
|
+
const isAuthenticated = dittoAuthClientIsWebValid(dittoPointer);
|
|
4016
|
+
const userID = dittoAuthClientUserID(dittoPointer);
|
|
3803
4017
|
const status = { isAuthenticated, userID };
|
|
3804
4018
|
this['status'] = status;
|
|
3805
4019
|
if (shouldNotify) {
|
|
@@ -3810,11 +4024,7 @@ class OnlineAuthenticator extends Authenticator {
|
|
|
3810
4024
|
}
|
|
3811
4025
|
}
|
|
3812
4026
|
}
|
|
3813
|
-
static finalize(authClientPointer) {
|
|
3814
|
-
dittoAuthClientFree(authClientPointer);
|
|
3815
|
-
}
|
|
3816
4027
|
}
|
|
3817
|
-
OnlineAuthenticator.finalizationRegistry = new FinalizationRegistry(OnlineAuthenticator.finalize);
|
|
3818
4028
|
// -----------------------------------------------------------------------------
|
|
3819
4029
|
/** @internal */
|
|
3820
4030
|
class NotAvailableAuthenticator extends Authenticator {
|
|
@@ -3980,94 +4190,6 @@ class TransportConfig {
|
|
|
3980
4190
|
}
|
|
3981
4191
|
}
|
|
3982
4192
|
|
|
3983
|
-
//
|
|
3984
|
-
/**
|
|
3985
|
-
* These objects are returned by calls to
|
|
3986
|
-
* {@link Collection.fetchAttachment | fetchAttachment()} on {@link Collection}
|
|
3987
|
-
* and allow you to stop an in-flight attachment fetch.
|
|
3988
|
-
*/
|
|
3989
|
-
class AttachmentFetcher {
|
|
3990
|
-
/**
|
|
3991
|
-
* Stops fetching the associated attachment and cleans up any associated
|
|
3992
|
-
* resources.
|
|
3993
|
-
*
|
|
3994
|
-
* Note that you are not required to call `stop()` once your attachment fetch
|
|
3995
|
-
* operation has finished. The method primarily exists to allow you to cancel
|
|
3996
|
-
* an attachment fetch request while it is ongoing if you no longer wish for
|
|
3997
|
-
* the attachment to be made available locally to the device.
|
|
3998
|
-
*/
|
|
3999
|
-
stop() {
|
|
4000
|
-
AttachmentFetcher.stopWithContextInfo({
|
|
4001
|
-
ditto: this.ditto,
|
|
4002
|
-
attachmentTokenID: this.token.id,
|
|
4003
|
-
cancelTokenPromise: this.cancelTokenPromise,
|
|
4004
|
-
});
|
|
4005
|
-
}
|
|
4006
|
-
/** @internal */
|
|
4007
|
-
then(onfulfilled, onrejected) {
|
|
4008
|
-
return this.attachment.then(onfulfilled, onrejected);
|
|
4009
|
-
}
|
|
4010
|
-
/** @internal */
|
|
4011
|
-
constructor(ditto, token, eventHandler) {
|
|
4012
|
-
this.ditto = ditto;
|
|
4013
|
-
this.token = token;
|
|
4014
|
-
this.eventHandler = eventHandler || null;
|
|
4015
|
-
this.cancelTokenPromise = null;
|
|
4016
|
-
const eventHandlerOrNoOp = eventHandler || function () { };
|
|
4017
|
-
this.attachment = new Promise((resolve, reject) => {
|
|
4018
|
-
const dittoPointer = Bridge.ditto.pointerFor(ditto);
|
|
4019
|
-
// not awaited yet; only needs to be fully resolved (and `await`ed over)
|
|
4020
|
-
// when using the actual `cancelTokenPromise`, which only happens in `stop()`.
|
|
4021
|
-
this.cancelTokenPromise = dittoResolveAttachment(dittoPointer, token.id, {
|
|
4022
|
-
onComplete: (attachmentHandlePointer) => {
|
|
4023
|
-
this['eventHandler'] = null;
|
|
4024
|
-
this['cancelTokenPromise'] = null;
|
|
4025
|
-
const attachment = new Attachment(this.ditto, this.token);
|
|
4026
|
-
Bridge.attachment.bridge(attachmentHandlePointer, () => attachment);
|
|
4027
|
-
eventHandlerOrNoOp({ type: 'Completed', attachment });
|
|
4028
|
-
resolve(attachment);
|
|
4029
|
-
},
|
|
4030
|
-
onProgress: (downloaded, toDownload) => {
|
|
4031
|
-
eventHandlerOrNoOp({ type: 'Progress', totalBytes: toDownload, downloadedBytes: downloaded });
|
|
4032
|
-
},
|
|
4033
|
-
onDelete: () => {
|
|
4034
|
-
this['eventHandler'] = null;
|
|
4035
|
-
this['cancelTokenPromise'] = null;
|
|
4036
|
-
eventHandlerOrNoOp({ type: 'Deleted' });
|
|
4037
|
-
resolve(null);
|
|
4038
|
-
},
|
|
4039
|
-
},
|
|
4040
|
-
/* onError: */ (err) => {
|
|
4041
|
-
this['eventHandler'] = null;
|
|
4042
|
-
this['cancelTokenPromise'] = null;
|
|
4043
|
-
// REFACTOR: We simply throw this error at the moment. Maybe we should
|
|
4044
|
-
// bubble it up?
|
|
4045
|
-
reject(err);
|
|
4046
|
-
});
|
|
4047
|
-
});
|
|
4048
|
-
const contextInfo = {
|
|
4049
|
-
ditto: ditto,
|
|
4050
|
-
attachmentTokenID: token.id,
|
|
4051
|
-
cancelTokenPromise: this.cancelTokenPromise,
|
|
4052
|
-
};
|
|
4053
|
-
AttachmentFetcher.finalizationRegistry.register(this, contextInfo, contextInfo);
|
|
4054
|
-
}
|
|
4055
|
-
static stopWithContextInfo(contextInfo) {
|
|
4056
|
-
// No need for synchronicity, here: we let the "stop promise", float / run
|
|
4057
|
-
// in a detached fashion since there is no point in awaiting the
|
|
4058
|
-
// "stop signaling" itself.
|
|
4059
|
-
step(async () => {
|
|
4060
|
-
// await the initial resolution of `FFI.dittoResolveAttachment` below.
|
|
4061
|
-
const cancelToken = await contextInfo.cancelTokenPromise;
|
|
4062
|
-
if (cancelToken) {
|
|
4063
|
-
const dittoPointer = Bridge.ditto.pointerFor(contextInfo.ditto);
|
|
4064
|
-
dittoCancelResolveAttachment(dittoPointer, contextInfo.attachmentTokenID, cancelToken);
|
|
4065
|
-
}
|
|
4066
|
-
});
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4069
|
-
AttachmentFetcher.finalizationRegistry = new FinalizationRegistry(AttachmentFetcher.stopWithContextInfo);
|
|
4070
|
-
|
|
4071
4193
|
//
|
|
4072
4194
|
/**
|
|
4073
4195
|
* Used to subscribe to receive updates from remote peers about matching
|
|
@@ -4090,7 +4212,7 @@ class Subscription {
|
|
|
4090
4212
|
cancel() {
|
|
4091
4213
|
if (!this.isCancelled) {
|
|
4092
4214
|
this['isCancelled'] = true;
|
|
4093
|
-
|
|
4215
|
+
this.manager.remove(this);
|
|
4094
4216
|
}
|
|
4095
4217
|
}
|
|
4096
4218
|
// ----------------------------------------------------------- Internal ------
|
|
@@ -4106,7 +4228,7 @@ class Subscription {
|
|
|
4106
4228
|
this.queryArgsCBOR = queryArgsCBOR;
|
|
4107
4229
|
this.collection = collection;
|
|
4108
4230
|
this.contextInfo = {
|
|
4109
|
-
|
|
4231
|
+
id: generateEphemeralToken(),
|
|
4110
4232
|
collectionName: collection.name,
|
|
4111
4233
|
query,
|
|
4112
4234
|
queryArgsCBOR,
|
|
@@ -4114,23 +4236,10 @@ class Subscription {
|
|
|
4114
4236
|
limit,
|
|
4115
4237
|
offset,
|
|
4116
4238
|
};
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
static add(subscription, contextInfo) {
|
|
4120
|
-
const dittoX = Bridge.ditto.pointerFor(contextInfo.ditto);
|
|
4121
|
-
addSubscription(dittoX, contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
|
|
4122
|
-
this.finalizationRegistry.register(subscription, contextInfo, contextInfo);
|
|
4239
|
+
this.manager = collection.store.ditto.subscriptionManager;
|
|
4240
|
+
this.manager.add(this);
|
|
4123
4241
|
}
|
|
4124
|
-
|
|
4125
|
-
const dittoX = Bridge.ditto.pointerFor(contextInfo.ditto);
|
|
4126
|
-
removeSubscription(dittoX, contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
|
|
4127
|
-
if (subscription)
|
|
4128
|
-
this.finalizationRegistry.unregister(contextInfo);
|
|
4129
|
-
}
|
|
4130
|
-
}
|
|
4131
|
-
Subscription.finalizationRegistry = new FinalizationRegistry((contextInfo) => {
|
|
4132
|
-
Subscription.remove(null, contextInfo);
|
|
4133
|
-
});
|
|
4242
|
+
}
|
|
4134
4243
|
|
|
4135
4244
|
//
|
|
4136
4245
|
// -----------------------------------------------------------------------------
|
|
@@ -4150,14 +4259,16 @@ class LiveQueryEventInitial {
|
|
|
4150
4259
|
* Returns a hash that represents the set of matching documents.
|
|
4151
4260
|
*/
|
|
4152
4261
|
hash(documents) {
|
|
4153
|
-
|
|
4262
|
+
const documentHandles = Bridge.document.handlesFor(documents);
|
|
4263
|
+
return documentsHash(documentHandles.deref());
|
|
4154
4264
|
}
|
|
4155
4265
|
/**
|
|
4156
4266
|
* Returns a pattern of words that together create a mnemonic, which
|
|
4157
4267
|
* represents the set of matching documents.
|
|
4158
4268
|
*/
|
|
4159
4269
|
hashMnemonic(documents) {
|
|
4160
|
-
|
|
4270
|
+
const documentHandles = Bridge.document.handlesFor(documents);
|
|
4271
|
+
return documentsHashMnemonic(documentHandles.deref());
|
|
4161
4272
|
}
|
|
4162
4273
|
}
|
|
4163
4274
|
// -----------------------------------------------------------------------------
|
|
@@ -4170,14 +4281,16 @@ class LiveQueryEventUpdate {
|
|
|
4170
4281
|
* Returns a hash that represents the set of matching documents.
|
|
4171
4282
|
*/
|
|
4172
4283
|
hash(documents) {
|
|
4173
|
-
|
|
4284
|
+
const documentHandles = Bridge.document.handlesFor(documents);
|
|
4285
|
+
return documentsHash(documentHandles.deref());
|
|
4174
4286
|
}
|
|
4175
4287
|
/**
|
|
4176
4288
|
* Returns a pattern of words that together create a mnemonic, which
|
|
4177
4289
|
* represents the set of matching documents.
|
|
4178
4290
|
*/
|
|
4179
4291
|
hashMnemonic(documents) {
|
|
4180
|
-
|
|
4292
|
+
const documentHandles = Bridge.document.handlesFor(documents);
|
|
4293
|
+
return documentsHashMnemonic(documentHandles.deref());
|
|
4181
4294
|
}
|
|
4182
4295
|
/** @internal */
|
|
4183
4296
|
constructor(params) {
|
|
@@ -4203,14 +4316,16 @@ class SingleDocumentLiveQueryEvent {
|
|
|
4203
4316
|
* Returns a hash that represents the set of matching documents.
|
|
4204
4317
|
*/
|
|
4205
4318
|
hash(document) {
|
|
4206
|
-
|
|
4319
|
+
const documentHandles = Bridge.document.handlesFor(document == null ? [] : [document]);
|
|
4320
|
+
return documentsHash(documentHandles.deref());
|
|
4207
4321
|
}
|
|
4208
4322
|
/**
|
|
4209
4323
|
* Returns a pattern of words that together create a mnemonic, which
|
|
4210
4324
|
* represents the set of matching documents.
|
|
4211
4325
|
*/
|
|
4212
4326
|
hashMnemonic(document) {
|
|
4213
|
-
|
|
4327
|
+
const documentHandles = Bridge.document.handlesFor(document == null ? [] : [document]);
|
|
4328
|
+
return documentsHashMnemonic(documentHandles.deref());
|
|
4214
4329
|
}
|
|
4215
4330
|
/** @internal */
|
|
4216
4331
|
constructor(isInitial, oldDocument) {
|
|
@@ -4241,24 +4356,18 @@ class LiveQuery {
|
|
|
4241
4356
|
}
|
|
4242
4357
|
/** Returns true if the receiver has been stopped. */
|
|
4243
4358
|
get isStopped() {
|
|
4244
|
-
return this.
|
|
4359
|
+
return !this.liveQueryManager;
|
|
4245
4360
|
}
|
|
4246
4361
|
/**
|
|
4247
4362
|
* Stop the live query from delivering updates.
|
|
4248
4363
|
*/
|
|
4249
4364
|
stop() {
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
if (liveQueryID !== null) {
|
|
4253
|
-
this.collection.store.ditto.keepAlive.release(`LiveQuery.${liveQueryID}`);
|
|
4254
|
-
this.liveQueryID = null;
|
|
4255
|
-
const dittoPointer = Bridge.ditto.pointerFor(this.collection.store.ditto);
|
|
4256
|
-
(_a = this.subscription) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
4257
|
-
liveQueryStop(dittoPointer, liveQueryID);
|
|
4365
|
+
if (!this.isStopped) {
|
|
4366
|
+
this.liveQueryManager.stopLiveQuery(this);
|
|
4258
4367
|
}
|
|
4259
4368
|
}
|
|
4260
4369
|
/** @internal */
|
|
4261
|
-
constructor(query, queryArgs, queryArgsCBOR, orderBys, limit, offset, collection,
|
|
4370
|
+
constructor(query, queryArgs, queryArgsCBOR, orderBys, limit, offset, collection, handler) {
|
|
4262
4371
|
// Query should be validated at this point.
|
|
4263
4372
|
this.query = query;
|
|
4264
4373
|
this.queryArgs = queryArgs ? Object.freeze({ ...queryArgs }) : null;
|
|
@@ -4268,52 +4377,59 @@ class LiveQuery {
|
|
|
4268
4377
|
this.offset = offset;
|
|
4269
4378
|
this.collection = collection;
|
|
4270
4379
|
this.handler = handler;
|
|
4271
|
-
|
|
4272
|
-
this.subscription = subscription;
|
|
4273
|
-
}
|
|
4380
|
+
this.liveQueryManager = null;
|
|
4274
4381
|
const collectionName = collection.name;
|
|
4275
4382
|
const weakDitto = new WeakRef(collection.store.ditto);
|
|
4276
4383
|
let liveQueryID = undefined;
|
|
4277
4384
|
const signalNext = async () => {
|
|
4278
4385
|
const ditto = weakDitto.deref();
|
|
4279
|
-
if (ditto)
|
|
4280
|
-
|
|
4386
|
+
if (!ditto)
|
|
4387
|
+
return;
|
|
4388
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4389
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
4390
|
+
if (!dittoPointer)
|
|
4391
|
+
return;
|
|
4392
|
+
return ditto.deferCloseAsync(async () => {
|
|
4281
4393
|
await liveQuerySignalAvailableNext(dittoPointer, liveQueryID);
|
|
4282
|
-
}
|
|
4394
|
+
});
|
|
4283
4395
|
};
|
|
4284
|
-
const
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
event
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4396
|
+
const ditto = collection.store.ditto;
|
|
4397
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4398
|
+
ditto.deferClose(() => {
|
|
4399
|
+
liveQueryID = liveQueryRegister(dittoHandle.deref(), collectionName, query, queryArgsCBOR, this.orderBys, limit, offset, (cCBParams) => {
|
|
4400
|
+
const documents = cCBParams.documents.map((ptr) => Bridge.document.bridge(ptr));
|
|
4401
|
+
let event;
|
|
4402
|
+
if (cCBParams.is_initial) {
|
|
4403
|
+
event = new LiveQueryEventInitial();
|
|
4404
|
+
}
|
|
4405
|
+
else {
|
|
4406
|
+
event = new LiveQueryEventUpdate({
|
|
4407
|
+
oldDocuments: cCBParams.old_documents.map((ptr) => Bridge.document.bridge(ptr)),
|
|
4408
|
+
insertions: cCBParams.insertions,
|
|
4409
|
+
deletions: cCBParams.deletions,
|
|
4410
|
+
updates: cCBParams.updates,
|
|
4411
|
+
moves: cCBParams.moves.map((move) => ({ from: move[0], to: move[1] })),
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
handler(documents, event, signalNext);
|
|
4415
|
+
});
|
|
4416
|
+
if (!liveQueryID) {
|
|
4417
|
+
throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
|
|
4299
4418
|
}
|
|
4300
|
-
|
|
4419
|
+
this['liveQueryID'] = liveQueryID;
|
|
4301
4420
|
});
|
|
4302
|
-
if (!liveQueryID) {
|
|
4303
|
-
throw new Error("Internal inconsistency, couldn't create a valid live query ID.");
|
|
4304
|
-
}
|
|
4305
|
-
this.liveQueryID = liveQueryID;
|
|
4306
|
-
// not awaited on purpose; let the invocation of the initial observation
|
|
4307
|
-
// happen in a "fire-and-forget" / detached / escaping fashion, to be
|
|
4308
|
-
// consistent with the subsequent invocations.
|
|
4309
|
-
step(async () => await liveQueryStart(dittoPointer, liveQueryID));
|
|
4310
|
-
collection.store.ditto.keepAlive.retain(`LiveQuery.${liveQueryID}`);
|
|
4311
4421
|
}
|
|
4312
4422
|
/** @internal */
|
|
4313
4423
|
async signalNext() {
|
|
4314
4424
|
// IDEA: make this public?
|
|
4315
|
-
const
|
|
4316
|
-
|
|
4425
|
+
const ditto = this.collection.store.ditto;
|
|
4426
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4427
|
+
const dittoPointer = dittoHandle.derefOrNull();
|
|
4428
|
+
if (!dittoPointer)
|
|
4429
|
+
return;
|
|
4430
|
+
return ditto.deferCloseAsync(async () => {
|
|
4431
|
+
await liveQuerySignalAvailableNext(dittoHandle.deref(), this.liveQueryID);
|
|
4432
|
+
});
|
|
4317
4433
|
}
|
|
4318
4434
|
}
|
|
4319
4435
|
|
|
@@ -4355,40 +4471,48 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4355
4471
|
return super.limit(limit);
|
|
4356
4472
|
}
|
|
4357
4473
|
async remove() {
|
|
4358
|
-
const
|
|
4359
|
-
const
|
|
4360
|
-
|
|
4361
|
-
const
|
|
4362
|
-
const
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4474
|
+
const ditto = this.collection.store.ditto;
|
|
4475
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4476
|
+
return ditto.deferCloseAsync(async () => {
|
|
4477
|
+
const query = this.query;
|
|
4478
|
+
const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4479
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4480
|
+
const results = await collectionRemoveQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
|
|
4481
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4482
|
+
return results;
|
|
4483
|
+
});
|
|
4484
|
+
return documentsX.map((idCBOR) => {
|
|
4485
|
+
return new DocumentID(idCBOR, true);
|
|
4486
|
+
});
|
|
4368
4487
|
});
|
|
4369
4488
|
}
|
|
4370
4489
|
async evict() {
|
|
4371
|
-
const
|
|
4372
|
-
const
|
|
4373
|
-
|
|
4374
|
-
const
|
|
4375
|
-
const
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4490
|
+
const ditto = this.collection.store.ditto;
|
|
4491
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4492
|
+
return ditto.deferCloseAsync(async () => {
|
|
4493
|
+
const query = this.query;
|
|
4494
|
+
const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4495
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4496
|
+
const results = await collectionEvictQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
|
|
4497
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4498
|
+
return results;
|
|
4499
|
+
});
|
|
4500
|
+
return documentsX.map((idCBOR) => {
|
|
4501
|
+
return new DocumentID(idCBOR, true);
|
|
4502
|
+
});
|
|
4381
4503
|
});
|
|
4382
4504
|
}
|
|
4383
4505
|
async update(closure) {
|
|
4384
|
-
const
|
|
4385
|
-
const
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4506
|
+
const ditto = this.collection.store.ditto;
|
|
4507
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4508
|
+
return ditto.deferCloseAsync(async () => {
|
|
4509
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4510
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4511
|
+
const results = await super.updateWithTransaction(closure, writeTransactionX);
|
|
4512
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4513
|
+
return results;
|
|
4514
|
+
});
|
|
4390
4515
|
});
|
|
4391
|
-
return results;
|
|
4392
4516
|
}
|
|
4393
4517
|
/**
|
|
4394
4518
|
* Enables you to subscribe to changes that occur in a collection remotely.
|
|
@@ -4406,7 +4530,9 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4406
4530
|
* query specified in the preceding chain.
|
|
4407
4531
|
*/
|
|
4408
4532
|
subscribe() {
|
|
4409
|
-
|
|
4533
|
+
const subscription = new Subscription(this.collection, this.query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset);
|
|
4534
|
+
this.collection.store.ditto.subscriptionManager.add(subscription);
|
|
4535
|
+
return subscription;
|
|
4410
4536
|
}
|
|
4411
4537
|
/**
|
|
4412
4538
|
* Enables you to listen for changes that occur in a collection locally.
|
|
@@ -4433,7 +4559,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4433
4559
|
* as you want to keep receiving updates.
|
|
4434
4560
|
*/
|
|
4435
4561
|
observeLocal(handler) {
|
|
4436
|
-
return this._observe(handler, false
|
|
4562
|
+
return this._observe(handler, false);
|
|
4437
4563
|
}
|
|
4438
4564
|
/**
|
|
4439
4565
|
* Enables you to listen for changes that occur in a collection locally and
|
|
@@ -4462,7 +4588,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4462
4588
|
* as you want to keep receiving updates.
|
|
4463
4589
|
*/
|
|
4464
4590
|
observeLocalWithNextSignal(handler) {
|
|
4465
|
-
return this._observe(handler,
|
|
4591
|
+
return this._observe(handler, true);
|
|
4466
4592
|
}
|
|
4467
4593
|
// ----------------------------------------------------------- Internal ------
|
|
4468
4594
|
/** @internal */
|
|
@@ -4470,8 +4596,7 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4470
4596
|
super(query, queryArgs, collection);
|
|
4471
4597
|
}
|
|
4472
4598
|
/** @internal */
|
|
4473
|
-
_observe(handler,
|
|
4474
|
-
const subscription = createSubscription ? this.subscribe() : null;
|
|
4599
|
+
_observe(handler, waitForNextSignal) {
|
|
4475
4600
|
function wrappedHandler(documents, event, nextSignal) {
|
|
4476
4601
|
try {
|
|
4477
4602
|
return handler.call(this, documents, event);
|
|
@@ -4481,7 +4606,9 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4481
4606
|
}
|
|
4482
4607
|
}
|
|
4483
4608
|
const handlerOrWrapped = waitForNextSignal ? handler : wrappedHandler;
|
|
4484
|
-
|
|
4609
|
+
const liveQuery = new LiveQuery(this.query, this.queryArgs, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset, this.collection, handlerOrWrapped);
|
|
4610
|
+
this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery);
|
|
4611
|
+
return liveQuery;
|
|
4485
4612
|
}
|
|
4486
4613
|
}
|
|
4487
4614
|
|
|
@@ -4509,39 +4636,48 @@ class PendingCursorOperation extends BasePendingCursorOperation {
|
|
|
4509
4636
|
*/
|
|
4510
4637
|
class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
4511
4638
|
async remove() {
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4639
|
+
const ditto = this.collection.store.ditto;
|
|
4640
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4641
|
+
return ditto.deferCloseAsync(async () => {
|
|
4642
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4643
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4644
|
+
const didRemove = await collectionRemove(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR);
|
|
4645
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4646
|
+
return didRemove;
|
|
4647
|
+
});
|
|
4518
4648
|
});
|
|
4519
4649
|
}
|
|
4520
4650
|
async evict() {
|
|
4521
|
-
const
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4651
|
+
const ditto = this.collection.store.ditto;
|
|
4652
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4653
|
+
return ditto.deferCloseAsync(async () => {
|
|
4654
|
+
return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4655
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4656
|
+
const didEvict = await collectionEvict(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR);
|
|
4657
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4658
|
+
return didEvict;
|
|
4659
|
+
});
|
|
4527
4660
|
});
|
|
4528
4661
|
}
|
|
4529
4662
|
async update(closure) {
|
|
4530
|
-
const
|
|
4531
|
-
const
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4663
|
+
const ditto = this.collection.store.ditto;
|
|
4664
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4665
|
+
return ditto.deferCloseAsync(async () => {
|
|
4666
|
+
const readTransactionX = await readTransaction(dittoHandle.deref());
|
|
4667
|
+
const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
|
|
4668
|
+
readTransactionFree(readTransactionX);
|
|
4669
|
+
if (!documentX)
|
|
4670
|
+
throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
|
|
4671
|
+
const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
|
|
4672
|
+
closure(mutableDocument);
|
|
4673
|
+
// Ownership is transferred back to the FFI layer via collectionUpdate(),
|
|
4674
|
+
// we therefore need to explicitly unregister the instance.
|
|
4675
|
+
Bridge.mutableDocument.unregister(mutableDocument);
|
|
4676
|
+
const writeTransactionX = await writeTransaction(dittoHandle.deref());
|
|
4677
|
+
await collectionUpdate(dittoHandle.deref(), this.collection.name, writeTransactionX, documentX);
|
|
4678
|
+
await writeTransactionCommit(dittoHandle.deref(), writeTransactionX);
|
|
4679
|
+
return mutableDocument['@ditto.updateResults'].slice();
|
|
4680
|
+
});
|
|
4545
4681
|
}
|
|
4546
4682
|
/**
|
|
4547
4683
|
* Enables you to subscribe to changes that occur in relation to a document
|
|
@@ -4558,7 +4694,9 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
|
4558
4694
|
* long as you want to keep receiving updates for the document.
|
|
4559
4695
|
*/
|
|
4560
4696
|
subscribe() {
|
|
4561
|
-
|
|
4697
|
+
const subscription = new Subscription(this.collection, this.query, null, [], -1, 0);
|
|
4698
|
+
this.collection.store.ditto.subscriptionManager.add(subscription);
|
|
4699
|
+
return subscription;
|
|
4562
4700
|
}
|
|
4563
4701
|
/**
|
|
4564
4702
|
* Enables you to listen for changes that occur in relation to a document
|
|
@@ -4583,7 +4721,7 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
|
4583
4721
|
* as you want to keep receiving updates.
|
|
4584
4722
|
*/
|
|
4585
4723
|
observeLocal(handler) {
|
|
4586
|
-
return this._observe(handler, false
|
|
4724
|
+
return this._observe(handler, false);
|
|
4587
4725
|
}
|
|
4588
4726
|
/**
|
|
4589
4727
|
* Enables you to listen for changes that occur in relation to a document
|
|
@@ -4609,16 +4747,15 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
|
4609
4747
|
* as you want to keep receiving updates.
|
|
4610
4748
|
*/
|
|
4611
4749
|
observeLocalWithNextSignal(handler) {
|
|
4612
|
-
return this._observe(handler,
|
|
4750
|
+
return this._observe(handler, true);
|
|
4613
4751
|
}
|
|
4614
4752
|
/** @internal */
|
|
4615
4753
|
constructor(documentID, collection) {
|
|
4616
4754
|
super(documentID, collection);
|
|
4617
4755
|
}
|
|
4618
4756
|
/** @internal */
|
|
4619
|
-
_observe(handler,
|
|
4620
|
-
const
|
|
4621
|
-
return new LiveQuery(this.query, null, null, [], -1, 0, this.collection, subscription, (documents, event, signalNext) => {
|
|
4757
|
+
_observe(handler, waitForNextSignal) {
|
|
4758
|
+
const liveQuery = new LiveQuery(this.query, null, null, [], -1, 0, this.collection, (documents, event, signalNext) => {
|
|
4622
4759
|
if (documents.length > 1) {
|
|
4623
4760
|
const documentIDsAsBase64Strings = documents.map((document) => document.id.toBase64String());
|
|
4624
4761
|
throw new Error(`Internal inconsistency, single document live query returned more than one document. Query: ${this.query}, documentIDs: ${documentIDsAsBase64Strings.join(', ')}.`);
|
|
@@ -4653,6 +4790,8 @@ class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
|
4653
4790
|
}
|
|
4654
4791
|
}
|
|
4655
4792
|
});
|
|
4793
|
+
this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery);
|
|
4794
|
+
return liveQuery;
|
|
4656
4795
|
}
|
|
4657
4796
|
}
|
|
4658
4797
|
|
|
@@ -4712,28 +4851,18 @@ class Collection {
|
|
|
4712
4851
|
return new PendingIDSpecificOperation(documentID, this);
|
|
4713
4852
|
}
|
|
4714
4853
|
async upsert(value, options = {}) {
|
|
4715
|
-
var _a;
|
|
4716
|
-
const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
|
|
4717
4854
|
const ditto = this.store.ditto;
|
|
4718
|
-
const
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
documentID = new DocumentID(id);
|
|
4729
|
-
}
|
|
4730
|
-
const documentIDCBOR = typeof documentID === 'undefined' ? null : documentID.toCBOR();
|
|
4731
|
-
const documentValueJSON = desugarJSObject(value, true);
|
|
4732
|
-
const documentValueCBOR = CBOR.encode(documentValueJSON);
|
|
4733
|
-
const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4734
|
-
return await collectionInsertValue(dittoPointer, this.name, documentValueCBOR, documentIDCBOR, writeStrategy, undefined);
|
|
4855
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4856
|
+
return ditto.deferCloseAsync(async () => {
|
|
4857
|
+
var _a;
|
|
4858
|
+
const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
|
|
4859
|
+
const documentValueJSON = desugarJSObject(value, true);
|
|
4860
|
+
const documentValueCBOR = CBOR.encode(documentValueJSON);
|
|
4861
|
+
const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
4862
|
+
return await collectionInsertValue(dittoHandle.deref(), this.name, documentValueCBOR, writeStrategy, undefined);
|
|
4863
|
+
});
|
|
4864
|
+
return new DocumentID(idCBOR, true);
|
|
4735
4865
|
});
|
|
4736
|
-
return new DocumentID(idCBOR, true);
|
|
4737
4866
|
}
|
|
4738
4867
|
/**
|
|
4739
4868
|
* Creates a new {@link Attachment} object, which can then be inserted into a
|
|
@@ -4763,23 +4892,25 @@ class Collection {
|
|
|
4763
4892
|
*/
|
|
4764
4893
|
async newAttachment(pathOrData, metadata = {}) {
|
|
4765
4894
|
const ditto = this.store.ditto;
|
|
4766
|
-
const
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
{
|
|
4770
|
-
|
|
4895
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
4896
|
+
return ditto.deferCloseAsync(async () => {
|
|
4897
|
+
const { id, len, handle } = await (async () => {
|
|
4898
|
+
if (typeof pathOrData === 'string') {
|
|
4899
|
+
{
|
|
4900
|
+
return dittoNewAttachmentFromFile(dittoHandle.deref(), pathOrData, 'Copy');
|
|
4901
|
+
}
|
|
4771
4902
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4903
|
+
if (pathOrData instanceof Uint8Array) {
|
|
4904
|
+
return await dittoNewAttachmentFromBytes(dittoHandle.deref(), pathOrData);
|
|
4905
|
+
}
|
|
4906
|
+
throw new Error(`Can't create new attachment, only file path as string or raw data as Uint8Array are supported, but got: ${typeof pathOrData}, ${pathOrData}`);
|
|
4907
|
+
})();
|
|
4908
|
+
const attachmentTokenJSON = { _id: id, _len: len, _meta: { ...metadata } };
|
|
4909
|
+
attachmentTokenJSON[DittoCRDTTypeKey] = DittoCRDTType.attachment;
|
|
4910
|
+
const attachmentToken = new AttachmentToken(attachmentTokenJSON);
|
|
4911
|
+
const attachment = new Attachment(ditto, attachmentToken);
|
|
4912
|
+
return Bridge.attachment.bridge(handle, () => attachment);
|
|
4913
|
+
});
|
|
4783
4914
|
}
|
|
4784
4915
|
/**
|
|
4785
4916
|
* Trigger an attachment to be downloaded locally to the device and observe
|
|
@@ -4806,8 +4937,13 @@ class Collection {
|
|
|
4806
4937
|
* fetch status changes.
|
|
4807
4938
|
*/
|
|
4808
4939
|
fetchAttachment(token, eventHandler) {
|
|
4940
|
+
if (token == null || !(token instanceof AttachmentToken)) {
|
|
4941
|
+
throw new Error(`Invalid attachment token: ${token}`);
|
|
4942
|
+
}
|
|
4809
4943
|
const ditto = this.store.ditto;
|
|
4810
|
-
return
|
|
4944
|
+
return ditto.deferClose(() => {
|
|
4945
|
+
return ditto.attachmentFetcherManager.startAttachmentFetcher(token, eventHandler);
|
|
4946
|
+
});
|
|
4811
4947
|
}
|
|
4812
4948
|
/** @internal */
|
|
4813
4949
|
constructor(name, store) {
|
|
@@ -4959,7 +5095,7 @@ class PendingCollectionsOperation {
|
|
|
4959
5095
|
* as you want to keep receiving updates.
|
|
4960
5096
|
*/
|
|
4961
5097
|
observeLocal(handler) {
|
|
4962
|
-
return this._observe(handler, false
|
|
5098
|
+
return this._observe(handler, false);
|
|
4963
5099
|
}
|
|
4964
5100
|
/**
|
|
4965
5101
|
* Enables you to listen for changes that occur in relation to the collections
|
|
@@ -4980,7 +5116,7 @@ class PendingCollectionsOperation {
|
|
|
4980
5116
|
* as you want to keep receiving updates.
|
|
4981
5117
|
*/
|
|
4982
5118
|
observeLocalWithNextSignal(handler) {
|
|
4983
|
-
return this._observe(handler,
|
|
5119
|
+
return this._observe(handler, true);
|
|
4984
5120
|
}
|
|
4985
5121
|
/**
|
|
4986
5122
|
* Return the list of collections requested based on the preceding function
|
|
@@ -5004,7 +5140,7 @@ class PendingCollectionsOperation {
|
|
|
5004
5140
|
}
|
|
5005
5141
|
// ----------------------------------------------------------- Internal ------
|
|
5006
5142
|
/** @internal */
|
|
5007
|
-
_observe(handler,
|
|
5143
|
+
_observe(handler, waitForNextSignal) {
|
|
5008
5144
|
const weakStore = new WeakRef(this.store);
|
|
5009
5145
|
const collectionsObservationHandler = function (documents, event, nextSignal) {
|
|
5010
5146
|
const strongStore = weakStore.deref();
|
|
@@ -5035,7 +5171,7 @@ class PendingCollectionsOperation {
|
|
|
5035
5171
|
handler(collEvent);
|
|
5036
5172
|
}
|
|
5037
5173
|
};
|
|
5038
|
-
return this.pendingCursorOperation._observe(collectionsObservationHandler,
|
|
5174
|
+
return this.pendingCursorOperation._observe(collectionsObservationHandler, waitForNextSignal);
|
|
5039
5175
|
}
|
|
5040
5176
|
}
|
|
5041
5177
|
/** @private */
|
|
@@ -5076,16 +5212,23 @@ class ExperimentalStore {
|
|
|
5076
5212
|
* @returns a promise for an array of Ditto documents matching the query
|
|
5077
5213
|
**/
|
|
5078
5214
|
async execute(query) {
|
|
5079
|
-
const
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5215
|
+
const ditto = this.ditto;
|
|
5216
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5217
|
+
return ditto.deferCloseAsync(async () => {
|
|
5218
|
+
// A one-off query execution uses a transaction internally but a
|
|
5219
|
+
// transaction API is not implemented yet.
|
|
5220
|
+
const writeTransaction = null;
|
|
5221
|
+
// Not implemented yet.
|
|
5222
|
+
const queryParameters = null;
|
|
5223
|
+
const documentPointers = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
5224
|
+
return await experimentalExecQueryStr(dittoHandle.deref(), writeTransaction, query, queryParameters);
|
|
5225
|
+
});
|
|
5226
|
+
return documentPointers.map((documentPointer) => Bridge.document.bridge(documentPointer));
|
|
5087
5227
|
});
|
|
5088
|
-
|
|
5228
|
+
}
|
|
5229
|
+
/** @internal */
|
|
5230
|
+
close() {
|
|
5231
|
+
// Nothing to do yet.
|
|
5089
5232
|
}
|
|
5090
5233
|
}
|
|
5091
5234
|
|
|
@@ -5115,30 +5258,36 @@ class WriteTransactionPendingCursorOperation extends BasePendingCursorOperation
|
|
|
5115
5258
|
return super.limit(limit);
|
|
5116
5259
|
}
|
|
5117
5260
|
async remove() {
|
|
5118
|
-
const
|
|
5119
|
-
const
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5261
|
+
const ditto = this.collection.store.ditto;
|
|
5262
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5263
|
+
return ditto.deferCloseAsync(async () => {
|
|
5264
|
+
const query = this.query;
|
|
5265
|
+
const transaction = this.collection.writeTransaction;
|
|
5266
|
+
const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemoveQueryStr(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
|
|
5267
|
+
const results = documentsX.map((idCBOR) => {
|
|
5268
|
+
return new DocumentID(idCBOR, true);
|
|
5269
|
+
});
|
|
5270
|
+
results.forEach((documentId) => {
|
|
5271
|
+
transaction.addResult('removed', documentId, this.collection.name);
|
|
5272
|
+
});
|
|
5273
|
+
return results;
|
|
5127
5274
|
});
|
|
5128
|
-
return results;
|
|
5129
5275
|
}
|
|
5130
5276
|
async evict() {
|
|
5131
|
-
const
|
|
5132
|
-
const
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5277
|
+
const ditto = this.collection.store.ditto;
|
|
5278
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5279
|
+
return ditto.deferCloseAsync(async () => {
|
|
5280
|
+
const query = this.query;
|
|
5281
|
+
const transaction = this.collection.writeTransaction;
|
|
5282
|
+
const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionEvictQueryStr(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset));
|
|
5283
|
+
const results = documentsX.map((idCBOR) => {
|
|
5284
|
+
return new DocumentID(idCBOR, true);
|
|
5285
|
+
});
|
|
5286
|
+
results.forEach((documentId) => {
|
|
5287
|
+
transaction.addResult('evicted', documentId, this.collection.name);
|
|
5288
|
+
});
|
|
5289
|
+
return results;
|
|
5140
5290
|
});
|
|
5141
|
-
return results;
|
|
5142
5291
|
}
|
|
5143
5292
|
async update(closure) {
|
|
5144
5293
|
const transaction = this.collection.writeTransaction;
|
|
@@ -5158,35 +5307,44 @@ class WriteTransactionPendingCursorOperation extends BasePendingCursorOperation
|
|
|
5158
5307
|
//
|
|
5159
5308
|
class WriteTransactionPendingIDSpecificOperation extends BasePendingIDSpecificOperation {
|
|
5160
5309
|
async remove() {
|
|
5161
|
-
const
|
|
5162
|
-
const
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5310
|
+
const ditto = this.collection.store.ditto;
|
|
5311
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5312
|
+
return ditto.deferCloseAsync(async () => {
|
|
5313
|
+
const transaction = this.collection.writeTransaction;
|
|
5314
|
+
const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => collectionRemove(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
|
|
5315
|
+
transaction.addResult('removed', this.documentID, this.collection.name);
|
|
5316
|
+
return result;
|
|
5317
|
+
});
|
|
5166
5318
|
}
|
|
5167
5319
|
async evict() {
|
|
5168
|
-
const
|
|
5169
|
-
const
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5320
|
+
const ditto = this.collection.store.ditto;
|
|
5321
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5322
|
+
return ditto.deferCloseAsync(async () => {
|
|
5323
|
+
const transaction = this.collection.writeTransaction;
|
|
5324
|
+
const result = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => await collectionEvict(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, this.documentIDCBOR));
|
|
5325
|
+
transaction.addResult('evicted', this.documentID, this.collection.name);
|
|
5326
|
+
return result;
|
|
5327
|
+
});
|
|
5173
5328
|
}
|
|
5174
5329
|
async update(closure) {
|
|
5175
|
-
const
|
|
5176
|
-
const
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5330
|
+
const ditto = this.collection.store.ditto;
|
|
5331
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5332
|
+
return ditto.deferCloseAsync(async () => {
|
|
5333
|
+
const transaction = this.collection.writeTransaction;
|
|
5334
|
+
const readTransactionX = await readTransaction(dittoHandle.deref());
|
|
5335
|
+
const documentX = await collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX);
|
|
5336
|
+
readTransactionFree(readTransactionX);
|
|
5337
|
+
if (!documentX)
|
|
5338
|
+
throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`);
|
|
5339
|
+
const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument());
|
|
5340
|
+
closure(mutableDocument);
|
|
5341
|
+
// Ownership is transferred back to the FFI layer via collectionUpdate(),
|
|
5342
|
+
// we therefore need to explicitly unregister the instance.
|
|
5343
|
+
Bridge.mutableDocument.unregister(mutableDocument);
|
|
5344
|
+
await collectionUpdate(dittoHandle.deref(), this.collection.name, transaction.writeTransactionPointer, documentX);
|
|
5345
|
+
transaction.addResult('updated', this.documentID, this.collection.name);
|
|
5346
|
+
return mutableDocument['@ditto.updateResults'].slice();
|
|
5347
|
+
});
|
|
5190
5348
|
}
|
|
5191
5349
|
// ----------------------------------------------------------- Internal ------
|
|
5192
5350
|
/** @internal */
|
|
@@ -5241,30 +5399,20 @@ class WriteTransactionCollection {
|
|
|
5241
5399
|
return new WriteTransactionPendingIDSpecificOperation(documentID, this);
|
|
5242
5400
|
}
|
|
5243
5401
|
async upsert(value, options = {}) {
|
|
5244
|
-
|
|
5245
|
-
const
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
}
|
|
5258
|
-
const documentIDCBOR = typeof documentID === 'undefined' ? null : documentID.toCBOR();
|
|
5259
|
-
const documentValueJSON = desugarJSObject(value, true);
|
|
5260
|
-
const documentValueCBOR = CBOR.encode(documentValueJSON);
|
|
5261
|
-
const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
5262
|
-
// @ts-ignore
|
|
5263
|
-
return await collectionInsertValue(dittoPointer, this.name, documentValueCBOR, documentIDCBOR, writeStrategy, this.writeTransaction.writeTransactionPointer);
|
|
5402
|
+
const ditto = this.store.ditto;
|
|
5403
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5404
|
+
return ditto.deferCloseAsync(async () => {
|
|
5405
|
+
var _a;
|
|
5406
|
+
const writeStrategy = (_a = options.writeStrategy) !== null && _a !== void 0 ? _a : 'merge';
|
|
5407
|
+
const documentValueJSON = desugarJSObject(value, true);
|
|
5408
|
+
const documentValueCBOR = CBOR.encode(documentValueJSON);
|
|
5409
|
+
const idCBOR = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
|
|
5410
|
+
return await collectionInsertValue(dittoHandle.deref(), this.name, documentValueCBOR, writeStrategy, this.writeTransaction.writeTransactionPointer);
|
|
5411
|
+
});
|
|
5412
|
+
const insertedDocumentId = new DocumentID(idCBOR, true);
|
|
5413
|
+
this.writeTransaction.addResult('inserted', insertedDocumentId, this.name);
|
|
5414
|
+
return new DocumentID(idCBOR, true);
|
|
5264
5415
|
});
|
|
5265
|
-
const insertedDocumentId = new DocumentID(idCBOR, true);
|
|
5266
|
-
this.writeTransaction.addResult('inserted', insertedDocumentId, this.name);
|
|
5267
|
-
return new DocumentID(idCBOR, true);
|
|
5268
5416
|
}
|
|
5269
5417
|
/**
|
|
5270
5418
|
* See comment in {@link CollectionInterface.findByIDCBOR()}
|
|
@@ -5303,9 +5451,11 @@ class WriteTransaction {
|
|
|
5303
5451
|
* @internal
|
|
5304
5452
|
*/
|
|
5305
5453
|
static async init(ditto) {
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5454
|
+
return ditto.deferCloseAsync(async () => {
|
|
5455
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5456
|
+
const writeTransactionPointer = await writeTransaction(dittoHandle.deref());
|
|
5457
|
+
return new WriteTransaction(ditto, writeTransactionPointer);
|
|
5458
|
+
});
|
|
5309
5459
|
}
|
|
5310
5460
|
/**
|
|
5311
5461
|
* Creates a transaction-specific
|
|
@@ -5324,13 +5474,19 @@ class WriteTransaction {
|
|
|
5324
5474
|
// ----------------------------------------------------------- Internal ------
|
|
5325
5475
|
/** @internal */
|
|
5326
5476
|
async commit() {
|
|
5327
|
-
const
|
|
5328
|
-
|
|
5477
|
+
const ditto = this.ditto;
|
|
5478
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5479
|
+
return ditto.deferCloseAsync(async () => {
|
|
5480
|
+
return writeTransactionCommit(dittoHandle.deref(), this.writeTransactionPointer);
|
|
5481
|
+
});
|
|
5329
5482
|
}
|
|
5330
5483
|
/** @internal */
|
|
5331
5484
|
async rollback() {
|
|
5332
|
-
const
|
|
5333
|
-
|
|
5485
|
+
const ditto = this.ditto;
|
|
5486
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5487
|
+
return ditto.deferCloseAsync(async () => {
|
|
5488
|
+
return writeTransactionRollback(dittoHandle.deref(), this.writeTransactionPointer);
|
|
5489
|
+
});
|
|
5334
5490
|
}
|
|
5335
5491
|
/**
|
|
5336
5492
|
* Adds an entry to the list of results that is returned at the end of a
|
|
@@ -5377,14 +5533,18 @@ class Store {
|
|
|
5377
5533
|
* fetch or observe the collections in the store
|
|
5378
5534
|
*/
|
|
5379
5535
|
collections() {
|
|
5380
|
-
return new PendingCollectionsOperation(this
|
|
5536
|
+
return new PendingCollectionsOperation(this);
|
|
5381
5537
|
}
|
|
5382
5538
|
/**
|
|
5383
5539
|
* Returns the names of all available collections in the store of the
|
|
5384
5540
|
* related {@link Ditto} instance.
|
|
5385
5541
|
*/
|
|
5386
5542
|
collectionNames() {
|
|
5387
|
-
|
|
5543
|
+
const ditto = this.ditto;
|
|
5544
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5545
|
+
return ditto.deferClose(() => {
|
|
5546
|
+
return dittoGetCollectionNames(dittoHandle.deref());
|
|
5547
|
+
});
|
|
5388
5548
|
}
|
|
5389
5549
|
/**
|
|
5390
5550
|
* Initiate a write transaction in a callback.
|
|
@@ -5395,26 +5555,20 @@ class Store {
|
|
|
5395
5555
|
* @returns a list of `WriteTransactionResult`s. There is a result for each operation performed as part of the write transaction.
|
|
5396
5556
|
*/
|
|
5397
5557
|
async write(callback) {
|
|
5398
|
-
const transaction = await WriteTransaction.init(this.ditto);
|
|
5399
5558
|
// Run caller's callback, rolling back if needed.
|
|
5400
|
-
|
|
5401
|
-
await
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5559
|
+
return this.ditto.deferCloseAsync(async () => {
|
|
5560
|
+
const transaction = await WriteTransaction.init(this.ditto);
|
|
5561
|
+
try {
|
|
5562
|
+
await callback(transaction);
|
|
5563
|
+
}
|
|
5564
|
+
catch (error) {
|
|
5565
|
+
await transaction.rollback();
|
|
5566
|
+
Logger.warning(`Transaction rolled back due to an error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
|
5567
|
+
throw error;
|
|
5568
|
+
}
|
|
5410
5569
|
await transaction.commit();
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
await transaction.rollback();
|
|
5414
|
-
Logger.warning(`Transaction rolled back due to an error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
|
5415
|
-
throw error;
|
|
5416
|
-
}
|
|
5417
|
-
return transaction.results;
|
|
5570
|
+
return transaction.results;
|
|
5571
|
+
});
|
|
5418
5572
|
}
|
|
5419
5573
|
// ----------------------------------------------------------- Internal ------
|
|
5420
5574
|
/** @internal */
|
|
@@ -5422,14 +5576,24 @@ class Store {
|
|
|
5422
5576
|
this.ditto = ditto;
|
|
5423
5577
|
this.experimental = new ExperimentalStore(ditto);
|
|
5424
5578
|
}
|
|
5579
|
+
/** @internal */
|
|
5580
|
+
close() {
|
|
5581
|
+
this.experimental.close();
|
|
5582
|
+
// NOTE: live query webhook is taken care of by the FFI's
|
|
5583
|
+
// `ditto_shutdown()`, no need to unregister it here.
|
|
5584
|
+
}
|
|
5425
5585
|
/**
|
|
5426
5586
|
* Private method, used only by the Portal https://github.com/getditto/ditto/pull/3652
|
|
5427
5587
|
* @internal
|
|
5428
5588
|
*/
|
|
5429
5589
|
async registerLiveQueryWebhook(collectionName, query, url) {
|
|
5430
|
-
const
|
|
5431
|
-
const
|
|
5432
|
-
return
|
|
5590
|
+
const ditto = this.ditto;
|
|
5591
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5592
|
+
return ditto.deferCloseAsync(async () => {
|
|
5593
|
+
const validatedQuery = validateQuery(query);
|
|
5594
|
+
const idCBOR = await liveQueryWebhookRegister(dittoHandle.deref(), collectionName, validatedQuery, [], 0, 0, url);
|
|
5595
|
+
return new DocumentID(idCBOR, true);
|
|
5596
|
+
});
|
|
5433
5597
|
}
|
|
5434
5598
|
}
|
|
5435
5599
|
|
|
@@ -5457,9 +5621,12 @@ class Presence {
|
|
|
5457
5621
|
* connections between them.
|
|
5458
5622
|
*/
|
|
5459
5623
|
get graph() {
|
|
5460
|
-
const
|
|
5461
|
-
const
|
|
5462
|
-
return
|
|
5624
|
+
const ditto = this.ditto;
|
|
5625
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5626
|
+
return ditto.deferClose(() => {
|
|
5627
|
+
const graphJSONString = dittoPresenceV3(dittoHandle.deref());
|
|
5628
|
+
return JSON.parse(graphJSONString);
|
|
5629
|
+
});
|
|
5463
5630
|
}
|
|
5464
5631
|
/**
|
|
5465
5632
|
* Request information about Ditto peers in range of this device.
|
|
@@ -5486,12 +5653,18 @@ class Presence {
|
|
|
5486
5653
|
this.observerManager = new ObserverManager('PresenceObservation', {
|
|
5487
5654
|
keepAlive: ditto.keepAlive,
|
|
5488
5655
|
register: (callback) => {
|
|
5489
|
-
const
|
|
5490
|
-
|
|
5656
|
+
const ditto = this.ditto;
|
|
5657
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5658
|
+
ditto.deferClose(() => {
|
|
5659
|
+
dittoRegisterPresenceV3Callback(dittoHandle.deref(), callback);
|
|
5660
|
+
});
|
|
5491
5661
|
},
|
|
5492
5662
|
unregister: () => {
|
|
5493
|
-
const
|
|
5494
|
-
|
|
5663
|
+
const ditto = this.ditto;
|
|
5664
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5665
|
+
ditto.deferClose(() => {
|
|
5666
|
+
dittoClearPresenceV3Callback(dittoHandle.deref());
|
|
5667
|
+
});
|
|
5495
5668
|
},
|
|
5496
5669
|
process: (presenceGraphJSONString) => {
|
|
5497
5670
|
const presenceGraph = JSON.parse(presenceGraphJSONString);
|
|
@@ -5499,22 +5672,114 @@ class Presence {
|
|
|
5499
5672
|
},
|
|
5500
5673
|
});
|
|
5501
5674
|
}
|
|
5675
|
+
/** @internal */
|
|
5676
|
+
close() {
|
|
5677
|
+
this.observerManager.close();
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
//
|
|
5682
|
+
/** @internal */
|
|
5683
|
+
class LiveQueryManager {
|
|
5684
|
+
/** @internal */
|
|
5685
|
+
constructor(ditto, keepAlive) {
|
|
5686
|
+
this.finalizationRegistry = new FinalizationRegistry(this.finalize);
|
|
5687
|
+
this.ditto = ditto;
|
|
5688
|
+
this.keepAlive = keepAlive;
|
|
5689
|
+
this.liveQueriesByID = {};
|
|
5690
|
+
}
|
|
5691
|
+
/** @internal */
|
|
5692
|
+
startLiveQuery(liveQuery) {
|
|
5693
|
+
const ditto = this.ditto;
|
|
5694
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5695
|
+
// REFACTOR: the starting closure runs detached from here which is a smell.
|
|
5696
|
+
// Can we make this whole starting mechanism non-async? The culprit is
|
|
5697
|
+
// the workaround for a zalgo with `ditto_live_query_start()` FFI function:
|
|
5698
|
+
// It immediately triggers the live query handler making it run "in-line"
|
|
5699
|
+
// when creating a live query, while subsequent invocations are async
|
|
5700
|
+
// (by definition). This is a classic zalgo case. Fix by making
|
|
5701
|
+
// `ditto_live_query_start()` trigger the first live query callback `async`,
|
|
5702
|
+
// just like the subsequent ones.
|
|
5703
|
+
ditto.deferCloseAsync(async () => {
|
|
5704
|
+
const liveQueryID = liveQuery.liveQueryID;
|
|
5705
|
+
if (!liveQueryID) {
|
|
5706
|
+
throw new Error("Internal inconsistency, tried to add a live query that doesn't have a live query ID (probably stopped).");
|
|
5707
|
+
}
|
|
5708
|
+
const existingLiveQuery = this.liveQueriesByID[liveQueryID];
|
|
5709
|
+
if (existingLiveQuery) {
|
|
5710
|
+
throw new Error('Internal inconsistency, tried to add a live query with an ID that has already been added.');
|
|
5711
|
+
}
|
|
5712
|
+
const weakLiveQuery = new WeakRef(liveQuery);
|
|
5713
|
+
this.liveQueriesByID[liveQueryID] = weakLiveQuery;
|
|
5714
|
+
this.finalizationRegistry.register(liveQuery, liveQueryID, this.finalize);
|
|
5715
|
+
liveQuery.liveQueryManager = this;
|
|
5716
|
+
ditto.keepAlive.retain(`LiveQuery.${liveQueryID}`);
|
|
5717
|
+
return new Promise((resolve, reject) => {
|
|
5718
|
+
// not awaited on purpose; let the invocation of the initial observation
|
|
5719
|
+
// happen in a "fire-and-forget" / detached / escaping fashion, to be
|
|
5720
|
+
// consistent with the subsequent invocations.
|
|
5721
|
+
step(async () => {
|
|
5722
|
+
await liveQueryStart(dittoHandle.deref(), liveQueryID);
|
|
5723
|
+
// @ts-ignore
|
|
5724
|
+
resolve();
|
|
5725
|
+
});
|
|
5726
|
+
});
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5729
|
+
/** @internal */
|
|
5730
|
+
stopLiveQuery(liveQuery) {
|
|
5731
|
+
this.finalizationRegistry.unregister(liveQuery);
|
|
5732
|
+
const liveQueryID = liveQuery.liveQueryID;
|
|
5733
|
+
if (!liveQueryID) {
|
|
5734
|
+
throw new Error("Internal inconsistency, tried to remove a live query that doesn't have a live query ID (probably stopped).");
|
|
5735
|
+
}
|
|
5736
|
+
liveQuery.liveQueryManager = null;
|
|
5737
|
+
this.stopLiveQueryWithID(liveQueryID);
|
|
5738
|
+
}
|
|
5739
|
+
/** @internal */
|
|
5740
|
+
close() {
|
|
5741
|
+
for (const liveQueryID in this.liveQueriesByID) {
|
|
5742
|
+
const weakLiveQuery = this.liveQueriesByID[liveQueryID];
|
|
5743
|
+
const liveQuery = weakLiveQuery.deref();
|
|
5744
|
+
if (liveQuery) {
|
|
5745
|
+
this.stopLiveQuery(liveQuery);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
stopLiveQueryWithID(liveQueryID) {
|
|
5750
|
+
const ditto = this.ditto;
|
|
5751
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5752
|
+
ditto.deferClose(() => {
|
|
5753
|
+
liveQueryStop(dittoHandle.deref(), liveQueryID);
|
|
5754
|
+
this.keepAlive.release(`LiveQuery.${liveQueryID}`);
|
|
5755
|
+
delete this.liveQueriesByID[liveQueryID];
|
|
5756
|
+
});
|
|
5757
|
+
}
|
|
5758
|
+
finalize(liveQueryID) {
|
|
5759
|
+
this.stopLiveQueryWithID(liveQueryID);
|
|
5760
|
+
}
|
|
5502
5761
|
}
|
|
5503
5762
|
|
|
5504
5763
|
//
|
|
5505
5764
|
/**
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5765
|
+
* @internal
|
|
5766
|
+
* @deprecated Replaced by `Presence`.
|
|
5767
|
+
*/
|
|
5509
5768
|
class PresenceManager {
|
|
5510
5769
|
constructor(ditto) {
|
|
5511
5770
|
this.ditto = ditto;
|
|
5771
|
+
this.isClosed = false;
|
|
5512
5772
|
this.isRegistered = false;
|
|
5513
5773
|
this.currentRemotePeers = [];
|
|
5514
5774
|
this.callbacksByPresenceToken = {};
|
|
5515
5775
|
}
|
|
5516
5776
|
/** @internal */
|
|
5517
5777
|
addObserver(callback) {
|
|
5778
|
+
if (this.isClosed) {
|
|
5779
|
+
// REFACTOR: throw a catchable error here, such that calling code
|
|
5780
|
+
// can be more specific when forwarding it to the user.
|
|
5781
|
+
throw new Error(`Internal inconsistency, can't add presence observer, observer mananger close()-ed.`);
|
|
5782
|
+
}
|
|
5518
5783
|
this.registerIfNeeded();
|
|
5519
5784
|
const token = generateEphemeralToken();
|
|
5520
5785
|
this.callbacksByPresenceToken[token] = callback;
|
|
@@ -5529,31 +5794,62 @@ class PresenceManager {
|
|
|
5529
5794
|
}
|
|
5530
5795
|
/** @internal */
|
|
5531
5796
|
removeObserver(token) {
|
|
5532
|
-
this.
|
|
5533
|
-
|
|
5534
|
-
|
|
5797
|
+
const callback = this.callbacksByPresenceToken[token];
|
|
5798
|
+
if (typeof callback === 'undefined') {
|
|
5799
|
+
throw new Error(`Can't remove presence observer, token '${token}' has never been registered before.`);
|
|
5800
|
+
}
|
|
5801
|
+
if (callback === null) {
|
|
5802
|
+
// Observer has already been removed, no-op.
|
|
5803
|
+
return;
|
|
5804
|
+
}
|
|
5805
|
+
if (typeof this.callbacksByPresenceToken[token] != 'undefined') {
|
|
5806
|
+
this.ditto.keepAlive.release(`PresenceObservation.${token}`);
|
|
5807
|
+
// REFACTOR: not deleting the token here will keep eating up
|
|
5808
|
+
// memory over long periods of time. We actually need to track
|
|
5809
|
+
// the observer objects themselves and remove it from the table
|
|
5810
|
+
// as soon as the observer object is garbage collected.
|
|
5811
|
+
this.callbacksByPresenceToken[token] = null;
|
|
5812
|
+
this.unregisterIfNeeded();
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
/** @internal */
|
|
5816
|
+
hasObserver(token) {
|
|
5817
|
+
return typeof this.callbacksByPresenceToken[token] != 'undefined';
|
|
5818
|
+
}
|
|
5819
|
+
/** @internal */
|
|
5820
|
+
close() {
|
|
5821
|
+
this.isClosed = true;
|
|
5822
|
+
for (const token in this.callbacksByPresenceToken) {
|
|
5823
|
+
this.removeObserver(token);
|
|
5824
|
+
}
|
|
5535
5825
|
}
|
|
5536
5826
|
hasObservers() {
|
|
5537
5827
|
return Object.keys(this.callbacksByPresenceToken).length > 0;
|
|
5538
5828
|
}
|
|
5539
5829
|
registerIfNeeded() {
|
|
5540
|
-
const
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
const
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5830
|
+
const ditto = this.ditto;
|
|
5831
|
+
const dittoHandle = Bridge.ditto.handleFor(this.ditto);
|
|
5832
|
+
ditto.deferClose(() => {
|
|
5833
|
+
const needsToRegister = !this.isRegistered;
|
|
5834
|
+
if (needsToRegister) {
|
|
5835
|
+
this.isRegistered = true;
|
|
5836
|
+
const remotePeersJSONString = dittoPresenceV1(dittoHandle.deref());
|
|
5837
|
+
this.currentRemotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers);
|
|
5838
|
+
dittoRegisterPresenceV1Callback(dittoHandle.deref(), this.handlePresenceV1Callback.bind(this));
|
|
5839
|
+
}
|
|
5840
|
+
});
|
|
5548
5841
|
}
|
|
5549
5842
|
unregisterIfNeeded() {
|
|
5550
|
-
const
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
const
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5843
|
+
const ditto = this.ditto;
|
|
5844
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5845
|
+
ditto.deferClose(() => {
|
|
5846
|
+
const needsToUnregister = !this.hasObservers() && this.isRegistered;
|
|
5847
|
+
if (needsToUnregister) {
|
|
5848
|
+
this.isRegistered = false;
|
|
5849
|
+
dittoClearPresenceCallback(dittoHandle.deref());
|
|
5850
|
+
this.currentRemotePeers = [];
|
|
5851
|
+
}
|
|
5852
|
+
});
|
|
5557
5853
|
}
|
|
5558
5854
|
handlePresenceV1Callback(remotePeersJSONString) {
|
|
5559
5855
|
const remotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers);
|
|
@@ -5561,6 +5857,10 @@ class PresenceManager {
|
|
|
5561
5857
|
this.notify();
|
|
5562
5858
|
}
|
|
5563
5859
|
notify() {
|
|
5860
|
+
if (this.isClosed) {
|
|
5861
|
+
// NOTE: we don't notify observers after closing.
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5564
5864
|
for (const token in this.callbacksByPresenceToken) {
|
|
5565
5865
|
const callback = this.callbacksByPresenceToken[token];
|
|
5566
5866
|
callback(this.currentRemotePeers);
|
|
@@ -5579,9 +5879,6 @@ class PresenceManager {
|
|
|
5579
5879
|
};
|
|
5580
5880
|
});
|
|
5581
5881
|
}
|
|
5582
|
-
finalize(token) {
|
|
5583
|
-
this.removeObserver(token);
|
|
5584
|
-
}
|
|
5585
5882
|
compareRemotePeers(left, right) {
|
|
5586
5883
|
// NOTE: we use the exact same sort order here as in the ObjC version.
|
|
5587
5884
|
if (left.connections.length === 0 && right.connections.length > 0)
|
|
@@ -5605,12 +5902,18 @@ class TransportConditionsManager extends ObserverManager {
|
|
|
5605
5902
|
this.ditto = ditto;
|
|
5606
5903
|
}
|
|
5607
5904
|
register(callback) {
|
|
5608
|
-
const
|
|
5609
|
-
|
|
5905
|
+
const ditto = this.ditto;
|
|
5906
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5907
|
+
return ditto.deferClose(() => {
|
|
5908
|
+
return dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), callback);
|
|
5909
|
+
});
|
|
5610
5910
|
}
|
|
5611
5911
|
unregister() {
|
|
5612
|
-
const
|
|
5613
|
-
|
|
5912
|
+
const ditto = this.ditto;
|
|
5913
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
5914
|
+
return ditto.deferClose(() => {
|
|
5915
|
+
return dittoRegisterTransportConditionChangedCallback(dittoHandle.deref(), null);
|
|
5916
|
+
});
|
|
5614
5917
|
}
|
|
5615
5918
|
process(conditionSource, transportCondition) {
|
|
5616
5919
|
/* eslint-disable */
|
|
@@ -5717,277 +6020,582 @@ class Sync {
|
|
|
5717
6020
|
this.updateConnectRetryInterval(stateOld, stateNew);
|
|
5718
6021
|
this.state = stateNew;
|
|
5719
6022
|
}
|
|
6023
|
+
/** @internal */
|
|
6024
|
+
close() {
|
|
6025
|
+
if (this.parameters.isSyncActive) {
|
|
6026
|
+
throw new Error(`Internal inconsistency, can't close sync object while sync is active, please 'stopSync()' first.`);
|
|
6027
|
+
}
|
|
6028
|
+
// Nothing to do, when sync is stopped, this object should be
|
|
6029
|
+
// already be cleaned up properly.
|
|
6030
|
+
}
|
|
5720
6031
|
updatePeerToPeerBluetoothLE(stateOld, stateNew) {
|
|
5721
|
-
const
|
|
5722
|
-
const
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
if
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
6032
|
+
const ditto = this.ditto;
|
|
6033
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6034
|
+
ditto.deferClose(() => {
|
|
6035
|
+
const bluetoothLEOld = stateOld.effectiveTransportConfig.peerToPeer.bluetoothLE;
|
|
6036
|
+
const bluetoothLENew = stateNew.effectiveTransportConfig.peerToPeer.bluetoothLE;
|
|
6037
|
+
const shouldStart = !bluetoothLEOld.isEnabled && bluetoothLENew.isEnabled;
|
|
6038
|
+
const shouldStop = bluetoothLEOld.isEnabled && !bluetoothLENew.isEnabled;
|
|
6039
|
+
if (shouldStart && this.bluetoothLETransportPointer)
|
|
6040
|
+
throw new Error(`Internal inconsistency, when starting BLE transport, no BLE transport pointer should exist.`);
|
|
6041
|
+
if (shouldStop && !this.bluetoothLETransportPointer)
|
|
6042
|
+
throw new Error(`Internal inconsistency, when stopping BLE transport, a BLE transport pointer should exist.`);
|
|
6043
|
+
{
|
|
6044
|
+
// HACK: quick & dirty Linux & Windows hack. A proper implementation
|
|
6045
|
+
// should encapsulate everything behind the transports module. To undo,
|
|
6046
|
+
// remove the whole if block.
|
|
6047
|
+
if (process.platform === 'linux' || process.platform === 'win32') {
|
|
6048
|
+
if (shouldStart) {
|
|
6049
|
+
const clientTransport = dittoAddInternalBLEClientTransport(dittoHandle.deref());
|
|
6050
|
+
const serverTransport = dittoAddInternalBLEServerTransport(dittoHandle.deref());
|
|
6051
|
+
const blePlatform = { clientTransport, serverTransport };
|
|
6052
|
+
this.bluetoothLETransportPointer = blePlatform;
|
|
6053
|
+
}
|
|
6054
|
+
if (shouldStop) {
|
|
6055
|
+
const blePlatform = this.bluetoothLETransportPointer;
|
|
6056
|
+
const { clientTransport, serverTransport } = blePlatform;
|
|
6057
|
+
bleServerFreeHandle(serverTransport);
|
|
6058
|
+
bleClientFreeHandle(clientTransport);
|
|
6059
|
+
this.bluetoothLETransportPointer = null;
|
|
6060
|
+
}
|
|
6061
|
+
return;
|
|
5747
6062
|
}
|
|
5748
|
-
return;
|
|
5749
6063
|
}
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
}
|
|
6064
|
+
if (shouldStart) {
|
|
6065
|
+
this.bluetoothLETransportPointer = bleCreate(dittoHandle.deref());
|
|
6066
|
+
}
|
|
6067
|
+
if (shouldStop) {
|
|
6068
|
+
bleDestroy(this.bluetoothLETransportPointer);
|
|
6069
|
+
delete this.bluetoothLETransportPointer;
|
|
6070
|
+
}
|
|
6071
|
+
});
|
|
5758
6072
|
}
|
|
5759
6073
|
updatePeerToPeerAWDL(stateOld, stateNew) {
|
|
5760
|
-
const
|
|
5761
|
-
const
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
6074
|
+
const ditto = this.ditto;
|
|
6075
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6076
|
+
ditto.deferClose(() => {
|
|
6077
|
+
const awdlOld = stateOld.effectiveTransportConfig.peerToPeer.awdl;
|
|
6078
|
+
const awdlNew = stateNew.effectiveTransportConfig.peerToPeer.awdl;
|
|
6079
|
+
const shouldStart = !awdlOld.isEnabled && awdlNew.isEnabled;
|
|
6080
|
+
const shouldStop = awdlOld.isEnabled && !awdlNew.isEnabled;
|
|
6081
|
+
if (shouldStart && this.awdlTransportPointer)
|
|
6082
|
+
throw new Error(`Internal inconsistency, when starting AWDL transport, no AWDL transport pointer should exist.`);
|
|
6083
|
+
if (shouldStop && !this.awdlTransportPointer)
|
|
6084
|
+
throw new Error(`Internal inconsistency, when stopping AWDL transport, an AWDL transport pointer should exist.`);
|
|
6085
|
+
if (shouldStart) {
|
|
6086
|
+
this.awdlTransportPointer = awdlCreate(dittoHandle.deref());
|
|
6087
|
+
}
|
|
6088
|
+
if (shouldStop) {
|
|
6089
|
+
awdlDestroy(this.awdlTransportPointer);
|
|
6090
|
+
this.awdlTransportPointer = null;
|
|
6091
|
+
}
|
|
6092
|
+
});
|
|
5776
6093
|
}
|
|
5777
6094
|
updatePeerToPeerLAN(stateOld, stateNew) {
|
|
5778
|
-
const
|
|
5779
|
-
const
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
if
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
6095
|
+
const ditto = this.ditto;
|
|
6096
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6097
|
+
ditto.deferClose(() => {
|
|
6098
|
+
const lanOld = stateOld.effectiveTransportConfig.peerToPeer.lan;
|
|
6099
|
+
const lanNew = stateNew.effectiveTransportConfig.peerToPeer.lan;
|
|
6100
|
+
{
|
|
6101
|
+
// HACK: quick & dirty Linux & Windows hack. A proper implementation
|
|
6102
|
+
// should encapsulate everything behind the transports module. To undo,
|
|
6103
|
+
// remove the whole if block.
|
|
6104
|
+
if (process.platform === 'win32' || process.platform === 'linux') {
|
|
6105
|
+
if (lanOld.isEnabled) {
|
|
6106
|
+
if (lanOld.isMdnsEnabled) {
|
|
6107
|
+
mdnsClientFreeHandle(this.mdnsClientTransportPointer);
|
|
6108
|
+
this.mdnsClientTransportPointer = null;
|
|
6109
|
+
mdnsServerFreeHandle(this.mdnsServerAdvertiserPointer);
|
|
6110
|
+
this.mdnsServerAdvertiserPointer = null;
|
|
6111
|
+
}
|
|
6112
|
+
if (lanOld.isMulticastEnabled) {
|
|
6113
|
+
dittoRemoveMulticastTransport(dittoHandle.deref());
|
|
6114
|
+
}
|
|
5795
6115
|
}
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
6116
|
+
if (lanNew.isEnabled) {
|
|
6117
|
+
if (lanNew.isMdnsEnabled) {
|
|
6118
|
+
this.mdnsClientTransportPointer = dittoAddInternalMdnsTransport(dittoHandle.deref());
|
|
6119
|
+
this.mdnsServerAdvertiserPointer = dittoAddInternalMdnsAdvertiser(dittoHandle.deref());
|
|
6120
|
+
}
|
|
6121
|
+
if (lanNew.isMulticastEnabled) {
|
|
6122
|
+
dittoAddMulticastTransport(dittoHandle.deref());
|
|
6123
|
+
}
|
|
5804
6124
|
}
|
|
6125
|
+
return;
|
|
5805
6126
|
}
|
|
5806
|
-
return;
|
|
5807
|
-
}
|
|
5808
|
-
}
|
|
5809
|
-
// IDEA: move the logic for this dance into stateFrom() signal
|
|
5810
|
-
// via some additional state signaling the actions here.
|
|
5811
|
-
if (lanOld.isEnabled) {
|
|
5812
|
-
if (lanOld.isMdnsEnabled) {
|
|
5813
|
-
lanDestroy(this.lanTransportPointer);
|
|
5814
|
-
delete this.lanTransportPointer;
|
|
5815
6127
|
}
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
6128
|
+
// IDEA: move the logic for this dance into stateFrom() signal
|
|
6129
|
+
// via some additional state signaling the actions here.
|
|
6130
|
+
if (lanOld.isEnabled) {
|
|
6131
|
+
if (lanOld.isMdnsEnabled) {
|
|
6132
|
+
lanDestroy(this.lanTransportPointer);
|
|
6133
|
+
delete this.lanTransportPointer;
|
|
6134
|
+
}
|
|
6135
|
+
if (lanOld.isMulticastEnabled) {
|
|
6136
|
+
dittoRemoveMulticastTransport(dittoHandle.deref());
|
|
6137
|
+
}
|
|
5823
6138
|
}
|
|
5824
|
-
if (lanNew.
|
|
5825
|
-
|
|
6139
|
+
if (lanNew.isEnabled) {
|
|
6140
|
+
if (lanNew.isMdnsEnabled) {
|
|
6141
|
+
this.lanTransportPointer = lanCreate(dittoHandle.deref());
|
|
6142
|
+
}
|
|
6143
|
+
if (lanNew.isMulticastEnabled) {
|
|
6144
|
+
dittoAddMulticastTransport(dittoHandle.deref());
|
|
6145
|
+
}
|
|
5826
6146
|
}
|
|
5827
|
-
}
|
|
6147
|
+
});
|
|
5828
6148
|
}
|
|
5829
6149
|
updateListenTCP(stateOld, stateNew) {
|
|
5830
|
-
const
|
|
5831
|
-
const
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
6150
|
+
const ditto = this.ditto;
|
|
6151
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6152
|
+
ditto.deferClose(() => {
|
|
6153
|
+
const tcpOld = stateOld.effectiveTransportConfig.listen.tcp;
|
|
6154
|
+
const tcpNew = stateNew.effectiveTransportConfig.listen.tcp;
|
|
6155
|
+
if (TransportConfig.areListenTCPsEqual(tcpNew, tcpOld))
|
|
6156
|
+
return;
|
|
6157
|
+
if (tcpOld.isEnabled)
|
|
6158
|
+
dittoStopTCPServer(dittoHandle.deref());
|
|
6159
|
+
if (tcpNew.isEnabled)
|
|
6160
|
+
dittoStartTCPServer(dittoHandle.deref(), `${tcpNew.interfaceIP}:${tcpNew.port}`);
|
|
6161
|
+
});
|
|
5839
6162
|
}
|
|
5840
6163
|
updateListenHTTP(stateOld, stateNew) {
|
|
5841
|
-
const
|
|
5842
|
-
const
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
6164
|
+
const ditto = this.ditto;
|
|
6165
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6166
|
+
ditto.deferClose(() => {
|
|
6167
|
+
const httpOld = stateOld.effectiveTransportConfig.listen.http;
|
|
6168
|
+
const httpNew = stateNew.effectiveTransportConfig.listen.http;
|
|
6169
|
+
if (TransportConfig.areListenHTTPsEqual(httpOld, httpNew))
|
|
6170
|
+
return;
|
|
6171
|
+
if (httpOld.isEnabled)
|
|
6172
|
+
dittoStopHTTPServer(dittoHandle.deref());
|
|
6173
|
+
/* eslint-disable */
|
|
6174
|
+
if (httpNew.isEnabled) {
|
|
6175
|
+
dittoStartHTTPServer(dittoHandle.deref(), `${httpNew.interfaceIP}:${httpNew.port}`, httpNew.staticContentPath || null, httpNew.websocketSync ? 'Enabled' : 'Disabled', httpNew.tlsCertificatePath || null, httpNew.tlsKeyPath || null);
|
|
6176
|
+
}
|
|
6177
|
+
/* eslint-enable */
|
|
6178
|
+
});
|
|
5853
6179
|
}
|
|
5854
6180
|
updateConnectTCPServers(stateOld, stateNew) {
|
|
5855
|
-
|
|
5856
|
-
const
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
const
|
|
5868
|
-
const
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
const
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
6181
|
+
const ditto = this.ditto;
|
|
6182
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6183
|
+
ditto.deferClose(() => {
|
|
6184
|
+
// REFACTOR: same "algorithm" as for Websockets below, DRY up.
|
|
6185
|
+
const currentTCPServers = Object.getOwnPropertyNames(this.staticTCPClientsByAddress);
|
|
6186
|
+
const desiredTCPServers = stateNew.effectiveTransportConfig.connect.tcpServers;
|
|
6187
|
+
const tcpServersToConnectToSet = new Set(desiredTCPServers);
|
|
6188
|
+
for (const tcpServer of currentTCPServers)
|
|
6189
|
+
tcpServersToConnectToSet.delete(tcpServer);
|
|
6190
|
+
const tcpServersToDisconnectFromSet = new Set(currentTCPServers);
|
|
6191
|
+
for (const tcpServer of desiredTCPServers)
|
|
6192
|
+
tcpServersToDisconnectFromSet.delete(tcpServer);
|
|
6193
|
+
const tcpServersToConnectTo = tcpServersToConnectToSet.values();
|
|
6194
|
+
const tcpServersToDisconnectFrom = tcpServersToDisconnectFromSet.values();
|
|
6195
|
+
for (const tcpServer of tcpServersToConnectTo) {
|
|
6196
|
+
const staticTCPClientPointer = addStaticTCPClient(dittoHandle.deref(), tcpServer);
|
|
6197
|
+
const staticTCPClient = Bridge.staticTCPClient.bridge(staticTCPClientPointer);
|
|
6198
|
+
this.staticTCPClientsByAddress[tcpServer] = staticTCPClient;
|
|
6199
|
+
}
|
|
6200
|
+
for (const tcpServer of tcpServersToDisconnectFrom) {
|
|
6201
|
+
// REFACTOR: we have to make sure freeing the tcpServer handle is done
|
|
6202
|
+
// BEFORE the Ditto instance itself is freed.
|
|
6203
|
+
const staticTCPClient = this.staticTCPClientsByAddress[tcpServer];
|
|
6204
|
+
if (!staticTCPClient)
|
|
6205
|
+
throw new Error(`Internal inconsistency, can't disconnect from TCP address '${tcpServer}', no staticTCPClient found.`);
|
|
6206
|
+
const staticTCPClientPointer = Bridge.staticTCPClient.handleFor(staticTCPClient).deref();
|
|
6207
|
+
// Unregister to avoid double-free for this pointer, then free manually:
|
|
6208
|
+
Bridge.staticTCPClient.unregister(staticTCPClient);
|
|
6209
|
+
staticTCPClientFreeHandle(staticTCPClientPointer);
|
|
6210
|
+
delete this.staticTCPClientsByAddress[tcpServer];
|
|
6211
|
+
}
|
|
6212
|
+
});
|
|
5883
6213
|
}
|
|
5884
6214
|
updateConnectWebsocketURLs(stateOld, stateNew) {
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
const
|
|
5900
|
-
const
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
const
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
6215
|
+
const ditto = this.ditto;
|
|
6216
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6217
|
+
ditto.deferClose(() => {
|
|
6218
|
+
// REFACTOR: same "algorithm" as for TCP servers above, DRY up.
|
|
6219
|
+
// IDEA: normalize URLs so that we don't connect to the same URL twice?
|
|
6220
|
+
const currentWebsocketURLs = Object.getOwnPropertyNames(this.websocketClientsByURL);
|
|
6221
|
+
const desiredWebsocketURLs = stateNew.effectiveTransportConfig.connect.websocketURLs.slice();
|
|
6222
|
+
const websocketURLsToConnectToSet = new Set(desiredWebsocketURLs);
|
|
6223
|
+
for (const websocketURL of currentWebsocketURLs)
|
|
6224
|
+
websocketURLsToConnectToSet.delete(websocketURL);
|
|
6225
|
+
const websocketURLsToDisconnectFromSet = new Set(currentWebsocketURLs);
|
|
6226
|
+
for (const websocketURL of desiredWebsocketURLs)
|
|
6227
|
+
websocketURLsToDisconnectFromSet.delete(websocketURL);
|
|
6228
|
+
const websocketURLsToConnectTo = websocketURLsToConnectToSet.values();
|
|
6229
|
+
const websocketURLsToDisconnectFrom = websocketURLsToDisconnectFromSet.values();
|
|
6230
|
+
const routingHint = stateNew.effectiveTransportConfig.global.routingHint;
|
|
6231
|
+
for (const websocketURL of websocketURLsToConnectTo) {
|
|
6232
|
+
const websocketClientPointer = addWebsocketClient(dittoHandle.deref(), websocketURL, routingHint);
|
|
6233
|
+
const websocketClient = Bridge.websocketClient.bridge(websocketClientPointer);
|
|
6234
|
+
this.websocketClientsByURL[websocketURL] = websocketClient;
|
|
6235
|
+
}
|
|
6236
|
+
for (const websocketURL of websocketURLsToDisconnectFrom) {
|
|
6237
|
+
// REFACTOR: we have to make sure freeing the websocket handle is done
|
|
6238
|
+
// BEFORE the Ditto instance itself is freed.
|
|
6239
|
+
const websocketClient = this.websocketClientsByURL[websocketURL];
|
|
6240
|
+
if (!websocketClient)
|
|
6241
|
+
throw new Error(`Internal inconsistency, can't disconnect from websocket URL '${websocketURL}', no websocketClient found.`);
|
|
6242
|
+
const websocketClientPointer = Bridge.websocketClient.handleFor(websocketClient).deref();
|
|
6243
|
+
// Unregister to avoid double-free for this pointer, then free manually:
|
|
6244
|
+
Bridge.websocketClient.unregister(websocketClient);
|
|
6245
|
+
websocketClientFreeHandle(websocketClientPointer);
|
|
6246
|
+
delete this.websocketClientsByURL[websocketURL];
|
|
6247
|
+
}
|
|
6248
|
+
});
|
|
5915
6249
|
}
|
|
5916
6250
|
updateGlobal(stateOld, stateNew) {
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
6251
|
+
const ditto = this.ditto;
|
|
6252
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6253
|
+
ditto.deferClose(() => {
|
|
6254
|
+
if (stateOld.effectiveTransportConfig.global.syncGroup !== stateNew.effectiveTransportConfig.global.syncGroup) {
|
|
6255
|
+
dittoSetSyncGroup(dittoHandle.deref(), stateNew.effectiveTransportConfig.global.syncGroup);
|
|
6256
|
+
}
|
|
6257
|
+
});
|
|
5920
6258
|
}
|
|
5921
6259
|
updateConnectRetryInterval(stateOld, stateNew) {
|
|
5922
|
-
|
|
6260
|
+
const ditto = this.ditto;
|
|
6261
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6262
|
+
ditto.deferClose(() => {
|
|
6263
|
+
dittoSetConnectRetryInterval(dittoHandle.deref(), stateNew.effectiveTransportConfig.connect.retryInterval);
|
|
6264
|
+
});
|
|
5923
6265
|
}
|
|
5924
6266
|
}
|
|
5925
6267
|
/**
|
|
5926
6268
|
* @private
|
|
5927
6269
|
*/
|
|
5928
6270
|
function stateFrom(parameters) {
|
|
5929
|
-
// IMPORTANT: this function maps a set of sync parameters to an effective
|
|
5930
|
-
// transport config plus some extra state where need be. Make sure
|
|
5931
|
-
// to keep this function free of side-effects.
|
|
5932
|
-
var _a;
|
|
5933
|
-
const transportConfig = parameters.transportConfig.copy();
|
|
5934
|
-
const identity = parameters.identity;
|
|
5935
6271
|
const ditto = parameters.ditto;
|
|
5936
|
-
const
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
transportConfig.peerToPeer.
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
6272
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6273
|
+
return ditto.deferClose(() => {
|
|
6274
|
+
// IMPORTANT: this function maps a set of sync parameters to an effective
|
|
6275
|
+
// transport config plus some extra state where need be. Make sure
|
|
6276
|
+
// to keep this function free of side-effects.
|
|
6277
|
+
var _a;
|
|
6278
|
+
const transportConfig = parameters.transportConfig.copy();
|
|
6279
|
+
const identity = parameters.identity;
|
|
6280
|
+
const isSyncActive = parameters.isSyncActive;
|
|
6281
|
+
const isX509Valid = parameters.isX509Valid;
|
|
6282
|
+
const isWebValid = parameters.isWebValid;
|
|
6283
|
+
transportConfig.connect.tcpServers;
|
|
6284
|
+
transportConfig.connect.websocketURLs;
|
|
6285
|
+
let appID;
|
|
6286
|
+
let customDittoCloudURL;
|
|
6287
|
+
let isDittoCloudSyncEnabled = false;
|
|
6288
|
+
if (identity.type === 'onlinePlayground' || identity.type === 'onlineWithAuthentication') {
|
|
6289
|
+
// NOTE: enableDittoCloudSync is true by default for any online identity.
|
|
6290
|
+
appID = identity.appID;
|
|
6291
|
+
customDittoCloudURL = (_a = identity.customDittoCloudURL) !== null && _a !== void 0 ? _a : null;
|
|
6292
|
+
isDittoCloudSyncEnabled = identity.hasOwnProperty('enableDittoCloudSync') ? identity.enableDittoCloudSync : true;
|
|
6293
|
+
}
|
|
6294
|
+
// Not all P2P transports are always supported.
|
|
6295
|
+
const unavailableButEnabledP2PTransports = [];
|
|
6296
|
+
const isBLEAvailable = bleIsAvailable(dittoHandle.deref());
|
|
6297
|
+
const isAWDLAvailable = awdlIsAvailable(dittoHandle.deref());
|
|
6298
|
+
const isLANAvailable = lanIsAvailable(dittoHandle.deref());
|
|
6299
|
+
if (transportConfig.peerToPeer.bluetoothLE.isEnabled && !isBLEAvailable) {
|
|
6300
|
+
unavailableButEnabledP2PTransports.push('BluetoothLE');
|
|
6301
|
+
}
|
|
6302
|
+
if (transportConfig.peerToPeer.awdl.isEnabled && !isAWDLAvailable) {
|
|
6303
|
+
unavailableButEnabledP2PTransports.push('AWDL');
|
|
6304
|
+
}
|
|
6305
|
+
if (transportConfig.peerToPeer.lan.isEnabled && !isLANAvailable) {
|
|
6306
|
+
unavailableButEnabledP2PTransports.push('LAN');
|
|
6307
|
+
}
|
|
6308
|
+
if (unavailableButEnabledP2PTransports.length > 0) {
|
|
6309
|
+
throw new Error(`The following P2P transports are enabled in the transport config but are not supported in the current environment: ${unavailableButEnabledP2PTransports.join(', ')}`);
|
|
6310
|
+
}
|
|
6311
|
+
if (!isSyncActive || !isX509Valid) {
|
|
6312
|
+
transportConfig.peerToPeer.bluetoothLE.isEnabled = false;
|
|
6313
|
+
transportConfig.peerToPeer.awdl.isEnabled = false;
|
|
6314
|
+
transportConfig.peerToPeer.lan.isEnabled = false;
|
|
6315
|
+
transportConfig.listen.tcp.isEnabled = false;
|
|
6316
|
+
transportConfig.connect.tcpServers = [];
|
|
6317
|
+
}
|
|
6318
|
+
if (!isSyncActive || !isWebValid) {
|
|
6319
|
+
transportConfig.connect.websocketURLs = [];
|
|
6320
|
+
}
|
|
6321
|
+
if (!isSyncActive) {
|
|
6322
|
+
transportConfig.listen.http.isEnabled = false;
|
|
6323
|
+
}
|
|
6324
|
+
// NOTE: we have to smuggle in an additional Ditto Cloud websocket URL to
|
|
6325
|
+
// connect to if cloud sync is enabled.
|
|
6326
|
+
if (isSyncActive && isWebValid && isDittoCloudSyncEnabled) {
|
|
6327
|
+
const dittoCloudURL = customDittoCloudURL !== null && customDittoCloudURL !== void 0 ? customDittoCloudURL : defaultDittoCloudURL(appID);
|
|
6328
|
+
transportConfig.connect.websocketURLs.push(dittoCloudURL);
|
|
6329
|
+
}
|
|
6330
|
+
return {
|
|
6331
|
+
underlyingSyncParameters: parameters,
|
|
6332
|
+
effectiveTransportConfig: transportConfig.freeze(),
|
|
6333
|
+
};
|
|
6334
|
+
});
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
//
|
|
6338
|
+
/**
|
|
6339
|
+
* Tracks `Subscription` instances in order to remove them when Ditto is
|
|
6340
|
+
* closed.
|
|
6341
|
+
*
|
|
6342
|
+
* @internal
|
|
6343
|
+
*/
|
|
6344
|
+
class SubscriptionManager {
|
|
6345
|
+
/** @internal */
|
|
6346
|
+
constructor(ditto) {
|
|
6347
|
+
this.ditto = ditto;
|
|
6348
|
+
this.subscriptions = {};
|
|
6349
|
+
this.finalizationRegistry = new FinalizationRegistry(this.removeWithContextinfo.bind(this));
|
|
6350
|
+
}
|
|
6351
|
+
/**
|
|
6352
|
+
* Begin tracking a subscription instance and start it.
|
|
6353
|
+
*
|
|
6354
|
+
* @internal */
|
|
6355
|
+
add(subscription) {
|
|
6356
|
+
const ditto = this.ditto;
|
|
6357
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6358
|
+
const contextInfo = subscription.contextInfo;
|
|
6359
|
+
ditto.deferClose(async () => {
|
|
6360
|
+
this.subscriptions[contextInfo.id] = new WeakRef(subscription);
|
|
6361
|
+
this.finalizationRegistry.register(subscription, subscription.contextInfo, subscription);
|
|
6362
|
+
addSubscription(dittoHandle.deref(), contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
|
|
6363
|
+
});
|
|
6364
|
+
}
|
|
6365
|
+
/**
|
|
6366
|
+
* Stop tracking a subscription instance and cancel it.
|
|
6367
|
+
*
|
|
6368
|
+
* @internal */
|
|
6369
|
+
remove(subscription) {
|
|
6370
|
+
if (this.subscriptions[subscription.contextInfo.id] == null) {
|
|
6371
|
+
throw new Error(`Internal inconsistency, tried to remove a subscription that is not tracked: ${subscription.contextInfo.id}`);
|
|
6372
|
+
}
|
|
6373
|
+
this.finalizationRegistry.unregister(subscription);
|
|
6374
|
+
this.removeWithContextinfo(subscription.contextInfo);
|
|
6375
|
+
}
|
|
6376
|
+
/**
|
|
6377
|
+
* Stop tracking all subscriptions and cancel them.
|
|
6378
|
+
*
|
|
6379
|
+
* @internal */
|
|
6380
|
+
close() {
|
|
6381
|
+
this.ditto.deferClose(async () => {
|
|
6382
|
+
for (const subscriptionID in this.subscriptions) {
|
|
6383
|
+
const subscription = this.subscriptions[subscriptionID].deref();
|
|
6384
|
+
if (subscription != null) {
|
|
6385
|
+
// This doesn't call `Subscription.cancel()` because that is not
|
|
6386
|
+
// async and we want to wait for all subscriptions to be removed.
|
|
6387
|
+
this.remove(subscription);
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
});
|
|
6391
|
+
}
|
|
6392
|
+
/**
|
|
6393
|
+
* Remove tracked subscription without unregistering from finalization
|
|
6394
|
+
* registry.
|
|
6395
|
+
*
|
|
6396
|
+
* @internal */
|
|
6397
|
+
removeWithContextinfo(contextInfo) {
|
|
6398
|
+
const ditto = this.ditto;
|
|
6399
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6400
|
+
ditto.deferClose(() => {
|
|
6401
|
+
delete this.subscriptions[contextInfo.id];
|
|
6402
|
+
removeSubscription(dittoHandle.deref(), contextInfo.collectionName, contextInfo.query, contextInfo.queryArgsCBOR, contextInfo.orderBys, contextInfo.limit, contextInfo.offset);
|
|
6403
|
+
});
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
|
|
6407
|
+
//
|
|
6408
|
+
/**
|
|
6409
|
+
* These objects are returned by calls to
|
|
6410
|
+
* {@link Collection.fetchAttachment | fetchAttachment()} on {@link Collection}
|
|
6411
|
+
* and allow you to stop an in-flight attachment fetch.
|
|
6412
|
+
*/
|
|
6413
|
+
class AttachmentFetcher {
|
|
6414
|
+
/**
|
|
6415
|
+
* Stops fetching the associated attachment and cleans up any associated
|
|
6416
|
+
* resources.
|
|
6417
|
+
*
|
|
6418
|
+
* Note that you are not required to call `stop()` once your attachment fetch
|
|
6419
|
+
* operation has finished. The method primarily exists to allow you to cancel
|
|
6420
|
+
* an attachment fetch request while it is ongoing if you no longer wish for
|
|
6421
|
+
* the attachment to be made available locally to the device.
|
|
6422
|
+
*/
|
|
6423
|
+
stop() {
|
|
6424
|
+
// No need for synchronicity here: we let the "stop promise" float / run in
|
|
6425
|
+
// a detached fashion since there is no point in awaiting the "stop
|
|
6426
|
+
// signaling" itself.
|
|
6427
|
+
step(async () => {
|
|
6428
|
+
await this.manager.stopAttachmentFetcher(this);
|
|
6429
|
+
if (this.rejectPendingFetch != null) {
|
|
6430
|
+
this.rejectPendingFetch();
|
|
6431
|
+
}
|
|
6432
|
+
});
|
|
6433
|
+
}
|
|
6434
|
+
/** @internal */
|
|
6435
|
+
then(onfulfilled, onrejected) {
|
|
6436
|
+
return this.attachment.then(onfulfilled, onrejected);
|
|
6437
|
+
}
|
|
6438
|
+
/** @internal */
|
|
6439
|
+
constructor(ditto, token, manager, eventHandler) {
|
|
6440
|
+
// --------------------------------------- Internal ------------------------
|
|
6441
|
+
/** @internal */
|
|
6442
|
+
this.cancelTokenPromise = null;
|
|
6443
|
+
/**
|
|
6444
|
+
* This function is defined while a fetch is in progress and is used to reject
|
|
6445
|
+
* the promise `this.attachment` when the fetch is canceled.
|
|
6446
|
+
*
|
|
6447
|
+
* @internal
|
|
6448
|
+
*/
|
|
6449
|
+
this.rejectPendingFetch = null;
|
|
6450
|
+
this.ditto = ditto;
|
|
6451
|
+
this.token = token;
|
|
6452
|
+
this.manager = manager;
|
|
6453
|
+
this.id = generateEphemeralToken();
|
|
6454
|
+
const eventHandlerOrNoOp = eventHandler || function () { };
|
|
6455
|
+
const dittoHandle = Bridge.ditto.handleFor(ditto);
|
|
6456
|
+
this.attachment = new Promise((resolve, reject) => {
|
|
6457
|
+
// REFACTOR: The callbacks hold quite a bunch of objects with most
|
|
6458
|
+
// probably lead to retain cycles and therefore memory leaks. This needs
|
|
6459
|
+
// to be
|
|
6460
|
+
// fixed.
|
|
6461
|
+
const onComplete = (attachmentHandlePointer) => {
|
|
6462
|
+
const attachment = new Attachment(this.ditto, this.token);
|
|
6463
|
+
Bridge.attachment.bridge(attachmentHandlePointer, () => attachment);
|
|
6464
|
+
eventHandlerOrNoOp({ type: 'Completed', attachment });
|
|
6465
|
+
this.rejectPendingFetch = null;
|
|
6466
|
+
resolve(attachment);
|
|
6467
|
+
};
|
|
6468
|
+
const onProgress = (downloaded, toDownload) => {
|
|
6469
|
+
eventHandlerOrNoOp({ type: 'Progress', totalBytes: toDownload, downloadedBytes: downloaded });
|
|
6470
|
+
};
|
|
6471
|
+
const onDelete = () => {
|
|
6472
|
+
eventHandlerOrNoOp({ type: 'Deleted' });
|
|
6473
|
+
this.rejectPendingFetch = null;
|
|
6474
|
+
resolve(null);
|
|
6475
|
+
};
|
|
6476
|
+
const onError = () => {
|
|
6477
|
+
};
|
|
6478
|
+
// The core doesn't call any of the handlers defined above when a fetch is
|
|
6479
|
+
// cancelled through `this.stop()` so we use this function to reject the
|
|
6480
|
+
// promise from the outside.
|
|
6481
|
+
this.rejectPendingFetch = () => {
|
|
6482
|
+
reject(new Error('Attachment fetch was canceled'));
|
|
6483
|
+
};
|
|
6484
|
+
// Not awaited yet; only needs to be fully resolved (and `await`ed over)
|
|
6485
|
+
// when using the actual `cancelTokenPromise`, which only happens in
|
|
6486
|
+
// `AttachmentFetcherManager.stopWithContextInfo()`.
|
|
6487
|
+
// @ts-expect-error setting readonly property
|
|
6488
|
+
this.cancelTokenPromise = dittoResolveAttachment(dittoHandle.deref(), token.id, { onComplete, onProgress, onDelete }, onError);
|
|
6489
|
+
});
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
//
|
|
6494
|
+
/**
|
|
6495
|
+
* Manages attachment fetchers to make sure we free all resources when the
|
|
6496
|
+
* fetcher is garbage collected and to allow us to wait for freeing of
|
|
6497
|
+
* ressources to be finished before the ditto instance is closed.
|
|
6498
|
+
*
|
|
6499
|
+
* @internal */
|
|
6500
|
+
class AttachmentFetcherManager {
|
|
6501
|
+
/** @internal */
|
|
6502
|
+
constructor(ditto) {
|
|
6503
|
+
// --- Private ---
|
|
6504
|
+
this.contextInfoByID = {};
|
|
6505
|
+
this.finalizationRegistry = new FinalizationRegistry(this.stopWithContextInfo);
|
|
6506
|
+
this.ditto = ditto;
|
|
6507
|
+
}
|
|
6508
|
+
/**
|
|
6509
|
+
* Start an attachment fetcher.
|
|
6510
|
+
*
|
|
6511
|
+
* @internal */
|
|
6512
|
+
startAttachmentFetcher(token, eventHandler) {
|
|
6513
|
+
return this.ditto.deferClose(() => {
|
|
6514
|
+
const attachmentFetcher = new AttachmentFetcher(this.ditto, token, this, eventHandler);
|
|
6515
|
+
// Register in finalization registry.
|
|
6516
|
+
const contextInfo = {
|
|
6517
|
+
id: attachmentFetcher.id,
|
|
6518
|
+
attachmentTokenID: token.id,
|
|
6519
|
+
cancelTokenPromise: attachmentFetcher.cancelTokenPromise,
|
|
6520
|
+
attachmentFetcher: new WeakRef(attachmentFetcher),
|
|
6521
|
+
};
|
|
6522
|
+
this.finalizationRegistry.register(attachmentFetcher, contextInfo, attachmentFetcher);
|
|
6523
|
+
// Keep a reference to the context info so that we can stop the fetcher.
|
|
6524
|
+
this.contextInfoByID[attachmentFetcher.id] = contextInfo;
|
|
6525
|
+
// Prevent cancellation of the fetch once it was fulfilled or rejected.
|
|
6526
|
+
const resetCancelToken = () => {
|
|
6527
|
+
if (this.contextInfoByID[attachmentFetcher.id] != null) {
|
|
6528
|
+
this.contextInfoByID[attachmentFetcher.id].cancelTokenPromise = null;
|
|
6529
|
+
}
|
|
6530
|
+
};
|
|
6531
|
+
attachmentFetcher.attachment.then((value) => {
|
|
6532
|
+
resetCancelToken();
|
|
6533
|
+
return value;
|
|
6534
|
+
}, (reason) => {
|
|
6535
|
+
resetCancelToken();
|
|
6536
|
+
return reason;
|
|
6537
|
+
});
|
|
6538
|
+
// Keep the attachment fetcher alive until it is stopped.
|
|
6539
|
+
this.ditto.keepAlive.retain(`AttachmentFetcher.${attachmentFetcher.id})`);
|
|
6540
|
+
return attachmentFetcher;
|
|
6541
|
+
});
|
|
6542
|
+
}
|
|
6543
|
+
/**
|
|
6544
|
+
* Stop an attachment fetcher and wait for it to be stopped.
|
|
6545
|
+
*
|
|
6546
|
+
* @internal */
|
|
6547
|
+
async stopAttachmentFetcher(attachmentFetcher) {
|
|
6548
|
+
this.finalizationRegistry.unregister(attachmentFetcher);
|
|
6549
|
+
const contextInfo = this.contextInfoByID[attachmentFetcher.id];
|
|
6550
|
+
if (contextInfo == null) {
|
|
6551
|
+
throw new Error(`Internal inconsistency: cannot stop attachment fetcher ${attachmentFetcher.id}, which is not registered.`);
|
|
6552
|
+
}
|
|
6553
|
+
await this.stopWithContextInfo(contextInfo);
|
|
6554
|
+
}
|
|
6555
|
+
/**
|
|
6556
|
+
* Closing the manager will cancel all attachment fetchers.
|
|
6557
|
+
*
|
|
6558
|
+
* @internal
|
|
6559
|
+
*/
|
|
6560
|
+
close() {
|
|
6561
|
+
// REFACTOR: the closing closure runs detached from here which is a smell.
|
|
6562
|
+
// Can we make this whole closing mechanism non-async? The culprit is
|
|
6563
|
+
// the cancelTokenPromise that _have to_ `await` on close. The reason
|
|
6564
|
+
// for this is that the FFI function `ditto_resolve_attachment()` is
|
|
6565
|
+
// async but should be non-async. Refactor and make that non-async, then
|
|
6566
|
+
// de-async-ify the closing mechanism.
|
|
6567
|
+
this.ditto.deferCloseAsync(async () => {
|
|
6568
|
+
const contextInfos = Object.values(this.contextInfoByID);
|
|
6569
|
+
const stopped = contextInfos.map(async (contextInfo) => {
|
|
6570
|
+
const attachmentFetcher = contextInfo.attachmentFetcher.deref();
|
|
6571
|
+
if (attachmentFetcher != null) {
|
|
6572
|
+
await this.stopAttachmentFetcher(attachmentFetcher);
|
|
6573
|
+
}
|
|
6574
|
+
});
|
|
6575
|
+
await Promise.all(stopped);
|
|
6576
|
+
});
|
|
6577
|
+
}
|
|
6578
|
+
/**
|
|
6579
|
+
* Stop the attachment fetcher without unregistering it from the finalization
|
|
6580
|
+
* registry.
|
|
6581
|
+
*/
|
|
6582
|
+
stopWithContextInfo(contextInfo) {
|
|
6583
|
+
const dittoHandle = Bridge.ditto.handleFor(this.ditto);
|
|
6584
|
+
return this.ditto.deferCloseAsync(async () => {
|
|
6585
|
+
// Remove the manager's own record of the context info.
|
|
6586
|
+
if (this.contextInfoByID[contextInfo.id] == null) {
|
|
6587
|
+
throw new Error(`Internal inconsistency: attachment fetcher ${contextInfo.id} not found in active attachment fetchers.`);
|
|
6588
|
+
}
|
|
6589
|
+
delete this.contextInfoByID[contextInfo.id];
|
|
6590
|
+
// Release the keep-alive.
|
|
6591
|
+
this.ditto.keepAlive.release(`AttachmentFetcher.${contextInfo.id})`);
|
|
6592
|
+
// Cancel the fetcher if it is still running.
|
|
6593
|
+
const cancelToken = await contextInfo.cancelTokenPromise;
|
|
6594
|
+
if (cancelToken) {
|
|
6595
|
+
dittoCancelResolveAttachment(dittoHandle.deref(), contextInfo.attachmentTokenID, cancelToken);
|
|
6596
|
+
}
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
5991
6599
|
}
|
|
5992
6600
|
|
|
5993
6601
|
//
|
|
@@ -5998,8 +6606,10 @@ function stateFrom(parameters) {
|
|
|
5998
6606
|
class Ditto {
|
|
5999
6607
|
/** Returns a string identifying the version of the Ditto SDK. */
|
|
6000
6608
|
get sdkVersion() {
|
|
6001
|
-
const
|
|
6002
|
-
return
|
|
6609
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
6610
|
+
return this.deferClose(() => {
|
|
6611
|
+
return dittoGetSDKVersion(dittoHandle.deref());
|
|
6612
|
+
});
|
|
6003
6613
|
}
|
|
6004
6614
|
/**
|
|
6005
6615
|
* Initializes a new `Ditto` instance.
|
|
@@ -6020,8 +6630,16 @@ class Ditto {
|
|
|
6020
6630
|
* @see {@link Ditto.path}
|
|
6021
6631
|
*/
|
|
6022
6632
|
constructor(identity, path) {
|
|
6633
|
+
/**
|
|
6634
|
+
* `true` once {@link close | Ditto.close()} has been called, otherwise
|
|
6635
|
+
* `false`.
|
|
6636
|
+
*/
|
|
6637
|
+
this.isClosed = false;
|
|
6638
|
+
this.deferCloseAllowed = true;
|
|
6023
6639
|
this.isWebValid = false;
|
|
6024
6640
|
this.isX509Valid = false;
|
|
6641
|
+
/** Set of pending operations that need to complete before the Ditto instance can be closed in a safe manner. */
|
|
6642
|
+
this.pendingOperations = new Set();
|
|
6025
6643
|
if (!Ditto.isEnvironmentSupported()) {
|
|
6026
6644
|
throw new Error('Ditto does not support this JavaScript environment. Please consult the Ditto JavaScript documentation for a list of supported environments and browsers. You can use `Ditto.isEnvironmentSupported()` to run this check anytime.');
|
|
6027
6645
|
}
|
|
@@ -6056,50 +6674,35 @@ class Ditto {
|
|
|
6056
6674
|
// it after all pieces are in place.
|
|
6057
6675
|
let secondsRemainingUntilAuthenticationExpires = null;
|
|
6058
6676
|
const weakThis = new WeakRef(this);
|
|
6059
|
-
const
|
|
6677
|
+
const identityConfig = (() => {
|
|
6060
6678
|
var _a, _b, _c;
|
|
6061
6679
|
if (validIdentity.type === 'offlinePlayground') {
|
|
6062
|
-
return
|
|
6680
|
+
return dittoIdentityConfigMakeOfflinePlayground(validIdentity.appID, (_a = validIdentity.siteID) !== null && _a !== void 0 ? _a : 0);
|
|
6063
6681
|
}
|
|
6064
6682
|
if (validIdentity.type === 'manual') {
|
|
6065
|
-
return
|
|
6683
|
+
return dittoIdentityConfigMakeManual(validIdentity.certificate);
|
|
6066
6684
|
}
|
|
6067
6685
|
if (validIdentity.type === 'sharedKey') {
|
|
6068
|
-
return
|
|
6686
|
+
return dittoIdentityConfigMakeSharedKey(validIdentity.appID, validIdentity.sharedKey, validIdentity.siteID);
|
|
6069
6687
|
}
|
|
6070
6688
|
if (validIdentity.type === 'onlinePlayground') {
|
|
6071
6689
|
const authURL = (_b = validIdentity.customAuthURL) !== null && _b !== void 0 ? _b : defaultAuthURL(validIdentity.appID);
|
|
6072
|
-
return
|
|
6690
|
+
return dittoIdentityConfigMakeOnlinePlayground(validIdentity.appID, validIdentity.token, authURL);
|
|
6073
6691
|
}
|
|
6074
6692
|
if (validIdentity.type === 'onlineWithAuthentication') {
|
|
6075
6693
|
const authURL = (_c = validIdentity.customAuthURL) !== null && _c !== void 0 ? _c : defaultAuthURL(validIdentity.appID);
|
|
6076
|
-
|
|
6077
|
-
const strongThis = weakThis.deref();
|
|
6078
|
-
if (!strongThis) {
|
|
6079
|
-
Logger.warning(`Internal inconsistency, LoginProvider callback fired after the corresponding Ditto instance has been deallocated.`);
|
|
6080
|
-
return;
|
|
6081
|
-
}
|
|
6082
|
-
if (strongThis.auth) {
|
|
6083
|
-
strongThis.auth['@ditto.authenticationExpiring'](secondsRemaining);
|
|
6084
|
-
}
|
|
6085
|
-
else {
|
|
6086
|
-
// WORKAROUND: see description above where the
|
|
6087
|
-
// secondsRemainingUntilAuthenticationExpires variable is declared.
|
|
6088
|
-
secondsRemainingUntilAuthenticationExpires = secondsRemaining;
|
|
6089
|
-
}
|
|
6090
|
-
});
|
|
6091
|
-
return dittoAuthClientMakeWithWeb(pathOrDefault, validIdentity.appID, authURL, loginProviderX);
|
|
6694
|
+
return dittoIdentityConfigMakeOnlineWithAuthentication(validIdentity.appID, authURL);
|
|
6092
6695
|
}
|
|
6093
6696
|
throw new Error(`Can't create Ditto, unsupported identity type: ${validIdentity}`);
|
|
6094
6697
|
})();
|
|
6095
|
-
|
|
6698
|
+
const dittoPointer = dittoMake(uninitializedDittoX, identityConfig);
|
|
6699
|
+
dittoAuthClientSetValidityListener(dittoPointer, function (...args) {
|
|
6096
6700
|
var _a;
|
|
6097
6701
|
(_a = weakThis.deref()) === null || _a === void 0 ? void 0 : _a.authClientValidityChanged(...args);
|
|
6098
6702
|
});
|
|
6099
|
-
const isWebValid = dittoAuthClientIsWebValid(
|
|
6100
|
-
const isX509Valid = dittoAuthClientIsX509Valid(
|
|
6101
|
-
const siteID = dittoAuthClientGetSiteID(
|
|
6102
|
-
const dittoPointer = dittoMake(uninitializedDittoX, authClientX);
|
|
6703
|
+
const isWebValid = dittoAuthClientIsWebValid(dittoPointer);
|
|
6704
|
+
const isX509Valid = dittoAuthClientIsX509Valid(dittoPointer);
|
|
6705
|
+
const siteID = dittoAuthClientGetSiteID(dittoPointer);
|
|
6103
6706
|
Bridge.ditto.bridge(dittoPointer, this);
|
|
6104
6707
|
// IMPORTANT: Keeping the auth client around accumulates run-times and
|
|
6105
6708
|
// resources which becomes a problem specifically in tests (where we use one
|
|
@@ -6107,10 +6710,30 @@ class Ditto {
|
|
|
6107
6710
|
// _only_ for the onlineWithAuthentication and online identities and
|
|
6108
6711
|
// transfer ownership of it to the authenticator.
|
|
6109
6712
|
if (validIdentity.type === 'onlineWithAuthentication') {
|
|
6110
|
-
this.auth = new OnlineAuthenticator(this.keepAlive,
|
|
6713
|
+
this.auth = new OnlineAuthenticator(this.keepAlive, this, validIdentity.authHandler);
|
|
6714
|
+
const loginProviderX = dittoAuthClientMakeLoginProvider(function (secondsRemaining) {
|
|
6715
|
+
const strongThis = weakThis.deref();
|
|
6716
|
+
if (!strongThis) {
|
|
6717
|
+
Logger.warning(`Internal inconsistency, LoginProvider callback fired after the corresponding Ditto instance has been deallocated.`);
|
|
6718
|
+
return;
|
|
6719
|
+
}
|
|
6720
|
+
if (strongThis.auth) {
|
|
6721
|
+
strongThis.auth['@ditto.authenticationExpiring'](secondsRemaining);
|
|
6722
|
+
}
|
|
6723
|
+
else {
|
|
6724
|
+
// WORKAROUND: see description above where the
|
|
6725
|
+
// secondsRemainingUntilAuthenticationExpires variable is declared.
|
|
6726
|
+
secondsRemainingUntilAuthenticationExpires = secondsRemaining;
|
|
6727
|
+
}
|
|
6728
|
+
});
|
|
6729
|
+
// We don't need to worry about awaiting the result of this call because
|
|
6730
|
+
// auth all happens in the background and so there are no guarantees we
|
|
6731
|
+
// need to uphold by making sure things are in a certain state before the
|
|
6732
|
+
// constructor finishes
|
|
6733
|
+
dittoAuthSetLoginProvider(dittoPointer, loginProviderX);
|
|
6111
6734
|
}
|
|
6112
6735
|
else if (validIdentity.type === 'onlinePlayground') {
|
|
6113
|
-
this.auth = new OnlineAuthenticator(this.keepAlive,
|
|
6736
|
+
this.auth = new OnlineAuthenticator(this.keepAlive, this, {
|
|
6114
6737
|
authenticationRequired: function (authenticator) {
|
|
6115
6738
|
// No-op.
|
|
6116
6739
|
},
|
|
@@ -6120,7 +6743,6 @@ class Ditto {
|
|
|
6120
6743
|
});
|
|
6121
6744
|
}
|
|
6122
6745
|
else {
|
|
6123
|
-
dittoAuthClientFree(authClientX);
|
|
6124
6746
|
this.auth = new NotAvailableAuthenticator(this.keepAlive);
|
|
6125
6747
|
}
|
|
6126
6748
|
const transportConfig = this.makeDefaultTransportConfig();
|
|
@@ -6136,7 +6758,10 @@ class Ditto {
|
|
|
6136
6758
|
this.store = new Store(this);
|
|
6137
6759
|
this.presence = new Presence(this);
|
|
6138
6760
|
this.presenceManager = new PresenceManager(this);
|
|
6761
|
+
this.liveQueryManager = new LiveQueryManager(this, this.keepAlive);
|
|
6762
|
+
this.attachmentFetcherManager = new AttachmentFetcherManager(this);
|
|
6139
6763
|
this.transportConditionsManager = new TransportConditionsManager(this);
|
|
6764
|
+
this.subscriptionManager = new SubscriptionManager(this);
|
|
6140
6765
|
// WORKAROUND: see description above where the
|
|
6141
6766
|
// secondsRemainingUntilAuthenticationExpires variable is declared.
|
|
6142
6767
|
if (secondsRemainingUntilAuthenticationExpires !== null) {
|
|
@@ -6305,8 +6930,10 @@ class Ditto {
|
|
|
6305
6930
|
* Only available in Node environments at the moment, no-op in the browser.
|
|
6306
6931
|
*/
|
|
6307
6932
|
runGarbageCollection() {
|
|
6308
|
-
const
|
|
6309
|
-
|
|
6933
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
6934
|
+
return this.deferClose(() => {
|
|
6935
|
+
dittoRunGarbageCollection(dittoHandle.deref());
|
|
6936
|
+
});
|
|
6310
6937
|
}
|
|
6311
6938
|
/**
|
|
6312
6939
|
* Explicitly opt-in to disabling the ability to sync with Ditto peers running
|
|
@@ -6318,8 +6945,99 @@ class Ditto {
|
|
|
6318
6945
|
* or has (transitively) had disabled, syncing with v3 SDK peers.
|
|
6319
6946
|
*/
|
|
6320
6947
|
disableSyncWithV3() {
|
|
6321
|
-
const
|
|
6322
|
-
|
|
6948
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
6949
|
+
return this.deferClose(() => {
|
|
6950
|
+
dittoDisableSyncWithV3(dittoHandle.deref());
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
/**
|
|
6954
|
+
* Shut down Ditto and release all resources.
|
|
6955
|
+
*
|
|
6956
|
+
* Must be called before recreating a Ditto instance that uses the same
|
|
6957
|
+
* persistence directory.
|
|
6958
|
+
*/
|
|
6959
|
+
async close() {
|
|
6960
|
+
if (this.isClosed) {
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6963
|
+
this['isClosed'] = true;
|
|
6964
|
+
this.stopSync();
|
|
6965
|
+
this.store.close();
|
|
6966
|
+
this.presence.close();
|
|
6967
|
+
this.auth.close();
|
|
6968
|
+
this.sync.close();
|
|
6969
|
+
this.presenceManager.close();
|
|
6970
|
+
this.liveQueryManager.close();
|
|
6971
|
+
this.attachmentFetcherManager.close();
|
|
6972
|
+
this.transportConditionsManager.close();
|
|
6973
|
+
this.subscriptionManager.close();
|
|
6974
|
+
if (this.keepAlive.isActive) {
|
|
6975
|
+
throw new Error('Internal inconsistency, still kept alive after the Ditto object has been close()-ed. ');
|
|
6976
|
+
}
|
|
6977
|
+
// Await all pending operations before closing. Rejected promises are
|
|
6978
|
+
// ignored because they are handled at the original call site.
|
|
6979
|
+
do {
|
|
6980
|
+
await Promise.allSettled(this.pendingOperations);
|
|
6981
|
+
// REFACTOR: in theory, we could end up in an endless loop here if for
|
|
6982
|
+
// some reason a resolved or rejected promise isn't removed from
|
|
6983
|
+
// `pendingOperations`. AFAICS, this is not possible atm due to the
|
|
6984
|
+
// way `deferClose` and `deferCloseAsync` is implemented. Would be
|
|
6985
|
+
// great to rework this and make it more solid if possible.
|
|
6986
|
+
} while (this.pendingOperations.size > 0);
|
|
6987
|
+
this.deferCloseAllowed = false;
|
|
6988
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
6989
|
+
await dittoShutdown(dittoHandle.deref());
|
|
6990
|
+
Bridge.ditto.markAsClosed(this);
|
|
6991
|
+
}
|
|
6992
|
+
/**
|
|
6993
|
+
* The number of operations pending before the Ditto instance can be closed.
|
|
6994
|
+
*
|
|
6995
|
+
* For testing purposes only.
|
|
6996
|
+
* @internal */
|
|
6997
|
+
get numPendingOperations() {
|
|
6998
|
+
return this.pendingOperations.size;
|
|
6999
|
+
}
|
|
7000
|
+
/**
|
|
7001
|
+
* Makes sure that the closure is executed only if the Ditto instance hasn't
|
|
7002
|
+
* been closed yet.
|
|
7003
|
+
*
|
|
7004
|
+
* @param closure the synchronous closure to execute.
|
|
7005
|
+
* @returns the result of the closure.
|
|
7006
|
+
* @throws if the Ditto instance was closed before calling this method.
|
|
7007
|
+
* @internal */
|
|
7008
|
+
deferClose(closure) {
|
|
7009
|
+
if (!this.deferCloseAllowed) {
|
|
7010
|
+
throw new Error(`Can't perform operation using a Ditto instance that has been closed.`);
|
|
7011
|
+
}
|
|
7012
|
+
return closure();
|
|
7013
|
+
}
|
|
7014
|
+
/**
|
|
7015
|
+
* Makes sure that the closure is executed to completion before the Ditto
|
|
7016
|
+
* instance is closed.
|
|
7017
|
+
*
|
|
7018
|
+
* Any calls to {@link close | `Ditto.close()`} will wait until the closure
|
|
7019
|
+
* has completed before closing the Ditto instance.
|
|
7020
|
+
*
|
|
7021
|
+
* @param closure the asynchronous closure to execute.
|
|
7022
|
+
* @returns the result of the closure.
|
|
7023
|
+
* @throws if the Ditto instance was closed before calling this method.
|
|
7024
|
+
* @internal */
|
|
7025
|
+
async deferCloseAsync(closure) {
|
|
7026
|
+
if (!this.deferCloseAllowed) {
|
|
7027
|
+
throw new Error(`Can't perform operation using a Ditto instance that has been closed.`);
|
|
7028
|
+
}
|
|
7029
|
+
const pendingOperation = closure();
|
|
7030
|
+
this.pendingOperations.add(pendingOperation);
|
|
7031
|
+
let result;
|
|
7032
|
+
try {
|
|
7033
|
+
result = await pendingOperation;
|
|
7034
|
+
}
|
|
7035
|
+
finally {
|
|
7036
|
+
// Remove the promise from the set of pending operations even if it
|
|
7037
|
+
// rejected.
|
|
7038
|
+
this.pendingOperations.delete(pendingOperation);
|
|
7039
|
+
}
|
|
7040
|
+
return result;
|
|
6323
7041
|
}
|
|
6324
7042
|
authClientValidityChanged(isWebValid, isX509Valid) {
|
|
6325
7043
|
const transportConfig = this.transportConfig;
|
|
@@ -6370,49 +7088,41 @@ class Ditto {
|
|
|
6370
7088
|
return validIdentity;
|
|
6371
7089
|
}
|
|
6372
7090
|
setSyncActive(flag) {
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
7091
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
7092
|
+
this.deferClose(() => {
|
|
7093
|
+
if (flag && IdentityTypesRequiringOfflineLicenseToken.includes(this.identity.type) && !this.isActivated) {
|
|
7094
|
+
throw new Error('Sync could not be started because Ditto has not yet been activated. This can be achieved with a successful call to `setOfflineOnlyLicenseToken`. If you need to obtain a license token then please visit https://portal.ditto.live.');
|
|
7095
|
+
}
|
|
7096
|
+
if (!this.isSyncActive && flag) {
|
|
7097
|
+
this.keepAlive.retain('sync');
|
|
7098
|
+
}
|
|
7099
|
+
if (this.isSyncActive && !flag) {
|
|
7100
|
+
this.keepAlive.release('sync');
|
|
7101
|
+
}
|
|
7102
|
+
this['isSyncActive'] = flag;
|
|
7103
|
+
const isWebValid = this.isWebValid;
|
|
7104
|
+
const isX509Valid = this.isX509Valid;
|
|
7105
|
+
const identity = this.identity;
|
|
7106
|
+
const transportConfig = this.transportConfig;
|
|
7107
|
+
dittoSetDeviceName(dittoHandle.deref(), this.deviceName);
|
|
7108
|
+
this.sync.update({ identity, transportConfig, isWebValid, isX509Valid, isSyncActive: !!flag, ditto: this });
|
|
7109
|
+
});
|
|
6390
7110
|
}
|
|
6391
7111
|
makeDefaultTransportConfig() {
|
|
6392
|
-
const
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
if (awdlIsAvailable(dittoPointer)) {
|
|
6398
|
-
transportConfig.peerToPeer.awdl.isEnabled = true;
|
|
6399
|
-
}
|
|
6400
|
-
if (lanIsAvailable(dittoPointer)) {
|
|
6401
|
-
transportConfig.peerToPeer.lan.isEnabled = true;
|
|
6402
|
-
}
|
|
6403
|
-
// WORKAROUND: the Linux implementation panics if no BLE hardware is
|
|
6404
|
-
// available but should report `NoBleHardware` via transport condition
|
|
6405
|
-
// observation instead. Until this is fixed, we'll disable the BLE transport
|
|
6406
|
-
// by default on Linux. To undo, remove the following block of code.
|
|
6407
|
-
//
|
|
6408
|
-
// See issue #5686 for details:
|
|
6409
|
-
// https://github.com/getditto/ditto/pull/5608
|
|
6410
|
-
{
|
|
6411
|
-
if (process.platform === 'linux') {
|
|
6412
|
-
transportConfig.peerToPeer.bluetoothLE.isEnabled = false;
|
|
7112
|
+
const dittoHandle = Bridge.ditto.handleFor(this);
|
|
7113
|
+
return this.deferClose(() => {
|
|
7114
|
+
const transportConfig = new TransportConfig();
|
|
7115
|
+
if (bleIsAvailable(dittoHandle.deref())) {
|
|
7116
|
+
transportConfig.peerToPeer.bluetoothLE.isEnabled = true;
|
|
6413
7117
|
}
|
|
6414
|
-
|
|
6415
|
-
|
|
7118
|
+
if (awdlIsAvailable(dittoHandle.deref())) {
|
|
7119
|
+
transportConfig.peerToPeer.awdl.isEnabled = true;
|
|
7120
|
+
}
|
|
7121
|
+
if (lanIsAvailable(dittoHandle.deref())) {
|
|
7122
|
+
transportConfig.peerToPeer.lan.isEnabled = true;
|
|
7123
|
+
}
|
|
7124
|
+
return transportConfig.freeze();
|
|
7125
|
+
});
|
|
6416
7126
|
}
|
|
6417
7127
|
}
|
|
6418
7128
|
/** @internal */
|