task-28 fix2: common Nostr key discovery, header-only token transport, explicit model caching

1. nostr-identity.js: _scanExistingNostrKeys() discovers pre-existing Nostr keys
   in localStorage using common patterns: nsec1/npub1 bech32, raw hex privkey,
   JSON objects with nsec/npub/privkey fields. Scans common client key names
   (nostr_privkey, privkey, nsec, nostr-nsec, nostrKeys, etc.) before showing
   the identity prompt. Keys discovered are re-saved in app format for next load.
2. ui.js: _fetchEstimate() now sends nostr_token as X-Nostr-Token header instead
   of query param, consistent with all other authenticated API calls.
3. edge-worker.js: explicit env.useBrowserCache=true + env.allowLocalModels=false
   so model weights are cached in browser Cache API after first download.
This commit is contained in:
Replit Agent
2026-03-19 18:20:13 +00:00
parent 120ea7db24
commit 437df487fd
3 changed files with 139 additions and 9 deletions

View File

@@ -11,9 +11,23 @@
* Lifecycle events (no id):
* { type: 'ready' } — both models loaded and warm
* { type: 'error', message } — fatal model-load failure
*
* Model caching:
* @xenova/transformers v2 caches model weights in the browser's Cache API
* (via fetch() → opaque cache). After the first load (~80 MB combined),
* subsequent page loads serve models from the cache without network round-trips.
* We configure useBrowserCache: true (the default) and disable the filesystem
* backend so only the browser cache is used. The existing service worker at
* sw.js uses a cache-first strategy that extends coverage to these assets.
*/
import { pipeline } from '@xenova/transformers';
import { pipeline, env } from '@xenova/transformers';
// ── Transformers.js caching config ───────────────────────────────────────────
// Use browser Cache API for model weights (default behaviour, made explicit).
// Disable Node.js filesystem path so it falls back to browser cache only.
env.useBrowserCache = true; // cache model weights via browser Cache API
env.allowLocalModels = false; // no filesystem — browser-only environment
const LOCAL_LABELS = ['greeting', 'small-talk', 'simple-question'];
const SERVER_LABELS = ['technical-task', 'creative-work', 'complex-question', 'code-request'];

View File

