@synonymdev/pubky 0.6.0-rc.2 → 0.6.0-rc.7
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 +167 -45
- package/index.cjs +1335 -514
- package/index.js +1454 -601
- package/package.json +9 -5
- package/pubky.d.ts +397 -167
- package/pubky_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Module system + TS types: ESM and CommonJS both supported; TypeScript typings ge
|
|
|
17
17
|
## Getting Started
|
|
18
18
|
|
|
19
19
|
```js
|
|
20
|
-
import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
20
|
+
import { Pubky, PublicKey, Keypair, AuthFlowKind } from "@synonymdev/pubky";
|
|
21
21
|
|
|
22
22
|
// Initiate a Pubky SDK facade wired for default mainnet Pkarr relays.
|
|
23
23
|
const pubky = new Pubky(); // or: const pubky = Pubky.testnet(); for localhost testnet.
|
|
@@ -35,22 +35,31 @@ const session = await signer.signup(homeserver, signupToken);
|
|
|
35
35
|
|
|
36
36
|
// 3) Write a public JSON file (session-scoped storage uses cookies automatically)
|
|
37
37
|
const path = "/pub/example.com/hello.json";
|
|
38
|
-
await session.storage
|
|
38
|
+
await session.storage.putJson(path, { hello: "world" });
|
|
39
39
|
|
|
40
40
|
// 4) Read it publicly (no auth needed)
|
|
41
|
-
const userPk = session.info
|
|
42
|
-
const addr =
|
|
43
|
-
const json = await pubky.publicStorage
|
|
41
|
+
const userPk = session.info.publicKey.z32();
|
|
42
|
+
const addr = `pubky${userPk}/pub/example.com/hello.json`;
|
|
43
|
+
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();
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
Find here [**ready-to-run examples**](https://github.com/pubky/pubky-core/tree/main/examples).
|
|
47
52
|
|
|
53
|
+
### Initialization & events
|
|
54
|
+
|
|
55
|
+
The npm package bundles the WebAssembly module and **initializes it before exposing any APIs**. This avoids the common wasm-pack pitfall where events fire before the module finishes instantiating. Long-polling flows such as `authFlow.awaitApproval()` or `authFlow.tryPollOnce()` only start their relay calls after the underlying module is ready, so you won't miss approvals while the bundle is loading.
|
|
56
|
+
|
|
48
57
|
## API Overview
|
|
49
58
|
|
|
50
59
|
Use `new Pubky()` to quickly get any flow started:
|
|
51
60
|
|
|
52
61
|
```js
|
|
53
|
-
import { Pubky, Keypair } from "@synonymdev/pubky";
|
|
62
|
+
import { Pubky, Keypair, AuthFlowKind } from "@synonymdev/pubky";
|
|
54
63
|
|
|
55
64
|
// Mainnet (default relays)
|
|
56
65
|
const pubky = new Pubky();
|
|
@@ -62,25 +71,29 @@ const pubkyLocal = Pubky.testnet("localhost");
|
|
|
62
71
|
// Signer (bind your keypair to a new Signer actor)
|
|
63
72
|
const signer = pubky.signer(Keypair.random());
|
|
64
73
|
|
|
74
|
+
// Pubky Auth flow (with capabilities)
|
|
75
|
+
const authFlow = pubky.startAuthFlow("/pub/my-cool-app/:rw", AuthFlowKind::signin());
|
|
76
|
+
|
|
65
77
|
// Public storage (read-only)
|
|
66
|
-
const publicStorage = pubky.publicStorage
|
|
78
|
+
const publicStorage = pubky.publicStorage;
|
|
67
79
|
|
|
68
|
-
//
|
|
69
|
-
const pkdns = pubky.
|
|
80
|
+
// Pkdns resolver
|
|
81
|
+
const pkdns = pubky.getHomeserverOf(publicKey);
|
|
70
82
|
|
|
71
83
|
// Optional: raw HTTP client for advanced use
|
|
72
|
-
const client = pubky.client
|
|
84
|
+
const client = pubky.client;
|
|
73
85
|
```
|
|
74
86
|
|
|
75
87
|
### Client (HTTP bridge)
|
|
76
88
|
|
|
77
89
|
```js
|
|
78
|
-
import { Client } from "@synonymdev/pubky";
|
|
90
|
+
import { Client, resolvePubky } from "@synonymdev/pubky";
|
|
79
91
|
|
|
80
|
-
const client = new Client(); // or: pubky.client(); instead of constructing a client manually
|
|
92
|
+
const client = new Client(); // or: pubky.client.fetch(); instead of constructing a client manually
|
|
81
93
|
|
|
82
|
-
//
|
|
83
|
-
const
|
|
94
|
+
// Convert the identifier into a transport URL before fetching.
|
|
95
|
+
const url = resolvePubky("pubky<pubky>/pub/example.com/file.txt");
|
|
96
|
+
const res = await client.fetch(url);
|
|
84
97
|
```
|
|
85
98
|
|
|
86
99
|
---
|
|
@@ -91,7 +104,7 @@ const res = await client.fetch("pubky://<pubky>/pub/example.com/file.txt");
|
|
|
91
104
|
import { Keypair, PublicKey } from "@synonymdev/pubky";
|
|
92
105
|
|
|
93
106
|
const keypair = Keypair.random();
|
|
94
|
-
const pubkey = keypair.publicKey
|
|
107
|
+
const pubkey = keypair.publicKey;
|
|
95
108
|
|
|
96
109
|
// z-base-32 roundtrip
|
|
97
110
|
const parsed = PublicKey.from(pubkey.z32());
|
|
@@ -138,13 +151,26 @@ await session.signout(); // invalidates server session
|
|
|
138
151
|
**Session details**
|
|
139
152
|
|
|
140
153
|
```js
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
154
|
+
const userPk = session.info.publicKey.z32(); // -> PublicKey as z32 string
|
|
155
|
+
const caps = session.info.capabilities; // -> string[] permissions and paths
|
|
156
|
+
|
|
157
|
+
const storage = session.storage; // -> This User's storage API (absolute paths)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Persist a session across tab refreshes (browser)**
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// Save the session snapshot (no secrets inside; relies on the HTTP-only cookie).
|
|
164
|
+
const snapshot = session.export();
|
|
165
|
+
localStorage.setItem("pubky-session", snapshot);
|
|
144
166
|
|
|
145
|
-
|
|
167
|
+
// Later (after a reload), rehydrate using the browser's stored cookie.
|
|
168
|
+
const restored = await pubky.restoreSession(localStorage.getItem("pubky-session")!);
|
|
146
169
|
```
|
|
147
170
|
|
|
171
|
+
> The exported string contains only public session metadata. The browser must keep the
|
|
172
|
+
> HTTP-only cookie alive for the restored session to remain authenticated.
|
|
173
|
+
|
|
148
174
|
**Approve a pubkyauth request URL**
|
|
149
175
|
|
|
150
176
|
```js
|
|
@@ -158,24 +184,57 @@ await signer.approveAuthRequest("pubkyauth:///?caps=...&secret=...&relay=...");
|
|
|
158
184
|
End-to-end auth (3rd-party app asks a user to approve via QR/deeplink, E.g. Pubky Ring).
|
|
159
185
|
|
|
160
186
|
```js
|
|
161
|
-
import { Pubky } from "@synonymdev/pubky";
|
|
187
|
+
import { Pubky, AuthFlowKind } from "@synonymdev/pubky";
|
|
162
188
|
const pubky = new Pubky();
|
|
163
189
|
|
|
164
190
|
// Comma-separated capabilities string
|
|
165
|
-
const caps = "/pub/my
|
|
191
|
+
const caps = "/pub/my-cool-app/:rw,/pub/another-app/folder/:w";
|
|
166
192
|
|
|
167
193
|
// Optional relay; defaults to Synonym-hosted relay if omitted
|
|
168
194
|
const relay = "https://httprelay.pubky.app/link/"; // optional (defaults to this)
|
|
169
195
|
|
|
170
196
|
// Start the auth polling
|
|
171
|
-
const flow = pubky.startAuthFlow(caps, relay);
|
|
197
|
+
const flow = pubky.startAuthFlow(caps, AuthFlowKind::signin(), relay);
|
|
172
198
|
|
|
173
|
-
renderQr(flow.authorizationUrl
|
|
199
|
+
renderQr(flow.authorizationUrl); // show to user
|
|
174
200
|
|
|
175
201
|
// Blocks until the signer approves; returns a ready Session
|
|
176
202
|
const session = await flow.awaitApproval();
|
|
177
203
|
```
|
|
178
204
|
|
|
205
|
+
#### Validate and normalize capabilities
|
|
206
|
+
|
|
207
|
+
If you accept capability strings from user input (forms, CLI arguments, etc.),
|
|
208
|
+
use `validateCapabilities` before calling `startAuthFlow`. The helper returns a
|
|
209
|
+
normalized string (ordering actions like `:rw`) and throws a structured error
|
|
210
|
+
when the input is malformed.
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
import { Pubky, validateCapabilities, AuthFlowKind } from "@synonymdev/pubky";
|
|
214
|
+
|
|
215
|
+
const pubky = new Pubky();
|
|
216
|
+
|
|
217
|
+
const rawCaps = formData.get("caps");
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const caps = validateCapabilities(rawCaps ?? "");
|
|
221
|
+
const flow = pubky.startAuthFlow(caps, AuthFlowKind::signin());
|
|
222
|
+
renderQr(flow.authorizationUrl);
|
|
223
|
+
const session = await flow.awaitApproval();
|
|
224
|
+
// ...
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (error.name === "InvalidInput") {
|
|
227
|
+
surfaceValidationError(error.message);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
On invalid input, `validateCapabilities` throws a `PubkyError` with
|
|
235
|
+
`{ name: "InvalidInput", message: "Invalid capability entries: …" }`, so you can
|
|
236
|
+
surface precise feedback to the user.
|
|
237
|
+
|
|
179
238
|
#### Http Relay & reliability
|
|
180
239
|
|
|
181
240
|
- If you don’t specify a relay, `PubkyAuthFlow` defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
|
|
@@ -189,26 +248,37 @@ const session = await flow.awaitApproval();
|
|
|
189
248
|
#### PublicStorage (read-only)
|
|
190
249
|
|
|
191
250
|
```js
|
|
192
|
-
const pub = pubky.publicStorage
|
|
251
|
+
const pub = pubky.publicStorage;
|
|
193
252
|
|
|
194
253
|
// Reads
|
|
195
|
-
await pub.
|
|
196
|
-
|
|
197
|
-
|
|
254
|
+
const response = await pub.get(
|
|
255
|
+
`pubky${userPk.z32()}/pub/example.com/data.json`
|
|
256
|
+
); // -> Response (stream it)
|
|
257
|
+
await pub.getJson(`pubky${userPk.z32()}/pub/example.com/data.json`);
|
|
258
|
+
await pub.getText(`pubky${userPk.z32()}/pub/example.com/readme.txt`);
|
|
259
|
+
await pub.getBytes(`pubky${userPk.z32()}/pub/example.com/icon.png`); // Uint8Array
|
|
198
260
|
|
|
199
261
|
// Metadata
|
|
200
|
-
await pub.exists(
|
|
201
|
-
await pub.stats(
|
|
262
|
+
await pub.exists(`pubky${userPk.z32()}/pub/example.com/foo`); // boolean
|
|
263
|
+
await pub.stats(`pubky${userPk.z32()}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null
|
|
202
264
|
|
|
203
265
|
// List directory (addressed path "<pubky>/pub/.../") must include trailing `/`.
|
|
204
266
|
// list(addr, cursor=null|suffix|fullUrl, reverse=false, limit?, shallow=false)
|
|
205
|
-
await pub.list(
|
|
267
|
+
await pub.list(
|
|
268
|
+
`pubky${userPk.z32()}/pub/example.com/`,
|
|
269
|
+
null,
|
|
270
|
+
false,
|
|
271
|
+
100,
|
|
272
|
+
false
|
|
273
|
+
);
|
|
206
274
|
```
|
|
207
275
|
|
|
276
|
+
Use `get()` when you need the raw `Response` for streaming or custom parsing.
|
|
277
|
+
|
|
208
278
|
#### SessionStorage (read/write; uses cookies)
|
|
209
279
|
|
|
210
280
|
```js
|
|
211
|
-
const s = session.storage
|
|
281
|
+
const s = session.storage;
|
|
212
282
|
|
|
213
283
|
// Writes
|
|
214
284
|
await s.putJson("/pub/example.com/data.json", { ok: true });
|
|
@@ -216,6 +286,7 @@ await s.putText("/pub/example.com/note.txt", "hello");
|
|
|
216
286
|
await s.putBytes("/pub/example.com/img.bin", new Uint8Array([1, 2, 3]));
|
|
217
287
|
|
|
218
288
|
// Reads
|
|
289
|
+
const response = await s.get("/pub/example.com/data.json"); // -> Response (stream it)
|
|
219
290
|
await s.getJson("/pub/example.com/data.json");
|
|
220
291
|
await s.getText("/pub/example.com/note.txt");
|
|
221
292
|
await s.getBytes("/pub/example.com/img.bin");
|
|
@@ -231,12 +302,14 @@ await s.list("/pub/example.com/", null, false, 100, false);
|
|
|
231
302
|
await s.delete("/pub/example.com/data.json");
|
|
232
303
|
```
|
|
233
304
|
|
|
305
|
+
`get()` exposes the underlying `Response`, which is handy for streaming bodies or inspecting headers before consuming content.
|
|
306
|
+
|
|
234
307
|
Path rules:
|
|
235
308
|
|
|
236
309
|
- Session storage uses **absolute** paths like `"/pub/app/file.txt"`.
|
|
237
|
-
- Public storage uses **addressed** form
|
|
310
|
+
- Public storage uses **addressed** form `pubky<user>/pub/app/file.txt` (preferred) or `pubky://<user>/...`.
|
|
238
311
|
|
|
239
|
-
**Convention:** put your app’s public data under a domain-like folder in `/pub`, e.g. `/pub/
|
|
312
|
+
**Convention:** put your app’s public data under a domain-like folder in `/pub`, e.g. `/pub/my-new-app/`.
|
|
240
313
|
|
|
241
314
|
---
|
|
242
315
|
|
|
@@ -250,26 +323,60 @@ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
|
250
323
|
const pubky = new Pubky();
|
|
251
324
|
|
|
252
325
|
// Read-only resolver
|
|
253
|
-
const
|
|
254
|
-
const homeserver = await resolver.getHomeserverOf(PublicKey.from("<user-z32>")); // string | undefined
|
|
326
|
+
const homeserver = await pubky.getHomeserverOf(PublicKey.from("<user-z32>")); // string | undefined
|
|
255
327
|
|
|
256
328
|
// With keys (signer-bound)
|
|
257
329
|
const signer = pubky.signer(Keypair.random());
|
|
258
330
|
|
|
259
331
|
// Republish if missing or stale (reuses current host unless overridden)
|
|
260
|
-
await signer.pkdns
|
|
332
|
+
await signer.pkdns.publishHomeserverIfStale();
|
|
261
333
|
// Or force an override now:
|
|
262
|
-
await signer.pkdns
|
|
334
|
+
await signer.pkdns.publishHomeserverForce(/* optional override homeserver*/);
|
|
335
|
+
// Resolve your own homeserver:
|
|
336
|
+
await signer.pkdns.getHomeserver();
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Logging
|
|
340
|
+
|
|
341
|
+
The SDK ships with a WASM logger that bridges Rust `log` output into the browser or Node console. Call `setLogLevel` **once at application start**, before constructing `Pubky` or other SDK actors, to choose how verbose the logs should be.
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
import { setLogLevel } from "@synonymdev/pubky";
|
|
345
|
+
|
|
346
|
+
setLogLevel("debug"); // "error" | "warn" | "info" | "debug" | "trace"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
If the logger is already initialized, calling `setLogLevel` again will throw. Pick the most verbose level (`"debug"` or `"trace"`) while developing to see pkarr resolution, network requests and storage operations in the console.
|
|
350
|
+
|
|
351
|
+
#### Resolve `pubky` identifiers into transport URLs
|
|
352
|
+
|
|
353
|
+
Use `resolvePubky()` when you need to feed an addressed resource into a raw HTTP client:
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
import { resolvePubky } from "@synonymdev/pubky";
|
|
357
|
+
|
|
358
|
+
const identifier =
|
|
359
|
+
"pubkyoperrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG";
|
|
360
|
+
const url = resolvePubky(identifier);
|
|
361
|
+
// -> "https://_pubky.operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG"
|
|
263
362
|
```
|
|
264
363
|
|
|
364
|
+
Both `pubky<pk>/…` (preferred) and `pubky://<pk>/…` resolve to the same HTTPS endpoint.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## WASM memory (`free()` helpers)
|
|
369
|
+
|
|
370
|
+
`wasm-bindgen` generates `free()` methods on exported classes (for example `Pubky`, `AuthFlow` `PublicKey`). JavaScript's GC eventually releases the underlying Rust structs on its own, but calling `free()` lets you drop them **immediately** if you are creating many short-lived instances (e.g. in a long-running worker). It is safe to skip manual frees in typical browser or Node apps.
|
|
371
|
+
|
|
265
372
|
---
|
|
266
373
|
|
|
267
374
|
## Errors
|
|
268
375
|
|
|
269
|
-
All async methods throw a structured `
|
|
376
|
+
All async methods throw a structured `PubkyError`:
|
|
270
377
|
|
|
271
378
|
```ts
|
|
272
|
-
|
|
379
|
+
interface PubkyError extends Error {
|
|
273
380
|
name:
|
|
274
381
|
| "RequestError" // network/server/validation/JSON
|
|
275
382
|
| "InvalidInput"
|
|
@@ -277,8 +384,8 @@ type PubkyJsError = {
|
|
|
277
384
|
| "PkarrError"
|
|
278
385
|
| "InternalError";
|
|
279
386
|
message: string;
|
|
280
|
-
|
|
281
|
-
}
|
|
387
|
+
data?: unknown; // structured context when available (e.g. { statusCode: number })
|
|
388
|
+
}
|
|
282
389
|
```
|
|
283
390
|
|
|
284
391
|
Example:
|
|
@@ -287,28 +394,43 @@ Example:
|
|
|
287
394
|
try {
|
|
288
395
|
await publicStorage.getJson(`${pk}/pub/example.com/missing.json`);
|
|
289
396
|
} catch (e) {
|
|
290
|
-
|
|
397
|
+
const error = e as PubkyError;
|
|
398
|
+
if (
|
|
399
|
+
error.name === "RequestError" &&
|
|
400
|
+
typeof error.data === "object" &&
|
|
401
|
+
error.data !== null &&
|
|
402
|
+
"statusCode" in error.data &&
|
|
403
|
+
typeof (error.data as { statusCode?: number }).statusCode === "number" &&
|
|
404
|
+
(error.data as { statusCode?: number }).statusCode === 404
|
|
405
|
+
) {
|
|
291
406
|
// handle not found
|
|
292
407
|
}
|
|
293
408
|
}
|
|
294
409
|
```
|
|
295
410
|
|
|
411
|
+
## Browser environment notes
|
|
412
|
+
|
|
413
|
+
- 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.
|
|
414
|
+
- 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).
|
|
415
|
+
- When troubleshooting auth/session caching: open a fresh incognito window, clear site data for the target origin, and verify the request includes credentials.
|
|
416
|
+
|
|
296
417
|
---
|
|
297
418
|
|
|
298
419
|
## Local Test & Development
|
|
299
420
|
|
|
300
421
|
For test and development, you can run a local homeserver in a test network.
|
|
301
422
|
|
|
302
|
-
1. Install Rust (for wasm builds):
|
|
423
|
+
1. Install Rust (for wasm and testnet builds):
|
|
303
424
|
|
|
304
425
|
```bash
|
|
305
426
|
curl https://sh.rustup.rs -sSf | sh
|
|
306
427
|
```
|
|
307
428
|
|
|
308
|
-
2.
|
|
429
|
+
2. Install and run the local testnet:
|
|
309
430
|
|
|
310
431
|
```bash
|
|
311
|
-
|
|
432
|
+
cargo install pubky-testnet
|
|
433
|
+
pubky-testnet
|
|
312
434
|
```
|
|
313
435
|
|
|
314
436
|
3. Point the SDK at testnet:
|