dexie-cloud-addon 4.1.0-beta.43 → 4.1.0-beta.45
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/dist/modern/WSObservable.d.ts +4 -3
- package/dist/modern/dexie-cloud-addon.js +248 -155
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/service-worker.js +234 -149
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/yjs/getUpdatesTable.d.ts +1 -1
- package/dist/umd/WSObservable.d.ts +4 -3
- package/dist/umd/dexie-cloud-addon.js +248 -155
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/service-worker.js +234 -149
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/dist/umd/yjs/getUpdatesTable.d.ts +1 -1
- package/package.json +4 -4
- package/dist/modern/helpers/dbOnClosed.d.ts +0 -2
- package/dist/umd/helpers/dbOnClosed.d.ts +0 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.1.0-beta.
|
|
11
|
+
* Version 4.1.0-beta.45, Mon Mar 31 2025
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -2799,6 +2799,7 @@ function encodeYMessage(msg) {
|
|
|
2799
2799
|
break;
|
|
2800
2800
|
case 'u-s':
|
|
2801
2801
|
writeVarUint8Array(encoder, msg.u);
|
|
2802
|
+
writeVarString(encoder, msg.r || '');
|
|
2802
2803
|
break;
|
|
2803
2804
|
}
|
|
2804
2805
|
}
|
|
@@ -3185,7 +3186,8 @@ function decodeYMessage(a) {
|
|
|
3185
3186
|
table,
|
|
3186
3187
|
prop,
|
|
3187
3188
|
k,
|
|
3188
|
-
u: readVarUint8Array(decoder)
|
|
3189
|
+
u: readVarUint8Array(decoder),
|
|
3190
|
+
r: (decoder.pos < decoder.arr.length && readVarString(decoder)) || undefined,
|
|
3189
3191
|
};
|
|
3190
3192
|
default:
|
|
3191
3193
|
throw new TypeError(`Unknown message type: ${type}`);
|
|
@@ -4462,6 +4464,7 @@ function syncWithServer(changes, y, syncState, baseRevs, db, databaseUrl, schema
|
|
|
4462
4464
|
baseRevs,
|
|
4463
4465
|
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes),
|
|
4464
4466
|
y,
|
|
4467
|
+
dxcv: db.cloud.version
|
|
4465
4468
|
};
|
|
4466
4469
|
console.debug('Sync request', syncRequest);
|
|
4467
4470
|
db.syncStateChangedEvent.next({
|
|
@@ -4604,9 +4607,11 @@ function applyServerChanges(changes, db) {
|
|
|
4604
4607
|
return __awaiter(this, void 0, void 0, function* () {
|
|
4605
4608
|
console.debug('Applying server changes', changes, Dexie.currentTransaction);
|
|
4606
4609
|
for (const { table: tableName, muts } of changes) {
|
|
4610
|
+
if (!db.dx._allTables[tableName]) {
|
|
4611
|
+
console.debug(`Server sent changes for table ${tableName} that we don't have. Ignoring.`);
|
|
4612
|
+
continue;
|
|
4613
|
+
}
|
|
4607
4614
|
const table = db.table(tableName);
|
|
4608
|
-
if (!table)
|
|
4609
|
-
continue; // If server sends changes on a table we don't have, ignore it.
|
|
4610
4615
|
const { primaryKey } = table.core.schema;
|
|
4611
4616
|
const keyDecoder = (key) => {
|
|
4612
4617
|
switch (key[0]) {
|
|
@@ -4792,9 +4797,15 @@ function listYClientMessagesAndStateVector(db, tablesToSync) {
|
|
|
4792
4797
|
|
|
4793
4798
|
function getUpdatesTable(db, table, ydocProp) {
|
|
4794
4799
|
var _a, _b, _c;
|
|
4800
|
+
if (!db.dx._allTables[table])
|
|
4801
|
+
return undefined;
|
|
4795
4802
|
const utbl = (_c = (_b = (_a = db.table(table)) === null || _a === void 0 ? void 0 : _a.schema.yProps) === null || _b === void 0 ? void 0 : _b.find(p => p.prop === ydocProp)) === null || _c === void 0 ? void 0 : _c.updatesTable;
|
|
4796
|
-
if (!utbl)
|
|
4797
|
-
|
|
4803
|
+
if (!utbl) {
|
|
4804
|
+
console.debug(`No updatesTable found for ${table}.${ydocProp}`);
|
|
4805
|
+
return undefined;
|
|
4806
|
+
}
|
|
4807
|
+
if (!db.dx._allTables[utbl])
|
|
4808
|
+
return undefined;
|
|
4798
4809
|
return db.table(utbl);
|
|
4799
4810
|
}
|
|
4800
4811
|
|
|
@@ -4805,74 +4816,91 @@ function applyYServerMessages(yMessages, db) {
|
|
|
4805
4816
|
let resyncNeeded = false;
|
|
4806
4817
|
let yServerRevision;
|
|
4807
4818
|
for (const m of yMessages) {
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
.
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
break;
|
|
4826
|
-
}
|
|
4827
|
-
case 'u-reject': {
|
|
4828
|
-
// Acces control or constraint rejected the update.
|
|
4829
|
-
// We delete it. It's not going to be sent again.
|
|
4830
|
-
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
4831
|
-
// This is only an issue when the document is open. We could find the open document and
|
|
4832
|
-
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
4833
|
-
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
4834
|
-
console.debug(`Y update rejected. Deleting it.`);
|
|
4835
|
-
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
4836
|
-
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
4837
|
-
// and destroy it's open document if there is one.
|
|
4838
|
-
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
4839
|
-
if (primaryKey != null) {
|
|
4840
|
-
yield db.transaction('rw', utbl, (tx) => {
|
|
4841
|
-
// @ts-ignore
|
|
4842
|
-
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
4843
|
-
return utbl
|
|
4844
|
-
.where('i')
|
|
4845
|
-
.aboveOrEqual(m.i)
|
|
4846
|
-
.filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
4847
|
-
.delete();
|
|
4848
|
-
});
|
|
4849
|
-
// Destroy active doc
|
|
4850
|
-
const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
4851
|
-
if (activeDoc)
|
|
4852
|
-
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
4819
|
+
try {
|
|
4820
|
+
switch (m.type) {
|
|
4821
|
+
case 'u-s': {
|
|
4822
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
4823
|
+
if (utbl) {
|
|
4824
|
+
const updateRow = {
|
|
4825
|
+
k: m.k,
|
|
4826
|
+
u: m.u,
|
|
4827
|
+
};
|
|
4828
|
+
if (m.r) {
|
|
4829
|
+
// @ts-ignore
|
|
4830
|
+
updateRow.r = m.r;
|
|
4831
|
+
yServerRevision = m.r;
|
|
4832
|
+
}
|
|
4833
|
+
receivedUntils[utbl.name] = yield utbl.add(updateRow);
|
|
4834
|
+
}
|
|
4835
|
+
break;
|
|
4853
4836
|
}
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4837
|
+
case 'u-ack': {
|
|
4838
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
4839
|
+
if (utbl) {
|
|
4840
|
+
yield db.transaction('rw', utbl, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
4841
|
+
let syncer = (yield tx
|
|
4842
|
+
.table(utbl.name)
|
|
4843
|
+
.get(DEXIE_CLOUD_SYNCER_ID));
|
|
4844
|
+
yield tx.table(utbl.name).put(Object.assign(Object.assign({}, (syncer || { i: DEXIE_CLOUD_SYNCER_ID })), { unsentFrom: Math.max((syncer === null || syncer === void 0 ? void 0 : syncer.unsentFrom) || 1, m.i + 1) }));
|
|
4845
|
+
}));
|
|
4846
|
+
}
|
|
4847
|
+
break;
|
|
4860
4848
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4849
|
+
case 'u-reject': {
|
|
4850
|
+
// Acces control or constraint rejected the update.
|
|
4851
|
+
// We delete it. It's not going to be sent again.
|
|
4852
|
+
// What's missing is a way to notify consumers, such as Tiptap editor, that the update was rejected.
|
|
4853
|
+
// This is only an issue when the document is open. We could find the open document and
|
|
4854
|
+
// in a perfect world, we should send a reverse update to the open document to undo the change.
|
|
4855
|
+
// See my question in https://discuss.yjs.dev/t/generate-an-inverse-update/2765
|
|
4856
|
+
console.debug(`Y update rejected. Deleting it.`);
|
|
4857
|
+
const utbl = getUpdatesTable(db, m.table, m.prop);
|
|
4858
|
+
if (!utbl)
|
|
4859
|
+
break;
|
|
4860
|
+
// Delete the rejected update and all local updates since (avoid holes in the CRDT)
|
|
4861
|
+
// and destroy it's open document if there is one.
|
|
4862
|
+
const primaryKey = (_a = (yield utbl.get(m.i))) === null || _a === void 0 ? void 0 : _a.k;
|
|
4863
|
+
if (primaryKey != null) {
|
|
4864
|
+
yield db.transaction('rw', utbl, (tx) => {
|
|
4865
|
+
// @ts-ignore
|
|
4866
|
+
tx.idbtrans._rejecting_y_ypdate = true; // Inform ydoc triggers that we delete because of a rejection and not GC
|
|
4867
|
+
return utbl
|
|
4868
|
+
.where('i')
|
|
4869
|
+
.aboveOrEqual(m.i)
|
|
4870
|
+
.filter((u) => cmp(u.k, primaryKey) === 0 && ((u.f || 0) & 1) === 1)
|
|
4871
|
+
.delete();
|
|
4872
|
+
});
|
|
4873
|
+
// Destroy active doc
|
|
4874
|
+
const activeDoc = DexieYProvider.getDocCache(db.dx).find(m.table, primaryKey, m.prop);
|
|
4875
|
+
if (activeDoc)
|
|
4876
|
+
activeDoc.destroy(); // Destroy the document so that editors don't continue to work on it
|
|
4877
|
+
}
|
|
4878
|
+
break;
|
|
4879
|
+
}
|
|
4880
|
+
case 'in-sync': {
|
|
4881
|
+
const doc = DexieYProvider.getDocCache(db.dx).find(m.table, m.k, m.prop);
|
|
4882
|
+
if (doc && !doc.isSynced) {
|
|
4883
|
+
doc.emit('sync', [true]);
|
|
4884
|
+
}
|
|
4885
|
+
break;
|
|
4886
|
+
}
|
|
4887
|
+
case 'y-complete-sync-done': {
|
|
4888
|
+
yServerRevision = m.yServerRev;
|
|
4889
|
+
break;
|
|
4890
|
+
}
|
|
4891
|
+
case 'outdated-server-rev':
|
|
4892
|
+
resyncNeeded = true;
|
|
4893
|
+
break;
|
|
4866
4894
|
}
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4895
|
+
}
|
|
4896
|
+
catch (e) {
|
|
4897
|
+
console.error(`Failed to apply YMessage`, m, e);
|
|
4870
4898
|
}
|
|
4871
4899
|
}
|
|
4872
4900
|
return {
|
|
4873
4901
|
receivedUntils,
|
|
4874
4902
|
resyncNeeded,
|
|
4875
|
-
yServerRevision
|
|
4903
|
+
yServerRevision,
|
|
4876
4904
|
};
|
|
4877
4905
|
});
|
|
4878
4906
|
}
|
|
@@ -4983,7 +5011,9 @@ function downloadYDocsFromServer(db_1, databaseUrl_1, _a) {
|
|
|
4983
5011
|
throw new Error(`Protocol error from ${databaseUrl}/y/download`);
|
|
4984
5012
|
}
|
|
4985
5013
|
const yTable = getUpdatesTable(db, currentTable, currentProp);
|
|
4986
|
-
|
|
5014
|
+
if (yTable) {
|
|
5015
|
+
yield yTable.bulkAdd(docsToInsert);
|
|
5016
|
+
}
|
|
4987
5017
|
docsToInsert = [];
|
|
4988
5018
|
}
|
|
4989
5019
|
if (currentRealmId &&
|
|
@@ -5319,12 +5349,12 @@ function _sync(db_1, options_1, schema_1) {
|
|
|
5319
5349
|
return false; // Not needed anymore
|
|
5320
5350
|
});
|
|
5321
5351
|
}
|
|
5322
|
-
function deleteObjectsFromRemovedRealms(db, res,
|
|
5352
|
+
function deleteObjectsFromRemovedRealms(db, res, syncState) {
|
|
5323
5353
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5324
5354
|
const deletedRealms = new Set();
|
|
5325
5355
|
const rejectedRealms = new Set();
|
|
5326
|
-
const previousRealmSet =
|
|
5327
|
-
const previousInviteRealmSet =
|
|
5356
|
+
const previousRealmSet = syncState ? syncState.realms : [];
|
|
5357
|
+
const previousInviteRealmSet = syncState ? syncState.inviteRealms : [];
|
|
5328
5358
|
const updatedRealmSet = new Set(res.realms);
|
|
5329
5359
|
const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
|
|
5330
5360
|
for (const realmId of previousRealmSet) {
|
|
@@ -5366,17 +5396,10 @@ function deleteObjectsFromRemovedRealms(db, res, prevState) {
|
|
|
5366
5396
|
}
|
|
5367
5397
|
}
|
|
5368
5398
|
}
|
|
5369
|
-
if (rejectedRealms.size > 0) {
|
|
5370
|
-
// Remove rejected/deleted realms from yDownloadedRealms because of the following use case:
|
|
5371
|
-
// 1. User becomes added to the realm
|
|
5372
|
-
// 2. User syncs and all documents of the realm is downloaded (downloadYDocsFromServer.ts)
|
|
5373
|
-
// 3. User leaves the realm and all docs are deleted locally (built-in-trigger of deleting their rows in this file)
|
|
5374
|
-
// 4. User is yet again added to the realm. At this point, we must make sure the docs are not considered already downloaded.
|
|
5375
|
-
const updateSpec = {};
|
|
5399
|
+
if (rejectedRealms.size > 0 && (syncState === null || syncState === void 0 ? void 0 : syncState.yDownloadedRealms)) {
|
|
5376
5400
|
for (const realmId of rejectedRealms) {
|
|
5377
|
-
|
|
5401
|
+
delete syncState.yDownloadedRealms[realmId];
|
|
5378
5402
|
}
|
|
5379
|
-
yield db.$syncState.update('syncState', updateSpec);
|
|
5380
5403
|
}
|
|
5381
5404
|
});
|
|
5382
5405
|
}
|
|
@@ -5915,8 +5938,6 @@ function prodLog(level, ...args) {
|
|
|
5915
5938
|
*/
|
|
5916
5939
|
function setCurrentUser(db, user) {
|
|
5917
5940
|
return __awaiter(this, void 0, void 0, function* () {
|
|
5918
|
-
if (user.userId === db.cloud.currentUserId)
|
|
5919
|
-
return; // Already this user.
|
|
5920
5941
|
const $logins = db.table('$logins');
|
|
5921
5942
|
yield db.transaction('rw', $logins, (tx) => __awaiter(this, void 0, void 0, function* () {
|
|
5922
5943
|
const existingLogins = yield $logins.toArray();
|
|
@@ -6158,8 +6179,14 @@ function createIdGenerationMiddleware(db) {
|
|
|
6158
6179
|
}
|
|
6159
6180
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6160
6181
|
var _a, _b;
|
|
6161
|
-
|
|
6162
|
-
if (
|
|
6182
|
+
const idbtrans = req.trans;
|
|
6183
|
+
if (idbtrans.mode === 'versionchange') {
|
|
6184
|
+
// Tell all the other middlewares to skip bothering. We're in versionchange mode.
|
|
6185
|
+
// dexie-cloud is not initialized yet.
|
|
6186
|
+
idbtrans.disableChangeTracking = true;
|
|
6187
|
+
idbtrans.disableAccessControl = true;
|
|
6188
|
+
}
|
|
6189
|
+
if (idbtrans.disableChangeTracking) {
|
|
6163
6190
|
// Disable ID policy checks and ID generation
|
|
6164
6191
|
return table.mutate(req);
|
|
6165
6192
|
}
|
|
@@ -6216,17 +6243,13 @@ function createImplicitPropSetterMiddleware(db) {
|
|
|
6216
6243
|
return Object.assign(Object.assign({}, core), { table: (tableName) => {
|
|
6217
6244
|
const table = core.table(tableName);
|
|
6218
6245
|
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6219
|
-
var _a, _b, _c, _d;
|
|
6220
|
-
|
|
6221
|
-
if (
|
|
6246
|
+
var _a, _b, _c, _d, _e, _f;
|
|
6247
|
+
const trans = req.trans;
|
|
6248
|
+
if (trans.disableChangeTracking) {
|
|
6222
6249
|
return table.mutate(req);
|
|
6223
6250
|
}
|
|
6224
|
-
const
|
|
6225
|
-
if ((
|
|
6226
|
-
if (trans.mode === 'versionchange') {
|
|
6227
|
-
// Don't mutate tables marked for sync in versionchange transactions.
|
|
6228
|
-
return Promise.reject(new Dexie.UpgradeError(`Dexie Cloud Addon: Cannot upgrade or populate synced table "${tableName}". See https://dexie.org/cloud/docs/best-practices`));
|
|
6229
|
-
}
|
|
6251
|
+
const currentUserId = (_b = (_a = trans.currentUser) === null || _a === void 0 ? void 0 : _a.userId) !== null && _b !== void 0 ? _b : UNAUTHORIZED_USER.userId;
|
|
6252
|
+
if ((_d = (_c = db.cloud.schema) === null || _c === void 0 ? void 0 : _c[tableName]) === null || _d === void 0 ? void 0 : _d.markedForSync) {
|
|
6230
6253
|
if (req.type === 'add' || req.type === 'put') {
|
|
6231
6254
|
if (tableName === 'members') {
|
|
6232
6255
|
for (const member of req.values) {
|
|
@@ -6250,12 +6273,12 @@ function createImplicitPropSetterMiddleware(db) {
|
|
|
6250
6273
|
// and expect them to be returned. That scenario must work also when db.cloud.currentUserId === 'unauthorized'.
|
|
6251
6274
|
for (const obj of req.values) {
|
|
6252
6275
|
if (!obj.owner) {
|
|
6253
|
-
obj.owner =
|
|
6276
|
+
obj.owner = currentUserId;
|
|
6254
6277
|
}
|
|
6255
6278
|
if (!obj.realmId) {
|
|
6256
|
-
obj.realmId =
|
|
6279
|
+
obj.realmId = currentUserId;
|
|
6257
6280
|
}
|
|
6258
|
-
const key = (
|
|
6281
|
+
const key = (_f = (_e = table.schema.primaryKey).extractKey) === null || _f === void 0 ? void 0 : _f.call(_e, obj);
|
|
6259
6282
|
if (typeof key === 'string' && key[0] === '#') {
|
|
6260
6283
|
// Add $ts prop for put operations and
|
|
6261
6284
|
// disable update operations as well as consistent
|
|
@@ -6351,16 +6374,14 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
|
|
|
6351
6374
|
name: 'MutationTrackingMiddleware',
|
|
6352
6375
|
level: 1,
|
|
6353
6376
|
create: (core) => {
|
|
6377
|
+
const allTableNames = new Set(core.schema.tables.map((t) => t.name));
|
|
6354
6378
|
const ordinaryTables = core.schema.tables.filter((t) => !/^\$/.test(t.name));
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
}
|
|
6362
|
-
catch (_a) {
|
|
6363
|
-
throwVersionIncrementNeeded();
|
|
6379
|
+
const mutTableMap = new Map();
|
|
6380
|
+
for (const tbl of ordinaryTables) {
|
|
6381
|
+
const mutationTableName = `$${tbl.name}_mutations`;
|
|
6382
|
+
if (allTableNames.has(mutationTableName)) {
|
|
6383
|
+
mutTableMap.set(tbl.name, core.table(mutationTableName));
|
|
6384
|
+
}
|
|
6364
6385
|
}
|
|
6365
6386
|
return Object.assign(Object.assign({}, core), { transaction: (tables, mode) => {
|
|
6366
6387
|
let tx;
|
|
@@ -6438,6 +6459,11 @@ function createMutationTrackingMiddleware({ currentUserObservable, db, }) {
|
|
|
6438
6459
|
}
|
|
6439
6460
|
const { schema } = table;
|
|
6440
6461
|
const mutsTable = mutTableMap.get(tableName);
|
|
6462
|
+
if (!mutsTable) {
|
|
6463
|
+
// We cannot track mutations on this table because there is no mutations table for it.
|
|
6464
|
+
// This might happen in upgraders that executes before cloud schema is applied.
|
|
6465
|
+
return table;
|
|
6466
|
+
}
|
|
6441
6467
|
return guardedTable(Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
6442
6468
|
var _a, _b, _c;
|
|
6443
6469
|
const trans = req.trans;
|
|
@@ -6896,13 +6922,13 @@ const SERVER_PING_TIMEOUT = 20000;
|
|
|
6896
6922
|
const CLIENT_PING_INTERVAL = 30000;
|
|
6897
6923
|
const FAIL_RETRY_WAIT_TIME = 60000;
|
|
6898
6924
|
class WSObservable extends Observable$1 {
|
|
6899
|
-
constructor(db, rev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6900
|
-
super((subscriber) => new WSConnection(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6925
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, messageProducer, webSocketStatus, user) {
|
|
6926
|
+
super((subscriber) => new WSConnection(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus));
|
|
6901
6927
|
}
|
|
6902
6928
|
}
|
|
6903
6929
|
let counter = 0;
|
|
6904
6930
|
class WSConnection extends Subscription$1 {
|
|
6905
|
-
constructor(db, rev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6931
|
+
constructor(db, rev, yrev, realmSetHash, clientIdentity, user, subscriber, messageProducer, webSocketStatus) {
|
|
6906
6932
|
super(() => this.teardown());
|
|
6907
6933
|
this.id = ++counter;
|
|
6908
6934
|
this.subscriptions = new Set();
|
|
@@ -6911,6 +6937,7 @@ class WSConnection extends Subscription$1 {
|
|
|
6911
6937
|
this.db = db;
|
|
6912
6938
|
this.databaseUrl = db.cloud.options.databaseUrl;
|
|
6913
6939
|
this.rev = rev;
|
|
6940
|
+
this.yrev = yrev;
|
|
6914
6941
|
this.realmSetHash = realmSetHash;
|
|
6915
6942
|
this.clientIdentity = clientIdentity;
|
|
6916
6943
|
this.user = user;
|
|
@@ -6980,6 +7007,11 @@ class WSConnection extends Subscription$1 {
|
|
|
6980
7007
|
}
|
|
6981
7008
|
this.webSocketStatus.next('connecting');
|
|
6982
7009
|
this.pinger = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
7010
|
+
// setInterval here causes unnecessary pings when server is proved active anyway.
|
|
7011
|
+
// TODO: Use setTimout() here instead. When triggered, check if we really need to ping.
|
|
7012
|
+
// In case we've had server activity, we don't need to ping. Then schedule then next ping
|
|
7013
|
+
// to the time when we should ping next time (based on lastServerActivity + CLIENT_PING_INTERVAL).
|
|
7014
|
+
// Else, ping now and schedule next ping to CLIENT_PING_INTERVAL from now.
|
|
6983
7015
|
if (this.closed) {
|
|
6984
7016
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
6985
7017
|
this.teardown();
|
|
@@ -7026,9 +7058,13 @@ class WSConnection extends Subscription$1 {
|
|
|
7026
7058
|
if (this.subscriber.closed)
|
|
7027
7059
|
return;
|
|
7028
7060
|
searchParams.set('v', '2');
|
|
7029
|
-
|
|
7061
|
+
if (this.rev)
|
|
7062
|
+
searchParams.set('rev', this.rev);
|
|
7063
|
+
if (this.yrev)
|
|
7064
|
+
searchParams.set('yrev', this.yrev);
|
|
7030
7065
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
7031
7066
|
searchParams.set('clientId', this.clientIdentity);
|
|
7067
|
+
searchParams.set('dxcv', this.db.cloud.version);
|
|
7032
7068
|
if (this.user.accessToken) {
|
|
7033
7069
|
searchParams.set('token', this.user.accessToken);
|
|
7034
7070
|
}
|
|
@@ -7065,8 +7101,8 @@ class WSConnection extends Subscription$1 {
|
|
|
7065
7101
|
}
|
|
7066
7102
|
}
|
|
7067
7103
|
}
|
|
7068
|
-
else if (msg.type === '
|
|
7069
|
-
|
|
7104
|
+
else if (msg.type === 'pong') {
|
|
7105
|
+
// Do nothing
|
|
7070
7106
|
}
|
|
7071
7107
|
else if (msg.type === 'doc-open') {
|
|
7072
7108
|
const docCache = DexieYProvider.getDocCache(this.db.dx);
|
|
@@ -7075,11 +7111,26 @@ class WSConnection extends Subscription$1 {
|
|
|
7075
7111
|
getOpenDocSignal(doc).next(); // Make yHandler reopen the document on server.
|
|
7076
7112
|
}
|
|
7077
7113
|
}
|
|
7078
|
-
else if (msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7079
|
-
|
|
7080
|
-
|
|
7114
|
+
else if (msg.type === 'u-ack' || msg.type === 'u-reject' || msg.type === 'u-s' || msg.type === 'in-sync' || msg.type === 'outdated-server-rev' || msg.type === 'y-complete-sync-done') {
|
|
7115
|
+
applyYServerMessages([msg], this.db).then((_a) => __awaiter(this, [_a], void 0, function* ({ resyncNeeded, yServerRevision, receivedUntils }) {
|
|
7116
|
+
if (yServerRevision) {
|
|
7117
|
+
yield this.db.$syncState.update('syncState', { yServerRevision: yServerRevision });
|
|
7118
|
+
}
|
|
7119
|
+
if (msg.type === 'u-s' && receivedUntils) {
|
|
7120
|
+
const utbl = getUpdatesTable(this.db, msg.table, msg.prop);
|
|
7121
|
+
if (utbl) {
|
|
7122
|
+
const receivedUntil = receivedUntils[utbl.name];
|
|
7123
|
+
if (receivedUntil) {
|
|
7124
|
+
yield utbl.update(DEXIE_CLOUD_SYNCER_ID, { receivedUntil });
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
if (resyncNeeded) {
|
|
7129
|
+
yield this.db.cloud.sync({ purpose: 'pull', wait: true });
|
|
7130
|
+
}
|
|
7131
|
+
}));
|
|
7081
7132
|
}
|
|
7082
|
-
else
|
|
7133
|
+
else {
|
|
7083
7134
|
// Forward the request to our subscriber, wich is in messageFromServerQueue.ts (via connectWebSocket's subscribe() at the end!)
|
|
7084
7135
|
this.subscriber.next(msg);
|
|
7085
7136
|
}
|
|
@@ -7212,7 +7263,7 @@ function connectWebSocket(db) {
|
|
|
7212
7263
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
7213
7264
|
// and the baseRev of the last from same client-ID.
|
|
7214
7265
|
if (userLogin) {
|
|
7215
|
-
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
7266
|
+
return new WSObservable(db, db.cloud.persistedSyncState.value.serverRevision, db.cloud.persistedSyncState.value.yServerRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin);
|
|
7216
7267
|
}
|
|
7217
7268
|
else {
|
|
7218
7269
|
return from$1([]);
|
|
@@ -7342,61 +7393,95 @@ function syncIfPossible(db, cloudOptions, cloudSchema, options) {
|
|
|
7342
7393
|
}
|
|
7343
7394
|
|
|
7344
7395
|
const SECONDS = 1000;
|
|
7345
|
-
const MINUTES = 60 * SECONDS;
|
|
7346
7396
|
|
|
7347
7397
|
function LocalSyncWorker(db, cloudOptions, cloudSchema) {
|
|
7348
7398
|
let localSyncEventSubscription = null;
|
|
7349
|
-
//let syncHandler: ((event: Event) => void) | null = null;
|
|
7350
|
-
//let periodicSyncHandler: ((event: Event) => void) | null = null;
|
|
7351
7399
|
let cancelToken = { cancelled: false };
|
|
7352
|
-
let
|
|
7353
|
-
let
|
|
7354
|
-
function syncAndRetry(
|
|
7400
|
+
let nextRetryTime = 0;
|
|
7401
|
+
let syncStartTime = 0;
|
|
7402
|
+
function syncAndRetry(retryNum = 1) {
|
|
7355
7403
|
// Use setTimeout() to get onto a clean stack and
|
|
7356
7404
|
// break free from possible active transaction:
|
|
7357
7405
|
setTimeout(() => {
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
const combPurpose = retryPurpose === 'pull' ? 'pull' : purpose;
|
|
7361
|
-
retryHandle = null;
|
|
7362
|
-
retryPurpose = null;
|
|
7406
|
+
const purpose = pullSignalled ? 'pull' : 'push';
|
|
7407
|
+
syncStartTime = Date.now();
|
|
7363
7408
|
syncIfPossible(db, cloudOptions, cloudSchema, {
|
|
7364
7409
|
cancelToken,
|
|
7365
7410
|
retryImmediatelyOnFetchError: true, // workaround for "net::ERR_NETWORK_CHANGED" in chrome.
|
|
7366
|
-
purpose
|
|
7367
|
-
}).
|
|
7368
|
-
console.error('error in syncIfPossible()', e);
|
|
7411
|
+
purpose,
|
|
7412
|
+
}).then(() => {
|
|
7369
7413
|
if (cancelToken.cancelled) {
|
|
7370
7414
|
stop();
|
|
7371
7415
|
}
|
|
7372
|
-
else
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7416
|
+
else {
|
|
7417
|
+
if (pullSignalled || pushSignalled) {
|
|
7418
|
+
// If we have signalled for more sync, do it now.
|
|
7419
|
+
pullSignalled = false;
|
|
7420
|
+
pushSignalled = false;
|
|
7421
|
+
return syncAndRetry();
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
ongoingSync = false;
|
|
7425
|
+
nextRetryTime = 0;
|
|
7426
|
+
syncStartTime = 0;
|
|
7427
|
+
}).catch((error) => {
|
|
7428
|
+
console.error('error in syncIfPossible()', error);
|
|
7429
|
+
if (cancelToken.cancelled) {
|
|
7430
|
+
stop();
|
|
7431
|
+
ongoingSync = false;
|
|
7432
|
+
nextRetryTime = 0;
|
|
7433
|
+
syncStartTime = 0;
|
|
7434
|
+
}
|
|
7435
|
+
else if (retryNum < 5) {
|
|
7436
|
+
// Mimic service worker sync event but a bit more eager: retry 4 times
|
|
7437
|
+
// * first retry after 20 seconds
|
|
7438
|
+
// * second retry 40 seconds later
|
|
7439
|
+
// * third retry 5 minutes later
|
|
7440
|
+
// * last retry 15 minutes later
|
|
7441
|
+
const retryIn = [0, 20, 40, 300, 900][retryNum] * SECONDS;
|
|
7442
|
+
nextRetryTime = Date.now() + retryIn;
|
|
7443
|
+
syncStartTime = 0;
|
|
7444
|
+
setTimeout(() => syncAndRetry(retryNum + 1), retryIn);
|
|
7445
|
+
}
|
|
7446
|
+
else {
|
|
7447
|
+
ongoingSync = false;
|
|
7448
|
+
nextRetryTime = 0;
|
|
7449
|
+
syncStartTime = 0;
|
|
7383
7450
|
}
|
|
7384
7451
|
});
|
|
7385
7452
|
}, 0);
|
|
7386
7453
|
}
|
|
7454
|
+
let pullSignalled = false;
|
|
7455
|
+
let pushSignalled = false;
|
|
7456
|
+
let ongoingSync = false;
|
|
7457
|
+
const consumer = (purpose) => {
|
|
7458
|
+
if (cancelToken.cancelled)
|
|
7459
|
+
return;
|
|
7460
|
+
if (purpose === 'pull') {
|
|
7461
|
+
pullSignalled = true;
|
|
7462
|
+
}
|
|
7463
|
+
if (purpose === 'push') {
|
|
7464
|
+
pushSignalled = true;
|
|
7465
|
+
}
|
|
7466
|
+
if (ongoingSync) {
|
|
7467
|
+
if (nextRetryTime) {
|
|
7468
|
+
console.debug(`Sync is paused until ${new Date(nextRetryTime).toISOString()} due to error in last sync attempt`);
|
|
7469
|
+
}
|
|
7470
|
+
else if (syncStartTime > 0 && Date.now() - syncStartTime > 20 * SECONDS) {
|
|
7471
|
+
console.debug(`An existing sync operation is taking more than 20 seconds. Will resync when done.`);
|
|
7472
|
+
}
|
|
7473
|
+
return;
|
|
7474
|
+
}
|
|
7475
|
+
ongoingSync = true;
|
|
7476
|
+
syncAndRetry();
|
|
7477
|
+
};
|
|
7387
7478
|
const start = () => {
|
|
7388
7479
|
// Sync eagerly whenever a change has happened (+ initially when there's no syncState yet)
|
|
7389
7480
|
// This initial subscribe will also trigger an sync also now.
|
|
7390
7481
|
console.debug('Starting LocalSyncWorker', db.localSyncEvent['id']);
|
|
7391
7482
|
localSyncEventSubscription = db.localSyncEvent.subscribe(({ purpose }) => {
|
|
7392
|
-
|
|
7393
|
-
syncAndRetry(purpose || 'pull');
|
|
7394
|
-
}
|
|
7395
|
-
catch (err) {
|
|
7396
|
-
console.error('What-the....', err);
|
|
7397
|
-
}
|
|
7483
|
+
consumer(purpose || 'pull');
|
|
7398
7484
|
});
|
|
7399
|
-
//setTimeout(()=>db.localSyncEvent.next({}), 5000);
|
|
7400
7485
|
};
|
|
7401
7486
|
const stop = () => {
|
|
7402
7487
|
console.debug('Stopping LocalSyncWorker');
|
|
@@ -8188,7 +8273,7 @@ function dexieCloud(dexie) {
|
|
|
8188
8273
|
const syncComplete = new Subject();
|
|
8189
8274
|
dexie.cloud = {
|
|
8190
8275
|
// @ts-ignore
|
|
8191
|
-
version: "4.1.0-beta.
|
|
8276
|
+
version: "4.1.0-beta.45",
|
|
8192
8277
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
8193
8278
|
schema: null,
|
|
8194
8279
|
get currentUserId() {
|
|
@@ -8506,7 +8591,7 @@ function dexieCloud(dexie) {
|
|
|
8506
8591
|
}
|
|
8507
8592
|
}
|
|
8508
8593
|
// @ts-ignore
|
|
8509
|
-
dexieCloud.version = "4.1.0-beta.
|
|
8594
|
+
dexieCloud.version = "4.1.0-beta.45";
|
|
8510
8595
|
Dexie.Cloud = dexieCloud;
|
|
8511
8596
|
|
|
8512
8597
|
// In case the SW lives for a while, let it reuse already opened connections:
|