diff options
Diffstat (limited to 'src/nostr-login.js')
| -rw-r--r-- | src/nostr-login.js | 42 |
1 files changed, 37 insertions, 5 deletions
diff --git a/src/nostr-login.js b/src/nostr-login.js index fe60261..3357f61 100644 --- a/src/nostr-login.js +++ b/src/nostr-login.js @@ -324,7 +324,7 @@ let amberWatch = null; const amberListeners = new Set(); export const onAmberChange = (fn) => { amberListeners.add(fn); return () => amberListeners.delete(fn); }; const amberChanged = () => { for (const fn of amberListeners) { try { fn(); } catch {} } }; -export const amberPending = () => (amberReq ? { type: amberReq.type, since: amberReq.ts } : null); +export const amberPending = () => (amberReq ? { type: amberReq.type, since: amberReq.ts, needsTap: !!amberReq.needsTap } : null); const HEX64 = /^[0-9a-f]{64}$/i; // What an answer of this type looks like — anything else on the clipboard // is somebody's grocery list, not a signature. @@ -384,7 +384,10 @@ function amberWatchClipboard() { if (amberReq && amberDeliver(txt)) return; } catch {} // no permission: the paste box is the way in }; - const onVis = () => { if (document.visibilityState === 'visible') setTimeout(read, 250); }; + const onVis = () => { + if (document.visibilityState === 'hidden') { if (amberReq) amberReq.hidden = true; return; } // the signer came up + setTimeout(read, 250); + }; document.addEventListener('visibilitychange', onVis); window.addEventListener('focus', onVis); const iv = setInterval(read, 800); @@ -395,6 +398,35 @@ function amberLaunch(url) { if (typeof window !== 'undefined' && typeof window.__coinosAmberLaunch === 'function') { window.__coinosAmberLaunch(url); return; } // tests window.location.href = url; } +// Chrome lets a page open another app only on the heels of a tap: without +// one the `nostrsigner:` navigation is dropped with a console warning the +// page never sees ("Navigation is blocked"). The first request rides the +// Sign-in tap; the ones the login fires once an answer is back (decrypt the +// backup, sign the relay list…) have no tap behind them, so they wait for +// one — the card shows an Open button — instead of spinning forever. A +// launch that leaves the page in front is taken as blocked too. +export function amberOpen() { + const r = amberReq; + if (!r) return false; + r.needsTap = false; + r.hidden = false; + clearTimeout(r.timer); + r.timer = setTimeout(() => amberCancel('signer did not answer'), r.timeoutMs); + amberChanged(); + amberLaunch(r.url); + if (typeof window !== 'undefined' && typeof window.__coinosAmberLaunch === 'function') return true; // tests: no app to come up + setTimeout(() => { + if (amberReq !== r || r.hidden || typeof document === 'undefined' || document.visibilityState !== 'visible') return; + r.needsTap = true; + amberChanged(); + }, 2500); + return true; +} +const amberTapAvailable = () => { + if (typeof window !== 'undefined' && typeof window.__coinosAmberLaunch === 'function') return true; // tests + const ua = typeof navigator !== 'undefined' ? navigator.userActivation : null; + return ua ? ua.isActive : true; +}; // One trip at a time: the app fires several signatures at once right after a // sign-in (the backup, the relay list, the follows), and Amber can only be // asked for one; the rest wait their turn in this chain. @@ -404,11 +436,11 @@ function amberRequest(type, payload, params = {}, { timeoutMs = 180_000 } = {}) if (params.pubkey) q.set('pubkey', params.pubkey); const url = 'nostrsigner:' + encodeURIComponent(payload || '') + '?' + q.toString(); const run = () => new Promise((resolve, reject) => { - amberReq = { type, payload: payload || '', ts: Date.now(), resolve, reject, + amberReq = { type, payload: payload || '', url, ts: Date.now(), resolve, reject, timeoutMs, timer: setTimeout(() => amberCancel('signer did not answer'), timeoutMs) }; amberWatch = amberWatchClipboard(); - amberChanged(); - amberLaunch(url); + if (amberTapAvailable()) amberOpen(); + else { amberReq.needsTap = true; amberChanged(); } }); const p = amberQueue.then(run, run); amberQueue = p.catch(() => {}); |
