diff --git a/.github/workflows/check-snippets.yml b/.github/workflows/check-snippets.yml index de476ee..7366e35 100644 --- a/.github/workflows/check-snippets.yml +++ b/.github/workflows/check-snippets.yml @@ -45,30 +45,14 @@ jobs: working-directory: snippets/js run: npm install - - name: Type-check (vanilla JS) + - name: Validate snippets (vanilla JS) working-directory: snippets/js - run: npx tsc --noEmit - - - name: Lint (vanilla JS) - working-directory: snippets/js - run: npx eslint src/ - - - name: Format (vanilla JS) - working-directory: snippets/js - run: npx prettier --check src/ + run: npm run check - name: Install (React Native) working-directory: snippets/react-native run: npm install --legacy-peer-deps - - name: Type-check (React Native) - working-directory: snippets/react-native - run: npx tsc --noEmit - - - name: Lint (React Native) - working-directory: snippets/react-native - run: npx eslint src/ - - - name: Format (React Native) + - name: Validate snippets (React Native) working-directory: snippets/react-native - run: npx prettier --check src/ + run: npm run check diff --git a/package.json b/package.json index 976b4a6..81fbeb8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "start": "astro dev", "build": "astro build && node scripts/copy-md-sources.mjs && node scripts/generate-llms-small.mjs", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "check:snippets": "npm run check:snippets:js && npm run check:snippets:react-native && npm run check:snippets:rust", + "check:snippets:js": "npm --prefix snippets/js run check", + "check:snippets:react-native": "npm --prefix snippets/react-native run check", + "check:snippets:rust": "cargo clippy --manifest-path snippets/rust/Cargo.toml -- -D warnings && cargo fmt --manifest-path snippets/rust/Cargo.toml --check" }, "dependencies": { "@astrojs/starlight": "^0.37.6", diff --git a/snippets/js/package.json b/snippets/js/package.json index 349c367..5cd51b2 100644 --- a/snippets/js/package.json +++ b/snippets/js/package.json @@ -2,6 +2,12 @@ "name": "pubky-doc-snippets-js", "private": true, "type": "module", + "scripts": { + "check": "npm run typecheck && npm run lint && npm run format:check", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "format:check": "prettier --check src/" + }, "dependencies": { "@synonymdev/pubky": "0.8.0" }, diff --git a/snippets/js/src/quick-start-getting-started.ts b/snippets/js/src/quick-start-getting-started.ts index bb5c86b..85cd8c2 100644 --- a/snippets/js/src/quick-start-getting-started.ts +++ b/snippets/js/src/quick-start-getting-started.ts @@ -31,3 +31,10 @@ console.log("Files:", files); // Sign out await session.signout(); // --8<-- [end:js_getting_started_quick_example] + +async function snippet_nexus_global_feed() { + // --8<-- [start:js_nexus_global_feed] + const response = await fetch("https://nexus.pubky.app/v0/feeds/global"); + const posts = await response.json(); + // --8<-- [end:js_nexus_global_feed] +} diff --git a/snippets/js/src/troubleshooting.ts b/snippets/js/src/troubleshooting.ts new file mode 100644 index 0000000..f864b8b --- /dev/null +++ b/snippets/js/src/troubleshooting.ts @@ -0,0 +1,146 @@ +import { + Client, + Pubky, + PublicKey, + setLogLevel, + type Path, + type PubkyError, + type Session, + type Signer, +} from "@synonymdev/pubky"; + +declare const signer: Signer; +declare const session: Session; +declare const homeserverPk: PublicKey; +declare const data: string; + +async function snippet_publish_pkdns_record() { + // --8<-- [start:js_publish_pkdns_record] + await signer.pkdns.publishHomeserverForce(homeserverPk); + // --8<-- [end:js_publish_pkdns_record] +} + +function snippet_republish_pkdns_record() { + // --8<-- [start:js_republish_pkdns_record] + // Periodically check whether the record is stale before republishing + setInterval( + async () => { + await signer.pkdns.publishHomeserverIfStale(homeserverPk); + }, + 2 * 60 * 60 * 1000, + ); // Every 2 hours + // --8<-- [end:js_republish_pkdns_record] +} + +async function snippet_reauth() { + // --8<-- [start:js_reauth] + const session = await signer.signin(); + // --8<-- [end:js_reauth] +} + +async function snippet_force_reauth() { + // --8<-- [start:js_force_reauth] + // Force re-authentication + await session.signout(); + const newSession = await signer.signin(); + // --8<-- [end:js_force_reauth] +} + +function snippet_direct_homeserver_url() { + // --8<-- [start:js_direct_homeserver_url] + // In browser, use full HTTPS URL + const url = `https://your-homeserver.com/pub/...`; + // --8<-- [end:js_direct_homeserver_url] +} + +async function snippet_valid_storage_path() { + // --8<-- [start:js_valid_storage_path] + await session.storage.putText("/pub/myapp/data.json", data); + + // Invalid paths: + // - "data.json" + // - "/myapp/data.json" + // --8<-- [end:js_valid_storage_path] +} + +function typecheck_invalid_storage_paths() { + // These intentionally stay outside the rendered docs. They verify that + // TypeScript still rejects the invalid paths described above. + // @ts-expect-error Path must start with /pub/. + void session.storage.putText("data.json", data); + // @ts-expect-error Path must start with /pub/. + void session.storage.putText("/myapp/data.json", data); +} + +function statusCodeOf(error: unknown): number | undefined { + const data = (error as PubkyError).data; + if (typeof data !== "object" || data === null || !("statusCode" in data)) { + return undefined; + } + + return (data as { statusCode?: number }).statusCode; +} + +// --8<-- [start:js_put_with_retry] +async function putWithRetry( + session: Session, + path: Path, + data: string, + retries = 3, +): Promise { + for (let i = 0; i < retries; i++) { + try { + return await session.storage.putText(path, data); + } catch (error) { + if (statusCodeOf(error) === 429) { + await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); + continue; + } + throw error; + } + } + + throw new Error("PUT failed after retrying rate limits"); +} +// --8<-- [end:js_put_with_retry] + +function snippet_pkarr_relay_config() { + // --8<-- [start:js_pkarr_relay_config] + const client = new Client({ + pkarr: { + relays: ["https://pkarr.pubky.org"], + }, + }); + + const pubky = Pubky.withClient(client); + // --8<-- [end:js_pkarr_relay_config] +} + +const homeserverCache = new Map(); + +// --8<-- [start:js_cache_homeserver_lookup] +async function getCachedHomeserver( + pubky: Pubky, + userPublicKey: string, +): Promise { + const cached = homeserverCache.get(userPublicKey); + if (cached) return cached; + + const user = PublicKey.from(userPublicKey); + const homeserver = await pubky.getHomeserverOf(user); + + if (homeserver) { + homeserverCache.set(userPublicKey, homeserver); + } + + return homeserver; +} +// --8<-- [end:js_cache_homeserver_lookup] + +function snippet_enable_debug_logging() { + // --8<-- [start:js_enable_debug_logging] + // Call once at application startup, before creating Pubky or Client instances. + setLogLevel("debug"); + + // --8<-- [end:js_enable_debug_logging] +} diff --git a/snippets/react-native/package.json b/snippets/react-native/package.json index 7b359ba..5af4e97 100644 --- a/snippets/react-native/package.json +++ b/snippets/react-native/package.json @@ -2,6 +2,12 @@ "name": "pubky-doc-snippets-react-native", "private": true, "type": "module", + "scripts": { + "check": "npm run typecheck && npm run lint && npm run format:check", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "format:check": "prettier --check src/" + }, "dependencies": { "@synonymdev/react-native-pubky": "0.11.3" }, diff --git a/src/content/docs/explore/pubkycore/api.md b/src/content/docs/explore/pubkycore/api.md index b9487e4..b0b44f6 100644 --- a/src/content/docs/explore/pubkycore/api.md +++ b/src/content/docs/explore/pubkycore/api.md @@ -523,20 +523,7 @@ PUT /pub/myapp/all_posts (large JSON array) ### Handle Rate Limits -```javascript -async function putWithRetry(session, path, data, retries = 3) { - for (let i = 0; i < retries; i++) { - try { - return await session.storage.putText(path, data); - } catch (error) { - if (error.status === 429) { // Too Many Requests - await new Promise(r => setTimeout(r, 1000 * (i + 1))); - continue; - } - throw error; - } - } -} +```javascript snippet="snippets/js/src/troubleshooting.ts:js_put_with_retry" ``` ## Resources @@ -550,4 +537,3 @@ async function putWithRetry(session, path, data, retries = 3) { --- **The Pubky Core API provides a simple, RESTful interface for decentralized data storage.** - diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 38ce508..ece2bd8 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -162,10 +162,7 @@ If building a social app, leverage [Pubky Nexus](/explore/pubky-apps/indexing-an - User recommendations - Notifications -```javascript -// Query Nexus API -const response = await fetch('https://nexus.pubky.app/v0/feeds/global'); -const posts = await response.json(); +```javascript snippet="snippets/js/src/quick-start-getting-started.ts:js_nexus_global_feed" ``` 📊 [Nexus API Docs](https://nexus.pubky.app/swagger-ui/) @@ -274,4 +271,3 @@ A: Several models work: Homeserver hosting, indexing services (like Nexus), prem --- **Ready to build the decentralized web? Start with the [SDK](/explore/pubkycore/sdk/)!** - diff --git a/src/content/docs/troubleshooting.md b/src/content/docs/troubleshooting.md index 034ec7c..d92c4a1 100644 --- a/src/content/docs/troubleshooting.md +++ b/src/content/docs/troubleshooting.md @@ -10,28 +10,26 @@ Common issues and solutions when working with Pubky. ### PKARR Record Not Resolving -**Symptom**: Public-key domain doesn't resolve, apps can't find Homeserver +**Symptom**: A user public key does not resolve, so apps cannot find that user's Homeserver. + +A user's PKARR record is published under the user's own public key. The record contains a `_pubky` pointer whose target is the Homeserver public key. So `signer.pkdns.publishHomeserverForce(homeserverPk)` signs and publishes the record for `signer.publicKey`; `homeserverPk` is the value stored in that record, not the DHT key being published. **Common Causes:** -1. **Record Not Published** +1. **User Record Not Published or Points to the Wrong Homeserver** ```bash - # Verify record exists on DHT - curl "https://pkarr.pubky.org/" + # Verify the user's record exists on the DHT + curl -fsI https://pkarr.pubky.app/ >/dev/null && echo "on DHT" || echo "NOT on DHT" ``` - **Solution**: Ensure you've published your PKARR record: - ```javascript - await pubky.publishPkarrRecord(); + + **Solution**: Explicitly publish the user's `_pubky` Homeserver pointer. Signup normally does this for you; use force publish when setting the pointer manually, repairing a wrong or missing pointer, or migrating to a different Homeserver. Force publish means "write this pointer now", even if the existing record is still fresh: + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_publish_pkdns_record" ``` 2. **Record Expired (TTL)** - - PKARR records on DHT expire after several hours - - **Solution**: Republish regularly (recommended: every 2 hours) - ```javascript - // Automatic republishing - setInterval(async () => { - await pubky.publishPkarrRecord(); - }, 2 * 60 * 60 * 1000); // Every 2 hours + - PKARR records need periodic refresh to stay easy to discover + - **Solution**: Use stale-aware publishing for routine maintenance. It checks the existing record age first and no-ops while the record is fresh, then republishes once the SDK considers it stale (default: older than 1 hour). Pass `homeserverPk` when you need missing records to be recreated; omitting it can only reuse a Homeserver target found in the existing record. + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_republish_pkdns_record" ``` 3. **DHT Propagation Delay** @@ -95,9 +93,7 @@ pubky-cli tools verify-pkarr 4. **PKDNS Resolution Failure** - Browser can't resolve public-key domain - **Solution**: Use PKDNS-enabled resolver or DoH: - ```javascript - // In browser, use full HTTPS URL - const url = `https://your-homeserver.com/pub/...`; + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_direct_homeserver_url" ``` **Test Connection:** @@ -133,8 +129,7 @@ See [Authentication](/explore/pubkycore/authentication/) for how Pubky authentic 3. **Session Expired** - Sessions have TTL (typically 24 hours) - **Solution**: Sign in again: - ```javascript - const session = await signer.signin(); + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_reauth" ``` 4. **Clock Skew** @@ -150,10 +145,7 @@ See [Authentication](/explore/pubkycore/authentication/) for how Pubky authentic **Debug Authentication:** -```javascript -// Force re-authentication -await session.signout(); -const newSession = await signer.signin(); +```javascript snippet="snippets/js/src/troubleshooting.ts:js_force_reauth" ``` --- @@ -245,13 +237,7 @@ docker compose logs neo4j 1. **Invalid Path** - Path must start with `/pub/` for public data - **Solution**: Use correct path format: - ```javascript - // ✅ Correct - await session.storage.putText('/pub/myapp/data.json', data); - - // ❌ Wrong — path must start with /pub/ - await session.storage.putText('data.json', data); - await session.storage.putText('/myapp/data.json', data); + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_valid_storage_path" ``` 2. **Data Too Large** @@ -261,18 +247,7 @@ docker compose logs neo4j 3. **Rate Limiting** - Too many requests in short time - **Solution**: Implement backoff: - ```javascript - async function putWithRetry(session, path, data, retries = 3) { - for (let i = 0; i < retries; i++) { - try { - return await session.storage.putText(path, data); - } catch (e) { - if (e.status === 429) { // Too Many Requests - await new Promise(r => setTimeout(r, 1000 * (i + 1))); - } else throw e; - } - } - } + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_put_with_retry" ``` 4. **Insufficient Permissions** @@ -311,18 +286,11 @@ docker compose logs neo4j **Solutions**: 1. **Use PKARR relay**: Faster than direct DHT: - ```javascript - const config = { - pkarrRelay: 'https://pkarr.pubky.org' - }; + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_pkarr_relay_config" ``` -2. **Cache aggressively**: Store resolved Homeserver URLs: - ```javascript - const cache = new Map(); - if (cache.has(publicKey)) { - return cache.get(publicKey); - } +2. **Cache aggressively**: Store resolved Homeserver public keys: + ```javascript snippet="snippets/js/src/troubleshooting.ts:js_cache_homeserver_lookup" ``` 3. **Use local PKDNS**: Run your own PKDNS server for faster resolution @@ -401,7 +369,7 @@ When reporting bugs, include: ```markdown ## Environment - OS: macOS 14.2 -- SDK: @synonymdev/pubky@0.7.0 +- SDK: @synonymdev/pubky@0.8.0 - Browser: Chrome 120 ## Steps to Reproduce @@ -422,13 +390,13 @@ Data should be stored successfully ### Useful Debugging Tools -**Browser DevTools:** -```javascript -// Enable verbose logging -localStorage.setItem('pubky:debug', 'true'); +**Set Log Level:** +```javascript snippet="snippets/js/src/troubleshooting.ts:js_enable_debug_logging" +``` -// Check network requests -// Open DevTools → Network tab → Filter: pubky +**Browser DevTools:** +```text +Open DevTools → Network tab → Filter: pubky ``` **Command Line:** @@ -467,4 +435,3 @@ PUBKY_ADMIN_PASSWORD=admin pubky-cli admin info - **[SDK Documentation](/explore/pubkycore/sdk/)**: Detailed API docs - **[PKDNS](/explore/technologies/pkdns/)**: DNS resolution details - **[Homeserver](/explore/pubkycore/homeserver/)**: Homeserver administration -