selfstore 1.8.17 → 1.8.19
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/advanced.d.ts +7 -7
- package/dist/advanced.js +2 -2
- package/dist/backups.d.ts +4 -4
- package/dist/{chunk-3BPGPSPX.js → chunk-TQH7ZYID.js} +14 -4
- package/dist/chunk-TQH7ZYID.js.map +1 -0
- package/dist/{connect-BaWYvB1w.d.ts → connect-BTVzRJY8.d.ts} +3 -3
- package/dist/{drive-BUPsPaIu.d.ts → drive-CY_Vsj7V.d.ts} +1 -1
- package/dist/flows.d.ts +5 -5
- package/dist/households.d.ts +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +3 -3
- package/dist/{local-Dt1hYNG4.d.ts → local-Dh6eGiK9.d.ts} +2 -2
- package/dist/{manager-CaMd6T1T.d.ts → manager-DhoIbJyg.d.ts} +1 -1
- package/dist/passkey.d.ts +86 -0
- package/dist/passkey.js +152 -0
- package/dist/passkey.js.map +1 -0
- package/dist/{s3-CCE6lc_U.d.ts → s3-0qqIyphl.d.ts} +1 -1
- package/dist/{store-CCe8b5Eg.d.ts → store-Cs6QIzzG.d.ts} +7 -0
- package/dist/widgets.d.ts +5 -5
- package/dist/widgets.js +13 -5
- package/dist/widgets.js.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-3BPGPSPX.js.map +0 -1
package/dist/flows.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-
|
|
2
|
-
export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-
|
|
3
|
-
import { S as StoreError, k as ReplicaState, B as BackupTarget } from './store-
|
|
1
|
+
import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-BTVzRJY8.js';
|
|
2
|
+
export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-BTVzRJY8.js';
|
|
3
|
+
import { S as StoreError, k as ReplicaState, B as BackupTarget } from './store-Cs6QIzzG.js';
|
|
4
4
|
import { F as FlowStore } from './machine-ALMkT3mk.js';
|
|
5
5
|
export { w as withDeadline } from './machine-ALMkT3mk.js';
|
|
6
|
-
import { W as WebdavConfig, S as S3Config } from './s3-
|
|
6
|
+
import { W as WebdavConfig, S as S3Config } from './s3-0qqIyphl.js';
|
|
7
7
|
import './cache-ilsK3Tki.js';
|
|
8
|
-
import './drive-
|
|
8
|
+
import './drive-CY_Vsj7V.js';
|
|
9
9
|
import './sync.js';
|
|
10
10
|
|
|
11
11
|
/** The one replica this flow manages, as its id on the engine. */
|
package/dist/households.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { o as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, r as StatusDescriptor, S as StoreError, c as LocalStoreState, L as LocalStore } from './store-
|
|
2
|
-
export { d as Mode, e as PasswordCheck, g as PasswordRequirement, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, p as SnapshotFile, q as StatusAction, t as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-
|
|
1
|
+
import { o as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, r as StatusDescriptor, S as StoreError, c as LocalStoreState, L as LocalStore } from './store-Cs6QIzzG.js';
|
|
2
|
+
export { d as Mode, e as PasswordCheck, g as PasswordRequirement, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, p as SnapshotFile, q as StatusAction, t as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-Cs6QIzzG.js';
|
|
3
3
|
import { L as LocalCache, K as KV } from './cache-ilsK3Tki.js';
|
|
4
4
|
import { SyncConfig } from './sync.js';
|
|
5
|
-
import { R as ResumeOffer, C as ConnectTargets } from './connect-
|
|
6
|
-
import { f as BackupsManager } from './manager-
|
|
7
|
-
import { a as DriveAuth } from './drive-
|
|
8
|
-
import { W as WebdavConfig, S as S3Config } from './s3-
|
|
9
|
-
export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-
|
|
5
|
+
import { R as ResumeOffer, C as ConnectTargets } from './connect-BTVzRJY8.js';
|
|
6
|
+
import { f as BackupsManager } from './manager-DhoIbJyg.js';
|
|
7
|
+
import { a as DriveAuth } from './drive-CY_Vsj7V.js';
|
|
8
|
+
import { W as WebdavConfig, S as S3Config } from './s3-0qqIyphl.js';
|
|
9
|
+
export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-Dh6eGiK9.js';
|
|
10
10
|
import './machine-ALMkT3mk.js';
|
|
11
11
|
|
|
12
12
|
declare const BACKUP_EXTENSION = ".zip";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-
|
|
2
|
-
export { gisDriveAuth } from './chunk-
|
|
3
|
-
import './chunk-SKWHKM25.js';
|
|
1
|
+
import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-TQH7ZYID.js';
|
|
2
|
+
export { gisDriveAuth } from './chunk-TQH7ZYID.js';
|
|
4
3
|
import { saveToDisk } from './chunk-DCEXWSM6.js';
|
|
5
4
|
export { pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
|
|
6
5
|
import { fromSession as fromSession$1, fromSession2, isSupported, connect3, connect2, connect } from './chunk-TKWU52LK.js';
|
|
@@ -12,6 +11,7 @@ import './chunk-QBCU7OOP.js';
|
|
|
12
11
|
import './chunk-O3Y6FORN.js';
|
|
13
12
|
import { SelfstoreError } from './chunk-3OBJOMQ2.js';
|
|
14
13
|
export { SelfstoreError, errorLabelKey, isSelfstoreError } from './chunk-3OBJOMQ2.js';
|
|
14
|
+
import './chunk-SKWHKM25.js';
|
|
15
15
|
|
|
16
16
|
// src/simple/simple.ts
|
|
17
17
|
function idFieldOf(sync, collection) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as Snapshot, E as EncodeOptions, H as Header } from './store-
|
|
2
|
-
import { a as DriveAuth } from './drive-
|
|
1
|
+
import { o as Snapshot, E as EncodeOptions, H as Header } from './store-Cs6QIzzG.js';
|
|
2
|
+
import { a as DriveAuth } from './drive-CY_Vsj7V.js';
|
|
3
3
|
|
|
4
4
|
/** Serialize a snapshot to a portable backup blob (compressed; encrypted if a password is given). */
|
|
5
5
|
declare function exportSnapshot(snapshot: Snapshot, opts: EncodeOptions): Promise<Blob>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { K as KV } from './cache-ilsK3Tki.js';
|
|
2
|
-
import { B as BackupTarget, L as LocalStore } from './store-
|
|
2
|
+
import { B as BackupTarget, L as LocalStore } from './store-Cs6QIzzG.js';
|
|
3
3
|
import { F as FlowStore } from './machine-ALMkT3mk.js';
|
|
4
4
|
|
|
5
5
|
/** What the destination reports about a listed file (one cheap metadata call). */
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a store with the device instead of the keyboard: Face, fingerprint or
|
|
3
|
+
* Windows Hello in place of typing the password.
|
|
4
|
+
*
|
|
5
|
+
* WHAT IT IS. A platform passkey carrying the WebAuthn PRF extension (FIDO2
|
|
6
|
+
* hmac-secret) yields a stable 32-byte secret - deterministic for that
|
|
7
|
+
* credential and a fixed per-app salt, and it never leaves the authenticator.
|
|
8
|
+
* That secret becomes an AES-256-GCM key, under which this module seals the
|
|
9
|
+
* app's own secret (typically the backup password). Unlocking is then: user
|
|
10
|
+
* verification -> PRF secret -> the password in the clear, in memory.
|
|
11
|
+
*
|
|
12
|
+
* WHAT IT IS NOT. It does not replace the password, it carries it. The
|
|
13
|
+
* password keeps opening everything, on every device, forever; this is a
|
|
14
|
+
* convenience bound to ONE browser profile on ONE machine. Losing the passkey
|
|
15
|
+
* costs a typed password, never data. That asymmetry is deliberate: a second
|
|
16
|
+
* independent key to the data would widen the blast radius of a stolen device,
|
|
17
|
+
* which is the opposite of what a locked cache is for.
|
|
18
|
+
*
|
|
19
|
+
* THE PROPERTY IT PRESERVES. The sealed blob is useless on its own: a copy of
|
|
20
|
+
* the browser profile does not open it, because the PRF secret lives in the
|
|
21
|
+
* authenticator and demands user verification. That is exactly the guarantee a
|
|
22
|
+
* lock-mode cache exists for, so enrolling does not weaken it.
|
|
23
|
+
*
|
|
24
|
+
* OPT-IN BY CONSTRUCTION. Nothing here runs unless an app calls
|
|
25
|
+
* `passkeyUnlock()`. An app that never does simply has no such feature, and an
|
|
26
|
+
* app that does still decides when to offer it - the recommended shape is an
|
|
27
|
+
* explicit control the user turns on, and can turn off again with `forget()`.
|
|
28
|
+
*
|
|
29
|
+
* Requires the PRF extension: recent Chrome/Edge, Safari 18+, recent Android;
|
|
30
|
+
* inside a native WebView it depends on the system WebView version. Nothing
|
|
31
|
+
* trusts that blindly - `available()` and `enroll()` feature-detect at runtime
|
|
32
|
+
* and FAIL CLOSED, so the option is only ever usable where a real secret comes
|
|
33
|
+
* back. Dependency-free, and guarded for SSR and no-WebAuthn environments.
|
|
34
|
+
*/
|
|
35
|
+
/** How the passkey is presented by the platform, and what it protects. */
|
|
36
|
+
interface PasskeyUnlockOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Shown by the operating system's passkey prompt and stored with the
|
|
39
|
+
* credential. The application name, as a person would read it.
|
|
40
|
+
*/
|
|
41
|
+
appName: string;
|
|
42
|
+
/**
|
|
43
|
+
* Domain separator for the PRF evaluation - NOT a secret, and it may be a
|
|
44
|
+
* constant: the credential's own secret is what makes the derived key
|
|
45
|
+
* unique. Use a stable per-app string. Changing it rotates every
|
|
46
|
+
* passkey-derived key at once, which orphans every existing enrolment.
|
|
47
|
+
*/
|
|
48
|
+
salt: string;
|
|
49
|
+
/** Where the sealed secret is kept on this device (default `selfstore.passkey`). */
|
|
50
|
+
storageKey?: string;
|
|
51
|
+
}
|
|
52
|
+
/** The device-unlock facade. Every method is safe to call anywhere: outside a
|
|
53
|
+
* browser, or without WebAuthn, they report unavailable rather than throw. */
|
|
54
|
+
interface PasskeyUnlock {
|
|
55
|
+
/**
|
|
56
|
+
* Whether a user-verifying platform authenticator exists at all. Necessary
|
|
57
|
+
* but NOT sufficient: only an actual `enroll()` proves the PRF extension
|
|
58
|
+
* works here. Use it to decide whether to show the control, not to promise
|
|
59
|
+
* that it will succeed.
|
|
60
|
+
*/
|
|
61
|
+
available(): Promise<boolean>;
|
|
62
|
+
/** Whether this device currently holds a sealed secret to reveal. */
|
|
63
|
+
enrolled(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Create a platform passkey and seal `secret` behind it. `false` when the
|
|
66
|
+
* user cancelled, or when PRF did not yield a key here - fail closed, the
|
|
67
|
+
* host keeps its password prompt and should say so rather than pretend.
|
|
68
|
+
*/
|
|
69
|
+
enroll(secret: string): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Ask for Face / fingerprint / Hello and return the sealed secret. `null`
|
|
72
|
+
* on cancel, on a missing enrolment, or on any failure; the host then falls
|
|
73
|
+
* back to asking for the password. Never throws.
|
|
74
|
+
*/
|
|
75
|
+
reveal(): Promise<string | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Drop the sealed secret from this device. The platform credential itself
|
|
78
|
+
* is NOT deleted - the operating system owns it, and only the user can
|
|
79
|
+
* remove it from their passkey manager. Say that in the interface rather
|
|
80
|
+
* than implying a full erasure.
|
|
81
|
+
*/
|
|
82
|
+
forget(): void;
|
|
83
|
+
}
|
|
84
|
+
declare function passkeyUnlock(options: PasskeyUnlockOptions): PasskeyUnlock;
|
|
85
|
+
|
|
86
|
+
export { type PasskeyUnlock, type PasskeyUnlockOptions, passkeyUnlock };
|
package/dist/passkey.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// src/passkey/passkey.ts
|
|
2
|
+
var DEFAULT_STORAGE_KEY = "selfstore.passkey";
|
|
3
|
+
var PRF_BYTES = 32;
|
|
4
|
+
function toB64(bytes) {
|
|
5
|
+
const b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
6
|
+
let s = "";
|
|
7
|
+
for (const byte of b) s += String.fromCharCode(byte);
|
|
8
|
+
return btoa(s);
|
|
9
|
+
}
|
|
10
|
+
function fromB64(s) {
|
|
11
|
+
const bin = atob(s);
|
|
12
|
+
const out = new Uint8Array(bin.length);
|
|
13
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function webauthnUsable() {
|
|
17
|
+
return typeof window !== "undefined" && !!window.PublicKeyCredential && typeof navigator !== "undefined" && !!navigator.credentials && typeof crypto !== "undefined" && !!crypto.subtle;
|
|
18
|
+
}
|
|
19
|
+
function prfSecret(credential) {
|
|
20
|
+
const first = credential?.getClientExtensionResults()?.prf?.results?.first;
|
|
21
|
+
if (!first) return null;
|
|
22
|
+
const bytes = new Uint8Array(first);
|
|
23
|
+
return bytes.length === PRF_BYTES ? bytes : null;
|
|
24
|
+
}
|
|
25
|
+
function sealKey(secret) {
|
|
26
|
+
return crypto.subtle.importKey("raw", secret, "AES-GCM", false, [
|
|
27
|
+
"encrypt",
|
|
28
|
+
"decrypt"
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
function passkeyUnlock(options) {
|
|
32
|
+
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
33
|
+
const prfSalt = new TextEncoder().encode(options.salt);
|
|
34
|
+
function read() {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof localStorage === "undefined") return null;
|
|
37
|
+
const raw = localStorage.getItem(storageKey);
|
|
38
|
+
if (!raw) return null;
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
const complete = typeof parsed?.credentialId === "string" && typeof parsed?.iv === "string" && typeof parsed?.sealed === "string";
|
|
41
|
+
return complete ? parsed : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function write(entry) {
|
|
47
|
+
try {
|
|
48
|
+
if (typeof localStorage === "undefined") return;
|
|
49
|
+
if (entry) localStorage.setItem(storageKey, JSON.stringify(entry));
|
|
50
|
+
else localStorage.removeItem(storageKey);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function derive(credentialId) {
|
|
55
|
+
try {
|
|
56
|
+
const assertion = await navigator.credentials.get({
|
|
57
|
+
publicKey: {
|
|
58
|
+
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
59
|
+
allowCredentials: credentialId ? [{ type: "public-key", id: fromB64(credentialId) }] : [],
|
|
60
|
+
userVerification: "required",
|
|
61
|
+
extensions: { prf: { eval: { first: prfSalt } } },
|
|
62
|
+
timeout: 6e4
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return prfSecret(assertion);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function create() {
|
|
71
|
+
const credential = await navigator.credentials.create({
|
|
72
|
+
publicKey: {
|
|
73
|
+
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
|
74
|
+
rp: { name: options.appName, id: location.hostname },
|
|
75
|
+
user: {
|
|
76
|
+
id: crypto.getRandomValues(new Uint8Array(16)),
|
|
77
|
+
name: options.appName,
|
|
78
|
+
displayName: options.appName
|
|
79
|
+
},
|
|
80
|
+
pubKeyCredParams: [
|
|
81
|
+
{ type: "public-key", alg: -7 },
|
|
82
|
+
{ type: "public-key", alg: -257 }
|
|
83
|
+
],
|
|
84
|
+
authenticatorSelection: {
|
|
85
|
+
authenticatorAttachment: "platform",
|
|
86
|
+
userVerification: "required",
|
|
87
|
+
residentKey: "required"
|
|
88
|
+
},
|
|
89
|
+
extensions: { prf: { eval: { first: prfSalt } } },
|
|
90
|
+
timeout: 6e4
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (!credential) return null;
|
|
94
|
+
const credentialId = toB64(credential.rawId);
|
|
95
|
+
const secret = prfSecret(credential) ?? await derive(credentialId);
|
|
96
|
+
return secret ? { credentialId, secret } : null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
async available() {
|
|
100
|
+
try {
|
|
101
|
+
if (!webauthnUsable()) return false;
|
|
102
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
enrolled() {
|
|
108
|
+
return read() !== null;
|
|
109
|
+
},
|
|
110
|
+
async enroll(secret) {
|
|
111
|
+
if (!webauthnUsable()) return false;
|
|
112
|
+
try {
|
|
113
|
+
const created = await create();
|
|
114
|
+
if (!created) return false;
|
|
115
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
116
|
+
const sealed = await crypto.subtle.encrypt(
|
|
117
|
+
{ name: "AES-GCM", iv },
|
|
118
|
+
await sealKey(created.secret),
|
|
119
|
+
new TextEncoder().encode(secret)
|
|
120
|
+
);
|
|
121
|
+
write({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
async reveal() {
|
|
128
|
+
const entry = read();
|
|
129
|
+
if (!entry || !webauthnUsable()) return null;
|
|
130
|
+
const secret = await derive(entry.credentialId);
|
|
131
|
+
if (!secret) return null;
|
|
132
|
+
try {
|
|
133
|
+
const opened = await crypto.subtle.decrypt(
|
|
134
|
+
{ name: "AES-GCM", iv: fromB64(entry.iv) },
|
|
135
|
+
await sealKey(secret),
|
|
136
|
+
fromB64(entry.sealed)
|
|
137
|
+
);
|
|
138
|
+
return new TextDecoder().decode(opened);
|
|
139
|
+
} catch {
|
|
140
|
+
write(null);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
forget() {
|
|
145
|
+
write(null);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { passkeyUnlock };
|
|
151
|
+
//# sourceMappingURL=passkey.js.map
|
|
152
|
+
//# sourceMappingURL=passkey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/passkey/passkey.ts"],"names":[],"mappings":"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","file":"passkey.js","sourcesContent":["/**\n * Open a store with the device instead of the keyboard: Face, fingerprint or\n * Windows Hello in place of typing the password.\n *\n * WHAT IT IS. A platform passkey carrying the WebAuthn PRF extension (FIDO2\n * hmac-secret) yields a stable 32-byte secret - deterministic for that\n * credential and a fixed per-app salt, and it never leaves the authenticator.\n * That secret becomes an AES-256-GCM key, under which this module seals the\n * app's own secret (typically the backup password). Unlocking is then: user\n * verification -> PRF secret -> the password in the clear, in memory.\n *\n * WHAT IT IS NOT. It does not replace the password, it carries it. The\n * password keeps opening everything, on every device, forever; this is a\n * convenience bound to ONE browser profile on ONE machine. Losing the passkey\n * costs a typed password, never data. That asymmetry is deliberate: a second\n * independent key to the data would widen the blast radius of a stolen device,\n * which is the opposite of what a locked cache is for.\n *\n * THE PROPERTY IT PRESERVES. The sealed blob is useless on its own: a copy of\n * the browser profile does not open it, because the PRF secret lives in the\n * authenticator and demands user verification. That is exactly the guarantee a\n * lock-mode cache exists for, so enrolling does not weaken it.\n *\n * OPT-IN BY CONSTRUCTION. Nothing here runs unless an app calls\n * `passkeyUnlock()`. An app that never does simply has no such feature, and an\n * app that does still decides when to offer it - the recommended shape is an\n * explicit control the user turns on, and can turn off again with `forget()`.\n *\n * Requires the PRF extension: recent Chrome/Edge, Safari 18+, recent Android;\n * inside a native WebView it depends on the system WebView version. Nothing\n * trusts that blindly - `available()` and `enroll()` feature-detect at runtime\n * and FAIL CLOSED, so the option is only ever usable where a real secret comes\n * back. Dependency-free, and guarded for SSR and no-WebAuthn environments.\n */\n\n/** How the passkey is presented by the platform, and what it protects. */\nexport interface PasskeyUnlockOptions {\n\t/**\n\t * Shown by the operating system's passkey prompt and stored with the\n\t * credential. The application name, as a person would read it.\n\t */\n\tappName: string;\n\t/**\n\t * Domain separator for the PRF evaluation - NOT a secret, and it may be a\n\t * constant: the credential's own secret is what makes the derived key\n\t * unique. Use a stable per-app string. Changing it rotates every\n\t * passkey-derived key at once, which orphans every existing enrolment.\n\t */\n\tsalt: string;\n\t/** Where the sealed secret is kept on this device (default `selfstore.passkey`). */\n\tstorageKey?: string;\n}\n\n/** The device-unlock facade. Every method is safe to call anywhere: outside a\n * browser, or without WebAuthn, they report unavailable rather than throw. */\nexport interface PasskeyUnlock {\n\t/**\n\t * Whether a user-verifying platform authenticator exists at all. Necessary\n\t * but NOT sufficient: only an actual `enroll()` proves the PRF extension\n\t * works here. Use it to decide whether to show the control, not to promise\n\t * that it will succeed.\n\t */\n\tavailable(): Promise<boolean>;\n\t/** Whether this device currently holds a sealed secret to reveal. */\n\tenrolled(): boolean;\n\t/**\n\t * Create a platform passkey and seal `secret` behind it. `false` when the\n\t * user cancelled, or when PRF did not yield a key here - fail closed, the\n\t * host keeps its password prompt and should say so rather than pretend.\n\t */\n\tenroll(secret: string): Promise<boolean>;\n\t/**\n\t * Ask for Face / fingerprint / Hello and return the sealed secret. `null`\n\t * on cancel, on a missing enrolment, or on any failure; the host then falls\n\t * back to asking for the password. Never throws.\n\t */\n\treveal(): Promise<string | null>;\n\t/**\n\t * Drop the sealed secret from this device. The platform credential itself\n\t * is NOT deleted - the operating system owns it, and only the user can\n\t * remove it from their passkey manager. Say that in the interface rather\n\t * than implying a full erasure.\n\t */\n\tforget(): void;\n}\n\n/** PRF extension shapes are absent from some TS DOM libs; describe what we use. */\ntype PrfInputs = { prf?: { eval?: { first: BufferSource } } };\ntype PrfOutputs = { prf?: { results?: { first?: ArrayBuffer } } };\n\n/** What one device remembers: which credential to prefer, and the sealed secret. */\ninterface StoredEnrolment {\n\tcredentialId: string;\n\tiv: string;\n\tsealed: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'selfstore.passkey';\nconst PRF_BYTES = 32;\n\nfunction toB64(bytes: ArrayBuffer | Uint8Array): string {\n\tconst b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n\tlet s = '';\n\tfor (const byte of b) s += String.fromCharCode(byte);\n\treturn btoa(s);\n}\n\nfunction fromB64(s: string): Uint8Array<ArrayBuffer> {\n\tconst bin = atob(s);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n\nfunction webauthnUsable(): boolean {\n\treturn (\n\t\ttypeof window !== 'undefined' &&\n\t\t!!window.PublicKeyCredential &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\t!!navigator.credentials &&\n\t\ttypeof crypto !== 'undefined' &&\n\t\t!!crypto.subtle\n\t);\n}\n\nfunction prfSecret(credential: PublicKeyCredential | null): Uint8Array | null {\n\tconst first = (credential?.getClientExtensionResults() as PrfOutputs | undefined)?.prf?.results\n\t\t?.first;\n\tif (!first) return null;\n\tconst bytes = new Uint8Array(first);\n\treturn bytes.length === PRF_BYTES ? bytes : null;\n}\n\n/**\n * The 32 bytes coming out of PRF are an HMAC output, so they are already\n * uniform: import them straight as an AES-GCM key rather than running a KDF\n * over them. Stretching high-entropy material buys nothing and would only make\n * every unlock slower.\n */\nfunction sealKey(secret: Uint8Array): Promise<CryptoKey> {\n\treturn crypto.subtle.importKey('raw', secret as BufferSource, 'AES-GCM', false, [\n\t\t'encrypt',\n\t\t'decrypt'\n\t]);\n}\n\nexport function passkeyUnlock(options: PasskeyUnlockOptions): PasskeyUnlock {\n\tconst storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;\n\tconst prfSalt = new TextEncoder().encode(options.salt);\n\n\tfunction read(): StoredEnrolment | null {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return null;\n\t\t\tconst raw = localStorage.getItem(storageKey);\n\t\t\tif (!raw) return null;\n\t\t\tconst parsed = JSON.parse(raw) as StoredEnrolment;\n\t\t\tconst complete =\n\t\t\t\ttypeof parsed?.credentialId === 'string' &&\n\t\t\t\ttypeof parsed?.iv === 'string' &&\n\t\t\t\ttypeof parsed?.sealed === 'string';\n\t\t\treturn complete ? parsed : null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tfunction write(entry: StoredEnrolment | null): void {\n\t\ttry {\n\t\t\tif (typeof localStorage === 'undefined') return;\n\t\t\tif (entry) localStorage.setItem(storageKey, JSON.stringify(entry));\n\t\t\telse localStorage.removeItem(storageKey);\n\t\t} catch {\n\t\t\t/* storage blocked: the enrolment is simply not remembered */\n\t\t}\n\t}\n\n\t/**\n\t * Re-derive the PRF secret. With a credential id it targets that one; with\n\t * none, the platform offers the user's discoverable passkeys for this site.\n\t */\n\tasync function derive(credentialId?: string): Promise<Uint8Array | null> {\n\t\ttry {\n\t\t\tconst assertion = (await navigator.credentials.get({\n\t\t\t\tpublicKey: {\n\t\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\t\tallowCredentials: credentialId\n\t\t\t\t\t\t? [{ type: 'public-key', id: fromB64(credentialId) as BufferSource }]\n\t\t\t\t\t\t: [],\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\t\ttimeout: 60_000\n\t\t\t\t}\n\t\t\t})) as PublicKeyCredential | null;\n\t\t\treturn prfSecret(assertion);\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function create(): Promise<{ credentialId: string; secret: Uint8Array } | null> {\n\t\tconst credential = (await navigator.credentials.create({\n\t\t\tpublicKey: {\n\t\t\t\tchallenge: crypto.getRandomValues(new Uint8Array(32)),\n\t\t\t\trp: { name: options.appName, id: location.hostname },\n\t\t\t\tuser: {\n\t\t\t\t\tid: crypto.getRandomValues(new Uint8Array(16)),\n\t\t\t\t\tname: options.appName,\n\t\t\t\t\tdisplayName: options.appName\n\t\t\t\t},\n\t\t\t\tpubKeyCredParams: [\n\t\t\t\t\t{ type: 'public-key', alg: -7 },\n\t\t\t\t\t{ type: 'public-key', alg: -257 }\n\t\t\t\t],\n\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\tauthenticatorAttachment: 'platform',\n\t\t\t\t\tuserVerification: 'required',\n\t\t\t\t\tresidentKey: 'required'\n\t\t\t\t},\n\t\t\t\textensions: { prf: { eval: { first: prfSalt } } } as PrfInputs,\n\t\t\t\ttimeout: 60_000\n\t\t\t}\n\t\t})) as PublicKeyCredential | null;\n\t\tif (!credential) return null;\n\t\tconst credentialId = toB64(credential.rawId);\n\t\t// Some platforms hand the PRF result back from create(); most need a\n\t\t// second get(). Try the cheap path first to save the user one prompt.\n\t\tconst secret = prfSecret(credential) ?? (await derive(credentialId));\n\t\treturn secret ? { credentialId, secret } : null;\n\t}\n\n\treturn {\n\t\tasync available(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tif (!webauthnUsable()) return false;\n\t\t\t\treturn await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tenrolled(): boolean {\n\t\t\treturn read() !== null;\n\t\t},\n\n\t\tasync enroll(secret: string): Promise<boolean> {\n\t\t\tif (!webauthnUsable()) return false;\n\t\t\ttry {\n\t\t\t\tconst created = await create();\n\t\t\t\tif (!created) return false;\n\t\t\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\t\t\tconst sealed = await crypto.subtle.encrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv },\n\t\t\t\t\tawait sealKey(created.secret),\n\t\t\t\t\tnew TextEncoder().encode(secret)\n\t\t\t\t);\n\t\t\t\twrite({ credentialId: created.credentialId, iv: toB64(iv), sealed: toB64(sealed) });\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync reveal(): Promise<string | null> {\n\t\t\tconst entry = read();\n\t\t\tif (!entry || !webauthnUsable()) return null;\n\t\t\tconst secret = await derive(entry.credentialId);\n\t\t\tif (!secret) return null;\n\t\t\ttry {\n\t\t\t\tconst opened = await crypto.subtle.decrypt(\n\t\t\t\t\t{ name: 'AES-GCM', iv: fromB64(entry.iv) as BufferSource },\n\t\t\t\t\tawait sealKey(secret),\n\t\t\t\t\tfromB64(entry.sealed) as BufferSource\n\t\t\t\t);\n\t\t\t\treturn new TextDecoder().decode(opened);\n\t\t\t} catch {\n\t\t\t\t// A wrong key or a tampered blob: an unopenable enrolment is dead\n\t\t\t\t// weight that would fail at every attempt, so drop it and let the\n\t\t\t\t// host fall back to the password.\n\t\t\t\twrite(null);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tforget(): void {\n\t\t\twrite(null);\n\t\t}\n\t};\n}\n"]}
|
|
@@ -229,6 +229,13 @@ interface StatusInput {
|
|
|
229
229
|
locked: boolean;
|
|
230
230
|
/** Degraded file mode has changes awaiting a manual download. */
|
|
231
231
|
pendingDownload: boolean;
|
|
232
|
+
/** A portable copy was written at some point, and whether anything has been
|
|
233
|
+
* entered since. Without a durable destination, this is the difference
|
|
234
|
+
* between "never saved anywhere" and "saved this morning". */
|
|
235
|
+
copy?: {
|
|
236
|
+
at: number | null;
|
|
237
|
+
stale: boolean;
|
|
238
|
+
};
|
|
232
239
|
}
|
|
233
240
|
/** Map the store's raw flags to a single, ranked status; the most important wins. */
|
|
234
241
|
declare function deriveStatus(input: StatusInput): StatusDescriptor;
|
package/dist/widgets.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { S as StoreLike, F as FlowHost, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow } from './connect-
|
|
1
|
+
import { S as StoreLike, F as FlowHost, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow } from './connect-BTVzRJY8.js';
|
|
2
2
|
import { ShareEngine, ShareLevel, ShareFlow, JoinEngine, JoinFlow, ReplicaFlow } from './flows.js';
|
|
3
|
-
import { f as BackupsManager } from './manager-
|
|
4
|
-
import './store-
|
|
3
|
+
import { f as BackupsManager } from './manager-DhoIbJyg.js';
|
|
4
|
+
import './store-Cs6QIzzG.js';
|
|
5
5
|
import './cache-ilsK3Tki.js';
|
|
6
6
|
import './sync.js';
|
|
7
|
-
import './drive-
|
|
8
|
-
import './s3-
|
|
7
|
+
import './drive-CY_Vsj7V.js';
|
|
8
|
+
import './s3-0qqIyphl.js';
|
|
9
9
|
import './machine-ALMkT3mk.js';
|
|
10
10
|
|
|
11
11
|
/** Flat key -> string map; widgets merge a partial override over EN defaults. */
|
package/dist/widgets.js
CHANGED
|
@@ -2087,6 +2087,8 @@ var EN5 = {
|
|
|
2087
2087
|
"status.justNow": "just now",
|
|
2088
2088
|
"status.ephemeral": "Nothing is saved",
|
|
2089
2089
|
"status.cacheOnly": "Never saved anywhere yet",
|
|
2090
|
+
"status.copy": "Last copy written {when}. Nothing has changed since.",
|
|
2091
|
+
"status.copyStale": "Last copy written {when}. What you typed since is not in it.",
|
|
2090
2092
|
"status.saving": "Saving...",
|
|
2091
2093
|
"status.saved": "Saved to {label}, at every change",
|
|
2092
2094
|
"status.needsAttention": "Reconnect to continue",
|
|
@@ -2103,6 +2105,8 @@ var FR5 = {
|
|
|
2103
2105
|
"status.justNow": "a l'instant",
|
|
2104
2106
|
"status.ephemeral": "Rien n'est enregistr\xE9",
|
|
2105
2107
|
"status.cacheOnly": "Vos donn\xE9es n'ont jamais \xE9t\xE9 enregistr\xE9es ailleurs",
|
|
2108
|
+
"status.copy": "Derni\xE8re copie \xE9crite {when}. Rien n'a chang\xE9 depuis.",
|
|
2109
|
+
"status.copyStale": "Derni\xE8re copie \xE9crite {when}. Vos saisies depuis n'y sont pas.",
|
|
2106
2110
|
"status.saving": "Enregistrement...",
|
|
2107
2111
|
"status.saved": "Enregistr\xE9 dans {label}, \xE0 chaque changement",
|
|
2108
2112
|
"status.needsAttention": "Acc\xE8s \xE0 retrouver",
|
|
@@ -2152,16 +2156,20 @@ var SelfstoreStatusElement = class extends FlowWidget {
|
|
|
2152
2156
|
const { status, targetKind, label } = host.engine.state;
|
|
2153
2157
|
const sansLieu = (cle) => {
|
|
2154
2158
|
const jumelle = `${cle}.placeless`;
|
|
2155
|
-
const copie = this.t(jumelle);
|
|
2156
|
-
return copie === jumelle ? this.t(cle) : copie;
|
|
2159
|
+
const copie = this.t(jumelle, vars);
|
|
2160
|
+
return copie === jumelle ? this.t(cle, vars) : copie;
|
|
2157
2161
|
};
|
|
2158
2162
|
const selonLeType = (cle) => {
|
|
2159
2163
|
const jumelle = `${cle}.${targetKind}`;
|
|
2160
|
-
const copie = this.t(jumelle,
|
|
2164
|
+
const copie = this.t(jumelle, vars);
|
|
2161
2165
|
return copie === jumelle ? null : copie;
|
|
2162
2166
|
};
|
|
2163
|
-
const { lastSavedAt } = host.engine.state;
|
|
2164
|
-
const
|
|
2167
|
+
const { lastSavedAt, lastCopyAt } = host.engine.state;
|
|
2168
|
+
const vars = {
|
|
2169
|
+
label: label ?? "",
|
|
2170
|
+
when: lastCopyAt ? this.since(lastCopyAt, this.t("status.justNow")) : ""
|
|
2171
|
+
};
|
|
2172
|
+
const text = selonLeType(status.labelKey) ?? (label ? this.t(status.labelKey, vars) : sansLieu(status.labelKey));
|
|
2165
2173
|
const dot = h("span", {
|
|
2166
2174
|
part: `status-dot sev-${status.severity}`,
|
|
2167
2175
|
"aria-hidden": "true"
|