@synonymdev/pubky 0.14.0 → 0.16.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@synonymdev/pubky",
3
3
  "type": "module",
4
4
  "description": "Decentralized identity, auth flows, and user-owned storage",
5
- "version": "0.14.0",
5
+ "version": "0.16.0",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
@@ -10,14 +10,15 @@
10
10
  },
11
11
  "scripts": {
12
12
  "testnet": "cargo run -p pubky-testnet",
13
- "build:test": "tsc --project tsconfig.json && mkdir -p dist && cp index.js dist/index.js",
13
+ "build:test": "rm -rf dist/tests && tsc --project tsconfig.json && mkdir -p dist && cp index.js dist/index.js",
14
14
  "test": "npm run test-nodejs && npm run test-browser",
15
15
  "test-nodejs": "npm run build:test && node --require ./node-header.cjs node_modules/tape/bin/tape dist/tests/*.js -cov",
16
16
  "test-browser": "npm run build:test && browserify dist/tests/setup.js dist/tests/*.js -p esmify | npx tape-run",
17
17
  "test-browser:ci": "npm run build:test && browserify dist/tests/setup.js dist/tests/*.js -p esmify | tape-run --browser electron --electron-arg=--no-sandbox --electron-arg=--disable-gpu",
18
18
  "build": "cargo run --bin bundle_npm",
19
19
  "docs": "typedoc",
20
- "prepublishOnly": "npm run build"
20
+ "prepublishOnly": "npm run build",
21
+ "test-browser:tabs": "npm run build:test && browserify dist/tests/browser/session-tabs.js -p esmify -o dist/session-tabs.bundle.js && electron scripts/session-tabs.cjs"
21
22
  },
22
23
  "files": [
23
24
  "index.cjs",
@@ -40,6 +41,7 @@
40
41
  "@types/tape": "^5.6.4",
41
42
  "browser-resolve": "^2.0.0",
42
43
  "browserify": "^17.0.1",
44
+ "electron": "41.0.2",
43
45
  "esmify": "^2.1.1",
44
46
  "tape": "^5.8.1",
45
47
  "tape-run": "^11.0.0",
package/pubky.d.ts CHANGED
@@ -12,6 +12,32 @@ export type Level = "error" | "warn" | "info" | "debug" | "trace";
12
12
  */
13
13
 
14
14
  export type ReadableStreamType = "bytes";
15
+
16
+ /** Offline recovery result. Check the identity before using the keys. */
17
+ export interface RecoveredEncryptionKeys {
18
+ /** Authenticated account public key, encoded as z-base-32. */
19
+ publicKey: string;
20
+ /** Authenticated grant identifier (jti). */
21
+ grantId: string;
22
+ /** Independently owned scoped keys. Call keys.free() when finished. */
23
+ keys: EncryptionKeys;
24
+ }
25
+
26
+
27
+
28
+ export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
29
+ /** Root-signed grant claims. Public keys are z-base-32; times are Unix seconds. */
30
+ export interface VerifiedGrantClaims {
31
+ iss: string;
32
+ client_id: string;
33
+ caps: string[];
34
+ cnf: string;
35
+ jti: string;
36
+ iat: number;
37
+ exp: number;
38
+ }
39
+
40
+
15
41
  /**
16
42
  * A union type of all possible machine-readable codes for the `name` property
17
43
  * of a {@link PubkyError}.
@@ -63,6 +89,25 @@ export interface GrantAuthFlowOptions {
63
89
  * Optional return destinations for success, error, and cancellation.
64
90
  */
65
91
  xCallback?: XCallbackParams | null;
92
+ /**
93
+ * Relay approval format. `signedApprovalV1` delivers keys for approved `e` scopes
94
+ * and rejects bare-grant downgrades.
95
+ * Omitted or `bareGrant` selects a grant without an approval envelope.
96
+ * `signedApprovalV1` sets `af=v1` in the authorization link.
97
+ */
98
+ approvalFormat?: GrantApprovalFormat;
99
+ }
100
+
101
+ /**
102
+ * Options for verifying custom grant proofs.
103
+ */
104
+ export interface CustomPopVerificationOptions {
105
+ /**
106
+ * Allow the grant's issue time this many seconds ahead of the local clock.
107
+ * Defaults to 30; zero disables the allowance. Expiry remains strict.
108
+ * Must be an integer between 0 and 4294967295.
109
+ */
110
+ clockSkewSeconds?: number;
66
111
  }
