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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Dexie, { cmp, liveQuery } from 'dexie';
|
|
2
|
-
import { Observable as Observable$1, BehaviorSubject, Subject,
|
|
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, switchMap as switchMap$1 } from 'rxjs';
|
|
3
3
|
|
|
4
4
|
const UNAUTHORIZED_USER = {
|
|
5
5
|
userId: "unauthorized",
|
|
@@ -4642,110 +4642,13 @@ function overrideParseStoresSpec(origFunc, dexie) {
|
|
|
4642
4642
|
};
|
|
4643
4643
|
}
|
|
4644
4644
|
|
|
4645
|
-
const SECONDS = 1000;
|
|
4646
|
-
const MINUTES = 60 * SECONDS;
|
|
4647
|
-
|
|
4648
|
-
const myId = randomString$1(16);
|
|
4649
|
-
|
|
4650
|
-
const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
|
|
4651
|
-
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
4652
|
-
async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
4653
|
-
// Start working.
|
|
4654
|
-
//
|
|
4655
|
-
// Check if someone else is working on this already.
|
|
4656
|
-
//
|
|
4657
|
-
const jobsTable = db.table(jobsTableName);
|
|
4658
|
-
async function aquireLock() {
|
|
4659
|
-
const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
|
|
4660
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4661
|
-
if (!currentWork) {
|
|
4662
|
-
// No one else is working. Let's record that we are.
|
|
4663
|
-
await jobsTable.add({
|
|
4664
|
-
nodeId: myId,
|
|
4665
|
-
started: new Date(),
|
|
4666
|
-
heartbeat: new Date()
|
|
4667
|
-
}, jobName);
|
|
4668
|
-
return true;
|
|
4669
|
-
}
|
|
4670
|
-
else if (currentWork.heartbeat.getTime() <
|
|
4671
|
-
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
4672
|
-
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!`);
|
|
4673
|
-
// Now, take over!
|
|
4674
|
-
await jobsTable.put({
|
|
4675
|
-
nodeId: myId,
|
|
4676
|
-
started: new Date(),
|
|
4677
|
-
heartbeat: new Date()
|
|
4678
|
-
}, jobName);
|
|
4679
|
-
return true;
|
|
4680
|
-
}
|
|
4681
|
-
return false;
|
|
4682
|
-
});
|
|
4683
|
-
if (gotTheLock)
|
|
4684
|
-
return true;
|
|
4685
|
-
// Someone else took the job.
|
|
4686
|
-
if (awaitRemoteJob) {
|
|
4687
|
-
try {
|
|
4688
|
-
const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
4689
|
-
await jobDoneObservable.toPromise();
|
|
4690
|
-
return false;
|
|
4691
|
-
}
|
|
4692
|
-
catch (err) {
|
|
4693
|
-
if (err.name !== 'TimeoutError') {
|
|
4694
|
-
throw err;
|
|
4695
|
-
}
|
|
4696
|
-
// Timeout stopped us! Try aquire the lock now.
|
|
4697
|
-
// It will likely succeed this time unless
|
|
4698
|
-
// another client took it.
|
|
4699
|
-
return await aquireLock();
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
return false;
|
|
4703
|
-
}
|
|
4704
|
-
if (await aquireLock()) {
|
|
4705
|
-
// We own the lock entry and can do our job undisturbed.
|
|
4706
|
-
// We're not within a transaction, but these type of locks
|
|
4707
|
-
// spans over transactions.
|
|
4708
|
-
// Start our heart beat during the job.
|
|
4709
|
-
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
4710
|
-
const heartbeat = setInterval(() => {
|
|
4711
|
-
jobsTable.update(jobName, (job) => {
|
|
4712
|
-
if (job.nodeId === myId) {
|
|
4713
|
-
job.heartbeat = new Date();
|
|
4714
|
-
}
|
|
4715
|
-
});
|
|
4716
|
-
}, GUARDED_JOB_HEARTBEAT);
|
|
4717
|
-
try {
|
|
4718
|
-
return await job();
|
|
4719
|
-
}
|
|
4720
|
-
finally {
|
|
4721
|
-
// Stop heartbeat
|
|
4722
|
-
clearInterval(heartbeat);
|
|
4723
|
-
// Remove the persisted job state:
|
|
4724
|
-
await db.transaction('rw!', jobsTableName, async () => {
|
|
4725
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4726
|
-
if (currentWork && currentWork.nodeId === myId) {
|
|
4727
|
-
jobsTable.delete(jobName);
|
|
4728
|
-
}
|
|
4729
|
-
});
|
|
4730
|
-
}
|
|
4731
|
-
}
|
|
4732
|
-
}
|
|
4733
|
-
|
|
4734
4645
|
async function performInitialSync(db, cloudOptions, cloudSchema) {
|
|
4735
|
-
console.debug(
|
|
4736
|
-
await
|
|
4737
|
-
|
|
4738
|
-
// Do check again (now within a transaction) that we really do not have a sync state:
|
|
4739
|
-
const syncState = await db.getPersistedSyncState();
|
|
4740
|
-
if (!syncState?.initiallySynced) {
|
|
4741
|
-
await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4742
|
-
}
|
|
4743
|
-
}, { awaitRemoteJob: true } // Don't return until the job is done!
|
|
4744
|
-
);
|
|
4745
|
-
console.debug("Done initial sync");
|
|
4646
|
+
console.debug('Performing initial sync');
|
|
4647
|
+
await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4648
|
+
console.debug('Done initial sync');
|
|
4746
4649
|
}
|
|
4747
4650
|
|
|
4748
|
-
const USER_INACTIVITY_TIMEOUT =
|
|
4651
|
+
const USER_INACTIVITY_TIMEOUT = 180000; // 3 minutes
|
|
4749
4652
|
const INACTIVE_WAIT_TIME = 20000;
|
|
4750
4653
|
// This observable will be emitted to later down....
|
|
4751
4654
|
const userIsActive = new BehaviorSubject(true);
|
|
@@ -4759,9 +4662,13 @@ const userIsActive = new BehaviorSubject(true);
|
|
|
4759
4662
|
// for just a short time.
|
|
4760
4663
|
const userIsReallyActive = new BehaviorSubject(true);
|
|
4761
4664
|
userIsActive
|
|
4762
|
-
.pipe(switchMap((isActive) =>
|
|
4763
|
-
|
|
4764
|
-
|
|
4665
|
+
.pipe(switchMap((isActive) => {
|
|
4666
|
+
//console.debug('SyncStatus: DUBB: isActive changed to', isActive);
|
|
4667
|
+
return isActive
|
|
4668
|
+
? of(true)
|
|
4669
|
+
: of(false).pipe(delay(INACTIVE_WAIT_TIME))
|
|
4670
|
+
;
|
|
4671
|
+
}), distinctUntilChanged())
|
|
4765
4672
|
.subscribe(userIsReallyActive);
|
|
4766
4673
|
//
|
|
4767
4674
|
// First create some corner-stone observables to build the flow on
|
|
@@ -4776,7 +4683,7 @@ const documentBecomesHidden = visibilityStateIsChanged.pipe(filter(() => documen
|
|
|
4776
4683
|
const documentBecomesVisible = visibilityStateIsChanged.pipe(filter(() => document.visibilityState === 'visible'));
|
|
4777
4684
|
// Any of various user-activity-related events happen:
|
|
4778
4685
|
const userDoesSomething = typeof window !== 'undefined'
|
|
4779
|
-
? merge(documentBecomesVisible, fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
|
|
4686
|
+
? merge(documentBecomesVisible, fromEvent(window, 'mousedown'), fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
|
|
4780
4687
|
: of({});
|
|
4781
4688
|
if (typeof document !== 'undefined') {
|
|
4782
4689
|
//
|
|
@@ -4827,6 +4734,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4827
4734
|
constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
|
|
4828
4735
|
super(() => this.teardown());
|
|
4829
4736
|
this.id = ++counter;
|
|
4737
|
+
this.reconnecting = false;
|
|
4830
4738
|
console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
|
|
4831
4739
|
this.databaseUrl = databaseUrl;
|
|
4832
4740
|
this.rev = rev;
|
|
@@ -4846,7 +4754,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4846
4754
|
this.disconnect();
|
|
4847
4755
|
}
|
|
4848
4756
|
disconnect() {
|
|
4849
|
-
this.webSocketStatus.next(
|
|
4757
|
+
this.webSocketStatus.next('disconnected');
|
|
4850
4758
|
if (this.pinger) {
|
|
4851
4759
|
clearInterval(this.pinger);
|
|
4852
4760
|
this.pinger = null;
|
|
@@ -4864,11 +4772,18 @@ class WSConnection extends Subscription$1 {
|
|
|
4864
4772
|
}
|
|
4865
4773
|
}
|
|
4866
4774
|
reconnect() {
|
|
4867
|
-
this.
|
|
4868
|
-
|
|
4775
|
+
if (this.reconnecting)
|
|
4776
|
+
return;
|
|
4777
|
+
this.reconnecting = true;
|
|
4778
|
+
try {
|
|
4779
|
+
this.disconnect();
|
|
4780
|
+
}
|
|
4781
|
+
catch { }
|
|
4782
|
+
this.connect()
|
|
4783
|
+
.catch(() => { })
|
|
4784
|
+
.then(() => (this.reconnecting = false)); // finally()
|
|
4869
4785
|
}
|
|
4870
4786
|
async connect() {
|
|
4871
|
-
this.webSocketStatus.next("connecting");
|
|
4872
4787
|
this.lastServerActivity = new Date();
|
|
4873
4788
|
if (this.pauseUntil && this.pauseUntil > new Date()) {
|
|
4874
4789
|
console.debug('WS not reconnecting just yet', {
|
|
@@ -4883,12 +4798,14 @@ class WSConnection extends Subscription$1 {
|
|
|
4883
4798
|
if (!this.databaseUrl)
|
|
4884
4799
|
throw new Error(`Cannot connect without a database URL`);
|
|
4885
4800
|
if (this.closed) {
|
|
4801
|
+
//console.debug('SyncStatus: DUBB: Ooops it was closed!');
|
|
4886
4802
|
return;
|
|
4887
4803
|
}
|
|
4888
4804
|
if (this.tokenExpiration && this.tokenExpiration < new Date()) {
|
|
4889
4805
|
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
4890
4806
|
return;
|
|
4891
4807
|
}
|
|
4808
|
+
this.webSocketStatus.next('connecting');
|
|
4892
4809
|
this.pinger = setInterval(async () => {
|
|
4893
4810
|
if (this.closed) {
|
|
4894
4811
|
console.debug('pinger check', this.id, 'CLOSED.');
|
|
@@ -4935,7 +4852,7 @@ class WSConnection extends Subscription$1 {
|
|
|
4935
4852
|
const searchParams = new URLSearchParams();
|
|
4936
4853
|
if (this.subscriber.closed)
|
|
4937
4854
|
return;
|
|
4938
|
-
searchParams.set('v',
|
|
4855
|
+
searchParams.set('v', '2');
|
|
4939
4856
|
searchParams.set('rev', this.rev);
|
|
4940
4857
|
searchParams.set('realmsHash', this.realmSetHash);
|
|
4941
4858
|
searchParams.set('clientId', this.clientIdentity);
|
|
@@ -4974,23 +4891,30 @@ class WSConnection extends Subscription$1 {
|
|
|
4974
4891
|
}
|
|
4975
4892
|
};
|
|
4976
4893
|
try {
|
|
4894
|
+
let everConnected = false;
|
|
4977
4895
|
await new Promise((resolve, reject) => {
|
|
4978
4896
|
ws.onopen = (event) => {
|
|
4979
4897
|
console.debug('dexie-cloud WebSocket onopen');
|
|
4898
|
+
everConnected = true;
|
|
4980
4899
|
resolve(null);
|
|
4981
4900
|
};
|
|
4982
4901
|
ws.onerror = (event) => {
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4902
|
+
if (!everConnected) {
|
|
4903
|
+
const error = event.error || new Error('WebSocket Error');
|
|
4904
|
+
this.subscriber.error(error);
|
|
4905
|
+
this.webSocketStatus.next('error');
|
|
4906
|
+
reject(error);
|
|
4907
|
+
}
|
|
4908
|
+
else {
|
|
4909
|
+
this.reconnect();
|
|
4910
|
+
}
|
|
4988
4911
|
};
|
|
4989
4912
|
});
|
|
4990
|
-
this.messageProducerSubscription = this.messageProducer.subscribe(msg => {
|
|
4913
|
+
this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
|
|
4991
4914
|
if (!this.closed) {
|
|
4992
|
-
if (msg.type === 'ready' &&
|
|
4993
|
-
this.webSocketStatus.
|
|
4915
|
+
if (msg.type === 'ready' &&
|
|
4916
|
+
this.webSocketStatus.value !== 'connected') {
|
|
4917
|
+
this.webSocketStatus.next('connected');
|
|
4994
4918
|
}
|
|
4995
4919
|
this.ws?.send(TSON.stringify(msg));
|
|
4996
4920
|
}
|
|
@@ -5027,9 +4951,9 @@ function connectWebSocket(db) {
|
|
|
5027
4951
|
rev: syncState.serverRevision,
|
|
5028
4952
|
})));
|
|
5029
4953
|
function createObservable() {
|
|
5030
|
-
return db.cloud.persistedSyncState.pipe(filter(syncState => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
|
|
4954
|
+
return db.cloud.persistedSyncState.pipe(filter((syncState) => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
|
|
5031
4955
|
take(1), // Don't continue waking up whenever syncState change
|
|
5032
|
-
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]) =>
|
|
4956
|
+
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]) =>
|
|
5033
4957
|
// Let server end query changes from last entry of same client-ID and forward.
|
|
5034
4958
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
5035
4959
|
// and the baseRev of the last from same client-ID.
|
|
@@ -5052,7 +4976,10 @@ function connectWebSocket(db) {
|
|
|
5052
4976
|
else {
|
|
5053
4977
|
return throwError(error);
|
|
5054
4978
|
}
|
|
5055
|
-
}), catchError((error) =>
|
|
4979
|
+
}), catchError((error) => {
|
|
4980
|
+
db.cloud.webSocketStatus.next("error");
|
|
4981
|
+
return from$1(waitAndReconnectWhenUserDoesSomething(error)).pipe(switchMap(() => createObservable()));
|
|
4982
|
+
}));
|
|
5056
4983
|
}
|
|
5057
4984
|
return createObservable().subscribe((msg) => {
|
|
5058
4985
|
if (msg) {
|
|
@@ -5072,6 +4999,95 @@ async function isSyncNeeded(db) {
|
|
|
5072
4999
|
: false;
|
|
5073
5000
|
}
|
|
5074
5001
|
|
|
5002
|
+
const SECONDS = 1000;
|
|
5003
|
+
const MINUTES = 60 * SECONDS;
|
|
5004
|
+
|
|
5005
|
+
const myId = randomString$1(16);
|
|
5006
|
+
|
|
5007
|
+
const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
|
|
5008
|
+
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
5009
|
+
async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
5010
|
+
// Start working.
|
|
5011
|
+
//
|
|
5012
|
+
// Check if someone else is working on this already.
|
|
5013
|
+
//
|
|
5014
|
+
const jobsTable = db.table(jobsTableName);
|
|
5015
|
+
async function aquireLock() {
|
|
5016
|
+
const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
|
|
5017
|
+
const currentWork = await jobsTable.get(jobName);
|
|
5018
|
+
if (!currentWork) {
|
|
5019
|
+
// No one else is working. Let's record that we are.
|
|
5020
|
+
await jobsTable.add({
|
|
5021
|
+
nodeId: myId,
|
|
5022
|
+
started: new Date(),
|
|
5023
|
+
heartbeat: new Date()
|
|
5024
|
+
}, jobName);
|
|
5025
|
+
return true;
|
|
5026
|
+
}
|
|
5027
|
+
else if (currentWork.heartbeat.getTime() <
|
|
5028
|
+
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
5029
|
+
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!`);
|
|
5030
|
+
// Now, take over!
|
|
5031
|
+
await jobsTable.put({
|
|
5032
|
+
nodeId: myId,
|
|
5033
|
+
started: new Date(),
|
|
5034
|
+
heartbeat: new Date()
|
|
5035
|
+
}, jobName);
|
|
5036
|
+
return true;
|
|
5037
|
+
}
|
|
5038
|
+
return false;
|
|
5039
|
+
});
|
|
5040
|
+
if (gotTheLock)
|
|
5041
|
+
return true;
|
|
5042
|
+
// Someone else took the job.
|
|
5043
|
+
if (awaitRemoteJob) {
|
|
5044
|
+
try {
|
|
5045
|
+
const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
5046
|
+
await jobDoneObservable.toPromise();
|
|
5047
|
+
return false;
|
|
5048
|
+
}
|
|
5049
|
+
catch (err) {
|
|
5050
|
+
if (err.name !== 'TimeoutError') {
|
|
5051
|
+
throw err;
|
|
5052
|
+
}
|
|
5053
|
+
// Timeout stopped us! Try aquire the lock now.
|
|
5054
|
+
// It will likely succeed this time unless
|
|
5055
|
+
// another client took it.
|
|
5056
|
+
return await aquireLock();
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
return false;
|
|
5060
|
+
}
|
|
5061
|
+
if (await aquireLock()) {
|
|
5062
|
+
// We own the lock entry and can do our job undisturbed.
|
|
5063
|
+
// We're not within a transaction, but these type of locks
|
|
5064
|
+
// spans over transactions.
|
|
5065
|
+
// Start our heart beat during the job.
|
|
5066
|
+
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
5067
|
+
const heartbeat = setInterval(() => {
|
|
5068
|
+
jobsTable.update(jobName, (job) => {
|
|
5069
|
+
if (job.nodeId === myId) {
|
|
5070
|
+
job.heartbeat = new Date();
|
|
5071
|
+
}
|
|
5072
|
+
});
|
|
5073
|
+
}, GUARDED_JOB_HEARTBEAT);
|
|
5074
|
+
try {
|
|
5075
|
+
return await job();
|
|
5076
|
+
}
|
|
5077
|
+
finally {
|
|
5078
|
+
// Stop heartbeat
|
|
5079
|
+
clearInterval(heartbeat);
|
|
5080
|
+
// Remove the persisted job state:
|
|
5081
|
+
await db.transaction('rw!', jobsTableName, async () => {
|
|
5082
|
+
const currentWork = await jobsTable.get(jobName);
|
|
5083
|
+
if (currentWork && currentWork.nodeId === myId) {
|
|
5084
|
+
await jobsTable.delete(jobName);
|
|
5085
|
+
}
|
|
5086
|
+
});
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5075
5091
|
const ongoingSyncs = new WeakMap();
|
|
5076
5092
|
function syncIfPossible(db, cloudOptions, cloudSchema, options) {
|
|
5077
5093
|
const ongoing = ongoingSyncs.get(db);
|
|
@@ -5476,6 +5492,21 @@ function createSharedValueObservable(o, defaultValue) {
|
|
|
5476
5492
|
return rv;
|
|
5477
5493
|
}
|
|
5478
5494
|
|
|
5495
|
+
const getGlobalRolesObservable = associate((db) => {
|
|
5496
|
+
return createSharedValueObservable(liveQuery(() => db.roles
|
|
5497
|
+
.where({ realmId: 'rlm-public' })
|
|
5498
|
+
.toArray()
|
|
5499
|
+
.then((roles) => {
|
|
5500
|
+
const rv = {};
|
|
5501
|
+
for (const role of roles
|
|
5502
|
+
.slice()
|
|
5503
|
+
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))) {
|
|
5504
|
+
rv[role.name] = role;
|
|
5505
|
+
}
|
|
5506
|
+
return rv;
|
|
5507
|
+
})), {});
|
|
5508
|
+
});
|
|
5509
|
+
|
|
5479
5510
|
const getCurrentUserEmitter = associate((db) => new BehaviorSubject(UNAUTHORIZED_USER));
|
|
5480
5511
|
|
|
5481
5512
|
const getInternalAccessControlObservable = associate((db) => {
|
|
@@ -5577,18 +5608,38 @@ function mergePermissions(...permissions) {
|
|
|
5577
5608
|
}
|
|
5578
5609
|
|
|
5579
5610
|
const getPermissionsLookupObservable = associate((db) => {
|
|
5580
|
-
const o =
|
|
5581
|
-
|
|
5611
|
+
const o = createSharedValueObservable(combineLatest([
|
|
5612
|
+
getInternalAccessControlObservable(db._novip),
|
|
5613
|
+
getGlobalRolesObservable(db._novip),
|
|
5614
|
+
]).pipe(map(([{ selfMembers, realms, userId }, globalRoles]) => ({
|
|
5615
|
+
selfMembers,
|
|
5616
|
+
realms,
|
|
5617
|
+
userId,
|
|
5618
|
+
globalRoles,
|
|
5619
|
+
}))), {
|
|
5620
|
+
selfMembers: [],
|
|
5621
|
+
realms: [],
|
|
5622
|
+
userId: UNAUTHORIZED_USER.userId,
|
|
5623
|
+
globalRoles: {},
|
|
5624
|
+
});
|
|
5625
|
+
return mapValueObservable(o, ({ selfMembers, realms, userId, globalRoles }) => {
|
|
5582
5626
|
const rv = realms
|
|
5583
|
-
.map((realm) =>
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5627
|
+
.map((realm) => {
|
|
5628
|
+
const selfRealmMembers = selfMembers.filter((m) => m.realmId === realm.realmId);
|
|
5629
|
+
const directPermissionSets = selfRealmMembers
|
|
5630
|
+
.map((m) => m.permissions)
|
|
5631
|
+
.filter((p) => p);
|
|
5632
|
+
const rolePermissionSets = flatten(selfRealmMembers.map((m) => m.roles).filter((roleName) => roleName))
|
|
5633
|
+
.map((role) => globalRoles[role])
|
|
5634
|
+
.filter((role) => role)
|
|
5635
|
+
.map((role) => role.permissions);
|
|
5636
|
+
return {
|
|
5637
|
+
...realm,
|
|
5638
|
+
permissions: realm.owner === userId
|
|
5639
|
+
? { manage: '*' }
|
|
5640
|
+
: mergePermissions(...directPermissionSets, ...rolePermissionSets),
|
|
5641
|
+
};
|
|
5642
|
+
})
|
|
5592
5643
|
.reduce((p, c) => ({ ...p, [c.realmId]: c }), {
|
|
5593
5644
|
[userId]: {
|
|
5594
5645
|
realmId: userId,
|
|
@@ -5672,7 +5723,7 @@ function permissions(dexie, obj, tableName) {
|
|
|
5672
5723
|
const realm = permissionsLookup[realmId || dexie.cloud.currentUserId];
|
|
5673
5724
|
if (!realm)
|
|
5674
5725
|
return new PermissionChecker({}, tableName, !owner || owner === dexie.cloud.currentUserId);
|
|
5675
|
-
return new PermissionChecker(realm.permissions, tableName,
|
|
5726
|
+
return new PermissionChecker(realm.permissions, tableName, realmId === dexie.cloud.currentUserId || owner === dexie.cloud.currentUserId);
|
|
5676
5727
|
};
|
|
5677
5728
|
const o = source.pipe(map(mapper));
|
|
5678
5729
|
o.getValue = () => mapper(source.getValue());
|
|
@@ -5701,6 +5752,7 @@ function dexieCloud(dexie) {
|
|
|
5701
5752
|
//
|
|
5702
5753
|
const currentUserEmitter = getCurrentUserEmitter(dexie);
|
|
5703
5754
|
const subscriptions = [];
|
|
5755
|
+
let configuredProgramatically = false;
|
|
5704
5756
|
// local sync worker - used when there's no service worker.
|
|
5705
5757
|
let localSyncWorker = null;
|
|
5706
5758
|
dexie.on('ready', async (dexie) => {
|
|
@@ -5727,7 +5779,7 @@ function dexieCloud(dexie) {
|
|
|
5727
5779
|
currentUserEmitter.next(UNAUTHORIZED_USER);
|
|
5728
5780
|
});
|
|
5729
5781
|
dexie.cloud = {
|
|
5730
|
-
version: '4.0.0-beta.
|
|
5782
|
+
version: '4.0.0-beta.17',
|
|
5731
5783
|
options: { ...DEFAULT_OPTIONS },
|
|
5732
5784
|
schema: null,
|
|
5733
5785
|
serverState: null,
|
|
@@ -5748,8 +5800,10 @@ function dexieCloud(dexie) {
|
|
|
5748
5800
|
await login(db, hint);
|
|
5749
5801
|
},
|
|
5750
5802
|
invites: getInvitesObservable(dexie),
|
|
5803
|
+
roles: getGlobalRolesObservable(dexie),
|
|
5751
5804
|
configure(options) {
|
|
5752
5805
|
options = dexie.cloud.options = { ...dexie.cloud.options, ...options };
|
|
5806
|
+
configuredProgramatically = true;
|
|
5753
5807
|
if (options.databaseUrl && options.nameSuffix) {
|
|
5754
5808
|
// @ts-ignore
|
|
5755
5809
|
dexie.name = `${origIdbName}-${getDbNameFromDbUrl(options.databaseUrl)}`;
|
|
@@ -5836,7 +5890,7 @@ function dexieCloud(dexie) {
|
|
|
5836
5890
|
db.getSchema(),
|
|
5837
5891
|
db.getPersistedSyncState(),
|
|
5838
5892
|
]);
|
|
5839
|
-
if (!
|
|
5893
|
+
if (!configuredProgramatically) {
|
|
5840
5894
|
// Options not specified programatically (use case for SW!)
|
|
5841
5895
|
// Take persisted options:
|
|
5842
5896
|
db.cloud.options = persistedOptions || null;
|
|
@@ -5844,6 +5898,8 @@ function dexieCloud(dexie) {
|
|
|
5844
5898
|
else if (!persistedOptions ||
|
|
5845
5899
|
JSON.stringify(persistedOptions) !== JSON.stringify(options)) {
|
|
5846
5900
|
// Update persisted options:
|
|
5901
|
+
if (!options)
|
|
5902
|
+
throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
|
|
5847
5903
|
await db.$syncState.put(options, 'options');
|
|
5848
5904
|
}
|
|
5849
5905
|
if (db.cloud.options?.tryUseServiceWorker &&
|
|
@@ -5965,7 +6021,7 @@ function dexieCloud(dexie) {
|
|
|
5965
6021
|
}
|
|
5966
6022
|
}
|
|
5967
6023
|
}
|
|
5968
|
-
dexieCloud.version = '4.0.0-beta.
|
|
6024
|
+
dexieCloud.version = '4.0.0-beta.17';
|
|
5969
6025
|
Dexie.Cloud = dexieCloud;
|
|
5970
6026
|
|
|
5971
6027
|
// In case the SW lives for a while, let it reuse already opened connections:
|