aboutsummaryrefslogtreecommitdiffstats
path: root/src/nostr-login.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr-login.js')
-rw-r--r--src/nostr-login.js42
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(() => {});