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.
@@ -1,7 +1,7 @@
1
1
  import Dexie, { cmp, liveQuery } from 'dexie';
2
2
  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';
3
3
 
4
- /*! *****************************************************************************
4
+ /******************************************************************************
5
5
  Copyright (c) Microsoft Corporation.
6
6
 
7
7
  Permission to use, copy, modify, and/or distribute this software for any
@@ -106,7 +106,7 @@ function __spreadArray(to, from, pack) {
106
106
  ar[i] = from[i];
107
107
  }
108
108
  }
109
- return to.concat(ar || from);
109
+ return to.concat(ar || Array.prototype.slice.call(from));
110
110
  }
111
111
 
112
112
  function __await(v) {
@@ -203,6 +203,36 @@ class SWBroadcastChannel {
203
203
  }
204
204
  }
205
205
 
206
+ const events = globalThis['lbc-events'] || (globalThis['lbc-events'] = new Map());
207
+ function addListener(name, listener) {
208
+ if (events.has(name)) {
209
+ events.get(name).push(listener);
210
+ }
211
+ else {
212
+ events.set(name, [listener]);
213
+ }
214
+ }
215
+ function removeListener(name, listener) {
216
+ const listeners = events.get(name);
217
+ if (listeners) {
218
+ const idx = listeners.indexOf(listener);
219
+ if (idx !== -1) {
220
+ listeners.splice(idx, 1);
221
+ }
222
+ }
223
+ }
224
+ function dispatch(ev) {
225
+ const listeners = events.get(ev.type);
226
+ if (listeners) {
227
+ listeners.forEach(listener => {
228
+ try {
229
+ listener(ev);
230
+ }
231
+ catch (_a) {
232
+ }
233
+ });
234
+ }
235
+ }
206
236
  class BroadcastedAndLocalEvent extends Observable$1 {
207
237
  constructor(name) {
208
238
  const bc = typeof BroadcastChannel === "undefined"
@@ -216,16 +246,24 @@ class BroadcastedAndLocalEvent extends Observable$1 {
216
246
  subscriber.next(ev.data);
217
247
  }
218
248
  let unsubscribe;
219
- self.addEventListener(`lbc-${name}`, onCustomEvent);
220
- if (bc instanceof SWBroadcastChannel) {
221
- unsubscribe = bc.subscribe(message => subscriber.next(message));
249
+ //self.addEventListener(`lbc-${name}`, onCustomEvent); // Fails in service workers
250
+ addListener(`lbc-${name}`, onCustomEvent); // Works better in service worker
251
+ try {
252
+ if (bc instanceof SWBroadcastChannel) {
253
+ unsubscribe = bc.subscribe(message => subscriber.next(message));
254
+ }
255
+ else {
256
+ console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
257
+ bc.addEventListener("message", onMessageEvent);
258
+ }
222
259
  }
223
- else {
224
- console.debug("BroadcastedAndLocalEvent: bc.addEventListener()", name, "bc is a", bc);
225
- bc.addEventListener("message", onMessageEvent);
260
+ catch (err) {
261
+ // Service workers might fail to subscribe outside its initial script.
262
+ console.warn('Failed to subscribe to broadcast channel', err);
226
263
  }
227
264
  return () => {
228
- self.removeEventListener(`lbc-${name}`, onCustomEvent);
265
+ //self.removeEventListener(`lbc-${name}`, onCustomEvent);
266
+ removeListener(`lbc-${name}`, onCustomEvent);
229
267
  if (bc instanceof SWBroadcastChannel) {
230
268
  unsubscribe();
231
269
  }
@@ -241,7 +279,8 @@ class BroadcastedAndLocalEvent extends Observable$1 {
241
279
  console.debug("BroadcastedAndLocalEvent: bc.postMessage()", Object.assign({}, message), "bc is a", this.bc);
242
280
  this.bc.postMessage(message);
243
281
  const ev = new CustomEvent(`lbc-${this.name}`, { detail: message });
244
- self.dispatchEvent(ev);
282
+ //self.dispatchEvent(ev);
283
+ dispatch(ev);
245
284
  }
246
285
  }
247
286
 
@@ -1089,7 +1128,6 @@ var AsyncAction = (function (_super) {
1089
1128
  return _this;
1090
1129
  }
1091
1130
  AsyncAction.prototype.schedule = function (state, delay) {
1092
- var _a;
1093
1131
  if (delay === void 0) { delay = 0; }
1094
1132
  if (this.closed) {
1095
1133
  return this;
@@ -1102,7 +1140,7 @@ var AsyncAction = (function (_super) {
1102
1140
  }
1103
1141
  this.pending = true;
1104
1142
  this.delay = delay;
1105
- this.id = (_a = this.id) !== null && _a !== void 0 ? _a : this.requestAsyncId(scheduler, this.id, delay);
1143
+ this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
1106
1144
  return this;
1107
1145
  };
1108
1146
  AsyncAction.prototype.requestAsyncId = function (scheduler, _id, delay) {
@@ -1114,9 +1152,7 @@ var AsyncAction = (function (_super) {
1114
1152
  if (delay != null && this.delay === delay && this.pending === false) {
1115
1153
  return id;
1116
1154
  }
1117
- if (id != null) {
1118
- intervalProvider.clearInterval(id);
1119
- }
1155
+ intervalProvider.clearInterval(id);
1120
1156
  return undefined;
1121
1157
  };
1122
1158
  AsyncAction.prototype.execute = function (state, delay) {
@@ -1192,6 +1228,7 @@ var AsyncScheduler = (function (_super) {
1192
1228
  var _this = _super.call(this, SchedulerAction, now) || this;
1193
1229
  _this.actions = [];
1194
1230
  _this._active = false;
1231
+ _this._scheduled = undefined;
1195
1232
  return _this;
1196
1233
  }
1197
1234
  AsyncScheduler.prototype.flush = function (action) {
@@ -1601,7 +1638,7 @@ function delayWhen(delayDurationSelector, subscriptionDelay) {
1601
1638
  return concat$1(subscriptionDelay.pipe(take(1), ignoreElements()), source.pipe(delayWhen(delayDurationSelector)));
1602
1639
  };
1603
1640
  }
1604
- return mergeMap(function (value, index) { return innerFrom(delayDurationSelector(value, index)).pipe(take(1), mapTo(value)); });
1641
+ return mergeMap(function (value, index) { return delayDurationSelector(value, index).pipe(take(1), mapTo(value)); });
1605
1642
  }
1606
1643
 
1607
1644
  function delay(due, scheduler) {
@@ -2485,6 +2522,14 @@ function refreshAccessToken(url, login) {
2485
2522
  }
2486
2523
  function userAuthenticate(context, fetchToken, userInteraction, hints) {
2487
2524
  return __awaiter(this, void 0, void 0, function* () {
2525
+ if (!crypto.subtle) {
2526
+ if (typeof location !== 'undefined' && location.protocol === 'http:') {
2527
+ 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)`);
2528
+ }
2529
+ else {
2530
+ throw new Error(`This browser does not support WebCrypto.`);
2531
+ }
2532
+ }
2488
2533
  const { privateKey, publicKey } = yield crypto.subtle.generateKey({
2489
2534
  name: 'RSASSA-PKCS1-v1_5',
2490
2535
  modulusLength: 2048,
@@ -2530,7 +2575,7 @@ function userAuthenticate(context, fetchToken, userInteraction, hints) {
2530
2575
  type: 'error',
2531
2576
  messageCode: 'GENERIC_ERROR',
2532
2577
  message: `We're having a problem authenticating right now.`,
2533
- messageParams: {}
2578
+ messageParams: {},
2534
2579
  }).catch(() => { });
2535
2580
  throw error;
2536
2581
  }
@@ -3383,7 +3428,7 @@ function sync(db, options, schema, syncOptions) {
3383
3428
  return _sync
3384
3429
  .apply(this, arguments)
3385
3430
  .then(() => {
3386
- if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) {
3431
+ if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) { // && syncOptions?.purpose !== 'push') {
3387
3432
  db.syncStateChangedEvent.next({
3388
3433
  phase: 'in-sync',
3389
3434
  });
@@ -3482,12 +3527,12 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3482
3527
  }
3483
3528
  return [clientChanges, syncState, baseRevs];
3484
3529
  }));
3485
- const syncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3530
+ const pushSyncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
3486
3531
  if (justCheckIfNeeded) {
3487
- console.debug('Sync is needed:', syncIsNeeded);
3488
- return syncIsNeeded;
3532
+ console.debug('Sync is needed:', pushSyncIsNeeded);
3533
+ return pushSyncIsNeeded;
3489
3534
  }
3490
- if (purpose === 'push' && !syncIsNeeded) {
3535
+ if (purpose === 'push' && !pushSyncIsNeeded) {
3491
3536
  // The purpose of this request was to push changes
3492
3537
  return false;
3493
3538
  }
@@ -3605,6 +3650,7 @@ function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNee
3605
3650
  return yield _sync(db, options, schema, { isInitialSync, cancelToken });
3606
3651
  }
3607
3652
  console.debug('SYNC DONE', { isInitialSync });
3653
+ db.syncCompleteEvent.next();
3608
3654
  return false; // Not needed anymore
3609
3655
  });
3610
3656
  }
@@ -3884,6 +3930,7 @@ function DexieCloudDB(dx) {
3884
3930
  if (!db) {
3885
3931
  const localSyncEvent = new Subject();
3886
3932
  let syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
3933
+ let syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
3887
3934
  localSyncEvent['id'] = ++static_counter;
3888
3935
  let initiallySynced = false;
3889
3936
  db = {
@@ -3927,6 +3974,9 @@ function DexieCloudDB(dx) {
3927
3974
  get syncStateChangedEvent() {
3928
3975
  return syncStateChangedEvent;
3929
3976
  },
3977
+ get syncCompleteEvent() {
3978
+ return syncCompleteEvent;
3979
+ },
3930
3980
  dx,
3931
3981
  };
3932
3982
  const helperMethods = {
@@ -3958,6 +4008,7 @@ function DexieCloudDB(dx) {
3958
4008
  },
3959
4009
  reconfigure() {
3960
4010
  syncStateChangedEvent = new BroadcastedAndLocalEvent(`syncstatechanged-${dx.name}`);
4011
+ syncCompleteEvent = new BroadcastedAndLocalEvent(`synccomplete-${dx.name}`);
3961
4012
  },
3962
4013
  };
3963
4014
  Object.assign(db, helperMethods);
@@ -4172,18 +4223,32 @@ function login(db, hints) {
4172
4223
  }
4173
4224
  }
4174
4225
  // Already authenticated according to given hints.
4175
- return;
4226
+ return false;
4176
4227
  }
4177
4228
  const context = new AuthPersistedContext(db, {
4178
4229
  claims: {},
4179
4230
  lastLogin: new Date(0),
4180
4231
  });
4181
4232
  yield authenticate(db.cloud.options.databaseUrl, context, db.cloud.options.fetchTokens || otpFetchTokenCallback(db), db.cloud.userInteraction, hints);
4182
- yield context.save();
4233
+ try {
4234
+ yield context.save();
4235
+ }
4236
+ catch (e) {
4237
+ try {
4238
+ if (e.name === 'DataCloneError') {
4239
+ console.debug(`Login context property names:`, Object.keys(context));
4240
+ console.debug(`Login context:`, context);
4241
+ console.debug(`Login context JSON:`, JSON.stringify(context));
4242
+ }
4243
+ }
4244
+ catch (_a) { }
4245
+ throw e;
4246
+ }
4183
4247
  yield setCurrentUser(db, context);
4184
4248
  // Make sure to resync as the new login will be authorized
4185
4249
  // for new realms.
4186
4250
  triggerSync(db, "pull");
4251
+ return true;
4187
4252
  });
4188
4253
  }
4189
4254
 
@@ -5076,7 +5141,15 @@ function connectWebSocket(db) {
5076
5141
  function createObservable() {
5077
5142
  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.
5078
5143
  take(1), // Don't continue waking up whenever syncState change
5079
- 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]) =>
5144
+ switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(([userLogin, syncState]) => {
5145
+ if ((userLogin === null || userLogin === void 0 ? void 0 : userLogin.isLoggedIn) && !(syncState === null || syncState === void 0 ? void 0 : syncState.realms.includes(userLogin.userId))) {
5146
+ // We're in an in-between state when user is logged in but the user's realms are not yet synced.
5147
+ // Don't make this change reconnect the websocket just yet. Wait till syncState is updated
5148
+ // to iclude the user's realm.
5149
+ 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]));
5150
+ }
5151
+ return new BehaviorSubject([userLogin, syncState]);
5152
+ }), switchMap(([userLogin, syncState]) => __awaiter(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), switchMap(([userLogin, realmSetHash]) =>
5080
5153
  // Let server end query changes from last entry of same client-ID and forward.
5081
5154
  // If no new entries, server won't bother the client. If new entries, server sends only those
5082
5155
  // and the baseRev of the last from same client-ID.
@@ -5938,8 +6011,9 @@ function dexieCloud(dexie) {
5938
6011
  localSyncWorker = null;
5939
6012
  currentUserEmitter.next(UNAUTHORIZED_USER);
5940
6013
  });
6014
+ const syncComplete = new Subject();
5941
6015
  dexie.cloud = {
5942
- version: '4.0.1-beta.34',
6016
+ version: '4.0.1-beta.36',
5943
6017
  options: Object.assign({}, DEFAULT_OPTIONS),
5944
6018
  schema: null,
5945
6019
  get currentUserId() {
@@ -5950,6 +6024,9 @@ function dexieCloud(dexie) {
5950
6024
  phase: 'initial',
5951
6025
  status: 'not-started',
5952
6026
  }),
6027
+ events: {
6028
+ syncComplete,
6029
+ },
5953
6030
  persistedSyncState: new BehaviorSubject(undefined),
5954
6031
  userInteraction: new BehaviorSubject(undefined),
5955
6032
  webSocketStatus: new BehaviorSubject('not-started'),
@@ -6043,6 +6120,8 @@ function dexieCloud(dexie) {
6043
6120
  if (!db.cloud.isServiceWorkerDB) {
6044
6121
  subscriptions.push(computeSyncState(db).subscribe(dexie.cloud.syncState));
6045
6122
  }
6123
+ // Forward db.syncCompleteEvent to be publicly consumable via db.cloud.events.syncComplete:
6124
+ subscriptions.push(db.syncCompleteEvent.subscribe(syncComplete));
6046
6125
  //verifyConfig(db.cloud.options); Not needed (yet at least!)
6047
6126
  // Verify the user has allowed version increment.
6048
6127
  if (!db.tables.every((table) => table.core)) {
@@ -6150,15 +6229,16 @@ function dexieCloud(dexie) {
6150
6229
  ]).toPromise();
6151
6230
  }
6152
6231
  // HERE: If requireAuth, do athentication now.
6232
+ let changedUser = false;
6153
6233
  if ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth) {
6154
- yield login(db);
6234
+ changedUser = yield login(db);
6155
6235
  }
6156
6236
  if (localSyncWorker)
6157
6237
  localSyncWorker.stop();
6158
6238
  localSyncWorker = null;
6159
6239
  throwIfClosed();
6160
6240
  if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
6161
- registerSyncEvent(db, 'push').catch(() => { });
6241
+ registerSyncEvent(db, changedUser ? 'pull' : 'push').catch(() => { });
6162
6242
  registerPeriodicSyncEvent(db).catch(() => { });
6163
6243
  }
6164
6244
  else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
@@ -6167,7 +6247,7 @@ function dexieCloud(dexie) {
6167
6247
  // There's no SW. Start SyncWorker instead.
6168
6248
  localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
6169
6249
  localSyncWorker.start();
6170
- triggerSync(db, 'push');
6250
+ triggerSync(db, changedUser ? 'pull' : 'push');
6171
6251
  }
6172
6252
  // Listen to online event and do sync.
6173
6253
  throwIfClosed();
@@ -6194,7 +6274,7 @@ function dexieCloud(dexie) {
6194
6274
  });
6195
6275
  }
6196
6276
  }
6197
- dexieCloud.version = '4.0.1-beta.34';
6277
+ dexieCloud.version = '4.0.1-beta.36';
6198
6278
  Dexie.Cloud = dexieCloud;
6199
6279
 
6200
6280
  // In case the SW lives for a while, let it reuse already opened connections: