@evolu/web 2.4.0 → 3.0.0-next.1
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/README.md +3 -1
- package/dist/src/Platform.d.ts +3 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/{local-first/Platform.js → Platform.js} +1 -1
- package/dist/src/Sqlite.d.ts +3 -0
- package/dist/src/Sqlite.d.ts.map +1 -0
- package/dist/src/Sqlite.js +162 -0
- package/dist/src/Task.d.ts +29 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +38 -0
- package/dist/src/Worker.d.ts +60 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +328 -0
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/local-first/Db.worker.js +13 -10
- package/dist/src/local-first/Evolu.d.ts +11 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -0
- package/dist/src/local-first/Evolu.js +57 -0
- package/dist/src/local-first/LocalAuth.d.ts +2 -3
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +60 -71
- package/dist/src/local-first/Shared.worker.d.ts +2 -0
- package/dist/src/local-first/Shared.worker.d.ts.map +1 -0
- package/dist/src/local-first/Shared.worker.js +94 -0
- package/dist/src/local-first/index.d.ts +1 -3
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +1 -20
- package/package.json +16 -16
- package/src/Platform.ts +9 -0
- package/src/Sqlite.ts +138 -0
- package/src/Task.ts +59 -0
- package/src/Worker.ts +319 -0
- package/src/index.ts +3 -3
- package/src/local-first/Db.worker.ts +16 -16
- package/src/local-first/Evolu.ts +104 -0
- package/src/local-first/LocalAuth.ts +93 -105
- package/src/local-first/Shared.worker.ts +58 -0
- package/src/local-first/index.ts +1 -41
- package/dist/src/SharedWebWorker.d.ts +0 -30
- package/dist/src/SharedWebWorker.d.ts.map +0 -1
- package/dist/src/SharedWebWorker.js +0 -104
- package/dist/src/WasmSqliteDriver.d.ts +0 -3
- package/dist/src/WasmSqliteDriver.d.ts.map +0 -1
- package/dist/src/WasmSqliteDriver.js +0 -85
- package/dist/src/WebWorker.d.ts +0 -17
- package/dist/src/WebWorker.d.ts.map +0 -1
- package/dist/src/WebWorker.js +0 -42
- package/dist/src/local-first/Platform.d.ts +0 -3
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/test/SharedWebWorker.test.d.ts +0 -2
- package/dist/test/SharedWebWorker.test.d.ts.map +0 -1
- package/dist/test/SharedWebWorker.test.js +0 -248
- package/src/SharedWebWorker.ts +0 -149
- package/src/WasmSqliteDriver.ts +0 -118
- package/src/WebWorker.ts +0 -54
- package/src/local-first/Platform.ts +0 -9
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./local-first/index.js";
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
2
|
+
export * from "./Sqlite.js";
|
|
3
|
+
export * from "./Task.js";
|
|
4
|
+
export * from "./Worker.js";
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
import { installPolyfills } from "@evolu/common/polyfills";
|
|
3
|
+
installPolyfills();
|
|
4
|
+
import { createRandomBytes } from "@evolu/common";
|
|
5
|
+
import { startDbWorker } from "@evolu/common/local-first";
|
|
6
|
+
import { createWasmSqliteDriver } from "../Sqlite.js";
|
|
7
|
+
import { createRun } from "../Task.js";
|
|
8
|
+
import { createWorkerDeps, createWorkerSelf } from "../Worker.js";
|
|
9
|
+
// TODO: Disposal.
|
|
10
|
+
const run = createRun({
|
|
11
|
+
...createWorkerDeps(),
|
|
7
12
|
createSqliteDriver: createWasmSqliteDriver,
|
|
8
|
-
|
|
9
|
-
random: createRandom(),
|
|
13
|
+
lockManager: globalThis.navigator.locks,
|
|
10
14
|
randomBytes: createRandomBytes(),
|
|
11
|
-
time: createTime(),
|
|
12
15
|
});
|
|
13
|
-
|
|
16
|
+
run(startDbWorker(createWorkerSelf(self)));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ConsoleDep } from "@evolu/common";
|
|
2
|
+
import type { EvoluDeps } from "@evolu/common/local-first";
|
|
3
|
+
export interface SharedWorkerUnsupported {
|
|
4
|
+
readonly type: "SharedWorkerUnsupported";
|
|
5
|
+
}
|
|
6
|
+
export interface SharedWorkerUnsupportedDep {
|
|
7
|
+
readonly onSharedWorkerUnsupported: () => void;
|
|
8
|
+
}
|
|
9
|
+
/** Creates Evolu dependencies for the web platform. */
|
|
10
|
+
export declare const createEvoluDeps: (deps?: Partial<ConsoleDep> & Partial<SharedWorkerUnsupportedDep>) => EvoluDeps;
|
|
11
|
+
//# sourceMappingURL=Evolu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/local-first/Evolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAIV,SAAS,EAGV,MAAM,2BAA2B,CAAC;AAkBnC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;CAC1C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,yBAAyB,EAAE,MAAM,IAAI,CAAC;CAChD;AAED,uDAAuD;AACvD,eAAO,MAAM,eAAe,GAC1B,OAAM,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,0BAA0B,CAAM,KACnE,SAkEF,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { exhaustiveCheck } from "@evolu/common";
|
|
2
|
+
import { createEvoluDeps as createCommonEvoluDeps } from "@evolu/common/local-first";
|
|
3
|
+
import { reloadApp } from "../Platform.js";
|
|
4
|
+
import { createBroadcastChannel, createMessageChannel, createSharedWorker, createWorker, installOneTabSharedWorkerPolyfill, } from "../Worker.js";
|
|
5
|
+
/** Creates Evolu dependencies for the web platform. */
|
|
6
|
+
export const createEvoluDeps = (deps = {}) => {
|
|
7
|
+
installOneTabSharedWorkerPolyfill();
|
|
8
|
+
const createDbWorker = () => createWorker(new Worker(new URL("Db.worker.js", import.meta.url), {
|
|
9
|
+
type: "module",
|
|
10
|
+
}));
|
|
11
|
+
const webSharedWorker = createSharedWorker(new SharedWorker(new URL("Shared.worker.js", import.meta.url), {
|
|
12
|
+
type: "module",
|
|
13
|
+
}));
|
|
14
|
+
let onSharedWorkerMessage = null;
|
|
15
|
+
const sharedWorker = {
|
|
16
|
+
...webSharedWorker,
|
|
17
|
+
port: {
|
|
18
|
+
...webSharedWorker.port,
|
|
19
|
+
get onMessage() {
|
|
20
|
+
return onSharedWorkerMessage;
|
|
21
|
+
},
|
|
22
|
+
set onMessage(fn) {
|
|
23
|
+
onSharedWorkerMessage = fn;
|
|
24
|
+
webSharedWorker.port.onMessage = fn
|
|
25
|
+
? (message) => {
|
|
26
|
+
switch (message.type) {
|
|
27
|
+
case "DbWorkerInit": {
|
|
28
|
+
fn(message);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case "SharedWorkerUnsupported": {
|
|
32
|
+
if (deps.onSharedWorkerUnsupported) {
|
|
33
|
+
deps.onSharedWorkerUnsupported();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
globalThis.alert("This browser supports Evolu in one tab only. Close this tab and use the already open tab.");
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
exhaustiveCheck(message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
: null;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return createCommonEvoluDeps({
|
|
49
|
+
...deps,
|
|
50
|
+
createDbWorker,
|
|
51
|
+
createBroadcastChannel,
|
|
52
|
+
createMessageChannel,
|
|
53
|
+
lockManager: globalThis.navigator.locks,
|
|
54
|
+
reloadApp,
|
|
55
|
+
sharedWorker,
|
|
56
|
+
});
|
|
57
|
+
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare const createWebAuthnStore: (deps: RandomBytesDep & SymmetricCryptoDep) => SecureStorage;
|
|
1
|
+
import type { RandomBytesDep, SecureStorage } from "@evolu/common";
|
|
2
|
+
export declare const createWebAuthnStore: (deps: RandomBytesDep) => SecureStorage;
|
|
4
3
|
//# sourceMappingURL=LocalAuth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../../src/local-first/LocalAuth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalAuth.d.ts","sourceRoot":"","sources":["../../../src/local-first/LocalAuth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACd,aAAa,EAEd,MAAM,eAAe,CAAC;AAiBvB,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,aA4GzD,CAAC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
/** @experimental */
|
|
1
|
+
import { Base64Url, base64UrlToUint8Array, bytesToUtf8, createSlip21, decryptWithXChaCha20Poly1305, EncryptionKey, encryptWithXChaCha20Poly1305, Entropy24, uint8ArrayToBase64Url, utf8ToBytes, XChaCha20Poly1305Ciphertext, } from "@evolu/common";
|
|
2
|
+
import { clear, createStore, del, get, keys, set } from "idb-keyval";
|
|
4
3
|
export const createWebAuthnStore = (deps) => ({
|
|
5
4
|
setItem: async (key, value, options) => {
|
|
6
5
|
if (options?.accessControl === "none") {
|
|
@@ -38,7 +37,7 @@ export const createWebAuthnStore = (deps) => ({
|
|
|
38
37
|
const credential = await getCredential(deps)(data.credentialId, options?.relyingPartyID, options?.webAuthnUserVerification);
|
|
39
38
|
const credentialSeed = extractSeedFromCredential(credential);
|
|
40
39
|
const encryptionKey = deriveEncryptionKey(credentialSeed);
|
|
41
|
-
const authResultVal = decryptAuthResult(
|
|
40
|
+
const authResultVal = decryptAuthResult(data, encryptionKey);
|
|
42
41
|
if (!authResultVal) {
|
|
43
42
|
return null;
|
|
44
43
|
}
|
|
@@ -81,18 +80,14 @@ export const createWebAuthnStore = (deps) => ({
|
|
|
81
80
|
* Create default metadata for backwards compatibility with items that don't
|
|
82
81
|
* have stored metadata.
|
|
83
82
|
*/
|
|
84
|
-
const createMetadata = (isSecure = true) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
};
|
|
83
|
+
const createMetadata = (isSecure = true) => ({
|
|
84
|
+
backend: "keychain",
|
|
85
|
+
accessControl: isSecure ? "biometryCurrentSet" : "none",
|
|
86
|
+
securityLevel: isSecure ? "biometry" : "software",
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
});
|
|
92
89
|
/** Get storage key for owner ID. (supports namespaces via prefix) */
|
|
93
|
-
const getStore = (prefix = "default") =>
|
|
94
|
-
return createStore(prefix, "evolu-auth");
|
|
95
|
-
};
|
|
90
|
+
const getStore = (prefix = "default") => createStore(prefix, "evolu-auth");
|
|
96
91
|
const createCredential = (deps) => async (username, seed, relyingPartyID, relyingPartyName, userVerification, authenticatorAttachment) => {
|
|
97
92
|
const options = createCredentialCreationOptions(deps)(username, seed, relyingPartyID, relyingPartyName, userVerification, authenticatorAttachment);
|
|
98
93
|
const credential = (await navigator.credentials.create(options));
|
|
@@ -116,78 +111,72 @@ const extractSeedFromCredential = (credential) => {
|
|
|
116
111
|
}
|
|
117
112
|
return new Uint8Array(response.userHandle);
|
|
118
113
|
};
|
|
119
|
-
const createCredentialCreationOptions = (deps) => (username, seed, relyingPartyID, relyingPartyName, userVerification, authenticatorAttachment) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
name: relyingPartyName ?? "Evolu",
|
|
126
|
-
},
|
|
127
|
-
user: {
|
|
128
|
-
id: seed,
|
|
129
|
-
name: username,
|
|
130
|
-
displayName: username,
|
|
131
|
-
},
|
|
132
|
-
pubKeyCredParams: [
|
|
133
|
-
{ type: "public-key", alg: -8 }, // Ed25519
|
|
134
|
-
{ type: "public-key", alg: -7 }, // ES256
|
|
135
|
-
{ type: "public-key", alg: -257 }, // RS256
|
|
136
|
-
],
|
|
137
|
-
attestation: "none",
|
|
138
|
-
authenticatorSelection: {
|
|
139
|
-
// - "platform": Uses the platform's built-in authenticator.
|
|
140
|
-
// - "cross-platform": Uses a device specific authenticator (yubikey, fido2, etc.)
|
|
141
|
-
authenticatorAttachment: authenticatorAttachment ?? "platform",
|
|
142
|
-
// - "discouraged": Only User Presence is needed.
|
|
143
|
-
// - "preferred": User Verification is preferred but not required. Falls back to User Presence.
|
|
144
|
-
// - "required": User Verification MUST occur (biometrics/PIN). Clients may silently downgrade to User Presence only.
|
|
145
|
-
userVerification: userVerification ?? "required",
|
|
146
|
-
// - "discouraged": Server-side credential is preferable, but will accept client-side discoverable credential.
|
|
147
|
-
// - "preferred": Relying Party strongly prefers client-side discoverable credential but will accept server-side credential.
|
|
148
|
-
// - "required": Client-side discoverable credential MUST be created, error if it can't be created.
|
|
149
|
-
residentKey: "required",
|
|
150
|
-
// Included for backwards compatibility. Deprecated in favor of residentKey (true = "required")
|
|
151
|
-
requireResidentKey: true,
|
|
152
|
-
},
|
|
114
|
+
const createCredentialCreationOptions = (deps) => (username, seed, relyingPartyID, relyingPartyName, userVerification, authenticatorAttachment) => ({
|
|
115
|
+
publicKey: {
|
|
116
|
+
challenge: generateSeed(deps)(),
|
|
117
|
+
rp: {
|
|
118
|
+
id: relyingPartyID ?? document.location.hostname,
|
|
119
|
+
name: relyingPartyName ?? "Evolu",
|
|
153
120
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
publicKey: {
|
|
159
|
-
challenge: generateSeed(deps)(),
|
|
160
|
-
rpId: relyingPartyID ?? document.location.hostname,
|
|
161
|
-
userVerification: userVerification ?? "preferred",
|
|
162
|
-
allowCredentials: [
|
|
163
|
-
{
|
|
164
|
-
type: "public-key",
|
|
165
|
-
id: base64UrlToUint8Array(Base64Url.orThrow(credentialId)),
|
|
166
|
-
},
|
|
167
|
-
],
|
|
121
|
+
user: {
|
|
122
|
+
id: seed,
|
|
123
|
+
name: username,
|
|
124
|
+
displayName: username,
|
|
168
125
|
},
|
|
169
|
-
|
|
170
|
-
}
|
|
126
|
+
pubKeyCredParams: [
|
|
127
|
+
{ type: "public-key", alg: -8 }, // Ed25519
|
|
128
|
+
{ type: "public-key", alg: -7 }, // ES256
|
|
129
|
+
{ type: "public-key", alg: -257 }, // RS256
|
|
130
|
+
],
|
|
131
|
+
attestation: "none",
|
|
132
|
+
authenticatorSelection: {
|
|
133
|
+
// - "platform": Uses the platform's built-in authenticator.
|
|
134
|
+
// - "cross-platform": Uses a device specific authenticator (yubikey, fido2, etc.)
|
|
135
|
+
authenticatorAttachment: authenticatorAttachment ?? "platform",
|
|
136
|
+
// - "discouraged": Only User Presence is needed.
|
|
137
|
+
// - "preferred": User Verification is preferred but not required. Falls back to User Presence.
|
|
138
|
+
// - "required": User Verification MUST occur (biometrics/PIN). Clients may silently downgrade to User Presence only.
|
|
139
|
+
userVerification: userVerification ?? "required",
|
|
140
|
+
// - "discouraged": Server-side credential is preferable, but will accept client-side discoverable credential.
|
|
141
|
+
// - "preferred": Relying Party strongly prefers client-side discoverable credential but will accept server-side credential.
|
|
142
|
+
// - "required": Client-side discoverable credential MUST be created, error if it can't be created.
|
|
143
|
+
residentKey: "required",
|
|
144
|
+
// Included for backwards compatibility. Deprecated in favor of residentKey (true = "required")
|
|
145
|
+
requireResidentKey: true,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const createCredentialRequestOptions = (deps) => (credentialId, relyingPartyID, userVerification) => ({
|
|
150
|
+
publicKey: {
|
|
151
|
+
challenge: generateSeed(deps)(),
|
|
152
|
+
rpId: relyingPartyID ?? document.location.hostname,
|
|
153
|
+
userVerification: userVerification ?? "preferred",
|
|
154
|
+
allowCredentials: [
|
|
155
|
+
{
|
|
156
|
+
type: "public-key",
|
|
157
|
+
id: base64UrlToUint8Array(Base64Url.orThrow(credentialId)),
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
});
|
|
171
162
|
const deriveEncryptionKey = (seed) => {
|
|
172
163
|
const seed32 = seed.length === 32 ? seed : seed.slice(0, 32);
|
|
173
164
|
return EncryptionKey.orThrow(createSlip21(seed32, ["evolu", "auth"]));
|
|
174
165
|
};
|
|
175
166
|
const encryptAuthResult = (deps) => (authResult, encryptionKey) => {
|
|
176
167
|
const plaintext = utf8ToBytes(JSON.stringify(authResult));
|
|
177
|
-
const
|
|
168
|
+
const [ciphertext, nonce] = encryptWithXChaCha20Poly1305(deps)(plaintext, encryptionKey);
|
|
178
169
|
return {
|
|
179
170
|
nonce: uint8ArrayToBase64Url(nonce),
|
|
180
171
|
ciphertext: uint8ArrayToBase64Url(ciphertext),
|
|
181
172
|
};
|
|
182
173
|
};
|
|
183
|
-
const decryptAuthResult = (
|
|
174
|
+
const decryptAuthResult = (encryptedData, encryptionKey) => {
|
|
184
175
|
const nonce = base64UrlToUint8Array(encryptedData.nonce);
|
|
185
176
|
const ciphertext = base64UrlToUint8Array(encryptedData.ciphertext);
|
|
186
|
-
const result =
|
|
177
|
+
const result = decryptWithXChaCha20Poly1305(XChaCha20Poly1305Ciphertext.orThrow(ciphertext), Entropy24.orThrow(nonce), encryptionKey);
|
|
187
178
|
if (!result.ok)
|
|
188
179
|
return null;
|
|
189
180
|
return bytesToUtf8(result.value);
|
|
190
181
|
};
|
|
191
|
-
const generateSeed = (deps) => () =>
|
|
192
|
-
return deps.randomBytes.create(32);
|
|
193
|
-
};
|
|
182
|
+
const generateSeed = (deps) => () => deps.randomBytes.create(32);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Shared.worker.d.ts","sourceRoot":"","sources":["../../../src/local-first/Shared.worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
/// <reference lib="webworker" />
|
|
54
|
+
import { installPolyfills } from "@evolu/common/polyfills";
|
|
55
|
+
installPolyfills();
|
|
56
|
+
import { createWebSocket, ok } from "@evolu/common";
|
|
57
|
+
import { initSharedWorker } from "@evolu/common/local-first";
|
|
58
|
+
import { createRun } from "../Task.js";
|
|
59
|
+
import { createOneTabSharedWorkerSelfPolyfill, createSharedWorkerSelf, createWorkerDeps, } from "../Worker.js";
|
|
60
|
+
// No disposal (`await using`) is needed — a SharedWorker lives forever.
|
|
61
|
+
const run = createRun({
|
|
62
|
+
...createWorkerDeps(),
|
|
63
|
+
createWebSocket,
|
|
64
|
+
lockManager: globalThis.navigator.locks,
|
|
65
|
+
});
|
|
66
|
+
void run(async (run) => {
|
|
67
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
68
|
+
try {
|
|
69
|
+
if ("onconnect" in self) {
|
|
70
|
+
void run(initSharedWorker(createSharedWorkerSelf(self)));
|
|
71
|
+
return ok();
|
|
72
|
+
}
|
|
73
|
+
const workerSelf = __addDisposableResource(env_1, createOneTabSharedWorkerSelfPolyfill(self), false);
|
|
74
|
+
await globalThis.navigator.locks.request("evolu-one-tab-sharedworker-polyfill", { ifAvailable: true, mode: "exclusive" }, async (lock) => {
|
|
75
|
+
if (!lock) {
|
|
76
|
+
const message = {
|
|
77
|
+
type: "SharedWorkerUnsupported",
|
|
78
|
+
};
|
|
79
|
+
self.postMessage(message);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
void run(initSharedWorker(workerSelf));
|
|
83
|
+
await Promise.withResolvers().promise;
|
|
84
|
+
});
|
|
85
|
+
return ok();
|
|
86
|
+
}
|
|
87
|
+
catch (e_1) {
|
|
88
|
+
env_1.error = e_1;
|
|
89
|
+
env_1.hasError = true;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
__disposeResources(env_1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
|
|
@@ -1,20 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createSharedWebWorker } from "../SharedWebWorker.js";
|
|
3
|
-
import { createWebAuthnStore } from "./LocalAuth.js";
|
|
4
|
-
import { reloadApp } from "./Platform.js";
|
|
5
|
-
const randomBytes = createRandomBytes();
|
|
6
|
-
const symmetricCrypto = createSymmetricCrypto({ randomBytes });
|
|
7
|
-
const createDbWorker = (name) => createSharedWebWorker(name, () => new Worker(new URL("Db.worker.js", import.meta.url), {
|
|
8
|
-
type: "module",
|
|
9
|
-
}));
|
|
10
|
-
export const localAuth = createLocalAuth({
|
|
11
|
-
randomBytes,
|
|
12
|
-
secureStorage: createWebAuthnStore({ randomBytes, symmetricCrypto }),
|
|
13
|
-
});
|
|
14
|
-
export const evoluWebDeps = {
|
|
15
|
-
console: createConsole(),
|
|
16
|
-
createDbWorker,
|
|
17
|
-
randomBytes: createRandomBytes(),
|
|
18
|
-
reloadApp,
|
|
19
|
-
time: createTime(),
|
|
20
|
-
};
|
|
1
|
+
export * from "./Evolu.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-next.1",
|
|
4
4
|
"description": "Evolu for web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
],
|
|
9
9
|
"author": "Daniel Steigerwald <daniel@steigerwald.cz>",
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"repository":
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/evoluhq/evolu.git"
|
|
14
|
+
},
|
|
12
15
|
"bugs": {
|
|
13
16
|
"url": "https://github.com/evoluhq/evolu/issues"
|
|
14
17
|
},
|
|
15
18
|
"homepage": "https://evolu.dev",
|
|
16
19
|
"type": "module",
|
|
17
|
-
"types": "./dist/index.d.ts",
|
|
20
|
+
"types": "./dist/src/index.d.ts",
|
|
18
21
|
"exports": {
|
|
19
22
|
".": {
|
|
20
23
|
"types": "./dist/src/index.d.ts",
|
|
@@ -23,7 +26,7 @@
|
|
|
23
26
|
}
|
|
24
27
|
},
|
|
25
28
|
"files": [
|
|
26
|
-
"dist/**",
|
|
29
|
+
"dist/src/**",
|
|
27
30
|
"src/**",
|
|
28
31
|
"README.md"
|
|
29
32
|
],
|
|
@@ -32,29 +35,26 @@
|
|
|
32
35
|
"idb-keyval": "^6.2.2"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
38
|
+
"@types/node": "^24.10.9",
|
|
39
|
+
"@types/sharedworker": "^0.0.221",
|
|
35
40
|
"@types/web-locks-api": "^0.0.5",
|
|
36
|
-
"typescript": "^
|
|
41
|
+
"typescript": "^6",
|
|
37
42
|
"user-agent-data-types": "^0.4.2",
|
|
38
|
-
"
|
|
39
|
-
"@evolu/
|
|
40
|
-
"@evolu/tsconfig": "0.0.2"
|
|
43
|
+
"@evolu/common": "8.0.0-next.5",
|
|
44
|
+
"@evolu/typescript-config": "0.0.2"
|
|
41
45
|
},
|
|
42
46
|
"peerDependencies": {
|
|
43
|
-
"@evolu/common": "^
|
|
47
|
+
"@evolu/common": "^8.0.0-next.0"
|
|
44
48
|
},
|
|
45
49
|
"publishConfig": {
|
|
46
50
|
"access": "public"
|
|
47
51
|
},
|
|
48
52
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
53
|
+
"node": ">=24.0.0"
|
|
50
54
|
},
|
|
51
|
-
"sideEffects":
|
|
55
|
+
"sideEffects": false,
|
|
52
56
|
"scripts": {
|
|
53
|
-
"
|
|
54
|
-
"build": "rimraf dist && tsc",
|
|
55
|
-
"test": "vitest run",
|
|
56
|
-
"test:watch": "vitest",
|
|
57
|
-
"clean": "rimraf .turbo node_modules dist",
|
|
57
|
+
"build": "tsc --build tsconfig.build.json",
|
|
58
58
|
"format": "prettier --write \"src/*.{ts,tsx,md}\""
|
|
59
59
|
}
|
|
60
60
|
}
|
package/src/Platform.ts
ADDED
package/src/Sqlite.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { CreateSqliteDriver, SqliteRow } from "@evolu/common";
|
|
2
|
+
import { bytesToHex, createPreparedStatementsCache, ok } from "@evolu/common";
|
|
3
|
+
import sqlite3InitModule, {
|
|
4
|
+
type Database,
|
|
5
|
+
type PreparedStatement,
|
|
6
|
+
type SAHPoolUtil,
|
|
7
|
+
} from "@evolu/sqlite-wasm";
|
|
8
|
+
|
|
9
|
+
// @ts-expect-error Missing types.
|
|
10
|
+
globalThis.sqlite3ApiConfig = {
|
|
11
|
+
warn: (arg: unknown) => {
|
|
12
|
+
// Ignore irrelevant warning.
|
|
13
|
+
// https://github.com/sqlite/sqlite-wasm/issues/62
|
|
14
|
+
if (
|
|
15
|
+
typeof arg === "string" &&
|
|
16
|
+
arg.startsWith("Ignoring inability to install OPFS sqlite3_vfs")
|
|
17
|
+
)
|
|
18
|
+
return;
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.warn(arg);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Init ASAP.
|
|
25
|
+
const sqlite3Promise = sqlite3InitModule();
|
|
26
|
+
|
|
27
|
+
const fileName = "evolu1.db";
|
|
28
|
+
|
|
29
|
+
export const createWasmSqliteDriver: CreateSqliteDriver =
|
|
30
|
+
(name, options) => async () => {
|
|
31
|
+
const sqlite3 = await sqlite3Promise;
|
|
32
|
+
|
|
33
|
+
using disposer = new DisposableStack();
|
|
34
|
+
const useDatabase = (database: Database): Database =>
|
|
35
|
+
disposer.adopt(database, (database) => {
|
|
36
|
+
database.close();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let deleteDatabaseFile = false;
|
|
40
|
+
const createOpfsSAHPoolVfs = async (
|
|
41
|
+
options: Parameters<typeof sqlite3.installOpfsSAHPoolVfs>[0],
|
|
42
|
+
): Promise<SAHPoolUtil> => {
|
|
43
|
+
const pool = await sqlite3.installOpfsSAHPoolVfs(options);
|
|
44
|
+
if (pool.isPaused()) await pool.unpauseVfs();
|
|
45
|
+
disposer.defer(() => {
|
|
46
|
+
if (deleteDatabaseFile) pool.unlink(`/${fileName}`);
|
|
47
|
+
pool.pauseVfs();
|
|
48
|
+
});
|
|
49
|
+
return pool;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let db: Database;
|
|
53
|
+
|
|
54
|
+
switch (options?.mode) {
|
|
55
|
+
case "memory":
|
|
56
|
+
db = useDatabase(new sqlite3.oo1.DB(":memory:"));
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case "encrypted": {
|
|
60
|
+
// MultipleCiphers encryption requires its VFS wrapper for OPFS SAH-pool.
|
|
61
|
+
// @ts-expect-error Missing types (update @evolu/sqlite-wasm types)
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
63
|
+
sqlite3.capi.sqlite3mc_vfs_create("opfs", 1);
|
|
64
|
+
const pool = await createOpfsSAHPoolVfs({
|
|
65
|
+
directory: `.${name}`,
|
|
66
|
+
});
|
|
67
|
+
db = useDatabase(
|
|
68
|
+
new pool.OpfsSAHPoolDb(
|
|
69
|
+
// SQLite normalizes this URI filename to SAH-pool path "/evolu1.db".
|
|
70
|
+
`file:${fileName}?vfs=multipleciphers-opfs-sahpool`,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
db.exec(`
|
|
74
|
+
PRAGMA cipher = 'sqlcipher';
|
|
75
|
+
PRAGMA key = "x'${bytesToHex(options.encryptionKey)}'";
|
|
76
|
+
`);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
default: {
|
|
81
|
+
const pool = await createOpfsSAHPoolVfs({ name });
|
|
82
|
+
db = useDatabase(new pool.OpfsSAHPoolDb(`file:${fileName}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cache = disposer.use(
|
|
87
|
+
createPreparedStatementsCache<PreparedStatement>(
|
|
88
|
+
(sql) => db.prepare(sql),
|
|
89
|
+
(statement) => {
|
|
90
|
+
statement.finalize();
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const disposables = disposer.move();
|
|
96
|
+
|
|
97
|
+
return ok({
|
|
98
|
+
exec: (query) => {
|
|
99
|
+
const prepared = cache.get(query);
|
|
100
|
+
|
|
101
|
+
if (prepared) {
|
|
102
|
+
if (query.parameters.length > 0) prepared.bind(query.parameters);
|
|
103
|
+
|
|
104
|
+
const rows = [];
|
|
105
|
+
while (prepared.step()) {
|
|
106
|
+
rows.push(prepared.get({}));
|
|
107
|
+
}
|
|
108
|
+
prepared.reset();
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
rows: rows as ReadonlyArray<SqliteRow>,
|
|
112
|
+
changes: db.changes(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const rows = db.exec(query.sql, {
|
|
117
|
+
returnValue: "resultRows",
|
|
118
|
+
rowMode: "object",
|
|
119
|
+
bind: query.parameters,
|
|
120
|
+
}) as ReadonlyArray<SqliteRow>;
|
|
121
|
+
|
|
122
|
+
const changes = db.changes();
|
|
123
|
+
|
|
124
|
+
return { rows, changes };
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
export: () => sqlite3.capi.sqlite3_js_db_export(db),
|
|
128
|
+
|
|
129
|
+
deleteDatabase: () => {
|
|
130
|
+
deleteDatabaseFile = true;
|
|
131
|
+
disposables.dispose();
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
[Symbol.dispose]: () => {
|
|
135
|
+
disposables.dispose();
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
};
|