67
112
 
68
113
  /**
@@ -74,8 +119,8 @@ export interface PkarrConfig {
74
119
  */
75
120
  relays?: string[];
76
121
  /**
77
- * The timeout for DHT requests in milliseconds.
78
- * Default is 2000ms.
122
+ * The timeout for relay HTTP requests in milliseconds.
123
+ * Default is 5000ms.
79
124
  */
80
125
  requestTimeout?: number;
81
126
  }
@@ -96,6 +141,11 @@ export interface PubkyClientConfig {
96
141
  maxErrorBodyBytes?: number;
97
142
  }
98
143
 
144
+ /**
145
+ * Relay payload format understood by the requesting app.
146
+ */
147
+ export type GrantApprovalFormat = "bareGrant" | "signedApprovalV1";
148
+
99
149
  /**
100
150
  * Represents the standard error structure for all exceptions thrown by the Pubky
101
151
  * WASM client.
@@ -202,9 +252,50 @@ export interface ResourceStats {
202
252
  etag?: string;
203
253
  }
204
254
 
255
+ /**
256
+ * Self-contained credentials for offline custom-proof verification.
257
+ * Unknown fields are ignored so future bundles remain readable by older verifiers.
258
+ */
259
+ export interface CustomPop {
260
+ /**
261
+ * Original root-signed grant JWS.
262
+ */
263
+ grant: string;
264
+ /**
265
+ * Client-signed custom proof JWS.
266
+ */
267
+ pop: string;
268
+ }
269
+
270
+ /**
271
+ * Verified provenance and data; applications validate the data's meaning.
272
+ */
273
+ export interface VerifiedCustomPop {
274
+ /**
275
+ * Root public key encoded as z-base-32.
276
+ */
277
+ identity: string;
278
+ /**
279
+ * Root-signed claims; storage capabilities do not grant service permissions.
280
+ */
281
+ grantClaims: VerifiedGrantClaims;
282
+ /**
283
+ * Unix seconds at which the client signed the proof. No maximum age is enforced.
284
+ */
285
+ iat: number;
286
+ /**
287
+ * Random per-proof value. Record it until grant expiry to detect replays.
288
+ */
289
+ nonce: string;
290
+ /**
291
+ * Signed JSON data, without application-specific validation.
292
+ */
293
+ data: JsonValue;
294
+ }
295
+
205
296
  export type Address = `pubky${string}/pub/${string}` | `pubky://${string}/pub/${string}`;
206
297
 
207
- export type CapabilityAction = "r" | "w" | "rw";
298
+ export type CapabilityAction = "r" | "w" | "e" | "rw" | "re" | "we" | "rwe";
208
299
  export type CapabilityScope = `/${string}`;
209
300
  export type CapabilityEntry = `${CapabilityScope}:${CapabilityAction}`;
210
301
  type CapabilitiesTail = `,${CapabilityEntry}${string}`;
