@synonymdev/pubky 0.5.4 → 0.6.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +265 -154
  2. package/index.cjs +1932 -400
  3. package/index.js +2068 -475
  4. package/package.json +10 -5
  5. package/pubky.d.ts +818 -84
  6. package/pubky_bg.wasm +0 -0
package/README.md CHANGED
@@ -1,13 +1,8 @@
1
1
  # Pubky
2
2
 
3
- JavaScript implementation of [Pubky](https://github.com/pubky/pubky-core) client.
3
+ JS/WASM SDK for [Pubky](https://github.com/pubky/pubky-core).
4
4
 
5
- ## Table of Contents
6
-
7
- - [Install](#install)
8
- - [Getting Started](#getting-started)
9
- - [API](#api)
10
- - [Test and Development](#test-and-development)
5
+ Works in browsers and Node 20+.
11
6
 
12
7
  ## Install
13
8
 
@@ -15,277 +10,393 @@ JavaScript implementation of [Pubky](https://github.com/pubky/pubky-core) client
15
10
  npm install @synonymdev/pubky
16
11
  ```
17
12
 
18
- ### Prerequisites
13
+ > **Node**: requires Node v20+ (undici fetch, WebCrypto).
19
14
 
20
- For Nodejs, you need Node v20 or later.
15
+ Module system + TS types: ESM and CommonJS both supported; TypeScript typings generated via tsify are included. Use `import { Pubky } from "@synonymdev/pubky"` (ESM) or `const { Pubky } = require("@synonymdev/pubky")` (CJS).
21
16
 
22
- ## Getting started
17
+ ## Getting Started
23
18
 
24
19
  ```js
25
- import { Client, Keypair, PublicKey } from "../index.js";
20
+ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
26
21
 
27
- // Initialize Client with Pkarr relay(s).
28
- let client = new Client();
22
+ // Initiate a Pubky SDK facade wired for default mainnet Pkarr relays.
23
+ const pubky = new Pubky(); // or: const pubky = Pubky.testnet(); for localhost testnet.
29
24
 
30
- // Generate a keypair
31
- let keypair = Keypair.random();
25
+ // 1) Create random user keys and bind to a new Signer.
26
+ const keypair = Keypair.random();
27
+ const signer = pubky.signer(keypair);
32
28
 
33
- // Create a new account
34
- let homeserver = PublicKey.from(
35
- "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"
29
+ // 2) Sign up at a homeserver (optionally with an invite)
30
+ const homeserver = PublicKey.from(
31
+ "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo",
36
32
  );
33
+ const signupToken = "<your-invite-code-or-null>";
34
+ const session = await signer.signup(homeserver, signupToken);
35
+
36
+ // 3) Write a public JSON file (session-scoped storage uses cookies automatically)
37
+ const path = "/pub/example.com/hello.json";
38
+ await session.storage.putJson(path, { hello: "world" });
39
+
40
+ // 4) Read it publicly (no auth needed)
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.app/:rw"); // require permissions to read and write into `my.app`
47
+ renderQr(authFlow.authorizationUrl); // show to user
48
+ const session = await authFlow.awaitApproval();
49
+ ```
37
50
 
38
- await client.signup(keypair, homeserver, signup_token);
51
+ Find here [**ready-to-run examples**](https://github.com/pubky/pubky-core/tree/main/examples).
39
52
 
40
- const publicKey = keypair.publicKey();
53
+ ## API Overview
41
54
 
42
- // Pubky URL
43
- let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
55
+ Use `new Pubky()` to quickly get any flow started:
44
56
 
45
- // Verify that you are signed in.
46
- const session = await client.session(publicKey);
57
+ ```js
58
+ import { Pubky, Keypair } from "@synonymdev/pubky";
47
59
 
48
- // PUT public data, by authorized client
49
- await client.fetch(url, {
50
- method: "PUT",
51
- body: JSON.stringify({ foo: "bar" }),
52
- credentials: "include",
53
- });
60
+ // Mainnet (default relays)
61
+ const pubky = new Pubky();
54
62
 
55
- // GET public data without signup or signin
56
- {
57
- const client = new Client();
63
+ // Local testnet wiring (Pkarr + HTTP mapping).
64
+ // Omit the argument for "localhost".
65
+ const pubkyLocal = Pubky.testnet("localhost");
58
66
 
59
- let response = await client.fetch(url);
60
- }
67
+ // Signer (bind your keypair to a new Signer actor)
68
+ const signer = pubky.signer(Keypair.random());
61
69
 
62
- // Delete public data, by authorized client
63
- await client.fetch(url, { method: "DELETE", credentials: "include " });
64
- ```
70
+ // Pubky Auth flow (with capabilities)
71
+ const authFlow = pubky.startAuthFlow("/pub/my.app/:rw");
72
+
73
+ // Public storage (read-only)
74
+ const publicStorage = pubky.publicStorage;
65
75
 
66
- ## API
76
+ // Pkdns resolver
77
+ const pkdns = pubky.getHomeserverOf(publicKey);
67
78
 
68
- ### Client
79
+ // Optional: raw HTTP client for advanced use
80
+ const client = pubky.client;
81
+ ```
69
82
 
70
- #### constructor
83
+ ### Client (HTTP bridge)
71
84
 
72
85
  ```js
73
- let client = new Client();
86
+ import { Client, resolvePubky } from "@synonymdev/pubky";
87
+
88
+ const client = new Client(); // or: pubky.client.fetch(); instead of constructing a client manually
89
+
90
+ // Convert the identifier into a transport URL before fetching.
91
+ const url = resolvePubky("pubky<pubky>/pub/example.com/file.txt");
92
+ const res = await client.fetch(url);
74
93
  ```
75
94
 
76
- #### fetch
95
+ ---
96
+
97
+ ### Keys
77
98
 
78
99
  ```js
79
- let response = await client.fetch(url, opts);
80
- ```
100
+ import { Keypair, PublicKey } from "@synonymdev/pubky";
101
+
102
+ const keypair = Keypair.random();
103
+ const pubkey = keypair.publicKey;
81
104
 
82
- Just like normal Fetch API, but it can handle `pubky://` urls and `http(s)://` urls with Pkarr domains.
105
+ // z-base-32 roundtrip
106
+ const parsed = PublicKey.from(pubkey.z32());
107
+ ```
83
108
 
84
- #### signup
109
+ #### Recovery file (encrypt/decrypt root secret)
85
110
 
86
111
  ```js
87
- await client.signup(keypair, homeserver, signup_token);
112
+ // Encrypt to recovery file (Uint8Array)
113
+ const recoveryFile = keypair.createRecoveryFile("strong passphrase");
114
+
115
+ // Decrypt back into a Keypair
116
+ const restored = Keypair.fromRecoveryFile(recoveryFile, "strong passphrase");
117
+
118
+ // Build a Signer from a recovered key
119
+ const signer = pubky.signer(restored);
88
120
  ```
89
121
 
90
122
  - keypair: An instance of [Keypair](#keypair).
91
- - homeserver: An instance of [PublicKey](#publickey) representing the homeserver.
92
- - signup_token: A homeserver could optionally ask for a valid signup token (aka, invitation code).
93
-
94
- Returns:
123
+ - passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
124
+ - Returns: A recovery file with a spec line and an encrypted secret key.
95
125
 
96
- - session: An instance of [Session](#session).
126
+ ---
97
127
 
98
- #### signin
128
+ ### Signer & Session
99
129
 
100
130
  ```js
101
- let session = await client.signin(keypair);
102
- ```
131
+ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
103
132
 
104
- - keypair: An instance of [Keypair](#keypair).
133
+ const pubky = new Pubky();
105
134
 
106
- Returns:
135
+ const keypair = Keypair.random();
136
+ const signer = pubky.signer(keypair);
107
137
 
108
- - An instance of [Session](#session).
138
+ const homeserver = PublicKey.from("8pinxxgq…");
139
+ const session = await signer.signup(homeserver, /* invite */ null);
109
140
 
110
- #### signout
141
+ const session2 = await signer.signin(); // fast, prefer this; publishes PKDNS in background
142
+ const session3 = await signer.signinBlocking(); // slower but safer; waits for PKDNS publish
111
143
 
112
- ```js
113
- await client.signout(publicKey);
144
+ await session.signout(); // invalidates server session
114
145
  ```
115
146
 
116
- - publicKey: An instance of [PublicKey](#publicKey).
117
-
118
- #### authRequest
147
+ **Session details**
119
148
 
120
149
  ```js
121
- let pubkyAuthRequest = client.authRequest(relay, capabilities);
150
+ const userPk = session.info.publicKey.z32(); // -> PublicKey as z32 string
151
+ const caps = session.info.capabilities; // -> string[] permissions and paths
122
152
 
123
- let pubkyauthUrl = pubkyAuthRequest.url();
153
+ const storage = session.storage; // -> This User's storage API (absolute paths)
154
+ ```
124
155
 
125
- showQr(pubkyauthUrl);
156
+ **Approve a pubkyauth request URL**
126
157
 
127
- let pubky = await pubkyAuthRequest.response();
158
+ ```js
159
+ await signer.approveAuthRequest("pubkyauth:///?caps=...&secret=...&relay=...");
128
160
  ```
129
161
 
130
- Sign in to a user's Homeserver, without access to their [Keypair](#keypair), nor even [PublicKey](#publickey),
131
- instead request permissions (showing the user pubkyauthUrl), and await a Session after the user consenting to that request.
162
+ ---
132
163
 
133
- - relay: A URL to an [HTTP relay](https://httprelay.io/features/link/) endpoint.
134
- - capabilities: A list of capabilities required for the app for example `/pub/pubky.app/:rw,/pub/example.com/:r`.
164
+ ### AuthFlow (pubkyauth)
135
165
 
136
- #### sendAuthToken
166
+ End-to-end auth (3rd-party app asks a user to approve via QR/deeplink, E.g. Pubky Ring).
137
167
 
138
168
  ```js
139
- await client.sendAuthToken(keypair, pubkyauthUrl);
140
- ```
169
+ import { Pubky } from "@synonymdev/pubky";
170
+ const pubky = new Pubky();
141
171
 
142
- Consenting to authentication or authorization according to the required capabilities in the `pubkyauthUrl` , and sign and send an auth token to the requester.
172
+ // Comma-separated capabilities string
173
+ const caps = "/pub/my.app/:rw,/pub/another.app/folder/:w";
143
174
 
144
- - keypair: An instance of [KeyPair](#keypair)
145
- - pubkyauthUrl: A string `pubkyauth://` url
175
+ // Optional relay; defaults to Synonym-hosted relay if omitted
176
+ const relay = "https://httprelay.pubky.app/link/"; // optional (defaults to this)
146
177
 
147
- #### session {#session-method}
178
+ // Start the auth polling
179
+ const flow = pubky.startAuthFlow(caps, relay);
148
180
 
149
- ```js
150
- let session = await client.session(publicKey);
181
+ renderQr(flow.authorizationUrl); // show to user
182
+
183
+ // Blocks until the signer approves; returns a ready Session
184
+ const session = await flow.awaitApproval();
151
185
  ```
152
186
 
153
- - publicKey: An instance of [PublicKey](#publickey).
154
- - Returns: A [Session](#session) object if signed in, or undefined if not.
187
+ #### Validate and normalize capabilities
155
188
 
156
- ### list
189
+ If you accept capability strings from user input (forms, CLI arguments, etc.),
190
+ use `validateCapabilities` before calling `startAuthFlow`. The helper returns a
191
+ normalized string (ordering actions like `:rw`) and throws a structured error
192
+ when the input is malformed.
157
193
 
158
194
  ```js
159
- let response = await client.list(url, cursor, reverse, limit);
195
+ import { Pubky, validateCapabilities } from "@synonymdev/pubky";
196
+
197
+ const pubky = new Pubky();
198
+
199
+ const rawCaps = formData.get("caps");
200
+
201
+ try {
202
+ const caps = validateCapabilities(rawCaps ?? "");
203
+ const flow = pubky.startAuthFlow(caps);
204
+ renderQr(flow.authorizationUrl);
205
+ const session = await flow.awaitApproval();
206
+ // ...
207
+ } catch (error) {
208
+ if (error.name === "InvalidInput") {
209
+ surfaceValidationError(error.message);
210
+ return;
211
+ }
212
+ throw error;
213
+ }
160
214
  ```
161
215
 
162
- - url: A string representing the Pubky URL. The path in that url is the prefix that you want to list files within.
163
- - cursor: Usually the last URL from previous calls. List urls after/before (depending on `reverse`) the cursor.
164
- - reverse: Whether or not return urls in reverse order.
165
- - limit: Number of urls to return.
166
- - Returns: A list of URLs of the files in the `url` you passed.
216
+ On invalid input, `validateCapabilities` throws a `PubkyError` with
217
+ `{ name: "InvalidInput", message: "Invalid capability entries: …" }`, so you can
218
+ surface precise feedback to the user.
167
219
 
168
- ### Keypair
220
+ #### Http Relay & reliability
169
221
 
170
- #### random
222
+ - If you don’t specify a relay, `PubkyAuthFlow` defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
223
+ - For production and larger apps, run **your own http relay** (MIT, Docker): [https://httprelay.io](https://httprelay.io).
224
+ 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.
171
225
 
172
- ```js
173
- let keypair = Keypair.random();
174
- ```
226
+ ---
175
227
 
176
- - Returns: A new random Keypair.
228
+ ### Storage
177
229
 
178
- #### fromSecretKey
230
+ #### PublicStorage (read-only)
179
231
 
180
232
  ```js
181
- let keypair = Keypair.fromSecretKey(secretKey);
182
- ```
233
+ const pub = pubky.publicStorage;
183
234
 
184
- - secretKey: A 32 bytes Uint8array.
185
- - Returns: A new Keypair.
235
+ // Reads
236
+ await pub.getJson(`pubky${userPk.z32()}/pub/example.com/data.json`);
237
+ await pub.getText(`pubky${userPk.z32()}/pub/example.com/readme.txt`);
238
+ await pub.getBytes(`pubky${userPk.z32()}/pub/example.com/icon.png`); // Uint8Array
186
239
 
187
- #### publicKey {#publickey-method}
240
+ // Metadata
241
+ await pub.exists(`pubky${userPk.z32()}/pub/example.com/foo`); // boolean
242
+ await pub.stats(`pubky${userPk.z32()}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null
188
243
 
189
- ```js
190
- let publicKey = keypair.publicKey();
244
+ // List directory (addressed path "<pubky>/pub/.../") must include trailing `/`.
245
+ // list(addr, cursor=null|suffix|fullUrl, reverse=false, limit?, shallow=false)
246
+ await pub.list(`pubky${userPk.z32()}/pub/example.com/`, null, false, 100, false);
191
247
  ```
192
248
 
193
- - Returns: The [PublicKey](#publickey) associated with the Keypair.
194
-
195
- #### secretKey
249
+ #### SessionStorage (read/write; uses cookies)
196
250
 
197
251
  ```js
198
- let secretKey = keypair.secretKey();
199
- ```
252
+ const s = session.storage;
200
253
 
201
- - Returns: The Uint8array secret key associated with the Keypair.
254
+ // Writes
255
+ await s.putJson("/pub/example.com/data.json", { ok: true });
256
+ await s.putText("/pub/example.com/note.txt", "hello");
257
+ await s.putBytes("/pub/example.com/img.bin", new Uint8Array([1, 2, 3]));
202
258
 
203
- ### PublicKey
259
+ // Reads
260
+ await s.getJson("/pub/example.com/data.json");
261
+ await s.getText("/pub/example.com/note.txt");
262
+ await s.getBytes("/pub/example.com/img.bin");
204
263
 
205
- #### from
264
+ // Metadata
265
+ await s.exists("/pub/example.com/data.json");
266
+ await s.stats("/pub/example.com/data.json");
206
267
 
207
- ```js
208
- let publicKey = PublicKey.from(string);
268
+ // Listing (session-scoped absolute dir)
269
+ await s.list("/pub/example.com/", null, false, 100, false);
270
+
271
+ // Delete
272
+ await s.delete("/pub/example.com/data.json");
209
273
  ```
210
274
 
211
- - string: A string representing the public key.
212
- - Returns: A new PublicKey instance.
275
+ Path rules:
213
276
 
214
- #### z32
277
+ - Session storage uses **absolute** paths like `"/pub/app/file.txt"`.
278
+ - Public storage uses **addressed** form `pubky<user>/pub/app/file.txt` (preferred) or `pubky://<user>/...`.
215
279
 
216
- ```js
217
- let pubky = publicKey.z32();
218
- ```
280
+ **Convention:** put your app’s public data under a domain-like folder in `/pub`, e.g. `/pub/mycoolnew.app/`.
219
281
 
220
- Returns: The z-base-32 encoded string representation of the PublicKey.
282
+ ---
221
283
 
222
- ### Session
284
+ ### PKDNS (Pkarr)
223
285
 
224
- #### pubky
286
+ Resolve or publish `_pubky` records.
225
287
 
226
288
  ```js
227
- let pubky = session.pubky();
289
+ import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
290
+
291
+ const pubky = new Pubky();
292
+
293
+ // Read-only resolver
294
+ const homeserver = await pubky.getHomeserverOf(PublicKey.from("<user-z32>")); // string | undefined
295
+
296
+ // With keys (signer-bound)
297
+ const signer = pubky.signer(Keypair.random());
298
+
299
+ // Republish if missing or stale (reuses current host unless overridden)
300
+ await signer.pkdns.publishHomeserverIfStale();
301
+ // Or force an override now:
302
+ await signer.pkdns.publishHomeserverForce(/* optional override homeserver*/);
303
+ // Resolve your own homeserver:
304
+ await signer.pkdns.getHomeserver();
228
305
  ```
229
306
 
230
- Returns an instance of [PublicKey](#publickey)
307
+ ## Logging
231
308
 
232
- #### capabilities
309
+ 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.
233
310
 
234
311
  ```js
235
- let capabilities = session.capabilities();
312
+ import { setLogLevel } from "@synonymdev/pubky";
313
+
314
+ setLogLevel("debug"); // "error" | "warn" | "info" | "debug" | "trace"
236
315
  ```
237
316
 
238
- Returns an array of capabilities, for example `["/pub/pubky.app/:rw"]`
317
+ 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.
239
318
 
240
- ### Helper functions
319
+ #### Resolve `pubky` identifiers into transport URLs
241
320
 
242
- #### createRecoveryFile
321
+ Use `resolvePubky()` when you need to feed an addressed resource into a raw HTTP client:
243
322
 
244
323
  ```js
245
- let recoveryFile = createRecoveryFile(keypair, passphrase);
324
+ import { resolvePubky } from "@synonymdev/pubky";
325
+
326
+ const identifier = "pubkyoperrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG";
327
+ const url = resolvePubky(identifier);
328
+ // -> "https://_pubky.operrr8wsbpr3ue9d4qj41ge1kcc6r7fdiy6o3ugjrrhi4y77rdo/pub/pubky.app/posts/0033X02JAN0SG"
246
329
  ```
247
330
 
248
- - keypair: An instance of [Keypair](#keypair).
249
- - passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
250
- - Returns: A recovery file with a spec line and an encrypted secret key.
331
+ Both `pubky<pk>/…` (preferred) and `pubky://<pk>/…` resolve to the same HTTPS endpoint.
332
+
333
+ ---
334
+
335
+ ## Errors
336
+
337
+ All async methods throw a structured `PubkyError`:
251
338
 
252
- #### createRecoveryFile
339
+ ```ts
340
+ interface PubkyError extends Error {
341
+ name:
342
+ | "RequestError" // network/server/validation/JSON
343
+ | "InvalidInput"
344
+ | "AuthenticationError"
345
+ | "PkarrError"
346
+ | "InternalError";
347
+ message: string;
348
+ data?: unknown; // structured context when available (e.g. { statusCode: number })
349
+ }
350
+ ```
351
+
352
+ Example:
253
353
 
254
354
  ```js
255
- let keypair = decryptRecoveryfile(recoveryFile, passphrase);
355
+ try {
356
+ await publicStorage.getJson(`${pk}/pub/example.com/missing.json`);
357
+ } catch (e) {
358
+ const error = e as PubkyError;
359
+ if (
360
+ error.name === "RequestError" &&
361
+ typeof error.data === "object" &&
362
+ error.data !== null &&
363
+ "statusCode" in error.data &&
364
+ typeof (error.data as { statusCode?: number }).statusCode === "number" &&
365
+ (error.data as { statusCode?: number }).statusCode === 404
366
+ ) {
367
+ // handle not found
368
+ }
369
+ }
256
370
  ```
257
371
 
258
- - recoveryFile: An instance of Uint8Array containing the recovery file blob.
259
- - passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
260
- - Returns: An instance of [Keypair](#keypair).
372
+ ---
261
373
 
262
- ## Test and Development
374
+ ## Local Test & Development
263
375
 
264
376
  For test and development, you can run a local homeserver in a test network.
265
377
 
266
- If you don't have Cargo Installed, start by installing it:
378
+ 1. Install Rust (for wasm and testnet builds):
267
379
 
268
380
  ```bash
269
381
  curl https://sh.rustup.rs -sSf | sh
270
382
  ```
271
383
 
272
- Clone the Pubky repository:
384
+ 2. Install and run the local testnet:
273
385
 
274
386
  ```bash
275
- git clone https://github.com/pubky/pubky
276
- cd pubky-client/pkg
387
+ cargo install pubky-testnet
388
+ pubky-testnet
277
389
  ```
278
390
 
279
- Run the local testnet server
280
-
281
- ```bash
282
- npm run testnet
283
- ```
284
-
285
- Use the logged addresses as inputs to `Client`
391
+ 3. Point the SDK at testnet:
286
392
 
287
393
  ```js
288
- import { Client } from "../index.js";
394
+ import { Pubky } from "@synonymdev/pubky";
289
395
 
290
- const client = Client().testnet();
396
+ const pubky = Pubky.testnet(); // defaults to localhost
397
+ // or: const pubky = Pubky.testnet("testnet-host"); // custom host (e.g. Docker bridge)
291
398
  ```
399
+
400
+ ---
401
+
402
+ MIT © Synonym