From 459d0d576a19c3da8e0d0828fee4056d772f1189 Mon Sep 17 00:00:00 2001 From: benk10 Date: Tue, 4 Aug 2026 18:37:58 +0200 Subject: [PATCH 1/6] fix: support Bitkit local wallet tests --- compose.paykit-local-demo.yaml | 47 +++++++++--- docker/js-demo.Dockerfile | 13 +++- examples/js-sdk/README.md | 19 +++-- examples/js-sdk/package.json | 2 +- examples/js-sdk/reader-app.js | 72 +++++++++++++++---- examples/js-sdk/reader-flow.js | 9 ++- examples/js-sdk/reader.html | 3 +- examples/js-sdk/scripts/homegate-bridge.mjs | 55 ++++++++++++++ .../js-sdk/scripts/init-paykit-compose.mjs | 3 + .../scripts/lib/creator-session-state.mjs | 20 +++--- .../scripts/publish-creator-profile.mjs | 7 +- .../js-sdk/scripts/smoke-paykit-compose.mjs | 24 +++++++ examples/js-sdk/scripts/start-demo-server.mjs | 25 +++++-- .../scripts/start-reader-demo-server.mjs | 2 + .../scripts/validate-paykit-compose.mjs | 8 ++- .../bindings/js/scripts/smoke-examples.mjs | 29 ++++++-- 16 files changed, 283 insertions(+), 55 deletions(-) create mode 100644 examples/js-sdk/scripts/homegate-bridge.mjs diff --git a/compose.paykit-local-demo.yaml b/compose.paykit-local-demo.yaml index 0d5ac90..d3b4930 100644 --- a/compose.paykit-local-demo.yaml +++ b/compose.paykit-local-demo.yaml @@ -26,7 +26,8 @@ services: .local/pubky-homeserver \ .local/locks-server \ .local/paykit-server \ - .local/paykit-config + .local/paykit-config \ + .local/homegate-bridge chmod 0600 .local/paykit-server/paykit.env volumes: - ./examples/js-sdk:/workspace/examples/js-sdk:ro @@ -141,6 +142,8 @@ services: depends_on: bitcoin-bootstrap: condition: service_completed_successfully + ports: + - "127.0.0.1:${LOCKS_ELECTRUM_PORT:-60001}:50001" electrum-readiness: image: node:22-bookworm-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 @@ -165,7 +168,6 @@ services: - "127.0.0.1:${LOCKS_HTTP_RELAY_PORT:-15412}:15412" - "127.0.0.1:${LOCKS_HOMESERVER_HTTP_PORT:-6286}:6286" - "127.0.0.1:${LOCKS_HOMESERVER_PUBKY_PORT:-6287}:6287" - - "127.0.0.1:${LOCKS_HOMESERVER_ADMIN_PORT:-6288}:6288" - "127.0.0.1:${LOCKS_SERVER_PORT:-3000}:3000" - "127.0.0.1:${LOCKS_PAYKIT_PORT:-3001}:3001" - "127.0.0.1:${LOCKS_CREATOR_DEMO_PORT:-8080}:8080" @@ -177,6 +179,38 @@ services: postgres: condition: service_healthy + homegate-bridge: + image: node:22-bookworm-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 + user: "1000:1000" + working_dir: /workspace + command: + - /bin/sh + - -euc + - | + set -a + . /run/compose-local/homegate-bridge/homegate.env + set +a + exec node examples/js-sdk/scripts/homegate-bridge.mjs + environment: + HOMEGATE_BRIDGE_CONFIG: /run/compose-local/demo-config/config.json + HOMEGATE_BRIDGE_HOMESERVER_ADMIN_URL: http://pubky-testnet:6288 + ports: + - "127.0.0.1:${LOCKS_HOMEGATE_PORT:-6288}:8082" + volumes: + - ./examples/js-sdk:/workspace/examples/js-sdk:ro + - ./.local/demo-config:/run/compose-local/demo-config:ro + - ./.local/homegate-bridge:/run/compose-local/homegate-bridge:ro + depends_on: + pubky-testnet: + condition: service_started + demo-config: + condition: service_completed_successfully + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8082/health').then((response) => { if (!response.ok) process.exit(1); }).catch(() => process.exit(1))"] + interval: 2s + timeout: 2s + retries: 30 + locks-server: build: context: . @@ -320,19 +354,15 @@ services: PAYKIT_COMPANION_AUTH_BIN: /usr/local/bin/paykit-companion-auth PUBKY_LOCK_DEBUG: ${PUBKY_LOCK_DEBUG:-0} volumes: - - ./locks-sdk/bindings/js/pkg:/workspace/locks-sdk/bindings/js/pkg:ro - ./.local/demo-config:/workspace/.local/demo-config:ro - ./.local/js-sdk-demo:/workspace/.local/js-sdk-demo - - ./.local/content-creator:/workspace/.local/content-creator - ./.local/creator-public:/workspace/.local/creator-public command: - sh - -lc - | set -eu - npm --prefix examples/js-sdk run create-user -- --role content-creator - npm --prefix examples/js-sdk run publish-creator-profile - npm --prefix examples/js-sdk run start-server -- --allow-unhealthy + npm --prefix examples/js-sdk run start-server -- --external-wallet reader-demo: restart: unless-stopped @@ -365,7 +395,6 @@ services: PAYKIT_READER_WORKER_ENABLED: "1" PUBKY_LOCK_DEBUG: ${PUBKY_LOCK_DEBUG:-0} volumes: - - ./locks-sdk/bindings/js/pkg:/workspace/locks-sdk/bindings/js/pkg:ro - ./.local/demo-config:/workspace/.local/demo-config:ro - ./.local/creator-public:/workspace/.local/creator-public:ro - ./.local/content-viewer:/workspace/.local/content-viewer @@ -375,7 +404,7 @@ services: - -euc - | npm --prefix examples/js-sdk run create-user -- --role content-viewer - exec node examples/js-sdk/scripts/start-reader-demo-server.mjs --allow-unhealthy + exec node examples/js-sdk/scripts/start-reader-demo-server.mjs healthcheck: test: - CMD diff --git a/docker/js-demo.Dockerfile b/docker/js-demo.Dockerfile index c2d6ce6..3eb9440 100644 --- a/docker/js-demo.Dockerfile +++ b/docker/js-demo.Dockerfile @@ -1,6 +1,16 @@ # syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e FROM paykit-runtime AS paykit-runtime +FROM rust:1.91.1-slim-bookworm@sha256:8514999d4786ef12efe89239e86b3d0a021b94b9d35108c8efe6c79ca7dc1a65 AS locks-sdk-wasm +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential ca-certificates libssl-dev pkg-config \ + && rm -rf /var/lib/apt/lists/* +RUN rustup target add wasm32-unknown-unknown \ + && cargo install wasm-pack --version 0.13.1 --locked +WORKDIR /workspace +COPY . . +RUN cd locks-sdk/bindings/js && wasm-pack build --target web --out-dir pkg + FROM node:22-bookworm-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 WORKDIR /workspace RUN apt-get update \ @@ -10,8 +20,9 @@ COPY --chown=node:node examples/js-sdk/package.json examples/js-sdk/package-lock RUN npm --prefix examples/js-sdk ci --ignore-scripts \ && npm cache clean --force COPY --chown=node:node examples/js-sdk /workspace/examples/js-sdk +COPY --from=locks-sdk-wasm --chown=node:node /workspace/locks-sdk/bindings/js/pkg /workspace/locks-sdk/bindings/js/pkg COPY --from=paykit-runtime /usr/local/bin/paykit-companion-auth /usr/local/bin/paykit-companion-auth COPY --from=paykit-runtime /usr/local/bin/paykit-reader-demo /usr/local/bin/paykit-reader-demo -RUN mkdir -p /workspace/locks-sdk/bindings/js/pkg /workspace/.local \ +RUN mkdir -p /workspace/.local \ && chown -R node:node /workspace USER node:node diff --git a/examples/js-sdk/README.md b/examples/js-sdk/README.md index a019561..b50e9dd 100644 --- a/examples/js-sdk/README.md +++ b/examples/js-sdk/README.md @@ -15,6 +15,7 @@ examples/js-sdk/reader.html examples/js-sdk/reader-app.js examples/js-sdk/reader-flow.js examples/js-sdk/scripts/init-config.mjs +examples/js-sdk/scripts/homegate-bridge.mjs examples/js-sdk/scripts/create-user.mjs examples/js-sdk/scripts/authenticate.mjs examples/js-sdk/scripts/prepare-paykit-reader.mjs @@ -61,7 +62,7 @@ Required tools/services: DHT bootstrap localhost:6881 ``` -Build the local WASM SDK package first: +For direct npm development, build the local WASM SDK package first: ```bash npm --prefix locks-sdk/bindings/js run build @@ -83,6 +84,8 @@ The examples package uses `@synonymdev/pubky` for Node-side Pubky testnet auth/k The supported end-to-end path is the complete local Compose stack documented below. It generates ignored owner-only credentials, starts both databases and both application servers, bootstraps Bitcoin regtest, waits for Fulcrum using `server.version`, and starts the creator and reader demos. +The Compose image builds the JS/WASM package itself. A fresh checkout does not need a host-generated `locks-sdk/bindings/js/pkg` directory. + For direct npm development without Compose, provide a running local Pubky testnet, PostgreSQL, Lock Server, and Paykit Server first. `init-config` reads the Lock Server public key from `~/.pubky-lock/config.toml` by default; it does not read the Lock Server signing secret. ### Basic Locks stack @@ -190,7 +193,7 @@ checkout is required. `compose.paykit-local-demo.yaml` is intentionally limited to local development and demonstration. When `.local` is absent, the one-shot `compose-bootstrap` service creates the ignored owner-only credentials and non-state configuration before dependent services start. Existing generated credentials are validated and reused. For a quiet configuration check without printing generated environment values, run `npm --prefix examples/js-sdk run validate:paykit-compose`; the wrapper inspects a captured `docker compose -f compose.paykit-local-demo.yaml config --no-env-resolution` model. -This starts separate Locks and Paykit PostgreSQL services, Bitcoin Core regtest, a 101-block wallet bootstrap, Fulcrum readiness through `server.version`, Pubky testnet, Locks, Paykit Server, and both browser demos. All published ports bind to host loopback. Paykit is browser-visible at `http://localhost:3001`; Locks reaches it at `http://127.0.0.1:3001` inside the shared Pubky network namespace. The unprivileged creator and reader images contain the reviewed native helpers at `/usr/local/bin`; they receive only their explicit role/runtime directories and generated WASM package, never the repository root or Lock Server identity volume. +This starts separate Locks and Paykit PostgreSQL services, Bitcoin Core regtest, a 101-block wallet bootstrap, Fulcrum readiness through `server.version`, Pubky testnet, a local Homegate-compatible signup bridge, Locks, Paykit Server, and both browser demos. All published ports bind to host loopback. Paykit is browser-visible at `http://localhost:3001`, the Homegate bridge at `http://localhost:6288`, and Fulcrum at `tcp://localhost:60001`. Locks reaches Paykit at `http://127.0.0.1:3001` inside the shared Pubky network namespace. The unprivileged creator and reader images contain the reviewed native helpers and a package built in the image; they receive only their explicit runtime directories, never the repository root or Lock Server identity volume. Open: @@ -265,7 +268,9 @@ POST /api/demo-auth/start GET /api/demo-auth/status ``` -It displays a `pubkyauth://...` string and command like: +It displays a `pubkyauth://...` string. In the Compose external-wallet flow, scan or paste that request into the wallet under test. The approved wallet identity becomes the canonical creator identity and is published for the reader and Paykit services; the demo never imports the wallet private key. + +For direct npm development outside the Compose external-wallet mode, the recovery-file command remains available: ```bash npm --prefix examples/js-sdk run authenticate -- \ @@ -279,7 +284,7 @@ npm --prefix examples/js-sdk run authenticate -- \ npm --prefix examples/js-sdk run authenticate -- --role content-creator ``` -It signs up/registers the `content-creator` with the configured homeserver, approves the auth string, and the demo server persists its session to: +That command signs up/registers the local `content-creator`, approves the auth string, and the demo server persists its session to: ```text ./.local/js-sdk-demo/content-creator-session.json @@ -297,7 +302,7 @@ The shell returns `{ state, code }` directly to the parent with `postMessage`. T http://localhost:8080/auth/lock-server/callback ``` -Approve the Lock Server auth string with the same role: +Approve the Lock Server auth string with the same identity. In Compose, scan or paste it into the same external wallet. For direct npm development, use: ```bash npm --prefix examples/js-sdk run authenticate -- \ @@ -338,7 +343,7 @@ The Paykit iframe displays the auth URL and both approved local commands. First npm --prefix examples/js-sdk run generate-paykit-account-tpub ``` -This command uses the running Compose regtest node, requests public descriptors only, selects `m/84'/1'/0'`, and intentionally prints only the account-level `tpub` and index at this explicit setup boundary. It never prints or exports the account private key. Then run the companion-auth wrapper: +This command uses the running Compose regtest node, requests public descriptors only, selects `m/84'/1'/0'`, and intentionally prints only the account-level `tpub` and index at this explicit setup boundary. It never prints or exports the account private key. In the external-wallet flow, scan or paste the Paykit authorization request into the same wallet. For direct npm development with a generated creator recovery file, the companion-auth wrapper remains available: ```bash docker compose -f compose.paykit-local-demo.yaml exec creator-demo npm --prefix examples/js-sdk run authenticate-paykit -- --role content-creator @@ -389,7 +394,7 @@ The worker is the sole mutable owner of `./.local/paykit-reader/state.v1`. A dir The native helper is `/usr/local/bin/paykit-reader-demo`; `PAYKIT_READER_DEMO_BIN` is a test-only executable override. Its state path and local Pubky endpoints come from the `PAYKIT_READER_*` Compose environment. Reader homeserver registration runs in a separate direct-spawned Node subprocess with bounded output, timeout, and TERM→KILL cancellation because the Pubky JS API does not expose request cancellation; cancellation waits for child settlement before ownership is released. The worker derives the Paykit peer from the public `content-creator` profile, then passes only the closed native helper environment. The state path must end in `.local/paykit-reader/state.v1`. The helper owns encrypted versioned state, owner-only file permissions, fresh-nonce rewrites, and invariant validation. The worker fences status publication and state checkpoints on current kernel-lock ownership, atomically writes its separate owner-only `worker.v1.json` projection, and clears in-memory readiness immediately if ownership is lost. The HTTP server validates the projection again and requires current in-memory ownership before returning a ready browser status. Terminal worker failure closes PID 1 after a coarse error so Compose restart policy applies. -The reader page persists local progress in browser `localStorage` under `pubky-locks-reader-demo.*` and has a visible **Reset reader state** button. Bundle IDs and access credentials are bearer-like local-dev secrets; the demo displays them for debugging only. +The reader page persists local progress in browser `localStorage` under `pubky-locks-reader-demo.*` and has a visible **Reset reader state** button. Retrieved guarded bytes are never persisted: text and JSON render as text, images use a temporary object URL, and other binary content exposes metadata and a temporary download link. Bundle IDs and access credentials are bearer-like local-dev secrets; the demo displays them for debugging only. ## Static drift check diff --git a/examples/js-sdk/package.json b/examples/js-sdk/package.json index 4ef4760..1bfe6b4 100644 --- a/examples/js-sdk/package.json +++ b/examples/js-sdk/package.json @@ -20,7 +20,7 @@ "validate:paykit-compose": "node scripts/validate-paykit-compose.mjs", "smoke:paykit-compose": "npm run validate:paykit-compose && npm run test:paykit-reader-worker && node scripts/smoke-paykit-compose.mjs", "smoke": "npm --prefix ../../locks-sdk/bindings/js run smoke:examples", - "check": "node --check scripts/init-config.mjs && node --check scripts/init-paykit-compose.mjs && node --check scripts/generate-paykit-account-tpub.mjs && node --check scripts/reset-paykit-demo.mjs && node --check scripts/validate-paykit-compose.mjs && node --check scripts/electrum-readiness.mjs && node --check scripts/smoke-paykit-compose.mjs && node --check scripts/test-paykit-reader-worker.mjs && node --check scripts/create-user.mjs && node --check scripts/publish-creator-profile.mjs && node --check scripts/authenticate.mjs && node --check scripts/authenticate-paykit.mjs && node --check scripts/register-paykit-reader.mjs && node --check scripts/prepare-paykit-reader.mjs && node --check scripts/receive-paykit-request.mjs && node --check scripts/lib/creator-session-state.mjs && node --check scripts/lib/creator-static-path.mjs && node --check scripts/lib/paykit-reader-helper.mjs && node --check scripts/lib/paykit-reader-status.mjs && node --check scripts/lib/paykit-reader-worker.mjs && node --check scripts/start-demo-server.mjs && node --check scripts/start-reader-demo-server.mjs && node --check app.js && node --check app-iframe.js && node --check creator-identity.js && node --check creator-lock-policy.js && node --check paykit-setup.js && node --check creator-complete-flow.js && node --check reader-app.js && node --check reader-flow.js" + "check": "node --check scripts/init-config.mjs && node --check scripts/init-paykit-compose.mjs && node --check scripts/homegate-bridge.mjs && node --check scripts/generate-paykit-account-tpub.mjs && node --check scripts/reset-paykit-demo.mjs && node --check scripts/validate-paykit-compose.mjs && node --check scripts/electrum-readiness.mjs && node --check scripts/smoke-paykit-compose.mjs && node --check scripts/test-paykit-reader-worker.mjs && node --check scripts/create-user.mjs && node --check scripts/publish-creator-profile.mjs && node --check scripts/authenticate.mjs && node --check scripts/authenticate-paykit.mjs && node --check scripts/register-paykit-reader.mjs && node --check scripts/prepare-paykit-reader.mjs && node --check scripts/receive-paykit-request.mjs && node --check scripts/lib/creator-session-state.mjs && node --check scripts/lib/creator-static-path.mjs && node --check scripts/lib/paykit-reader-helper.mjs && node --check scripts/lib/paykit-reader-status.mjs && node --check scripts/lib/paykit-reader-worker.mjs && node --check scripts/start-demo-server.mjs && node --check scripts/start-reader-demo-server.mjs && node --check app.js && node --check app-iframe.js && node --check creator-identity.js && node --check creator-lock-policy.js && node --check paykit-setup.js && node --check creator-complete-flow.js && node --check reader-app.js && node --check reader-flow.js" }, "dependencies": { "@synonymdev/pubky": "^0.9.3" diff --git a/examples/js-sdk/reader-app.js b/examples/js-sdk/reader-app.js index 5896333..19753d7 100644 --- a/examples/js-sdk/reader-app.js +++ b/examples/js-sdk/reader-app.js @@ -101,6 +101,7 @@ async function bootstrap() { function bindEvents() { el.reset.addEventListener('click', async () => { invalidateWorkflow(); + clearReadResult(); localStorage.removeItem(STATE_KEY); Object.assign(state, { resource: '', @@ -299,7 +300,7 @@ async function submitProof() { state.completion = null; state.accessCredential = null; state.accessCredentialResponse = null; - state.readResult = null; + clearReadResult(); activeSubmissionToken = null; state.submittingProof = false; persistState(); @@ -453,12 +454,7 @@ async function readPaymentContent(handle, path, accessCredential) { }); if (!workflowMatches(handle)) return; state.guardedResourcePath = path; - state.readResult = { - path, - size: result.size, - contentType: result.contentType, - text: result.text, - }; + setReadResult(path, result); persistState(); render(); } @@ -516,12 +512,7 @@ async function readContent(path) { }); if (!workflowMatches(handle)) return; state.guardedResourcePath = path; - state.readResult = { - path, - size: result.size, - contentType: result.contentType, - text: result.text, - }; + setReadResult(path, result); persistState(); render(); await postClientLog('info', 'reader-proxy-read-succeeded', { @@ -639,7 +630,7 @@ function render() { el.readStatus.textContent = state.accessCredential ? 'Ready to read guarded content.' : 'Waiting for access credential.'; el.readStatus.className = 'muted'; } - el.readOutput.textContent = state.readResult ? state.readResult.text : ''; + renderReadOutput(); } function renderLockResources() { @@ -704,6 +695,7 @@ function restoreState() { paykitReaderState: 'starting', paykitPaymentRequest: null, baselinePaymentRequestId: null, + readResult: null, }); } catch { localStorage.removeItem(STATE_KEY); @@ -721,6 +713,7 @@ function persistState() { paykitReaderState: _paykitReaderState, paykitPaymentRequest: _paykitPaymentRequest, baselinePaymentRequestId: _baselinePaymentRequestId, + readResult: _readResult, ...persisted } = state; localStorage.setItem(STATE_KEY, JSON.stringify(persisted)); @@ -759,9 +752,60 @@ function clearVerificationState({ clearLoaded = false } = {}) { state.completion = null; state.accessCredential = null; state.accessCredentialResponse = null; + clearReadResult(); +} + +function setReadResult(path, result) { + clearReadResult(); + const objectUrl = result.kind === 'text' + ? null + : URL.createObjectURL(new Blob([result.bytes], { type: result.contentType })); + state.readResult = { + path, + size: result.size, + contentType: result.contentType, + kind: result.kind, + text: result.text, + objectUrl, + }; +} + +function clearReadResult() { + if (state.readResult?.objectUrl) URL.revokeObjectURL(state.readResult.objectUrl); state.readResult = null; } +function renderReadOutput() { + el.readOutput.replaceChildren(); + const result = state.readResult; + if (!result) return; + + if (result.kind === 'text') { + const output = document.createElement('pre'); + output.textContent = result.text; + el.readOutput.append(output); + return; + } + + if (result.kind === 'image') { + const image = document.createElement('img'); + image.src = result.objectUrl; + image.alt = `Guarded content from ${result.path}`; + el.readOutput.append(image); + return; + } + + const metadata = document.createElement('p'); + metadata.textContent = `${result.contentType}, ${result.size} bytes`; + const download = document.createElement('a'); + download.href = result.objectUrl; + download.download = result.path.split('/').pop() || 'guarded-content'; + download.textContent = 'Download guarded content'; + el.readOutput.append(metadata, download); +} + +window.addEventListener('pagehide', clearReadResult); + function workflowMatches(handle) { return workflowHandleMatches(handle, { incarnation: workflowIncarnation, diff --git a/examples/js-sdk/reader-flow.js b/examples/js-sdk/reader-flow.js index 8c7824a..3ce5761 100644 --- a/examples/js-sdk/reader-flow.js +++ b/examples/js-sdk/reader-flow.js @@ -307,10 +307,17 @@ export async function decodeGuardedContentResponse(response) { if (!(response instanceof Response)) throw new Error('guarded resource read returned a non-Response value'); const contentType = response.headers.get('content-type') || 'application/octet-stream'; const array = new Uint8Array(await response.arrayBuffer()); + const mediaType = contentType.split(';', 1)[0].trim().toLowerCase(); + const kind = mediaType.startsWith('text/') || mediaType === 'application/json' || mediaType.endsWith('+json') + ? 'text' + : mediaType.startsWith('image/') + ? 'image' + : 'binary'; return { response, bytes: array, - text: new TextDecoder().decode(array), + kind, + text: kind === 'text' ? new TextDecoder().decode(array) : null, size: array.byteLength, contentType, }; diff --git a/examples/js-sdk/reader.html b/examples/js-sdk/reader.html index bacc428..2419fdf 100644 --- a/examples/js-sdk/reader.html +++ b/examples/js-sdk/reader.html @@ -11,6 +11,7 @@ input, select, button, textarea { font: inherit; } input[type="text"], textarea { width: 100%; box-sizing: border-box; } pre { background: #f7f7f7; padding: 0.75rem; overflow: auto; white-space: pre-wrap; } + #read-output img { display: block; max-width: 100%; height: auto; } .ok { color: #067d17; } .error { color: #a40000; } .muted { color: #666; } @@ -101,7 +102,7 @@

