monoidentity 0.30.1 → 0.31.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.
Files changed (36) hide show
  1. package/dist/{storage.d.ts → +index.d.ts} +1 -1
  2. package/dist/{storage.js → +index.js} +4 -3
  3. package/dist/+receive-callback.js +1 -1
  4. package/dist/+types.d.ts +1 -0
  5. package/dist/storageclient.svelte.d.ts +1 -3
  6. package/dist/storageclient.svelte.js +9 -9
  7. package/package.json +5 -11
  8. package/dist/+client.d.ts +0 -4
  9. package/dist/+client.js +0 -4
  10. package/dist/bucket-create.remote.d.ts +0 -1
  11. package/dist/bucket-create.remote.js +0 -126
  12. package/dist/client.d.ts +0 -8
  13. package/dist/old/storage/_backupcloud.d.ts +0 -2
  14. package/dist/old/storage/_backupcloud.js +0 -23
  15. package/dist/old/storage/backupcloud-connection.d.ts +0 -3
  16. package/dist/old/storage/backupcloud-connection.js +0 -9
  17. package/dist/old/storage/backupcloud-pull.d.ts +0 -4
  18. package/dist/old/storage/backupcloud-pull.js +0 -95
  19. package/dist/old/storage/backupcloud-push.d.ts +0 -2
  20. package/dist/old/storage/backupcloud-push.js +0 -56
  21. package/dist/old/storage/backuplocally-pull.d.ts +0 -1
  22. package/dist/old/storage/backuplocally-pull.js +0 -58
  23. package/dist/old/storage/backuplocally-push.d.ts +0 -1
  24. package/dist/old/storage/backuplocally-push.js +0 -63
  25. package/dist/old/storage/utils-idb.d.ts +0 -1
  26. package/dist/old/storage/utils-idb.js +0 -2
  27. package/dist/old/storage/utils-storage.d.ts +0 -1
  28. package/dist/old/storage/utils-storage.js +0 -1
  29. package/dist/old/storage/utils-sync.d.ts +0 -13
  30. package/dist/old/storage/utils-sync.js +0 -58
  31. package/dist/old/utils-localstorage.d.ts +0 -1
  32. package/dist/old/utils-localstorage.js +0 -1
  33. package/dist/utils-base36.d.ts +0 -2
  34. package/dist/utils-base36.js +0 -16
  35. package/dist/utils-transport.d.ts +0 -32
  36. package/dist/utils-transport.js +0 -2
@@ -4,6 +4,6 @@ export declare const getLoginRecognized: () => {
4
4
  };
5
5
  export declare const setLoginRecognized: (login: string) => void;
6
6
  export declare const openHub: (path: string) => never;
7
- export declare const VERIFICATION_PATH = ".local/verification.jwt";
7
+ export declare const relog: () => never;
8
8
  export declare const getStorage: (realm: "config" | "userdata" | "cache" | (string & {})) => Record<string, any>;
9
9
  export declare const getScopedFS: (dir: string) => Record<string, any>;
@@ -1,8 +1,9 @@
1
1
  import { stringify, parse } from 'devalue';
2
2
  import { parse as useSchema } from 'valibot';
3
- import { decode } from './utils-base36.js';
4
- import { login as loginSchema } from './utils-transport.js';
3
+ import { decode } from 'base36-esm';
5
4
  import { storageClient } from './storageclient.svelte.js';
5
+ import { object, pipe, email, string } from 'valibot';
6
+ const loginSchema = object({ email: pipe(string(), email()), password: string() });
6
7
  const LOGIN_RECOGNIZED_PATH = '.local/login.encjson';
