dexie-cloud-addon 4.0.0-beta.15 → 4.0.0-beta.18
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 +1704 -1600
- 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 +1767 -1660
- 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 +857 -777
- 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 +857 -777
- 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 +1766 -1659
- 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 +1700 -1596
- package/dist/umd-modern/dexie-cloud-addon.js.map +1 -1
- package/package.json +3 -3
|
@@ -26,6 +26,31 @@
|
|
|
26
26
|
|
|
27
27
|
var Dexie__default = /*#__PURE__*/_interopDefaultLegacy(Dexie);
|
|
28
28
|
|
|
29
|
+
/*! *****************************************************************************
|
|
30
|
+
Copyright (c) Microsoft Corporation.
|
|
31
|
+
|
|
32
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
33
|
+
purpose with or without fee is hereby granted.
|
|
34
|
+
|
|
35
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
36
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
37
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
38
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
39
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
40
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
41
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
42
|
+
***************************************************************************** */
|
|
43
|
+
|
|
44
|
+
function __awaiter$1(thisArg, _arguments, P, generator) {
|
|
45
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
46
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
47
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
48
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
49
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
50
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
//@ts-check
|
|
30
55
|
const randomFillSync = crypto.getRandomValues;
|
|
31
56
|
|
|
@@ -1909,55 +1934,60 @@
|
|
|
1909
1934
|
|
|
1910
1935
|
//const hasSW = 'serviceWorker' in navigator;
|
|
1911
1936
|
let hasComplainedAboutSyncEvent = false;
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1937
|
+
function registerSyncEvent(db, purpose) {
|
|
1938
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
1939
|
+
try {
|
|
1940
|
+
// Send sync event to SW:
|
|
1941
|
+
const sw = yield navigator.serviceWorker.ready;
|
|
1942
|
+
if (purpose === "push" && sw.sync) {
|
|
1943
|
+
yield sw.sync.register(`dexie-cloud:${db.name}`);
|
|
1944
|
+
}
|
|
1945
|
+
if (sw.active) {
|
|
1946
|
+
// Use postMessage for pull syncs and for browsers not supporting sync event (Firefox, Safari).
|
|
1947
|
+
// Also chromium based browsers with sw.sync as a fallback for sleepy sync events not taking action for a while.
|
|
1948
|
+
sw.active.postMessage({
|
|
1949
|
+
type: 'dexie-cloud-sync',
|
|
1950
|
+
dbName: db.name,
|
|
1951
|
+
purpose
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
throw new Error(`Failed to trigger sync - there's no active service worker`);
|
|
1956
|
+
}
|
|
1957
|
+
return;
|
|
1930
1958
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
hasComplainedAboutSyncEvent = true;
|
|
1959
|
+
catch (e) {
|
|
1960
|
+
if (!hasComplainedAboutSyncEvent) {
|
|
1961
|
+
console.debug(`Dexie Cloud: Could not register sync event`, e);
|
|
1962
|
+
hasComplainedAboutSyncEvent = true;
|
|
1963
|
+
}
|
|
1937
1964
|
}
|
|
1938
|
-
}
|
|
1965
|
+
});
|
|
1939
1966
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1967
|
+
function registerPeriodicSyncEvent(db) {
|
|
1968
|
+
var _a;
|
|
1969
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
1970
|
+
try {
|
|
1971
|
+
// Register periodicSync event to SW:
|
|
1972
|
+
// @ts-ignore
|
|
1973
|
+
const { periodicSync } = yield navigator.serviceWorker.ready;
|
|
1974
|
+
if (periodicSync) {
|
|
1975
|
+
try {
|
|
1976
|
+
yield periodicSync.register(`dexie-cloud:${db.name}`, (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.periodicSync);
|
|
1977
|
+
console.debug(`Dexie Cloud: Successfully registered periodicsync event for ${db.name}`);
|
|
1978
|
+
}
|
|
1979
|
+
catch (e) {
|
|
1980
|
+
console.debug(`Dexie Cloud: Failed to register periodic sync. Your PWA must be installed to allow background sync.`, e);
|
|
1981
|
+
}
|
|
1949
1982
|
}
|
|
1950
|
-
|
|
1951
|
-
console.debug(`Dexie Cloud:
|
|
1983
|
+
else {
|
|
1984
|
+
console.debug(`Dexie Cloud: periodicSync not supported.`);
|
|
1952
1985
|
}
|
|
1953
1986
|
}
|
|
1954
|
-
|
|
1955
|
-
console.debug(`Dexie Cloud:
|
|
1987
|
+
catch (e) {
|
|
1988
|
+
console.debug(`Dexie Cloud: Could not register periodicSync for ${db.name}`, e);
|
|
1956
1989
|
}
|
|
1957
|
-
}
|
|
1958
|
-
catch (e) {
|
|
1959
|
-
console.debug(`Dexie Cloud: Could not register periodicSync for ${db.name}`, e);
|
|
1960
|
-
}
|
|
1990
|
+
});
|
|
1961
1991
|
}
|
|
1962
1992
|
|
|
1963
1993
|
function triggerSync(db, purpose) {
|
|
@@ -1989,19 +2019,15 @@
|
|
|
1989
2019
|
function interactWithUser(userInteraction, req) {
|
|
1990
2020
|
let done = false;
|
|
1991
2021
|
return new Promise((resolve, reject) => {
|
|
1992
|
-
const interactionProps = {
|
|
1993
|
-
...req,
|
|
1994
|
-
onSubmit: (res) => {
|
|
2022
|
+
const interactionProps = Object.assign(Object.assign({}, req), { onSubmit: (res) => {
|
|
1995
2023
|
userInteraction.next(undefined);
|
|
1996
2024
|
done = true;
|
|
1997
2025
|
resolve(res);
|
|
1998
|
-
},
|
|
1999
|
-
onCancel: () => {
|
|
2026
|
+
}, onCancel: () => {
|
|
2000
2027
|
userInteraction.next(undefined);
|
|
2001
2028
|
done = true;
|
|
2002
2029
|
reject(new Dexie__default["default"].AbortError("User cancelled"));
|
|
2003
|
-
}
|
|
2004
|
-
};
|
|
2030
|
+
} });
|
|
2005
2031
|
userInteraction.next(interactionProps);
|
|
2006
2032
|
// Start subscribing for external updates to db.cloud.userInteraction, and if so, cancel this request.
|
|
2007
2033
|
/*const subscription = userInteraction.subscribe((currentInteractionProps) => {
|
|
@@ -2022,180 +2048,193 @@
|
|
|
2022
2048
|
fields: {}
|
|
2023
2049
|
});
|
|
2024
2050
|
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
email
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2051
|
+
function promptForEmail(userInteraction, title, emailHint) {
|
|
2052
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2053
|
+
let email = emailHint || '';
|
|
2054
|
+
while (!email || !/^[\w-\.]+@([\w-]+\.)+[\w-]{2,10}$/.test(email)) {
|
|
2055
|
+
email = (yield interactWithUser(userInteraction, {
|
|
2056
|
+
type: 'email',
|
|
2057
|
+
title,
|
|
2058
|
+
alerts: email
|
|
2059
|
+
? [
|
|
2060
|
+
{
|
|
2061
|
+
type: 'error',
|
|
2062
|
+
messageCode: 'INVALID_EMAIL',
|
|
2063
|
+
message: 'Please enter a valid email address',
|
|
2064
|
+
messageParams: {},
|
|
2065
|
+
},
|
|
2066
|
+
]
|
|
2067
|
+
: [],
|
|
2068
|
+
fields: {
|
|
2069
|
+
email: {
|
|
2070
|
+
type: 'email',
|
|
2071
|
+
placeholder: 'you@somedomain.com',
|
|
2038
2072
|
},
|
|
2039
|
-
]
|
|
2040
|
-
: [],
|
|
2041
|
-
fields: {
|
|
2042
|
-
email: {
|
|
2043
|
-
type: 'email',
|
|
2044
|
-
placeholder: 'you@somedomain.com',
|
|
2045
2073
|
},
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2074
|
+
})).email;
|
|
2075
|
+
}
|
|
2076
|
+
return email;
|
|
2077
|
+
});
|
|
2050
2078
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
];
|
|
2060
|
-
if (alert) {
|
|
2061
|
-
alerts.push(alert);
|
|
2062
|
-
}
|
|
2063
|
-
const { otp } = await interactWithUser(userInteraction, {
|
|
2064
|
-
type: 'otp',
|
|
2065
|
-
title: 'Enter OTP',
|
|
2066
|
-
alerts,
|
|
2067
|
-
fields: {
|
|
2068
|
-
otp: {
|
|
2069
|
-
type: 'otp',
|
|
2070
|
-
label: 'OTP',
|
|
2071
|
-
placeholder: 'Paste OTP here',
|
|
2079
|
+
function promptForOTP(userInteraction, email, alert) {
|
|
2080
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2081
|
+
const alerts = [
|
|
2082
|
+
{
|
|
2083
|
+
type: 'info',
|
|
2084
|
+
messageCode: 'OTP_SENT',
|
|
2085
|
+
message: `A One-Time password has been sent to {email}`,
|
|
2086
|
+
messageParams: { email },
|
|
2072
2087
|
},
|
|
2073
|
-
|
|
2088
|
+
];
|
|
2089
|
+
if (alert) {
|
|
2090
|
+
alerts.push(alert);
|
|
2091
|
+
}
|
|
2092
|
+
const { otp } = yield interactWithUser(userInteraction, {
|
|
2093
|
+
type: 'otp',
|
|
2094
|
+
title: 'Enter OTP',
|
|
2095
|
+
alerts,
|
|
2096
|
+
fields: {
|
|
2097
|
+
otp: {
|
|
2098
|
+
type: 'otp',
|
|
2099
|
+
label: 'OTP',
|
|
2100
|
+
placeholder: 'Paste OTP here',
|
|
2101
|
+
},
|
|
2102
|
+
},
|
|
2103
|
+
});
|
|
2104
|
+
return otp;
|
|
2074
2105
|
});
|
|
2075
|
-
return otp;
|
|
2076
2106
|
}
|
|
2077
2107
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2108
|
+
function loadAccessToken(db) {
|
|
2109
|
+
var _a, _b;
|
|
2110
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2111
|
+
const currentUser = yield db.getCurrentUser();
|
|
2112
|
+
const { accessToken, accessTokenExpiration, refreshToken, refreshTokenExpiration, claims, } = currentUser;
|
|
2113
|
+
if (!accessToken)
|
|
2114
|
+
return;
|
|
2115
|
+
const expTime = (_a = accessTokenExpiration === null || accessTokenExpiration === void 0 ? void 0 : accessTokenExpiration.getTime()) !== null && _a !== void 0 ? _a : Infinity;
|
|
2116
|
+
if (expTime > Date.now()) {
|
|
2117
|
+
return accessToken;
|
|
2118
|
+
}
|
|
2119
|
+
if (!refreshToken) {
|
|
2120
|
+
throw new Error(`Refresh token missing`);
|
|
2121
|
+
}
|
|
2122
|
+
const refreshExpTime = (_b = refreshTokenExpiration === null || refreshTokenExpiration === void 0 ? void 0 : refreshTokenExpiration.getTime()) !== null && _b !== void 0 ? _b : Infinity;
|
|
2123
|
+
if (refreshExpTime <= Date.now()) {
|
|
2124
|
+
throw new Error(`Refresh token has expired`);
|
|
2125
|
+
}
|
|
2126
|
+
const refreshedLogin = yield refreshAccessToken(db.cloud.options.databaseUrl, currentUser);
|
|
2127
|
+
yield db.table('$logins').update(claims.sub, {
|
|
2128
|
+
accessToken: refreshedLogin.accessToken,
|
|
2129
|
+
accessTokenExpiration: refreshedLogin.accessTokenExpiration,
|
|
2130
|
+
});
|
|
2131
|
+
return refreshedLogin.accessToken;
|
|
2098
2132
|
});
|
|
2099
|
-
return refreshedLogin.accessToken;
|
|
2100
|
-
}
|
|
2101
|
-
async function authenticate(url, context, fetchToken, userInteraction, hints) {
|
|
2102
|
-
if (context.accessToken &&
|
|
2103
|
-
context.accessTokenExpiration.getTime() > Date.now()) {
|
|
2104
|
-
return context;
|
|
2105
|
-
}
|
|
2106
|
-
else if (context.refreshToken &&
|
|
2107
|
-
(!context.refreshTokenExpiration ||
|
|
2108
|
-
context.refreshTokenExpiration.getTime() > Date.now())) {
|
|
2109
|
-
return await refreshAccessToken(url, context);
|
|
2110
|
-
}
|
|
2111
|
-
else {
|
|
2112
|
-
return await userAuthenticate(context, fetchToken, userInteraction, hints);
|
|
2113
|
-
}
|
|
2114
2133
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
scopes: ['ACCESS_DB'],
|
|
2130
|
-
signature,
|
|
2131
|
-
signing_algorithm,
|
|
2132
|
-
time_stamp,
|
|
2133
|
-
};
|
|
2134
|
-
const res = await fetch(`${url}/token`, {
|
|
2135
|
-
body: JSON.stringify(tokenRequest),
|
|
2136
|
-
method: 'post',
|
|
2137
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2138
|
-
mode: 'cors',
|
|
2134
|
+
function authenticate(url, context, fetchToken, userInteraction, hints) {
|
|
2135
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2136
|
+
if (context.accessToken &&
|
|
2137
|
+
context.accessTokenExpiration.getTime() > Date.now()) {
|
|
2138
|
+
return context;
|
|
2139
|
+
}
|
|
2140
|
+
else if (context.refreshToken &&
|
|
2141
|
+
(!context.refreshTokenExpiration ||
|
|
2142
|
+
context.refreshTokenExpiration.getTime() > Date.now())) {
|
|
2143
|
+
return yield refreshAccessToken(url, context);
|
|
2144
|
+
}
|
|
2145
|
+
else {
|
|
2146
|
+
return yield userAuthenticate(context, fetchToken, userInteraction, hints);
|
|
2147
|
+
}
|
|
2139
2148
|
});
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
hints,
|
|
2149
|
+
}
|
|
2150
|
+
function refreshAccessToken(url, login) {
|
|
2151
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2152
|
+
if (!login.refreshToken)
|
|
2153
|
+
throw new Error(`Cannot refresh token - refresh token is missing.`);
|
|
2154
|
+
if (!login.nonExportablePrivateKey)
|
|
2155
|
+
throw new Error(`login.nonExportablePrivateKey is missing - cannot sign refresh token without a private key.`);
|
|
2156
|
+
const time_stamp = Date.now();
|
|
2157
|
+
const signing_algorithm = 'RSASSA-PKCS1-v1_5';
|
|
2158
|
+
const textEncoder = new TextEncoder();
|
|
2159
|
+
const data = textEncoder.encode(login.refreshToken + time_stamp);
|
|
2160
|
+
const binarySignature = yield crypto.subtle.sign(signing_algorithm, login.nonExportablePrivateKey, data);
|
|
2161
|
+
const signature = b64encode(binarySignature);
|
|
2162
|
+
const tokenRequest = {
|
|
2163
|
+
grant_type: 'refresh_token',
|
|
2164
|
+
refresh_token: login.refreshToken,
|
|
2165
|
+
scopes: ['ACCESS_DB'],
|
|
2166
|
+
signature,
|
|
2167
|
+
signing_algorithm,
|
|
2168
|
+
time_stamp,
|
|
2169
|
+
};
|
|
2170
|
+
const res = yield fetch(`${url}/token`, {
|
|
2171
|
+
body: JSON.stringify(tokenRequest),
|
|
2172
|
+
method: 'post',
|
|
2173
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2174
|
+
mode: 'cors',
|
|
2167
2175
|
});
|
|
2168
|
-
if (
|
|
2169
|
-
throw new Error(`
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2176
|
+
if (res.status !== 200)
|
|
2177
|
+
throw new Error(`RefreshToken: Status ${res.status} from ${url}/token`);
|
|
2178
|
+
const response = yield res.json();
|
|
2179
|
+
login.accessToken = response.accessToken;
|
|
2180
|
+
login.accessTokenExpiration = response.accessTokenExpiration
|
|
2181
|
+
? new Date(response.accessTokenExpiration)
|
|
2182
|
+
: undefined;
|
|
2183
|
+
return login;
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
function userAuthenticate(context, fetchToken, userInteraction, hints) {
|
|
2187
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2188
|
+
const { privateKey, publicKey } = yield crypto.subtle.generateKey({
|
|
2189
|
+
name: 'RSASSA-PKCS1-v1_5',
|
|
2190
|
+
modulusLength: 2048,
|
|
2191
|
+
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
2192
|
+
hash: { name: 'SHA-256' },
|
|
2193
|
+
}, false, // Non-exportable...
|
|
2194
|
+
['sign', 'verify']);
|
|
2195
|
+
if (!privateKey || !publicKey)
|
|
2196
|
+
throw new Error(`Could not generate RSA keypair`); // Typings suggest these can be undefined...
|
|
2197
|
+
context.nonExportablePrivateKey = privateKey; //...but storable!
|
|
2198
|
+
const publicKeySPKI = yield crypto.subtle.exportKey('spki', publicKey);
|
|
2199
|
+
const publicKeyPEM = spkiToPEM(publicKeySPKI);
|
|
2200
|
+
context.publicKey = publicKey;
|
|
2201
|
+
try {
|
|
2202
|
+
const response2 = yield fetchToken({
|
|
2203
|
+
public_key: publicKeyPEM,
|
|
2204
|
+
hints,
|
|
2186
2205
|
});
|
|
2206
|
+
if (response2.type !== 'tokens')
|
|
2207
|
+
throw new Error(`Unexpected response type from token endpoint: ${response2.type}`);
|
|
2208
|
+
context.accessToken = response2.accessToken;
|
|
2209
|
+
context.accessTokenExpiration = new Date(response2.accessTokenExpiration);
|
|
2210
|
+
context.refreshToken = response2.refreshToken;
|
|
2211
|
+
if (response2.refreshTokenExpiration) {
|
|
2212
|
+
context.refreshTokenExpiration = new Date(response2.refreshTokenExpiration);
|
|
2213
|
+
}
|
|
2214
|
+
context.userId = response2.claims.sub;
|
|
2215
|
+
context.email = response2.claims.email;
|
|
2216
|
+
context.name = response2.claims.name;
|
|
2217
|
+
context.claims = response2.claims;
|
|
2218
|
+
if (response2.alerts && response2.alerts.length > 0) {
|
|
2219
|
+
yield interactWithUser(userInteraction, {
|
|
2220
|
+
type: 'message-alert',
|
|
2221
|
+
title: 'Authentication Alert',
|
|
2222
|
+
fields: {},
|
|
2223
|
+
alerts: response2.alerts,
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
return context;
|
|
2187
2227
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
}
|
|
2228
|
+
catch (error) {
|
|
2229
|
+
yield alertUser(userInteraction, 'Authentication Failed', {
|
|
2230
|
+
type: 'error',
|
|
2231
|
+
messageCode: 'GENERIC_ERROR',
|
|
2232
|
+
message: `We're having a problem authenticating right now.`,
|
|
2233
|
+
messageParams: {}
|
|
2234
|
+
}).catch(() => { });
|
|
2235
|
+
throw error;
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2199
2238
|
}
|
|
2200
2239
|
function spkiToPEM(keydata) {
|
|
2201
2240
|
const keydataB64 = b64encode(keydata);
|
|
@@ -2231,9 +2270,11 @@
|
|
|
2231
2270
|
lastLogin: new Date(0)
|
|
2232
2271
|
}));
|
|
2233
2272
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2273
|
+
save() {
|
|
2274
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2275
|
+
const db = wm$1.get(this);
|
|
2276
|
+
db.table("$logins").put(this);
|
|
2277
|
+
});
|
|
2237
2278
|
}
|
|
2238
2279
|
}
|
|
2239
2280
|
|
|
@@ -2249,92 +2290,95 @@
|
|
|
2249
2290
|
|
|
2250
2291
|
function otpFetchTokenCallback(db) {
|
|
2251
2292
|
const { userInteraction } = db.cloud;
|
|
2252
|
-
return
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
demo_user,
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
email,
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
headers: { 'Content-Type': 'application/json', mode: 'cors' },
|
|
2279
|
-
});
|
|
2280
|
-
if (res1.status !== 200) {
|
|
2281
|
-
const errMsg = await res1.text();
|
|
2282
|
-
await alertUser(userInteraction, "Token request failed", {
|
|
2283
|
-
type: 'error',
|
|
2284
|
-
messageCode: 'GENERIC_ERROR',
|
|
2285
|
-
message: errMsg,
|
|
2286
|
-
messageParams: {}
|
|
2287
|
-
}).catch(() => { });
|
|
2288
|
-
throw new HttpError(res1, errMsg);
|
|
2289
|
-
}
|
|
2290
|
-
const response = await res1.json();
|
|
2291
|
-
if (response.type === 'tokens') {
|
|
2292
|
-
// Demo user request can get a "tokens" response right away
|
|
2293
|
-
return response;
|
|
2294
|
-
}
|
|
2295
|
-
else if (tokenRequest.grant_type === 'otp') {
|
|
2296
|
-
if (response.type !== 'otp-sent')
|
|
2297
|
-
throw new Error(`Unexpected response from ${url}/token`);
|
|
2298
|
-
const otp = await promptForOTP(userInteraction, tokenRequest.email);
|
|
2299
|
-
tokenRequest.otp = otp || '';
|
|
2300
|
-
tokenRequest.otp_id = response.otp_id;
|
|
2301
|
-
let res2 = await fetch(`${url}/token`, {
|
|
2293
|
+
return function otpAuthenticate({ public_key, hints }) {
|
|
2294
|
+
var _a;
|
|
2295
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2296
|
+
let tokenRequest;
|
|
2297
|
+
const url = (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl;
|
|
2298
|
+
if (!url)
|
|
2299
|
+
throw new Error(`No database URL given.`);
|
|
2300
|
+
if ((hints === null || hints === void 0 ? void 0 : hints.grant_type) === 'demo') {
|
|
2301
|
+
const demo_user = yield promptForEmail(userInteraction, 'Enter a demo user email', (hints === null || hints === void 0 ? void 0 : hints.email) || (hints === null || hints === void 0 ? void 0 : hints.userId));
|
|
2302
|
+
tokenRequest = {
|
|
2303
|
+
demo_user,
|
|
2304
|
+
grant_type: 'demo',
|
|
2305
|
+
scopes: ['ACCESS_DB'],
|
|
2306
|
+
public_key,
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
else {
|
|
2310
|
+
const email = yield promptForEmail(userInteraction, 'Enter email address', hints === null || hints === void 0 ? void 0 : hints.email);
|
|
2311
|
+
tokenRequest = {
|
|
2312
|
+
email,
|
|
2313
|
+
grant_type: 'otp',
|
|
2314
|
+
scopes: ['ACCESS_DB'],
|
|
2315
|
+
public_key,
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
const res1 = yield fetch(`${url}/token`, {
|
|
2302
2319
|
body: JSON.stringify(tokenRequest),
|
|
2303
2320
|
method: 'post',
|
|
2304
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2305
|
-
mode: 'cors',
|
|
2321
|
+
headers: { 'Content-Type': 'application/json', mode: 'cors' },
|
|
2306
2322
|
});
|
|
2307
|
-
|
|
2308
|
-
const
|
|
2309
|
-
|
|
2323
|
+
if (res1.status !== 200) {
|
|
2324
|
+
const errMsg = yield res1.text();
|
|
2325
|
+
yield alertUser(userInteraction, "Token request failed", {
|
|
2310
2326
|
type: 'error',
|
|
2311
|
-
messageCode: '
|
|
2312
|
-
message:
|
|
2327
|
+
messageCode: 'GENERIC_ERROR',
|
|
2328
|
+
message: errMsg,
|
|
2313
2329
|
messageParams: {}
|
|
2314
|
-
});
|
|
2315
|
-
|
|
2330
|
+
}).catch(() => { });
|
|
2331
|
+
throw new HttpError(res1, errMsg);
|
|
2332
|
+
}
|
|
2333
|
+
const response = yield res1.json();
|
|
2334
|
+
if (response.type === 'tokens') {
|
|
2335
|
+
// Demo user request can get a "tokens" response right away
|
|
2336
|
+
return response;
|
|
2337
|
+
}
|
|
2338
|
+
else if (tokenRequest.grant_type === 'otp') {
|
|
2339
|
+
if (response.type !== 'otp-sent')
|
|
2340
|
+
throw new Error(`Unexpected response from ${url}/token`);
|
|
2341
|
+
const otp = yield promptForOTP(userInteraction, tokenRequest.email);
|
|
2342
|
+
tokenRequest.otp = otp || '';
|
|
2343
|
+
tokenRequest.otp_id = response.otp_id;
|
|
2344
|
+
let res2 = yield fetch(`${url}/token`, {
|
|
2316
2345
|
body: JSON.stringify(tokenRequest),
|
|
2317
2346
|
method: 'post',
|
|
2318
2347
|
headers: { 'Content-Type': 'application/json' },
|
|
2319
2348
|
mode: 'cors',
|
|
2320
2349
|
});
|
|
2350
|
+
while (res2.status === 401) {
|
|
2351
|
+
const errorText = yield res2.text();
|
|
2352
|
+
tokenRequest.otp = yield promptForOTP(userInteraction, tokenRequest.email, {
|
|
2353
|
+
type: 'error',
|
|
2354
|
+
messageCode: 'INVALID_OTP',
|
|
2355
|
+
message: errorText,
|
|
2356
|
+
messageParams: {}
|
|
2357
|
+
});
|
|
2358
|
+
res2 = yield fetch(`${url}/token`, {
|
|
2359
|
+
body: JSON.stringify(tokenRequest),
|
|
2360
|
+
method: 'post',
|
|
2361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2362
|
+
mode: 'cors',
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
if (res2.status !== 200) {
|
|
2366
|
+
const errMsg = yield res2.text();
|
|
2367
|
+
yield alertUser(userInteraction, "OTP Authentication Failed", {
|
|
2368
|
+
type: 'error',
|
|
2369
|
+
messageCode: 'GENERIC_ERROR',
|
|
2370
|
+
message: errMsg,
|
|
2371
|
+
messageParams: {}
|
|
2372
|
+
}).catch(() => { });
|
|
2373
|
+
throw new HttpError(res2, errMsg);
|
|
2374
|
+
}
|
|
2375
|
+
const response2 = yield res2.json();
|
|
2376
|
+
return response2;
|
|
2321
2377
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
await alertUser(userInteraction, "OTP Authentication Failed", {
|
|
2325
|
-
type: 'error',
|
|
2326
|
-
messageCode: 'GENERIC_ERROR',
|
|
2327
|
-
message: errMsg,
|
|
2328
|
-
messageParams: {}
|
|
2329
|
-
}).catch(() => { });
|
|
2330
|
-
throw new HttpError(res2, errMsg);
|
|
2378
|
+
else {
|
|
2379
|
+
throw new Error(`Unexpected response from ${url}/token`);
|
|
2331
2380
|
}
|
|
2332
|
-
|
|
2333
|
-
return response2;
|
|
2334
|
-
}
|
|
2335
|
-
else {
|
|
2336
|
-
throw new Error(`Unexpected response from ${url}/token`);
|
|
2337
|
-
}
|
|
2381
|
+
});
|
|
2338
2382
|
};
|
|
2339
2383
|
}
|
|
2340
2384
|
|
|
@@ -2349,83 +2393,87 @@
|
|
|
2349
2393
|
* @param db
|
|
2350
2394
|
* @param newUser
|
|
2351
2395
|
*/
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
.
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2396
|
+
function setCurrentUser(db, user) {
|
|
2397
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2398
|
+
if (user.userId === db.cloud.currentUserId)
|
|
2399
|
+
return; // Already this user.
|
|
2400
|
+
const $logins = db.table('$logins');
|
|
2401
|
+
yield db.transaction('rw', $logins, (tx) => __awaiter$1(this, void 0, void 0, function* () {
|
|
2402
|
+
const existingLogins = yield $logins.toArray();
|
|
2403
|
+
yield Promise.all(existingLogins
|
|
2404
|
+
.filter((login) => login.userId !== user.userId && login.isLoggedIn)
|
|
2405
|
+
.map((login) => {
|
|
2406
|
+
login.isLoggedIn = false;
|
|
2407
|
+
return $logins.put(login);
|
|
2408
|
+
}));
|
|
2409
|
+
user.isLoggedIn = true;
|
|
2410
|
+
user.lastLogin = new Date();
|
|
2411
|
+
yield user.save();
|
|
2412
|
+
console.debug('Saved new user', user.email);
|
|
2363
2413
|
}));
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2414
|
+
yield new Promise((resolve) => {
|
|
2415
|
+
if (db.cloud.currentUserId === user.userId) {
|
|
2416
|
+
resolve(null);
|
|
2417
|
+
}
|
|
2418
|
+
else {
|
|
2419
|
+
const subscription = db.cloud.currentUser.subscribe((currentUser) => {
|
|
2420
|
+
if (currentUser.userId === user.userId) {
|
|
2421
|
+
subscription.unsubscribe();
|
|
2422
|
+
resolve(null);
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
// TANKAR!!!!
|
|
2428
|
+
// V: Service workern kommer inte ha tillgång till currentUserObservable om den inte istället härrör från ett liveQuery.
|
|
2429
|
+
// V: Samma med andra windows.
|
|
2430
|
+
// V: Så kanske göra om den till att häröra från liveQuery som läser $logins.orderBy('lastLogin').last().
|
|
2431
|
+
// V: Då bara vara medveten om:
|
|
2432
|
+
// V: En sån observable börjar hämta data vid första subscribe
|
|
2433
|
+
// V: Vi har inget "inital value" men kan emulera det till att vara ANONYMOUS_USER
|
|
2434
|
+
// V: Om requireAuth är true, så borde db.on(ready) hålla databasen stängd för alla utom denna observable.
|
|
2435
|
+
// V: Om inte så behöver den inte blocka.
|
|
2436
|
+
// Andra tankar:
|
|
2437
|
+
// * Man kan inte byta användare när man är offline. Skulle gå att flytta realms till undanstuff-tabell vid user-change.
|
|
2438
|
+
// men troligen inte värt det.
|
|
2439
|
+
// * Istället: sälj inte inte switch-user funktionalitet utan tala enbart om inloggat vs icke inloggat läge.
|
|
2440
|
+
// * populate $logins med ANONYMOUS så att en påbörjad inloggning inte räknas, alternativt ha en boolean prop!
|
|
2441
|
+
// Kanske bäst ha en boolean prop!
|
|
2442
|
+
// * Alternativ switch-user funktionalitet:
|
|
2443
|
+
// * DBCore gömmer data från realms man inte har tillgång till.
|
|
2444
|
+
// * Cursor impl behövs också då.
|
|
2445
|
+
// * Då blir det snabba user switch.
|
|
2446
|
+
// * claims-settet som skickas till servern blir summan av alla claims. Då måste servern stödja multipla tokens eller
|
|
2447
|
+
// att ens token är ett samlad.
|
|
2368
2448
|
});
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
function login(db, hints) {
|
|
2452
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2453
|
+
const currentUser = yield db.getCurrentUser();
|
|
2454
|
+
if (currentUser.isLoggedIn) {
|
|
2455
|
+
if (hints) {
|
|
2456
|
+
if (hints.email && db.cloud.currentUser.value.email !== hints.email) {
|
|
2457
|
+
throw new Error(`Must logout before changing user`);
|
|
2458
|
+
}
|
|
2459
|
+
if (hints.userId && db.cloud.currentUserId !== hints.userId) {
|
|
2460
|
+
throw new Error(`Must logout before changing user`);
|
|
2378
2461
|
}
|
|
2379
|
-
});
|
|
2380
|
-
}
|
|
2381
|
-
});
|
|
2382
|
-
// TANKAR!!!!
|
|
2383
|
-
// V: Service workern kommer inte ha tillgång till currentUserObservable om den inte istället härrör från ett liveQuery.
|
|
2384
|
-
// V: Samma med andra windows.
|
|
2385
|
-
// V: Så kanske göra om den till att häröra från liveQuery som läser $logins.orderBy('lastLogin').last().
|
|
2386
|
-
// V: Då bara vara medveten om:
|
|
2387
|
-
// V: En sån observable börjar hämta data vid första subscribe
|
|
2388
|
-
// V: Vi har inget "inital value" men kan emulera det till att vara ANONYMOUS_USER
|
|
2389
|
-
// V: Om requireAuth är true, så borde db.on(ready) hålla databasen stängd för alla utom denna observable.
|
|
2390
|
-
// V: Om inte så behöver den inte blocka.
|
|
2391
|
-
// Andra tankar:
|
|
2392
|
-
// * Man kan inte byta användare när man är offline. Skulle gå att flytta realms till undanstuff-tabell vid user-change.
|
|
2393
|
-
// men troligen inte värt det.
|
|
2394
|
-
// * Istället: sälj inte inte switch-user funktionalitet utan tala enbart om inloggat vs icke inloggat läge.
|
|
2395
|
-
// * populate $logins med ANONYMOUS så att en påbörjad inloggning inte räknas, alternativt ha en boolean prop!
|
|
2396
|
-
// Kanske bäst ha en boolean prop!
|
|
2397
|
-
// * Alternativ switch-user funktionalitet:
|
|
2398
|
-
// * DBCore gömmer data från realms man inte har tillgång till.
|
|
2399
|
-
// * Cursor impl behövs också då.
|
|
2400
|
-
// * Då blir det snabba user switch.
|
|
2401
|
-
// * claims-settet som skickas till servern blir summan av alla claims. Då måste servern stödja multipla tokens eller
|
|
2402
|
-
// att ens token är ett samlad.
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
async function login(db, hints) {
|
|
2406
|
-
const currentUser = await db.getCurrentUser();
|
|
2407
|
-
if (currentUser.isLoggedIn) {
|
|
2408
|
-
if (hints) {
|
|
2409
|
-
if (hints.email && db.cloud.currentUser.value.email !== hints.email) {
|
|
2410
|
-
throw new Error(`Must logout before changing user`);
|
|
2411
|
-
}
|
|
2412
|
-
if (hints.userId && db.cloud.currentUserId !== hints.userId) {
|
|
2413
|
-
throw new Error(`Must logout before changing user`);
|
|
2414
2462
|
}
|
|
2463
|
+
// Already authenticated according to given hints.
|
|
2464
|
+
return;
|
|
2415
2465
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2466
|
+
const context = new AuthPersistedContext(db, {
|
|
2467
|
+
claims: {},
|
|
2468
|
+
lastLogin: new Date(0),
|
|
2469
|
+
});
|
|
2470
|
+
yield authenticate(db.cloud.options.databaseUrl, context, db.cloud.options.fetchTokens || otpFetchTokenCallback(db), db.cloud.userInteraction, hints);
|
|
2471
|
+
yield context.save();
|
|
2472
|
+
yield setCurrentUser(db, context);
|
|
2473
|
+
// Make sure to resync as the new login will be authorized
|
|
2474
|
+
// for new realms.
|
|
2475
|
+
triggerSync(db, "pull");
|
|
2422
2476
|
});
|
|
2423
|
-
await authenticate(db.cloud.options.databaseUrl, context, db.cloud.options.fetchTokens || otpFetchTokenCallback(db), db.cloud.userInteraction, hints);
|
|
2424
|
-
await context.save();
|
|
2425
|
-
await setCurrentUser(db, context);
|
|
2426
|
-
// Make sure to resync as the new login will be authorized
|
|
2427
|
-
// for new realms.
|
|
2428
|
-
triggerSync(db, "pull");
|
|
2429
2477
|
}
|
|
2430
2478
|
|
|
2431
2479
|
const UNAUTHORIZED_USER = {
|
|
@@ -2440,7 +2488,7 @@
|
|
|
2440
2488
|
Object.freeze(UNAUTHORIZED_USER);
|
|
2441
2489
|
Object.freeze(UNAUTHORIZED_USER.claims);
|
|
2442
2490
|
}
|
|
2443
|
-
catch { }
|
|
2491
|
+
catch (_a) { }
|
|
2444
2492
|
|
|
2445
2493
|
const swHolder = {};
|
|
2446
2494
|
const swContainer = self.document && navigator.serviceWorker; // self.document is to verify we're not the SW ourself
|
|
@@ -2449,8 +2497,9 @@
|
|
|
2449
2497
|
if (typeof self !== 'undefined' && 'clients' in self && !self.document) {
|
|
2450
2498
|
// We are the service worker. Propagate messages to all our clients.
|
|
2451
2499
|
addEventListener('message', (ev) => {
|
|
2452
|
-
|
|
2453
|
-
|
|
2500
|
+
var _a, _b;
|
|
2501
|
+
if ((_b = (_a = ev.data) === null || _a === void 0 ? void 0 : _a.type) === null || _b === void 0 ? void 0 : _b.startsWith('sw-broadcast-')) {
|
|
2502
|
+
[...self['clients'].matchAll({ includeUncontrolled: true })].forEach((client) => { var _a; return client.id !== ((_a = ev.source) === null || _a === void 0 ? void 0 : _a.id) && client.postMessage(ev.data); });
|
|
2454
2503
|
}
|
|
2455
2504
|
});
|
|
2456
2505
|
}
|
|
@@ -2462,7 +2511,8 @@
|
|
|
2462
2511
|
if (!swContainer)
|
|
2463
2512
|
return () => { };
|
|
2464
2513
|
const forwarder = (ev) => {
|
|
2465
|
-
|
|
2514
|
+
var _a;
|
|
2515
|
+
if (((_a = ev.data) === null || _a === void 0 ? void 0 : _a.type) === `sw-broadcast-${this.name}`) {
|
|
2466
2516
|
listener(ev.data.message);
|
|
2467
2517
|
}
|
|
2468
2518
|
};
|
|
@@ -2470,6 +2520,7 @@
|
|
|
2470
2520
|
return () => swContainer.removeEventListener('message', forwarder);
|
|
2471
2521
|
}
|
|
2472
2522
|
postMessage(message) {
|
|
2523
|
+
var _a;
|
|
2473
2524
|
if (typeof self['clients'] === 'object') {
|
|
2474
2525
|
// We're a service worker. Propagate to our browser clients.
|
|
2475
2526
|
[...self['clients'].matchAll({ includeUncontrolled: true })].forEach((client) => client.postMessage({
|
|
@@ -2480,7 +2531,7 @@
|
|
|
2480
2531
|
else if (swHolder.registration) {
|
|
2481
2532
|
// We're a client (browser window or other worker)
|
|
2482
2533
|
// Post to SW so it can repost to all its clients and to itself
|
|
2483
|
-
swHolder.registration.active
|
|
2534
|
+
(_a = swHolder.registration.active) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
2484
2535
|
type: `sw-broadcast-${this.name}`,
|
|
2485
2536
|
message
|
|
2486
2537
|
});
|
|
@@ -2523,22 +2574,24 @@
|
|
|
2523
2574
|
this.bc = bc;
|
|
2524
2575
|
}
|
|
2525
2576
|
next(message) {
|
|
2526
|
-
console.debug("BroadcastedAndLocalEvent: bc.postMessage()", {
|
|
2577
|
+
console.debug("BroadcastedAndLocalEvent: bc.postMessage()", Object.assign({}, message), "bc is a", this.bc);
|
|
2527
2578
|
this.bc.postMessage(message);
|
|
2528
2579
|
const ev = new CustomEvent(`lbc-${this.name}`, { detail: message });
|
|
2529
2580
|
self.dispatchEvent(ev);
|
|
2530
2581
|
}
|
|
2531
2582
|
}
|
|
2532
2583
|
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2584
|
+
function computeRealmSetHash({ realms, inviteRealms, }) {
|
|
2585
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2586
|
+
const data = JSON.stringify([
|
|
2587
|
+
...realms.map((realmId) => ({ realmId, accepted: true })),
|
|
2588
|
+
...inviteRealms.map((realmId) => ({ realmId, accepted: false })),
|
|
2589
|
+
].sort((a, b) => a.realmId < b.realmId ? -1 : a.realmId > b.realmId ? 1 : 0));
|
|
2590
|
+
const byteArray = new TextEncoder().encode(data);
|
|
2591
|
+
const digestBytes = yield crypto.subtle.digest('SHA-1', byteArray);
|
|
2592
|
+
const base64 = b64encode(digestBytes);
|
|
2593
|
+
return base64;
|
|
2594
|
+
});
|
|
2542
2595
|
}
|
|
2543
2596
|
|
|
2544
2597
|
function getSyncableTables(db) {
|
|
@@ -2553,7 +2606,8 @@
|
|
|
2553
2606
|
}
|
|
2554
2607
|
|
|
2555
2608
|
function getTableFromMutationTable(mutationTable) {
|
|
2556
|
-
|
|
2609
|
+
var _a;
|
|
2610
|
+
const tableName = (_a = /^\$(.*)_mutations$/.exec(mutationTable)) === null || _a === void 0 ? void 0 : _a[1];
|
|
2557
2611
|
if (!tableName)
|
|
2558
2612
|
throw new Error(`Given mutationTable ${mutationTable} is not correct`);
|
|
2559
2613
|
return tableName;
|
|
@@ -2564,49 +2618,51 @@
|
|
|
2564
2618
|
return concat.apply([], a);
|
|
2565
2619
|
}
|
|
2566
2620
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
currentEntry
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2621
|
+
function listClientChanges(mutationTables, db, { since = {}, limit = Infinity } = {}) {
|
|
2622
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2623
|
+
const allMutsOnTables = yield Promise.all(mutationTables.map((mutationTable) => __awaiter$1(this, void 0, void 0, function* () {
|
|
2624
|
+
const tableName = getTableFromMutationTable(mutationTable.name);
|
|
2625
|
+
const lastRevision = since[tableName];
|
|
2626
|
+
let query = lastRevision
|
|
2627
|
+
? mutationTable.where('rev').above(lastRevision)
|
|
2628
|
+
: mutationTable;
|
|
2629
|
+
if (limit < Infinity)
|
|
2630
|
+
query = query.limit(limit);
|
|
2631
|
+
const muts = yield query.toArray();
|
|
2632
|
+
//const objTable = db.table(tableName);
|
|
2633
|
+
/*for (const mut of muts) {
|
|
2634
|
+
if (mut.type === "insert" || mut.type === "upsert") {
|
|
2635
|
+
mut.values = await objTable.bulkGet(mut.keys);
|
|
2636
|
+
}
|
|
2637
|
+
}*/
|
|
2638
|
+
return muts.map((mut) => ({
|
|
2639
|
+
table: tableName,
|
|
2640
|
+
mut,
|
|
2641
|
+
}));
|
|
2642
|
+
})));
|
|
2643
|
+
// Sort by time to get a true order of the operations (between tables)
|
|
2644
|
+
const sorted = flatten(allMutsOnTables).sort((a, b) => a.mut.ts - b.mut.ts);
|
|
2645
|
+
const result = [];
|
|
2646
|
+
let currentEntry = null;
|
|
2647
|
+
let currentTxid = null;
|
|
2648
|
+
for (const { table, mut } of sorted) {
|
|
2649
|
+
if (currentEntry &&
|
|
2650
|
+
currentEntry.table === table &&
|
|
2651
|
+
currentTxid === mut.txid) {
|
|
2652
|
+
currentEntry.muts.push(mut);
|
|
2653
|
+
}
|
|
2654
|
+
else {
|
|
2655
|
+
currentEntry = {
|
|
2656
|
+
table,
|
|
2657
|
+
muts: [mut],
|
|
2658
|
+
};
|
|
2659
|
+
currentTxid = mut.txid;
|
|
2660
|
+
result.push(currentEntry);
|
|
2661
|
+
}
|
|
2606
2662
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2663
|
+
// Filter out those tables that doesn't have any mutations:
|
|
2664
|
+
return result;
|
|
2665
|
+
});
|
|
2610
2666
|
}
|
|
2611
2667
|
|
|
2612
2668
|
function randomString(bytes) {
|
|
@@ -2615,58 +2671,60 @@
|
|
|
2615
2671
|
return btoa(String.fromCharCode.apply(null, buf));
|
|
2616
2672
|
}
|
|
2617
2673
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
if (
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
?
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2674
|
+
function listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms) {
|
|
2675
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
2676
|
+
const txid = `upload-${randomString(8)}`;
|
|
2677
|
+
if (currentUser.isLoggedIn) {
|
|
2678
|
+
if (tablesToSyncify.length > 0) {
|
|
2679
|
+
const ignoredRealms = new Set(alreadySyncedRealms || []);
|
|
2680
|
+
const upserts = yield Promise.all(tablesToSyncify.map((table) => __awaiter$1(this, void 0, void 0, function* () {
|
|
2681
|
+
const { extractKey } = table.core.schema.primaryKey;
|
|
2682
|
+
if (!extractKey)
|
|
2683
|
+
return { table: table.name, muts: [] }; // Outbound tables are not synced.
|
|
2684
|
+
const dexieCloudTableSchema = schema[table.name];
|
|
2685
|
+
const query = (dexieCloudTableSchema === null || dexieCloudTableSchema === void 0 ? void 0 : dexieCloudTableSchema.generatedGlobalId)
|
|
2686
|
+
? table.filter((item) => {
|
|
2687
|
+
const id = extractKey(item);
|
|
2688
|
+
return (!ignoredRealms.has(item.realmId || '') &&
|
|
2689
|
+
//(id[0] !== '#' || !!item.$ts) && // Private obj need no sync if not changed
|
|
2690
|
+
isValidSyncableID(id));
|
|
2691
|
+
})
|
|
2692
|
+
: table.filter((item) => {
|
|
2693
|
+
extractKey(item);
|
|
2694
|
+
return (!ignoredRealms.has(item.realmId || '') &&
|
|
2695
|
+
//(id[0] !== '#' || !!item.$ts) && // Private obj need no sync if not changed
|
|
2696
|
+
isValidAtID(extractKey(item), dexieCloudTableSchema === null || dexieCloudTableSchema === void 0 ? void 0 : dexieCloudTableSchema.idPrefix));
|
|
2697
|
+
});
|
|
2698
|
+
const unsyncedObjects = yield query.toArray();
|
|
2699
|
+
if (unsyncedObjects.length > 0) {
|
|
2700
|
+
const mut = {
|
|
2701
|
+
type: 'upsert',
|
|
2702
|
+
values: unsyncedObjects,
|
|
2703
|
+
keys: unsyncedObjects.map(extractKey),
|
|
2704
|
+
userId: currentUser.userId,
|
|
2705
|
+
txid,
|
|
2706
|
+
};
|
|
2707
|
+
return {
|
|
2708
|
+
table: table.name,
|
|
2709
|
+
muts: [mut],
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
else {
|
|
2713
|
+
return {
|
|
2714
|
+
table: table.name,
|
|
2715
|
+
muts: [],
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
})));
|
|
2719
|
+
return upserts.filter((op) => op.muts.length > 0);
|
|
2720
|
+
}
|
|
2663
2721
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2722
|
+
return [];
|
|
2723
|
+
});
|
|
2666
2724
|
}
|
|
2667
2725
|
|
|
2668
2726
|
function getTablesToSyncify(db, syncState) {
|
|
2669
|
-
const syncedTables = syncState
|
|
2727
|
+
const syncedTables = (syncState === null || syncState === void 0 ? void 0 : syncState.syncedTables) || [];
|
|
2670
2728
|
const syncableTables = getSyncableTables(db);
|
|
2671
2729
|
const tablesToSyncify = syncableTables.filter((tbl) => !syncedTables.includes(tbl.name));
|
|
2672
2730
|
return tablesToSyncify;
|
|
@@ -3188,23 +3246,17 @@
|
|
|
3188
3246
|
return this.v;
|
|
3189
3247
|
}
|
|
3190
3248
|
}
|
|
3191
|
-
const defs = {
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
};
|
|
3203
|
-
},
|
|
3204
|
-
revive: ({ v, }) => new FakeBigInt(v)
|
|
3205
|
-
}
|
|
3206
|
-
})
|
|
3207
|
-
};
|
|
3249
|
+
const defs = Object.assign(Object.assign({}, undefinedDef), (hasBigIntSupport
|
|
3250
|
+
? {}
|
|
3251
|
+
: {
|
|
3252
|
+
bigint: {
|
|
3253
|
+
test: (val) => val instanceof FakeBigInt,
|
|
3254
|
+
replace: (fakeBigInt) => {
|
|
3255
|
+
return Object.assign({ $t: 'bigint' }, fakeBigInt);
|
|
3256
|
+
},
|
|
3257
|
+
revive: ({ v, }) => new FakeBigInt(v)
|
|
3258
|
+
}
|
|
3259
|
+
}));
|
|
3208
3260
|
const TSON = TypesonSimplified(builtin, defs);
|
|
3209
3261
|
const BISON = Bison(defs);
|
|
3210
3262
|
|
|
@@ -3253,110 +3305,107 @@
|
|
|
3253
3305
|
}
|
|
3254
3306
|
function cloneChange(change, rewriteValues) {
|
|
3255
3307
|
// clone on demand:
|
|
3256
|
-
return {
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
? change.muts.map((m) => ({
|
|
3260
|
-
...m,
|
|
3261
|
-
keys: m.keys.slice(),
|
|
3262
|
-
values: m.values.slice(),
|
|
3263
|
-
}))
|
|
3264
|
-
: change.muts.map((m) => ({ ...m, keys: m.keys.slice() })),
|
|
3265
|
-
};
|
|
3308
|
+
return Object.assign(Object.assign({}, change), { muts: rewriteValues
|
|
3309
|
+
? change.muts.map((m) => (Object.assign(Object.assign({}, m), { keys: m.keys.slice(), values: m.values.slice() })))
|
|
3310
|
+
: change.muts.map((m) => (Object.assign(Object.assign({}, m), { keys: m.keys.slice() }))) });
|
|
3266
3311
|
}
|
|
3267
3312
|
|
|
3268
3313
|
//import {BisonWebStreamReader} from "dreambase-library/dist/typeson-simplified/BisonWebStreamReader";
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
const syncRequest = {
|
|
3282
|
-
v: 2,
|
|
3283
|
-
dbID: syncState?.remoteDbId,
|
|
3284
|
-
clientIdentity,
|
|
3285
|
-
schema: schema || {},
|
|
3286
|
-
lastPull: syncState ? {
|
|
3287
|
-
serverRevision: syncState.serverRevision,
|
|
3288
|
-
realms: syncState.realms,
|
|
3289
|
-
inviteRealms: syncState.inviteRealms
|
|
3290
|
-
} : undefined,
|
|
3291
|
-
baseRevs,
|
|
3292
|
-
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes)
|
|
3293
|
-
};
|
|
3294
|
-
console.debug("Sync request", syncRequest);
|
|
3295
|
-
db.syncStateChangedEvent.next({
|
|
3296
|
-
phase: 'pushing',
|
|
3297
|
-
});
|
|
3298
|
-
const res = await fetch(`${databaseUrl}/sync`, {
|
|
3299
|
-
method: 'post',
|
|
3300
|
-
headers,
|
|
3301
|
-
body: TSON.stringify(syncRequest)
|
|
3302
|
-
});
|
|
3303
|
-
//const contentLength = Number(res.headers.get('content-length'));
|
|
3304
|
-
db.syncStateChangedEvent.next({
|
|
3305
|
-
phase: 'pulling'
|
|
3306
|
-
});
|
|
3307
|
-
if (!res.ok) {
|
|
3308
|
-
throw new HttpError(res);
|
|
3309
|
-
}
|
|
3310
|
-
switch (res.headers.get('content-type')) {
|
|
3311
|
-
case 'application/x-bison':
|
|
3312
|
-
return BISON.fromBinary(await res.blob());
|
|
3313
|
-
case 'application/x-bison-stream': //return BisonWebStreamReader(BISON, res);
|
|
3314
|
-
default:
|
|
3315
|
-
case 'application/json': {
|
|
3316
|
-
const text = await res.text();
|
|
3317
|
-
const syncRes = TSON.parse(text);
|
|
3318
|
-
return syncRes;
|
|
3314
|
+
function syncWithServer(changes, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser) {
|
|
3315
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3316
|
+
//
|
|
3317
|
+
// Push changes to server using fetch
|
|
3318
|
+
//
|
|
3319
|
+
const headers = {
|
|
3320
|
+
Accept: 'application/json, application/x-bison, application/x-bison-stream',
|
|
3321
|
+
'Content-Type': 'application/tson'
|
|
3322
|
+
};
|
|
3323
|
+
const accessToken = yield loadAccessToken(db);
|
|
3324
|
+
if (accessToken) {
|
|
3325
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
3319
3326
|
}
|
|
3320
|
-
|
|
3327
|
+
const syncRequest = {
|
|
3328
|
+
v: 2,
|
|
3329
|
+
dbID: syncState === null || syncState === void 0 ? void 0 : syncState.remoteDbId,
|
|
3330
|
+
clientIdentity,
|
|
3331
|
+
schema: schema || {},
|
|
3332
|
+
lastPull: syncState ? {
|
|
3333
|
+
serverRevision: syncState.serverRevision,
|
|
3334
|
+
realms: syncState.realms,
|
|
3335
|
+
inviteRealms: syncState.inviteRealms
|
|
3336
|
+
} : undefined,
|
|
3337
|
+
baseRevs,
|
|
3338
|
+
changes: encodeIdsForServer(db.dx.core.schema, currentUser, changes)
|
|
3339
|
+
};
|
|
3340
|
+
console.debug("Sync request", syncRequest);
|
|
3341
|
+
db.syncStateChangedEvent.next({
|
|
3342
|
+
phase: 'pushing',
|
|
3343
|
+
});
|
|
3344
|
+
const res = yield fetch(`${databaseUrl}/sync`, {
|
|
3345
|
+
method: 'post',
|
|
3346
|
+
headers,
|
|
3347
|
+
body: TSON.stringify(syncRequest)
|
|
3348
|
+
});
|
|
3349
|
+
//const contentLength = Number(res.headers.get('content-length'));
|
|
3350
|
+
db.syncStateChangedEvent.next({
|
|
3351
|
+
phase: 'pulling'
|
|
3352
|
+
});
|
|
3353
|
+
if (!res.ok) {
|
|
3354
|
+
throw new HttpError(res);
|
|
3355
|
+
}
|
|
3356
|
+
switch (res.headers.get('content-type')) {
|
|
3357
|
+
case 'application/x-bison':
|
|
3358
|
+
return BISON.fromBinary(yield res.blob());
|
|
3359
|
+
case 'application/x-bison-stream': //return BisonWebStreamReader(BISON, res);
|
|
3360
|
+
default:
|
|
3361
|
+
case 'application/json': {
|
|
3362
|
+
const text = yield res.text();
|
|
3363
|
+
const syncRes = TSON.parse(text);
|
|
3364
|
+
return syncRes;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
});
|
|
3321
3368
|
}
|
|
3322
3369
|
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
member.userId
|
|
3331
|
-
|
|
3332
|
-
});
|
|
3333
|
-
}
|
|
3334
|
-
else if (table.name === "roles") ;
|
|
3335
|
-
else if (table.name === "realms") {
|
|
3336
|
-
// realms
|
|
3337
|
-
await table.toCollection().modify((realm) => {
|
|
3338
|
-
if (!ignoredRealms.has(realm.realmId) && (realm.owner === undefined || realm.owner === UNAUTHORIZED_USER.userId)) {
|
|
3339
|
-
realm.owner = currentUser.userId;
|
|
3340
|
-
}
|
|
3341
|
-
});
|
|
3342
|
-
}
|
|
3343
|
-
else {
|
|
3344
|
-
// application entities
|
|
3345
|
-
await table.toCollection().modify((obj) => {
|
|
3346
|
-
if (!obj.realmId || !ignoredRealms.has(obj.realmId)) {
|
|
3347
|
-
if (!obj.owner || obj.owner === UNAUTHORIZED_USER.userId)
|
|
3348
|
-
obj.owner = currentUser.userId;
|
|
3349
|
-
if (!obj.realmId || obj.realmId === UNAUTHORIZED_USER.userId) {
|
|
3350
|
-
obj.realmId = currentUser.userId;
|
|
3370
|
+
function modifyLocalObjectsWithNewUserId(syncifiedTables, currentUser, alreadySyncedRealms) {
|
|
3371
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3372
|
+
const ignoredRealms = new Set(alreadySyncedRealms || []);
|
|
3373
|
+
for (const table of syncifiedTables) {
|
|
3374
|
+
if (table.name === "members") {
|
|
3375
|
+
// members
|
|
3376
|
+
yield table.toCollection().modify((member) => {
|
|
3377
|
+
if (!ignoredRealms.has(member.realmId) && (!member.userId || member.userId === UNAUTHORIZED_USER.userId)) {
|
|
3378
|
+
member.userId = currentUser.userId;
|
|
3351
3379
|
}
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
else if (table.name === "roles") ;
|
|
3383
|
+
else if (table.name === "realms") {
|
|
3384
|
+
// realms
|
|
3385
|
+
yield table.toCollection().modify((realm) => {
|
|
3386
|
+
if (!ignoredRealms.has(realm.realmId) && (realm.owner === undefined || realm.owner === UNAUTHORIZED_USER.userId)) {
|
|
3387
|
+
realm.owner = currentUser.userId;
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
else {
|
|
3392
|
+
// application entities
|
|
3393
|
+
yield table.toCollection().modify((obj) => {
|
|
3394
|
+
if (!obj.realmId || !ignoredRealms.has(obj.realmId)) {
|
|
3395
|
+
if (!obj.owner || obj.owner === UNAUTHORIZED_USER.userId)
|
|
3396
|
+
obj.owner = currentUser.userId;
|
|
3397
|
+
if (!obj.realmId || obj.realmId === UNAUTHORIZED_USER.userId) {
|
|
3398
|
+
obj.realmId = currentUser.userId;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3354
3403
|
}
|
|
3355
|
-
}
|
|
3404
|
+
});
|
|
3356
3405
|
}
|
|
3357
3406
|
|
|
3358
3407
|
function throwIfCancelled(cancelToken) {
|
|
3359
|
-
if (cancelToken
|
|
3408
|
+
if (cancelToken === null || cancelToken === void 0 ? void 0 : cancelToken.cancelled)
|
|
3360
3409
|
throw new Dexie__default["default"].AbortError(`Operation was cancelled`);
|
|
3361
3410
|
}
|
|
3362
3411
|
|
|
@@ -3368,17 +3417,19 @@
|
|
|
3368
3417
|
self.addEventListener('online', () => isOnline = true);
|
|
3369
3418
|
self.addEventListener('offline', () => isOnline = false);
|
|
3370
3419
|
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
.
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3420
|
+
function updateBaseRevs(db, schema, latestRevisions, serverRev) {
|
|
3421
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3422
|
+
yield db.$baseRevs.bulkPut(Object.keys(schema)
|
|
3423
|
+
.filter((table) => schema[table].markedForSync)
|
|
3424
|
+
.map((tableName) => {
|
|
3425
|
+
const lastClientRevOnPreviousServerRev = latestRevisions[tableName] || 0;
|
|
3426
|
+
return {
|
|
3427
|
+
tableName,
|
|
3428
|
+
clientRev: lastClientRevOnPreviousServerRev + 1,
|
|
3429
|
+
serverRev,
|
|
3430
|
+
};
|
|
3431
|
+
}));
|
|
3432
|
+
});
|
|
3382
3433
|
}
|
|
3383
3434
|
|
|
3384
3435
|
function getLatestRevisionsPerTable(clientChangeSet, lastRevisions = {}) {
|
|
@@ -3389,104 +3440,108 @@
|
|
|
3389
3440
|
return lastRevisions;
|
|
3390
3441
|
}
|
|
3391
3442
|
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
if (
|
|
3402
|
-
|
|
3443
|
+
function bulkUpdate(table, keys, changeSpecs) {
|
|
3444
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3445
|
+
const objs = yield table.bulkGet(keys);
|
|
3446
|
+
const resultKeys = [];
|
|
3447
|
+
const resultObjs = [];
|
|
3448
|
+
keys.forEach((key, idx) => {
|
|
3449
|
+
const obj = objs[idx];
|
|
3450
|
+
if (obj) {
|
|
3451
|
+
for (const [keyPath, value] of Object.entries(changeSpecs[idx])) {
|
|
3452
|
+
if (keyPath === table.schema.primKey.keyPath) {
|
|
3453
|
+
if (Dexie.cmp(value, key) !== 0) {
|
|
3454
|
+
throw new Error(`Cannot change primary key`);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
else {
|
|
3458
|
+
Dexie__default["default"].setByKeyPath(obj, keyPath, value);
|
|
3403
3459
|
}
|
|
3404
3460
|
}
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
}
|
|
3461
|
+
resultKeys.push(key);
|
|
3462
|
+
resultObjs.push(obj);
|
|
3408
3463
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3464
|
+
});
|
|
3465
|
+
yield (table.schema.primKey.keyPath == null
|
|
3466
|
+
? table.bulkPut(resultObjs, resultKeys)
|
|
3467
|
+
: table.bulkPut(resultObjs));
|
|
3412
3468
|
});
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
function applyServerChanges(changes, db) {
|
|
3472
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3473
|
+
console.debug('Applying server changes', changes, Dexie__default["default"].currentTransaction);
|
|
3474
|
+
for (const { table: tableName, muts } of changes) {
|
|
3475
|
+
const table = db.table(tableName);
|
|
3476
|
+
if (!table)
|
|
3477
|
+
continue; // If server sends changes on a table we don't have, ignore it.
|
|
3478
|
+
const { primaryKey } = table.core.schema;
|
|
3479
|
+
const keyDecoder = (key) => {
|
|
3480
|
+
switch (key[0]) {
|
|
3481
|
+
case '[':
|
|
3482
|
+
// Decode JSON array
|
|
3483
|
+
if (key.endsWith(']'))
|
|
3484
|
+
try {
|
|
3485
|
+
// On server, array keys are transformed to JSON string representation
|
|
3486
|
+
return JSON.parse(key);
|
|
3487
|
+
}
|
|
3488
|
+
catch (_a) { }
|
|
3489
|
+
return key;
|
|
3490
|
+
case '#':
|
|
3491
|
+
// Decode private ID (do the opposite from what's done in encodeIdsForServer())
|
|
3492
|
+
if (key.endsWith(':' + db.cloud.currentUserId)) {
|
|
3493
|
+
return key.substr(0, key.length - db.cloud.currentUserId.length - 1);
|
|
3433
3494
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
case 'update':
|
|
3482
|
-
await bulkUpdate(table, keys, mut.changeSpecs);
|
|
3483
|
-
break;
|
|
3484
|
-
case 'delete':
|
|
3485
|
-
await table.bulkDelete(keys);
|
|
3486
|
-
break;
|
|
3495
|
+
return key;
|
|
3496
|
+
default:
|
|
3497
|
+
return key;
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
for (const mut of muts) {
|
|
3501
|
+
const keys = mut.keys.map(keyDecoder);
|
|
3502
|
+
switch (mut.type) {
|
|
3503
|
+
case 'insert':
|
|
3504
|
+
if (primaryKey.outbound) {
|
|
3505
|
+
yield table.bulkAdd(mut.values, keys);
|
|
3506
|
+
}
|
|
3507
|
+
else {
|
|
3508
|
+
keys.forEach((key, i) => {
|
|
3509
|
+
// Make sure inbound keys are consistent
|
|
3510
|
+
Dexie__default["default"].setByKeyPath(mut.values[i], primaryKey.keyPath, key);
|
|
3511
|
+
});
|
|
3512
|
+
yield table.bulkAdd(mut.values);
|
|
3513
|
+
}
|
|
3514
|
+
break;
|
|
3515
|
+
case 'upsert':
|
|
3516
|
+
if (primaryKey.outbound) {
|
|
3517
|
+
yield table.bulkPut(mut.values, keys);
|
|
3518
|
+
}
|
|
3519
|
+
else {
|
|
3520
|
+
keys.forEach((key, i) => {
|
|
3521
|
+
// Make sure inbound keys are consistent
|
|
3522
|
+
Dexie__default["default"].setByKeyPath(mut.values[i], primaryKey.keyPath, key);
|
|
3523
|
+
});
|
|
3524
|
+
yield table.bulkPut(mut.values);
|
|
3525
|
+
}
|
|
3526
|
+
break;
|
|
3527
|
+
case 'modify':
|
|
3528
|
+
if (keys.length === 1) {
|
|
3529
|
+
yield table.update(keys[0], mut.changeSpec);
|
|
3530
|
+
}
|
|
3531
|
+
else {
|
|
3532
|
+
yield table.where(':id').anyOf(keys).modify(mut.changeSpec);
|
|
3533
|
+
}
|
|
3534
|
+
break;
|
|
3535
|
+
case 'update':
|
|
3536
|
+
yield bulkUpdate(table, keys, mut.changeSpecs);
|
|
3537
|
+
break;
|
|
3538
|
+
case 'delete':
|
|
3539
|
+
yield table.bulkDelete(keys);
|
|
3540
|
+
break;
|
|
3541
|
+
}
|
|
3487
3542
|
}
|
|
3488
3543
|
}
|
|
3489
|
-
}
|
|
3544
|
+
});
|
|
3490
3545
|
}
|
|
3491
3546
|
|
|
3492
3547
|
const CURRENT_SYNC_WORKER = 'currentSyncWorker';
|
|
@@ -3494,14 +3549,14 @@
|
|
|
3494
3549
|
return _sync
|
|
3495
3550
|
.apply(this, arguments)
|
|
3496
3551
|
.then(() => {
|
|
3497
|
-
if (!syncOptions
|
|
3552
|
+
if (!(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)) {
|
|
3498
3553
|
db.syncStateChangedEvent.next({
|
|
3499
3554
|
phase: 'in-sync',
|
|
3500
3555
|
});
|
|
3501
3556
|
}
|
|
3502
3557
|
})
|
|
3503
|
-
.catch(
|
|
3504
|
-
if (syncOptions
|
|
3558
|
+
.catch((error) => __awaiter$1(this, void 0, void 0, function* () {
|
|
3559
|
+
if (syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.justCheckIfNeeded)
|
|
3505
3560
|
return Promise.reject(error); // Just rethrow.
|
|
3506
3561
|
console.debug('Error from _sync', {
|
|
3507
3562
|
isOnline,
|
|
@@ -3509,23 +3564,20 @@
|
|
|
3509
3564
|
error,
|
|
3510
3565
|
});
|
|
3511
3566
|
if (isOnline &&
|
|
3512
|
-
syncOptions
|
|
3513
|
-
error
|
|
3514
|
-
/fetch/.test(error
|
|
3567
|
+
(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.retryImmediatelyOnFetchError) &&
|
|
3568
|
+
(error === null || error === void 0 ? void 0 : error.name) === 'TypeError' &&
|
|
3569
|
+
/fetch/.test(error === null || error === void 0 ? void 0 : error.message)) {
|
|
3515
3570
|
db.syncStateChangedEvent.next({
|
|
3516
3571
|
phase: 'error',
|
|
3517
3572
|
error,
|
|
3518
3573
|
});
|
|
3519
3574
|
// Retry again in 500 ms but if it fails again, don't retry.
|
|
3520
|
-
|
|
3521
|
-
return
|
|
3522
|
-
...syncOptions,
|
|
3523
|
-
retryImmediatelyOnFetchError: false,
|
|
3524
|
-
});
|
|
3575
|
+
yield new Promise((resolve) => setTimeout(resolve, 500));
|
|
3576
|
+
return yield sync(db, options, schema, Object.assign(Object.assign({}, syncOptions), { retryImmediatelyOnFetchError: false }));
|
|
3525
3577
|
}
|
|
3526
3578
|
// Make sure that no matter whether sync() explodes or not,
|
|
3527
3579
|
// always update the timestamp. Also store the error.
|
|
3528
|
-
|
|
3580
|
+
yield db.$syncState.update('syncState', {
|
|
3529
3581
|
timestamp: new Date(),
|
|
3530
3582
|
error: '' + error,
|
|
3531
3583
|
});
|
|
@@ -3534,234 +3586,239 @@
|
|
|
3534
3586
|
error,
|
|
3535
3587
|
});
|
|
3536
3588
|
return Promise.reject(error);
|
|
3537
|
-
});
|
|
3589
|
+
}));
|
|
3538
3590
|
}
|
|
3539
|
-
|
|
3591
|
+
function _sync(db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
|
|
3540
3592
|
isInitialSync: false,
|
|
3541
3593
|
}) {
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
if (doSyncify) {
|
|
3564
|
-
if (justCheckIfNeeded)
|
|
3565
|
-
return true;
|
|
3566
|
-
//console.debug('sync doSyncify is true');
|
|
3567
|
-
await db.transaction('rw', tablesToSyncify, async (tx) => {
|
|
3568
|
-
// @ts-ignore
|
|
3569
|
-
tx.idbtrans.disableChangeTracking = true;
|
|
3570
|
-
// @ts-ignore
|
|
3571
|
-
tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!
|
|
3572
|
-
await modifyLocalObjectsWithNewUserId(tablesToSyncify, currentUser, persistedSyncState?.realms);
|
|
3573
|
-
});
|
|
3574
|
-
throwIfCancelled(cancelToken);
|
|
3575
|
-
}
|
|
3576
|
-
//
|
|
3577
|
-
// List changes to sync
|
|
3578
|
-
//
|
|
3579
|
-
const [clientChangeSet, syncState, baseRevs] = await db.transaction('r', db.tables, async () => {
|
|
3580
|
-
const syncState = await db.getPersistedSyncState();
|
|
3581
|
-
const baseRevs = await db.$baseRevs.toArray();
|
|
3582
|
-
let clientChanges = await listClientChanges(mutationTables);
|
|
3594
|
+
var _a;
|
|
3595
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3596
|
+
if (!justCheckIfNeeded) {
|
|
3597
|
+
console.debug('SYNC STARTED', { isInitialSync, purpose });
|
|
3598
|
+
}
|
|
3599
|
+
if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl))
|
|
3600
|
+
throw new Error(`Internal error: sync must not be called when no databaseUrl is configured`);
|
|
3601
|
+
const { databaseUrl } = options;
|
|
3602
|
+
const currentUser = yield db.getCurrentUser(); // Keep same value across entire sync flow:
|
|
3603
|
+
const tablesToSync = currentUser.isLoggedIn ? getSyncableTables(db) : [];
|
|
3604
|
+
const mutationTables = tablesToSync.map((tbl) => db.table(getMutationTable(tbl.name)));
|
|
3605
|
+
// If this is not the initial sync,
|
|
3606
|
+
// go through tables that were previously not synced but should now be according to
|
|
3607
|
+
// logged in state and the sync table whitelist in db.cloud.options.
|
|
3608
|
+
//
|
|
3609
|
+
// Prepare for syncification by modifying locally unauthorized objects:
|
|
3610
|
+
//
|
|
3611
|
+
const persistedSyncState = yield db.getPersistedSyncState();
|
|
3612
|
+
const tablesToSyncify = !isInitialSync && currentUser.isLoggedIn
|
|
3613
|
+
? getTablesToSyncify(db, persistedSyncState)
|
|
3614
|
+
: [];
|
|
3583
3615
|
throwIfCancelled(cancelToken);
|
|
3616
|
+
const doSyncify = tablesToSyncify.length > 0;
|
|
3584
3617
|
if (doSyncify) {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3618
|
+
if (justCheckIfNeeded)
|
|
3619
|
+
return true;
|
|
3620
|
+
//console.debug('sync doSyncify is true');
|
|
3621
|
+
yield db.transaction('rw', tablesToSyncify, (tx) => __awaiter$1(this, void 0, void 0, function* () {
|
|
3622
|
+
// @ts-ignore
|
|
3623
|
+
tx.idbtrans.disableChangeTracking = true;
|
|
3624
|
+
// @ts-ignore
|
|
3625
|
+
tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!
|
|
3626
|
+
yield modifyLocalObjectsWithNewUserId(tablesToSyncify, currentUser, persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.realms);
|
|
3627
|
+
}));
|
|
3590
3628
|
throwIfCancelled(cancelToken);
|
|
3591
|
-
clientChanges = clientChanges.concat(syncificationInserts);
|
|
3592
|
-
return [clientChanges, syncState, baseRevs];
|
|
3593
|
-
}
|
|
3594
|
-
return [clientChanges, syncState, baseRevs];
|
|
3595
|
-
});
|
|
3596
|
-
const syncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
|
|
3597
|
-
if (justCheckIfNeeded) {
|
|
3598
|
-
console.debug('Sync is needed:', syncIsNeeded);
|
|
3599
|
-
return syncIsNeeded;
|
|
3600
|
-
}
|
|
3601
|
-
if (purpose === 'push' && !syncIsNeeded) {
|
|
3602
|
-
// The purpose of this request was to push changes
|
|
3603
|
-
return false;
|
|
3604
|
-
}
|
|
3605
|
-
const latestRevisions = getLatestRevisionsPerTable(clientChangeSet, syncState?.latestRevisions);
|
|
3606
|
-
const clientIdentity = syncState?.clientIdentity || randomString$1(16);
|
|
3607
|
-
//
|
|
3608
|
-
// Push changes to server
|
|
3609
|
-
//
|
|
3610
|
-
throwIfCancelled(cancelToken);
|
|
3611
|
-
const res = await syncWithServer(clientChangeSet, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
|
|
3612
|
-
console.debug('Sync response', res);
|
|
3613
|
-
//
|
|
3614
|
-
// Apply changes locally and clear old change entries:
|
|
3615
|
-
//
|
|
3616
|
-
const done = await db.transaction('rw', db.tables, async (tx) => {
|
|
3617
|
-
// @ts-ignore
|
|
3618
|
-
tx.idbtrans.disableChangeTracking = true;
|
|
3619
|
-
// @ts-ignore
|
|
3620
|
-
tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!
|
|
3621
|
-
// Update db.cloud.schema from server response.
|
|
3622
|
-
// Local schema MAY include a subset of tables, so do not force all tables into local schema.
|
|
3623
|
-
for (const tableName of Object.keys(schema)) {
|
|
3624
|
-
if (res.schema[tableName]) {
|
|
3625
|
-
// Write directly into configured schema. This code can only be executed alone.
|
|
3626
|
-
schema[tableName] = res.schema[tableName];
|
|
3627
|
-
}
|
|
3628
3629
|
}
|
|
3629
|
-
await db.$syncState.put(schema, 'schema');
|
|
3630
|
-
// List mutations that happened during our exchange with the server:
|
|
3631
|
-
const addedClientChanges = await listClientChanges(mutationTables, db, {
|
|
3632
|
-
since: latestRevisions,
|
|
3633
|
-
});
|
|
3634
3630
|
//
|
|
3635
|
-
//
|
|
3636
|
-
// (but keep changes that haven't reached server yet)
|
|
3631
|
+
// List changes to sync
|
|
3637
3632
|
//
|
|
3638
|
-
|
|
3639
|
-
const
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
await Promise.all([
|
|
3653
|
-
mutTable.where('rev').belowOrEqual(latestRev).delete(),
|
|
3654
|
-
db.$baseRevs
|
|
3655
|
-
.where(':id')
|
|
3656
|
-
.between([tableName, -Infinity], [tableName, latestRev + 1], true, true)
|
|
3657
|
-
.reverse()
|
|
3658
|
-
.offset(1) // Keep one entry (the one mapping muts that came during fetch --> previous server revision)
|
|
3659
|
-
.delete(),
|
|
3660
|
-
]);
|
|
3633
|
+
const [clientChangeSet, syncState, baseRevs] = yield db.transaction('r', db.tables, () => __awaiter$1(this, void 0, void 0, function* () {
|
|
3634
|
+
const syncState = yield db.getPersistedSyncState();
|
|
3635
|
+
const baseRevs = yield db.$baseRevs.toArray();
|
|
3636
|
+
let clientChanges = yield listClientChanges(mutationTables);
|
|
3637
|
+
throwIfCancelled(cancelToken);
|
|
3638
|
+
if (doSyncify) {
|
|
3639
|
+
const alreadySyncedRealms = [
|
|
3640
|
+
...((persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.realms) || []),
|
|
3641
|
+
...((persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.inviteRealms) || []),
|
|
3642
|
+
];
|
|
3643
|
+
const syncificationInserts = yield listSyncifiedChanges(tablesToSyncify, currentUser, schema, alreadySyncedRealms);
|
|
3644
|
+
throwIfCancelled(cancelToken);
|
|
3645
|
+
clientChanges = clientChanges.concat(syncificationInserts);
|
|
3646
|
+
return [clientChanges, syncState, baseRevs];
|
|
3661
3647
|
}
|
|
3662
|
-
|
|
3663
|
-
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
const syncState = await db.getPersistedSyncState();
|
|
3676
|
-
//
|
|
3677
|
-
// Delete objects from removed realms
|
|
3678
|
-
//
|
|
3679
|
-
await deleteObjectsFromRemovedRealms(db, res, syncState);
|
|
3680
|
-
//
|
|
3681
|
-
// Update syncState
|
|
3682
|
-
//
|
|
3683
|
-
const newSyncState = syncState || {
|
|
3684
|
-
syncedTables: [],
|
|
3685
|
-
latestRevisions: {},
|
|
3686
|
-
realms: [],
|
|
3687
|
-
inviteRealms: [],
|
|
3688
|
-
clientIdentity,
|
|
3689
|
-
};
|
|
3690
|
-
newSyncState.syncedTables = tablesToSync
|
|
3691
|
-
.map((tbl) => tbl.name)
|
|
3692
|
-
.concat(tablesToSyncify.map((tbl) => tbl.name));
|
|
3693
|
-
newSyncState.latestRevisions = latestRevisions;
|
|
3694
|
-
newSyncState.remoteDbId = res.dbId;
|
|
3695
|
-
newSyncState.initiallySynced = true;
|
|
3696
|
-
newSyncState.realms = res.realms;
|
|
3697
|
-
newSyncState.inviteRealms = res.inviteRealms;
|
|
3698
|
-
newSyncState.serverRevision = res.serverRevision;
|
|
3699
|
-
newSyncState.timestamp = new Date();
|
|
3700
|
-
delete newSyncState.error;
|
|
3701
|
-
const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
|
|
3648
|
+
return [clientChanges, syncState, baseRevs];
|
|
3649
|
+
}));
|
|
3650
|
+
const syncIsNeeded = clientChangeSet.some((set) => set.muts.some((mut) => mut.keys.length > 0));
|
|
3651
|
+
if (justCheckIfNeeded) {
|
|
3652
|
+
console.debug('Sync is needed:', syncIsNeeded);
|
|
3653
|
+
return syncIsNeeded;
|
|
3654
|
+
}
|
|
3655
|
+
if (purpose === 'push' && !syncIsNeeded) {
|
|
3656
|
+
// The purpose of this request was to push changes
|
|
3657
|
+
return false;
|
|
3658
|
+
}
|
|
3659
|
+
const latestRevisions = getLatestRevisionsPerTable(clientChangeSet, syncState === null || syncState === void 0 ? void 0 : syncState.latestRevisions);
|
|
3660
|
+
const clientIdentity = (syncState === null || syncState === void 0 ? void 0 : syncState.clientIdentity) || randomString$1(16);
|
|
3702
3661
|
//
|
|
3703
|
-
//
|
|
3662
|
+
// Push changes to server
|
|
3704
3663
|
//
|
|
3705
|
-
|
|
3664
|
+
throwIfCancelled(cancelToken);
|
|
3665
|
+
const res = yield syncWithServer(clientChangeSet, syncState, baseRevs, db, databaseUrl, schema, clientIdentity, currentUser);
|
|
3666
|
+
console.debug('Sync response', res);
|
|
3706
3667
|
//
|
|
3707
|
-
//
|
|
3668
|
+
// Apply changes locally and clear old change entries:
|
|
3708
3669
|
//
|
|
3709
|
-
db
|
|
3710
|
-
|
|
3670
|
+
const done = yield db.transaction('rw', db.tables, (tx) => __awaiter$1(this, void 0, void 0, function* () {
|
|
3671
|
+
// @ts-ignore
|
|
3672
|
+
tx.idbtrans.disableChangeTracking = true;
|
|
3673
|
+
// @ts-ignore
|
|
3674
|
+
tx.idbtrans.disableAccessControl = true; // TODO: Take care of this flag in access control middleware!
|
|
3675
|
+
// Update db.cloud.schema from server response.
|
|
3676
|
+
// Local schema MAY include a subset of tables, so do not force all tables into local schema.
|
|
3677
|
+
for (const tableName of Object.keys(schema)) {
|
|
3678
|
+
if (res.schema[tableName]) {
|
|
3679
|
+
// Write directly into configured schema. This code can only be executed alone.
|
|
3680
|
+
schema[tableName] = res.schema[tableName];
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
yield db.$syncState.put(schema, 'schema');
|
|
3684
|
+
// List mutations that happened during our exchange with the server:
|
|
3685
|
+
const addedClientChanges = yield listClientChanges(mutationTables, db, {
|
|
3686
|
+
since: latestRevisions,
|
|
3687
|
+
});
|
|
3688
|
+
//
|
|
3689
|
+
// Delete changes now as server has return success
|
|
3690
|
+
// (but keep changes that haven't reached server yet)
|
|
3691
|
+
//
|
|
3692
|
+
for (const mutTable of mutationTables) {
|
|
3693
|
+
const tableName = getTableFromMutationTable(mutTable.name);
|
|
3694
|
+
if (!addedClientChanges.some((ch) => ch.table === tableName && ch.muts.length > 0)) {
|
|
3695
|
+
// No added mutations for this table during the time we sent changes
|
|
3696
|
+
// to the server.
|
|
3697
|
+
// It is therefore safe to clear all changes (which is faster than
|
|
3698
|
+
// deleting a range)
|
|
3699
|
+
yield Promise.all([
|
|
3700
|
+
mutTable.clear(),
|
|
3701
|
+
db.$baseRevs.where({ tableName }).delete(),
|
|
3702
|
+
]);
|
|
3703
|
+
}
|
|
3704
|
+
else if (latestRevisions[tableName]) {
|
|
3705
|
+
const latestRev = latestRevisions[tableName] || 0;
|
|
3706
|
+
yield Promise.all([
|
|
3707
|
+
mutTable.where('rev').belowOrEqual(latestRev).delete(),
|
|
3708
|
+
db.$baseRevs
|
|
3709
|
+
.where(':id')
|
|
3710
|
+
.between([tableName, -Infinity], [tableName, latestRev + 1], true, true)
|
|
3711
|
+
.reverse()
|
|
3712
|
+
.offset(1) // Keep one entry (the one mapping muts that came during fetch --> previous server revision)
|
|
3713
|
+
.delete(),
|
|
3714
|
+
]);
|
|
3715
|
+
}
|
|
3716
|
+
else ;
|
|
3717
|
+
}
|
|
3718
|
+
// Update latestRevisions object according to additional changes:
|
|
3719
|
+
getLatestRevisionsPerTable(addedClientChanges, latestRevisions);
|
|
3720
|
+
// Update/add new entries into baseRevs map.
|
|
3721
|
+
// * On tables without mutations since last serverRevision,
|
|
3722
|
+
// this will update existing entry.
|
|
3723
|
+
// * On tables where mutations have been recorded since last
|
|
3724
|
+
// serverRevision, this will create a new entry.
|
|
3725
|
+
// The purpose of this operation is to mark a start revision (per table)
|
|
3726
|
+
// so that all client-mutations that come after this, will be mapped to current
|
|
3727
|
+
// server revision.
|
|
3728
|
+
yield updateBaseRevs(db, schema, latestRevisions, res.serverRevision);
|
|
3729
|
+
const syncState = yield db.getPersistedSyncState();
|
|
3730
|
+
//
|
|
3731
|
+
// Delete objects from removed realms
|
|
3732
|
+
//
|
|
3733
|
+
yield deleteObjectsFromRemovedRealms(db, res, syncState);
|
|
3734
|
+
//
|
|
3735
|
+
// Update syncState
|
|
3736
|
+
//
|
|
3737
|
+
const newSyncState = syncState || {
|
|
3738
|
+
syncedTables: [],
|
|
3739
|
+
latestRevisions: {},
|
|
3740
|
+
realms: [],
|
|
3741
|
+
inviteRealms: [],
|
|
3742
|
+
clientIdentity,
|
|
3743
|
+
};
|
|
3744
|
+
newSyncState.syncedTables = tablesToSync
|
|
3745
|
+
.map((tbl) => tbl.name)
|
|
3746
|
+
.concat(tablesToSyncify.map((tbl) => tbl.name));
|
|
3747
|
+
newSyncState.latestRevisions = latestRevisions;
|
|
3748
|
+
newSyncState.remoteDbId = res.dbId;
|
|
3749
|
+
newSyncState.initiallySynced = true;
|
|
3750
|
+
newSyncState.realms = res.realms;
|
|
3751
|
+
newSyncState.inviteRealms = res.inviteRealms;
|
|
3752
|
+
newSyncState.serverRevision = res.serverRevision;
|
|
3753
|
+
newSyncState.timestamp = new Date();
|
|
3754
|
+
delete newSyncState.error;
|
|
3755
|
+
const filteredChanges = filterServerChangesThroughAddedClientChanges(res.changes, addedClientChanges);
|
|
3756
|
+
//
|
|
3757
|
+
// apply server changes
|
|
3758
|
+
//
|
|
3759
|
+
yield applyServerChanges(filteredChanges, db);
|
|
3760
|
+
//
|
|
3761
|
+
// Update syncState
|
|
3762
|
+
//
|
|
3763
|
+
db.$syncState.put(newSyncState, 'syncState');
|
|
3764
|
+
return addedClientChanges.length === 0;
|
|
3765
|
+
}));
|
|
3766
|
+
if (!done) {
|
|
3767
|
+
console.debug('MORE SYNC NEEDED. Go for it again!');
|
|
3768
|
+
return yield _sync(db, options, schema, { isInitialSync, cancelToken });
|
|
3769
|
+
}
|
|
3770
|
+
console.debug('SYNC DONE', { isInitialSync });
|
|
3771
|
+
return false; // Not needed anymore
|
|
3711
3772
|
});
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3773
|
+
}
|
|
3774
|
+
function deleteObjectsFromRemovedRealms(db, res, prevState) {
|
|
3775
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3776
|
+
const deletedRealms = new Set();
|
|
3777
|
+
const rejectedRealms = new Set();
|
|
3778
|
+
const previousRealmSet = prevState ? prevState.realms : [];
|
|
3779
|
+
const previousInviteRealmSet = prevState ? prevState.inviteRealms : [];
|
|
3780
|
+
const updatedRealmSet = new Set(res.realms);
|
|
3781
|
+
const updatedTotalRealmSet = new Set(res.realms.concat(res.inviteRealms));
|
|
3782
|
+
for (const realmId of previousRealmSet) {
|
|
3783
|
+
if (!updatedRealmSet.has(realmId)) {
|
|
3784
|
+
rejectedRealms.add(realmId);
|
|
3785
|
+
if (!updatedTotalRealmSet.has(realmId)) {
|
|
3786
|
+
deletedRealms.add(realmId);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
for (const realmId of previousInviteRealmSet.concat(previousRealmSet)) {
|
|
3729
3791
|
if (!updatedTotalRealmSet.has(realmId)) {
|
|
3730
3792
|
deletedRealms.add(realmId);
|
|
3731
3793
|
}
|
|
3732
3794
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
.where(
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
// No index to use:
|
|
3758
|
-
//console.debug(`REMOVAL: deleting all ${table.name} where realmId is any of `, JSON.stringify([...realmsToDelete]), realmsToDelete.size);
|
|
3759
|
-
await table
|
|
3760
|
-
.filter((obj) => !!obj?.realmId && realmsToDelete.has(obj.realmId))
|
|
3761
|
-
.delete();
|
|
3795
|
+
if (deletedRealms.size > 0 || rejectedRealms.size > 0) {
|
|
3796
|
+
const tables = getSyncableTables(db);
|
|
3797
|
+
for (const table of tables) {
|
|
3798
|
+
let realmsToDelete = ['realms', 'members', 'roles'].includes(table.name)
|
|
3799
|
+
? deletedRealms // These tables should spare rejected ones.
|
|
3800
|
+
: rejectedRealms; // All other tables shoudl delete rejected+deleted ones
|
|
3801
|
+
if (realmsToDelete.size === 0)
|
|
3802
|
+
continue;
|
|
3803
|
+
if (table.schema.indexes.some((idx) => idx.keyPath === 'realmId' ||
|
|
3804
|
+
(Array.isArray(idx.keyPath) && idx.keyPath[0] === 'realmId'))) {
|
|
3805
|
+
// There's an index to use:
|
|
3806
|
+
//console.debug(`REMOVAL: deleting all ${table.name} where realmId anyOf `, JSON.stringify([...realmsToDelete]));
|
|
3807
|
+
yield table
|
|
3808
|
+
.where('realmId')
|
|
3809
|
+
.anyOf([...realmsToDelete])
|
|
3810
|
+
.delete();
|
|
3811
|
+
}
|
|
3812
|
+
else {
|
|
3813
|
+
// No index to use:
|
|
3814
|
+
//console.debug(`REMOVAL: deleting all ${table.name} where realmId is any of `, JSON.stringify([...realmsToDelete]), realmsToDelete.size);
|
|
3815
|
+
yield table
|
|
3816
|
+
.filter((obj) => !!(obj === null || obj === void 0 ? void 0 : obj.realmId) && realmsToDelete.has(obj.realmId))
|
|
3817
|
+
.delete();
|
|
3818
|
+
}
|
|
3762
3819
|
}
|
|
3763
3820
|
}
|
|
3764
|
-
}
|
|
3821
|
+
});
|
|
3765
3822
|
}
|
|
3766
3823
|
function filterServerChangesThroughAddedClientChanges(serverChanges, addedClientChanges) {
|
|
3767
3824
|
const changes = {};
|
|
@@ -3779,7 +3836,7 @@
|
|
|
3779
3836
|
let isWorking = false;
|
|
3780
3837
|
let loopWarning = 0;
|
|
3781
3838
|
let loopDetection = [0, 0, 0, 0, 0, 0, 0, 0, 0, Date.now()];
|
|
3782
|
-
event.subscribe(
|
|
3839
|
+
event.subscribe(() => __awaiter$1(this, void 0, void 0, function* () {
|
|
3783
3840
|
if (isWorking)
|
|
3784
3841
|
return;
|
|
3785
3842
|
if (queue.length > 0) {
|
|
@@ -3788,7 +3845,7 @@
|
|
|
3788
3845
|
loopDetection.push(Date.now());
|
|
3789
3846
|
readyToServe.next(false);
|
|
3790
3847
|
try {
|
|
3791
|
-
|
|
3848
|
+
yield consumeQueue();
|
|
3792
3849
|
}
|
|
3793
3850
|
finally {
|
|
3794
3851
|
if (loopDetection[loopDetection.length - 1] - loopDetection[0] <
|
|
@@ -3798,170 +3855,173 @@
|
|
|
3798
3855
|
// Last time we did this, we ended up here too. Wait for a minute.
|
|
3799
3856
|
console.warn(`Slowing down websocket loop for one minute`);
|
|
3800
3857
|
loopWarning = Date.now() + 60000;
|
|
3801
|
-
|
|
3858
|
+
yield new Promise((resolve) => setTimeout(resolve, 60000));
|
|
3802
3859
|
}
|
|
3803
3860
|
else {
|
|
3804
3861
|
// This is a one-time event. Just pause 10 seconds.
|
|
3805
3862
|
console.warn(`Slowing down websocket loop for 10 seconds`);
|
|
3806
3863
|
loopWarning = Date.now() + 10000;
|
|
3807
|
-
|
|
3864
|
+
yield new Promise((resolve) => setTimeout(resolve, 10000));
|
|
3808
3865
|
}
|
|
3809
3866
|
}
|
|
3810
3867
|
isWorking = false;
|
|
3811
3868
|
readyToServe.next(true);
|
|
3812
3869
|
}
|
|
3813
3870
|
}
|
|
3814
|
-
});
|
|
3871
|
+
}));
|
|
3815
3872
|
function enqueue(msg) {
|
|
3816
3873
|
queue.push(msg);
|
|
3817
3874
|
event.next(null);
|
|
3818
3875
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
.
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
break;
|
|
3854
|
-
case 'realm-accepted':
|
|
3855
|
-
//if (!persistedSyncState?.realms?.includes(msg.realm)) {
|
|
3856
|
-
triggerSync(db, 'pull');
|
|
3857
|
-
//}
|
|
3858
|
-
break;
|
|
3859
|
-
case 'realm-removed':
|
|
3860
|
-
//if (
|
|
3861
|
-
persistedSyncState?.realms?.includes(msg.realm) ||
|
|
3862
|
-
persistedSyncState?.inviteRealms?.includes(msg.realm);
|
|
3863
|
-
//) {
|
|
3864
|
-
triggerSync(db, 'pull');
|
|
3865
|
-
//}
|
|
3866
|
-
break;
|
|
3867
|
-
case 'realms-changed':
|
|
3868
|
-
triggerSync(db, 'pull');
|
|
3869
|
-
break;
|
|
3870
|
-
case 'changes':
|
|
3871
|
-
console.debug('changes');
|
|
3872
|
-
if (db.cloud.syncState.value?.phase === 'error') {
|
|
3876
|
+
function consumeQueue() {
|
|
3877
|
+
var _a, _b, _c;
|
|
3878
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
3879
|
+
while (queue.length > 0) {
|
|
3880
|
+
const msg = queue.shift();
|
|
3881
|
+
try {
|
|
3882
|
+
// If the sync worker or service worker is syncing, wait 'til thei're done.
|
|
3883
|
+
// It's no need to have two channels at the same time - even though it wouldnt
|
|
3884
|
+
// be a problem - this is an optimization.
|
|
3885
|
+
yield db.cloud.syncState
|
|
3886
|
+
.pipe(filter(({ phase }) => phase === 'in-sync' || phase === 'error'), take(1))
|
|
3887
|
+
.toPromise();
|
|
3888
|
+
console.debug('processing msg', msg);
|
|
3889
|
+
const persistedSyncState = db.cloud.persistedSyncState.value;
|
|
3890
|
+
//syncState.
|
|
3891
|
+
if (!msg)
|
|
3892
|
+
continue;
|
|
3893
|
+
switch (msg.type) {
|
|
3894
|
+
case 'token-expired':
|
|
3895
|
+
console.debug('WebSocket observable: Token expired. Refreshing token...');
|
|
3896
|
+
const user = db.cloud.currentUser.value;
|
|
3897
|
+
// Refresh access token
|
|
3898
|
+
const refreshedLogin = yield refreshAccessToken(db.cloud.options.databaseUrl, user);
|
|
3899
|
+
// Persist updated access token
|
|
3900
|
+
yield db.table('$logins').update(user.userId, {
|
|
3901
|
+
accessToken: refreshedLogin.accessToken,
|
|
3902
|
+
accessTokenExpiration: refreshedLogin.accessTokenExpiration,
|
|
3903
|
+
});
|
|
3904
|
+
// Updating $logins will trigger emission of db.cloud.currentUser observable, which
|
|
3905
|
+
// in turn will lead to that connectWebSocket.ts will reconnect the socket with the
|
|
3906
|
+
// new token. So we don't need to do anything more here.
|
|
3907
|
+
break;
|
|
3908
|
+
case 'realm-added':
|
|
3909
|
+
//if (!persistedSyncState?.realms?.includes(msg.realm) && !persistedSyncState?.inviteRealms?.includes(msg.realm)) {
|
|
3873
3910
|
triggerSync(db, 'pull');
|
|
3911
|
+
//}
|
|
3874
3912
|
break;
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3913
|
+
case 'realm-accepted':
|
|
3914
|
+
//if (!persistedSyncState?.realms?.includes(msg.realm)) {
|
|
3915
|
+
triggerSync(db, 'pull');
|
|
3916
|
+
//}
|
|
3917
|
+
break;
|
|
3918
|
+
case 'realm-removed':
|
|
3919
|
+
//if (
|
|
3920
|
+
((_a = persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.realms) === null || _a === void 0 ? void 0 : _a.includes(msg.realm)) ||
|
|
3921
|
+
((_b = persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.inviteRealms) === null || _b === void 0 ? void 0 : _b.includes(msg.realm));
|
|
3922
|
+
//) {
|
|
3923
|
+
triggerSync(db, 'pull');
|
|
3924
|
+
//}
|
|
3925
|
+
break;
|
|
3926
|
+
case 'realms-changed':
|
|
3927
|
+
triggerSync(db, 'pull');
|
|
3928
|
+
break;
|
|
3929
|
+
case 'changes':
|
|
3930
|
+
console.debug('changes');
|
|
3931
|
+
if (((_c = db.cloud.syncState.value) === null || _c === void 0 ? void 0 : _c.phase) === 'error') {
|
|
3932
|
+
triggerSync(db, 'pull');
|
|
3933
|
+
break;
|
|
3894
3934
|
}
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
//
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3935
|
+
yield db.transaction('rw', db.dx.tables, (tx) => __awaiter$1(this, void 0, void 0, function* () {
|
|
3936
|
+
// @ts-ignore
|
|
3937
|
+
tx.idbtrans.disableChangeTracking = true;
|
|
3938
|
+
// @ts-ignore
|
|
3939
|
+
tx.idbtrans.disableAccessControl = true;
|
|
3940
|
+
const [schema, syncState, currentUser] = yield Promise.all([
|
|
3941
|
+
db.getSchema(),
|
|
3942
|
+
db.getPersistedSyncState(),
|
|
3943
|
+
db.getCurrentUser(),
|
|
3944
|
+
]);
|
|
3945
|
+
console.debug('ws message queue: in transaction');
|
|
3946
|
+
if (!syncState || !schema || !currentUser) {
|
|
3947
|
+
console.debug('required vars not present', {
|
|
3948
|
+
syncState,
|
|
3949
|
+
schema,
|
|
3950
|
+
currentUser,
|
|
3951
|
+
});
|
|
3952
|
+
return; // Initial sync must have taken place - otherwise, ignore this.
|
|
3953
|
+
}
|
|
3954
|
+
// Verify again in ACID tx that we're on same server revision.
|
|
3955
|
+
if (msg.baseRev !== syncState.serverRevision) {
|
|
3956
|
+
console.debug(`baseRev (${msg.baseRev}) differs from our serverRevision in syncState (${syncState.serverRevision})`);
|
|
3957
|
+
// Should we trigger a sync now? No. This is a normal case
|
|
3958
|
+
// when another local peer (such as the SW or a websocket channel on other tab) has
|
|
3959
|
+
// updated syncState from new server information but we are not aware yet. It would
|
|
3960
|
+
// be unnescessary to do a sync in that case. Instead, the caller of this consumeQueue()
|
|
3961
|
+
// function will do readyToServe.next(true) right after this return, which will lead
|
|
3962
|
+
// to a "ready" message being sent to server with the new accurate serverRev we have,
|
|
3963
|
+
// so that the next message indeed will be correct.
|
|
3964
|
+
if (typeof msg.baseRev === 'string' && // v2 format
|
|
3965
|
+
(typeof syncState.serverRevision === 'bigint' || // v1 format
|
|
3966
|
+
typeof syncState.serverRevision === 'object') // v1 format old browser
|
|
3967
|
+
) {
|
|
3968
|
+
// The reason for the diff seems to be that server has migrated the revision format.
|
|
3969
|
+
// Do a full sync to update revision format.
|
|
3970
|
+
// If we don't do a sync request now, we could stuck in an endless loop.
|
|
3971
|
+
triggerSync(db, 'pull');
|
|
3972
|
+
}
|
|
3973
|
+
return; // Ignore message
|
|
3974
|
+
}
|
|
3975
|
+
// Verify also that the message is based on the exact same set of realms
|
|
3976
|
+
const ourRealmSetHash = yield Dexie__default["default"].waitFor(
|
|
3977
|
+
// Keep TX in non-IDB work
|
|
3978
|
+
computeRealmSetHash(syncState));
|
|
3979
|
+
console.debug('ourRealmSetHash', ourRealmSetHash);
|
|
3980
|
+
if (ourRealmSetHash !== msg.realmSetHash) {
|
|
3981
|
+
console.debug('not same realmSetHash', msg.realmSetHash);
|
|
3912
3982
|
triggerSync(db, 'pull');
|
|
3983
|
+
// The message isn't based on the same realms.
|
|
3984
|
+
// Trigger a sync instead to resolve all things up.
|
|
3985
|
+
return;
|
|
3913
3986
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3987
|
+
// Get clientChanges
|
|
3988
|
+
let clientChanges = [];
|
|
3989
|
+
if (currentUser.isLoggedIn) {
|
|
3990
|
+
const mutationTables = getSyncableTables(db).map((tbl) => db.table(getMutationTable(tbl.name)));
|
|
3991
|
+
clientChanges = yield listClientChanges(mutationTables, db);
|
|
3992
|
+
console.debug('msg queue: client changes', clientChanges);
|
|
3993
|
+
}
|
|
3994
|
+
if (msg.changes.length > 0) {
|
|
3995
|
+
const filteredChanges = filterServerChangesThroughAddedClientChanges(msg.changes, clientChanges);
|
|
3996
|
+
//
|
|
3997
|
+
// apply server changes
|
|
3998
|
+
//
|
|
3999
|
+
console.debug('applying filtered server changes', filteredChanges);
|
|
4000
|
+
yield applyServerChanges(filteredChanges, db);
|
|
4001
|
+
}
|
|
4002
|
+
// Update latest revisions per table in case there are unsynced changes
|
|
4003
|
+
// This can be a real case in future when we allow non-eagery sync.
|
|
4004
|
+
// And it can actually be realistic now also, but very rare.
|
|
4005
|
+
syncState.latestRevisions = getLatestRevisionsPerTable(clientChanges, syncState.latestRevisions);
|
|
4006
|
+
syncState.serverRevision = msg.newRev;
|
|
4007
|
+
// Update base revs
|
|
4008
|
+
console.debug('Updating baseRefs', syncState.latestRevisions);
|
|
4009
|
+
yield updateBaseRevs(db, schema, syncState.latestRevisions, msg.newRev);
|
|
3937
4010
|
//
|
|
3938
|
-
//
|
|
4011
|
+
// Update syncState
|
|
3939
4012
|
//
|
|
3940
|
-
console.debug('
|
|
3941
|
-
|
|
3942
|
-
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
console.debug('Updating baseRefs', syncState.latestRevisions);
|
|
3950
|
-
await updateBaseRevs(db, schema, syncState.latestRevisions, msg.newRev);
|
|
3951
|
-
//
|
|
3952
|
-
// Update syncState
|
|
3953
|
-
//
|
|
3954
|
-
console.debug('Updating syncState', syncState);
|
|
3955
|
-
await db.$syncState.put(syncState, 'syncState');
|
|
3956
|
-
});
|
|
3957
|
-
console.debug('msg queue: done with rw transaction');
|
|
3958
|
-
break;
|
|
4013
|
+
console.debug('Updating syncState', syncState);
|
|
4014
|
+
yield db.$syncState.put(syncState, 'syncState');
|
|
4015
|
+
}));
|
|
4016
|
+
console.debug('msg queue: done with rw transaction');
|
|
4017
|
+
break;
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
catch (error) {
|
|
4021
|
+
console.error(`Error in msg queue`, error);
|
|
3959
4022
|
}
|
|
3960
4023
|
}
|
|
3961
|
-
|
|
3962
|
-
console.error(`Error in msg queue`, error);
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
4024
|
+
});
|
|
3965
4025
|
}
|
|
3966
4026
|
return {
|
|
3967
4027
|
enqueue,
|
|
@@ -4106,9 +4166,10 @@
|
|
|
4106
4166
|
return toString.call(o).slice(8, -1);
|
|
4107
4167
|
}
|
|
4108
4168
|
function getEffectiveKeys(primaryKey, req) {
|
|
4169
|
+
var _a;
|
|
4109
4170
|
if (req.type === 'delete')
|
|
4110
4171
|
return req.keys;
|
|
4111
|
-
return req.keys
|
|
4172
|
+
return ((_a = req.keys) === null || _a === void 0 ? void 0 : _a.slice()) || req.values.map(primaryKey.extractKey);
|
|
4112
4173
|
}
|
|
4113
4174
|
function applyToUpperBitFix(orig, bits) {
|
|
4114
4175
|
return ((bits & 1 ? orig[0].toUpperCase() : orig[0].toLowerCase()) +
|
|
@@ -4199,9 +4260,7 @@
|
|
|
4199
4260
|
name: 'idGenerationMiddleware',
|
|
4200
4261
|
level: 1,
|
|
4201
4262
|
create: (core) => {
|
|
4202
|
-
return {
|
|
4203
|
-
...core,
|
|
4204
|
-
table: (tableName) => {
|
|
4263
|
+
return Object.assign(Object.assign({}, core), { table: (tableName) => {
|
|
4205
4264
|
const table = core.table(tableName);
|
|
4206
4265
|
function generateOrVerifyAtKeys(req, idPrefix) {
|
|
4207
4266
|
let valueClones = null;
|
|
@@ -4227,24 +4286,19 @@
|
|
|
4227
4286
|
`If you want to generate IDs programmatically, remove '@' from the schema to get rid of this constraint. Dexie Cloud supports custom IDs as long as they are random and globally unique.`);
|
|
4228
4287
|
}
|
|
4229
4288
|
});
|
|
4230
|
-
return table.mutate({
|
|
4231
|
-
...req,
|
|
4232
|
-
keys,
|
|
4233
|
-
values: valueClones || req.values,
|
|
4234
|
-
});
|
|
4289
|
+
return table.mutate(Object.assign(Object.assign({}, req), { keys, values: valueClones || req.values }));
|
|
4235
4290
|
}
|
|
4236
|
-
return {
|
|
4237
|
-
|
|
4238
|
-
mutate: (req) => {
|
|
4291
|
+
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
4292
|
+
var _a, _b;
|
|
4239
4293
|
// @ts-ignore
|
|
4240
4294
|
if (req.trans.disableChangeTracking) {
|
|
4241
4295
|
// Disable ID policy checks and ID generation
|
|
4242
4296
|
return table.mutate(req);
|
|
4243
4297
|
}
|
|
4244
4298
|
if (req.type === 'add' || req.type === 'put') {
|
|
4245
|
-
const cloudTableSchema = db.cloud.schema
|
|
4246
|
-
if (!cloudTableSchema
|
|
4247
|
-
if (cloudTableSchema
|
|
4299
|
+
const cloudTableSchema = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[tableName];
|
|
4300
|
+
if (!(cloudTableSchema === null || cloudTableSchema === void 0 ? void 0 : cloudTableSchema.generatedGlobalId)) {
|
|
4301
|
+
if (cloudTableSchema === null || cloudTableSchema === void 0 ? void 0 : cloudTableSchema.markedForSync) {
|
|
4248
4302
|
// Just make sure primary key is of a supported type:
|
|
4249
4303
|
const keys = getEffectiveKeys(table.schema.primaryKey, req);
|
|
4250
4304
|
keys.forEach((key, idx) => {
|
|
@@ -4258,7 +4312,7 @@
|
|
|
4258
4312
|
}
|
|
4259
4313
|
}
|
|
4260
4314
|
else {
|
|
4261
|
-
if (db.cloud.options
|
|
4315
|
+
if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !db.initiallySynced) {
|
|
4262
4316
|
// A database URL is configured but no initial sync has been performed.
|
|
4263
4317
|
const keys = getEffectiveKeys(table.schema.primaryKey, req);
|
|
4264
4318
|
// Check if the operation would yield any INSERT. If so, complain! We never want wrong ID prefixes stored.
|
|
@@ -4279,10 +4333,8 @@
|
|
|
4279
4333
|
}
|
|
4280
4334
|
}
|
|
4281
4335
|
return table.mutate(req);
|
|
4282
|
-
}
|
|
4283
|
-
|
|
4284
|
-
},
|
|
4285
|
-
};
|
|
4336
|
+
} });
|
|
4337
|
+
} });
|
|
4286
4338
|
},
|
|
4287
4339
|
};
|
|
4288
4340
|
}
|
|
@@ -4293,19 +4345,16 @@
|
|
|
4293
4345
|
name: 'implicitPropSetterMiddleware',
|
|
4294
4346
|
level: 1,
|
|
4295
4347
|
create: (core) => {
|
|
4296
|
-
return {
|
|
4297
|
-
...core,
|
|
4298
|
-
table: (tableName) => {
|
|
4348
|
+
return Object.assign(Object.assign({}, core), { table: (tableName) => {
|
|
4299
4349
|
const table = core.table(tableName);
|
|
4300
|
-
return {
|
|
4301
|
-
|
|
4302
|
-
mutate: (req) => {
|
|
4350
|
+
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
4351
|
+
var _a, _b, _c, _d;
|
|
4303
4352
|
// @ts-ignore
|
|
4304
4353
|
if (req.trans.disableChangeTracking) {
|
|
4305
4354
|
return table.mutate(req);
|
|
4306
4355
|
}
|
|
4307
4356
|
const trans = req.trans;
|
|
4308
|
-
if (db.cloud.schema
|
|
4357
|
+
if ((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[tableName]) === null || _b === void 0 ? void 0 : _b.markedForSync) {
|
|
4309
4358
|
if (req.type === 'add' || req.type === 'put') {
|
|
4310
4359
|
// No matter if user is logged in or not, make sure "owner" and "realmId" props are set properly.
|
|
4311
4360
|
// If not logged in, this will be changed upon syncification of the tables (next sync after login),
|
|
@@ -4319,7 +4368,7 @@
|
|
|
4319
4368
|
if (!obj.realmId) {
|
|
4320
4369
|
obj.realmId = trans.currentUser.userId;
|
|
4321
4370
|
}
|
|
4322
|
-
const key = table.schema.primaryKey.extractKey
|
|
4371
|
+
const key = (_d = (_c = table.schema.primaryKey).extractKey) === null || _d === void 0 ? void 0 : _d.call(_c, obj);
|
|
4323
4372
|
if (typeof key === 'string' && key[0] === '#') {
|
|
4324
4373
|
// Add $ts prop for put operations and
|
|
4325
4374
|
// disable update operations as well as consistent
|
|
@@ -4346,10 +4395,8 @@
|
|
|
4346
4395
|
}
|
|
4347
4396
|
}
|
|
4348
4397
|
return table.mutate(req);
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
|
-
},
|
|
4352
|
-
};
|
|
4398
|
+
} });
|
|
4399
|
+
} });
|
|
4353
4400
|
},
|
|
4354
4401
|
};
|
|
4355
4402
|
}
|
|
@@ -4368,15 +4415,7 @@
|
|
|
4368
4415
|
let counter$1 = 0;
|
|
4369
4416
|
function guardedTable(table) {
|
|
4370
4417
|
const prop = "$lock" + (++counter$1);
|
|
4371
|
-
return {
|
|
4372
|
-
...table,
|
|
4373
|
-
count: readLock(table.count, prop),
|
|
4374
|
-
get: readLock(table.get, prop),
|
|
4375
|
-
getMany: readLock(table.getMany, prop),
|
|
4376
|
-
openCursor: readLock(table.openCursor, prop),
|
|
4377
|
-
query: readLock(table.query, prop),
|
|
4378
|
-
mutate: writeLock(table.mutate, prop),
|
|
4379
|
-
};
|
|
4418
|
+
return Object.assign(Object.assign({}, table), { count: readLock(table.count, prop), get: readLock(table.get, prop), getMany: readLock(table.getMany, prop), openCursor: readLock(table.openCursor, prop), query: readLock(table.query, prop), mutate: writeLock(table.mutate, prop) });
|
|
4380
4419
|
}
|
|
4381
4420
|
function readLock(fn, prop) {
|
|
4382
4421
|
return function readLocker(req) {
|
|
@@ -4426,16 +4465,14 @@
|
|
|
4426
4465
|
core.table(`$${tbl.name}_mutations`)
|
|
4427
4466
|
]));
|
|
4428
4467
|
}
|
|
4429
|
-
catch {
|
|
4468
|
+
catch (_a) {
|
|
4430
4469
|
throwVersionIncrementNeeded();
|
|
4431
4470
|
}
|
|
4432
|
-
return {
|
|
4433
|
-
...core,
|
|
4434
|
-
transaction: (tables, mode) => {
|
|
4471
|
+
return Object.assign(Object.assign({}, core), { transaction: (tables, mode) => {
|
|
4435
4472
|
let tx;
|
|
4436
4473
|
if (mode === 'readwrite') {
|
|
4437
4474
|
const mutationTables = tables
|
|
4438
|
-
.filter((tbl) => db.cloud.schema
|
|
4475
|
+
.filter((tbl) => { var _a, _b; return (_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[tbl]) === null || _b === void 0 ? void 0 : _b.markedForSync; })
|
|
4439
4476
|
.map((tbl) => getMutationTable(tbl));
|
|
4440
4477
|
tx = core.transaction([...tables, ...mutationTables], mode);
|
|
4441
4478
|
}
|
|
@@ -4458,7 +4495,8 @@
|
|
|
4458
4495
|
outstandingTransactions.next(outstandingTransactions.value);
|
|
4459
4496
|
};
|
|
4460
4497
|
const txComplete = () => {
|
|
4461
|
-
|
|
4498
|
+
var _a;
|
|
4499
|
+
if (tx.mutationsAdded && ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl)) {
|
|
4462
4500
|
if (db.cloud.usingServiceWorker) {
|
|
4463
4501
|
console.debug('registering sync event');
|
|
4464
4502
|
registerSyncEvent(db, "push");
|
|
@@ -4474,8 +4512,7 @@
|
|
|
4474
4512
|
tx.addEventListener('abort', removeTransaction);
|
|
4475
4513
|
}
|
|
4476
4514
|
return tx;
|
|
4477
|
-
},
|
|
4478
|
-
table: (tableName) => {
|
|
4515
|
+
}, table: (tableName) => {
|
|
4479
4516
|
const table = core.table(tableName);
|
|
4480
4517
|
if (/^\$/.test(tableName)) {
|
|
4481
4518
|
if (tableName.endsWith('_mutations')) {
|
|
@@ -4483,20 +4520,15 @@
|
|
|
4483
4520
|
// make sure to set the mutationsAdded flag on transaction.
|
|
4484
4521
|
// This is also done in mutateAndLog() as that function talks to a
|
|
4485
4522
|
// lower level DBCore and wouldn't be catched by this code.
|
|
4486
|
-
return {
|
|
4487
|
-
...table,
|
|
4488
|
-
mutate: (req) => {
|
|
4523
|
+
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
4489
4524
|
if (req.type === 'add' || req.type === 'put') {
|
|
4490
4525
|
req.trans.mutationsAdded = true;
|
|
4491
4526
|
}
|
|
4492
4527
|
return table.mutate(req);
|
|
4493
|
-
}
|
|
4494
|
-
};
|
|
4528
|
+
} });
|
|
4495
4529
|
}
|
|
4496
4530
|
else if (tableName === '$logins') {
|
|
4497
|
-
return {
|
|
4498
|
-
...table,
|
|
4499
|
-
mutate: (req) => {
|
|
4531
|
+
return Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
4500
4532
|
//console.debug('Mutating $logins table', req);
|
|
4501
4533
|
return table
|
|
4502
4534
|
.mutate(req)
|
|
@@ -4510,8 +4542,7 @@
|
|
|
4510
4542
|
console.debug('Failed mutation $logins', err);
|
|
4511
4543
|
return Promise.reject(err);
|
|
4512
4544
|
});
|
|
4513
|
-
}
|
|
4514
|
-
};
|
|
4545
|
+
} });
|
|
4515
4546
|
}
|
|
4516
4547
|
else {
|
|
4517
4548
|
return table;
|
|
@@ -4519,17 +4550,16 @@
|
|
|
4519
4550
|
}
|
|
4520
4551
|
const { schema } = table;
|
|
4521
4552
|
const mutsTable = mutTableMap.get(tableName);
|
|
4522
|
-
return guardedTable({
|
|
4523
|
-
|
|
4524
|
-
mutate: (req) => {
|
|
4553
|
+
return guardedTable(Object.assign(Object.assign({}, table), { mutate: (req) => {
|
|
4554
|
+
var _a, _b, _c;
|
|
4525
4555
|
const trans = req.trans;
|
|
4526
4556
|
if (!trans.txid)
|
|
4527
4557
|
return table.mutate(req); // Upgrade transactions not guarded by us.
|
|
4528
4558
|
if (trans.disableChangeTracking)
|
|
4529
4559
|
return table.mutate(req);
|
|
4530
|
-
if (!db.cloud.schema
|
|
4560
|
+
if (!((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[tableName]) === null || _b === void 0 ? void 0 : _b.markedForSync))
|
|
4531
4561
|
return table.mutate(req);
|
|
4532
|
-
if (!trans.currentUser
|
|
4562
|
+
if (!((_c = trans.currentUser) === null || _c === void 0 ? void 0 : _c.isLoggedIn)) {
|
|
4533
4563
|
// Unauthorized user should not log mutations.
|
|
4534
4564
|
// Instead, after login all local data should be logged at once.
|
|
4535
4565
|
return table.mutate(req);
|
|
@@ -4552,8 +4582,7 @@
|
|
|
4552
4582
|
});
|
|
4553
4583
|
})
|
|
4554
4584
|
: mutateAndLog(req);
|
|
4555
|
-
}
|
|
4556
|
-
});
|
|
4585
|
+
} }));
|
|
4557
4586
|
function mutateAndLog(req) {
|
|
4558
4587
|
const trans = req.trans;
|
|
4559
4588
|
trans.mutationsAdded = true;
|
|
@@ -4624,18 +4653,14 @@
|
|
|
4624
4653
|
: res;
|
|
4625
4654
|
});
|
|
4626
4655
|
}
|
|
4627
|
-
}
|
|
4628
|
-
};
|
|
4656
|
+
} });
|
|
4629
4657
|
}
|
|
4630
4658
|
};
|
|
4631
4659
|
}
|
|
4632
4660
|
|
|
4633
4661
|
function overrideParseStoresSpec(origFunc, dexie) {
|
|
4634
4662
|
return function (stores, dbSchema) {
|
|
4635
|
-
const storesClone = {
|
|
4636
|
-
...DEXIE_CLOUD_SCHEMA,
|
|
4637
|
-
...stores,
|
|
4638
|
-
};
|
|
4663
|
+
const storesClone = Object.assign(Object.assign({}, DEXIE_CLOUD_SCHEMA), stores);
|
|
4639
4664
|
const cloudSchema = dexie.cloud.schema || (dexie.cloud.schema = {});
|
|
4640
4665
|
const allPrefixes = new Set();
|
|
4641
4666
|
Object.keys(storesClone).forEach(tableName => {
|
|
@@ -4667,110 +4692,15 @@
|
|
|
4667
4692
|
};
|
|
4668
4693
|
}
|
|
4669
4694
|
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
4677
|
-
async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
4678
|
-
// Start working.
|
|
4679
|
-
//
|
|
4680
|
-
// Check if someone else is working on this already.
|
|
4681
|
-
//
|
|
4682
|
-
const jobsTable = db.table(jobsTableName);
|
|
4683
|
-
async function aquireLock() {
|
|
4684
|
-
const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
|
|
4685
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4686
|
-
if (!currentWork) {
|
|
4687
|
-
// No one else is working. Let's record that we are.
|
|
4688
|
-
await jobsTable.add({
|
|
4689
|
-
nodeId: myId,
|
|
4690
|
-
started: new Date(),
|
|
4691
|
-
heartbeat: new Date()
|
|
4692
|
-
}, jobName);
|
|
4693
|
-
return true;
|
|
4694
|
-
}
|
|
4695
|
-
else if (currentWork.heartbeat.getTime() <
|
|
4696
|
-
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
4697
|
-
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!`);
|
|
4698
|
-
// Now, take over!
|
|
4699
|
-
await jobsTable.put({
|
|
4700
|
-
nodeId: myId,
|
|
4701
|
-
started: new Date(),
|
|
4702
|
-
heartbeat: new Date()
|
|
4703
|
-
}, jobName);
|
|
4704
|
-
return true;
|
|
4705
|
-
}
|
|
4706
|
-
return false;
|
|
4707
|
-
});
|
|
4708
|
-
if (gotTheLock)
|
|
4709
|
-
return true;
|
|
4710
|
-
// Someone else took the job.
|
|
4711
|
-
if (awaitRemoteJob) {
|
|
4712
|
-
try {
|
|
4713
|
-
const jobDoneObservable = rxjs.from(Dexie.liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
4714
|
-
await jobDoneObservable.toPromise();
|
|
4715
|
-
return false;
|
|
4716
|
-
}
|
|
4717
|
-
catch (err) {
|
|
4718
|
-
if (err.name !== 'TimeoutError') {
|
|
4719
|
-
throw err;
|
|
4720
|
-
}
|
|
4721
|
-
// Timeout stopped us! Try aquire the lock now.
|
|
4722
|
-
// It will likely succeed this time unless
|
|
4723
|
-
// another client took it.
|
|
4724
|
-
return await aquireLock();
|
|
4725
|
-
}
|
|
4726
|
-
}
|
|
4727
|
-
return false;
|
|
4728
|
-
}
|
|
4729
|
-
if (await aquireLock()) {
|
|
4730
|
-
// We own the lock entry and can do our job undisturbed.
|
|
4731
|
-
// We're not within a transaction, but these type of locks
|
|
4732
|
-
// spans over transactions.
|
|
4733
|
-
// Start our heart beat during the job.
|
|
4734
|
-
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
4735
|
-
const heartbeat = setInterval(() => {
|
|
4736
|
-
jobsTable.update(jobName, (job) => {
|
|
4737
|
-
if (job.nodeId === myId) {
|
|
4738
|
-
job.heartbeat = new Date();
|
|
4739
|
-
}
|
|
4740
|
-
});
|
|
4741
|
-
}, GUARDED_JOB_HEARTBEAT);
|
|
4742
|
-
try {
|
|
4743
|
-
return await job();
|
|
4744
|
-
}
|
|
4745
|
-
finally {
|
|
4746
|
-
// Stop heartbeat
|
|
4747
|
-
clearInterval(heartbeat);
|
|
4748
|
-
// Remove the persisted job state:
|
|
4749
|
-
await db.transaction('rw!', jobsTableName, async () => {
|
|
4750
|
-
const currentWork = await jobsTable.get(jobName);
|
|
4751
|
-
if (currentWork && currentWork.nodeId === myId) {
|
|
4752
|
-
jobsTable.delete(jobName);
|
|
4753
|
-
}
|
|
4754
|
-
});
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
async function performInitialSync(db, cloudOptions, cloudSchema) {
|
|
4760
|
-
console.debug("Performing initial sync");
|
|
4761
|
-
await performGuardedJob(db, 'initialSync', '$jobs', async () => {
|
|
4762
|
-
// Even though caller has already checked it,
|
|
4763
|
-
// Do check again (now within a transaction) that we really do not have a sync state:
|
|
4764
|
-
const syncState = await db.getPersistedSyncState();
|
|
4765
|
-
if (!syncState?.initiallySynced) {
|
|
4766
|
-
await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4767
|
-
}
|
|
4768
|
-
}, { awaitRemoteJob: true } // Don't return until the job is done!
|
|
4769
|
-
);
|
|
4770
|
-
console.debug("Done initial sync");
|
|
4695
|
+
function performInitialSync(db, cloudOptions, cloudSchema) {
|
|
4696
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4697
|
+
console.debug('Performing initial sync');
|
|
4698
|
+
yield sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
|
|
4699
|
+
console.debug('Done initial sync');
|
|
4700
|
+
});
|
|
4771
4701
|
}
|
|
4772
4702
|
|
|
4773
|
-
const USER_INACTIVITY_TIMEOUT =
|
|
4703
|
+
const USER_INACTIVITY_TIMEOUT = 180000; // 3 minutes
|
|
4774
4704
|
const INACTIVE_WAIT_TIME = 20000;
|
|
4775
4705
|
// This observable will be emitted to later down....
|
|
4776
4706
|
const userIsActive = new rxjs.BehaviorSubject(true);
|
|
@@ -4784,9 +4714,13 @@
|
|
|
4784
4714
|
// for just a short time.
|
|
4785
4715
|
const userIsReallyActive = new rxjs.BehaviorSubject(true);
|
|
4786
4716
|
userIsActive
|
|
4787
|
-
.pipe(switchMap((isActive) =>
|
|
4788
|
-
|
|
4789
|
-
|
|
4717
|
+
.pipe(switchMap((isActive) => {
|
|
4718
|
+
//console.debug('SyncStatus: DUBB: isActive changed to', isActive);
|
|
4719
|
+
return isActive
|
|
4720
|
+
? rxjs.of(true)
|
|
4721
|
+
: rxjs.of(false).pipe(delay(INACTIVE_WAIT_TIME))
|
|
4722
|
+
;
|
|
4723
|
+
}), distinctUntilChanged())
|
|
4790
4724
|
.subscribe(userIsReallyActive);
|
|
4791
4725
|
//
|
|
4792
4726
|
// First create some corner-stone observables to build the flow on
|
|
@@ -4801,7 +4735,7 @@
|
|
|
4801
4735
|
const documentBecomesVisible = visibilityStateIsChanged.pipe(filter(() => document.visibilityState === 'visible'));
|
|
4802
4736
|
// Any of various user-activity-related events happen:
|
|
4803
4737
|
const userDoesSomething = typeof window !== 'undefined'
|
|
4804
|
-
? rxjs.merge(documentBecomesVisible, rxjs.fromEvent(window, 'mousemove'), rxjs.fromEvent(window, 'keydown'), rxjs.fromEvent(window, 'wheel'), rxjs.fromEvent(window, 'touchmove'))
|
|
4738
|
+
? rxjs.merge(documentBecomesVisible, rxjs.fromEvent(window, 'mousedown'), rxjs.fromEvent(window, 'mousemove'), rxjs.fromEvent(window, 'keydown'), rxjs.fromEvent(window, 'wheel'), rxjs.fromEvent(window, 'touchmove'))
|
|
4805
4739
|
: rxjs.of({});
|
|
4806
4740
|
if (typeof document !== 'undefined') {
|
|
4807
4741
|
//
|
|
@@ -4852,6 +4786,7 @@
|
|
|
4852
4786
|
constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
|
|
4853
4787
|
super(() => this.teardown());
|
|
4854
4788
|
this.id = ++counter;
|
|
4789
|
+
this.reconnecting = false;
|
|
4855
4790
|
console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
|
|
4856
4791
|
this.databaseUrl = databaseUrl;
|
|
4857
4792
|
this.rev = rev;
|
|
@@ -4871,7 +4806,7 @@
|
|
|
4871
4806
|
this.disconnect();
|
|
4872
4807
|
}
|
|
4873
4808
|
disconnect() {
|
|
4874
|
-
this.webSocketStatus.next(
|
|
4809
|
+
this.webSocketStatus.next('disconnected');
|
|
4875
4810
|
if (this.pinger) {
|
|
4876
4811
|
clearInterval(this.pinger);
|
|
4877
4812
|
this.pinger = null;
|
|
@@ -4880,7 +4815,7 @@
|
|
|
4880
4815
|
try {
|
|
4881
4816
|
this.ws.close();
|
|
4882
4817
|
}
|
|
4883
|
-
catch { }
|
|
4818
|
+
catch (_a) { }
|
|
4884
4819
|
}
|
|
4885
4820
|
this.ws = null;
|
|
4886
4821
|
if (this.messageProducerSubscription) {
|
|
@@ -4889,158 +4824,180 @@
|
|
|
4889
4824
|
}
|
|
4890
4825
|
}
|
|
4891
4826
|
reconnect() {
|
|
4892
|
-
this.
|
|
4893
|
-
this.connect();
|
|
4894
|
-
}
|
|
4895
|
-
async connect() {
|
|
4896
|
-
this.webSocketStatus.next("connecting");
|
|
4897
|
-
this.lastServerActivity = new Date();
|
|
4898
|
-
if (this.pauseUntil && this.pauseUntil > new Date()) {
|
|
4899
|
-
console.debug('WS not reconnecting just yet', {
|
|
4900
|
-
id: this.id,
|
|
4901
|
-
pauseUntil: this.pauseUntil,
|
|
4902
|
-
});
|
|
4903
|
-
return;
|
|
4904
|
-
}
|
|
4905
|
-
if (this.ws) {
|
|
4906
|
-
throw new Error(`Called connect() when a connection is already open`);
|
|
4907
|
-
}
|
|
4908
|
-
if (!this.databaseUrl)
|
|
4909
|
-
throw new Error(`Cannot connect without a database URL`);
|
|
4910
|
-
if (this.closed) {
|
|
4911
|
-
return;
|
|
4912
|
-
}
|
|
4913
|
-
if (this.tokenExpiration && this.tokenExpiration < new Date()) {
|
|
4914
|
-
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
4827
|
+
if (this.reconnecting)
|
|
4915
4828
|
return;
|
|
4829
|
+
this.reconnecting = true;
|
|
4830
|
+
try {
|
|
4831
|
+
this.disconnect();
|
|
4916
4832
|
}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4833
|
+
catch (_a) { }
|
|
4834
|
+
this.connect()
|
|
4835
|
+
.catch(() => { })
|
|
4836
|
+
.then(() => (this.reconnecting = false)); // finally()
|
|
4837
|
+
}
|
|
4838
|
+
connect() {
|
|
4839
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4840
|
+
this.lastServerActivity = new Date();
|
|
4841
|
+
if (this.pauseUntil && this.pauseUntil > new Date()) {
|
|
4842
|
+
console.debug('WS not reconnecting just yet', {
|
|
4843
|
+
id: this.id,
|
|
4844
|
+
pauseUntil: this.pauseUntil,
|
|
4845
|
+
});
|
|
4921
4846
|
return;
|
|
4922
4847
|
}
|
|
4923
4848
|
if (this.ws) {
|
|
4924
|
-
|
|
4925
|
-
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
4926
|
-
setTimeout(() => {
|
|
4927
|
-
console.debug('pinger setTimeout', this.id, this.pinger ? `alive` : 'dead');
|
|
4928
|
-
if (!this.pinger)
|
|
4929
|
-
return;
|
|
4930
|
-
if (this.closed) {
|
|
4931
|
-
console.debug('pinger setTimeout', this.id, 'subscription is closed');
|
|
4932
|
-
this.teardown();
|
|
4933
|
-
return;
|
|
4934
|
-
}
|
|
4935
|
-
if (this.lastServerActivity <
|
|
4936
|
-
new Date(Date.now() - SERVER_PING_TIMEOUT)) {
|
|
4937
|
-
// Server inactive. Reconnect if user is active.
|
|
4938
|
-
console.debug('pinger: server is inactive');
|
|
4939
|
-
console.debug('pinger reconnecting');
|
|
4940
|
-
this.reconnect();
|
|
4941
|
-
}
|
|
4942
|
-
else {
|
|
4943
|
-
console.debug('pinger: server still active');
|
|
4944
|
-
}
|
|
4945
|
-
}, SERVER_PING_TIMEOUT);
|
|
4946
|
-
}
|
|
4947
|
-
catch {
|
|
4948
|
-
console.debug('pinger catch error', this.id, 'reconnecting');
|
|
4949
|
-
this.reconnect();
|
|
4950
|
-
}
|
|
4849
|
+
throw new Error(`Called connect() when a connection is already open`);
|
|
4951
4850
|
}
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
}, CLIENT_PING_INTERVAL);
|
|
4957
|
-
// The following vars are needed because we must know which callback to ack when server sends it's ack to us.
|
|
4958
|
-
const wsUrl = new URL(this.databaseUrl);
|
|
4959
|
-
wsUrl.protocol = wsUrl.protocol === 'http:' ? 'ws' : 'wss';
|
|
4960
|
-
const searchParams = new URLSearchParams();
|
|
4961
|
-
if (this.subscriber.closed)
|
|
4962
|
-
return;
|
|
4963
|
-
searchParams.set('v', "2");
|
|
4964
|
-
searchParams.set('rev', this.rev);
|
|
4965
|
-
searchParams.set('realmsHash', this.realmSetHash);
|
|
4966
|
-
searchParams.set('clientId', this.clientIdentity);
|
|
4967
|
-
if (this.token) {
|
|
4968
|
-
searchParams.set('token', this.token);
|
|
4969
|
-
}
|
|
4970
|
-
// Connect the WebSocket to given url:
|
|
4971
|
-
console.debug('dexie-cloud WebSocket create');
|
|
4972
|
-
const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));
|
|
4973
|
-
//ws.binaryType = "arraybuffer"; // For future when subscribing to actual changes.
|
|
4974
|
-
ws.onclose = (event) => {
|
|
4975
|
-
if (!this.pinger)
|
|
4851
|
+
if (!this.databaseUrl)
|
|
4852
|
+
throw new Error(`Cannot connect without a database URL`);
|
|
4853
|
+
if (this.closed) {
|
|
4854
|
+
//console.debug('SyncStatus: DUBB: Ooops it was closed!');
|
|
4976
4855
|
return;
|
|
4977
|
-
|
|
4978
|
-
this.
|
|
4979
|
-
|
|
4980
|
-
ws.onmessage = (event) => {
|
|
4981
|
-
if (!this.pinger)
|
|
4856
|
+
}
|
|
4857
|
+
if (this.tokenExpiration && this.tokenExpiration < new Date()) {
|
|
4858
|
+
this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
|
|
4982
4859
|
return;
|
|
4983
|
-
|
|
4984
|
-
this.
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4860
|
+
}
|
|
4861
|
+
this.webSocketStatus.next('connecting');
|
|
4862
|
+
this.pinger = setInterval(() => __awaiter$1(this, void 0, void 0, function* () {
|
|
4863
|
+
if (this.closed) {
|
|
4864
|
+
console.debug('pinger check', this.id, 'CLOSED.');
|
|
4865
|
+
this.teardown();
|
|
4866
|
+
return;
|
|
4989
4867
|
}
|
|
4990
|
-
if (
|
|
4991
|
-
|
|
4868
|
+
if (this.ws) {
|
|
4869
|
+
try {
|
|
4870
|
+
this.ws.send(JSON.stringify({ type: 'ping' }));
|
|
4871
|
+
setTimeout(() => {
|
|
4872
|
+
console.debug('pinger setTimeout', this.id, this.pinger ? `alive` : 'dead');
|
|
4873
|
+
if (!this.pinger)
|
|
4874
|
+
return;
|
|
4875
|
+
if (this.closed) {
|
|
4876
|
+
console.debug('pinger setTimeout', this.id, 'subscription is closed');
|
|
4877
|
+
this.teardown();
|
|
4878
|
+
return;
|
|
4879
|
+
}
|
|
4880
|
+
if (this.lastServerActivity <
|
|
4881
|
+
new Date(Date.now() - SERVER_PING_TIMEOUT)) {
|
|
4882
|
+
// Server inactive. Reconnect if user is active.
|
|
4883
|
+
console.debug('pinger: server is inactive');
|
|
4884
|
+
console.debug('pinger reconnecting');
|
|
4885
|
+
this.reconnect();
|
|
4886
|
+
}
|
|
4887
|
+
else {
|
|
4888
|
+
console.debug('pinger: server still active');
|
|
4889
|
+
}
|
|
4890
|
+
}, SERVER_PING_TIMEOUT);
|
|
4891
|
+
}
|
|
4892
|
+
catch (_a) {
|
|
4893
|
+
console.debug('pinger catch error', this.id, 'reconnecting');
|
|
4894
|
+
this.reconnect();
|
|
4895
|
+
}
|
|
4992
4896
|
}
|
|
4993
|
-
|
|
4994
|
-
this.
|
|
4897
|
+
else {
|
|
4898
|
+
console.debug('pinger', this.id, 'reconnecting');
|
|
4899
|
+
this.reconnect();
|
|
4995
4900
|
}
|
|
4901
|
+
}), CLIENT_PING_INTERVAL);
|
|
4902
|
+
// The following vars are needed because we must know which callback to ack when server sends it's ack to us.
|
|
4903
|
+
const wsUrl = new URL(this.databaseUrl);
|
|
4904
|
+
wsUrl.protocol = wsUrl.protocol === 'http:' ? 'ws' : 'wss';
|
|
4905
|
+
const searchParams = new URLSearchParams();
|
|
4906
|
+
if (this.subscriber.closed)
|
|
4907
|
+
return;
|
|
4908
|
+
searchParams.set('v', '2');
|
|
4909
|
+
searchParams.set('rev', this.rev);
|
|
4910
|
+
searchParams.set('realmsHash', this.realmSetHash);
|
|
4911
|
+
searchParams.set('clientId', this.clientIdentity);
|
|
4912
|
+
if (this.token) {
|
|
4913
|
+
searchParams.set('token', this.token);
|
|
4996
4914
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
}
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
if (msg.type === '
|
|
5018
|
-
this.
|
|
4915
|
+
// Connect the WebSocket to given url:
|
|
4916
|
+
console.debug('dexie-cloud WebSocket create');
|
|
4917
|
+
const ws = (this.ws = new WebSocket(`${wsUrl}/changes?${searchParams}`));
|
|
4918
|
+
//ws.binaryType = "arraybuffer"; // For future when subscribing to actual changes.
|
|
4919
|
+
ws.onclose = (event) => {
|
|
4920
|
+
if (!this.pinger)
|
|
4921
|
+
return;
|
|
4922
|
+
console.debug('dexie-cloud WebSocket onclosed', this.id);
|
|
4923
|
+
this.reconnect();
|
|
4924
|
+
};
|
|
4925
|
+
ws.onmessage = (event) => {
|
|
4926
|
+
if (!this.pinger)
|
|
4927
|
+
return;
|
|
4928
|
+
console.debug('dexie-cloud WebSocket onmessage', event.data);
|
|
4929
|
+
this.lastServerActivity = new Date();
|
|
4930
|
+
try {
|
|
4931
|
+
const msg = TSON.parse(event.data);
|
|
4932
|
+
if (msg.type === 'error') {
|
|
4933
|
+
throw new Error(`Error message from dexie-cloud: ${msg.error}`);
|
|
4934
|
+
}
|
|
4935
|
+
if (msg.type === 'rev') {
|
|
4936
|
+
this.rev = msg.rev; // No meaning but seems reasonable.
|
|
4937
|
+
}
|
|
4938
|
+
if (msg.type !== 'pong') {
|
|
4939
|
+
this.subscriber.next(msg);
|
|
5019
4940
|
}
|
|
5020
|
-
this.ws?.send(TSON.stringify(msg));
|
|
5021
4941
|
}
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
4942
|
+
catch (e) {
|
|
4943
|
+
this.subscriber.error(e);
|
|
4944
|
+
}
|
|
4945
|
+
};
|
|
4946
|
+
try {
|
|
4947
|
+
let everConnected = false;
|
|
4948
|
+
yield new Promise((resolve, reject) => {
|
|
4949
|
+
ws.onopen = (event) => {
|
|
4950
|
+
console.debug('dexie-cloud WebSocket onopen');
|
|
4951
|
+
everConnected = true;
|
|
4952
|
+
resolve(null);
|
|
4953
|
+
};
|
|
4954
|
+
ws.onerror = (event) => {
|
|
4955
|
+
if (!everConnected) {
|
|
4956
|
+
const error = event.error || new Error('WebSocket Error');
|
|
4957
|
+
this.subscriber.error(error);
|
|
4958
|
+
this.webSocketStatus.next('error');
|
|
4959
|
+
reject(error);
|
|
4960
|
+
}
|
|
4961
|
+
else {
|
|
4962
|
+
this.reconnect();
|
|
4963
|
+
}
|
|
4964
|
+
};
|
|
4965
|
+
});
|
|
4966
|
+
this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
|
|
4967
|
+
var _a;
|
|
4968
|
+
if (!this.closed) {
|
|
4969
|
+
if (msg.type === 'ready' &&
|
|
4970
|
+
this.webSocketStatus.value !== 'connected') {
|
|
4971
|
+
this.webSocketStatus.next('connected');
|
|
4972
|
+
}
|
|
4973
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(TSON.stringify(msg));
|
|
4974
|
+
}
|
|
4975
|
+
});
|
|
4976
|
+
}
|
|
4977
|
+
catch (error) {
|
|
4978
|
+
this.pauseUntil = new Date(Date.now() + FAIL_RETRY_WAIT_TIME);
|
|
4979
|
+
}
|
|
4980
|
+
});
|
|
5027
4981
|
}
|
|
5028
4982
|
}
|
|
5029
4983
|
|
|
5030
4984
|
function sleep(ms) {
|
|
5031
4985
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5032
4986
|
}
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
4987
|
+
function waitAndReconnectWhenUserDoesSomething(error) {
|
|
4988
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
4989
|
+
console.error(`WebSocket observable: error but revive when user does some active thing...`, error);
|
|
4990
|
+
// Sleep some seconds...
|
|
4991
|
+
yield sleep(3000);
|
|
4992
|
+
// Wait til user does something (move mouse, tap, scroll, click etc)
|
|
4993
|
+
console.debug('waiting for someone to do something');
|
|
4994
|
+
yield userDoesSomething.pipe(take(1)).toPromise();
|
|
4995
|
+
console.debug('someone did something!');
|
|
4996
|
+
});
|
|
5041
4997
|
}
|
|
5042
4998
|
function connectWebSocket(db) {
|
|
5043
|
-
|
|
4999
|
+
var _a;
|
|
5000
|
+
if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl)) {
|
|
5044
5001
|
throw new Error(`No database URL to connect WebSocket to`);
|
|
5045
5002
|
}
|
|
5046
5003
|
const messageProducer = db.messageConsumer.readyToServe.pipe(filter((isReady) => isReady), // When consumer is ready for new messages, produce such a message to inform server about it
|
|
@@ -5052,32 +5009,35 @@
|
|
|
5052
5009
|
rev: syncState.serverRevision,
|
|
5053
5010
|
})));
|
|
5054
5011
|
function createObservable() {
|
|
5055
|
-
return db.cloud.persistedSyncState.pipe(filter(syncState => syncState
|
|
5012
|
+
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.
|
|
5056
5013
|
take(1), // Don't continue waking up whenever syncState change
|
|
5057
|
-
switchMap((syncState) => db.cloud.currentUser.pipe(map(userLogin => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(
|
|
5014
|
+
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$1(this, void 0, void 0, function* () { return [userLogin, yield computeRealmSetHash(syncState)]; })), switchMap(([userLogin, realmSetHash]) =>
|
|
5058
5015
|
// Let server end query changes from last entry of same client-ID and forward.
|
|
5059
5016
|
// If no new entries, server won't bother the client. If new entries, server sends only those
|
|
5060
5017
|
// and the baseRev of the last from same client-ID.
|
|
5061
5018
|
userLogin
|
|
5062
5019
|
? new WSObservable(db.cloud.options.databaseUrl, db.cloud.persistedSyncState.value.serverRevision, realmSetHash, db.cloud.persistedSyncState.value.clientIdentity, messageProducer, db.cloud.webSocketStatus, userLogin.accessToken, userLogin.accessTokenExpiration)
|
|
5063
5020
|
: rxjs.from([])), catchError((error) => {
|
|
5064
|
-
if (error
|
|
5021
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'TokenExpiredError') {
|
|
5065
5022
|
console.debug('WebSocket observable: Token expired. Refreshing token...');
|
|
5066
|
-
return rxjs.of(true).pipe(switchMap(
|
|
5023
|
+
return rxjs.of(true).pipe(switchMap(() => __awaiter$1(this, void 0, void 0, function* () {
|
|
5067
5024
|
// Refresh access token
|
|
5068
|
-
const user =
|
|
5069
|
-
const refreshedLogin =
|
|
5025
|
+
const user = yield db.getCurrentUser();
|
|
5026
|
+
const refreshedLogin = yield refreshAccessToken(db.cloud.options.databaseUrl, user);
|
|
5070
5027
|
// Persist updated access token
|
|
5071
|
-
|
|
5028
|
+
yield db.table('$logins').update(user.userId, {
|
|
5072
5029
|
accessToken: refreshedLogin.accessToken,
|
|
5073
5030
|
accessTokenExpiration: refreshedLogin.accessTokenExpiration,
|
|
5074
5031
|
});
|
|
5075
|
-
}), switchMap(() => createObservable()));
|
|
5032
|
+
})), switchMap(() => createObservable()));
|
|
5076
5033
|
}
|
|
5077
5034
|
else {
|
|
5078
5035
|
return rxjs.throwError(error);
|
|
5079
5036
|
}
|
|
5080
|
-
}), catchError((error) =>
|
|
5037
|
+
}), catchError((error) => {
|
|
5038
|
+
db.cloud.webSocketStatus.next("error");
|
|
5039
|
+
return rxjs.from(waitAndReconnectWhenUserDoesSomething(error)).pipe(switchMap(() => createObservable()));
|
|
5040
|
+
}));
|
|
5081
5041
|
}
|
|
5082
5042
|
return createObservable().subscribe((msg) => {
|
|
5083
5043
|
if (msg) {
|
|
@@ -5091,17 +5051,113 @@
|
|
|
5091
5051
|
});
|
|
5092
5052
|
}
|
|
5093
5053
|
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
:
|
|
5054
|
+
function isSyncNeeded(db) {
|
|
5055
|
+
var _a;
|
|
5056
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5057
|
+
return ((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.databaseUrl) && db.cloud.schema
|
|
5058
|
+
? yield sync(db, db.cloud.options, db.cloud.schema, { justCheckIfNeeded: true })
|
|
5059
|
+
: false;
|
|
5060
|
+
});
|
|
5061
|
+
}
|
|
5062
|
+
|
|
5063
|
+
const SECONDS = 1000;
|
|
5064
|
+
const MINUTES = 60 * SECONDS;
|
|
5065
|
+
|
|
5066
|
+
const myId = randomString(16);
|
|
5067
|
+
|
|
5068
|
+
const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
|
|
5069
|
+
const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
|
|
5070
|
+
function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
|
|
5071
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5072
|
+
// Start working.
|
|
5073
|
+
//
|
|
5074
|
+
// Check if someone else is working on this already.
|
|
5075
|
+
//
|
|
5076
|
+
const jobsTable = db.table(jobsTableName);
|
|
5077
|
+
function aquireLock() {
|
|
5078
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5079
|
+
const gotTheLock = yield db.transaction('rw!', jobsTableName, () => __awaiter$1(this, void 0, void 0, function* () {
|
|
5080
|
+
const currentWork = yield jobsTable.get(jobName);
|
|
5081
|
+
if (!currentWork) {
|
|
5082
|
+
// No one else is working. Let's record that we are.
|
|
5083
|
+
yield jobsTable.add({
|
|
5084
|
+
nodeId: myId,
|
|
5085
|
+
started: new Date(),
|
|
5086
|
+
heartbeat: new Date()
|
|
5087
|
+
}, jobName);
|
|
5088
|
+
return true;
|
|
5089
|
+
}
|
|
5090
|
+
else if (currentWork.heartbeat.getTime() <
|
|
5091
|
+
Date.now() - GUARDED_JOB_TIMEOUT) {
|
|
5092
|
+
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!`);
|
|
5093
|
+
// Now, take over!
|
|
5094
|
+
yield jobsTable.put({
|
|
5095
|
+
nodeId: myId,
|
|
5096
|
+
started: new Date(),
|
|
5097
|
+
heartbeat: new Date()
|
|
5098
|
+
}, jobName);
|
|
5099
|
+
return true;
|
|
5100
|
+
}
|
|
5101
|
+
return false;
|
|
5102
|
+
}));
|
|
5103
|
+
if (gotTheLock)
|
|
5104
|
+
return true;
|
|
5105
|
+
// Someone else took the job.
|
|
5106
|
+
if (awaitRemoteJob) {
|
|
5107
|
+
try {
|
|
5108
|
+
const jobDoneObservable = rxjs.from(Dexie.liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
|
|
5109
|
+
yield jobDoneObservable.toPromise();
|
|
5110
|
+
return false;
|
|
5111
|
+
}
|
|
5112
|
+
catch (err) {
|
|
5113
|
+
if (err.name !== 'TimeoutError') {
|
|
5114
|
+
throw err;
|
|
5115
|
+
}
|
|
5116
|
+
// Timeout stopped us! Try aquire the lock now.
|
|
5117
|
+
// It will likely succeed this time unless
|
|
5118
|
+
// another client took it.
|
|
5119
|
+
return yield aquireLock();
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
return false;
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
5125
|
+
if (yield aquireLock()) {
|
|
5126
|
+
// We own the lock entry and can do our job undisturbed.
|
|
5127
|
+
// We're not within a transaction, but these type of locks
|
|
5128
|
+
// spans over transactions.
|
|
5129
|
+
// Start our heart beat during the job.
|
|
5130
|
+
// Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
|
|
5131
|
+
const heartbeat = setInterval(() => {
|
|
5132
|
+
jobsTable.update(jobName, (job) => {
|
|
5133
|
+
if (job.nodeId === myId) {
|
|
5134
|
+
job.heartbeat = new Date();
|
|
5135
|
+
}
|
|
5136
|
+
});
|
|
5137
|
+
}, GUARDED_JOB_HEARTBEAT);
|
|
5138
|
+
try {
|
|
5139
|
+
return yield job();
|
|
5140
|
+
}
|
|
5141
|
+
finally {
|
|
5142
|
+
// Stop heartbeat
|
|
5143
|
+
clearInterval(heartbeat);
|
|
5144
|
+
// Remove the persisted job state:
|
|
5145
|
+
yield db.transaction('rw!', jobsTableName, () => __awaiter$1(this, void 0, void 0, function* () {
|
|
5146
|
+
const currentWork = yield jobsTable.get(jobName);
|
|
5147
|
+
if (currentWork && currentWork.nodeId === myId) {
|
|
5148
|
+
yield jobsTable.delete(jobName);
|
|
5149
|
+
}
|
|
5150
|
+
}));
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
});
|
|
5098
5154
|
}
|
|
5099
5155
|
|
|
5100
5156
|
const ongoingSyncs = new WeakMap();
|
|
5101
5157
|
function syncIfPossible(db, cloudOptions, cloudSchema, options) {
|
|
5102
5158
|
const ongoing = ongoingSyncs.get(db);
|
|
5103
5159
|
if (ongoing) {
|
|
5104
|
-
if (ongoing.pull || options
|
|
5160
|
+
if (ongoing.pull || (options === null || options === void 0 ? void 0 : options.purpose) === 'push') {
|
|
5105
5161
|
console.debug('syncIfPossible(): returning the ongoing sync promise.');
|
|
5106
5162
|
return ongoing.promise;
|
|
5107
5163
|
}
|
|
@@ -5143,32 +5199,34 @@
|
|
|
5143
5199
|
}
|
|
5144
5200
|
}
|
|
5145
5201
|
const promise = _syncIfPossible();
|
|
5146
|
-
ongoingSyncs.set(db, { promise, pull: options
|
|
5202
|
+
ongoingSyncs.set(db, { promise, pull: (options === null || options === void 0 ? void 0 : options.purpose) !== 'push' });
|
|
5147
5203
|
return promise;
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
if (
|
|
5152
|
-
|
|
5204
|
+
function _syncIfPossible() {
|
|
5205
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5206
|
+
try {
|
|
5207
|
+
if (db.cloud.usingServiceWorker) {
|
|
5208
|
+
if (IS_SERVICE_WORKER) {
|
|
5209
|
+
yield sync(db, cloudOptions, cloudSchema, options);
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
else {
|
|
5213
|
+
// We use a flow that is better suited for the case when multiple workers want to
|
|
5214
|
+
// do the same thing.
|
|
5215
|
+
yield performGuardedJob(db, CURRENT_SYNC_WORKER, '$jobs', () => sync(db, cloudOptions, cloudSchema, options));
|
|
5153
5216
|
}
|
|
5217
|
+
ongoingSyncs.delete(db);
|
|
5218
|
+
console.debug('Done sync');
|
|
5154
5219
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5220
|
+
catch (error) {
|
|
5221
|
+
ongoingSyncs.delete(db);
|
|
5222
|
+
console.error(`Failed to sync client changes`, error);
|
|
5223
|
+
throw error; // Make sure we rethrow error so that sync event is retried.
|
|
5224
|
+
// I don't think we should setTimout or so here.
|
|
5225
|
+
// Unless server tells us to in some response.
|
|
5226
|
+
// Then we could follow that advice but not by waiting here but by registering
|
|
5227
|
+
// Something that triggers an event listened to in startPushWorker()
|
|
5159
5228
|
}
|
|
5160
|
-
|
|
5161
|
-
console.debug('Done sync');
|
|
5162
|
-
}
|
|
5163
|
-
catch (error) {
|
|
5164
|
-
ongoingSyncs.delete(db);
|
|
5165
|
-
console.error(`Failed to sync client changes`, error);
|
|
5166
|
-
throw error; // Make sure we rethrow error so that sync event is retried.
|
|
5167
|
-
// I don't think we should setTimout or so here.
|
|
5168
|
-
// Unless server tells us to in some response.
|
|
5169
|
-
// Then we could follow that advice but not by waiting here but by registering
|
|
5170
|
-
// Something that triggers an event listened to in startPushWorker()
|
|
5171
|
-
}
|
|
5229
|
+
});
|
|
5172
5230
|
}
|
|
5173
5231
|
}
|
|
5174
5232
|
|
|
@@ -5238,8 +5296,9 @@
|
|
|
5238
5296
|
}
|
|
5239
5297
|
|
|
5240
5298
|
function verifySchema(db) {
|
|
5299
|
+
var _a, _b;
|
|
5241
5300
|
for (const table of db.tables) {
|
|
5242
|
-
if (db.cloud.schema
|
|
5301
|
+
if ((_b = (_a = db.cloud.schema) === null || _a === void 0 ? void 0 : _a[table.name]) === null || _b === void 0 ? void 0 : _b.markedForSync) {
|
|
5243
5302
|
if (table.schema.primKey.auto) {
|
|
5244
5303
|
throw new Dexie__default["default"].SchemaError(`Table ${table.name} is both autoIncremented and synced. ` +
|
|
5245
5304
|
`Use db.cloud.configure({unsyncedTables: [${JSON.stringify(table.name)}]}) to blacklist it from sync`);
|
|
@@ -5332,7 +5391,7 @@
|
|
|
5332
5391
|
function LoginDialog({ title, alerts, fields, onCancel, onSubmit, }) {
|
|
5333
5392
|
const [params, setParams] = l({});
|
|
5334
5393
|
const firstFieldRef = s();
|
|
5335
|
-
h(() => firstFieldRef.current
|
|
5394
|
+
h(() => { var _a; return (_a = firstFieldRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, []);
|
|
5336
5395
|
return (a$1(Dialog, null,
|
|
5337
5396
|
a$1(y, null,
|
|
5338
5397
|
a$1("h3", { style: Styles.WindowHeader }, title),
|
|
@@ -5342,7 +5401,7 @@
|
|
|
5342
5401
|
onSubmit(params);
|
|
5343
5402
|
} }, Object.entries(fields).map(([fieldName, { type, label, placeholder }], idx) => (a$1("label", { style: Styles.Label },
|
|
5344
5403
|
label ? `${label}: ` : '',
|
|
5345
|
-
a$1("input", { ref: idx === 0 ? firstFieldRef : undefined, type: type, name: fieldName, autoComplete: "on", style: Styles.Input, autoFocus: true, placeholder: placeholder, value: params[fieldName] || '', onInput: (ev) => setParams({
|
|
5404
|
+
a$1("input", { ref: idx === 0 ? firstFieldRef : undefined, type: type, name: fieldName, autoComplete: "on", style: Styles.Input, autoFocus: true, placeholder: placeholder, value: params[fieldName] || '', onInput: (ev) => { var _a; return setParams(Object.assign(Object.assign({}, params), { [fieldName]: valueTransformer(type, (_a = ev.target) === null || _a === void 0 ? void 0 : _a['value']) })); } })))))),
|
|
5346
5405
|
a$1("div", { style: Styles.ButtonsDiv },
|
|
5347
5406
|
a$1("button", { type: "submit", style: Styles.Button, onClick: () => onSubmit(params) }, "Submit"),
|
|
5348
5407
|
a$1("button", { style: Styles.Button, onClick: onCancel }, "Cancel"))));
|
|
@@ -5374,7 +5433,7 @@
|
|
|
5374
5433
|
if (!userInteraction)
|
|
5375
5434
|
return null;
|
|
5376
5435
|
//if (props.db.cloud.userInteraction.observers.length > 1) return null; // Someone else subscribes.
|
|
5377
|
-
return a$1(LoginDialog, {
|
|
5436
|
+
return a$1(LoginDialog, Object.assign({}, userInteraction));
|
|
5378
5437
|
}
|
|
5379
5438
|
}
|
|
5380
5439
|
function setupDefaultGUI(db) {
|
|
@@ -5501,6 +5560,21 @@
|
|
|
5501
5560
|
return rv;
|
|
5502
5561
|
}
|
|
5503
5562
|
|
|
5563
|
+
const getGlobalRolesObservable = associate((db) => {
|
|
5564
|
+
return createSharedValueObservable(Dexie.liveQuery(() => db.roles
|
|
5565
|
+
.where({ realmId: 'rlm-public' })
|
|
5566
|
+
.toArray()
|
|
5567
|
+
.then((roles) => {
|
|
5568
|
+
const rv = {};
|
|
5569
|
+
for (const role of roles
|
|
5570
|
+
.slice()
|
|
5571
|
+
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))) {
|
|
5572
|
+
rv[role.name] = role;
|
|
5573
|
+
}
|
|
5574
|
+
return rv;
|
|
5575
|
+
})), {});
|
|
5576
|
+
});
|
|
5577
|
+
|
|
5504
5578
|
const getCurrentUserEmitter = associate((db) => new rxjs.BehaviorSubject(UNAUTHORIZED_USER));
|
|
5505
5579
|
|
|
5506
5580
|
const getInternalAccessControlObservable = associate((db) => {
|
|
@@ -5545,7 +5619,7 @@
|
|
|
5545
5619
|
if (permissions.length === 0)
|
|
5546
5620
|
return {};
|
|
5547
5621
|
const reduced = permissions.reduce((result, next) => {
|
|
5548
|
-
const ret = {
|
|
5622
|
+
const ret = Object.assign({}, result);
|
|
5549
5623
|
for (const [verb, rights] of Object.entries(next)) {
|
|
5550
5624
|
if (verb in ret && ret[verb]) {
|
|
5551
5625
|
if (ret[verb] === '*')
|
|
@@ -5602,19 +5676,36 @@
|
|
|
5602
5676
|
}
|
|
5603
5677
|
|
|
5604
5678
|
const getPermissionsLookupObservable = associate((db) => {
|
|
5605
|
-
const o =
|
|
5606
|
-
|
|
5679
|
+
const o = createSharedValueObservable(rxjs.combineLatest([
|
|
5680
|
+
getInternalAccessControlObservable(db._novip),
|
|
5681
|
+
getGlobalRolesObservable(db._novip),
|
|
5682
|
+
]).pipe(map(([{ selfMembers, realms, userId }, globalRoles]) => ({
|
|
5683
|
+
selfMembers,
|
|
5684
|
+
realms,
|
|
5685
|
+
userId,
|
|
5686
|
+
globalRoles,
|
|
5687
|
+
}))), {
|
|
5688
|
+
selfMembers: [],
|
|
5689
|
+
realms: [],
|
|
5690
|
+
userId: UNAUTHORIZED_USER.userId,
|
|
5691
|
+
globalRoles: {},
|
|
5692
|
+
});
|
|
5693
|
+
return mapValueObservable(o, ({ selfMembers, realms, userId, globalRoles }) => {
|
|
5607
5694
|
const rv = realms
|
|
5608
|
-
.map((realm) =>
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
.
|
|
5695
|
+
.map((realm) => {
|
|
5696
|
+
const selfRealmMembers = selfMembers.filter((m) => m.realmId === realm.realmId);
|
|
5697
|
+
const directPermissionSets = selfRealmMembers
|
|
5698
|
+
.map((m) => m.permissions)
|
|
5699
|
+
.filter((p) => p);
|
|
5700
|
+
const rolePermissionSets = flatten(selfRealmMembers.map((m) => m.roles).filter((roleName) => roleName))
|
|
5701
|
+
.map((role) => globalRoles[role])
|
|
5702
|
+
.filter((role) => role)
|
|
5703
|
+
.map((role) => role.permissions);
|
|
5704
|
+
return Object.assign(Object.assign({}, realm), { permissions: realm.owner === userId
|
|
5705
|
+
? { manage: '*' }
|
|
5706
|
+
: mergePermissions(...directPermissionSets, ...rolePermissionSets) });
|
|
5707
|
+
})
|
|
5708
|
+
.reduce((p, c) => (Object.assign(Object.assign({}, p), { [c.realmId]: c })), {
|
|
5618
5709
|
[userId]: {
|
|
5619
5710
|
realmId: userId,
|
|
5620
5711
|
owner: userId,
|
|
@@ -5633,47 +5724,50 @@
|
|
|
5633
5724
|
this.isOwner = isOwner;
|
|
5634
5725
|
}
|
|
5635
5726
|
add(...tableNames) {
|
|
5727
|
+
var _a;
|
|
5636
5728
|
// If user can manage the whole realm, return true.
|
|
5637
5729
|
if (this.permissions.manage === '*')
|
|
5638
5730
|
return true;
|
|
5639
5731
|
// If user can manage given table in realm, return true
|
|
5640
|
-
if (this.permissions.manage
|
|
5732
|
+
if ((_a = this.permissions.manage) === null || _a === void 0 ? void 0 : _a.includes(this.tableName))
|
|
5641
5733
|
return true;
|
|
5642
5734
|
// If user can add any type, return true
|
|
5643
5735
|
if (this.permissions.add === '*')
|
|
5644
5736
|
return true;
|
|
5645
5737
|
// If user can add objects into given table names in the realm, return true
|
|
5646
|
-
if (tableNames.every((tableName) => this.permissions.add
|
|
5738
|
+
if (tableNames.every((tableName) => { var _a; return (_a = this.permissions.add) === null || _a === void 0 ? void 0 : _a.includes(tableName); })) {
|
|
5647
5739
|
return true;
|
|
5648
5740
|
}
|
|
5649
5741
|
return false;
|
|
5650
5742
|
}
|
|
5651
5743
|
update(...props) {
|
|
5744
|
+
var _a, _b;
|
|
5652
5745
|
// If user is owner of this object, or if user can manage the whole realm, return true.
|
|
5653
5746
|
if (this.isOwner || this.permissions.manage === '*')
|
|
5654
5747
|
return true;
|
|
5655
5748
|
// If user can manage given table in realm, return true
|
|
5656
|
-
if (this.permissions.manage
|
|
5749
|
+
if ((_a = this.permissions.manage) === null || _a === void 0 ? void 0 : _a.includes(this.tableName))
|
|
5657
5750
|
return true;
|
|
5658
5751
|
// If user can update any prop in any table in this realm, return true unless
|
|
5659
5752
|
// it regards to ownership change:
|
|
5660
5753
|
if (this.permissions.update === '*') {
|
|
5661
5754
|
return props.every((prop) => prop !== 'owner');
|
|
5662
5755
|
}
|
|
5663
|
-
const tablePermissions = this.permissions.update
|
|
5756
|
+
const tablePermissions = (_b = this.permissions.update) === null || _b === void 0 ? void 0 : _b[this.tableName];
|
|
5664
5757
|
// If user can update any prop in table and realm, return true unless
|
|
5665
5758
|
// accessing special props owner or realmId
|
|
5666
5759
|
if (tablePermissions === '*')
|
|
5667
5760
|
return props.every((prop) => prop !== 'owner');
|
|
5668
5761
|
// Explicitely listed properties to allow updates on:
|
|
5669
|
-
return props.every((prop) => tablePermissions
|
|
5762
|
+
return props.every((prop) => tablePermissions === null || tablePermissions === void 0 ? void 0 : tablePermissions.some((permittedProp) => permittedProp === prop || (permittedProp === '*' && prop !== 'owner')));
|
|
5670
5763
|
}
|
|
5671
5764
|
delete() {
|
|
5765
|
+
var _a;
|
|
5672
5766
|
// If user is owner of this object, or if user can manage the whole realm, return true.
|
|
5673
5767
|
if (this.isOwner || this.permissions.manage === '*')
|
|
5674
5768
|
return true;
|
|
5675
5769
|
// If user can manage given table in realm, return true
|
|
5676
|
-
if (this.permissions.manage
|
|
5770
|
+
if ((_a = this.permissions.manage) === null || _a === void 0 ? void 0 : _a.includes(this.tableName))
|
|
5677
5771
|
return true;
|
|
5678
5772
|
return false;
|
|
5679
5773
|
}
|
|
@@ -5697,7 +5791,7 @@
|
|
|
5697
5791
|
const realm = permissionsLookup[realmId || dexie.cloud.currentUserId];
|
|
5698
5792
|
if (!realm)
|
|
5699
5793
|
return new PermissionChecker({}, tableName, !owner || owner === dexie.cloud.currentUserId);
|
|
5700
|
-
return new PermissionChecker(realm.permissions, tableName,
|
|
5794
|
+
return new PermissionChecker(realm.permissions, tableName, realmId === dexie.cloud.currentUserId || owner === dexie.cloud.currentUserId);
|
|
5701
5795
|
};
|
|
5702
5796
|
const o = source.pipe(map(mapper));
|
|
5703
5797
|
o.getValue = () => mapper(source.getValue());
|
|
@@ -5709,7 +5803,7 @@
|
|
|
5709
5803
|
const permissions = getPermissionsLookupObservable(db._novip);
|
|
5710
5804
|
const accessControl = getInternalAccessControlObservable(db._novip);
|
|
5711
5805
|
return createSharedValueObservable(rxjs.combineLatest([membersByEmail, accessControl, permissions]).pipe(rxjs.map(([membersByEmail, accessControl, realmLookup]) => {
|
|
5712
|
-
const reducer = (result, m) => ({
|
|
5806
|
+
const reducer = (result, m) => (Object.assign(Object.assign({}, result), { [m.id]: Object.assign(Object.assign({}, m), { realm: realmLookup[m.realmId] }) }));
|
|
5713
5807
|
const emailMembersById = membersByEmail.reduce(reducer, {});
|
|
5714
5808
|
const membersById = accessControl.selfMembers.reduce(reducer, emailMembersById);
|
|
5715
5809
|
return Object.values(membersById).filter(m => !m.accepted);
|
|
@@ -5736,15 +5830,15 @@
|
|
|
5736
5830
|
let configuredProgramatically = false;
|
|
5737
5831
|
// local sync worker - used when there's no service worker.
|
|
5738
5832
|
let localSyncWorker = null;
|
|
5739
|
-
dexie.on('ready',
|
|
5833
|
+
dexie.on('ready', (dexie) => __awaiter$1(this, void 0, void 0, function* () {
|
|
5740
5834
|
try {
|
|
5741
|
-
|
|
5835
|
+
yield onDbReady(dexie);
|
|
5742
5836
|
}
|
|
5743
5837
|
catch (error) {
|
|
5744
5838
|
console.error(error);
|
|
5745
5839
|
// Make sure to succeed with database open even if network is down.
|
|
5746
5840
|
}
|
|
5747
|
-
}, true // true = sticky
|
|
5841
|
+
}), true // true = sticky
|
|
5748
5842
|
);
|
|
5749
5843
|
/** Void starting subscribers after a close has happened. */
|
|
5750
5844
|
let closed = false;
|
|
@@ -5761,7 +5855,7 @@
|
|
|
5761
5855
|
});
|
|
5762
5856
|
dexie.cloud = {
|
|
5763
5857
|
version: '{version}',
|
|
5764
|
-
options: {
|
|
5858
|
+
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
5765
5859
|
schema: null,
|
|
5766
5860
|
serverState: null,
|
|
5767
5861
|
get currentUserId() {
|
|
@@ -5775,14 +5869,17 @@
|
|
|
5775
5869
|
persistedSyncState: new rxjs.BehaviorSubject(undefined),
|
|
5776
5870
|
userInteraction: new rxjs.BehaviorSubject(undefined),
|
|
5777
5871
|
webSocketStatus: new rxjs.BehaviorSubject('not-started'),
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5872
|
+
login(hint) {
|
|
5873
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5874
|
+
const db = DexieCloudDB(dexie);
|
|
5875
|
+
yield db.cloud.sync();
|
|
5876
|
+
yield login(db, hint);
|
|
5877
|
+
});
|
|
5782
5878
|
},
|
|
5783
5879
|
invites: getInvitesObservable(dexie),
|
|
5880
|
+
roles: getGlobalRolesObservable(dexie),
|
|
5784
5881
|
configure(options) {
|
|
5785
|
-
options = dexie.cloud.options = {
|
|
5882
|
+
options = dexie.cloud.options = Object.assign(Object.assign({}, dexie.cloud.options), options);
|
|
5786
5883
|
configuredProgramatically = true;
|
|
5787
5884
|
if (options.databaseUrl && options.nameSuffix) {
|
|
5788
5885
|
// @ts-ignore
|
|
@@ -5791,41 +5888,43 @@
|
|
|
5791
5888
|
}
|
|
5792
5889
|
updateSchemaFromOptions(dexie.cloud.schema, dexie.cloud.options);
|
|
5793
5890
|
},
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
wait
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5891
|
+
sync({ wait, purpose } = { wait: true, purpose: 'push' }) {
|
|
5892
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5893
|
+
if (wait === undefined)
|
|
5894
|
+
wait = true;
|
|
5895
|
+
const db = DexieCloudDB(dexie);
|
|
5896
|
+
if (purpose === 'pull') {
|
|
5897
|
+
const syncState = db.cloud.persistedSyncState.value;
|
|
5898
|
+
triggerSync(db, purpose);
|
|
5899
|
+
if (wait) {
|
|
5900
|
+
const newSyncState = yield db.cloud.persistedSyncState
|
|
5901
|
+
.pipe(filter((newSyncState) => (newSyncState === null || newSyncState === void 0 ? void 0 : newSyncState.timestamp) != null &&
|
|
5902
|
+
(!syncState || newSyncState.timestamp > syncState.timestamp)), take(1))
|
|
5903
|
+
.toPromise();
|
|
5904
|
+
if (newSyncState === null || newSyncState === void 0 ? void 0 : newSyncState.error) {
|
|
5905
|
+
throw new Error(`Sync error: ` + newSyncState.error);
|
|
5906
|
+
}
|
|
5808
5907
|
}
|
|
5809
5908
|
}
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
.
|
|
5826
|
-
|
|
5909
|
+
else if (yield isSyncNeeded(db)) {
|
|
5910
|
+
const syncState = db.cloud.persistedSyncState.value;
|
|
5911
|
+
triggerSync(db, purpose);
|
|
5912
|
+
if (wait) {
|
|
5913
|
+
console.debug('db.cloud.login() is waiting for sync completion...');
|
|
5914
|
+
yield rxjs.from(Dexie.liveQuery(() => __awaiter$1(this, void 0, void 0, function* () {
|
|
5915
|
+
const syncNeeded = yield isSyncNeeded(db);
|
|
5916
|
+
const newSyncState = yield db.getPersistedSyncState();
|
|
5917
|
+
if ((newSyncState === null || newSyncState === void 0 ? void 0 : newSyncState.timestamp) !== (syncState === null || syncState === void 0 ? void 0 : syncState.timestamp) &&
|
|
5918
|
+
(newSyncState === null || newSyncState === void 0 ? void 0 : newSyncState.error))
|
|
5919
|
+
throw new Error(`Sync error: ` + newSyncState.error);
|
|
5920
|
+
return syncNeeded;
|
|
5921
|
+
})))
|
|
5922
|
+
.pipe(filter((isNeeded) => !isNeeded), take(1))
|
|
5923
|
+
.toPromise();
|
|
5924
|
+
console.debug('Done waiting for sync completion because we have nothing to push anymore');
|
|
5925
|
+
}
|
|
5827
5926
|
}
|
|
5828
|
-
}
|
|
5927
|
+
});
|
|
5829
5928
|
},
|
|
5830
5929
|
permissions(obj, tableName) {
|
|
5831
5930
|
return permissions(dexie._novip, obj, tableName);
|
|
@@ -5837,7 +5936,8 @@
|
|
|
5837
5936
|
return generateKey(dexie.cloud.schema[this.name].idPrefix || '', shardKey);
|
|
5838
5937
|
};
|
|
5839
5938
|
dexie.Table.prototype.idPrefix = function () {
|
|
5840
|
-
|
|
5939
|
+
var _a, _b;
|
|
5940
|
+
return ((_b = (_a = this.db.cloud.schema) === null || _a === void 0 ? void 0 : _a[this.name]) === null || _b === void 0 ? void 0 : _b.idPrefix) || '';
|
|
5841
5941
|
};
|
|
5842
5942
|
dexie.use(createMutationTrackingMiddleware({
|
|
5843
5943
|
currentUserObservable: dexie.cloud.currentUser,
|
|
@@ -5845,160 +5945,164 @@
|
|
|
5845
5945
|
}));
|
|
5846
5946
|
dexie.use(createImplicitPropSetterMiddleware(DexieCloudDB(dexie)));
|
|
5847
5947
|
dexie.use(createIdGenerationMiddleware(DexieCloudDB(dexie)));
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
}
|
|
5858
|
-
//verifyConfig(db.cloud.options); Not needed (yet at least!)
|
|
5859
|
-
// Verify the user has allowed version increment.
|
|
5860
|
-
if (!db.tables.every((table) => table.core)) {
|
|
5861
|
-
throwVersionIncrementNeeded();
|
|
5862
|
-
}
|
|
5863
|
-
const swRegistrations = 'serviceWorker' in navigator
|
|
5864
|
-
? await navigator.serviceWorker.getRegistrations()
|
|
5865
|
-
: [];
|
|
5866
|
-
const initiallySynced = await db.transaction('rw', db.$syncState, async () => {
|
|
5867
|
-
const { options, schema } = db.cloud;
|
|
5868
|
-
const [persistedOptions, persistedSchema, persistedSyncState] = await Promise.all([
|
|
5869
|
-
db.getOptions(),
|
|
5870
|
-
db.getSchema(),
|
|
5871
|
-
db.getPersistedSyncState(),
|
|
5872
|
-
]);
|
|
5873
|
-
if (!configuredProgramatically) {
|
|
5874
|
-
// Options not specified programatically (use case for SW!)
|
|
5875
|
-
// Take persisted options:
|
|
5876
|
-
db.cloud.options = persistedOptions || null;
|
|
5877
|
-
}
|
|
5878
|
-
else if (!persistedOptions ||
|
|
5879
|
-
JSON.stringify(persistedOptions) !== JSON.stringify(options)) {
|
|
5880
|
-
// Update persisted options:
|
|
5881
|
-
if (!options)
|
|
5882
|
-
throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
|
|
5883
|
-
await db.$syncState.put(options, 'options');
|
|
5884
|
-
}
|
|
5885
|
-
if (db.cloud.options?.tryUseServiceWorker &&
|
|
5886
|
-
'serviceWorker' in navigator &&
|
|
5887
|
-
swRegistrations.length > 0 &&
|
|
5888
|
-
!DISABLE_SERVICEWORKER_STRATEGY) {
|
|
5889
|
-
// * Configured for using service worker if available.
|
|
5890
|
-
// * Browser supports service workers
|
|
5891
|
-
// * There are at least one service worker registration
|
|
5892
|
-
console.debug('Dexie Cloud Addon: Using service worker');
|
|
5893
|
-
db.cloud.usingServiceWorker = true;
|
|
5894
|
-
}
|
|
5895
|
-
else {
|
|
5896
|
-
// Not configured for using service worker or no service worker
|
|
5897
|
-
// registration exists. Don't rely on service worker to do any job.
|
|
5898
|
-
// Use LocalSyncWorker instead.
|
|
5899
|
-
if (db.cloud.options?.tryUseServiceWorker && !IS_SERVICE_WORKER) {
|
|
5900
|
-
console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
|
|
5901
|
-
? 'No SW registrations found.'
|
|
5902
|
-
: 'serviceWorker' in navigator && DISABLE_SERVICEWORKER_STRATEGY
|
|
5903
|
-
? 'Avoiding SW background sync and SW periodic bg sync for this browser due to browser bugs.'
|
|
5904
|
-
: 'navigator.serviceWorker not present');
|
|
5948
|
+
function onDbReady(dexie) {
|
|
5949
|
+
var _a, _b, _c, _d, _e, _f;
|
|
5950
|
+
return __awaiter$1(this, void 0, void 0, function* () {
|
|
5951
|
+
closed = false; // As Dexie calls us, we are not closed anymore. Maybe reopened? Remember db.ready event is registered with sticky flag!
|
|
5952
|
+
const db = DexieCloudDB(dexie);
|
|
5953
|
+
// Setup default GUI:
|
|
5954
|
+
if (!IS_SERVICE_WORKER) {
|
|
5955
|
+
if (!((_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.customLoginGui)) {
|
|
5956
|
+
subscriptions.push(setupDefaultGUI(dexie));
|
|
5905
5957
|
}
|
|
5906
|
-
db.cloud.
|
|
5958
|
+
subscriptions.push(computeSyncState(db).subscribe(dexie.cloud.syncState));
|
|
5907
5959
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
if (!
|
|
5911
|
-
|
|
5912
|
-
// Take persisted schema:
|
|
5913
|
-
db.cloud.schema = persistedSchema || null;
|
|
5960
|
+
//verifyConfig(db.cloud.options); Not needed (yet at least!)
|
|
5961
|
+
// Verify the user has allowed version increment.
|
|
5962
|
+
if (!db.tables.every((table) => table.core)) {
|
|
5963
|
+
throwVersionIncrementNeeded();
|
|
5914
5964
|
}
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5965
|
+
const swRegistrations = 'serviceWorker' in navigator
|
|
5966
|
+
? yield navigator.serviceWorker.getRegistrations()
|
|
5967
|
+
: [];
|
|
5968
|
+
const initiallySynced = yield db.transaction('rw', db.$syncState, () => __awaiter$1(this, void 0, void 0, function* () {
|
|
5969
|
+
var _g, _h;
|
|
5970
|
+
const { options, schema } = db.cloud;
|
|
5971
|
+
const [persistedOptions, persistedSchema, persistedSyncState] = yield Promise.all([
|
|
5972
|
+
db.getOptions(),
|
|
5973
|
+
db.getSchema(),
|
|
5974
|
+
db.getPersistedSyncState(),
|
|
5975
|
+
]);
|
|
5976
|
+
if (!configuredProgramatically) {
|
|
5977
|
+
// Options not specified programatically (use case for SW!)
|
|
5978
|
+
// Take persisted options:
|
|
5979
|
+
db.cloud.options = persistedOptions || null;
|
|
5980
|
+
}
|
|
5981
|
+
else if (!persistedOptions ||
|
|
5982
|
+
JSON.stringify(persistedOptions) !== JSON.stringify(options)) {
|
|
5983
|
+
// Update persisted options:
|
|
5984
|
+
if (!options)
|
|
5985
|
+
throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
|
|
5986
|
+
yield db.$syncState.put(options, 'options');
|
|
5987
|
+
}
|
|
5988
|
+
if (((_g = db.cloud.options) === null || _g === void 0 ? void 0 : _g.tryUseServiceWorker) &&
|
|
5989
|
+
'serviceWorker' in navigator &&
|
|
5990
|
+
swRegistrations.length > 0 &&
|
|
5991
|
+
!DISABLE_SERVICEWORKER_STRATEGY) {
|
|
5992
|
+
// * Configured for using service worker if available.
|
|
5993
|
+
// * Browser supports service workers
|
|
5994
|
+
// * There are at least one service worker registration
|
|
5995
|
+
console.debug('Dexie Cloud Addon: Using service worker');
|
|
5996
|
+
db.cloud.usingServiceWorker = true;
|
|
5997
|
+
}
|
|
5998
|
+
else {
|
|
5999
|
+
// Not configured for using service worker or no service worker
|
|
6000
|
+
// registration exists. Don't rely on service worker to do any job.
|
|
6001
|
+
// Use LocalSyncWorker instead.
|
|
6002
|
+
if (((_h = db.cloud.options) === null || _h === void 0 ? void 0 : _h.tryUseServiceWorker) && !IS_SERVICE_WORKER) {
|
|
6003
|
+
console.debug('dexie-cloud-addon: Not using service worker.', swRegistrations.length === 0
|
|
6004
|
+
? 'No SW registrations found.'
|
|
6005
|
+
: 'serviceWorker' in navigator && DISABLE_SERVICEWORKER_STRATEGY
|
|
6006
|
+
? 'Avoiding SW background sync and SW periodic bg sync for this browser due to browser bugs.'
|
|
6007
|
+
: 'navigator.serviceWorker not present');
|
|
5923
6008
|
}
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
6009
|
+
db.cloud.usingServiceWorker = false;
|
|
6010
|
+
}
|
|
6011
|
+
updateSchemaFromOptions(schema, db.cloud.options);
|
|
6012
|
+
updateSchemaFromOptions(persistedSchema, db.cloud.options);
|
|
6013
|
+
if (!schema) {
|
|
6014
|
+
// Database opened dynamically (use case for SW!)
|
|
6015
|
+
// Take persisted schema:
|
|
6016
|
+
db.cloud.schema = persistedSchema || null;
|
|
6017
|
+
}
|
|
6018
|
+
else if (!persistedSchema ||
|
|
6019
|
+
JSON.stringify(persistedSchema) !== JSON.stringify(schema)) {
|
|
6020
|
+
// Update persisted schema (but don't overwrite table prefixes)
|
|
6021
|
+
const newPersistedSchema = persistedSchema || {};
|
|
6022
|
+
for (const [table, tblSchema] of Object.entries(schema)) {
|
|
6023
|
+
const newTblSchema = newPersistedSchema[table];
|
|
6024
|
+
if (!newTblSchema) {
|
|
6025
|
+
newPersistedSchema[table] = Object.assign({}, tblSchema);
|
|
6026
|
+
}
|
|
6027
|
+
else {
|
|
6028
|
+
newTblSchema.markedForSync = tblSchema.markedForSync;
|
|
6029
|
+
tblSchema.deleted = newTblSchema.deleted;
|
|
6030
|
+
newTblSchema.generatedGlobalId = tblSchema.generatedGlobalId;
|
|
6031
|
+
}
|
|
5928
6032
|
}
|
|
6033
|
+
yield db.$syncState.put(newPersistedSchema, 'schema');
|
|
6034
|
+
// Make sure persisted table prefixes are being used instead of computed ones:
|
|
6035
|
+
// Let's assign all props as the newPersistedSchems should be what we should be working with.
|
|
6036
|
+
Object.assign(schema, newPersistedSchema);
|
|
5929
6037
|
}
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
6038
|
+
return persistedSyncState === null || persistedSyncState === void 0 ? void 0 : persistedSyncState.initiallySynced;
|
|
6039
|
+
}));
|
|
6040
|
+
if (initiallySynced) {
|
|
6041
|
+
db.setInitiallySynced(true);
|
|
5934
6042
|
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
db.cloud.schema &&
|
|
5974
|
-
!IS_SERVICE_WORKER) {
|
|
5975
|
-
// There's no SW. Start SyncWorker instead.
|
|
5976
|
-
localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
|
|
5977
|
-
localSyncWorker.start();
|
|
5978
|
-
triggerSync(db, 'push');
|
|
5979
|
-
}
|
|
5980
|
-
// Listen to online event and do sync.
|
|
5981
|
-
throwIfClosed();
|
|
5982
|
-
if (!IS_SERVICE_WORKER) {
|
|
5983
|
-
subscriptions.push(rxjs.fromEvent(self, 'online').subscribe(() => {
|
|
5984
|
-
console.debug('online!');
|
|
5985
|
-
db.syncStateChangedEvent.next({
|
|
5986
|
-
phase: 'not-in-sync',
|
|
5987
|
-
});
|
|
6043
|
+
verifySchema(db);
|
|
6044
|
+
if (((_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.databaseUrl) && !initiallySynced) {
|
|
6045
|
+
yield performInitialSync(db, db.cloud.options, db.cloud.schema);
|
|
6046
|
+
db.setInitiallySynced(true);
|
|
6047
|
+
}
|
|
6048
|
+
// Manage CurrentUser observable:
|
|
6049
|
+
throwIfClosed();
|
|
6050
|
+
if (!IS_SERVICE_WORKER) {
|
|
6051
|
+
subscriptions.push(Dexie.liveQuery(() => db.getCurrentUser()).subscribe(currentUserEmitter));
|
|
6052
|
+
// Manage PersistendSyncState observable:
|
|
6053
|
+
subscriptions.push(Dexie.liveQuery(() => db.getPersistedSyncState()).subscribe(db.cloud.persistedSyncState));
|
|
6054
|
+
// Wait till currentUser and persistedSyncState gets populated
|
|
6055
|
+
// with things from the database and not just the default values.
|
|
6056
|
+
// This is so that when db.open() completes, user should be safe
|
|
6057
|
+
// to subscribe to these observables and get actual data.
|
|
6058
|
+
yield rxjs.combineLatest([
|
|
6059
|
+
currentUserEmitter.pipe(skip(1), take(1)),
|
|
6060
|
+
db.cloud.persistedSyncState.pipe(skip(1), take(1)),
|
|
6061
|
+
]).toPromise();
|
|
6062
|
+
}
|
|
6063
|
+
// HERE: If requireAuth, do athentication now.
|
|
6064
|
+
if ((_c = db.cloud.options) === null || _c === void 0 ? void 0 : _c.requireAuth) {
|
|
6065
|
+
yield login(db);
|
|
6066
|
+
}
|
|
6067
|
+
if (localSyncWorker)
|
|
6068
|
+
localSyncWorker.stop();
|
|
6069
|
+
localSyncWorker = null;
|
|
6070
|
+
throwIfClosed();
|
|
6071
|
+
if (db.cloud.usingServiceWorker && ((_d = db.cloud.options) === null || _d === void 0 ? void 0 : _d.databaseUrl)) {
|
|
6072
|
+
registerSyncEvent(db, 'push').catch(() => { });
|
|
6073
|
+
registerPeriodicSyncEvent(db).catch(() => { });
|
|
6074
|
+
}
|
|
6075
|
+
else if (((_e = db.cloud.options) === null || _e === void 0 ? void 0 : _e.databaseUrl) &&
|
|
6076
|
+
db.cloud.schema &&
|
|
6077
|
+
!IS_SERVICE_WORKER) {
|
|
6078
|
+
// There's no SW. Start SyncWorker instead.
|
|
6079
|
+
localSyncWorker = LocalSyncWorker(db, db.cloud.options, db.cloud.schema);
|
|
6080
|
+
localSyncWorker.start();
|
|
5988
6081
|
triggerSync(db, 'push');
|
|
5989
|
-
}
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6082
|
+
}
|
|
6083
|
+
// Listen to online event and do sync.
|
|
6084
|
+
throwIfClosed();
|
|
6085
|
+
if (!IS_SERVICE_WORKER) {
|
|
6086
|
+
subscriptions.push(rxjs.fromEvent(self, 'online').subscribe(() => {
|
|
6087
|
+
console.debug('online!');
|
|
6088
|
+
db.syncStateChangedEvent.next({
|
|
6089
|
+
phase: 'not-in-sync',
|
|
6090
|
+
});
|
|
6091
|
+
triggerSync(db, 'push');
|
|
6092
|
+
}), rxjs.fromEvent(self, 'offline').subscribe(() => {
|
|
6093
|
+
console.debug('offline!');
|
|
6094
|
+
db.syncStateChangedEvent.next({
|
|
6095
|
+
phase: 'offline',
|
|
6096
|
+
});
|
|
6097
|
+
}));
|
|
6098
|
+
}
|
|
6099
|
+
// Connect WebSocket only if we're a browser window
|
|
6100
|
+
if (typeof window !== 'undefined' &&
|
|
6101
|
+
!IS_SERVICE_WORKER &&
|
|
6102
|
+
((_f = db.cloud.options) === null || _f === void 0 ? void 0 : _f.databaseUrl)) {
|
|
6103
|
+
subscriptions.push(connectWebSocket(db));
|
|
6104
|
+
}
|
|
6105
|
+
});
|
|
6002
6106
|
}
|
|
6003
6107
|
}
|
|
6004
6108
|
dexieCloud.version = '{version}';
|