Secondary files

Waiting for access credential.

Content

-

+      
diff --git a/examples/js-sdk/scripts/homegate-bridge.mjs b/examples/js-sdk/scripts/homegate-bridge.mjs new file mode 100644 index 0000000..fee6867 --- /dev/null +++ b/examples/js-sdk/scripts/homegate-bridge.mjs @@ -0,0 +1,55 @@ +#!/usr/bin/env node +import { readFile } from 'node:fs/promises'; +import { createServer } from 'node:http'; + +const listenPort = Number(process.env.HOMEGATE_BRIDGE_PORT ?? 8082); +const configPath = process.env.HOMEGATE_BRIDGE_CONFIG; +const homeserverAdminUrl = process.env.HOMEGATE_BRIDGE_HOMESERVER_ADMIN_URL; +const homeserverAdminPassword = process.env.PUBKY_HOMESERVER_ADMIN_PASSWORD; + +if (!configPath || !homeserverAdminUrl || !homeserverAdminPassword) { + throw new Error('Homegate bridge configuration is incomplete'); +} + +const server = createServer(async (request, response) => { + if (request.method === 'GET' && request.url === '/health') { + return sendJson(response, { ok: true }); + } + if (request.method !== 'POST' || request.url !== '/ip_verification') { + response.writeHead(404).end('not found'); + return; + } + + try { + const [{ testnet }, signupResponse] = await Promise.all([ + readDemoConfig(configPath), + fetch(`${homeserverAdminUrl}/generate_signup_token`, { + headers: { 'x-admin-password': homeserverAdminPassword }, + }), + ]); + if (!signupResponse.ok) throw new Error(`homeserver returned HTTP ${signupResponse.status}`); + const signupCode = (await signupResponse.text()).trim(); + if (!signupCode || typeof testnet?.homeserver !== 'string') { + throw new Error('homeserver signup response is incomplete'); + } + sendJson(response, { signupCode, homeserverPubky: testnet.homeserver }); + } catch { + sendJson(response, { error: 'signup unavailable' }, 503); + } +}); + +server.listen(listenPort, '0.0.0.0', () => { + console.log(`Homegate bridge listening on port ${listenPort}`); +}); + +async function readDemoConfig(path) { + return JSON.parse(await readFile(path, 'utf8')); +} + +function sendJson(response, value, status = 200) { + response.writeHead(status, { + 'cache-control': 'no-store', + 'content-type': 'application/json; charset=utf-8', + }); + response.end(`${JSON.stringify(value)}\n`); +} diff --git a/examples/js-sdk/scripts/init-paykit-compose.mjs b/examples/js-sdk/scripts/init-paykit-compose.mjs index 22c0751..615923e 100644 --- a/examples/js-sdk/scripts/init-paykit-compose.mjs +++ b/examples/js-sdk/scripts/init-paykit-compose.mjs @@ -86,6 +86,7 @@ function generatedPaths(root) { paykitConfig: join(root, 'paykit-config', 'config.toml'), bitcoinRpc: join(root, 'bitcoin-rpc', 'bitcoin-rpc.env'), pubkyHomeserver: join(root, 'pubky-homeserver', 'config.toml'), + homegateBridge: join(root, 'homegate-bridge', 'homegate.env'), }; } @@ -116,6 +117,7 @@ export async function initializePaykitCompose({ 'pubky-homeserver', 'paykit-server', 'paykit-config', + 'homegate-bridge', ].map(async (directory) => { const path = join(root, directory); await mkdir(path, { recursive: true, mode: 0o700 }); @@ -149,6 +151,7 @@ export async function initializePaykitCompose({ databasePassword: secrets.locksPostgresPassword, adminPassword: secrets.pubkyHomeserverAdminPassword, })), + writeSecure(paths.homegateBridge, `PUBKY_HOMESERVER_ADMIN_PASSWORD=${secrets.pubkyHomeserverAdminPassword}\n`), ]); if (lockConfigPath) { diff --git a/examples/js-sdk/scripts/lib/creator-session-state.mjs b/examples/js-sdk/scripts/lib/creator-session-state.mjs index 458a49c..d982fc5 100644 --- a/examples/js-sdk/scripts/lib/creator-session-state.mjs +++ b/examples/js-sdk/scripts/lib/creator-session-state.mjs @@ -25,8 +25,8 @@ export async function readCreatorDemoSessionForCurrentRole({ throw error; } - const profile = await readJson(profilePath); - if (!creatorIdentitiesMatch(session, profile)) { + const profile = profilePath ? await readJson(profilePath) : null; + if (!validCreatorSession(session) || (profile && !creatorIdentitiesMatch(session, profile))) { await clearCreatorDemoSession(sessionPath); return null; } @@ -42,8 +42,8 @@ export async function writeCreatorDemoSessionForCurrentRole( profilePath = defaultProfilePath, } = {}, ) { - const profileBeforeWrite = await readJson(profilePath); - if (!creatorIdentitiesMatch(session, profileBeforeWrite)) { + const profileBeforeWrite = profilePath ? await readJson(profilePath) : null; + if (!validCreatorSession(session) || (profileBeforeWrite && !creatorIdentitiesMatch(session, profileBeforeWrite))) { await clearCreatorDemoSession(sessionPath); throw new Error('creator identity changed during demo authentication'); } @@ -51,16 +51,20 @@ export async function writeCreatorDemoSessionForCurrentRole( await writeJson(sessionPath, session, { mode: 0o600 }); await chmod(sessionPath, 0o600); - const profileAfterWrite = await readJson(profilePath); - if (!creatorIdentitiesMatch(session, profileAfterWrite)) { + const profileAfterWrite = profilePath ? await readJson(profilePath) : null; + if (profileAfterWrite && !creatorIdentitiesMatch(session, profileAfterWrite)) { await clearCreatorDemoSession(sessionPath); throw new Error('creator identity changed during demo authentication'); } } function creatorIdentitiesMatch(session, profile) { - return session?.role === 'content-creator' + return validCreatorSession(session) && profile?.role === 'content-creator' - && typeof session.pubky === 'string' && session.pubky === profile.pubky; } + +function validCreatorSession(session) { + return session?.role === 'content-creator' + && /^pubky[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/.test(session.pubky ?? ''); +} diff --git a/examples/js-sdk/scripts/publish-creator-profile.mjs b/examples/js-sdk/scripts/publish-creator-profile.mjs index 884151a..d618161 100644 --- a/examples/js-sdk/scripts/publish-creator-profile.mjs +++ b/examples/js-sdk/scripts/publish-creator-profile.mjs @@ -14,12 +14,13 @@ const CANONICAL_PUBKY = /^pubky[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/; export async function publishCreatorProfile({ source = roleProfilePath('content-creator'), destination = creatorPublicProfilePath, + profile, } = {}) { - const profile = await readJson(source); - if (profile?.role !== 'content-creator' || !CANONICAL_PUBKY.test(profile.pubky ?? '')) { + const creatorProfile = profile ?? await readJson(source); + if (creatorProfile?.role !== 'content-creator' || !CANONICAL_PUBKY.test(creatorProfile.pubky ?? '')) { throw new Error('valid content-creator profile is required'); } - const publicProfile = Object.freeze({ role: 'content-creator', pubky: profile.pubky }); + const publicProfile = Object.freeze({ role: 'content-creator', pubky: creatorProfile.pubky }); await writeJson(destination, publicProfile); return publicProfile; } diff --git a/examples/js-sdk/scripts/smoke-paykit-compose.mjs b/examples/js-sdk/scripts/smoke-paykit-compose.mjs index c459218..d4456fe 100644 --- a/examples/js-sdk/scripts/smoke-paykit-compose.mjs +++ b/examples/js-sdk/scripts/smoke-paykit-compose.mjs @@ -151,6 +151,7 @@ try { 'paykit-server/paykit.env', 'bitcoin-rpc/bitcoin-rpc.env', 'pubky-homeserver/config.toml', + 'homegate-bridge/homegate.env', ]; for (const file of generatedFiles) { assert.equal((await stat(join(generatedRoot, file))).mode & 0o777, 0o600, `${file} must be mode 0600`); @@ -169,6 +170,7 @@ try { 'pubky-homeserver', 'paykit-server', 'paykit-config', + 'homegate-bridge', ]) { assert.equal((await stat(join(generatedRoot, directory))).mode & 0o777, 0o700, `${directory} must be mode 0700`); } @@ -241,6 +243,14 @@ try { loadProfile: async () => { throw new Error('private creator profile must not be loaded'); }, }); assert.equal(readerFromPublicProfile.PAYKIT_READER_SERVER_PUBKY, creatorPubky); + await publishCreatorProfile({ + profile: { role: 'content-creator', pubky: lockServerPubky }, + destination: publicProfilePath, + }); + assert.deepEqual(JSON.parse(await readFile(publicProfilePath, 'utf8')), { + role: 'content-creator', + pubky: lockServerPubky, + }); } finally { await rm(configOnlyRoot, { recursive: true, force: true }); } @@ -266,6 +276,7 @@ for (const service of [ 'bitcoin-bootstrap:', 'fulcrum:', 'electrum-readiness:', + 'homegate-bridge:', 'paykit-config:', 'demo-config:', 'paykit-server:', @@ -285,6 +296,8 @@ for (const required of [ 'https://github.com/pubky/locks.git#df5ea1b6d8dcdec3a9b5a915c3f57bca69d75c8a', '127.0.0.1:${LOCKS_PAYKIT_PORT:-3001}:3001', '127.0.0.1:${LOCKS_READER_DEMO_PORT:-8088}:8081', + '127.0.0.1:${LOCKS_ELECTRUM_PORT:-60001}:50001', + '127.0.0.1:${LOCKS_HOMEGATE_PORT:-6288}:8082', 'bitcoin-cli -conf=\\"$${BITCOIN_DATA}/bitcoin.conf\\" -regtest getblockchaininfo', 'user: "1000:1000"', '.local/bitcoin-bootstrap:/home/bitcoin/.bitcoin', @@ -297,6 +310,7 @@ for (const required of [ './.local/pubky-homeserver:/run/compose-local/pubky-homeserver:ro', './.local/locks-server:/run/compose-local/locks-server:ro', './.local/paykit-server:/run/compose-local/paykit-server:ro', + './.local/homegate-bridge:/run/compose-local/homegate-bridge:ro', 'node examples/js-sdk/scripts/init-paykit-compose.mjs', 'exec /entrypoint.sh Fulcrum', 'PAYKIT_READER_DEMO_BIN:', @@ -327,6 +341,11 @@ for (const privateVolume of ['name: locks_lock-home', 'name: pubky-locks-demo-pu } assert.ok(!compose.includes('- ./:/workspace'), 'services must not mount the repository root'); assert.ok(!compose.includes('- lock-home:/root'), 'demo services must not mount Lock Server identity state'); +const creatorService = compose.slice(compose.indexOf(' creator-demo:'), compose.indexOf('\n reader-demo:')); +assert.ok(creatorService.includes('--external-wallet'), 'creator must use the authenticated external wallet identity'); +assert.ok(!creatorService.includes('create-user -- --role content-creator'), 'external wallet mode must not create a second creator identity'); +assert.ok(!creatorService.includes('.local/content-creator'), 'external wallet mode must not mount creator recovery state'); +assert.ok(!creatorService.includes('--allow-unhealthy'), 'creator preflight must fail closed'); const readerService = compose.slice(compose.indexOf(' reader-demo:'), compose.indexOf('\nvolumes:')); assert.ok(!readerService.includes('.local/js-sdk-demo'), 'reader must not mount creator session state'); assert.ok(!readerService.includes('.local/content-creator'), 'reader must not mount creator recovery state'); @@ -334,10 +353,15 @@ assert.ok(readerService.includes('.local/creator-public'), 'reader requires only assert.ok(readerService.includes('PAYKIT_READER_WORKER_ENABLED: "1"'), 'reader must enable its embedded Paykit worker'); assert.ok(readerService.includes('npm --prefix examples/js-sdk run create-user -- --role content-viewer'), 'reader must create or reuse its recovery identity'); assert.ok(readerService.includes('exec node examples/js-sdk/scripts/start-reader-demo-server.mjs'), 'reader server must replace its bootstrap shell as PID 1'); +assert.ok(!readerService.includes('--allow-unhealthy'), 'reader preflight must fail closed'); assert.ok(readerService.includes('healthcheck:'), 'reader must expose worker-aware Compose health'); assert.ok(readerService.includes('http://127.0.0.1:8081/api/paykit-reader/status'), 'reader health must use the closed worker status endpoint'); assert.ok(readerService.includes('restart: unless-stopped'), 'reader worker must have an explicit restart policy'); assert.ok(!compose.includes('POSTGRES_PASSWORD: locks'), 'database credentials must not be committed inline'); +assert.ok(!compose.includes('./locks-sdk/bindings/js/pkg:/workspace/locks-sdk/bindings/js/pkg'), 'demo images must provide their own WASM package'); +for (const required of ['FROM rust:1.91.1-slim-bookworm@sha256:8514999d4786ef12efe89239e86b3d0a021b94b9d35108c8efe6c79ca7dc1a65 AS locks-sdk-wasm', 'cargo install wasm-pack --version 0.13.1 --locked', 'wasm-pack build --target web --out-dir pkg', 'COPY --from=locks-sdk-wasm']) { + assert.ok(jsDemoDockerfile.includes(required), `JS demo image missing ${required}`); +} assert.ok(locksEntrypoint.includes('LOCKS_PUBLIC_CONFIG'), 'Lock Server must publish an explicit public artifact'); for (const required of ['[paykit]', 'server_url = "http://127.0.0.1:3001"', 'minimum_confirmations = 0']) { assert.ok(locksEntrypoint.includes(required), `Locks generated config missing ${required}`); diff --git a/examples/js-sdk/scripts/start-demo-server.mjs b/examples/js-sdk/scripts/start-demo-server.mjs index 9672471..585f0b8 100644 --- a/examples/js-sdk/scripts/start-demo-server.mjs +++ b/examples/js-sdk/scripts/start-demo-server.mjs @@ -11,9 +11,11 @@ import { writeCreatorDemoSessionForCurrentRole, } from './lib/creator-session-state.mjs'; import { resolveCreatorStaticPath } from './lib/creator-static-path.mjs'; +import { publishCreatorProfile } from './publish-creator-profile.mjs'; const args = parseArgs(); const allowUnhealthy = Boolean(args['allow-unhealthy']); +const externalWallet = Boolean(args['external-wallet']); const config = await readDemoConfig(); const serviceConfig = withInternalServiceUrls(config); const port = Number(new URL(config.demoServer.url).port || 8080); @@ -105,13 +107,15 @@ async function startDemoAuth() { demoAuthPromise = activeDemoAuthFlow .awaitApproval() .then(async (session) => { - await writeCreatorDemoSessionForCurrentRole({ + const creatorSession = { role: 'content-creator', pubky: session.info.publicKey.toString(), capabilities: session.info.capabilities, exported_session: session.export(), authenticated_at: new Date().toISOString(), - }); + }; + await writeCreatorDemoSessionForCurrentRole(creatorSession, sessionStateOptions()); + if (externalWallet) await publishCreatorProfile({ profile: creatorSession }); return session; }) .catch((error) => { @@ -134,7 +138,7 @@ async function startDemoAuth() { } async function demoAuthStatus() { - const session = await readCreatorDemoSessionForCurrentRole(); + const session = await readCurrentCreatorSession(); if (session) { if (debugEnabled) { console.log(`[demo] demo-auth persisted session pubky=${session.pubky} path=./.local/js-sdk-demo/content-creator-session.json`); @@ -157,7 +161,17 @@ async function demoAuthStatus() { } async function hasPersistedDemoSession() { - return Boolean(await readCreatorDemoSessionForCurrentRole()); + return Boolean(await readCurrentCreatorSession()); +} + +function sessionStateOptions() { + return externalWallet ? { profilePath: null } : {}; +} + +async function readCurrentCreatorSession() { + const session = await readCreatorDemoSessionForCurrentRole(sessionStateOptions()); + if (session && externalWallet) await publishCreatorProfile({ profile: session }); + return session; } function publicBrowserConfig(source) { @@ -228,6 +242,9 @@ async function runPreflight(source) { push('config', false, error.message); } + const wasmPackage = join(repoRoot, 'locks-sdk/bindings/js/pkg/locks_sdk_wasm_bg.wasm'); + push('WASM package', existsSync(wasmPackage), existsSync(wasmPackage) ? 'present' : 'missing'); + await checkHttp(`${source.lockServer.url}/healthz`, 'lock-server /healthz', checks, (status) => status >= 200 && status < 300); await checkHttp(`${source.lockServer.url}/readyz`, 'lock-server /readyz', checks, (status) => status >= 200 && status < 300); await checkHttp(source.testnet.pkarrRelay, 'pkarr relay', checks, (status) => status < 500); // status < 500 diff --git a/examples/js-sdk/scripts/start-reader-demo-server.mjs b/examples/js-sdk/scripts/start-reader-demo-server.mjs index e5febc8..fac6f1e 100644 --- a/examples/js-sdk/scripts/start-reader-demo-server.mjs +++ b/examples/js-sdk/scripts/start-reader-demo-server.mjs @@ -176,6 +176,8 @@ async function runPreflight(source) { } catch (error) { checks.push({ name: 'config', ok: false, message: error.message }); } + const wasmPackage = join(repoRoot, 'locks-sdk/bindings/js/pkg/locks_sdk_wasm_bg.wasm'); + checks.push({ name: 'WASM package', ok: existsSync(wasmPackage), message: existsSync(wasmPackage) ? 'present' : 'missing' }); await checkHttp(`${source.lockServer.url}/healthz`, 'lock-server /healthz', checks, (status) => status === 200); await checkHttp(`${source.lockServer.url}/readyz`, 'lock-server /readyz', checks, (status) => status === 200); await checkHttp(source.testnet.pkarrRelay, 'pkarr relay', checks, (status) => status === 200 || status === 404); diff --git a/examples/js-sdk/scripts/validate-paykit-compose.mjs b/examples/js-sdk/scripts/validate-paykit-compose.mjs index d4b8fd8..bd4218b 100644 --- a/examples/js-sdk/scripts/validate-paykit-compose.mjs +++ b/examples/js-sdk/scripts/validate-paykit-compose.mjs @@ -14,6 +14,7 @@ const REQUIRED_SERVICES = [ 'fulcrum', 'electrum-readiness', 'pubky-testnet', + 'homegate-bridge', 'locks-server', 'paykit-config', 'demo-config', @@ -39,6 +40,7 @@ export function validateSafeComposeModel(model) { 'electrum-readiness', 'paykit-config', 'demo-config', + 'homegate-bridge', 'creator-demo', 'reader-demo', ]) { @@ -84,8 +86,10 @@ export function validateSafeComposeModel(model) { ) { throw new Error('bitcoin-bootstrap must use reset-managed scratch state'); } - for (const port of model.services['pubky-testnet'].ports ?? []) { - if (port.host_ip !== '127.0.0.1') throw new Error('published demo ports must bind to loopback'); + for (const service of Object.values(model.services)) { + for (const port of service.ports ?? []) { + if (port.host_ip !== '127.0.0.1') throw new Error('published demo ports must bind to loopback'); + } } return model; } diff --git a/locks-sdk/bindings/js/scripts/smoke-examples.mjs b/locks-sdk/bindings/js/scripts/smoke-examples.mjs index 2317e87..d6e9a0d 100644 --- a/locks-sdk/bindings/js/scripts/smoke-examples.mjs +++ b/locks-sdk/bindings/js/scripts/smoke-examples.mjs @@ -385,14 +385,16 @@ const { const creatorSessionTestDir = mkdtempSync(join(tmpdir(), 'locks-creator-session-')); const creatorSessionTestPath = join(creatorSessionTestDir, 'content-creator-session.json'); const creatorProfileTestPath = join(creatorSessionTestDir, 'profile.json'); +const firstCreatorPubky = 'pubkytkrq8zmwb8a3m9k15csu3q17qmfgqnp9dskbrg9uq1rydpyxp7qy'; +const secondCreatorPubky = 'pubky7ir1ttte48bcp4zjychjyscicrwi1j34mtt91ptsafdbjmr8g9eo'; try { writeFileSync(creatorSessionTestPath, '{"exported_session":"sensitive"}'); await clearCreatorDemoSession(creatorSessionTestPath); assert.equal(existsSync(creatorSessionTestPath), false); await clearCreatorDemoSession(creatorSessionTestPath); - writeFileSync(creatorProfileTestPath, JSON.stringify({ role: 'content-creator', pubky: 'creator-b' })); - writeFileSync(creatorSessionTestPath, JSON.stringify({ role: 'content-creator', pubky: 'creator-a', exported_session: 'old-secret' })); + writeFileSync(creatorProfileTestPath, JSON.stringify({ role: 'content-creator', pubky: secondCreatorPubky })); + writeFileSync(creatorSessionTestPath, JSON.stringify({ role: 'content-creator', pubky: firstCreatorPubky, exported_session: 'old-secret' })); assert.equal(await readCreatorDemoSessionForCurrentRole({ sessionPath: creatorSessionTestPath, profilePath: creatorProfileTestPath, @@ -401,14 +403,14 @@ try { await assert.rejects( writeCreatorDemoSessionForCurrentRole( - { role: 'content-creator', pubky: 'creator-a', exported_session: 'late-old-secret' }, + { role: 'content-creator', pubky: firstCreatorPubky, exported_session: 'late-old-secret' }, { sessionPath: creatorSessionTestPath, profilePath: creatorProfileTestPath }, ), /creator identity changed during demo authentication/, ); assert.equal(existsSync(creatorSessionTestPath), false); - const currentSession = { role: 'content-creator', pubky: 'creator-b', exported_session: 'current-secret' }; + const currentSession = { role: 'content-creator', pubky: secondCreatorPubky, exported_session: 'current-secret' }; await writeCreatorDemoSessionForCurrentRole(currentSession, { sessionPath: creatorSessionTestPath, profilePath: creatorProfileTestPath, @@ -421,6 +423,18 @@ try { currentSession, ); assert.equal(statSync(creatorSessionTestPath).mode & 0o777, 0o600); + const externalSession = { role: 'content-creator', pubky: firstCreatorPubky, exported_session: 'external-secret' }; + await writeCreatorDemoSessionForCurrentRole(externalSession, { + sessionPath: creatorSessionTestPath, + profilePath: null, + }); + assert.deepEqual( + await readCreatorDemoSessionForCurrentRole({ + sessionPath: creatorSessionTestPath, + profilePath: null, + }), + externalSession, + ); } finally { rmSync(creatorSessionTestDir, { recursive: true, force: true }); } @@ -965,8 +979,15 @@ const guardedResponse = new Response(new TextEncoder().encode('payment unlocked' }); const decodedGuarded = await decodeGuardedContentResponse(guardedResponse); assert.equal(decodedGuarded.contentType, 'text/plain; charset=utf-8'); +assert.equal(decodedGuarded.kind, 'text'); assert.equal(decodedGuarded.text, 'payment unlocked'); assert.equal(decodedGuarded.size, 16); +const decodedImage = await decodeGuardedContentResponse(new Response(Uint8Array.of(1, 2, 3), { + headers: { 'content-type': 'image/png' }, +})); +assert.equal(decodedImage.kind, 'image'); +assert.equal(decodedImage.text, null); +assert.deepEqual([...decodedImage.bytes], [1, 2, 3]); const { buildReaderHelperInput, From c6fcbde22c427e8a6844769b479211c27fd9f1dc Mon Sep 17 00:00:00 2001 From: benk10 Date: Thu, 6 Aug 2026 08:20:49 -0500 Subject: [PATCH 2/6] fix: complete external wallet Paykit demo --- compose.paykit-local-demo.yaml | 11 ++++--- docker/locks-server-compose-entrypoint.sh | 2 +- examples/js-sdk/reader-app.js | 2 ++ examples/js-sdk/reader.html | 4 +-- examples/js-sdk/scripts/start-demo-server.mjs | 2 +- .../scripts/start-reader-demo-server.mjs | 9 +++++- locks-server/src/app_state/pubky_clients.rs | 30 +++++++------------ locks-server/src/app_state/test_support.rs | 17 ++--------- 8 files changed, 33 insertions(+), 44 deletions(-) diff --git a/compose.paykit-local-demo.yaml b/compose.paykit-local-demo.yaml index d3b4930..2b4c91f 100644 --- a/compose.paykit-local-demo.yaml +++ b/compose.paykit-local-demo.yaml @@ -281,11 +281,11 @@ services: paykit-server: image: pubky-locks-paykit-server:local build: - context: "https://github.com/pubky/paykit-server.git#f38c7915e6b9b104e040773e78438f8aa984c46c" + context: "https://github.com/pubky/paykit-server.git#4d6992fa3a7dd14479775b2d75f06cdb43f857ea" dockerfile: Dockerfile.local additional_contexts: - paykit-lib: "https://github.com/pubky/paykit-rs.git#52a852995bfc457b78d32f5a45f6741766a89bba:paykit-lib" - paykit-sdk: "https://github.com/pubky/paykit-rs.git#52a852995bfc457b78d32f5a45f6741766a89bba:paykit-sdk" + paykit-lib: "https://github.com/pubky/paykit-rs.git#6b241878a9bba5cecea919c0298c3f90624be6ff:paykit-lib" + paykit-sdk: "https://github.com/pubky/paykit-rs.git#6b241878a9bba5cecea919c0298c3f90624be6ff:paykit-sdk" locks: "https://github.com/pubky/locks.git#df5ea1b6d8dcdec3a9b5a915c3f57bca69d75c8a" depends_on: @@ -393,6 +393,7 @@ services: PAYKIT_READER_RECEIVER_PATH: bitkit/wallet PAYKIT_READER_SERVER_PATH: bitkit/server PAYKIT_READER_WORKER_ENABLED: "1" + PAYKIT_EXTERNAL_READER_PUBKY: ${PAYKIT_EXTERNAL_READER_PUBKY:-} PUBKY_LOCK_DEBUG: ${PUBKY_LOCK_DEBUG:-0} volumes: - ./.local/demo-config:/workspace/.local/demo-config:ro @@ -403,7 +404,9 @@ services: - sh - -euc - | - npm --prefix examples/js-sdk run create-user -- --role content-viewer + if [ -z "$PAYKIT_EXTERNAL_READER_PUBKY" ]; then + npm --prefix examples/js-sdk run create-user -- --role content-viewer + fi exec node examples/js-sdk/scripts/start-reader-demo-server.mjs healthcheck: test: diff --git a/docker/locks-server-compose-entrypoint.sh b/docker/locks-server-compose-entrypoint.sh index c7b015c..ccd040e 100644 --- a/docker/locks-server-compose-entrypoint.sh +++ b/docker/locks-server-compose-entrypoint.sh @@ -102,7 +102,7 @@ public_ip = "127.0.0.1" public_pubky_tls_port = 6287 public_icann_http_port = 3000 icann_domain = "localhost" -pkarr_relays = ["http://localhost:15411"] +pkarr_relays = ["http://127.0.0.1:15411"] key_republisher_interval_seconds = 86400 [rate_limits.verification_submission] diff --git a/examples/js-sdk/reader-app.js b/examples/js-sdk/reader-app.js index 19753d7..3c42374 100644 --- a/examples/js-sdk/reader-app.js +++ b/examples/js-sdk/reader-app.js @@ -85,6 +85,8 @@ async function bootstrap() { el.configStatus.textContent = `Reader demo using Lock Server ${state.config.lockServer.pubky}`; el.configStatus.className = 'ok'; restoreState(); + const resource = new URL(window.location.href).searchParams.get('resource')?.trim(); + if (resource) state.resource = resource; bindEvents(); await refreshPaykitReaderStatus(); render(); diff --git a/examples/js-sdk/reader.html b/examples/js-sdk/reader.html index 2419fdf..b3e01d2 100644 --- a/examples/js-sdk/reader.html +++ b/examples/js-sdk/reader.html @@ -59,9 +59,9 @@

2. Submit proof bundle