dexie-cloud-addon 4.0.0-beta.14 → 4.0.0-beta.17
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/dexie-cloud-addon.js +194 -138
- 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 +193 -137
- 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/module-es5/dexie-cloud-addon.js +231 -180
- package/dist/module-es5/dexie-cloud-addon.js.map +1 -1
- package/dist/module-es5/dexie-cloud-addon.min.js +1 -1
- package/dist/module-es5/dexie-cloud-addon.min.js.map +1 -1
- package/dist/types/DexieCloudAPI.d.ts +4 -1
- package/dist/types/WSObservable.d.ts +1 -0
- package/dist/types/getGlobalRolesObservable.d.ts +5 -0
- package/dist/types/getInvitesObservable.d.ts +1 -1
- package/dist/umd/dexie-cloud-addon.js +231 -180
- 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 +192 -136
- 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-modern/dexie-cloud-addon.js +190 -134
- package/dist/umd-modern/dexie-cloud-addon.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.0.0-beta.
|
|
11
|
+
* Version 4.0.0-beta.17, Fri Apr 08 2022
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import Dexie, { cmp, liveQuery } from 'dexie';
|
|
20
|
-
import { Observable as Observable$1, BehaviorSubject, Subject,
|
|
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, switchMap as switchMap$1 } from 'rxjs';
|
|
21
21
|
|
|
22
22
|
//@ts-check
|
|
23
23
|
const randomFillSync = crypto.getRandomValues;
|
|
@@ -4660,110 +4660,13 @@ function overrideParseStoresSpec(origFunc, dexie) {
|
|
|
4660
4660
|
};
|
|
4661
4661
|
}
|
|
4662
4662
|
|
|
4663
|
-
const SECONDS = 1000;
|
|
4664
|
-
const MINUTES = 60 * SECONDS;
|
|
4665
|
-
|
|
4666
|
-
const myId = randomString(16);
|
|
4667
|
-
|
|
4668
|
-
const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
|
|
4669
|
-
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
4670
|
-
async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
4671
|
-
// Start working.
|
|
4672
|
-
//
|
|
4673
|
-
// Check if someone else is working on this already.
|
|
4674
|
-
//
|
|
4675
|
-
const jobsTable = db.table(jobsTableName);
|
|
4676
|
-
async function aquireLock() {
|
|
4677
|
-
const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
|
|
4678
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4679
|
-
if (!currentWork) {
|
|
4680
|
-
// No one else is working. Let's record that we are.
|
|
4681
|
-
await jobsTable.add({
|
|
4682
|
-
nodeId: myId,
|
|
4683
|
-
started: new Date(),
|
|
4684
|
-
heartbeat: new Date()
|
|
4685
|
-
}, jobName);
|
|
4686
|
-
return true;
|
|
4687
|
-
}
|
|
4688
|
-
else if (currentWork.heartbeat.getTime() <
|
|
4689
|
-
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
4690
|
-
console.warn(`Latest ${jobName} worker seem to have died.\n`, `The dead job started:`, currentWork.started, `\n`, `Last heart beat was:`, currentWork.heartbeat, '\n', `We're now taking over!`);
|
|
4691
|
-
// Now, take over!
|
|
4692
|
-
await jobsTable.put({
|
|
4693
|
-
nodeId: myId,
|
|
4694
|
-
started: new Date(),
|
|
4695
|
-
heartbeat: new Date()
|
|
4696
|
-
}, jobName);
|
|
4697
|
-
return true;
|
|
4698
|
-
}
|
|
4699
|
-
return false;
|
|
4700
|
-
});
|
|
4701
|
-
if (gotTheLock)
|
|
4702
|
-
return true;
|
|
4703
|
-
// Someone else took the job.
|
|
4704
|
-
if (awaitRemoteJob) {
|
|
4705
|
-
try {
|
|
4706
|
-
const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
4707
|
-
await jobDoneObservable.toPromise();
|
|
4708
|
-
return false;
|
|
4709
|
-
}
|
|
4710
|
-
catch (err) {
|
|
4711
|
-
if (err.name !== 'TimeoutError') {
|
|
4712
|
-
throw err;
|
|
4713
|
-
}
|
|
4714
|
-
// Timeout stopped us! Try aquire the lock now.
|
|
4715
|
-
// It will likely succeed this time unless
|
|
4716
|
-
// another client took it.
|
|
4717
|
-
return await aquireLock();
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
return false;
|
|
4721
|
-
}
|
|
4722
|
-
if (await aquireLock()) {
|
|
4723
|
-
// We own the lock entry and can do our job undisturbed.
|
|
4724
|
-
// We're not within a transaction, but these type of locks
|
|
4725
|
-
// spans over transactions.
|
|
4726
|
-
// Start our heart beat during the job.
|
|
4727
|
-
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
4728
|
-
const heartbeat = setInterval(() => {
|
|
4729
|
-
jobsTable.update(jobName, (job) => {
|
|
4730
|
-
if (job.nodeId === myId) {
|
|
4731
|
-
job.heartbeat = new Date();
|
|
4732
|
-
}
|
|
4733
|
-
});
|
|
4734
|
-
}, GUARDED_JOB_HEARTBEAT);
|
|
4735
|
-
try {
|
|
4736
|
-
return await job();
|
|
4737
|
-
}
|
|
4738
|
-
finally {
|
|
4739
|
-
// Stop heartbeat
|
|
4740
|
-
clearInterval(heartbeat);
|
|
4741
|
-
// Remove the persisted job state:
|
|
4742
|
-
await db.transaction('rw!', jobsTableName, async () => {
|
|
4743
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4744
|
-
if (currentWork && currentWork.nodeId === myId) {
|
|
4745
|
-
jobsTable.delete(jobName);
|
|
4746
|
-
}
|
|
4747
|
-
});
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
|
|
4752
4663
|
async function performInitialSync(db, cloudOptions, cloudSchema) {
|
|
4753
|
-
console.debug(
|
|
4754
|
-
await
|
|
4755
|
-
|
|
4756
|
-
// Do check again (now within a transaction) that we really do not have a sync state:
|
|
4757
|
-
const syncState = await db.getPersistedSyncState();
|
|
4758
|
-
if (!syncState?.initiallySynced) {
|
|
4759
|
-
await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4760
|
-
}
|
|
4761
|
-
}, { awaitRemoteJob: true } // Don't return until the job is done!
|
|
4762
|
-
);
|
|
4763
|
-
console.debug("Done initial sync");
|
|
4664
|
+
console.debug('Performing initial sync');
|
|
4665
|
+
await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4666
|
+
console.debug('Done initial sync');
|
|
4764
4667
|
}
|
|
4765
4668
|
|
|
4766
|
-
const USER_INACTIVITY_TIMEOUT =
|
|
4669
|
+
const USER_INACTIVITY_TIMEOUT = 180000; // 3 minutes
|
|
4767
4670
|
const INACTIVE_WAIT_TIME = 20000;
|
|
4768
4671
|
// This observable will be emitted to later down....
|
|
4769
4672
|
const userIsActive = new BehaviorSubject(true);
|
|
@@ -4777,9 +4680,13 @@ const userIsActive = new BehaviorSubject(true);
|
|
|
4777
4680
|
// for just a short time.
|
|
4778
4681
|
const userIsReallyActive = new BehaviorSubject(true);
|
|
4779
4682
|
userIsActive
|
|
4780
|
-
.pipe(switchMap((isActive) =>
|
|
4781
|
-
|
|
4782
|
-
|
|
4683
|
+
.pipe(switchMap((isActive) => {
|
|
4684
|
+
//console.debug('SyncStatus: DUBB: isActive changed to', isActive);
|
|
4685
|
+
return isActive
|
|
4686
|
+
? of(true)
|
|
4687
|
+
: of(false).pipe(delay(INACTIVE_WAIT_TIME))
|
|
4688
|
+
;
|
|
4689
|
+
}), distinctUntilChanged())
|
|
4783
4690
|
.subscribe(userIsReallyActive);
|
|
4784
4691
|
//
|
|
4785
4692
|
// First create some corner-stone observables to build the flow on
|
|
@@ -4794,7 +4701,7 @@ const documentBecomesHidden = visibilityStateIsChanged.pipe(filter(() => documen
|
|
|
4794
4701
|
const documentBecomesVisible = visibilityStateIsChanged.pipe(filter(() => document.visibilityState === 'visible'));
|
|
4795
4702
|
// Any of various user-activity-related events happen:
|
|
4796
4703
|
const userDoesSomething = typeof window !== 'undefined'
|
|
4797
|
-
? merge(documentBecomesVisible, fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
|
|
4704
|
+
? merge(documentBecomesVisible, fromEvent(window, 'mousedown'), fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
|
|
4798
4705
|
: of({});
|
|
4799
4706
|
if (typeof document !== 'undefined') {
|
|
4800
4707
|
//
|
|
@@ -4845,6 +4752,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4845
4752
|
constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
|
|
4846
4753
|
super(() => this.teardown());
|
|
4847
4754
|
this.id = ++counter;
|
|
4755
|
+
this.reconnecting = false;
|
|
4848
4756
|
console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
|
|
4849
4757
|
this.databaseUrl = databaseUrl;
|
|
4850
4758
|
this.rev = rev;
|
|
@@ -4864,7 +4772,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4864
4772
|
this.disconnect();
|
|
4865
4773
|
}
|
|
4866
4774
|
disconnect() {
|
|
4867
|
-
this.webSocketStatus.next(
|
|
4775
|
+
this.webSocketStatus.next('disconnected');
|
|
4868
4776
|
if (this.pinger) {
|
|
4869
4777
|
clearInterval(this.pinger);
|
|
4870
4778
|
this.pinger = null;
|
|
@@ -4882,11 +4790,18 @@ class WSConnection extends Subscription$1 {
|
|
|
4882
4790
|
}
|
|
4883
4791
|
}
|
|
4884
4792
|
reconnect() {
|
|
4885
|
-
this.
|
|
4886
|
-
|
|
4793
|
+
if (this.reconnecting)
|
|
4794
|
+
return;
|
|
4795
|
+
this.reconnecting = true;
|
|
4796
|
+
try {
|
|
4797
|
+
this.disconnect();
|
|
4798
|
+
}
|
|
4799
|
+
catch { }
|
|
4800
|
+
this.connect()
|
|
4801
|
+
.catch(() => { })
|
|
4802
|
+
.then(() => (this.reconnecting = false)); // finally()
|
|
4887
4803
|
}
|
|
4888
4804
|
async connect() {
|
|
4889
|
-
this.webSocketStatus.next("connecting");
|
|
4890
4805
|
this.lastServerActivity = new Date();
|
|
4891
4806
|
if (this.pauseUntil && this.pauseUntil > new Date()) {
|
|
4892
4807
|
console.debug('WS not reconnecting just yet', {
|
|
@@ -4901,12 +4816,14 @@ class WSConnection extends Subscription$1 {
|
|
|
4901
4816
|
if (!this.databaseUrl)
|
|
4902
4817
|
throw new Error(`Cannot connect without a database URL`);
|
|
4903
4818
|
if (this.closed) {
|
|
4819
|
+
//console.debug('SyncStatus: DUBB: Ooops it was closed!');
|
|
4904
4820
|
return;
|
|
4905
4821
|
}
|
|
4906
4822
|
if (this.tokenExpiration && this.tokenExpiration < new Date()) {
|
|
4907
4823
|
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
4908
4824
|
return;
|
|
4909
4825
|
}
|
|
4826
|
+
this.webSocketStatus.next('connecting');
|
|
4910
4827
|
this.pinger = setInterval(async () => {
|
|
4911
4828
|
if (this.closed) {
|
|
4912
4829
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
@@ -4953,7 +4870,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4953
4870
|
const searchParams = new URLSearchParams();
|
|
4954
4871
|
if (this.subscriber.closed)
|
|
4955
4872
|
return;
|
|
4956
|
-
searchParams.set('v',
|
|
4873
|
+
searchParams.set('v', '2');
|
|
4957
4874
|
searchParams.set('rev', this.rev);
|
|
4958
4875
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
4959
4876
|
searchParams.set('clientId', this.clientIdentity);
|
|
@@ -4992,23 +4909,30 @@ class WSConnection extends Subscription$1 {
|
|
|
4992
4909
|
}
|
|
4993
4910
|
};
|
|
4994
4911
|
try {
|
|
4912
|
+
let everConnected = false;
|
|
4995
4913
|
await new Promise((resolve, reject) => {
|
|
4996
4914
|
ws.onopen = (event) => {
|
|
4997
4915
|
console.debug('dexie-cloud WebSocket onopen');
|
|
4916
|
+
everConnected = true;
|
|
4998
4917
|
resolve(null);
|
|
4999
4918
|
};
|
|
5000
4919
|
ws.onerror = (event) => {
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
4920
|
+
if (!everConnected) {
|
|
4921
|
+
const error = event.error || new Error('WebSocket Error');
|
|
4922
|
+
this.subscriber.error(error);
|
|
4923
|
+
this.webSocketStatus.next('error');
|
|
4924
|
+
reject(error);
|
|
4925
|
+
}
|
|
4926
|
+
else {
|
|
4927
|
+
this.reconnect();
|
|
4928
|
+
}
|
|
5006
4929
|
};
|
|
5007
4930
|
});
|
|
5008
|
-
this.messageProducerSubscription = this.messageProducer.subscribe(msg => {
|
|
4931
|
+
this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
|
|
5009
4932
|
if (!this.closed) {
|
|
5010
|
-
if (msg.type === 'ready' &&
|
|
5011
|
-
this.webSocketStatus.
|
|
4933
|
+
if (msg.type === 'ready' &&
|
|
4934
|
+
this.webSocketStatus.value !== 'connected') {
|
|
4935
|
+
this.webSocketStatus.next('connected');
|
|
5012
4936
|
}
|
|
5013
4937
|
this.ws?.send(TSON.stringify(msg));
|
|
5014
4938
|
}
|
|
@@ -5045,9 +4969,9 @@ function connectWebSocket(db) {
|
|
|
5045
4969
|
rev: syncState.serverRevision,
|
|
5046
4970
|
})));
|
|
5047
4971
|
function createObservable() {
|
|
5048
|
-
return db.cloud.persistedSyncState.pipe(filter(syncState => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
|
|
4972
|
+
return db.cloud.persistedSyncState.pipe(filter((syncState) => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
|
|
5049
4973
|
take(1), // Don't continue waking up whenever syncState change
|
|
5050
|
-
switchMap((syncState) => db.cloud.currentUser.pipe(map(userLogin => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(async ([userLogin, syncState]) => [userLogin, await computeRealmSetHash(syncState)]), switchMap(([userLogin, realmSetHash]) =>
|
|
4974
|
+
switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(async ([userLogin, syncState]) => [userLogin, await computeRealmSetHash(syncState)]), switchMap(([userLogin, realmSetHash]) =>
|
|
5051
4975
|
// Let server end query changes from last entry of same client-ID and forward.
|
|
5052
4976
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
5053
4977
|
// and the baseRev of the last from same client-ID.
|
|
@@ -5070,7 +4994,10 @@ function connectWebSocket(db) {
|
|
|
5070
4994
|
else {
|
|
5071
4995
|
return throwError(error);
|
|
5072
4996
|
}
|
|
5073
|
-
}), catchError((error) =>
|
|
4997
|
+
}), catchError((error) => {
|
|
4998
|
+
db.cloud.webSocketStatus.next("error");
|
|
4999
|
+
return from$1(waitAndReconnectWhenUserDoesSomething(error)).pipe(switchMap(() => createObservable()));
|
|
5000
|
+
}));
|
|
5074
5001
|
}
|
|
5075
5002
|
return createObservable().subscribe((msg) => {
|
|
5076
5003
|
if (msg) {
|
|
@@ -5090,6 +5017,95 @@ async function isSyncNeeded(db) {
|
|
|
5090
5017
|
: false;
|
|
5091
5018
|
}
|
|
5092
5019
|
|
|
5020
|
+
const SECONDS = 1000;
|
|
5021
|
+
const MINUTES = 60 * SECONDS;
|
|
5022
|
+
|
|
5023
|
+
const myId = randomString(16);
|
|
5024
|
+
|
|
5025
|
+
const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
|
|
5026
|
+
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
5027
|
+
async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
5028
|
+
// Start working.
|
|
5029
|
+
//
|
|
5030
|
+
// Check if someone else is working on this already.
|
|
5031
|
+
//
|
|
5032
|
+
const jobsTable = db.table(jobsTableName);
|
|
5033
|
+
async function aquireLock() {
|
|
5034
|
+
const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
|
|
5035
|
+
const currentWork = await jobsTable.get(jobName);
|
|
5036
|
+
if (!currentWork) {
|
|
5037
|
+
// No one else is working. Let's record that we are.
|
|
5038
|
+
await jobsTable.add({
|
|
5039
|
+
nodeId: myId,
|
|
5040
|
+
started: new Date(),
|
|
5041
|
+
heartbeat: new Date()
|
|
5042
|
+
}, jobName);
|
|
5043
|
+
return true;
|
|
5044
|
+
}
|
|
5045
|
+
else if (currentWork.heartbeat.getTime() <
|
|
5046
|
+
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
5047
|
+
console.warn(`Latest ${jobName} worker seem to have died.\n`, `The dead job started:`, currentWork.started, `\n`, `Last heart beat was:`, currentWork.heartbeat, '\n', `We're now taking over!`);
|
|
5048
|
+
// Now, take over!
|
|
5049
|
+
await jobsTable.put({
|
|
5050
|
+
nodeId: myId,
|
|
5051
|
+
started: new Date(),
|
|
5052
|
+
heartbeat: new Date()
|
|
5053
|
+
}, jobName);
|
|
5054
|
+
return true;
|
|
5055
|
+
}
|
|
5056
|
+
return false;
|
|
5057
|
+
});
|
|
5058
|
+
if (gotTheLock)
|
|
5059
|
+
return true;
|
|
5060
|
+
// Someone else took the job.
|
|
5061
|
+
if (awaitRemoteJob) {
|
|
5062
|
+
try {
|
|
5063
|
+
const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
5064
|
+
await jobDoneObservable.toPromise();
|
|
5065
|
+
return false;
|
|
5066
|
+
}
|
|
5067
|
+
catch (err) {
|
|
5068
|
+
if (err.name !== 'TimeoutError') {
|
|
5069
|
+
throw err;
|
|
5070
|
+
}
|
|
5071
|
+
// Timeout stopped us! Try aquire the lock now.
|
|
5072
|
+
// It will likely succeed this time unless
|
|
5073
|
+
// another client took it.
|
|
5074
|
+
return await aquireLock();
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
return false;
|
|
5078
|
+
}
|
|
5079
|
+
if (await aquireLock()) {
|
|
5080
|
+
// We own the lock entry and can do our job undisturbed.
|
|
5081
|
+
// We're not within a transaction, but these type of locks
|
|
5082
|
+
// spans over transactions.
|
|
5083
|
+
// Start our heart beat during the job.
|
|
5084
|
+
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
5085
|
+
const heartbeat = setInterval(() => {
|
|
5086
|
+
jobsTable.update(jobName, (job) => {
|
|
5087
|
+
if (job.nodeId === myId) {
|
|
5088
|
+
job.heartbeat = new Date();
|
|
5089
|
+
}
|
|
5090
|
+
});
|
|
5091
|
+
}, GUARDED_JOB_HEARTBEAT);
|
|
5092
|
+
try {
|
|
5093
|
+
return await job();
|
|
5094
|
+
}
|
|
5095
|
+
finally {
|
|
5096
|
+
// Stop heartbeat
|
|
5097
|
+
clearInterval(heartbeat);
|
|
5098
|
+
// Remove the persisted job state:
|
|
5099
|
+
await db.transaction('rw!', jobsTableName, async () => {
|
|
5100
|
+
const currentWork = await jobsTable.get(jobName);
|
|
5101
|
+
if (currentWork && currentWork.nodeId === myId) {
|
|
5102
|
+
await jobsTable.delete(jobName);
|
|
5103
|
+
}
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
|
|
5093
5109
|
const ongoingSyncs = new WeakMap();
|
|
5094
5110
|
function syncIfPossible(db, cloudOptions, cloudSchema, options) {
|
|
5095
5111
|
const ongoing = ongoingSyncs.get(db);
|
|
@@ -5494,6 +5510,21 @@ function createSharedValueObservable(o, defaultValue) {
|
|
|
5494
5510
|
return rv;
|
|
5495
5511
|
}
|
|
5496
5512
|
|
|
5513
|
+
const getGlobalRolesObservable = associate((db) => {
|
|
5514
|
+
return createSharedValueObservable(liveQuery(() => db.roles
|
|
5515
|
+
.where({ realmId: 'rlm-public' })
|
|
5516
|
+
.toArray()
|
|
5517
|
+
.then((roles) => {
|
|
5518
|
+
const rv = {};
|
|
5519
|
+
for (const role of roles
|
|
5520
|
+
.slice()
|
|
5521
|
+
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))) {
|
|
5522
|
+
rv[role.name] = role;
|
|
5523
|
+
}
|
|
5524
|
+
return rv;
|
|
5525
|
+
})), {});
|
|
5526
|
+
});
|
|
5527
|
+
|
|
5497
5528
|
const getCurrentUserEmitter = associate((db) => new BehaviorSubject(UNAUTHORIZED_USER));
|
|
5498
5529
|
|
|
5499
5530
|
const getInternalAccessControlObservable = associate((db) => {
|
|
@@ -5595,18 +5626,38 @@ function mergePermissions(...permissions) {
|
|
|
5595
5626
|
}
|
|
5596
5627
|
|
|
5597
5628
|
const getPermissionsLookupObservable = associate((db) => {
|
|
5598
|
-
const o =
|
|
5599
|
-
|
|
5629
|
+
const o = createSharedValueObservable(combineLatest([
|
|
5630
|
+
getInternalAccessControlObservable(db._novip),
|
|
5631
|
+
getGlobalRolesObservable(db._novip),
|
|
5632
|
+
]).pipe(map(([{ selfMembers, realms, userId }, globalRoles]) => ({
|
|
5633
|
+
selfMembers,
|
|
5634
|
+
realms,
|
|
5635
|
+
userId,
|
|
5636
|
+
globalRoles,
|
|
5637
|
+
}))), {
|
|
5638
|
+
selfMembers: [],
|
|
5639
|
+
realms: [],
|
|
5640
|
+
userId: UNAUTHORIZED_USER.userId,
|
|
5641
|
+
globalRoles: {},
|
|
5642
|
+
});
|
|
5643
|
+
return mapValueObservable(o, ({ selfMembers, realms, userId, globalRoles }) => {
|
|
5600
5644
|
const rv = realms
|
|
5601
|
-
.map((realm) =>
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5645
|
+
.map((realm) => {
|
|
5646
|
+
const selfRealmMembers = selfMembers.filter((m) => m.realmId === realm.realmId);
|
|
5647
|
+
const directPermissionSets = selfRealmMembers
|
|
5648
|
+
.map((m) => m.permissions)
|
|
5649
|
+
.filter((p) => p);
|
|
5650
|
+
const rolePermissionSets = flatten(selfRealmMembers.map((m) => m.roles).filter((roleName) => roleName))
|
|
5651
|
+
.map((role) => globalRoles[role])
|
|
5652
|
+
.filter((role) => role)
|
|
5653
|
+
.map((role) => role.permissions);
|
|
5654
|
+
return {
|
|
5655
|
+
...realm,
|
|
5656
|
+
permissions: realm.owner === userId
|
|
5657
|
+
? { manage: '*' }
|
|
5658
|
+
: mergePermissions(...directPermissionSets, ...rolePermissionSets),
|
|
5659
|
+
};
|
|
5660
|
+
})
|
|
5610
5661
|
.reduce((p, c) => ({ ...p, [c.realmId]: c }), {
|
|
5611
5662
|
[userId]: {
|
|
5612
5663
|
realmId: userId,
|
|
@@ -5690,7 +5741,7 @@ function permissions(dexie, obj, tableName) {
|
|
|
5690
5741
|
const realm = permissionsLookup[realmId || dexie.cloud.currentUserId];
|
|
5691
5742
|
if (!realm)
|
|
5692
5743
|
return new PermissionChecker({}, tableName, !owner || owner === dexie.cloud.currentUserId);
|
|
5693
|
-
return new PermissionChecker(realm.permissions, tableName,
|
|
5744
|
+
return new PermissionChecker(realm.permissions, tableName, realmId === dexie.cloud.currentUserId || owner === dexie.cloud.currentUserId);
|
|
5694
5745
|
};
|
|
5695
5746
|
const o = source.pipe(map(mapper));
|
|
5696
5747
|
o.getValue = () => mapper(source.getValue());
|
|
@@ -5726,6 +5777,7 @@ function dexieCloud(dexie) {
|
|
|
5726
5777
|
//
|
|
5727
5778
|
const currentUserEmitter = getCurrentUserEmitter(dexie);
|
|
5728
5779
|
const subscriptions = [];
|
|
5780
|
+
let configuredProgramatically = false;
|
|
5729
5781
|
// local sync worker - used when there's no service worker.
|
|
5730
5782
|
let localSyncWorker = null;
|
|
5731
5783
|
dexie.on('ready', async (dexie) => {
|
|
@@ -5752,7 +5804,7 @@ function dexieCloud(dexie) {
|
|
|
5752
5804
|
currentUserEmitter.next(UNAUTHORIZED_USER);
|
|
5753
5805
|
});
|
|
5754
5806
|
dexie.cloud = {
|
|
5755
|
-
version: '4.0.0-beta.
|
|
5807
|
+
version: '4.0.0-beta.17',
|
|
5756
5808
|
options: { ...DEFAULT_OPTIONS },
|
|
5757
5809
|
schema: null,
|
|
5758
5810
|
serverState: null,
|
|
@@ -5773,8 +5825,10 @@ function dexieCloud(dexie) {
|
|
|
5773
5825
|
await login(db, hint);
|
|
5774
5826
|
},
|
|
5775
5827
|
invites: getInvitesObservable(dexie),
|
|
5828
|
+
roles: getGlobalRolesObservable(dexie),
|
|
5776
5829
|
configure(options) {
|
|
5777
5830
|
options = dexie.cloud.options = { ...dexie.cloud.options, ...options };
|
|
5831
|
+
configuredProgramatically = true;
|
|
5778
5832
|
if (options.databaseUrl && options.nameSuffix) {
|
|
5779
5833
|
// @ts-ignore
|
|
5780
5834
|
dexie.name = `${origIdbName}-${getDbNameFromDbUrl(options.databaseUrl)}`;
|
|
@@ -5861,7 +5915,7 @@ function dexieCloud(dexie) {
|
|
|
5861
5915
|
db.getSchema(),
|
|
5862
5916
|
db.getPersistedSyncState(),
|
|
5863
5917
|
]);
|
|
5864
|
-
if (!
|
|
5918
|
+
if (!configuredProgramatically) {
|
|
5865
5919
|
// Options not specified programatically (use case for SW!)
|
|
5866
5920
|
// Take persisted options:
|
|
5867
5921
|
db.cloud.options = persistedOptions || null;
|
|
@@ -5869,6 +5923,8 @@ function dexieCloud(dexie) {
|
|
|
5869
5923
|
else if (!persistedOptions ||
|
|
5870
5924
|
JSON.stringify(persistedOptions) !== JSON.stringify(options)) {
|
|
5871
5925
|
// Update persisted options:
|
|
5926
|
+
if (!options)
|
|
5927
|
+
throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
|
|
5872
5928
|
await db.$syncState.put(options, 'options');
|
|
5873
5929
|
}
|
|
5874
5930
|
if (db.cloud.options?.tryUseServiceWorker &&
|
|
@@ -5990,7 +6046,7 @@ function dexieCloud(dexie) {
|
|
|
5990
6046
|
}
|
|
5991
6047
|
}
|
|
5992
6048
|
}
|
|
5993
|
-
dexieCloud.version = '4.0.0-beta.
|
|
6049
|
+
dexieCloud.version = '4.0.0-beta.17';
|
|
5994
6050
|
Dexie.Cloud = dexieCloud;
|
|
5995
6051
|
|
|
5996
6052
|
export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId };
|