@@ -459,7 +550,7 @@ export class BrowserSessionStore {
459
550
  * This removes all stored session records and all browser-held delegated
460
551
  * grant keys, including keys for pending delegated grant flows. Saved
461
552
  * delegated flow state becomes unrestorable. This does not revoke remote
462
- * grants or immediately invalidate already-live in-memory sessions.
553
+ * grants. Browser-managed handles stop working when their saved record is removed.
463
554
  */
464
555
  clearAll(): Promise<void>;
465
556
  /**
@@ -470,7 +561,7 @@ export class BrowserSessionStore {
470
561
  */
471
562
  clear(): Promise<void>;
472
563
  /**
473
- * Whether IndexedDB is available for durable session persistence.
564
+ * Whether IndexedDB and Web Locks are available.
474
565
  */
475
566
  isAvailable(): Promise<boolean>;
476
567
  /**
@@ -481,12 +572,29 @@ export class BrowserSessionStore {
481
572
  * Remove local stored session metadata and any SDK-owned delegated key for that record.
482
573
  */
483
574
  remove(id: string): Promise<void>;
575
+ /**
576
+ * Recover a stored session's encryption keys without network access.
577
+ * Verifies the signed approval, exact grant binding, and requested session
578
+ * identity. Works after grant expiry/revocation and without the delegated
579
+ * WebCrypto signing key.
580
+ * Restore material is stored as plaintext in IndexedDB.
581
+ * Bare-grant records return undefined; signed approvals without `e` scopes
582
+ * return an empty bundle. Creates no session.
583
+ */
584
+ restoreEncryptionKeys(id: string): Promise<EncryptionKeys | undefined>;
484
585
  /**
485
586
  * Restore a specific stored session by id.
587
+ *
588
+ * Shares one bearer with other tabs on this origin. Requires IndexedDB and
589
+ * Web Locks in a secure browser context.
486
590
  */
487
591
  restore(id: string): Promise<Session>;
488
592
  /**
489
- * Persist a completed grant session in IndexedDB.
593
+ * Persist a completed grant session and its optional signed approval.
594
+ * Confidential restore material is stored as plaintext in IndexedDB.
595
+ * Delegated signing keys remain non-extractable WebCrypto keys.
596
+ * Records are identified by user and grant ID. Reauthorizing the same
597
+ * client creates a separate record; use the returned ID to restore it.
490
598
  */
491
599
  save(session: Session): Promise<StoredSessionInfo>;
492
600
  }
@@ -614,6 +722,42 @@ export class DirectSignupDeepLink {
614
722
  readonly xCallback: XCallbackParams;
615
723
  }
616
724
 
725
+ /**
726
+ * Verified scoped encryption keys obtained from a session or offline recovery.
727
+ * This object owns its secrets independently of the session and grants no storage
728
+ * access. Call `free()` when finished; secret bytes are zeroized on free/drop.
729
+ */
730
+ export class EncryptionKeys {
731
+ private constructor();
732
+ free(): void;
733
+ [Symbol.dispose](): void;
734
+ /**
735
+ * Derive a non-extractable AES-GCM-256 key for WebCrypto encryption/decryption.
736
+ * Rejects directories and files outside approved scopes. Requires WebCrypto.
737
+ * Temporary JS key bytes are cleared after import, including on failure.
738
+ */
739
+ deriveCryptoKeyForPath(path: string): Promise<CryptoKey>;
740
+ /**
741
+ * Derive a 32-byte content key for a canonical decoded file path.
742
+ * Rejects directories and files outside the approved scopes. The caller
743
+ * owns the JS byte copy and should clear it after use.
744
+ */
745
+ deriveForPath(path: string): Uint8Array;
746
+ /**
747
+ * Recover authenticated identity and keys from an exportLocalSecret() token offline.
748
+ * Verifies the signed approval and grant binding even if the grant expired
749
+ * or was revoked. Bare-grant tokens return undefined; signed approvals
750
+ * without `e` scopes return a result with an empty key bundle.
751
+ * Check publicKey (z-base-32) against the expected account and grantId
752
+ * against the expected grant before using keys. Call keys.free() when done.
753
+ */
754
+ static fromLocalSecret(token: string): RecoveredEncryptionKeys | undefined;
755
+ /**
756
+ * Approved scopes; directory scopes cover descendant files.
757
+ */
758
+ readonly scopes: string[];
759
+ }
760
+
617
761
  /**
618
762
  * A single event from the event stream.
619
763
  *
@@ -864,7 +1008,8 @@ export class GrantAuthFlow {
864
1008
  * Resume a previously saved pending grant auth flow (standalone).
865
1009
  * Prefer `pubky.resumeGrantAuthFlow()` to reuse a facade client.
866
1010
  *
867
- * **Security:** `savedState` contains the relay secret and PoP client private key.
1011
+ * **Security:** `savedState` contains the PoP client private key and either
1012
+ * a shared relay secret or a temporary HPKE recipient secret.
868
1013
  * Store it only temporarily and delete it once the flow completes or is abandoned.
869
1014
  *
870
1015
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
@@ -874,9 +1019,9 @@ export class GrantAuthFlow {
874
1019
  /**
875
1020
  * Save sensitive state required to resume this delegated pending grant flow.
876
1021
  *
877
- * This does not export the delegated private key, but it includes the relay
878
- * secret in the authorization URL. Store it only temporarily and delete it
879
- * once the flow completes or is abandoned.
1022
+ * This includes either a shared relay secret or a temporary HPKE recipient
1023
+ * secret. Store it only temporarily and delete it once the flow completes
1024
+ * or is abandoned.
880
1025
  */
881
1026
  saveDelegated(): string;
882
1027
  /**
@@ -1009,11 +1154,19 @@ export class GrantSession {
1009
1154
  private constructor();
1010
1155
  free(): void;
1011
1156
  [Symbol.dispose](): void;
1157
+ /**
1158
+ * Sign JSON data and return both the custom proof and its root-signed grant.
1159
+ * Data follows `JSON.stringify` semantics, e.g. `undefined` properties are omitted.
1160
+ * Each proof carries its signing time (`iat`) and a random `nonce`.
1161
+ * Makes no network requests. Your applications handle freshness and replay protection.
1162
+ */
1163
+ createCustomPop(data: JsonValue): Promise<CustomPop>;
1012
1164
  /**
1013
1165
  * Export the portable local secret material needed to restore this grant session.
1014
1166
  *
1015
1167
  * Treat the returned string as bearer-equivalent secret material until the
1016
- * grant expires or is revoked.
1168
+ * grant expires or is revoked. Included scoped keys remain sensitive after
1169
+ * expiry or revocation.
1017
1170
  *
1018
1171
  * @returns {Promise<string>}
1019
1172
  */
@@ -1030,6 +1183,15 @@ export class GrantSession {
1030
1183
  * @returns {Promise<GrantSessionInfo>}
1031
1184
  */
1032
1185
  sessionInfo(): Promise<GrantSessionInfo>;
1186
+ /**
1187
+ * Return scoped keys with the same derivation API as offline recovered keys.
1188
+ * Bare grants return `undefined`; signed approvals without `e` scopes
1189
+ * return an object with empty scopes.
1190
+ * Each access creates an owned copy. Keep it for repeated use and call
1191
+ * `free()` when finished. It remains usable after this view or its session
1192
+ * is freed, signed out, or revoked; freeing it does not affect the session.
1193
+ */
1194
+ readonly encryptionKeys: EncryptionKeys | undefined;
1033
1195
  }
1034
1196
 
1035
1197
  /**
@@ -1367,8 +1529,9 @@ export class Pubky {
1367
1529
  /**
1368
1530
  * Resume a previously saved pending grant auth flow.
1369
1531
  *
1370
- * **Security:** `savedState` contains the relay secret and PoP client private key.
1371
- * Delete it from storage as soon as the resumed flow completes.
1532
+ * **Security:** `savedState` contains the PoP client private key and either
1533
+ * a shared relay secret or a temporary HPKE private key. Delete it from
1534
+ * storage when the flow completes or is abandoned.
1372
1535
  *
1373
1536
  * @param {string} savedState A string produced by `grantFlow.saveLocal()`.
1374
1537
  * @returns {GrantAuthFlow} A flow reconnected to the original relay channel.
@@ -1427,11 +1590,13 @@ export class Pubky {
1427
1590
  *
1428
1591
  * Grant auth uses a user-signed grant JWS plus Proof-of-Possession and
1429
1592
  * returns a self-refreshing session.
1593
+ * Select `approvalFormat: "signedApprovalV1"` and include `e` on scopes that need content
1594
+ * keys. Storage `r` and `w` alone do not deliver encryption keys.
1430
1595
  *
1431
1596
  * @param {string} capabilities Comma-separated caps, e.g. `"/pub/app/:rw,/pub/foo/file:r"`.
1432
1597
  * @param {AuthFlowKind} kind The kind of authentication flow to perform.
1433
1598
  * @param {GrantAuthFlowOptions} options Options for the grant flow:
1434
- * `{ clientId, relay?, xCallback? }`.
1599
+ * `{ clientId, relay?, xCallback?, approvalFormat? }`.
1435
1600
  * @returns {Promise<GrantAuthFlow>}
1436
1601
  * A running grant auth flow. Show `authorizationUrl` as QR/deeplink,
1437
1602
  * then `awaitApproval()` to obtain a grant-backed `Session`.
@@ -1778,12 +1943,23 @@ export class SessionInfo {
1778
1943
  * Only {@link SessionStorage.exists} and {@link SessionStorage.stats} treat
1779
1944
  * HTTP 404/410 as missing. Other HTTP failures, including 401/403 and 5xx, reject.
1780
1945
  * Writes require write permission and can fail on directory targets (400),
1781
- * file/directory path conflicts (409), or exceeded quotas (507).
1946
+ * file/directory path conflicts (409), a path someone holds a
1947
+ * {@link StorageLock} on (423), or exceeded quotas (507).
1782
1948
  */
1783
1949
  export class SessionStorage {
1784
1950
  private constructor();
1785
1951
  free(): void;
1786
1952
  [Symbol.dispose](): void;
1953
+ /**
1954
+ * `DELETE` the path of a lock this client holds. The lock stays in place.
1955
+ *
1956
+ * @param {StorageLock} lock A lock this client holds.
1957
+ * @returns {Promise<void>}
1958
+ * @throws {PubkyError} A lock that has expired or was unlocked rejects with
1959
+ * `RequestError` and status `412`; take a new one.
1960
+ * Otherwise as {@link SessionStorage.delete}.
1961
+ */
1962
+ deleteLocked(lock: StorageLock): Promise<void>;
1787
1963
  /**
1788
1964
  * Delete a file. Directory targets are unsupported.
1789
1965
  *
@@ -1850,6 +2026,35 @@ export class SessionStorage {
1850
2026
  * entries, or shared errors (see {@link SessionStorage}).
1851
2027
  */
1852
2028
  list(path: Path, cursor?: string | null, reverse?: boolean | null, limit?: number | null, shallow?: boolean | null): Promise<string[]>;
2029
+ /**
2030
+ * Take an exclusive write lock on a file at an **absolute session path**.
2031
+ *
2032
+ * The file need not exist; locking a free path reserves it. `timeoutSeconds` is
2033
+ * the lifetime asked for; the homeserver caps it, and
2034
+ * {@link StorageLock.timeoutSeconds} tells what was granted. A write made under
2035
+ * the lock keeps it alive until the write ends, so it need not cover the upload itself.
2036
+ *
2037
+ * @param {Path} path File path; must not end with `/`.
2038
+ * @param {number} timeoutSeconds Lifetime to ask for, in seconds; rounded down to
2039
+ * whole seconds, and at least 1.
2040
+ * @returns {Promise<StorageLock>} The granted lock.
2041
+ * @throws {PubkyError} `InvalidInput` for a negative, NaN or out-of-range timeout;
2042
+ * a path that is already locked rejects with `RequestError` and status `423`;
2043
+ * directory targets with `400`; a homeserver without lock support with `405`.
2044
+ * See {@link SessionStorage} for shared errors.
2045
+ */
2046
+ lock(path: Path, timeout_seconds: number): Promise<StorageLock>;
2047
+ /**
2048
+ * `PUT` binary data to the path of a lock this client holds.
2049
+ *
2050
+ * @param {StorageLock} lock A lock this client holds.
2051
+ * @param {Uint8Array} body
2052
+ * @returns {Promise<void>}
2053
+ * @throws {PubkyError} A lock that has expired or was unlocked rejects with
2054
+ * `RequestError` and status `412`; take a new one and read the file again.
2055
+ * Otherwise as {@link SessionStorage.putBytes}.
2056
+ */
2057
+ putBytesLocked(lock: StorageLock, body: Uint8Array): Promise<void>;
1853
2058
  /**
1854
2059
  * Create or replace a file with binary data at an absolute session path.
1855
2060
  *
@@ -1869,6 +2074,17 @@ export class SessionStorage {
1869
2074
  * otherwise serialization, upload, or shared write/request errors (see {@link SessionStorage}).
1870
2075
  */
1871
2076
  putJson(path: Path, body: any): Promise<void>;
2077
+ /**
2078
+ * `PUT` text to the path of a lock this client holds.
2079
+ *
2080
+ * @param {StorageLock} lock A lock this client holds.
2081
+ * @param {string} body
2082
+ * @returns {Promise<void>}
2083
+ * @throws {PubkyError} A lock that has expired or was unlocked rejects with
2084
+ * `RequestError` and status `412`; take a new one and read the file again.
2085
+ * Otherwise as {@link SessionStorage.putText}.
2086
+ */
2087
+ putTextLocked(lock: StorageLock, body: string): Promise<void>;
1872
2088
  /**
1873
2089
  * Create or replace a file with text at an absolute session path.
1874
2090
  *
@@ -1878,6 +2094,22 @@ export class SessionStorage {
1878
2094
  * @throws {PubkyError} Upload or shared write/request errors (see {@link SessionStorage}).
1879
2095
  */
1880
2096
  putText(path: Path, body: string): Promise<void>;
2097
+ /**
2098
+ * Restart the lifetime of a lock this client holds.
2099
+ *
2100
+ * On success the lock carries the newly granted
2101
+ * {@link StorageLock.timeoutSeconds}.
2102
+ *
2103
+ * @param {StorageLock} lock A lock this client holds.
2104
+ * @param {number} timeoutSeconds Lifetime to ask for, in seconds; rounded down to
2105
+ * whole seconds, and at least 1.
2106
+ * @returns {Promise<void>}
2107
+ * @throws {PubkyError} A lock that has expired or was unlocked rejects with
2108
+ * `RequestError` and status `412`; take a new one and read the file again.
2109
+ * `InvalidInput` for a negative, NaN or out-of-range timeout.
2110
+ * See {@link SessionStorage} for shared errors.
2111
+ */
2112
+ refreshLock(lock: StorageLock, timeout_seconds: number): Promise<void>;
1881
2113
  /**
1882
2114
  * Get metadata for an absolute, session-scoped path (e.g. `"/pub/app/file.json"`).
1883
2115
  *
@@ -1888,6 +2120,15 @@ export class SessionStorage {
1888
2120
  * @throws {PubkyError} All other failures (see {@link SessionStorage}).
1889
2121
  */
1890
2122
  stats(path: Path): Promise<ResourceStats | undefined>;
2123
+ /**
2124
+ * Release a lock this client holds.
2125
+ *
2126
+ * @param {StorageLock} lock A lock this client holds.
2127
+ * @returns {Promise<void>}
2128
+ * @throws {PubkyError} A lock that no longer exists rejects with `RequestError`
2129
+ * and status `409`. See {@link SessionStorage} for shared errors.
2130
+ */
2131
+ unlock(lock: StorageLock): Promise<void>;
1891
2132
  }
1892
2133
 
1893
2134
  /**
@@ -1922,7 +2163,8 @@ export class Signer {
1922
2163
  /**
1923
2164
  * Blocking sign-in. Waits for PKDNS publish to complete (slower; safer).
1924
2165
  *
1925
- * Creates a valid grant-backed homeserver Session with root capabilities.
2166
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
2167
+ * Encryption keys require an explicit `e` request through a signed approval flow.
1926
2168
  * `clientId` is shown in the user's grant/session list.
1927
2169
  * @param {string} clientId App identifier, typically a domain.
1928
2170
  *
@@ -1944,7 +2186,8 @@ export class Signer {
1944
2186
  /**
1945
2187
  * Fast sign-in for a returning user. Publishes PKDNS in the background.
1946
2188
  *
1947
- * Creates a valid grant-backed homeserver Session with root capabilities.
2189
+ * Creates a grant-backed homeserver session with root storage access (`/:rw`).
2190
+ * Encryption keys require an explicit `e` request through a signed approval flow.
1948
2191
  * `clientId` is shown in the user's grant/session list.
1949
2192
  * @param {string} clientId App identifier, typically a domain.
1950
2193
  *
@@ -2029,6 +2272,10 @@ export class SigninGrantDeepLink {
2029
2272
  * @returns {string}
2030
2273
  */
2031
2274
  toString(): string;
2275
+ /**
2276
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
2277
+ */
2278
+ readonly approvalFormat: string;
2032
2279
  /**
2033
2280
  * Base HTTP relay inbox URL used by this auth request.
2034
2281
  *
@@ -2054,13 +2301,21 @@ export class SigninGrantDeepLink {
2054
2301
  */
2055
2302
  readonly clientPublicKey: PublicKey;
2056
2303
  /**
2057
- * Relay channel secret embedded in the deeplink.
2304
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
2305
+ * for a shared-secret link. The relay channel ID is the unpadded
2306
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
2307
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
2058
2308
  *
2059
- * Treat this as sensitive temporary auth-flow material.
2309
+ * @returns {Uint8Array|undefined}
2310
+ */
2311
+ readonly ephemeralPublicKey: Uint8Array | undefined;
2312
+ /**
2313
+ * Shared relay secret, or `undefined` for an HPKE link.
2314
+ * Keep this secret confidential until the flow completes or is abandoned.
2060
2315
  *
2061
- * @returns {Uint8Array}
2316
+ * @returns {Uint8Array|undefined}
2062
2317
  */
2063
- readonly secret: Uint8Array;
2318
+ readonly secret: Uint8Array | undefined;
2064
2319
  /**
2065
2320
  * Optional x-callback-url metadata carried by this deep link.
2066
2321
  */
