dexie-cloud-addon 4.0.1-beta.34 → 4.0.1-beta.36

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.
@@ -8,7 +8,7 @@
8
8
 
9
9
  var Dexie__default = /*#__PURE__*/_interopDefaultLegacy(Dexie);
10
10
 
11
- /*! *****************************************************************************
11
+ /******************************************************************************
12
12
  Copyright (c) Microsoft Corporation.
13
13
 
14
14
  Permission to use, copy, modify, and/or distribute this software for any
@@ -113,7 +113,7 @@
113
113
  ar[i] = from[i];
114
114
  }
115
115
  }
116
- return to.concat(ar || from);
116
+ return to.concat(ar || Array.prototype.slice.call(from));
117
117
  }
118
118
 
119
119
  function __await(v) {
@@ -210,6 +210,36 @@
210
210
  }
211
211
  }
212
212
 
213
+ const events = globalThis['lbc-events'] || (globalThis['lbc-events'] = new Map());
214
+ function addListener(name, listener) {
215
+ if (events.has(name)) {
216
+ events.get(name).push(listener);
217
+ }
218
+ else {
219
+ events.set(name, [listener]);
220
+ }
221
+ }
222
+ function removeListener(name, listener) {
223
+ const listeners = events.get(name);
224
+ if (listeners) {
225
+ const idx = listeners.indexOf(listener);
226
+ if (idx !== -1) {
227
+ listeners.splice(idx, 1);
228
+ }
229
+ }
230
+ }
231
+ function dispatch(ev) {
232
+ const listeners = events.get(ev.type);
233
+ if (listeners) {
234
+ listeners.forEach(listener => {
235
+ try {
236
+ listener(ev);
237
+ }
238
+ catch (_a) {
239
+ }
240
+ });
241
+ }
242
+ }
213
243
  class BroadcastedAndLocalEvent extends rxjs.Observable {
214
244
  constructor(name) {
215
245
  const bc = typeof BroadcastChannel === "undefined"
@@ -223,16 +253,24 @@
223
253
  subscriber.next(ev.data);
224
254
  }
225
255
  let unsubscribe;
226
- self.addEventListener(`lbc-${name}`, onCustomEvent);
227
- if (bc instanceof SWBroadcastChannel) {
228
- unsubscribe = bc.subscribe(message => subscriber.next(message));
256
+ //self.addEventListener(`lbc-${name}`, onCustomEvent); // Fails in service workers
257
+ addListener(`lbc-${name}`, onCustomEvent); // Works better in service worker
258
+ try {
259
+ if (bc instanceof SWBroadcastChannel) {
260
+ unsubscribe = bc.subscribe(message => subscriber.next(message));
261
+ }
262
+ else {
263
+ console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
264
+ bc.addEventListener("message", onMessageEvent);
265
+ }
229
266
  }
230
- else {
231
- console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
232
- bc.addEventListener("message", onMessageEvent);
267
+ catch (err) {
268
+ // Service workers might fail to subscribe outside its initial script.
269
+ console.warn('Failed to subscribe to broadcast channel', err);
233
270
  }
234
271
  return () => {
235
- self.removeEventListener(`lbc-${name}`, onCustomEvent);
272
+ //self.removeEventListener(`lbc-${name}`, onCustomEvent);
273
+ removeListener(`lbc-${name}`, onCustomEvent);
236
274
  if (bc instanceof SWBroadcastChannel) {
237
275
  unsubscribe();
238
276
  }
@@ -248,7 +286,8 @@
248
286
  console.debug("BroadcastedAndLocalEvent: bc.postMessage()", Object.assign({}, message), "bc is a", this.bc);
249
287
  this.bc.postMessage(message);
250
288
  const ev = new CustomEvent(`lbc-${this.name}`, { detail: message });
251
- self.dispatchEvent(ev);
289
+ //self.dispatchEvent(ev);
290
+ dispatch(ev);
252
291
  }
253
292
  }
254
293
 
@@ -1096,7 +1135,6 @@
1096
1135
  return _this;
1097
1136
  }
1098
1137
  AsyncAction.prototype.schedule = function (state, delay) {
1099
- var _a;
1100
1138
  if (delay === void 0) { delay = 0; }
1101
1139
  if (this.closed) {
1102
1140
  return this;
@@ -1109,7 +1147,7 @@
1109
1147
  }
1110
1148
  this.pending = true;
1111
1149
  this.delay = delay;
1112
- this.id = (_a = this.id) !== null && _a !== void 0 ? _a : this.requestAsyncId(scheduler, this.id, delay);
1150
+ this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
1113
1151
  return this;
1114
1152
  };
1115
1153
  AsyncAction.prototype.requestAsyncId = function (scheduler, _id, delay) {
@@ -1121,9 +1159,7 @@
1121
1159
  if (delay != null && this.delay === delay && this.pending === false) {
1122
1160
  return id;
1123
1161
  }
1124
- if (id != null) {
1125
- intervalProvider.clearInterval(id);
1126
- }
1162
+ intervalProvider.clearInterval(id);
1127
1163
  return undefined;
1128
1164
  };
1129
1165
  AsyncAction.prototype.execute = function (state, delay) {
@@ -1199,6 +1235,7 @@
1199
1235
  var _this = _super.call(this, SchedulerAction, now) || this;
1200
1236
  _this.actions = [];
1201
1237
  _this._active = false;
1238
+ _this._scheduled = undefined;
1202
1239
  return _this;
1203
1240
  }
1204
1241
  AsyncScheduler.prototype.flush = function (action) {
@@ -1608,7 +1645,7 @@
1608
1645
  return concat$1(subscriptionDelay.pipe(take(1), ignoreElements()), source.pipe(delayWhen(delayDurationSelector)));
1609
1646
  };
1610
1647
  }
1611
- return mergeMap(function (value, index) { return innerFrom(delayDurationSelector(value, index)).pipe(take(1), mapTo(value)); });
1648
+ return mergeMap(function (value, index) { return delayDurationSelector(value, index).pipe(take(1), mapTo(value)); });
1612
1649
  }
1613
1650
 
1614
1651
  function delay(due, scheduler) {
@@ -2492,6 +2529,14 @@
2492
2529
  }
2493
2530
  function userAuthenticate(context, fetchToken, userInteraction, hints) {
2494
2531
  return __awaiter(this, void 0, void 0, function* () {
2532
+ if (!crypto.subtle) {
2533
+ if (typeof location !== 'undefined' && location.protocol === 'http:') {
2534
+ throw new Error(`Dexie Cloud Addon needs to use WebCrypto, but your browser has disabled it due to being served from an insecure location. Please serve it from https or http://localhost:<port> (See https://stackoverflow.com/questions/46670556/how-to-enable-crypto-subtle-for-unsecure-origins-in-chrome/46671627#46671627)`);
2535
+ }
2536
+ else {
2537
+ throw new Error(`This browser does not support WebCrypto.`);
2538
+ }
2539
+ }
2495
2540
  const { privateKey, publicKey } = yield crypto.subtle.generateKey({
2496
2541
  name: 'RSASSA-PKCS1-v1_5',
2497
2542
  modulusLength: 2048,
@@ -2537,7 +2582,7 @@
2537
2582
  type: 'error',
2538
2583
  messageCode: 'GENERIC_ERROR',
2539
2584
  message: `We're having a problem authenticating right now.`,
2540
- messageParams: {}
2585
+ messageParams: {},
2541
2586
  }).catch(() => { });
2542
2587
  throw error;
2543
2588
  }
@@ -3390,7 +3435,7 @@
3390
3435
  return _sync
3391
3436
  .apply(this, arguments)
3392
3437
  .then(() => {
3393
- if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) {
3438
+ if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
3394
3439
  db.syncStateChangedEvent.next({
3395
3440
  phase: 'in-sync',
3396
3441
  });
@@ -3489,12 +3534,12 @@
3489
3534
  }
3490
3535
  return [clientChanges, syncState, baseRevs];
3491
3536
  }));
3492
- const syncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3537
+ const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3493
3538
  if (justCheckIfNeeded) {
3494
- console.debug('Sync is needed:', syncIsNeeded);
3495
- return syncIsNeeded;
3539
+ console.debug('Sync is needed:', pushSyncIsNeeded);
3540
+ return pushSyncIsNeeded;
3496
3541
  }
3497
- if (purpose === 'push' && !syncIsNeeded) {
3542
+ if (purpose === 'push' && !pushSyncIsNeeded) {
3498
3543
  // The purpose of this request was to push changes
3499
3544
  return false;
3500
3545
  }
@@ -3612,6 +3657,7 @@
3612
3657
  return yield _sync(db, options, schema, { isInitialSync, cancelToken });
3613
3658
  }
3614
3659
  console.debug('SYNC DONE', { isInitialSync });
3660
+ db.syncCompleteEvent.next();
3615
3661
  return false; // Not needed anymore
3616
3662
  });
3617
3663
  }
@@ -3891,6 +3937,7 @@
3891
3937
  if (!db) {
3892
3938
  const localSyncEvent = new rxjs.Subject();
3893
3939
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
3940
+ let syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
3894
3941
  localSyncEvent['id'] = ++static_counter;
3895
3942
  let initiallySynced = false;
3896
3943
  db = {
@@ -3934,6 +3981,9 @@
3934
3981
  get syncStateChangedEvent() {
3935
3982
  return syncStateChangedEvent;
3936
3983
  },
3984
+ get syncCompleteEvent() {
3985
+ return syncCompleteEvent;
3986
+ },
3937
3987
  dx,
3938
3988
  };
3939
3989
  const helperMethods = {
@@ -3965,6 +4015,7 @@
3965
4015
  },
3966
4016
  reconfigure() {
3967
4017
  syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
4018
+ syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
3968
4019
  },
3969
4020
  };
3970
4021
  Object.assign(db, helperMethods);
@@ -4179,18 +4230,32 @@
4179
4230
  }
4180
4231
  }
4181
4232
  // Already authenticated according to given hints.
4182
- return;
4233
+ return false;
4183
4234
  }
4184
4235
  const context = new AuthPersistedContext(db, {
4185
4236
  claims: {},
4186
4237
  lastLogin: new Date(0),
4187
4238
  });
4188
4239
  yield authenticate(db.cloud.options.databaseUrl, context, db.cloud.options.fetchTokens || otpFetchTokenCallback(db), db.cloud.userInteraction, hints);
4189
- yield context.save();
4240
+ try {
4241
+ yield context.save();
4242
+ }
4243
+ catch (e) {
4244
+ try {
4245
+ if (e.name === 'DataCloneError') {
4246
+ console.debug(`Login context property names:`, Object.keys(context));
4247
+ console.debug(`Login context:`, context);
4248
+ console.debug(`Login context JSON:`, JSON.stringify(context));
4249
+ }
4250
+ }
4251
+ catch (_a) { }
4252
+ throw e;
4253
+ }
4190
4254
  yield setCurrentUser(db, context);
4191
4255
  // Make sure to resync as the new login will be authorized
4192
4256
  // for new realms.
4193
4257
  triggerSync(db, "pull");
4258
+ return true;
4194
4259
  });
4195
4260
  }
4196
4261
 
@@ -5083,7 +5148,15 @@
5083
5148
  function createObservable() {
5084
5149
  return db.cloud.persistedSyncState.pipe(filter((syncState) => syncState === null || syncState === void 0 ? void 0 : syncState.serverRevision), // Don't connect before there's no initial sync performed.
5085
5150
  take(1), // Don't continue waking up whenever syncState change
5086
- switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), switchMap(([userLogin, realmSetHash]) =>
5151
+ switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(([userLogin, syncState]) => {
5152
+ if ((userLogin === null || userLogin === void 0 ? void 0 : userLogin.isLoggedIn) && !(syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId))) {
5153
+ // We're in an in-between state when user is logged in but the user's realms are not yet synced.
5154
+ // Don't make this change reconnect the websocket just yet. Wait till syncState is updated
5155
+ // to iclude the user's realm.
5156
+ return db.cloud.persistedSyncState.pipe(filter((syncState) => (syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId)) || false), take(1), map((syncState) => [userLogin, syncState]));
5157
+ }
5158
+ return new rxjs.BehaviorSubject([userLogin, syncState]);
5159
+ }), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), switchMap(([userLogin, realmSetHash]) =>
5087
5160
  // Let server end query changes from last entry of same client-ID and forward.
5088
5161
  // If no new entries, server won't bother the client. If new entries, server sends only those
5089
5162
  // and the baseRev of the last from same client-ID.
@@ -5945,8 +6018,9 @@
5945
6018
  localSyncWorker = null;
5946
6019
  currentUserEmitter.next(UNAUTHORIZED_USER);
5947
6020
  });
6021
+ const syncComplete = new rxjs.Subject();
5948
6022
  dexie.cloud = {
5949
- version: '4.0.1-beta.34',
6023
+ version: '4.0.1-beta.36',
5950
6024
  options: Object.assign({}, DEFAULT_OPTIONS),
5951
6025
  schema: null,
5952
6026
  get currentUserId() {
@@ -5957,6 +6031,9 @@
5957
6031
  phase: 'initial',
5958
6032
  status: 'not-started',
5959
6033
  }),
6034
+ events: {
6035
+ syncComplete,
6036
+ },
5960
6037
  persistedSyncState: new rxjs.BehaviorSubject(undefined),
5961
6038
  userInteraction: new rxjs.BehaviorSubject(undefined),
5962
6039
  webSocketStatus: new rxjs.BehaviorSubject('not-started'),
@@ -6050,6 +6127,8 @@
6050
6127
  if (!db.cloud.isServiceWorkerDB) {
6051
6128
  subscriptions.push(computeSyncState(db).subscribe(dexie.cloud.syncState));
6052
6129
  }
6130
+ // Forward db.syncCompleteEvent to be publicly consumable via db.cloud.events.syncComplete:
6131
+ subscriptions.push(db.syncCompleteEvent.subscribe(syncComplete));
6053
6132
  //verifyConfig(db.cloud.options); Not needed (yet at least!)
6054
6133
  // Verify the user has allowed version increment.
6055
6134
  if (!db.tables.every((table) => table.core)) {
@@ -6157,15 +6236,16 @@
6157
6236
  ]).toPromise();
6158
6237
  }
6159
6238
  // HERE: If requireAuth, do athentication now.
6239
+ let changedUser = false;
6160
6240
  if ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth) {
6161
- yield login(db);
6241
+ changedUser = yield login(db);
6162
6242
  }
6163
6243
  if (localSyncWorker)
6164
6244
  localSyncWorker.stop();
6165
6245
  localSyncWorker = null;
6166
6246
  throwIfClosed();
6167
6247
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
6168
- registerSyncEvent(db, 'push').catch(() => { });
6248
+ registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
6169
6249
  registerPeriodicSyncEvent(db).catch(() => { });
6170
6250
  }
6171
6251
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -6174,7 +6254,7 @@
6174
6254
  // There's no SW. Start SyncWorker instead.
6175
6255
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
6176
6256
  localSyncWorker.start();
6177
- triggerSync(db, 'push');
6257
+ triggerSync(db, changedUser ? 'pull' : 'push');
6178
6258
  }
6179
6259
  // Listen to online event and do sync.
6180
6260
  throwIfClosed();
@@ -6201,7 +6281,7 @@
6201
6281
  });
6202
6282
  }
6203
6283
  }
6204
- dexieCloud.version = '4.0.1-beta.34';
6284
+ dexieCloud.version = '4.0.1-beta.36';
6205
6285
  Dexie__default["default"].Cloud = dexieCloud;
6206
6286
 
6207
6287
  // In case the SW lives for a while, let it reuse already opened connections: