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
  * ==========================================================================
10
10
  *
11
- * Version 4.0.1-beta.34, Mon Mar 27 2023
11
+ * Version 4.0.1-beta.36, Mon Apr 17 2023
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -19,7 +19,7 @@
19
19
  import Dexie, { cmp, liveQuery } from 'dexie';
20
20
  import { Observable as Observable$1, BehaviorSubject, Subject, fromEvent, of, merge, Subscription as Subscription$1, from as from$1, throwError, combineLatest, map as map$1, share, timer as timer$1 } from 'rxjs';
21
21
 
22
- /*! *****************************************************************************
22
+ /******************************************************************************
23
23
  Copyright (c) Microsoft Corporation.
24
24
 
25
25
  Permission to use, copy, modify, and/or distribute this software for any
@@ -124,7 +124,7 @@ function __spreadArray(to, from, pack) {
124
124
  ar[i] = from[i];
125
125
  }
126
126
  }
127
- return to.concat(ar || from);
127
+ return to.concat(ar || Array.prototype.slice.call(from));
128
128
  }
129
129
 
130
130
  function __await(v) {
@@ -1299,7 +1299,6 @@ var AsyncAction = (function (_super) {
1299
1299
  return _this;
1300
1300
  }
1301
1301
  AsyncAction.prototype.schedule = function (state, delay) {
1302
- var _a;
1303
1302
  if (delay === void 0) { delay = 0; }
1304
1303
  if (this.closed) {
1305
1304
  return this;
@@ -1312,7 +1311,7 @@ var AsyncAction = (function (_super) {
1312
1311
  }
1313
1312
  this.pending = true;
1314
1313
  this.delay = delay;
1315
- this.id = (_a = this.id) !== null && _a !== void 0 ? _a : this.requestAsyncId(scheduler, this.id, delay);
1314
+ this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
1316
1315
  return this;
1317
1316
  };
1318
1317
  AsyncAction.prototype.requestAsyncId = function (scheduler, _id, delay) {
@@ -1324,9 +1323,7 @@ var AsyncAction = (function (_super) {
1324
1323
  if (delay != null && this.delay === delay && this.pending === false) {
1325
1324
  return id;
1326
1325
  }
1327
- if (id != null) {
1328
- intervalProvider.clearInterval(id);
1329
- }
1326
+ intervalProvider.clearInterval(id);
1330
1327
  return undefined;
1331
1328
  };
1332
1329
  AsyncAction.prototype.execute = function (state, delay) {
@@ -1402,6 +1399,7 @@ var AsyncScheduler = (function (_super) {
1402
1399
  var _this = _super.call(this, SchedulerAction, now) || this;
1403
1400
  _this.actions = [];
1404
1401
  _this._active = false;
1402
+ _this._scheduled = undefined;
1405
1403
  return _this;
1406
1404
  }
1407
1405
  AsyncScheduler.prototype.flush = function (action) {
@@ -1811,7 +1809,7 @@ function delayWhen(delayDurationSelector, subscriptionDelay) {
1811
1809
  return concat$1(subscriptionDelay.pipe(take(1), ignoreElements()), source.pipe(delayWhen(delayDurationSelector)));
1812
1810
  };
1813
1811
  }
1814
- return mergeMap(function (value, index) { return innerFrom(delayDurationSelector(value, index)).pipe(take(1), mapTo(value)); });
1812
+ return mergeMap(function (value, index) { return delayDurationSelector(value, index).pipe(take(1), mapTo(value)); });
1815
1813
  }
1816
1814
 
1817
1815
  function delay(due, scheduler) {
@@ -2228,6 +2226,14 @@ function refreshAccessToken(url, login) {
2228
2226
  }
2229
2227
  function userAuthenticate(context, fetchToken, userInteraction, hints) {
2230
2228
  return __awaiter(this, void 0, void 0, function* () {
2229
+ if (!crypto.subtle) {
2230
+ if (typeof location !== 'undefined' && location.protocol === 'http:') {
2231
+ 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)`);
2232
+ }
2233
+ else {
2234
+ throw new Error(`This browser does not support WebCrypto.`);
2235
+ }
2236
+ }
2231
2237
  const { privateKey, publicKey } = yield crypto.subtle.generateKey({
2232
2238
  name: 'RSASSA-PKCS1-v1_5',
2233
2239
  modulusLength: 2048,
@@ -2273,7 +2279,7 @@ function userAuthenticate(context, fetchToken, userInteraction, hints) {
2273
2279
  type: 'error',
2274
2280
  messageCode: 'GENERIC_ERROR',
2275
2281
  message: `We're having a problem authenticating right now.`,
2276
- messageParams: {}
2282
+ messageParams: {},
2277
2283
  }).catch(() => { });
2278
2284
  throw error;
2279
2285
  }
@@ -2504,18 +2510,32 @@ function login(db, hints) {
2504
2510
  }
2505
2511
  }
2506
2512
  // Already authenticated according to given hints.
2507
- return;
2513
+ return false;
2508
2514
  }
2509
2515
  const context = new AuthPersistedContext(db, {
2510
2516
  claims: {},
2511
2517
  lastLogin: new Date(0),
2512
2518
  });
2513
2519
  yield authenticate(db.cloud.options.databaseUrl, context, db.cloud.options.fetchTokens || otpFetchTokenCallback(db), db.cloud.userInteraction, hints);
2514
- yield context.save();
2520
+ try {
2521
+ yield context.save();
2522
+ }
2523
+ catch (e) {
2524
+ try {
2525
+ if (e.name === 'DataCloneError') {
2526
+ console.debug(`Login context property names:`, Object.keys(context));
2527
+ console.debug(`Login context:`, context);
2528
+ console.debug(`Login context JSON:`, JSON.stringify(context));
2529
+ }
2530
+ }
2531
+ catch (_a) { }
2532
+ throw e;
2533
+ }
2515
2534
  yield setCurrentUser(db, context);
2516
2535
  // Make sure to resync as the new login will be authorized
2517
2536
  // for new realms.
2518
2537
  triggerSync(db, "pull");
2538
+ return true;
2519
2539
  });
2520
2540
  }
2521
2541
 
@@ -2589,6 +2609,36 @@ class SWBroadcastChannel {
2589
2609
  }
2590
2610
  }
2591
2611
 
2612
+ const events = globalThis['lbc-events'] || (globalThis['lbc-events'] = new Map());
2613
+ function addListener(name, listener) {
2614
+ if (events.has(name)) {
2615
+ events.get(name).push(listener);
2616
+ }
2617
+ else {
2618
+ events.set(name, [listener]);
2619
+ }
2620
+ }
2621
+ function removeListener(name, listener) {
2622
+ const listeners = events.get(name);
2623
+ if (listeners) {
2624
+ const idx = listeners.indexOf(listener);
2625
+ if (idx !== -1) {
2626
+ listeners.splice(idx, 1);
2627
+ }
2628
+ }
2629
+ }
2630
+ function dispatch(ev) {
2631
+ const listeners = events.get(ev.type);
2632
+ if (listeners) {
2633
+ listeners.forEach(listener => {
2634
+ try {
2635
+ listener(ev);
2636
+ }
2637
+ catch (_a) {
2638
+ }
2639
+ });
2640
+ }
2641
+ }
2592
2642
  class BroadcastedAndLocalEvent extends Observable$1 {
2593
2643
  constructor(name) {
2594
2644
  const bc = typeof BroadcastChannel === "undefined"
@@ -2602,16 +2652,24 @@ class BroadcastedAndLocalEvent extends Observable$1 {
2602
2652
  subscriber.next(ev.data);
2603
2653
  }
2604
2654
  let unsubscribe;
2605
- self.addEventListener(`lbc-${name}`, onCustomEvent);
2606
- if (bc instanceof SWBroadcastChannel) {
2607
- unsubscribe = bc.subscribe(message => subscriber.next(message));
2655
+ //self.addEventListener(`lbc-${name}`, onCustomEvent); // Fails in service workers
2656
+ addListener(`lbc-${name}`, onCustomEvent); // Works better in service worker
2657
+ try {
2658
+ if (bc instanceof SWBroadcastChannel) {
2659
+ unsubscribe = bc.subscribe(message => subscriber.next(message));
2660
+ }
2661
+ else {
2662
+ console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
2663
+ bc.addEventListener("message", onMessageEvent);
2664
+ }
2608
2665
  }
2609
- else {
2610
- console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
2611
- bc.addEventListener("message", onMessageEvent);
2666
+ catch (err) {
2667
+ // Service workers might fail to subscribe outside its initial script.
2668
+ console.warn('Failed to subscribe to broadcast channel', err);
2612
2669
  }
2613
2670
  return () => {
2614
- self.removeEventListener(`lbc-${name}`, onCustomEvent);
2671
+ //self.removeEventListener(`lbc-${name}`, onCustomEvent);
2672
+ removeListener(`lbc-${name}`, onCustomEvent);
2615
2673
  if (bc instanceof SWBroadcastChannel) {
2616
2674
  unsubscribe();
2617
2675
  }
@@ -2627,7 +2685,8 @@ class BroadcastedAndLocalEvent extends Observable$1 {
2627
2685
  console.debug("BroadcastedAndLocalEvent: bc.postMessage()", Object.assign({}, message), "bc is a", this.bc);
2628
2686
  this.bc.postMessage(message);
2629
2687
  const ev = new CustomEvent(`lbc-${this.name}`, { detail: message });
2630
- self.dispatchEvent(ev);
2688
+ //self.dispatchEvent(ev);
2689
+ dispatch(ev);
2631
2690
  }
2632
2691
  }
2633
2692
 
@@ -3616,7 +3675,7 @@ function sync(db, options, schema, syncOptions) {
3616
3675
  return _sync
3617
3676
  .apply(this, arguments)
3618
3677
  .then(() => {
3619
- if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) {
3678
+ if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
3620
3679
  db.syncStateChangedEvent.next({
3621
3680
  phase: 'in-sync',
3622
3681
  });
@@ -3715,12 +3774,12 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3715
3774
  }
3716
3775
  return [clientChanges, syncState, baseRevs];
3717
3776
  }));
3718
- const syncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3777
+ const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3719
3778
  if (justCheckIfNeeded) {
3720
- console.debug('Sync is needed:', syncIsNeeded);
3721
- return syncIsNeeded;
3779
+ console.debug('Sync is needed:', pushSyncIsNeeded);
3780
+ return pushSyncIsNeeded;
3722
3781
  }
3723
- if (purpose === 'push' && !syncIsNeeded) {
3782
+ if (purpose === 'push' && !pushSyncIsNeeded) {
3724
3783
  // The purpose of this request was to push changes
3725
3784
  return false;
3726
3785
  }
@@ -3838,6 +3897,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3838
3897
  return yield _sync(db, options, schema, { isInitialSync, cancelToken });
3839
3898
  }
3840
3899
  console.debug('SYNC DONE', { isInitialSync });
3900
+ db.syncCompleteEvent.next();
3841
3901
  return false; // Not needed anymore
3842
3902
  });
3843
3903
  }
@@ -4117,6 +4177,7 @@ function DexieCloudDB(dx) {
4117
4177
  if (!db) {
4118
4178
  const localSyncEvent = new Subject();
4119
4179
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
4180
+ let syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
4120
4181
  localSyncEvent['id'] = ++static_counter;
4121
4182
  let initiallySynced = false;
4122
4183
  db = {
@@ -4160,6 +4221,9 @@ function DexieCloudDB(dx) {
4160
4221
  get syncStateChangedEvent() {
4161
4222
  return syncStateChangedEvent;
4162
4223
  },
4224
+ get syncCompleteEvent() {
4225
+ return syncCompleteEvent;
4226
+ },
4163
4227
  dx,
4164
4228
  };
4165
4229
  const helperMethods = {
@@ -4191,6 +4255,7 @@ function DexieCloudDB(dx) {
4191
4255
  },
4192
4256
  reconfigure() {
4193
4257
  syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
4258
+ syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
4194
4259
  },
4195
4260
  };
4196
4261
  Object.assign(db, helperMethods);
@@ -5094,7 +5159,15 @@ function connectWebSocket(db) {
5094
5159
  function createObservable() {
5095
5160
  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.
5096
5161
  take(1), // Don't continue waking up whenever syncState change
5097
- 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]) =>
5162
+ switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(([userLogin, syncState]) => {
5163
+ if ((userLogin === null || userLogin === void 0 ? void 0 : userLogin.isLoggedIn) && !(syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId))) {
5164
+ // We're in an in-between state when user is logged in but the user's realms are not yet synced.
5165
+ // Don't make this change reconnect the websocket just yet. Wait till syncState is updated
5166
+ // to iclude the user's realm.
5167
+ 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]));
5168
+ }
5169
+ return new BehaviorSubject([userLogin, syncState]);
5170
+ }), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), switchMap(([userLogin, realmSetHash]) =>
5098
5171
  // Let server end query changes from last entry of same client-ID and forward.
5099
5172
  // If no new entries, server won't bother the client. If new entries, server sends only those
5100
5173
  // and the baseRev of the last from same client-ID.
@@ -5963,8 +6036,9 @@ function dexieCloud(dexie) {
5963
6036
  localSyncWorker = null;
5964
6037
  currentUserEmitter.next(UNAUTHORIZED_USER);
5965
6038
  });
6039
+ const syncComplete = new Subject();
5966
6040
  dexie.cloud = {
5967
- version: '4.0.1-beta.34',
6041
+ version: '4.0.1-beta.36',
5968
6042
  options: Object.assign({}, DEFAULT_OPTIONS),
5969
6043
  schema: null,
5970
6044
  get currentUserId() {
@@ -5975,6 +6049,9 @@ function dexieCloud(dexie) {
5975
6049
  phase: 'initial',
5976
6050
  status: 'not-started',
5977
6051
  }),
6052
+ events: {
6053
+ syncComplete,
6054
+ },
5978
6055
  persistedSyncState: new BehaviorSubject(undefined),
5979
6056
  userInteraction: new BehaviorSubject(undefined),
5980
6057
  webSocketStatus: new BehaviorSubject('not-started'),
@@ -6068,6 +6145,8 @@ function dexieCloud(dexie) {
6068
6145
  if (!db.cloud.isServiceWorkerDB) {
6069
6146
  subscriptions.push(computeSyncState(db).subscribe(dexie.cloud.syncState));
6070
6147
  }
6148
+ // Forward db.syncCompleteEvent to be publicly consumable via db.cloud.events.syncComplete:
6149
+ subscriptions.push(db.syncCompleteEvent.subscribe(syncComplete));
6071
6150
  //verifyConfig(db.cloud.options); Not needed (yet at least!)
6072
6151
  // Verify the user has allowed version increment.
6073
6152
  if (!db.tables.every((table) => table.core)) {
@@ -6175,15 +6254,16 @@ function dexieCloud(dexie) {
6175
6254
  ]).toPromise();
6176
6255
  }
6177
6256
  // HERE: If requireAuth, do athentication now.
6257
+ let changedUser = false;
6178
6258
  if ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth) {
6179
- yield login(db);
6259
+ changedUser = yield login(db);
6180
6260
  }
6181
6261
  if (localSyncWorker)
6182
6262
  localSyncWorker.stop();
6183
6263
  localSyncWorker = null;
6184
6264
  throwIfClosed();
6185
6265
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
6186
- registerSyncEvent(db, 'push').catch(() => { });
6266
+ registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
6187
6267
  registerPeriodicSyncEvent(db).catch(() => { });
6188
6268
  }
6189
6269
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -6192,7 +6272,7 @@ function dexieCloud(dexie) {
6192
6272
  // There's no SW. Start SyncWorker instead.
6193
6273
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
6194
6274
  localSyncWorker.start();
6195
- triggerSync(db, 'push');
6275
+ triggerSync(db, changedUser ? 'pull' : 'push');
6196
6276
  }
6197
6277
  // Listen to online event and do sync.
6198
6278
  throwIfClosed();
@@ -6219,7 +6299,7 @@ function dexieCloud(dexie) {
6219
6299
  });
6220
6300
  }
6221
6301
  }
6222
- dexieCloud.version = '4.0.1-beta.34';
6302
+ dexieCloud.version = '4.0.1-beta.36';
6223
6303
  Dexie.Cloud = dexieCloud;
6224
6304
 
6225
6305
  export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId };