@synonymdev/pubky 0.9.3 → 0.10.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/README.md +134 -46
- package/index.cjs +3336 -1129
- package/index.js +3337 -1130
- package/package.json +7 -2
- package/pubky.d.ts +790 -36
- package/pubky_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -31,21 +31,29 @@ const homeserver = PublicKey.from(
|
|
|
31
31
|
"8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"
|
|
32
32
|
);
|
|
33
33
|
const signupToken = "<your-invite-code-or-null>";
|
|
34
|
-
|
|
34
|
+
await signer.signup(homeserver, signupToken);
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
|
|
36
|
+
// 3a) Signin with the signer directly.
|
|
37
|
+
let session = await signer.signin("example.com");
|
|
38
|
+
|
|
39
|
+
// 3b) Or, if you do not have the keypair available, authenticate on a 3rd-party app
|
|
40
|
+
// by delegating the authentication to a signer with a QR code.
|
|
41
|
+
const authFlow = await pubky.startGrantAuthFlow(
|
|
42
|
+
"/pub/my-cool-app/:rw",
|
|
43
|
+
AuthFlowKind.signin(),
|
|
44
|
+
{ clientId: "my-cool-app.example" },
|
|
45
|
+
);
|
|
46
|
+
renderQr(authFlow.authorizationUrl); // Show to user the signin deeplink via a QR code
|
|
47
|
+
session = await authFlow.awaitApproval();
|
|
48
|
+
|
|
49
|
+
// 4) Write a public JSON file
|
|
50
|
+
const path = "/pub/my-cool-app/hello.json";
|
|
38
51
|
await session.storage.putJson(path, { hello: "world" });
|
|
39
52
|
|
|
40
|
-
//
|
|
53
|
+
// 5) Read it publicly (no auth needed)
|
|
41
54
|
const userPk = session.info.publicKey.toString();
|
|
42
|
-
const addr = `${userPk}/pub/
|
|
55
|
+
const addr = `${userPk}/pub/my-cool-app/hello.json`;
|
|
43
56
|
const json = await pubky.publicStorage.getJson(addr); // -> { hello: "world" }
|
|
44
|
-
|
|
45
|
-
// 5) Authenticate on a 3rd-party app
|
|
46
|
-
const authFlow = pubky.startAuthFlow("/pub/my-cool-app/:rw", AuthFlowKind::signin()); // require permissions to read and write into `my.app`
|
|
47
|
-
renderQr(authFlow.authorizationUrl); // show to user
|
|
48
|
-
const session = await authFlow.awaitApproval();
|
|
49
57
|
```
|
|
50
58
|
|
|
51
59
|
Find here [**ready-to-run examples**](https://github.com/pubky/pubky-core/tree/main/examples).
|
|
@@ -84,8 +92,12 @@ const pubkyLocal = Pubky.testnet("localhost");
|
|
|
84
92
|
// Signer (bind your keypair to a new Signer actor)
|
|
85
93
|
const signer = pubky.signer(Keypair.random());
|
|
86
94
|
|
|
87
|
-
// Pubky Auth flow (with capabilities)
|
|
88
|
-
const authFlow = pubky.
|
|
95
|
+
// Grant Pubky Auth flow (with capabilities)
|
|
96
|
+
const authFlow = await pubky.startGrantAuthFlow(
|
|
97
|
+
"/pub/my-cool-app/:rw",
|
|
98
|
+
AuthFlowKind.signin(),
|
|
99
|
+
{ clientId: "my-cool-app.example" },
|
|
100
|
+
);
|
|
89
101
|
|
|
90
102
|
// Public storage (read-only)
|
|
91
103
|
const publicStorage = pubky.publicStorage;
|
|
@@ -106,7 +118,7 @@ const client = new Client(); // or: pubky.client.fetch(); instead of constructin
|
|
|
106
118
|
|
|
107
119
|
// Convert the identifier into a transport URL before fetching.
|
|
108
120
|
const userId = PublicKey.from("pubky<z32>").toString();
|
|
109
|
-
const url = resolvePubky(`${userId}/pub/
|
|
121
|
+
const url = resolvePubky(`${userId}/pub/my-cool-app/file.txt`);
|
|
110
122
|
const res = await client.fetch(url);
|
|
111
123
|
```
|
|
112
124
|
|
|
@@ -155,12 +167,12 @@ const keypair = Keypair.random();
|
|
|
155
167
|
const signer = pubky.signer(keypair);
|
|
156
168
|
|
|
157
169
|
const homeserver = PublicKey.from("8pinxxgq…");
|
|
158
|
-
|
|
170
|
+
await signer.signup(homeserver); // Add optional signup code here if required
|
|
159
171
|
|
|
160
|
-
const
|
|
161
|
-
const
|
|
172
|
+
const session1 = await signer.signin("example.com"); // fast, prefer this; publishes PKDNS in background
|
|
173
|
+
const session2 = await signer.signinBlocking("example.com"); // slower but safer; waits for PKDNS publish
|
|
162
174
|
|
|
163
|
-
await
|
|
175
|
+
await session1.signout(); // invalidates server session
|
|
164
176
|
```
|
|
165
177
|
|
|
166
178
|
**Session details**
|
|
@@ -172,29 +184,60 @@ const caps = session.info.capabilities; // -> string[] permissions and paths
|
|
|
172
184
|
const storage = session.storage; // -> This User's storage API (absolute paths)
|
|
173
185
|
```
|
|
174
186
|
|
|
175
|
-
**Persist a session
|
|
187
|
+
**Persist a grant session**
|
|
176
188
|
|
|
177
189
|
```js
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
// Persist explicitly when your app wants reload/returning-user restore.
|
|
191
|
+
// In browsers, this uses IndexedDB and supports multiple accounts.
|
|
192
|
+
const stored = await pubky.browserSessionStore.save(session);
|
|
193
|
+
|
|
194
|
+
const accounts = await pubky.browserSessionStore.list();
|
|
195
|
+
const restored = await pubky.browserSessionStore.restore(stored.id);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`pubky.startGrantAuthFlow(...)` chooses delegated browser PoP keys
|
|
199
|
+
when the runtime supports them, then falls back to local PoP keys. Delegated
|
|
200
|
+
sessions keep the private PoP key non-extractable in browser storage; local
|
|
201
|
+
sessions store bearer-equivalent secret material in IndexedDB.
|
|
181
202
|
|
|
182
|
-
|
|
183
|
-
const
|
|
203
|
+
```js
|
|
204
|
+
const flow = await pubky.startGrantAuthFlow(
|
|
205
|
+
"/pub/example.com/:rw",
|
|
206
|
+
AuthFlowKind.signin(),
|
|
207
|
+
{ clientId: "example.com" },
|
|
208
|
+
);
|
|
209
|
+
const session = await flow.awaitApproval();
|
|
210
|
+
await pubky.browserSessionStore.save(session);
|
|
184
211
|
```
|
|
185
212
|
|
|
186
|
-
|
|
187
|
-
|
|
213
|
+
For advanced/manual storage, local grant sessions still expose
|
|
214
|
+
`exportLocalSecret()` and `pubky.restoreSession(secret)`. Treat that string
|
|
215
|
+
like a bearer token. Delegated browser grant sessions should use
|
|
216
|
+
`browserSessionStore` because their PoP keys are non-extractable.
|
|
217
|
+
|
|
218
|
+
> `session.export()` is still available for legacy cookie sessions, but new
|
|
219
|
+
> applications should use grant auth plus `browserSessionStore` or
|
|
220
|
+
> `exportLocalSecret()`.
|
|
188
221
|
|
|
189
222
|
**Approve a pubkyauth request URL**
|
|
190
223
|
|
|
191
224
|
```js
|
|
192
|
-
await signer.approveAuthRequest("pubkyauth
|
|
225
|
+
await signer.approveAuthRequest("pubkyauth://signin?caps=...&secret=...&relay=...");
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Handle a pubkyauth deep link**
|
|
229
|
+
|
|
230
|
+
General entry point for any supported `pubkyauth://` link. Auth request links
|
|
231
|
+
are approved through their relay (like `approveAuthRequest`), while a
|
|
232
|
+
`direct_signup` link creates the account directly on its target homeserver:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
await signer.handleDeepLink("pubkyauth://direct_signup?hs=...");
|
|
193
236
|
```
|
|
194
237
|
|
|
195
238
|
---
|
|
196
239
|
|
|
197
|
-
###
|
|
240
|
+
### GrantAuthFlow (pubkyauth)
|
|
198
241
|
|
|
199
242
|
End-to-end auth (3rd-party app asks a user to approve via QR/deeplink, E.g. Pubky Ring).
|
|
200
243
|
|
|
@@ -208,8 +251,21 @@ const caps = "/pub/my-cool-app/:rw,/pub/another-app/folder/:w";
|
|
|
208
251
|
// Optional relay; defaults to Synonym-hosted relay if omitted
|
|
209
252
|
const relay = "https://httprelay.pubky.app/inbox/"; // optional (defaults to this)
|
|
210
253
|
|
|
211
|
-
// Start
|
|
212
|
-
const flow = pubky.
|
|
254
|
+
// Start grant auth polling
|
|
255
|
+
const flow = await pubky.startGrantAuthFlow(
|
|
256
|
+
caps,
|
|
257
|
+
AuthFlowKind.signin(),
|
|
258
|
+
{
|
|
259
|
+
clientId: "my-cool-app.example",
|
|
260
|
+
relay,
|
|
261
|
+
xCallback: {
|
|
262
|
+
xSource: "My Cool App",
|
|
263
|
+
xSuccess: "my-cool-app://auth/success?nonce=unique",
|
|
264
|
+
xError: "my-cool-app://auth/error?nonce=unique",
|
|
265
|
+
xCancel: "my-cool-app://auth/cancel?nonce=unique",
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
);
|
|
213
269
|
|
|
214
270
|
renderQr(flow.authorizationUrl); // show to user
|
|
215
271
|
|
|
@@ -217,10 +273,40 @@ renderQr(flow.authorizationUrl); // show to user
|
|
|
217
273
|
const session = await flow.awaitApproval();
|
|
218
274
|
```
|
|
219
275
|
|
|
276
|
+
The same callback object can be passed as the fourth argument to legacy cookie
|
|
277
|
+
auth: `startCookieAuthFlow(caps, kind, relay, xCallback)`. The SDK emits
|
|
278
|
+
`x-source`, `x-success`, `x-error`, and `x-cancel` using the encoding expected
|
|
279
|
+
by Pubky Ring. Parsed deep-link objects expose them through `xCallback`; the
|
|
280
|
+
legacy `callback` parameter is accepted as an `xSuccess` fallback.
|
|
281
|
+
|
|
282
|
+
#### Resume an auth flow after page refresh
|
|
283
|
+
|
|
284
|
+
Grant auth flows are resumable by saving the pending flow state before refresh and restoring it afterwards:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
const flow = pubky.startGrantAuthFlow(caps, AuthFlowKind.signin(), {
|
|
288
|
+
clientId: "my-cool-app.example",
|
|
289
|
+
relay,
|
|
290
|
+
});
|
|
291
|
+
sessionStorage.setItem("pubky-grant-auth", flow.save());
|
|
292
|
+
|
|
293
|
+
const saved = sessionStorage.getItem("pubky-grant-auth");
|
|
294
|
+
if (saved) {
|
|
295
|
+
try {
|
|
296
|
+
const resumed = pubky.resumeGrantAuthFlow(saved);
|
|
297
|
+
const session = await resumed.awaitApproval();
|
|
298
|
+
} finally {
|
|
299
|
+
sessionStorage.removeItem("pubky-grant-auth");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Legacy cookie auth flows can be resumed with `pubky.resumeCookieAuthFlow(authorizationUrl)`. Store pending auth state in `sessionStorage`, not `localStorage`, and delete it once the flow completes or is abandoned.
|
|
305
|
+
|
|
220
306
|
#### Validate and normalize capabilities
|
|
221
307
|
|
|
222
308
|
If you accept capability strings from user input (forms, CLI arguments, etc.),
|
|
223
|
-
use `validateCapabilities` before calling `
|
|
309
|
+
use `validateCapabilities` before calling `startGrantAuthFlow`. The helper returns a
|
|
224
310
|
normalized string (ordering actions like `:rw`) and throws a structured error
|
|
225
311
|
when the input is malformed.
|
|
226
312
|
|
|
@@ -233,7 +319,9 @@ const rawCaps = formData.get("caps");
|
|
|
233
319
|
|
|
234
320
|
try {
|
|
235
321
|
const caps = validateCapabilities(rawCaps ?? "");
|
|
236
|
-
const flow = pubky.
|
|
322
|
+
const flow = await pubky.startGrantAuthFlow(caps, AuthFlowKind.signin(), {
|
|
323
|
+
clientId: "my-cool-app.example",
|
|
324
|
+
});
|
|
237
325
|
renderQr(flow.authorizationUrl);
|
|
238
326
|
const session = await flow.awaitApproval();
|
|
239
327
|
// ...
|
|
@@ -246,15 +334,18 @@ try {
|
|
|
246
334
|
}
|
|
247
335
|
```
|
|
248
336
|
|
|
249
|
-
On invalid input, `validateCapabilities` throws a `PubkyError`
|
|
250
|
-
|
|
251
|
-
surface precise feedback to the user.
|
|
337
|
+
On invalid input, `validateCapabilities` throws a `PubkyError` identifying the
|
|
338
|
+
first malformed entry and its position. Its `data.invalidEntries` array contains
|
|
339
|
+
that entry so applications can surface precise feedback to the user.
|
|
340
|
+
|
|
341
|
+
Capability scopes must be canonical absolute paths. Repeated separators and
|
|
342
|
+
`.` or `..` segments are rejected rather than normalized. Percent sequences are
|
|
343
|
+
literal scope characters; URL encoding is handled by the enclosing deep link.
|
|
252
344
|
|
|
253
345
|
#### Http Relay & reliability
|
|
254
346
|
|
|
255
|
-
- If you don’t specify a relay,
|
|
256
|
-
- For production and larger apps, run **your own http relay** (MIT, Docker): [https://
|
|
257
|
-
The channel is derived as `base64url(hash(secret))`; the token is end-to-end encrypted with the `secret` and cannot be decrypted by the relay.
|
|
347
|
+
- If you don’t specify a relay, the auth flow defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
|
|
348
|
+
- For production and larger apps, run **your own http relay** (MIT, Docker): [https://github.com/pubky/http-relay](https://github.com/pubky/http-relay).
|
|
258
349
|
|
|
259
350
|
---
|
|
260
351
|
|
|
@@ -290,7 +381,7 @@ await pub.list(
|
|
|
290
381
|
|
|
291
382
|
Use `get()` when you need the raw `Response` for streaming or custom parsing.
|
|
292
383
|
|
|
293
|
-
#### SessionStorage (read/write
|
|
384
|
+
#### SessionStorage (read/write)
|
|
294
385
|
|
|
295
386
|
```js
|
|
296
387
|
const s = session.storage;
|
|
@@ -321,11 +412,13 @@ await s.delete("/pub/example.com/data.json");
|
|
|
321
412
|
|
|
322
413
|
Path rules:
|
|
323
414
|
|
|
324
|
-
- Session storage uses **absolute** paths
|
|
415
|
+
- Session storage uses **absolute** paths under `/pub/` or `/priv/`.
|
|
325
416
|
- Public storage uses **addressed** form `pubky<user>/pub/app/file.txt` (preferred) or `pubky://<user>/...`.
|
|
326
417
|
|
|
327
418
|
**Convention:** put your app’s public data under a domain-like folder in `/pub`, e.g. `/pub/my-new-app/`.
|
|
328
419
|
|
|
420
|
+
See [Private Storage](https://github.com/pubky/pubky-core/blob/main/docs/PRIVATE_STORAGE.md) for `/priv/` access and events.
|
|
421
|
+
|
|
329
422
|
---
|
|
330
423
|
|
|
331
424
|
### PKDNS (Pkarr)
|
|
@@ -338,7 +431,8 @@ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
|
338
431
|
const pubky = new Pubky();
|
|
339
432
|
|
|
340
433
|
// Read-only resolver
|
|
341
|
-
const homeserver = await pubky.getHomeserverOf(PublicKey.from("pubky<z32>")); //
|
|
434
|
+
const homeserver = await pubky.getHomeserverOf(PublicKey.from("pubky<z32>")); // PublicKey | undefined
|
|
435
|
+
// Rejects if PKARR resolution fails or the `_pubky` target is malformed.
|
|
342
436
|
|
|
343
437
|
// With keys (signer-bound)
|
|
344
438
|
const signer = pubky.signer(Keypair.random());
|
|
@@ -423,12 +517,6 @@ try {
|
|
|
423
517
|
}
|
|
424
518
|
```
|
|
425
519
|
|
|
426
|
-
## Browser environment notes
|
|
427
|
-
|
|
428
|
-
- Keep the Pubky client UI and the homeserver on the **same origin family** (both local or both remote). Browsers partition cookies by scheme/host, and cross-site requests (e.g., http://localhost calling https://staging…) can silently drop or cache `SameSite`/`Secure` session cookies.
|
|
429
|
-
- If you must mix environments, use a reverse proxy so the browser always talks to one consistent origin (or disable caching via devtools and clear cookies between switches).
|
|
430
|
-
- When troubleshooting auth/session caching: open a fresh incognito window, clear site data for the target origin, and verify the request includes credentials.
|
|
431
|
-
|
|
432
520
|
---
|
|
433
521
|
|
|
434
522
|
## Local Test & Development
|