@synonymdev/pubky 0.5.4 → 0.6.0-rc.2
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 +193 -159
- package/index.cjs +1565 -385
- package/index.js +1672 -461
- package/package.json +3 -2
- package/pubky.d.ts +706 -82
- package/pubky_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
# Pubky
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JS/WASM SDK for [Pubky](https://github.com/pubky/pubky-core).
|
|
4
4
|
|
|
5
|
-
|
|
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,316 @@ JavaScript implementation of [Pubky](https://github.com/pubky/pubky-core) client
|
|
|
15
10
|
npm install @synonymdev/pubky
|
|
16
11
|
```
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
> **Node**: requires Node v20+ (undici fetch, WebCrypto).
|
|
19
14
|
|
|
20
|
-
|
|
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
|
|
17
|
+
## Getting Started
|
|
23
18
|
|
|
24
19
|
```js
|
|
25
|
-
import {
|
|
20
|
+
import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
26
21
|
|
|
27
|
-
//
|
|
28
|
-
|
|
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
|
-
//
|
|
31
|
-
|
|
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
|
-
//
|
|
34
|
-
|
|
29
|
+
// 2) Sign up at a homeserver (optionally with an invite)
|
|
30
|
+
const homeserver = PublicKey.from(
|
|
35
31
|
"8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo"
|
|
36
32
|
);
|
|
33
|
+
const signupToken = "<your-invite-code-or-null>";
|
|
34
|
+
const session = await signer.signup(homeserver, signupToken);
|
|
37
35
|
|
|
38
|
-
|
|
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
39
|
|
|
40
|
-
|
|
40
|
+
// 4) Read it publicly (no auth needed)
|
|
41
|
+
const userPk = session.info().publicKey();
|
|
42
|
+
const addr = `${userPk.z32()}/pub/example.com/hello.json`;
|
|
43
|
+
const json = await pubky.publicStorage().getJson(addr); // -> { hello: "world" }
|
|
44
|
+
```
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
let url = `pubky://${publicKey.z32()}/pub/example.com/arbitrary`;
|
|
46
|
+
Find here [**ready-to-run examples**](https://github.com/pubky/pubky-core/tree/main/examples).
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
const session = await client.session(publicKey);
|
|
48
|
+
## API Overview
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
await client.fetch(url, {
|
|
50
|
-
method: "PUT",
|
|
51
|
-
body: JSON.stringify({ foo: "bar" }),
|
|
52
|
-
credentials: "include",
|
|
53
|
-
});
|
|
50
|
+
Use `new Pubky()` to quickly get any flow started:
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
const client = new Client();
|
|
52
|
+
```js
|
|
53
|
+
import { Pubky, Keypair } from "@synonymdev/pubky";
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
// Mainnet (default relays)
|
|
56
|
+
const pubky = new Pubky();
|
|
61
57
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
// Local testnet wiring (Pkarr + HTTP mapping).
|
|
59
|
+
// Omit the argument for "localhost".
|
|
60
|
+
const pubkyLocal = Pubky.testnet("localhost");
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
// Signer (bind your keypair to a new Signer actor)
|
|
63
|
+
const signer = pubky.signer(Keypair.random());
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
// Public storage (read-only)
|
|
66
|
+
const publicStorage = pubky.publicStorage();
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
// PKDNS resolver (read-only)
|
|
69
|
+
const pkdns = pubky.pkdns();
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
// Optional: raw HTTP client for advanced use
|
|
72
|
+
const client = pubky.client();
|
|
74
73
|
```
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
### Client (HTTP bridge)
|
|
77
76
|
|
|
78
77
|
```js
|
|
79
|
-
|
|
78
|
+
import { Client } from "@synonymdev/pubky";
|
|
79
|
+
|
|
80
|
+
const client = new Client(); // or: pubky.client(); instead of constructing a client manually
|
|
81
|
+
|
|
82
|
+
// Works with both pubky:// and http(s)://
|
|
83
|
+
const res = await client.fetch("pubky://<pubky>/pub/example.com/file.txt");
|
|
80
84
|
```
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
---
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
### Keys
|
|
85
89
|
|
|
86
90
|
```js
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
- 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).
|
|
91
|
+
import { Keypair, PublicKey } from "@synonymdev/pubky";
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
const keypair = Keypair.random();
|
|
94
|
+
const pubkey = keypair.publicKey();
|
|
95
95
|
|
|
96
|
-
-
|
|
96
|
+
// z-base-32 roundtrip
|
|
97
|
+
const parsed = PublicKey.from(pubkey.z32());
|
|
98
|
+
```
|
|
97
99
|
|
|
98
|
-
####
|
|
100
|
+
#### Recovery file (encrypt/decrypt root secret)
|
|
99
101
|
|
|
100
102
|
```js
|
|
101
|
-
|
|
103
|
+
// Encrypt to recovery file (Uint8Array)
|
|
104
|
+
const recoveryFile = keypair.createRecoveryFile("strong passphrase");
|
|
105
|
+
|
|
106
|
+
// Decrypt back into a Keypair
|
|
107
|
+
const restored = Keypair.fromRecoveryFile(recoveryFile, "strong passphrase");
|
|
108
|
+
|
|
109
|
+
// Build a Signer from a recovered key
|
|
110
|
+
const signer = pubky.signer(restored);
|
|
102
111
|
```
|
|
103
112
|
|
|
104
113
|
- keypair: An instance of [Keypair](#keypair).
|
|
114
|
+
- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
|
|
115
|
+
- Returns: A recovery file with a spec line and an encrypted secret key.
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- An instance of [Session](#session).
|
|
117
|
+
---
|
|
109
118
|
|
|
110
|
-
|
|
119
|
+
### Signer & Session
|
|
111
120
|
|
|
112
121
|
```js
|
|
113
|
-
|
|
114
|
-
```
|
|
122
|
+
import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
const pubky = new Pubky();
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
const keypair = Keypair.random();
|
|
127
|
+
const signer = pubky.signer(keypair);
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
let pubkyauthUrl = pubkyAuthRequest.url();
|
|
129
|
+
const homeserver = PublicKey.from("8pinxxgq…");
|
|
130
|
+
const session = await signer.signup(homeserver, /* invite */ null);
|
|
124
131
|
|
|
125
|
-
|
|
132
|
+
const session2 = await signer.signin(); // fast, prefer this; publishes PKDNS in background
|
|
133
|
+
const session3 = await signer.signinBlocking(); // slower but safer; waits for PKDNS publish
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
await session.signout(); // invalidates server session
|
|
128
136
|
```
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
instead request permissions (showing the user pubkyauthUrl), and await a Session after the user consenting to that request.
|
|
138
|
+
**Session details**
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
```js
|
|
141
|
+
const info = session.info();
|
|
142
|
+
const userPk = info.publicKey(); // -> PublicKey
|
|
143
|
+
const caps = info.capabilities(); // -> string[]
|
|
135
144
|
|
|
136
|
-
|
|
145
|
+
const storage = session.storage(); // -> SessionStorage (absolute paths)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Approve a pubkyauth request URL**
|
|
137
149
|
|
|
138
150
|
```js
|
|
139
|
-
await
|
|
151
|
+
await signer.approveAuthRequest("pubkyauth:///?caps=...&secret=...&relay=...");
|
|
140
152
|
```
|
|
141
153
|
|
|
142
|
-
|
|
154
|
+
---
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
- pubkyauthUrl: A string `pubkyauth://` url
|
|
156
|
+
### AuthFlow (pubkyauth)
|
|
146
157
|
|
|
147
|
-
|
|
158
|
+
End-to-end auth (3rd-party app asks a user to approve via QR/deeplink, E.g. Pubky Ring).
|
|
148
159
|
|
|
149
160
|
```js
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
import { Pubky } from "@synonymdev/pubky";
|
|
162
|
+
const pubky = new Pubky();
|
|
152
163
|
|
|
153
|
-
-
|
|
154
|
-
|
|
164
|
+
// Comma-separated capabilities string
|
|
165
|
+
const caps = "/pub/my.app/:rw,/pub/another.app/folder/:w";
|
|
155
166
|
|
|
156
|
-
|
|
167
|
+
// Optional relay; defaults to Synonym-hosted relay if omitted
|
|
168
|
+
const relay = "https://httprelay.pubky.app/link/"; // optional (defaults to this)
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
// Start the auth polling
|
|
171
|
+
const flow = pubky.startAuthFlow(caps, relay);
|
|
172
|
+
|
|
173
|
+
renderQr(flow.authorizationUrl()); // show to user
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- limit: Number of urls to return.
|
|
166
|
-
- Returns: A list of URLs of the files in the `url` you passed.
|
|
175
|
+
// Blocks until the signer approves; returns a ready Session
|
|
176
|
+
const session = await flow.awaitApproval();
|
|
177
|
+
```
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
#### Http Relay & reliability
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
- If you don’t specify a relay, `PubkyAuthFlow` defaults to a Synonym-hosted relay. If that relay is down, logins won’t complete.
|
|
182
|
+
- For production and larger apps, run **your own http relay** (MIT, Docker): [https://httprelay.io](https://httprelay.io).
|
|
183
|
+
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
184
|
|
|
172
|
-
|
|
173
|
-
let keypair = Keypair.random();
|
|
174
|
-
```
|
|
185
|
+
---
|
|
175
186
|
|
|
176
|
-
|
|
187
|
+
### Storage
|
|
177
188
|
|
|
178
|
-
####
|
|
189
|
+
#### PublicStorage (read-only)
|
|
179
190
|
|
|
180
191
|
```js
|
|
181
|
-
|
|
182
|
-
```
|
|
192
|
+
const pub = pubky.publicStorage();
|
|
183
193
|
|
|
184
|
-
|
|
185
|
-
|
|
194
|
+
// Reads
|
|
195
|
+
await pub.getJson(`${userPk.z32()}/pub/example.com/data.json`);
|
|
196
|
+
await pub.getText(`${userPk.z32()}/pub/example.com/readme.txt`);
|
|
197
|
+
await pub.getBytes(`${userPk.z32()}/pub/example.com/icon.png`); // Uint8Array
|
|
186
198
|
|
|
187
|
-
|
|
199
|
+
// Metadata
|
|
200
|
+
await pub.exists(`${userPk.z32()}/pub/example.com/foo`); // boolean
|
|
201
|
+
await pub.stats(`${userPk.z32()}/pub/example.com/foo`); // { content_length, content_type, etag, last_modified } | null
|
|
188
202
|
|
|
189
|
-
|
|
190
|
-
|
|
203
|
+
// List directory (addressed path "<pubky>/pub/.../") must include trailing `/`.
|
|
204
|
+
// list(addr, cursor=null|suffix|fullUrl, reverse=false, limit?, shallow=false)
|
|
205
|
+
await pub.list(`${userPk.z32()}/pub/example.com/`, null, false, 100, false);
|
|
191
206
|
```
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
#### secretKey
|
|
208
|
+
#### SessionStorage (read/write; uses cookies)
|
|
196
209
|
|
|
197
210
|
```js
|
|
198
|
-
|
|
199
|
-
```
|
|
211
|
+
const s = session.storage();
|
|
200
212
|
|
|
201
|
-
|
|
213
|
+
// Writes
|
|
214
|
+
await s.putJson("/pub/example.com/data.json", { ok: true });
|
|
215
|
+
await s.putText("/pub/example.com/note.txt", "hello");
|
|
216
|
+
await s.putBytes("/pub/example.com/img.bin", new Uint8Array([1, 2, 3]));
|
|
202
217
|
|
|
203
|
-
|
|
218
|
+
// Reads
|
|
219
|
+
await s.getJson("/pub/example.com/data.json");
|
|
220
|
+
await s.getText("/pub/example.com/note.txt");
|
|
221
|
+
await s.getBytes("/pub/example.com/img.bin");
|
|
204
222
|
|
|
205
|
-
|
|
223
|
+
// Metadata
|
|
224
|
+
await s.exists("/pub/example.com/data.json");
|
|
225
|
+
await s.stats("/pub/example.com/data.json");
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
// Listing (session-scoped absolute dir)
|
|
228
|
+
await s.list("/pub/example.com/", null, false, 100, false);
|
|
229
|
+
|
|
230
|
+
// Delete
|
|
231
|
+
await s.delete("/pub/example.com/data.json");
|
|
209
232
|
```
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
- Returns: A new PublicKey instance.
|
|
234
|
+
Path rules:
|
|
213
235
|
|
|
214
|
-
|
|
236
|
+
- Session storage uses **absolute** paths like `"/pub/app/file.txt"`.
|
|
237
|
+
- Public storage uses **addressed** form `<user>/pub/app/file.txt` (or `pubky://<user>/...`).
|
|
215
238
|
|
|
216
|
-
|
|
217
|
-
let pubky = publicKey.z32();
|
|
218
|
-
```
|
|
239
|
+
**Convention:** put your app’s public data under a domain-like folder in `/pub`, e.g. `/pub/mycoolnew.app/`.
|
|
219
240
|
|
|
220
|
-
|
|
241
|
+
---
|
|
221
242
|
|
|
222
|
-
###
|
|
243
|
+
### PKDNS (Pkarr)
|
|
223
244
|
|
|
224
|
-
|
|
245
|
+
Resolve or publish `_pubky` records.
|
|
225
246
|
|
|
226
247
|
```js
|
|
227
|
-
|
|
228
|
-
```
|
|
248
|
+
import { Pubky, PublicKey, Keypair } from "@synonymdev/pubky";
|
|
229
249
|
|
|
230
|
-
|
|
250
|
+
const pubky = new Pubky();
|
|
231
251
|
|
|
232
|
-
|
|
252
|
+
// Read-only resolver
|
|
253
|
+
const resolver = pubky.pkdns();
|
|
254
|
+
const homeserver = await resolver.getHomeserverOf(PublicKey.from("<user-z32>")); // string | undefined
|
|
233
255
|
|
|
234
|
-
|
|
235
|
-
|
|
256
|
+
// With keys (signer-bound)
|
|
257
|
+
const signer = pubky.signer(Keypair.random());
|
|
258
|
+
|
|
259
|
+
// Republish if missing or stale (reuses current host unless overridden)
|
|
260
|
+
await signer.pkdns().publishHomeserverIfStale();
|
|
261
|
+
// Or force an override now:
|
|
262
|
+
await signer.pkdns().publishHomeserverForce(/* optional override homeserver*/);
|
|
236
263
|
```
|
|
237
264
|
|
|
238
|
-
|
|
265
|
+
---
|
|
239
266
|
|
|
240
|
-
|
|
267
|
+
## Errors
|
|
241
268
|
|
|
242
|
-
|
|
269
|
+
All async methods throw a structured `PubkyJsError`:
|
|
243
270
|
|
|
244
|
-
```
|
|
245
|
-
|
|
271
|
+
```ts
|
|
272
|
+
type PubkyJsError = {
|
|
273
|
+
name:
|
|
274
|
+
| "RequestError" // network/server/validation/JSON
|
|
275
|
+
| "InvalidInput"
|
|
276
|
+
| "AuthenticationError"
|
|
277
|
+
| "PkarrError"
|
|
278
|
+
| "InternalError";
|
|
279
|
+
message: string;
|
|
280
|
+
statusCode?: number; // present for HTTP server errors (4xx/5xx)
|
|
281
|
+
};
|
|
246
282
|
```
|
|
247
283
|
|
|
248
|
-
|
|
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.
|
|
251
|
-
|
|
252
|
-
#### createRecoveryFile
|
|
284
|
+
Example:
|
|
253
285
|
|
|
254
286
|
```js
|
|
255
|
-
|
|
287
|
+
try {
|
|
288
|
+
await publicStorage.getJson(`${pk}/pub/example.com/missing.json`);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
if (e.name === "RequestError" && e.statusCode === 404) {
|
|
291
|
+
// handle not found
|
|
292
|
+
}
|
|
293
|
+
}
|
|
256
294
|
```
|
|
257
295
|
|
|
258
|
-
|
|
259
|
-
- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
|
|
260
|
-
- Returns: An instance of [Keypair](#keypair).
|
|
296
|
+
---
|
|
261
297
|
|
|
262
|
-
## Test
|
|
298
|
+
## Local Test & Development
|
|
263
299
|
|
|
264
300
|
For test and development, you can run a local homeserver in a test network.
|
|
265
301
|
|
|
266
|
-
|
|
302
|
+
1. Install Rust (for wasm builds):
|
|
267
303
|
|
|
268
304
|
```bash
|
|
269
305
|
curl https://sh.rustup.rs -sSf | sh
|
|
270
306
|
```
|
|
271
307
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
```bash
|
|
275
|
-
git clone https://github.com/pubky/pubky
|
|
276
|
-
cd pubky-client/pkg
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Run the local testnet server
|
|
308
|
+
2. Run the local testnet:
|
|
280
309
|
|
|
281
310
|
```bash
|
|
282
311
|
npm run testnet
|
|
283
312
|
```
|
|
284
313
|
|
|
285
|
-
|
|
314
|
+
3. Point the SDK at testnet:
|
|
286
315
|
|
|
287
316
|
```js
|
|
288
|
-
import {
|
|
317
|
+
import { Pubky } from "@synonymdev/pubky";
|
|
289
318
|
|
|
290
|
-
const
|
|
319
|
+
const pubky = Pubky.testnet(); // defaults to localhost
|
|
320
|
+
// or: const pubky = Pubky.testnet("testnet-host"); // custom host (e.g. Docker bridge)
|
|
291
321
|
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
MIT © Synonym
|