@@ -2108,6 +2363,10 @@ export class SignupGrantDeepLink {
2108
2363
  * @returns {string}
2109
2364
  */
2110
2365
  toString(): string;
2366
+ /**
2367
+ * Negotiated approval format: `bareGrant` or `signedApprovalV1`.
2368
+ */
2369
+ readonly approvalFormat: string;
2111
2370
  /**
2112
2371
  * Base HTTP relay inbox URL used by this auth request.
2113
2372
  *
@@ -2132,6 +2391,15 @@ export class SignupGrantDeepLink {
2132
2391
  * @returns {PublicKey}
2133
2392
  */
2134
2393
  readonly clientPublicKey: PublicKey;
2394
+ /**
2395
+ * The 32-byte ephemeral HPKE public key from `epk`, or `undefined`
2396
+ * for a shared-secret link. The relay channel ID is the unpadded
2397
+ * base64url encoding of BLAKE3("pubky-grant-relay-channel-v1" || 0x00 || epk),
2398
+ * where `||` concatenates bytes and `epk` is the raw 32-byte public key.
2399
+ *
2400
+ * @returns {Uint8Array|undefined}
2401
+ */
2402
+ readonly ephemeralPublicKey: Uint8Array | undefined;
2135
2403
  /**
2136
2404
  * Homeserver public key requested for account creation.
2137
2405
  *
@@ -2139,13 +2407,12 @@ export class SignupGrantDeepLink {
2139
2407
  */
2140
2408
  readonly homeserver: PublicKey;
2141
2409
  /**
2142
- * Relay channel secret embedded in the deeplink.
2143
- *
2144
- * Treat this as sensitive temporary auth-flow material.
2410
+ * Shared relay secret, or `undefined` for an HPKE link.
2411
+ * Keep this secret confidential until the flow completes or is abandoned.
2145
2412
  *
2146
- * @returns {Uint8Array}
2413
+ * @returns {Uint8Array|undefined}
2147
2414
  */
2148
- readonly secret: Uint8Array;
2415
+ readonly secret: Uint8Array | undefined;
2149
2416
  /**
2150
2417
  * Optional signup token included in the request.
2151
2418
  *
@@ -2158,6 +2425,52 @@ export class SignupGrantDeepLink {
2158
2425
  readonly xCallback: XCallbackParams;
2159
2426
  }
2160
2427
 
2428
+ /**
2429
+ * An exclusive write lock on one file path, granted by the homeserver.
2430
+ *
2431
+ * While the lock lives, only writes that present it are accepted on the path;
2432
+ * every other write, and every other {@link SessionStorage.lock}, rejects with
2433
+ * `423 Locked`. The lock ends when it is {@link SessionStorage.unlock | unlocked}
2434
+ * or when its {@link StorageLock.timeoutSeconds} runs out without a
2435
+ * {@link SessionStorage.refreshLock | refresh}, so a client that disappears never
2436
+ * blocks a path for long.
2437
+ *
2438
+ * The lock is a bearer token: whoever holds this value, and may write the path,
2439
+ * can use and release the lock.
2440
+ *
2441
+ * Calls that take the lock may overlap: a {@link SessionStorage.refreshLock | refresh}
2442
+ * can run while a locked write is in flight.
2443
+ *
2444
+ * @example
2445
+ * const lock = await session.storage.lock("/pub/my.app/state.json", 30);
2446
+ * const state = await session.storage.getText(lock.path);
2447
+ * await session.storage.putTextLocked(lock, state);
2448
+ * await session.storage.unlock(lock);
2449
+ */
2450
+ export class StorageLock {
2451
+ private constructor();
2452
+ free(): void;
2453
+ [Symbol.dispose](): void;
2454
+ /**
2455
+ * Value of the `If` header that presents this lock on a write.
2456
+ */
2457
+ ifHeader(): string;
2458
+ /**
2459
+ * The locked path.
2460
+ */
2461
+ readonly path: Path;
2462
+ /**
2463
+ * Lifetime the homeserver granted at the last `lock` or `refreshLock`, in seconds,
2464
+ * counted from when that call resolved. It may be shorter than what was asked for.
2465
+ * It is not the time remaining: note when the call resolved to know when to refresh.
2466
+ */
2467
+ readonly timeoutSeconds: number;
2468
+ /**
2469
+ * The lock token URL, `opaquelocktoken:<uuid>`.
2470
+ */
2471
+ readonly token: string;
2472
+ }
2473
+
2161
2474
  /**
2162
2475
  * Metadata for a session saved in the browser session store.
2163
2476
  */
@@ -2237,7 +2550,8 @@ export function setLogLevel(level: Level): void;
2237
2550
  /**
2238
2551
  * Validate and normalize a capabilities string.
2239
2552
  *
2240
- * - Normalizes action order (`wr` -> `rw`)
2553
+ * - Normalizes action order (`wr` -> `rw`, `ewr` -> `rwe`).
2554
+ * - `e` requests scoped encryption/decryption keys in V1 grant approvals.
2241
2555
  * - Throws `InvalidInput` identifying the first malformed entry.
2242
2556
  *
2243
2557
  * @param {string} input
@@ -2246,3 +2560,10 @@ export function setLogLevel(level: Level): void;
2246
2560
  * The error's `data` field is `{ invalidEntries: string[] }` containing the malformed token.
2247
2561
  */
2248
2562
  export function validateCapabilities(input: string): string;
2563
+
2564
+ /**
2565
+ * Verify both signatures, grant binding, and grant validity against the local clock.
2566
+ * Allows 30 seconds of future clock skew by default; never extends grant expiry.
2567
+ * Does not check replay, application authorization, or homeserver revocation.
2568
+ */
2569
+ export function verifyCustomGrantPop(credentials: CustomPop, options?: CustomPopVerificationOptions | null): VerifiedCustomPop;
package/pubky_bg.wasm CHANGED
Binary file