7
8
  export const getLoginRecognized = () => {
8
9
  const client = storageClient();
@@ -30,7 +31,7 @@ export const openHub = (path) => {
30
31
  location.href = target.toString();
31
32
  throw new Error('relogging');
32
33
  };
33
- export const VERIFICATION_PATH = '.local/verification.jwt';
34
+ export const relog = () => openHub(MONOIDENTITY_APP_ID);
34
35
  export const getStorage = (realm) => {
35
36
  const prefix = `.${realm}/${MONOIDENTITY_APP_ID}/`;
36
37
  return storageClient((key) => `${prefix}${key}.devalue`, (key) => (key.startsWith(prefix) ? key.slice(prefix.length, -'.devalue'.length) : undefined), stringify, parse);
@@ -1,4 +1,4 @@
1
- import { setLoginRecognized } from './storage.js';
1
+ import { setLoginRecognized } from './+index.js';
2
2
  const params = new URLSearchParams(location.hash.slice(1));
3
3
  const monoidentityloginrecognized = params.get('monoidentityloginrecognized');
4
4
  if (monoidentityloginrecognized) {
@@ -0,0 +1 @@
1
+ declare const MONOIDENTITY_APP_ID: string;
@@ -1,11 +1,9 @@
1
- export declare const waitForSync: (key: string) => Promise<void>;
2
1
  declare global {
3
2
  interface WindowEventMap {
4
- "monoidentity-storage": CustomEvent<{
3
+ 'monoidentity-storage': CustomEvent<{
5
4
  key: string;
6
5
  value: string | undefined;
7
6
  }>;
8
7
  }
9
8
  }
10
- export declare const STORAGE_EVENT = "monoidentity-storage";
11
9
  export declare const storageClient: (prefix?: (key: string) => string, unprefix?: (key: string) => string | undefined, serialize?: (data: any) => string, deserialize?: (data: string) => any) => Record<string, any>;
@@ -1,8 +1,8 @@
1
- import { SYNC_REQUEST_EVENT } from "./old/storage/utils-sync.js";
2
- export const waitForSync = async (key) => {
1
+ const SYNC_REQUEST_EVENT = 'monoidentity-sync-request';
2
+ const STORAGE_EVENT = 'monoidentity-storage';
3
+ const waitForSync = async (key) => {
3
4
  await new Promise((resolve, reject) => window.dispatchEvent(new CustomEvent(SYNC_REQUEST_EVENT, { detail: { key, resolve, reject } })));
4
5
  };
5
- export const STORAGE_EVENT = "monoidentity-storage";
6
6
  const announce = (key, value) => {
7
7
  // Announce to all, even third parties
8
8
  window.dispatchEvent(new CustomEvent(STORAGE_EVENT, { detail: { key, value } }));
@@ -14,7 +14,7 @@ const increment = (key) => {
14
14
  allCounter++;
15
15
  };
16
16
  addEventListener(STORAGE_EVENT, (event) => increment(event.detail.key));
17
- addEventListener("storage", (event) => {
17
+ addEventListener('storage', (event) => {
18
18
  if (event.storageArea != localStorage)
19
19
  return;
20
20
  if (!event.key)
@@ -32,9 +32,9 @@ export const storageClient = (prefix, unprefix, serialize, deserialize) => {
32
32
  const getScopedKeys = () => {
33
33
  const keys = [];
34
34
  for (const key in localStorage) {
35
- if (!key.startsWith("monoidentity/"))
35
+ if (!key.startsWith('monoidentity/'))
36
36
  continue;
37
- let scopedKey = key.slice("monoidentity/".length);
37
+ let scopedKey = key.slice('monoidentity/'.length);
38
38
  if (unprefix) {
39
39
  const unprefixed = unprefix(scopedKey);
40
40
  if (!unprefixed)
@@ -47,9 +47,9 @@ export const storageClient = (prefix, unprefix, serialize, deserialize) => {
47
47
  };
48
48
  return new Proxy({}, {
49
49
  get(_, key) {
50
- if (typeof key == "symbol")
50
+ if (typeof key == 'symbol')
51
51
  return undefined;
52
- if (key == "sync") {
52
+ if (key == 'sync') {
53
53
  return async (userKey) => {
54
54
  await waitForSync(prefix(userKey));
55
55
  };
@@ -68,7 +68,7 @@ export const storageClient = (prefix, unprefix, serialize, deserialize) => {
68
68
  announce(key, value);
69
69
  }
70
70
  else {
71
- console.debug("[monoidentity storage] noop for", key);
71
+ console.debug('[monoidentity storage] noop for', key);
72
72
  }
73
73
  return true;
74
74
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monoidentity",
3
- "version": "0.30.1",
3
+ "version": "0.31.1",
4
4
  "license": "ISC",
5
5
  "repository": "KTibow/monoidentity",
6
6
  "author": {
@@ -20,8 +20,8 @@
20
20
  "type": "module",
21
21
  "exports": {
22
22
  ".": {
23
- "types": "./dist/+client.d.ts",
24
- "svelte": "./dist/+client.js"
23
+ "types": "./dist/+index.d.ts",
24
+ "svelte": "./dist/+index.js"
25
25
  },
26
26
  "./receive-callback": {
27
27
  "types": "./dist/+receive-callback.d.ts",
@@ -32,9 +32,8 @@
32
32
  "svelte": "^5.0.0"
33
33
  },
34
34
  "dependencies": {
35
- "aws4fetch": "^1.0.20",
35
+ "base36-esm": "^0.1.0",
36
36
  "devalue": "^5.6.2",
37
- "idb-keyval": "^6.2.2",
38
37
  "valibot": "^1.2.0"
39
38
  },
40
39
  "devDependencies": {
@@ -42,13 +41,9 @@
42
41
  "@sveltejs/kit": "^2.51.0",
43
42
  "@sveltejs/package": "^2.5.7",
44
43
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
45
- "@types/wicg-file-system-access": "^2023.10.7",
46
- "monoserve": "^3.2.4",
47
44
  "publint": "^0.3.17",
48
- "rolldown": "1.0.0-rc.4",
49
45
  "svelte": "^5.51.0",
50
46
  "svelte-check": "^4.4.0",
51
- "tinyglobby": "^0.2.15",
52
47
  "vite": "8.0.0-beta.15"
53
48
  },
54
49
  "keywords": [
@@ -58,7 +53,6 @@
58
53
  "dev": "vite dev",
59
54
  "build": "pnpm run prepack",
60
55
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
61
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
62
- "knip": "knip"
56
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
63
57
  }
64
58
  }
package/dist/+client.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { encode, decode } from './utils-base36.js';
2
- export { getLoginRecognized, openHub, getStorage, getScopedFS, VERIFICATION_PATH, } from './storage.js';
3
- export type { SyncStrategy } from './client.js';
4
- export type { Bucket, StorageSetup } from './utils-transport.js';
package/dist/+client.js DELETED
@@ -1,4 +0,0 @@
1
- // common
2
- export { encode, decode } from './utils-base36.js';
3
- // storage
4
- export { getLoginRecognized, openHub, getStorage, getScopedFS, VERIFICATION_PATH, } from './storage.js';
@@ -1 +0,0 @@
1
- export {};
@@ -1,126 +0,0 @@
1
- "use strict";
2
- // import { fn } from "monoserve";
3
- // import { string } from "valibot";
4
- // import { encodeBucket } from "./specific-utils";
5
- // import { useVerification } from "monoidentity/server";
6
- // import { CF_ACCOUNT_ID, CF_KEY } from "$env/static/private";
7
- // const KV_NAMESPACE_ID = "6b33cf77a0bf4a029bc17b738c9f2cdb";
8
- // async function sha256(text: string): Promise<string> {
9
- // const encoder = new TextEncoder();
10
- // const data = encoder.encode(text);
11
- // const hashBuffer = await crypto.subtle.digest("SHA-256", data);
12
- // const hashArray = Array.from(new Uint8Array(hashBuffer));
13
- // return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
14
- // }
15
- // export default fn(string(), async (jwt) => {
16
- // // Verify JWT and get user email
17
- // const { payload } = await useVerification(jwt);
18
- // const user = payload.sub!.replace(/[@.]/g, "-");
19
- // // Check if credentials already exist in KV
20
- // const existingCreds = await fetch(
21
- // `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/storage/kv/namespaces/${KV_NAMESPACE_ID}/values/${user}`,
22
- // {
23
- // headers: { authorization: `Bearer ${CF_KEY}` },
24
- // },
25
- // );
26
- // if (existingCreds.ok) {
27
- // return await existingCreds.text(); // Already encoded
28
- // }
29
- // // Generate unique bucket name
30
- // const bucketName = `monoidentity-cloud-${user}`;
31
- // // 1. Create R2 bucket
32
- // const bucketRes = await fetch(
33
- // `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/r2/buckets`,
34
- // {
35
- // method: "POST",
36
- // headers: {
37
- // authorization: `Bearer ${CF_KEY}`,
38
- // "content-type": "application/json",
39
- // },
40
- // body: JSON.stringify({ name: bucketName }),
41
- // },
42
- // );
43
- // if (!bucketRes.ok) {
44
- // throw new Error(`Failed to create bucket: ${await bucketRes.text()}`);
45
- // }
46
- // // 2. Set CORS policy
47
- // const corsConfig = {
48
- // rules: [
49
- // {
50
- // allowed: {
51
- // origins: ["*"],
52
- // methods: ["GET", "PUT", "POST", "DELETE", "HEAD"],
53
- // headers: [
54
- // "authorization",
55
- // "content-type",
56
- // "if-match",
57
- // "if-none-match",
58
- // "x-amz-date",
59
- // "x-amz-content-sha256",
60
- // ],
61
- // },
62
- // exposeHeaders: ["ETag"],
63
- // maxAgeSeconds: 3600,
64
- // },
65
- // ],
66
- // };
67
- // const corsRes = await fetch(
68
- // `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/r2/buckets/${bucketName}/cors`,
69
- // {
70
- // method: "PUT",
71
- // headers: {
72
- // authorization: `Bearer ${CF_KEY}`,
73
- // "content-type": "application/json",
74
- // },
75
- // body: JSON.stringify(corsConfig),
76
- // },
77
- // );
78
- // if (!corsRes.ok) {
79
- // throw new Error(`Failed to set CORS: ${await corsRes.text()}`);
80
- // }
81
- // // 3. Create scoped API token
82
- // const tokenRes = await fetch(`https://api.cloudflare.com/client/v4/user/tokens`, {
83
- // method: "POST",
84
- // headers: {
85
- // authorization: `Bearer ${CF_KEY}`,
86
- // "content-type": "application/json",
87
- // },
88
- // body: JSON.stringify({
89
- // name: `monoidentity-cloud-token-${user}`,
90
- // policies: [
91
- // {
92
- // effect: "allow",
93
- // resources: {
94
- // [`com.cloudflare.edge.r2.bucket.${CF_ACCOUNT_ID}_default_${bucketName}`]: "*",
95
- // },
96
- // permission_groups: [{ id: "2efd5506f9c8494dacb1fa10a3e7d5b6" }], // Workers R2 Storage Bucket Item Write
97
- // },
98
- // ],
99
- // }),
100
- // });
101
- // if (!tokenRes.ok) {
102
- // throw new Error(`Failed to create token: ${await tokenRes.text()}`);
103
- // }
104
- // const {
105
- // result: { id: accessKeyId, value: secretAccessKeyInput },
106
- // } = await tokenRes.json();
107
- // const secretAccessKey = await sha256(secretAccessKeyInput);
108
- // // 4. Encode and store in KV
109
- // const encoded = encodeBucket({
110
- // base: `https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com/${bucketName}`,
111
- // accessKeyId,
112
- // secretAccessKey,
113
- // });
114
- // const kvRes = await fetch(
115
- // `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/storage/kv/namespaces/${KV_NAMESPACE_ID}/values/${user}`,
116
- // {
117
- // method: "PUT",
118
- // headers: { authorization: `Bearer ${CF_KEY}` },
119
- // body: encoded,
120
- // },
121
- // );
122
- // if (!kvRes.ok) {
123
- // throw new Error(`Failed to store credentials in KV: ${await kvRes.text()}`);
124
- // }
125
- // return encoded;
126
- // });
package/dist/client.d.ts DELETED
@@ -1,8 +0,0 @@
1
- export type SyncStrategy =
2
- | undefined // do not upload or download; will be deleted on next sync unless shouldPersist(key)
3
- | { mode: "immediate" } // instant sync (e.g., config)
4
- | { mode: "debounced"; debounceMs: number }; // queued sync (e.g., notes, drawings, chats)
5
- declare global {
6
- declare const MONOIDENTITY_APP_ID: string;
7
- declare const MONOIDENTITY_SYNC_FOR: (path: string) => SyncStrategy;
8
- }
@@ -1,2 +0,0 @@
1
- export declare const encodeCloudContent: (key: string, value: string) => string | Uint8Array<ArrayBuffer>;
2
- export declare const decodeCloudContent: (key: string, response: Response) => Promise<string>;
@@ -1,23 +0,0 @@
1
- const isPlainTextCloudObject = (key) => key.endsWith(".md") || key.endsWith(".devalue");
2
- export const encodeCloudContent = (key, value) => {
3
- if (isPlainTextCloudObject(key)) {
4
- return value;
5
- }
6
- const bytes = new Uint8Array(value.length);
7
- for (let i = 0; i < value.length; i++) {
8
- bytes[i] = value.charCodeAt(i);
9
- }
10
- return bytes;
11
- };
12
- export const decodeCloudContent = async (key, response) => {
13
- if (isPlainTextCloudObject(key)) {
14
- return response.text();
15
- }
16
- const buf = new Uint8Array(await response.arrayBuffer());
17
- let content = "";
18
- const chunk = 8192;
19
- for (let i = 0; i < buf.length; i += chunk) {
20
- content += String.fromCharCode.apply(null, buf.subarray(i, i + chunk));
21
- }
22
- return content;
23
- };
@@ -1,3 +0,0 @@
1
- import type { Bucket } from "../utils-transport.js";
2
- export type AwsFetch = (path: string, options?: RequestInit) => Promise<Response>;
3
- export declare const createCloudClient: (bucket: Bucket) => AwsFetch;
@@ -1,9 +0,0 @@
1
- import { AwsClient } from "aws4fetch";
2
- export const createCloudClient = (bucket) => {
3
- const awsClient = new AwsClient({
4
- accessKeyId: bucket.accessKeyId,
5
- secretAccessKey: bucket.secretAccessKey,
6
- });
7
- const base = bucket.base.endsWith("/") ? bucket.base : bucket.base + "/";
8
- return (path, options) => awsClient.fetch(base + path, { ...options, aws: { signQuery: true } });
9
- };
@@ -1,4 +0,0 @@
1
- import type { AwsFetch } from "./backupcloud-connection.js";
2
- export declare const setCloudCacheEntry: (key: string, etag: string, content: string) => Promise<void>;
3
- export declare const pullFromCloud: (client: AwsFetch) => Promise<void>;
4
- export declare const mountCloudPull: (client: AwsFetch, signal: AbortSignal) => () => void;
@@ -1,95 +0,0 @@
1
- import { storageClient } from "./storageclient.svelte.js";
2
- import { addSync } from "./utils-sync.js";
3
- import { shouldPersist } from "./utils-storage.js";
4
- import { get, set } from "idb-keyval";
5
- import { store } from "./utils-idb.js";
6
- import { decodeCloudContent } from "./_backupcloud.js";
7
- const CLOUD_CACHE_KEY = "cloud-cache";
8
- let cache;
9
- const initCache = async () => {
10
- cache = (await get(CLOUD_CACHE_KEY, store)) || {};
11
- };
12
- const getCache = () => {
13
- if (!cache)
14
- throw new Error("Cache not initialized");
15
- return cache;
16
- };
17
- const saveCache = async () => {
18
- await set(CLOUD_CACHE_KEY, getCache(), store);
19
- };
20
- export const setCloudCacheEntry = async (key, etag, content) => {
21
- await initCache();
22
- getCache()[key] = { etag, content };
23
- await saveCache();
24
- };
25
- const listCloud = async (client) => {
26
- const listResp = await client("");
27
- if (!listResp.ok)
28
- throw new Error(`List bucket failed: ${listResp.status}`);
29
- const listXml = await listResp.text();
30
- return [...listXml.matchAll(/<Key>(.*?)<\/Key>.*?<ETag>(.*?)<\/ETag>/gs)]
31
- .map((m) => m.slice(1).map((s) => s.replaceAll("&quot;", `"`).replaceAll("&apos;", `'`)))
32
- .map(([key, etag]) => ({ key, etag: etag.replaceAll(`"`, "") }))
33
- .filter(({ key }) => MONOIDENTITY_SYNC_FOR(key));
34
- };
35
- const loadFromCloud = async (objects, client) => {
36
- const prevCache = getCache();
37
- const nextCache = {};
38
- const model = {};
39
- await Promise.all(objects.map(async ({ key, etag }) => {
40
- const cached = prevCache[key];
41
- if (cached?.etag == etag) {
42
- model[key] = cached.content;
43
- nextCache[key] = cached;
44
- return;
45
- }
46
- console.debug("[monoidentity cloud] loading", key);
47
- const r = await client(key);
48
- if (!r.ok)
49
- throw new Error(`Fetch ${key} failed: ${r.status}`);
50
- const content = await decodeCloudContent(key, r);
51
- model[key] = content;
52
- nextCache[key] = { etag, content };
53
- }));
54
- cache = nextCache;
55
- await saveCache();
56
- return model;
57
- };
58
- const _pullFromCloud = async (client) => {
59
- const cachePromise = initCache();
60
- const objects = await listCloud(client);
61
- await cachePromise;
62
- const remote = await loadFromCloud(objects, client);
63
- const local = storageClient();
64
- for (const key of Object.keys(local)) {
65
- if (key in remote)
66
- continue;
67
- if (shouldPersist(key))
68
- continue;
69
- delete local[key];
70
- }
71
- for (const [key, value] of Object.entries(remote)) {
72
- if (local[key] == value)
73
- continue;
74
- local[key] = value;
75
- }
76
- };
77
- export const pullFromCloud = async (client) => {
78
- const promise = _pullFromCloud(client);
79
- addSync("*", promise);
80
- await promise;
81
- };
82
- export const mountCloudPull = (client, signal) => {
83
- signal.throwIfAborted();
84
- const syncIntervalId = setInterval(() => {
85
- pullFromCloud(client).catch((err) => {
86
- console.error("[monoidentity cloud] pull failed", err);
87
- });
88
- }, 15 * 60 * 1000);
89
- const cleanup = () => {
90
- clearInterval(syncIntervalId);
91
- signal.removeEventListener("abort", cleanup);
92
- };
93
- signal.addEventListener("abort", cleanup, { once: true });
94
- return cleanup;
95
- };
@@ -1,2 +0,0 @@
1
- import type { AwsFetch } from "./backupcloud-connection.js";
2
- export declare const mountCloudPush: (client: AwsFetch, signal: AbortSignal) => () => void;
@@ -1,56 +0,0 @@
1
- import { STORAGE_EVENT } from "./storageclient.svelte.js";
2
- import { addSync, scheduleSync } from "./utils-sync.js";
3
- import { shouldPersist } from "./utils-storage.js";
4
- import { setCloudCacheEntry } from "./backupcloud-pull.js";
5
- import { encodeCloudContent } from "./_backupcloud.js";
6
- const write = async (key, value, client) => {
7
- console.debug("[monoidentity cloud] saving", key);
8
- if (value != undefined) {
9
- const r = await client(key, {
10
- method: "PUT",
11
- headers: { "content-type": "application/octet-stream" },
12
- body: encodeCloudContent(key, value),
13
- });
14
- if (!r.ok)
15
- throw new Error(`PUT ${key} failed: ${r.status}`);
16
- const etag = r.headers.get("etag")?.replaceAll('"', "");
17
- if (etag) {
18
- await setCloudCacheEntry(key, etag, value);
19
- }
20
- return;
21
- }
22
- const r = await client(key, { method: "DELETE" });
23
- if (!r.ok && r.status != 404)
24
- throw new Error(`DELETE ${key} failed: ${r.status}`);
25
- };
26
- export const mountCloudPush = (client, signal) => {
27
- signal.throwIfAborted();
28
- const writeWrapped = async (key, value) => write(key, value, client).catch((err) => {
29
- console.error("[monoidentity cloud] save failed", key, err);
30
- });
31
- const listener = (event) => {
32
- const fullKey = event.detail.key;
33
- if (!fullKey.startsWith("monoidentity/"))
34
- return;
35
- const key = fullKey.slice("monoidentity/".length);
36
- const strategy = MONOIDENTITY_SYNC_FOR(key);
37
- if (!strategy) {
38
- if (!shouldPersist(key))
39
- console.warn("[monoidentity cloud]", key, "isn't marked to be synced");
40
- return;
41
- }
42
- if (strategy.mode == "immediate") {
43
- addSync(fullKey, writeWrapped(key, event.detail.value));
44
- }
45
- else if (strategy.mode == "debounced") {
46
- scheduleSync(fullKey, () => writeWrapped(key, localStorage[fullKey]), strategy.debounceMs);
47
- }
48
- };
49
- addEventListener(STORAGE_EVENT, listener);
50
- const cleanup = () => {
51
- removeEventListener(STORAGE_EVENT, listener);
52
- signal.removeEventListener("abort", cleanup);
53
- };
54
- signal.addEventListener("abort", cleanup, { once: true });
55
- return cleanup;
56
- };
@@ -1 +0,0 @@
1
- export declare const pullFromLocalBackup: (requestBackup: (startBackup: () => void) => void) => Promise<FileSystemDirectoryHandle | undefined>;
@@ -1,58 +0,0 @@
1
- import { get, set } from "idb-keyval";
2
- import { canBackup } from "../utils-localstorage.js";
3
- import { storageClient } from "./storageclient.svelte.js";
4
- import { store } from "./utils-idb.js";
5
- import { addSync } from "./utils-sync.js";
6
- const HANDLE_KEY = "backup-handle";
7
- const HANDLE_DISABLED_KEY = "backup-handle-disabled";
8
- const restoreFromDir = async (dir) => {
9
- const backup = {};
10
- const traverse = async (d, path) => {
11
- for await (const entry of d.values()) {
12
- if (entry.kind == "file") {
13
- const file = await entry.getFile();
14
- const text = await file.text();
15
- backup[`${path}${entry.name}`] = text;
16
- }
17
- else if (entry.kind == "directory") {
18
- await traverse(entry, `${path}${entry.name}/`);
19
- }
20
- }
21
- };
22
- await traverse(dir, "");
23
- if (!Object.keys(backup).length)
24
- return;
25
- const client = storageClient();
26
- for (const key in backup) {
27
- console.debug("[monoidentity local] loading", key);
28
- client[key] = backup[key];
29
- }
30
- location.reload();
31
- };
32
- export const pullFromLocalBackup = async (requestBackup) => {
33
- if (!canBackup)
34
- return;
35
- const dir = await get(HANDLE_KEY, store);
36
- if (dir) {
37
- return dir;
38
- }
39
- const disabled = await get(HANDLE_DISABLED_KEY, store);
40
- if (disabled) {
41
- return;
42
- }
43
- await set(HANDLE_DISABLED_KEY, true);
44
- return new Promise((resolve, reject) => requestBackup(async () => {
45
- try {
46
- const dir = await showDirectoryPicker({ mode: "readwrite" });
47
- await set(HANDLE_KEY, dir, store);
48
- await set(HANDLE_DISABLED_KEY, false);
49
- const restorePromise = restoreFromDir(dir);
50
- addSync("*", restorePromise);
51
- await restorePromise;
52
- resolve(dir);
53
- }
54
- catch (error) {
55
- reject(error);
56
- }
57
- }));
58
- };
@@ -1 +0,0 @@
1
- export declare const mountLocalBackupPush: (dir: FileSystemDirectoryHandle, signal: AbortSignal) => () => void;
@@ -1,63 +0,0 @@
1
- import { STORAGE_EVENT } from "./storageclient.svelte.js";
2
- import { shouldPersist } from "./utils-storage.js";
3
- import { addSync } from "./utils-sync.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
- const writeFile = async (key, value) => {
20
- const pathParts = key.split("/");
21
- const name = pathParts.at(-1);
22
- console.debug("[monoidentity local] saving", name);
23
- const parent = await getDirCached(pathParts.slice(0, -1));
24
- if (value != undefined) {
25
- const file = await parent.getFileHandle(name, { create: true });
26
- const writable = await file.createWritable();
27
- await writable.write(value);
28
- await writable.close();
29
- return;
30
- }
31
- await parent.removeEntry(name);
32
- };
33
- const listener = (event) => {
34
- const fullKey = event.detail.key;
35
- if (!fullKey.startsWith("monoidentity/"))
36
- return;
37
- const key = fullKey.slice("monoidentity/".length);
38
- const strategy = MONOIDENTITY_SYNC_FOR(key);
39
- if (!strategy) {
40
- if (!shouldPersist(key))
41
- console.warn("[monoidentity local]", key, "isn't marked to be backed up or saved");
42
- return;
43
- }
44
- addSync(key, writeFile(key, event.detail.value));
45
- };
46
- addEventListener(STORAGE_EVENT, listener);
47
- return () => {
48
- removeEventListener(STORAGE_EVENT, listener);
49
- };
50
- };
51
- export const mountLocalBackupPush = (dir, signal) => {
52
- signal.throwIfAborted();
53
- const unmount = saveToDir(dir);
54
- const cleanup = () => {
55
- unmount();
56
- signal.removeEventListener("abort", onAbort);
57
- };
58
- const onAbort = () => {
59
- cleanup();
60
- };
61
- signal.addEventListener("abort", onAbort, { once: true });
62
- return cleanup;
63
- };
@@ -1 +0,0 @@
1
- export declare const store: import("idb-keyval").UseStore;
@@ -1,2 +0,0 @@
1
- import { createStore } from "idb-keyval";
2
- export const store = createStore("monoidentity-x", "keyval");
@@ -1 +0,0 @@
1
- export declare const shouldPersist: (key: string) => boolean;
@@ -1 +0,0 @@
1
- export const shouldPersist = (key) => key.startsWith(".cache/") || key.startsWith(".local/");
@@ -1,13 +0,0 @@
1
- export type SyncRequestDetail = {
2
- key: string;
3
- resolve?: () => void;
4
- reject?: (reason?: unknown) => void;
5
- };
6
- declare global {
7
- interface WindowEventMap {
8
- "monoidentity-sync-request": CustomEvent<SyncRequestDetail>;
9
- }
10
- }
11
- export declare const SYNC_REQUEST_EVENT = "monoidentity-sync-request";
12
- export declare const addSync: (key: string, promise: Promise<void>) => void;
13
- export declare const scheduleSync: (key: string, fn: () => Promise<void>, delay?: number) => void;
@@ -1,58 +0,0 @@
1
- export const SYNC_REQUEST_EVENT = "monoidentity-sync-request";
2
- const activeSyncs = {};
3
- const scheduledSyncs = {};
4
- export const addSync = (key, promise) => {
5
- const tracked = promise.catch((e) => {
6
- console.error(`[monoidentity] ${key} sync failed`, e);
7
- });
8
- activeSyncs[key] = key in activeSyncs ? activeSyncs[key].then(() => tracked) : tracked;
9
- };
10
- const runScheduledSync = async (key) => {
11
- const scheduled = scheduledSyncs[key];
12
- if (!scheduled)
13
- return;
14
- delete scheduledSyncs[key];
15
- const promise = scheduled.fn();
16
- addSync(key, promise);
17
- await promise;
18
- };
19
- const scheduleInterval = setInterval(() => {
20
- const now = Date.now();
21
- for (const [key, scheduled] of Object.entries(scheduledSyncs)) {
22
- if (scheduled.executeAt <= now) {
23
- runScheduledSync(key);
24
- }
25
- }
26
- }, 100);
27
- const waitForTrackedSync = async (key) => {
28
- if (key != "*") {
29
- await waitForTrackedSync("*");
30
- }
31
- if (key in activeSyncs) {
32
- await activeSyncs[key];
33
- }
34
- if (key != "*" && key in scheduledSyncs) {
35
- await runScheduledSync(key);
36
- }
37
- };
38
- export const scheduleSync = (key, fn, delay = 1000) => {
39
- const executeAt = Date.now() + delay;
40
- scheduledSyncs[key] = { fn, executeAt };
41
- };
42
- const onSyncRequest = (event) => {
43
- const { key, resolve, reject } = event.detail;
44
- waitForTrackedSync(key)
45
- .then(() => {
46
- resolve?.();
47
- })
48
- .catch((error) => {
49
- reject?.(error);
50
- });
51
- };
52
- addEventListener(SYNC_REQUEST_EVENT, onSyncRequest);
53
- if (import.meta.hot) {
54
- import.meta.hot.dispose(() => {
55
- clearInterval(scheduleInterval);
56
- removeEventListener(SYNC_REQUEST_EVENT, onSyncRequest);
57
- });
58
- }
@@ -1 +0,0 @@
1
- export declare const canBackup: boolean;
@@ -1 +0,0 @@
1
- export const canBackup = navigator.userAgent.includes("CrOS") && "showDirectoryPicker" in window;
@@ -1,2 +0,0 @@
1
- export declare const encode: (text: string) => string;
2
- export declare const decode: (text: string) => string;
@@ -1,16 +0,0 @@
1
- // This isn't encryption, it's just to prevent casual observation of sensitive data
2
- export const encode = (text) => {
3
- const bytes = new TextEncoder().encode(text);
4
- let output = "";
5
- for (let i = 0; i < bytes.length; i++) {
6
- output += bytes[i].toString(36).padStart(2, "0");
7
- }
8
- return output;
9
- };
10
- export const decode = (text) => {
11
- const bytes = new Uint8Array(text.length / 2);
12
- for (let i = 0; i < text.length; i += 2) {
13
- bytes[i / 2] = parseInt(text.slice(i, i + 2), 36);
14
- }
15
- return new TextDecoder().decode(bytes);
16
- };
@@ -1,32 +0,0 @@
1
- export type Bucket = {
2
- base: string;
3
- accessKeyId: string;
4
- secretAccessKey: string;
5
- };
6
- export type Intent = {
7
- storage: true;
8
- } | {
9
- loginRecognized: true;
10
- };
11
- export type IntentEnvelope = {
12
- intents: Intent[];
13
- redirectURI: string;
14
- };
15
- export type StorageSetup = ({
16
- method: "cloud";
17
- } & Bucket) | {
18
- method: "localStorage";
19
- };
20
- export type Provision = {
21
- setup: StorageSetup;
22
- } | {
23
- createLoginRecognized: string;
24
- };
25
- /** @knipexternal */
26
- export type ProvisionEnvelope = {
27
- provisions: Provision[];
28
- };
29
- export declare const login: import("valibot", { with: { "resolution-mode": "require" } }).ObjectSchema<{
30
- readonly email: import("valibot", { with: { "resolution-mode": "require" } }).SchemaWithPipe<readonly [import("valibot", { with: { "resolution-mode": "require" } }).StringSchema<undefined>, import("valibot", { with: { "resolution-mode": "require" } }).EmailAction<string, undefined>]>;
31
- readonly password: import("valibot", { with: { "resolution-mode": "require" } }).StringSchema<undefined>;
32
- }, undefined>;
@@ -1,2 +0,0 @@
1
- import { object, pipe, email, string } from "valibot";
2
- export const login = object({ email: pipe(string(), email()), password: string() });