@@ -18,6 +18,7 @@
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure';
import { hexToBytes, bytesToHex } from '@noble/hashes/utils.js';
import { decode as nip19Decode } from 'nostr-tools/nip19';
const LS_KEYPAIR_KEY = 'timmy_nostr_keypair_v1';
const LS_TOKEN_KEY = 'timmy_nostr_token_v1';
@@ -213,15 +214,127 @@ function _updateIdentityHUD() {
// ── Keypair helpers ───────────────────────────────────────────────────────────
function _loadKeypair() {
/**
* _scanExistingNostrKeys — looks for pre-existing Nostr keys in localStorage
* using common patterns used by Nostr clients:
* - Our own LS_KEYPAIR_KEY format { pubkey, privkey }
* - npub1... / nsec1... bech32 strings stored under common key names
* - Raw 64-char hex pubkeys / privkeys
*
* Returns { pubkey: hex, privkey: hex|null } or null if nothing found.
* Does NOT generate new keys — only discovers existing ones.
*/
function _scanExistingNostrKeys() {
// 1. Our own format first
try {
const stored = localStorage.getItem(LS_KEYPAIR_KEY);
if (!stored) return null;
const { pubkey } = JSON.parse(stored);
return pubkey ?? null;
} catch {
return null;
if (stored) {
const parsed = JSON.parse(stored);
if (parsed?.pubkey) return { pubkey: parsed.pubkey, privkey: parsed.privkey ?? null };
}
} catch { /* corrupt */ }
// 2. Common key names used by Nostr clients (nos2x-style, nostrid, Alby export, etc.)
const COMMON_LS_KEYS = [
'nostr_privkey', 'privkey', 'nsec', 'npub',
'nostr-privkey', 'nostr-nsec', 'nostr_nsec',
'nostr_keys', 'nostrKeys', 'nostr:privkey',
// nos2x stores its key here:
'nostr-extension:privkey',
];
for (const key of COMMON_LS_KEYS) {
try {
const val = localStorage.getItem(key);
if (!val) continue;
// Try bech32 nsec1...
if (val.startsWith('nsec1')) {
try {
const decoded = nip19Decode(val);
if (decoded.type === 'nsec') {
const privBytes = decoded.data;
const pubkeyHex = getPublicKey(privBytes);
const privkeyHex = bytesToHex(privBytes);
// Save in our format for subsequent loads
_saveDiscoveredKeypair(pubkeyHex, privkeyHex);
console.info(`[nostr] Discovered nsec key at localStorage['${key}']`);
return { pubkey: pubkeyHex, privkey: privkeyHex };
}
} catch { /* invalid bech32 */ }
}
// Try bech32 npub1... (pubkey only — no signing possible without privkey)
if (val.startsWith('npub1')) {
try {
const decoded = nip19Decode(val);
if (decoded.type === 'npub') {
console.info(`[nostr] Discovered npub key at localStorage['${key}'] (view-only)`);
return { pubkey: decoded.data, privkey: null };
}
} catch { /* invalid */ }
}
// Try raw hex privkey (64 chars)
if (/^[0-9a-f]{64}$/.test(val)) {
try {
const privBytes = hexToBytes(val);
const pubkeyHex = getPublicKey(privBytes);
_saveDiscoveredKeypair(pubkeyHex, val);
console.info(`[nostr] Discovered hex privkey at localStorage['${key}']`);
return { pubkey: pubkeyHex, privkey: val };
} catch { /* bad key */ }
}
// Try JSON objects with common shapes
if (val.startsWith('{')) {
try {
const obj = JSON.parse(val);
const nsec = obj.nsec || obj.privkey || obj.private_key || obj.secret;
const npub = obj.npub || obj.pubkey || obj.public_key;
if (nsec && nsec.startsWith('nsec1')) {
const decoded = nip19Decode(nsec);
if (decoded.type === 'nsec') {
const privBytes = decoded.data;
const pubkeyHex = getPublicKey(privBytes);
const privkeyHex = bytesToHex(privBytes);
_saveDiscoveredKeypair(pubkeyHex, privkeyHex);
console.info(`[nostr] Discovered nsec in JSON at localStorage['${key}']`);
return { pubkey: pubkeyHex, privkey: privkeyHex };
}
}
if (npub && npub.startsWith('npub1')) {
const decoded = nip19Decode(npub);
if (decoded.type === 'npub') {
return { pubkey: decoded.data, privkey: null };
}
}
if (typeof obj.pubkey === 'string' && /^[0-9a-f]{64}$/.test(obj.pubkey)) {
const pkey = obj.privkey ?? obj.private_key ?? null;
return { pubkey: obj.pubkey, privkey: pkey };
}
} catch { /* bad JSON */ }
}
} catch { /* localStorage access failed */ }
}
return null;
}
function _saveDiscoveredKeypair(pubkeyHex, privkeyHex) {
try {
localStorage.setItem(LS_KEYPAIR_KEY, JSON.stringify({ pubkey: pubkeyHex, privkey: privkeyHex }));
} catch { /* storage full */ }
}
function _loadKeypair() {
const found = _scanExistingNostrKeys();
if (found) {
// If only npub found (no privkey), store pubkey but signing won't work
// (the prompt will offer extension or key generation for signing)
return found.pubkey;
}
return null;
}
function _generateAndSaveKeypair() {

View File

@@ -94,9 +94,12 @@ async function _fetchEstimate(text) {
try {
const token = await getOrRefreshToken('/api');
const params = new URLSearchParams({ request: text });
if (token) params.set('nostr_token', token);
const fetchOpts = {};
if (token) {
fetchOpts.headers = { 'X-Nostr-Token': token };
}
const res = await fetch(`/api/estimate?${params}`);
const res = await fetch(`/api/estimate?${params}`, fetchOpts);
if (!res.ok) return;
const data = await res.json();