monoidentity 0.11.2 → 0.12.0
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/+client.d.ts +6 -0
- package/dist/+client.js +6 -0
- package/dist/+common.d.ts +1 -0
- package/dist/+common.js +1 -0
- package/dist/+server.d.ts +2 -0
- package/dist/+server.js +2 -0
- package/dist/Monoidentity.svelte +28 -26
- package/dist/storage/backupcloud.d.ts +2 -0
- package/dist/storage/backupcloud.js +107 -0
- package/dist/storage/backuplocally.d.ts +1 -0
- package/dist/storage/backuplocally.js +85 -0
- package/dist/storage/storageclient.svelte.d.ts +10 -0
- package/dist/storage/storageclient.svelte.js +69 -0
- package/dist/storage.d.ts +4 -6
- package/dist/storage.js +17 -107
- package/dist/trackready.js +14 -21
- package/dist/verification-server.d.ts +1 -0
- package/dist/verification-server.js +6 -0
- package/package.json +6 -7
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -5
- package/dist/storage/_cloud.d.ts +0 -3
- package/dist/storage/_cloud.js +0 -39
- package/dist/storage/_md5.d.ts +0 -1
- package/dist/storage/_md5.js +0 -52
- package/dist/storage/_replay.d.ts +0 -16
- package/dist/storage/_replay.js +0 -95
- package/dist/storage/_timing.d.ts +0 -1
- package/dist/storage/_timing.js +0 -21
- package/dist/storage/createlocalstorage.d.ts +0 -1
- package/dist/storage/createlocalstorage.js +0 -42
- package/dist/storage/createstore.d.ts +0 -11
- package/dist/storage/createstore.js +0 -9
- package/dist/storage/wrapbackup.d.ts +0 -2
- package/dist/storage/wrapbackup.js +0 -88
- package/dist/storage/wrapcloud.d.ts +0 -3
- package/dist/storage/wrapcloud.js +0 -54
- /package/dist/{storage-attest.d.ts → verification-client.d.ts} +0 -0
- /package/dist/{storage-attest.js → verification-client.js} +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./+common.js";
|
|
2
|
+
export { getLoginRecognized, getVerification, getStorage, getScopedFS } from "./storage.js";
|
|
3
|
+
export { retrieveVerification } from "./verification-client.js";
|
|
4
|
+
export { default as rawAttest } from "./verification/attest-remote.js";
|
|
5
|
+
export { trackReady } from "./trackready.js";
|
|
6
|
+
export { default as Monoidentity } from "./Monoidentity.svelte";
|
package/dist/+client.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./+common.js";
|
|
2
|
+
export { getLoginRecognized, getVerification, getStorage, getScopedFS } from "./storage.js";
|
|
3
|
+
export { retrieveVerification } from "./verification-client.js";
|
|
4
|
+
export { default as rawAttest } from "./verification/attest-remote.js";
|
|
5
|
+
export { trackReady } from "./trackready.js";
|
|
6
|
+
export { default as Monoidentity } from "./Monoidentity.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { encode, decode } from "./utils-base36.js";
|
package/dist/+common.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { encode, decode } from "./utils-base36.js";
|
package/dist/+server.js
ADDED
package/dist/Monoidentity.svelte
CHANGED
|
@@ -19,10 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
{#snippet backupUI(yes: () => void, no: () => void)}
|
|
21
21
|
<p>Avoid reconfiguration with a backup folder.</p>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
<button onclick={no}>Skip</button>
|
|
25
|
-
</div>
|
|
22
|
+
<button onclick={no}>Skip</button>
|
|
23
|
+
<button class="primary" onclick={yes}>Connect</button>
|
|
26
24
|
{/snippet}
|
|
27
25
|
|
|
28
26
|
{#await ready}
|
|
@@ -40,32 +38,36 @@
|
|
|
40
38
|
.backup {
|
|
41
39
|
display: flex;
|
|
42
40
|
flex-direction: column;
|
|
43
|
-
gap: 0.
|
|
44
|
-
|
|
45
|
-
background-color: light-dark(#000, #fff);
|
|
46
|
-
color: light-dark(#fff, #000);
|
|
41
|
+
gap: 0.25rem;
|
|
47
42
|
line-height: 1;
|
|
48
|
-
padding: 0.75rem;
|
|
49
|
-
border-radius: 1.5rem;
|
|
50
43
|
|
|
51
|
-
>
|
|
44
|
+
> * {
|
|
52
45
|
display: flex;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
height: 3rem;
|
|
49
|
+
margin: 0;
|
|
50
|
+
padding-inline: 0.5rem;
|
|
51
|
+
border-radius: 0.5rem;
|
|
52
|
+
border: none;
|
|
53
|
+
font: inherit;
|
|
54
|
+
&:first-child {
|
|
55
|
+
border-start-start-radius: 1.5rem;
|
|
56
|
+
border-start-end-radius: 1.5rem;
|
|
57
|
+
}
|
|
58
|
+
&:last-child {
|
|
59
|
+
border-end-start-radius: 1.5rem;
|
|
60
|
+
border-end-end-radius: 1.5rem;
|
|
68
61
|
}
|
|
62
|
+
background-color: light-dark(#fff, #000);
|
|
63
|
+
color: light-dark(#000, #fff);
|
|
64
|
+
}
|
|
65
|
+
> button {
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
> .primary {
|
|
69
|
+
background-color: light-dark(#000, #fff);
|
|
70
|
+
color: light-dark(#fff, #000);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
.toast {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AwsClient } from "aws4fetch";
|
|
2
|
+
import { storageClient, STORAGE_EVENT } from "./storageclient.svelte.js";
|
|
3
|
+
const CLOUD_CACHE_KEY = "monoidentity-x/cloud-cache";
|
|
4
|
+
const isJunk = (key) => key.includes(".obsidian/") || key.includes(".cache/");
|
|
5
|
+
const loadFromCloud = async (base, client) => {
|
|
6
|
+
const listResp = await client.fetch(base);
|
|
7
|
+
if (!listResp.ok)
|
|
8
|
+
throw new Error(`List bucket failed: ${listResp.status}`);
|
|
9
|
+
const listXml = await listResp.text();
|
|
10
|
+
const objects = [...listXml.matchAll(/<Key>(.*?)<\/Key>.*?<ETag>(.*?)<\/ETag>/gs)]
|
|
11
|
+
.map((m) => m.slice(1).map((s) => s.replaceAll(""", `"`).replaceAll("'", `'`)))
|
|
12
|
+
.map(([key, etag]) => ({ key, etag: etag.replaceAll(`"`, "") }))
|
|
13
|
+
.filter(({ key }) => !isJunk(key));
|
|
14
|
+
const prevCache = JSON.parse(localStorage[CLOUD_CACHE_KEY] || "{}");
|
|
15
|
+
const nextCache = {};
|
|
16
|
+
const model = {};
|
|
17
|
+
await Promise.all(objects.map(async ({ key, etag }) => {
|
|
18
|
+
const cached = prevCache[key];
|
|
19
|
+
if (cached?.etag == etag) {
|
|
20
|
+
model[key] = cached.content;
|
|
21
|
+
nextCache[key] = cached;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.debug("[monoidentity cloud] loading", key);
|
|
25
|
+
const r = await client.fetch(`${base}/${key}`);
|
|
26
|
+
if (!r.ok)
|
|
27
|
+
throw new Error(`Fetch ${key} failed: ${r.status}`);
|
|
28
|
+
let content;
|
|
29
|
+
if (key.endsWith(".md") || key.endsWith(".devalue")) {
|
|
30
|
+
content = await r.text();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const buf = new Uint8Array(await r.arrayBuffer());
|
|
34
|
+
content = "";
|
|
35
|
+
const chunk = 8192;
|
|
36
|
+
for (let i = 0; i < buf.length; i += chunk) {
|
|
37
|
+
content += String.fromCharCode.apply(null, buf.subarray(i, i + chunk));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
model[key] = content;
|
|
41
|
+
nextCache[key] = { etag, content };
|
|
42
|
+
}));
|
|
43
|
+
localStorage[CLOUD_CACHE_KEY] = JSON.stringify(nextCache);
|
|
44
|
+
return model;
|
|
45
|
+
};
|
|
46
|
+
const syncFromCloud = async (bucket, client) => {
|
|
47
|
+
const remote = await loadFromCloud(bucket.base, client);
|
|
48
|
+
const local = storageClient();
|
|
49
|
+
for (const key of Object.keys(local)) {
|
|
50
|
+
if (!(key in remote)) {
|
|
51
|
+
delete local[key];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [key, value] of Object.entries(remote)) {
|
|
55
|
+
local[key] = value;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
export const backupCloud = async (bucket) => {
|
|
59
|
+
const client = new AwsClient({
|
|
60
|
+
accessKeyId: bucket.accessKeyId,
|
|
61
|
+
secretAccessKey: bucket.secretAccessKey,
|
|
62
|
+
});
|
|
63
|
+
await syncFromCloud(bucket, client);
|
|
64
|
+
setInterval(() => syncFromCloud(bucket, client), 15 * 60 * 1000);
|
|
65
|
+
// Continuous sync: mirror local changes to cloud
|
|
66
|
+
addEventListener(STORAGE_EVENT, async (event) => {
|
|
67
|
+
let key = event.detail.key;
|
|
68
|
+
if (!key.startsWith("monoidentity/"))
|
|
69
|
+
return;
|
|
70
|
+
key = key.slice("monoidentity/".length);
|
|
71
|
+
if (isJunk(key))
|
|
72
|
+
return;
|
|
73
|
+
console.debug("[monoidentity cloud] saving", key);
|
|
74
|
+
const url = `${bucket.base}/${key}`;
|
|
75
|
+
try {
|
|
76
|
+
if (event.detail.value != undefined) {
|
|
77
|
+
// PUT content (unconditional to start; you can add If-Match/If-None-Match for safety)
|
|
78
|
+
const r = await client.fetch(url, {
|
|
79
|
+
method: "PUT",
|
|
80
|
+
headers: { "content-type": "application/octet-stream" },
|
|
81
|
+
body: event.detail.value,
|
|
82
|
+
});
|
|
83
|
+
if (!r.ok)
|
|
84
|
+
throw new Error(`PUT ${key} failed: ${r.status}`);
|
|
85
|
+
// Update cache
|
|
86
|
+
const etag = r.headers.get("etag")?.replaceAll('"', "");
|
|
87
|
+
if (etag) {
|
|
88
|
+
const cache = JSON.parse(localStorage[CLOUD_CACHE_KEY] || "{}");
|
|
89
|
+
cache[key] = { etag, content: event.detail.value };
|
|
90
|
+
localStorage[CLOUD_CACHE_KEY] = JSON.stringify(cache);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// DELETE key
|
|
95
|
+
const r = await client.fetch(url, { method: "DELETE" });
|
|
96
|
+
if (!r.ok && r.status != 404)
|
|
97
|
+
throw new Error(`DELETE ${key} failed: ${r.status}`);
|
|
98
|
+
const cache = JSON.parse(localStorage[CLOUD_CACHE_KEY] || "{}");
|
|
99
|
+
delete cache[key];
|
|
100
|
+
localStorage[CLOUD_CACHE_KEY] = JSON.stringify(cache);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.warn("[monoidentity cloud] sync failed", key, err);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const backupLocally: (requestBackup: (startBackup: () => void) => void) => Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createStore, get, set } from "idb-keyval";
|
|
2
|
+
import { STORAGE_EVENT, storageClient } from "./storageclient.svelte.js";
|
|
3
|
+
import { canBackup } from "../utils-transport.js";
|
|
4
|
+
const saveToDir = (dir) => {
|
|
5
|
+
let dirCache = {};
|
|
6
|
+
const getDirCached = async (route) => {
|
|
7
|
+
let key = "";
|
|
8
|
+
let parent = dir;
|
|
9
|
+
for (const path of route) {
|
|
10
|
+
key += "/";
|
|
11
|
+
key += path;
|
|
12
|
+
if (!dirCache[key]) {
|
|
13
|
+
dirCache[key] = await parent.getDirectoryHandle(path, { create: true });
|
|
14
|
+
}
|
|
15
|
+
parent = dirCache[key];
|
|
16
|
+
}
|
|
17
|
+
return parent;
|
|
18
|
+
};
|
|
19
|
+
addEventListener(STORAGE_EVENT, async (event) => {
|
|
20
|
+
let key = event.detail.key;
|
|
21
|
+
if (!key.startsWith("monoidentity/"))
|
|
22
|
+
return;
|
|
23
|
+
key = key.slice("monoidentity/".length);
|
|
24
|
+
const pathParts = key.split("/");
|
|
25
|
+
const name = pathParts.at(-1);
|
|
26
|
+
const value = event.detail.value;
|
|
27
|
+
console.debug("[monoidentity backup] saving", name);
|
|
28
|
+
const parent = await getDirCached(pathParts.slice(0, -1));
|
|
29
|
+
if (value != undefined) {
|
|
30
|
+
const file = await parent.getFileHandle(name, { create: true });
|
|
31
|
+
const writable = await file.createWritable();
|
|
32
|
+
await writable.write(value);
|
|
33
|
+
await writable.close();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await parent.removeEntry(name);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
export const backupLocally = async (requestBackup) => {
|
|
41
|
+
if (!canBackup)
|
|
42
|
+
return;
|
|
43
|
+
if (localStorage["monoidentity-x/backup"] == "off")
|
|
44
|
+
return;
|
|
45
|
+
const handles = createStore("monoidentity-x", "handles");
|
|
46
|
+
if (localStorage["monoidentity-x/backup"] == "on") {
|
|
47
|
+
const dir = await get("backup", handles);
|
|
48
|
+
if (!dir)
|
|
49
|
+
throw new Error("No backup handle found");
|
|
50
|
+
saveToDir(dir);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
localStorage["monoidentity-x/backup"] = "off";
|
|
54
|
+
requestBackup(async () => {
|
|
55
|
+
const dir = await showDirectoryPicker({ mode: "readwrite" });
|
|
56
|
+
await set("backup", dir, handles);
|
|
57
|
+
localStorage["monoidentity-x/backup"] = "on";
|
|
58
|
+
// Restore from backup
|
|
59
|
+
const backup = {};
|
|
60
|
+
const traverse = async (d, path) => {
|
|
61
|
+
for await (const entry of d.values()) {
|
|
62
|
+
if (entry.kind == "file") {
|
|
63
|
+
const file = await entry.getFile();
|
|
64
|
+
const text = await file.text();
|
|
65
|
+
backup[`${path}${entry.name}`] = text;
|
|
66
|
+
}
|
|
67
|
+
else if (entry.kind == "directory") {
|
|
68
|
+
await traverse(entry, `${path}${entry.name}/`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await traverse(dir, "");
|
|
73
|
+
if (Object.keys(backup).length) {
|
|
74
|
+
const client = storageClient();
|
|
75
|
+
for (const key in backup) {
|
|
76
|
+
console.debug("[monoidentity backup] loading", key);
|
|
77
|
+
client[key] = backup[key];
|
|
78
|
+
}
|
|
79
|
+
location.reload();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
saveToDir(dir);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface WindowEventMap {
|
|
3
|
+
"monoidentity-storage": CustomEvent<{
|
|
4
|
+
key: string;
|
|
5
|
+
value: string | undefined;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare const STORAGE_EVENT = "monoidentity-storage";
|
|
10
|
+
export declare const storageClient: (prefix?: (key: string) => string, unprefix?: (key: string) => string | undefined, serialize?: (data: any) => string, deserialize?: (data: string) => any) => Record<string, any>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const STORAGE_EVENT = "monoidentity-storage";
|
|
2
|
+
const announce = (key, value) => {
|
|
3
|
+
// Announce to all, even third parties
|
|
4
|
+
window.dispatchEvent(new CustomEvent(STORAGE_EVENT, { detail: { key, value } }));
|
|
5
|
+
};
|
|
6
|
+
const storageCounters = $state({});
|
|
7
|
+
let allCounter = $state(0);
|
|
8
|
+
const increment = (key) => {
|
|
9
|
+
storageCounters[key] = (storageCounters[key] || 0) + 1;
|
|
10
|
+
allCounter++;
|
|
11
|
+
};
|
|
12
|
+
addEventListener(STORAGE_EVENT, (event) => increment(event.detail.key));
|
|
13
|
+
addEventListener("storage", (event) => {
|
|
14
|
+
if (event.storageArea != localStorage)
|
|
15
|
+
return;
|
|
16
|
+
if (!event.key)
|
|
17
|
+
return;
|
|
18
|
+
increment(event.key);
|
|
19
|
+
});
|
|
20
|
+
export const storageClient = (prefix, unprefix, serialize, deserialize) => {
|
|
21
|
+
if (prefix) {
|
|
22
|
+
const oldPrefix = prefix;
|
|
23
|
+
prefix = (key) => `monoidentity/${oldPrefix(key)}`;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
prefix = (key) => `monoidentity/${key}`;
|
|
27
|
+
}
|
|
28
|
+
return new Proxy({}, {
|
|
29
|
+
get(_, key) {
|
|
30
|
+
key = prefix(key);
|
|
31
|
+
storageCounters[key];
|
|
32
|
+
const raw = localStorage[key];
|
|
33
|
+
return raw && deserialize ? deserialize(raw) : raw;
|
|
34
|
+
},
|
|
35
|
+
set(_, key, value) {
|
|
36
|
+
key = prefix(key);
|
|
37
|
+
localStorage[key] = serialize ? serialize(value) : value;
|
|
38
|
+
announce(key, value);
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
deleteProperty(_, key) {
|
|
42
|
+
key = prefix(key);
|
|
43
|
+
delete localStorage[key];
|
|
44
|
+
announce(key);
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
ownKeys() {
|
|
48
|
+
allCounter;
|
|
49
|
+
const keys = [];
|
|
50
|
+
for (let key in localStorage) {
|
|
51
|
+
if (!key.startsWith("monoidentity/"))
|
|
52
|
+
continue;
|
|
53
|
+
key = key.slice("monoidentity/".length);
|
|
54
|
+
if (unprefix) {
|
|
55
|
+
const unprefixed = unprefix(key);
|
|
56
|
+
if (!unprefixed)
|
|
57
|
+
continue;
|
|
58
|
+
key = unprefixed;
|
|
59
|
+
}
|
|
60
|
+
keys.push(key);
|
|
61
|
+
}
|
|
62
|
+
return keys;
|
|
63
|
+
},
|
|
64
|
+
getOwnPropertyDescriptor(_, key) {
|
|
65
|
+
key = prefix(key);
|
|
66
|
+
return Reflect.getOwnPropertyDescriptor(localStorage, key);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
export declare const conf: (
|
|
2
|
-
export declare const LOGIN_RECOGNIZED_PATH = ".core/login.encjson";
|
|
1
|
+
export declare const conf: (a: string) => void;
|
|
3
2
|
export declare const getLoginRecognized: () => {
|
|
4
3
|
email: string;
|
|
5
4
|
password: string;
|
|
6
5
|
};
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const getVerification: () => Promise<
|
|
6
|
+
export declare const setLoginRecognized: (login: string) => void;
|
|
7
|
+
export declare const getVerification: () => Promise<any>;
|
|
9
8
|
export declare const setVerification: (jwt: string) => void;
|
|
10
|
-
export declare const useVerification: (jwt: string) => Promise<import("@tsndr/cloudflare-worker-jwt").JwtData<{}, {}>>;
|
|
11
9
|
export declare const getStorage: (realm: "config" | "cache") => Record<string, any>;
|
|
12
|
-
export declare const getScopedFS: (dir: string) => Record<string,
|
|
10
|
+
export declare const getScopedFS: (dir: string) => Record<string, any>;
|
package/dist/storage.js
CHANGED
|
@@ -1,128 +1,38 @@
|
|
|
1
1
|
import { stringify, parse } from "devalue";
|
|
2
2
|
import { parse as useSchema } from "valibot";
|
|
3
3
|
import { decode } from "./utils-base36.js";
|
|
4
|
-
import { createStore } from "./storage/createstore.js";
|
|
5
4
|
import { login as loginSchema } from "./utils-transport.js";
|
|
6
5
|
import { verify } from "@tsndr/cloudflare-worker-jwt";
|
|
7
6
|
import publicKey from "./verification/public-key.js";
|
|
8
|
-
|
|
9
|
-
let app = "";
|
|
10
|
-
export const conf = (
|
|
11
|
-
implementation = i;
|
|
7
|
+
import { storageClient } from "./storage/storageclient.svelte.js";
|
|
8
|
+
let app = "unknown";
|
|
9
|
+
export const conf = (a) => {
|
|
12
10
|
app = a;
|
|
13
11
|
};
|
|
14
|
-
|
|
12
|
+
const LOGIN_RECOGNIZED_PATH = ".core/login.encjson";
|
|
15
13
|
export const getLoginRecognized = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const login = implementation[LOGIN_RECOGNIZED_PATH];
|
|
14
|
+
const client = storageClient();
|
|
15
|
+
const login = client[LOGIN_RECOGNIZED_PATH];
|
|
19
16
|
if (!login)
|
|
20
17
|
throw new Error("No login found");
|
|
21
18
|
return useSchema(loginSchema, JSON.parse(decode(login)));
|
|
22
19
|
};
|
|
23
|
-
export const
|
|
20
|
+
export const setLoginRecognized = (login) => {
|
|
21
|
+
const client = storageClient();
|
|
22
|
+
client[LOGIN_RECOGNIZED_PATH] = login;
|
|
23
|
+
};
|
|
24
|
+
const VERIFICATION_PATH = ".core/verification.jwt";
|
|
24
25
|
export const getVerification = async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const jwt = implementation[VERIFICATION_PATH];
|
|
26
|
+
const client = storageClient();
|
|
27
|
+
const jwt = client[VERIFICATION_PATH];
|
|
28
28
|
if (!jwt)
|
|
29
29
|
throw new Error("No verification found");
|
|
30
30
|
await verify(jwt, publicKey, { algorithm: "ES256", throwError: true });
|
|
31
31
|
return jwt;
|
|
32
32
|
};
|
|
33
33
|
export const setVerification = (jwt) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
implementation[VERIFICATION_PATH] = jwt;
|
|
37
|
-
};
|
|
38
|
-
export const useVerification = async (jwt) => {
|
|
39
|
-
const result = await verify(jwt, publicKey, { algorithm: "ES256", throwError: true });
|
|
40
|
-
return result;
|
|
34
|
+
const client = storageClient();
|
|
35
|
+
client[VERIFICATION_PATH] = jwt;
|
|
41
36
|
};
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
return target[prefix(prop)];
|
|
45
|
-
},
|
|
46
|
-
set(target, prop, value) {
|
|
47
|
-
target[prefix(prop)] = value;
|
|
48
|
-
return true;
|
|
49
|
-
},
|
|
50
|
-
has(target, prop) {
|
|
51
|
-
return prefix(prop) in target;
|
|
52
|
-
},
|
|
53
|
-
deleteProperty(target, prop) {
|
|
54
|
-
return delete target[prefix(prop)];
|
|
55
|
-
},
|
|
56
|
-
ownKeys(target) {
|
|
57
|
-
if (typeof unprefix != "function") {
|
|
58
|
-
throw new Error("unprefix must be a function");
|
|
59
|
-
}
|
|
60
|
-
return Object.keys(target)
|
|
61
|
-
.map((key) => unprefix(key))
|
|
62
|
-
.filter((key) => typeof key == "string");
|
|
63
|
-
},
|
|
64
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
65
|
-
return Reflect.getOwnPropertyDescriptor(target, prefix(prop));
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
export const getStorage = (realm) => withPrefix(createStore({
|
|
69
|
-
get(key) {
|
|
70
|
-
if (!implementation)
|
|
71
|
-
throw new Error("No implementation set");
|
|
72
|
-
const item = implementation[key];
|
|
73
|
-
return item ? parse(item) : undefined;
|
|
74
|
-
},
|
|
75
|
-
set(key, value) {
|
|
76
|
-
if (!implementation)
|
|
77
|
-
throw new Error("No implementation set");
|
|
78
|
-
implementation[key] = stringify(value);
|
|
79
|
-
return true;
|
|
80
|
-
},
|
|
81
|
-
has(key) {
|
|
82
|
-
if (!implementation)
|
|
83
|
-
throw new Error("No implementation set");
|
|
84
|
-
return key in implementation;
|
|
85
|
-
},
|
|
86
|
-
deleteProperty(key) {
|
|
87
|
-
if (!implementation)
|
|
88
|
-
throw new Error("No implementation set");
|
|
89
|
-
return delete implementation[key];
|
|
90
|
-
},
|
|
91
|
-
}), (text) => {
|
|
92
|
-
if (!app)
|
|
93
|
-
throw new Error("No app set");
|
|
94
|
-
return `.${realm}/${app}/${text}.devalue`;
|
|
95
|
-
});
|
|
96
|
-
export const getScopedFS = (dir) => withPrefix(createStore({
|
|
97
|
-
get(key) {
|
|
98
|
-
if (!implementation)
|
|
99
|
-
throw new Error("No implementation set");
|
|
100
|
-
return implementation[key];
|
|
101
|
-
},
|
|
102
|
-
set(key, value) {
|
|
103
|
-
if (!implementation)
|
|
104
|
-
throw new Error("No implementation set");
|
|
105
|
-
implementation[key] = value;
|
|
106
|
-
return true;
|
|
107
|
-
},
|
|
108
|
-
has(key) {
|
|
109
|
-
if (!implementation)
|
|
110
|
-
throw new Error("No implementation set");
|
|
111
|
-
return key in implementation;
|
|
112
|
-
},
|
|
113
|
-
deleteProperty(key) {
|
|
114
|
-
if (!implementation)
|
|
115
|
-
throw new Error("No implementation set");
|
|
116
|
-
return delete implementation[key];
|
|
117
|
-
},
|
|
118
|
-
ownKeys() {
|
|
119
|
-
if (!implementation)
|
|
120
|
-
throw new Error("No implementation set");
|
|
121
|
-
return Object.keys(implementation);
|
|
122
|
-
},
|
|
123
|
-
getOwnPropertyDescriptor(key) {
|
|
124
|
-
if (!implementation)
|
|
125
|
-
throw new Error("No implementation set");
|
|
126
|
-
return Reflect.getOwnPropertyDescriptor(implementation, key);
|
|
127
|
-
},
|
|
128
|
-
}), (text) => `${dir}/${text}`, (text) => (text.startsWith(`${dir}/`) ? text.slice(dir.length + 1) : undefined));
|
|
37
|
+
export const getStorage = (realm) => storageClient((key) => `.${realm}/${app}/${key}.devalue`, undefined, stringify, parse);
|
|
38
|
+
export const getScopedFS = (dir) => storageClient((key) => `${dir}/${key}`, (key) => (key.startsWith(dir + "/") ? key.slice(dir.length + 1) : undefined));
|
package/dist/trackready.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import {} from "./utils-transport.js";
|
|
2
|
-
import { createLocalStorage } from "./storage/createlocalstorage.js";
|
|
3
|
-
import { wrapBackup } from "./storage/wrapbackup.js";
|
|
4
|
-
import { wrapCloud } from "./storage/wrapcloud.js";
|
|
5
|
-
import {
|
|
2
|
+
// import { createLocalStorage } from "./storage/createlocalstorage.js";
|
|
3
|
+
// import { wrapBackup } from "./storage/wrapbackup.js";
|
|
4
|
+
// import { wrapCloud } from "./storage/wrapcloud.js";
|
|
5
|
+
import { conf, setLoginRecognized } from "./storage.js";
|
|
6
|
+
import { backupLocally } from "./storage/backuplocally.js";
|
|
7
|
+
import { backupCloud } from "./storage/backupcloud.js";
|
|
6
8
|
export const trackReady = async (app, intents, requestBackup) => {
|
|
7
|
-
|
|
9
|
+
conf(app);
|
|
8
10
|
let setup = localStorage["monoidentity-x/setup"]
|
|
9
11
|
? JSON.parse(localStorage["monoidentity-x/setup"])
|
|
10
12
|
: undefined;
|
|
11
13
|
let provisions = [];
|
|
14
|
+
const params = new URLSearchParams(location.hash.slice(1));
|
|
12
15
|
const cb = params.get("monoidentitycallback");
|
|
13
16
|
if (cb) {
|
|
14
17
|
history.replaceState(null, "", location.pathname);
|
|
@@ -27,27 +30,17 @@ export const trackReady = async (app, intents, requestBackup) => {
|
|
|
27
30
|
redirectURI: location.origin,
|
|
28
31
|
});
|
|
29
32
|
location.href = target.toString();
|
|
30
|
-
|
|
31
|
-
/* never resolves */
|
|
32
|
-
});
|
|
33
|
-
throw new Error("unreachable");
|
|
33
|
+
throw new Error("halt: redirecting");
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
storage = createLocalStorage();
|
|
38
|
-
storage = await wrapCloud(storage, setup);
|
|
39
|
-
}
|
|
40
|
-
else if (setup.method == "localStorage") {
|
|
41
|
-
storage = createLocalStorage();
|
|
42
|
-
storage = wrapBackup(storage, requestBackup);
|
|
35
|
+
if (setup.method == "localStorage") {
|
|
36
|
+
await backupLocally(requestBackup);
|
|
43
37
|
}
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
if (setup.method == "cloud") {
|
|
39
|
+
await backupCloud(setup);
|
|
46
40
|
}
|
|
47
|
-
conf(storage, app);
|
|
48
41
|
for (const provision of provisions) {
|
|
49
42
|
if ("createLoginRecognized" in provision) {
|
|
50
|
-
|
|
43
|
+
setLoginRecognized(provision.createLoginRecognized);
|
|
51
44
|
}
|
|
52
45
|
}
|
|
53
46
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useVerification: (jwt: string) => Promise<import("@tsndr/cloudflare-worker-jwt").JwtData<{}, {}>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { verify } from "@tsndr/cloudflare-worker-jwt";
|
|
2
|
+
import publicKey from "./verification/public-key.js";
|
|
3
|
+
export const useVerification = async (jwt) => {
|
|
4
|
+
const result = await verify(jwt, publicKey, { algorithm: "ES256", throwError: true });
|
|
5
|
+
return result;
|
|
6
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monoidentity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"repository": "KTibow/monoidentity",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "KTibow"
|
|
@@ -15,16 +15,15 @@
|
|
|
15
15
|
"sideEffects": [
|
|
16
16
|
"**/*.css"
|
|
17
17
|
],
|
|
18
|
-
"types": "./dist/index.d.ts",
|
|
19
18
|
"type": "module",
|
|
20
19
|
"exports": {
|
|
21
20
|
".": {
|
|
22
|
-
"types": "./dist
|
|
23
|
-
"
|
|
21
|
+
"types": "./dist/+client.d.ts",
|
|
22
|
+
"svelte": "./dist/+client.js"
|
|
24
23
|
},
|
|
25
|
-
"./
|
|
26
|
-
"types": "./dist
|
|
27
|
-
"
|
|
24
|
+
"./server": {
|
|
25
|
+
"types": "./dist/+server.d.ts",
|
|
26
|
+
"import": "./dist/+server.js"
|
|
28
27
|
}
|
|
29
28
|
},
|
|
30
29
|
"peerDependencies": {
|
package/dist/index.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { getLoginRecognized, getVerification, useVerification, getStorage, getScopedFS, } from "./storage.js";
|
|
2
|
-
export { retrieveVerification } from "./storage-attest.js";
|
|
3
|
-
export { default as rawAttest } from "./verification/attest-remote.js";
|
|
4
|
-
export { trackReady } from "./trackready.js";
|
|
5
|
-
export { encode, decode } from "./utils-base36.js";
|
package/dist/index.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { getLoginRecognized, getVerification, useVerification, getStorage, getScopedFS, } from "./storage.js";
|
|
2
|
-
export { retrieveVerification } from "./storage-attest.js";
|
|
3
|
-
export { default as rawAttest } from "./verification/attest-remote.js";
|
|
4
|
-
export { trackReady } from "./trackready.js";
|
|
5
|
-
export { encode, decode } from "./utils-base36.js";
|
package/dist/storage/_cloud.d.ts
DELETED
package/dist/storage/_cloud.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const bytesToBinaryString = (bytes) => {
|
|
2
|
-
const chunkSize = 8192;
|
|
3
|
-
let result = "";
|
|
4
|
-
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
5
|
-
const chunk = bytes.subarray(i, i + chunkSize);
|
|
6
|
-
result += String.fromCharCode.apply(null, chunk);
|
|
7
|
-
}
|
|
8
|
-
return result;
|
|
9
|
-
};
|
|
10
|
-
export const CLOUD_CACHE_KEY = "monoidentity-x/cloud-cache";
|
|
11
|
-
export const loadCloud = async (url, client) => {
|
|
12
|
-
// List bucket (source of truth)
|
|
13
|
-
const listResponse = await client.fetch(url);
|
|
14
|
-
const listXml = await listResponse.text();
|
|
15
|
-
const objects = [...listXml.matchAll(/<Key>(.*?)<\/Key>.*?<ETag>(.*?)<\/ETag>/gs)]
|
|
16
|
-
.map((m) => m.slice(1).map((s) => s.replaceAll(""", `"`).replaceAll("'", `'`)))
|
|
17
|
-
.map(([key, etag]) => ({ key, etag: etag.replaceAll(`"`, "") }))
|
|
18
|
-
.filter(({ key }) => !key.includes(".obsidian"));
|
|
19
|
-
// Build model by enriching list with content
|
|
20
|
-
const model = {};
|
|
21
|
-
const cache = JSON.parse(localStorage[CLOUD_CACHE_KEY] || "{}");
|
|
22
|
-
const newCache = {};
|
|
23
|
-
await Promise.all(objects.map(async ({ key, etag }) => {
|
|
24
|
-
// Cache hit: reuse existing content
|
|
25
|
-
if (cache[key]?.etag == etag) {
|
|
26
|
-
model[key] = cache[key].content;
|
|
27
|
-
newCache[key] = cache[key];
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
// Cache miss: fetch from S3
|
|
31
|
-
const response = await client.fetch(`${url}/${key}`);
|
|
32
|
-
const buffer = await response.arrayBuffer();
|
|
33
|
-
const content = bytesToBinaryString(new Uint8Array(buffer));
|
|
34
|
-
model[key] = content;
|
|
35
|
-
newCache[key] = { etag, content };
|
|
36
|
-
}));
|
|
37
|
-
localStorage[CLOUD_CACHE_KEY] = JSON.stringify(newCache);
|
|
38
|
-
return model;
|
|
39
|
-
};
|
package/dist/storage/_md5.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const md5: (s: string) => string;
|
package/dist/storage/_md5.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Based on https://github.com/jbt/tiny-hashes/blob/master/md5/md5.js
|
|
2
|
-
// @ts-nocheck
|
|
3
|
-
var k = [], i = 0;
|
|
4
|
-
for (; i < 64;) {
|
|
5
|
-
k[i] = 0 | (Math.sin(++i % Math.PI) * 4294967296);
|
|
6
|
-
}
|
|
7
|
-
export const md5 = (s) => {
|
|
8
|
-
for (let i = 0; i < s.length; i++) {
|
|
9
|
-
if (s.charCodeAt(i) > 255) {
|
|
10
|
-
// We weren't passed a binary string
|
|
11
|
-
console.log("?!");
|
|
12
|
-
s = new TextDecoder().decode(new TextEncoder().encode(s));
|
|
13
|
-
break;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
var b, c, d, h = [(b = 0x67452301), (c = 0xefcdab89), ~b, ~c], words = [], j = s + "\x80", a = j.length;
|
|
17
|
-
s = (--a / 4 + 2) | 15;
|
|
18
|
-
// See "Length bits" in notes
|
|
19
|
-
words[--s] = a * 8;
|
|
20
|
-
for (; ~a;) {
|
|
21
|
-
// a !== -1
|
|
22
|
-
words[a >> 2] |= j.charCodeAt(a) << (8 * a--);
|
|
23
|
-
}
|
|
24
|
-
for (i = j = 0; i < s; i += 16) {
|
|
25
|
-
a = h;
|
|
26
|
-
for (; j < 64; a = [
|
|
27
|
-
(d = a[3]),
|
|
28
|
-
b +
|
|
29
|
-
(((d =
|
|
30
|
-
a[0] +
|
|
31
|
-
[(b & c) | (~b & d), (d & b) | (~d & c), b ^ c ^ d, c ^ (b | ~d)][(a = j >> 4)] +
|
|
32
|
-
k[j] +
|
|
33
|
-
~~words[i | ([j, 5 * j + 1, 3 * j + 5, 7 * j][a] & 15)]) <<
|
|
34
|
-
(a = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21][4 * a + (j++ % 4)])) |
|
|
35
|
-
(d >>> -a)),
|
|
36
|
-
b,
|
|
37
|
-
c,
|
|
38
|
-
]) {
|
|
39
|
-
b = a[1] | 0;
|
|
40
|
-
c = a[2];
|
|
41
|
-
}
|
|
42
|
-
// See "Integer safety" in notes
|
|
43
|
-
for (j = 4; j;)
|
|
44
|
-
h[--j] += a[j];
|
|
45
|
-
// j === 0
|
|
46
|
-
}
|
|
47
|
-
for (s = ""; j < 32;) {
|
|
48
|
-
s += ((h[j >> 3] >> ((1 ^ j++) * 4)) & 15).toString(16);
|
|
49
|
-
// s += ((h[j >> 3] >> (4 ^ 4 * j++)) & 15).toString(16);
|
|
50
|
-
}
|
|
51
|
-
return s;
|
|
52
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Dict } from "./createstore.js";
|
|
2
|
-
type Modification = {
|
|
3
|
-
type: "set";
|
|
4
|
-
old?: string;
|
|
5
|
-
new: string;
|
|
6
|
-
} | {
|
|
7
|
-
type: "delete";
|
|
8
|
-
};
|
|
9
|
-
type Transmit = (path: string, mod: Modification) => Promise<void>;
|
|
10
|
-
export declare const wrapWithReplay: (storage: Dict) => {
|
|
11
|
-
proxy: Dict;
|
|
12
|
-
flush: () => Promise<void>;
|
|
13
|
-
setTransmit(tx: Transmit): void;
|
|
14
|
-
load(external: Dict): void;
|
|
15
|
-
};
|
|
16
|
-
export {};
|
package/dist/storage/_replay.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { throttle } from "./_timing.js";
|
|
2
|
-
export const wrapWithReplay = (storage) => {
|
|
3
|
-
let modifications = {};
|
|
4
|
-
if (localStorage["monoidentity-x/backup/modifications"]) {
|
|
5
|
-
modifications = JSON.parse(localStorage["monoidentity-x/backup/modifications"]);
|
|
6
|
-
}
|
|
7
|
-
const save = () => {
|
|
8
|
-
if (Object.keys(modifications).length) {
|
|
9
|
-
localStorage["monoidentity-x/backup/modifications"] = JSON.stringify(modifications);
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
delete localStorage["monoidentity-x/backup/modifications"];
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
window.addEventListener("beforeunload", save);
|
|
16
|
-
window.addEventListener("pagehide", save);
|
|
17
|
-
let transmit;
|
|
18
|
-
const flush = throttle(async () => {
|
|
19
|
-
try {
|
|
20
|
-
const tx = transmit;
|
|
21
|
-
if (!tx)
|
|
22
|
-
return;
|
|
23
|
-
const paths = Object.keys(modifications);
|
|
24
|
-
const tasks = paths.map((path) => (async () => {
|
|
25
|
-
const mod = modifications[path];
|
|
26
|
-
delete modifications[path];
|
|
27
|
-
if (mod.type == "set" && mod.old == mod.new) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
await tx(path, mod);
|
|
31
|
-
})()
|
|
32
|
-
.catch((err) => {
|
|
33
|
-
console.warn(`[monoidentity] transmitting "${path}" failed`, err);
|
|
34
|
-
}));
|
|
35
|
-
await Promise.all(tasks);
|
|
36
|
-
}
|
|
37
|
-
finally {
|
|
38
|
-
save();
|
|
39
|
-
}
|
|
40
|
-
}, 1000);
|
|
41
|
-
const proxy = new Proxy(storage, {
|
|
42
|
-
set(target, prop, value) {
|
|
43
|
-
let old = target[prop];
|
|
44
|
-
target[prop] = value;
|
|
45
|
-
const oldMod = modifications[prop];
|
|
46
|
-
if (oldMod?.type == "set")
|
|
47
|
-
old = oldMod.old;
|
|
48
|
-
modifications[prop] = { type: "set", old, new: value };
|
|
49
|
-
flush();
|
|
50
|
-
return true;
|
|
51
|
-
},
|
|
52
|
-
deleteProperty(target, prop) {
|
|
53
|
-
const success = delete target[prop];
|
|
54
|
-
if (success) {
|
|
55
|
-
modifications[prop] = { type: "delete" };
|
|
56
|
-
flush();
|
|
57
|
-
}
|
|
58
|
-
return success;
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
return {
|
|
62
|
-
proxy,
|
|
63
|
-
flush,
|
|
64
|
-
setTransmit(tx) {
|
|
65
|
-
transmit = tx;
|
|
66
|
-
},
|
|
67
|
-
// load() just resets to the external version
|
|
68
|
-
// if you have modifications, flush() them first
|
|
69
|
-
load(external) {
|
|
70
|
-
modifications = {};
|
|
71
|
-
save();
|
|
72
|
-
for (const [key, value] of Object.entries(external)) {
|
|
73
|
-
storage[key] = value;
|
|
74
|
-
}
|
|
75
|
-
for (const key of Object.keys(storage)) {
|
|
76
|
-
if (!(key in external)) {
|
|
77
|
-
delete storage[key];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// // If for *some* reason, there are pending modifications, rebase and resend them
|
|
81
|
-
// for (const [key, mod] of Object.entries(oldModifications)) {
|
|
82
|
-
// if (mod.type == "set") {
|
|
83
|
-
// if (proxy[key] != mod.old)
|
|
84
|
-
// console.warn(
|
|
85
|
-
// `[monoidentity] modification to "${key}" will be force applied over external change`,
|
|
86
|
-
// );
|
|
87
|
-
// proxy[key] = mod.new;
|
|
88
|
-
// } else if (mod.type == "delete") {
|
|
89
|
-
// delete proxy[key];
|
|
90
|
-
// }
|
|
91
|
-
// }
|
|
92
|
-
// flush();
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function throttle(fn: () => Promise<unknown>, delay: number): () => Promise<void>;
|
package/dist/storage/_timing.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export function throttle(fn, delay) {
|
|
2
|
-
let isRunning = false;
|
|
3
|
-
let hasPendingCall = false;
|
|
4
|
-
return async () => {
|
|
5
|
-
hasPendingCall = true;
|
|
6
|
-
if (isRunning) {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
try {
|
|
10
|
-
isRunning = true;
|
|
11
|
-
while (hasPendingCall) {
|
|
12
|
-
hasPendingCall = false;
|
|
13
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
14
|
-
await fn();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
finally {
|
|
18
|
-
isRunning = false;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const createLocalStorage: () => Record<string, string>;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { createStore } from "./createstore.js";
|
|
2
|
-
const prefix = "monoidentity/";
|
|
3
|
-
const prefixed = (key) => `${prefix}${key}`;
|
|
4
|
-
const unprefixed = (key) => {
|
|
5
|
-
if (!key.startsWith(prefix))
|
|
6
|
-
throw new Error("Key is not prefixed");
|
|
7
|
-
return key.slice(prefix.length);
|
|
8
|
-
};
|
|
9
|
-
export const createLocalStorage = () => createStore({
|
|
10
|
-
has(key) {
|
|
11
|
-
return typeof key == "string" && localStorage[prefixed(key)] !== undefined;
|
|
12
|
-
},
|
|
13
|
-
get(key) {
|
|
14
|
-
if (typeof key != "string")
|
|
15
|
-
return undefined;
|
|
16
|
-
return localStorage[prefixed(key)];
|
|
17
|
-
},
|
|
18
|
-
set(key, value) {
|
|
19
|
-
if (typeof key != "string")
|
|
20
|
-
return false;
|
|
21
|
-
localStorage[prefixed(key)] = value;
|
|
22
|
-
return true;
|
|
23
|
-
},
|
|
24
|
-
deleteProperty(key) {
|
|
25
|
-
if (typeof key != "string")
|
|
26
|
-
return false;
|
|
27
|
-
return delete localStorage[prefixed(key)];
|
|
28
|
-
},
|
|
29
|
-
ownKeys() {
|
|
30
|
-
const keys = [];
|
|
31
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
32
|
-
const key = localStorage.key(i);
|
|
33
|
-
if (key && key.startsWith(prefix)) {
|
|
34
|
-
keys.push(unprefixed(key));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return keys;
|
|
38
|
-
},
|
|
39
|
-
getOwnPropertyDescriptor(key) {
|
|
40
|
-
return Reflect.getOwnPropertyDescriptor(localStorage, prefixed(key));
|
|
41
|
-
},
|
|
42
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type Dict = Record<string, string>;
|
|
2
|
-
type ProxyHandlerWithoutTarget = {
|
|
3
|
-
has?(p: string | symbol): boolean;
|
|
4
|
-
get?(p: string | symbol, receiver: any): any;
|
|
5
|
-
set?(p: string | symbol, newValue: any, receiver: any): boolean;
|
|
6
|
-
deleteProperty?(p: string | symbol): boolean;
|
|
7
|
-
ownKeys?(): ArrayLike<string | symbol>;
|
|
8
|
-
getOwnPropertyDescriptor?(p: string | symbol): PropertyDescriptor | undefined;
|
|
9
|
-
};
|
|
10
|
-
export declare const createStore: <T>(implementation: ProxyHandlerWithoutTarget) => Record<string, T>;
|
|
11
|
-
export {};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export const createStore = (implementation) => {
|
|
2
|
-
const target = {};
|
|
3
|
-
const handler = {};
|
|
4
|
-
for (const key of Object.keys(implementation)) {
|
|
5
|
-
const trap = implementation[key];
|
|
6
|
-
handler[key] = (_, ...args) => trap(...args);
|
|
7
|
-
}
|
|
8
|
-
return new Proxy(target, handler);
|
|
9
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {} from "./createstore.js";
|
|
2
|
-
import { wrapWithReplay } from "./_replay.js";
|
|
3
|
-
import { canBackup } from "../utils-transport.js";
|
|
4
|
-
import { get, set, createStore } from "idb-keyval";
|
|
5
|
-
export const wrapBackup = (storage, requestBackup) => {
|
|
6
|
-
if (!canBackup)
|
|
7
|
-
return storage;
|
|
8
|
-
if (localStorage["monoidentity-x/backup"] == "off")
|
|
9
|
-
return storage;
|
|
10
|
-
const { proxy, flush, setTransmit, load } = wrapWithReplay(storage);
|
|
11
|
-
const store = createStore("monoidentity-x", "handles");
|
|
12
|
-
const getDir = async () => {
|
|
13
|
-
const handle = await get("backup", store);
|
|
14
|
-
if (!handle)
|
|
15
|
-
throw new Error("No backup handle found");
|
|
16
|
-
return handle;
|
|
17
|
-
};
|
|
18
|
-
const setDir = async (dir) => {
|
|
19
|
-
await set("backup", dir, store);
|
|
20
|
-
};
|
|
21
|
-
const init = async (dir) => {
|
|
22
|
-
let dirCache = {};
|
|
23
|
-
const getDirCached = async (route) => {
|
|
24
|
-
let key = "";
|
|
25
|
-
let parent = dir;
|
|
26
|
-
for (const path of route) {
|
|
27
|
-
key += "/";
|
|
28
|
-
key += path;
|
|
29
|
-
if (!dirCache[key]) {
|
|
30
|
-
dirCache[key] = await parent.getDirectoryHandle(path, { create: true });
|
|
31
|
-
}
|
|
32
|
-
parent = dirCache[key];
|
|
33
|
-
}
|
|
34
|
-
return parent;
|
|
35
|
-
};
|
|
36
|
-
setTransmit(async (path, mod) => {
|
|
37
|
-
const pathParts = path.split("/");
|
|
38
|
-
const parent = await getDirCached(pathParts.slice(0, -1));
|
|
39
|
-
if (mod.type == "set") {
|
|
40
|
-
const file = await parent.getFileHandle(pathParts.at(-1), { create: true });
|
|
41
|
-
const writable = await file.createWritable();
|
|
42
|
-
await writable.write(mod.new);
|
|
43
|
-
await writable.close();
|
|
44
|
-
}
|
|
45
|
-
else if (mod.type == "delete") {
|
|
46
|
-
await parent.removeEntry(pathParts.at(-1));
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
await flush();
|
|
50
|
-
};
|
|
51
|
-
if (localStorage["monoidentity-x/backup"] == "on") {
|
|
52
|
-
getDir()
|
|
53
|
-
.then((dir) => init(dir))
|
|
54
|
-
.catch(() => {
|
|
55
|
-
delete localStorage["monoidentity-x/backup"];
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
localStorage["monoidentity-x/backup"] = "off";
|
|
60
|
-
requestBackup(async () => {
|
|
61
|
-
const dir = await showDirectoryPicker({ mode: "readwrite" });
|
|
62
|
-
await setDir(dir);
|
|
63
|
-
await init(dir);
|
|
64
|
-
// Restore from backup
|
|
65
|
-
const backup = {};
|
|
66
|
-
const traverse = async (d, path) => {
|
|
67
|
-
for await (const entry of d.values()) {
|
|
68
|
-
if (entry.kind == "file") {
|
|
69
|
-
const file = await entry.getFile();
|
|
70
|
-
const text = await file.text();
|
|
71
|
-
backup[`${path}${entry.name}`] = text;
|
|
72
|
-
}
|
|
73
|
-
else if (entry.kind == "directory") {
|
|
74
|
-
await traverse(entry, `${path}${entry.name}/`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
await traverse(dir, "");
|
|
79
|
-
const hasBackup = Boolean(Object.keys(backup).length);
|
|
80
|
-
if (hasBackup)
|
|
81
|
-
load(backup);
|
|
82
|
-
localStorage["monoidentity-x/backup"] = "on";
|
|
83
|
-
if (hasBackup)
|
|
84
|
-
location.reload();
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return proxy;
|
|
88
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { wrapWithReplay } from "./_replay.js";
|
|
2
|
-
import { AwsClient } from "aws4fetch";
|
|
3
|
-
import { CLOUD_CACHE_KEY, loadCloud } from "./_cloud.js";
|
|
4
|
-
import { md5 } from "./_md5.js";
|
|
5
|
-
export const wrapCloud = async (storage, bucket) => {
|
|
6
|
-
const client = new AwsClient({
|
|
7
|
-
accessKeyId: bucket.accessKeyId,
|
|
8
|
-
secretAccessKey: bucket.secretAccessKey,
|
|
9
|
-
});
|
|
10
|
-
const { proxy, setTransmit, flush, load } = wrapWithReplay(storage);
|
|
11
|
-
const isFirstLoad = !localStorage[CLOUD_CACHE_KEY];
|
|
12
|
-
if (isFirstLoad) {
|
|
13
|
-
const data = await loadCloud(bucket.base, client);
|
|
14
|
-
load(data);
|
|
15
|
-
}
|
|
16
|
-
setTransmit(async (path, mod) => {
|
|
17
|
-
if (mod.type == "set") {
|
|
18
|
-
const url = `${bucket.base}/${path}`;
|
|
19
|
-
const headers = new Headers();
|
|
20
|
-
if (mod.old) {
|
|
21
|
-
// Update only if current matches our known prior content
|
|
22
|
-
const etagFromOld = md5(mod.old);
|
|
23
|
-
headers.set("If-Match", `"${etagFromOld}"`);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
// Create only if it does not already exist
|
|
27
|
-
headers.set("If-None-Match", "*");
|
|
28
|
-
}
|
|
29
|
-
const r = await client.fetch(url, {
|
|
30
|
-
method: "PUT",
|
|
31
|
-
headers,
|
|
32
|
-
body: mod.new,
|
|
33
|
-
});
|
|
34
|
-
if (!r.ok) {
|
|
35
|
-
throw new Error(`Cloud is ${r.status}ing`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else if (mod.type == "delete") {
|
|
39
|
-
const url = `${bucket.base}/${path}`;
|
|
40
|
-
const r = await client.fetch(url, {
|
|
41
|
-
method: "DELETE",
|
|
42
|
-
});
|
|
43
|
-
if (!r.ok)
|
|
44
|
-
throw new Error(`Cloud is ${r.status}ing`);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
if (!isFirstLoad) {
|
|
48
|
-
flush().then(async () => {
|
|
49
|
-
const data = await loadCloud(bucket.base, client);
|
|
50
|
-
load(data);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return proxy;
|
|
54
|
-
};
|
|
File without changes
|
|
File without changes
|