feat(release): stage GitWorkshop and next node updates
This commit is contained in:
+351
-111
@@ -1,160 +1,400 @@
|
||||
/**
|
||||
* NIP-07 Nostr Provider Shim — Archipelago
|
||||
*
|
||||
* Provides window.nostr (NIP-07) for iframe apps.
|
||||
* Auto sign-in: does NIP-98 auth directly then reloads so the app
|
||||
* picks up the valid session. Shows a loading overlay during auth.
|
||||
* In an Archipelago iframe, requests go directly to the parent dashboard.
|
||||
* In a browser tab or companion WebView, a dashboard-origin signer frame
|
||||
* supplies the same identity picker and consent UI. No opener is required,
|
||||
* and private keys never leave the node backend.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
if (window.__archipelagoNostr) return;
|
||||
window.__archipelagoNostr = true;
|
||||
if (window === window.top) return;
|
||||
|
||||
var pending = {}, nextId = 1;
|
||||
var providerScript = document.currentScript;
|
||||
var autoNip98 = !(providerScript && providerScript.hasAttribute('data-no-nip98'));
|
||||
var embedded = window !== window.top;
|
||||
var pending = {}, nextId = 1, queuedMessages = [];
|
||||
var identitySelection = null;
|
||||
var selectedIdentity = null, identitySubscribers = [];
|
||||
var selectedPublicKey = null, selectedPublicKeyTimer = null;
|
||||
var signerFrame = null, signerReady = embedded, signerInitialised = embedded;
|
||||
var signerVisible = false, signerHideWaiters = [];
|
||||
var appReady = embedded || document.readyState === 'complete';
|
||||
|
||||
function dashboardOrigin() {
|
||||
var url = new URL(window.location.href);
|
||||
url.port = '';
|
||||
return url.origin;
|
||||
}
|
||||
|
||||
function inferAppId() {
|
||||
var configured = providerScript && providerScript.getAttribute('data-app-id');
|
||||
if (configured) return configured;
|
||||
var route = window.location.pathname.match(/^\/app\/([a-z0-9._-]+)(?:\/|$)/i);
|
||||
if (route) return route[1].toLowerCase();
|
||||
var ports = { '7778': 'indeedhub', '8337': 'archipelago-source' };
|
||||
return ports[window.location.port] || ('app-' + (window.location.port || 'dashboard'));
|
||||
}
|
||||
|
||||
function sendToSignerFrame(message) {
|
||||
if (!signerFrame || !signerFrame.contentWindow) return;
|
||||
signerFrame.contentWindow.postMessage(message, dashboardOrigin());
|
||||
}
|
||||
|
||||
function postToSigner(message) {
|
||||
if (embedded) {
|
||||
window.parent.postMessage(message, '*');
|
||||
return;
|
||||
}
|
||||
if (!signerFrame) createSignerFrame();
|
||||
// A loaded iframe is not yet an initialised signer. Requests that arrive
|
||||
// while the host app is still booting must follow signer-init, otherwise
|
||||
// the signer correctly rejects them because it has no app id/origin yet.
|
||||
if (!signerReady || !signerInitialised || !signerFrame || !signerFrame.contentWindow) {
|
||||
queuedMessages.push(message);
|
||||
return;
|
||||
}
|
||||
sendToSignerFrame(message);
|
||||
}
|
||||
|
||||
function setSignerVisible(visible) {
|
||||
if (!signerFrame) return;
|
||||
signerVisible = visible;
|
||||
signerFrame.style.display = 'block';
|
||||
signerFrame.style.visibility = 'visible';
|
||||
signerFrame.style.pointerEvents = visible ? 'auto' : 'none';
|
||||
signerFrame.style.opacity = visible ? '1' : '0';
|
||||
signerFrame.style.top = '0';
|
||||
signerFrame.style.left = '0';
|
||||
signerFrame.style.width = visible ? '100vw' : '1px';
|
||||
signerFrame.style.height = visible ? '100vh' : '1px';
|
||||
signerFrame.style.transform = visible ? 'none' : 'translate(-10000px, -10000px)';
|
||||
signerFrame.setAttribute('aria-hidden', visible ? 'false' : 'true');
|
||||
if (!visible && signerHideWaiters.length) {
|
||||
var waiters = signerHideWaiters.splice(0);
|
||||
waiters.forEach(function (resolve) { resolve(); });
|
||||
}
|
||||
}
|
||||
|
||||
// NIP-98 returns before the signer's short success animation has closed.
|
||||
// Reloading an Android WebView while that topmost cross-origin frame is
|
||||
// still visible can leave a blank compositor surface until the user reloads
|
||||
// again. Let the broker finish and hide first, with a bounded fallback so a
|
||||
// lost UI message can never prevent authentication from completing.
|
||||
function waitForSignerToHide() {
|
||||
if (embedded || !signerVisible) return Promise.resolve();
|
||||
return new Promise(function (resolve) {
|
||||
var settled = false;
|
||||
function finish() {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
resolve();
|
||||
}
|
||||
signerHideWaiters.push(finish);
|
||||
setTimeout(finish, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function createSignerFrame() {
|
||||
if (embedded || signerFrame) return;
|
||||
signerFrame = document.createElement('iframe');
|
||||
signerFrame.id = 'archipelago-nostr-signer';
|
||||
signerFrame.title = 'Archipelago Nostr signer';
|
||||
signerFrame.src = dashboardOrigin() + '/nostr-signer';
|
||||
// Keep the broker document alive between requests, but park its compositor
|
||||
// surface physically off-screen. Removing or display-hiding a full-screen
|
||||
// cross-origin iframe can leave Android WebView (and some mobile Chromium
|
||||
// builds) showing that stale black/grey surface until a manual refresh.
|
||||
// A 1px off-screen frame cannot obscure the app and also avoids reloading
|
||||
// the signer between getPublicKey/signEvent calls.
|
||||
signerFrame.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;transform:translate(-10000px,-10000px);border:0;z-index:2147483647;background:transparent;display:block;visibility:visible;opacity:0;pointer-events:none;';
|
||||
signerFrame.setAttribute('aria-hidden', 'true');
|
||||
document.documentElement.appendChild(signerFrame);
|
||||
}
|
||||
|
||||
function initialiseSignerWhenReady() {
|
||||
if (embedded || signerInitialised || !signerReady || !appReady) return;
|
||||
sendToSignerFrame({
|
||||
type: 'archipelago:signer-init',
|
||||
appId: inferAppId(),
|
||||
appName: (document.title || 'App').replace(/\s*[|—-]\s*Archipelago\s*$/i, ''),
|
||||
});
|
||||
signerInitialised = true;
|
||||
while (queuedMessages.length) sendToSignerFrame(queuedMessages.shift());
|
||||
}
|
||||
|
||||
function request(method, params) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var id = nextId++;
|
||||
pending[id] = { resolve: resolve, reject: reject };
|
||||
window.parent.postMessage({ type: 'nostr-request', id: id, method: method, params: params || {} }, '*');
|
||||
setTimeout(function () { if (pending[id]) { pending[id].reject(new Error('NIP-07 timeout')); delete pending[id]; } }, 30000);
|
||||
postToSigner({ type: 'nostr-request', id: id, method: method, params: params || {} });
|
||||
setTimeout(function () {
|
||||
if (pending[id]) {
|
||||
pending[id].reject(new Error('NIP-07 timeout'));
|
||||
delete pending[id];
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
// Archipelago-aware apps can call this immediately before an explicit login
|
||||
// action. Standard NIP-07 intentionally has no "choose account" method, so
|
||||
// getPublicKey() alone cannot distinguish a fresh login from a routine signer
|
||||
// call. Keeping this as an optional companion API preserves NIP-07 compatibility
|
||||
// while allowing users to change their node identity when they log in again.
|
||||
function selectIdentity() {
|
||||
if (identitySelection) {
|
||||
identitySelection.reject(new Error('A node identity choice is already open'));
|
||||
clearTimeout(identitySelection.timer);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var timer = setTimeout(function () {
|
||||
if (!identitySelection) return;
|
||||
identitySelection = null;
|
||||
reject(new Error('Identity selection timed out'));
|
||||
}, 30000);
|
||||
identitySelection = { resolve: resolve, reject: reject, timer: timer };
|
||||
postToSigner({
|
||||
type: embedded
|
||||
? 'archipelago:identity:request'
|
||||
: 'archipelago:signer-select-identity',
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finishIdentitySelection(identity) {
|
||||
// The identity picker is itself an explicit choice to disclose this key.
|
||||
// Keep it briefly so the login library's immediately-following
|
||||
// getPublicKey() does not depend on another cross-origin WebView round trip.
|
||||
// This is deliberately one-shot and short-lived.
|
||||
if (identity && typeof identity.nostr_pubkey === 'string' && identity.nostr_pubkey) {
|
||||
selectedIdentity = { nostr_pubkey: identity.nostr_pubkey };
|
||||
selectedPublicKey = identity.nostr_pubkey;
|
||||
clearTimeout(selectedPublicKeyTimer);
|
||||
selectedPublicKeyTimer = setTimeout(function () {
|
||||
selectedPublicKey = null;
|
||||
selectedPublicKeyTimer = null;
|
||||
}, 15000);
|
||||
identitySubscribers.slice().forEach(function (subscriber) {
|
||||
try { subscriber(selectedIdentity); } catch (error) {
|
||||
console.error('[nostr-provider] identity listener failed:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!identitySelection) return;
|
||||
var selection = identitySelection;
|
||||
identitySelection = null;
|
||||
clearTimeout(selection.timer);
|
||||
selection.resolve(identity);
|
||||
}
|
||||
|
||||
function cancelIdentitySelection() {
|
||||
if (!identitySelection) return;
|
||||
var selection = identitySelection;
|
||||
identitySelection = null;
|
||||
clearTimeout(selection.timer);
|
||||
selection.reject(new Error('Identity selection cancelled'));
|
||||
}
|
||||
|
||||
function getPublicKey() {
|
||||
// Most NIP-07 apps call getPublicKey directly from their login button. A
|
||||
// live user activation lets the node offer account switching to those apps
|
||||
// without making background account restoration reopen the picker. Apps
|
||||
// with an async login flow should call archipelagoNostr.selectIdentity()
|
||||
// explicitly; its result is consumed here so the picker is not shown twice.
|
||||
if (selectedPublicKey) {
|
||||
var publicKey = selectedPublicKey;
|
||||
selectedPublicKey = null;
|
||||
clearTimeout(selectedPublicKeyTimer);
|
||||
selectedPublicKeyTimer = null;
|
||||
return Promise.resolve(publicKey);
|
||||
}
|
||||
if (navigator.userActivation && navigator.userActivation.isActive) {
|
||||
return selectIdentity().then(function () {
|
||||
return getPublicKey();
|
||||
});
|
||||
}
|
||||
return request('getPublicKey');
|
||||
}
|
||||
|
||||
// Framework components often mount just after the provider receives the
|
||||
// eager first-launch identity. A sticky subscription prevents that choice
|
||||
// from being lost between window.load and React/Vue effect registration.
|
||||
function onIdentitySelected(subscriber) {
|
||||
if (typeof subscriber !== 'function') {
|
||||
throw new TypeError('Identity subscriber must be a function');
|
||||
}
|
||||
identitySubscribers.push(subscriber);
|
||||
if (selectedIdentity) {
|
||||
try { subscriber(selectedIdentity); } catch (error) {
|
||||
console.error('[nostr-provider] identity listener failed:', error);
|
||||
}
|
||||
}
|
||||
return function () {
|
||||
identitySubscribers = identitySubscribers.filter(function (entry) {
|
||||
return entry !== subscriber;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getSelectedIdentity() {
|
||||
return selectedIdentity && { nostr_pubkey: selectedIdentity.nostr_pubkey };
|
||||
}
|
||||
|
||||
window.addEventListener('message', function (e) {
|
||||
if (!e.data || e.data.type !== 'nostr-response') return;
|
||||
var h = pending[e.data.id]; if (!h) return; delete pending[e.data.id];
|
||||
e.data.error ? h.reject(new Error(e.data.error)) : h.resolve(e.data.result);
|
||||
var validSource = embedded
|
||||
? e.source === window.parent
|
||||
: signerFrame && e.source === signerFrame.contentWindow && e.origin === dashboardOrigin();
|
||||
if (!validSource || !e.data) return;
|
||||
|
||||
if (!embedded && e.data.type === 'archipelago:signer-ready') {
|
||||
signerReady = true;
|
||||
initialiseSignerWhenReady();
|
||||
return;
|
||||
}
|
||||
if (!embedded && e.data.type === 'archipelago:signer-show') {
|
||||
setSignerVisible(true);
|
||||
return;
|
||||
}
|
||||
if (!embedded && e.data.type === 'archipelago:signer-hide') {
|
||||
setSignerVisible(false);
|
||||
return;
|
||||
}
|
||||
if (!embedded && e.data.type === 'archipelago:signer-identity') {
|
||||
finishIdentitySelection(e.data.identity);
|
||||
window.postMessage({
|
||||
type: 'archipelago:identity',
|
||||
nostr_pubkey: e.data.identity && e.data.identity.nostr_pubkey,
|
||||
}, window.location.origin);
|
||||
return;
|
||||
}
|
||||
if (embedded && e.data.type === 'archipelago:identity') {
|
||||
finishIdentitySelection(e.data);
|
||||
return;
|
||||
}
|
||||
if (e.data.type === 'archipelago:identity-cancelled' ||
|
||||
e.data.type === 'archipelago:signer-identity-cancelled') {
|
||||
cancelIdentitySelection();
|
||||
return;
|
||||
}
|
||||
if (e.data.type !== 'nostr-response') return;
|
||||
var handler = pending[e.data.id];
|
||||
if (!handler) return;
|
||||
delete pending[e.data.id];
|
||||
e.data.error ? handler.reject(new Error(e.data.error)) : handler.resolve(e.data.result);
|
||||
});
|
||||
|
||||
window.nostr = {
|
||||
getPublicKey: function () { return request('getPublicKey'); },
|
||||
signEvent: function (ev) { return request('signEvent', { event: ev }); },
|
||||
sign: function (ev) { return request('signEvent', { event: ev }); },
|
||||
getPublicKey: getPublicKey,
|
||||
signEvent: function (event) { return request('signEvent', { event: event }); },
|
||||
sign: function (event) { return request('signEvent', { event: event }); },
|
||||
getRelays: function () { return request('getRelays'); },
|
||||
nip04: {
|
||||
encrypt: function (pk, pt) { return request('nip04.encrypt', { pubkey: pk, plaintext: pt }); },
|
||||
decrypt: function (pk, ct) { return request('nip04.decrypt', { pubkey: pk, ciphertext: ct }); },
|
||||
encrypt: function (pubkey, plaintext) { return request('nip04.encrypt', { pubkey: pubkey, plaintext: plaintext }); },
|
||||
decrypt: function (pubkey, ciphertext) { return request('nip04.decrypt', { pubkey: pubkey, ciphertext: ciphertext }); },
|
||||
},
|
||||
nip44: {
|
||||
encrypt: function (pk, pt) { return request('nip44.encrypt', { pubkey: pk, plaintext: pt }); },
|
||||
decrypt: function (pk, ct) { return request('nip44.decrypt', { pubkey: pk, ciphertext: ct }); },
|
||||
encrypt: function (pubkey, plaintext) { return request('nip44.encrypt', { pubkey: pubkey, plaintext: plaintext }); },
|
||||
decrypt: function (pubkey, ciphertext) { return request('nip44.decrypt', { pubkey: pubkey, ciphertext: ciphertext }); },
|
||||
},
|
||||
};
|
||||
|
||||
// --- Loading Overlay ---
|
||||
var overlay = null;
|
||||
window.archipelagoNostr = {
|
||||
selectIdentity: selectIdentity,
|
||||
onIdentitySelected: onIdentitySelected,
|
||||
getSelectedIdentity: getSelectedIdentity,
|
||||
};
|
||||
|
||||
function showLoader(message) {
|
||||
if (overlay) return;
|
||||
overlay = document.createElement('div');
|
||||
overlay.id = 'archipelago-auth-overlay';
|
||||
overlay.innerHTML =
|
||||
'<div style="display:flex;flex-direction:column;align-items:center;gap:16px;">' +
|
||||
'<svg width="40" height="40" viewBox="0 0 24 24" fill="none" style="animation:archy-spin 1s linear infinite">' +
|
||||
'<circle cx="12" cy="12" r="10" stroke="rgba(255,255,255,0.2)" stroke-width="3"/>' +
|
||||
'<path d="M12 2a10 10 0 019.95 9" stroke="#fb923c" stroke-width="3" stroke-linecap="round"/>' +
|
||||
'</svg>' +
|
||||
'<div style="color:rgba(255,255,255,0.9);font:500 14px/1.4 -apple-system,system-ui,sans-serif">' + (message || 'Signing in...') + '</div>' +
|
||||
'</div>';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.7);backdrop-filter:blur(8px);';
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '@keyframes archy-spin{to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function updateLoader(message) {
|
||||
if (!overlay) return;
|
||||
var txt = overlay.querySelector('div > div');
|
||||
if (txt) txt.textContent = message;
|
||||
}
|
||||
|
||||
function hideLoader() {
|
||||
if (overlay) { overlay.remove(); overlay = null; }
|
||||
}
|
||||
|
||||
// --- Direct NIP-98 Auth ---
|
||||
// Optional direct NIP-98 session bootstrap for apps that use it. Signing
|
||||
// itself is shown by the shared broker, so this deliberately adds no second
|
||||
// full-screen loader inside the app.
|
||||
var authDone = false;
|
||||
|
||||
function doNip98Auth(pubkey) {
|
||||
if (authDone) return;
|
||||
authDone = true;
|
||||
var healthUrl = window.location.origin + '/api/nostr-auth/health';
|
||||
var sessionUrl = window.location.origin + '/api/auth/nostr/session';
|
||||
var healthController = new AbortController();
|
||||
var healthTimeout = setTimeout(function () { healthController.abort(); }, 3000);
|
||||
|
||||
var apiBase = '/api';
|
||||
var healthUrl = window.location.origin + apiBase + '/nostr-auth/health';
|
||||
var sessionUrl = window.location.origin + apiBase + '/auth/nostr/session';
|
||||
|
||||
// 1. Check if API backend is reachable (3s timeout)
|
||||
var hc = new AbortController();
|
||||
var ht = setTimeout(function () { hc.abort(); }, 3000);
|
||||
|
||||
fetch(healthUrl, { signal: hc.signal }).then(function (r) {
|
||||
clearTimeout(ht);
|
||||
if (!r.ok) throw new Error('Health ' + r.status);
|
||||
|
||||
// 2. API is up — show loader and do NIP-98
|
||||
showLoader('Signing in with Nostr...');
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var event = {
|
||||
kind: 27235, created_at: now, content: '', pubkey: pubkey,
|
||||
tags: [['u', sessionUrl], ['method', 'POST']]
|
||||
};
|
||||
console.log('[nostr-provider] NIP-98: signing for', sessionUrl);
|
||||
return window.nostr.signEvent(event);
|
||||
|
||||
fetch(healthUrl, { signal: healthController.signal }).then(function (response) {
|
||||
clearTimeout(healthTimeout);
|
||||
if (!response.ok) throw new Error('Health ' + response.status);
|
||||
return window.nostr.signEvent({
|
||||
kind: 27235,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: '',
|
||||
pubkey: pubkey,
|
||||
tags: [['u', sessionUrl], ['method', 'POST']],
|
||||
});
|
||||
}).then(function (signed) {
|
||||
updateLoader('Creating session...');
|
||||
var ac = new AbortController();
|
||||
setTimeout(function () { ac.abort(); }, 10000);
|
||||
var controller = new AbortController();
|
||||
setTimeout(function () { controller.abort(); }, 10000);
|
||||
return fetch(sessionUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Nostr ' + btoa(JSON.stringify(signed)) },
|
||||
signal: ac.signal
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
}).then(function (res) {
|
||||
console.log('[nostr-provider] NIP-98: response', res.status);
|
||||
if (!res.ok) throw new Error('Auth failed: ' + res.status);
|
||||
return res.json();
|
||||
|
||||
}).then(function (response) {
|
||||
if (!response.ok) throw new Error('Auth failed: ' + response.status);
|
||||
return response.json();
|
||||
}).then(function (data) {
|
||||
if (data.accessToken) {
|
||||
sessionStorage.setItem('nostr_token', data.accessToken);
|
||||
sessionStorage.setItem('nostr_pubkey', pubkey);
|
||||
if (data.refreshToken) sessionStorage.setItem('refresh_token', data.refreshToken);
|
||||
updateLoader('Signed in! Loading...');
|
||||
console.log('[nostr-provider] NIP-98: success, reloading...');
|
||||
setTimeout(function () { window.location.reload(); }, 400);
|
||||
} else {
|
||||
hideLoader(); authDone = false;
|
||||
}
|
||||
|
||||
}).catch(function (err) {
|
||||
hideLoader(); authDone = false;
|
||||
var msg = err.message || String(err);
|
||||
if (msg.indexOf('abort') > -1) msg = 'API timeout';
|
||||
console.warn('[nostr-provider] NIP-98 skipped:', msg);
|
||||
if (!data.accessToken) throw new Error('Authentication returned no access token');
|
||||
sessionStorage.setItem('nostr_token', data.accessToken);
|
||||
sessionStorage.setItem('nostr_pubkey', pubkey);
|
||||
if (data.refreshToken) sessionStorage.setItem('refresh_token', data.refreshToken);
|
||||
return waitForSignerToHide().then(function () {
|
||||
// Give WebView one paint after the iframe is hidden before replacing
|
||||
// the document. The stored session is already durable at this point.
|
||||
return new Promise(function (resolve) {
|
||||
window.requestAnimationFrame(function () {
|
||||
window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
}).then(function () {
|
||||
if (window.ArchipelagoSurface &&
|
||||
typeof window.ArchipelagoSurface.expectPageTransition === 'function') {
|
||||
window.ArchipelagoSurface.expectPageTransition();
|
||||
}
|
||||
window.location.reload();
|
||||
});
|
||||
}).catch(function (error) {
|
||||
authDone = false;
|
||||
var message = error && error.message ? error.message : String(error);
|
||||
if (message.toLowerCase().indexOf('abort') > -1) message = 'API timeout';
|
||||
console.warn('[nostr-provider] NIP-98 skipped:', message);
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for identity from parent Archipelago frame
|
||||
window.addEventListener('message', function (e) {
|
||||
if (!e.data || e.data.type !== 'archipelago:identity') return;
|
||||
var pk = e.data.nostr_pubkey;
|
||||
console.log('[nostr-provider] Identity received:', pk ? pk.slice(0, 12) + '...' : 'none');
|
||||
if (!pk) return;
|
||||
|
||||
// Skip if already signed in with a real token (not mock)
|
||||
if (!e.data || e.data.type !== 'archipelago:identity' || !autoNip98) return;
|
||||
if (e.source !== window && e.source !== window.parent) return;
|
||||
var pubkey = e.data.nostr_pubkey;
|
||||
if (!pubkey) return;
|
||||
try {
|
||||
var token = sessionStorage.getItem('nostr_token');
|
||||
if (token && token.indexOf('mock-') === -1) {
|
||||
console.log('[nostr-provider] Already signed in with real token');
|
||||
return;
|
||||
}
|
||||
} catch (x) {}
|
||||
|
||||
setTimeout(function () { doNip98Auth(pk); }, 1500);
|
||||
if (token && token.indexOf('mock-') === -1) return;
|
||||
} catch (_) {}
|
||||
setTimeout(function () { doNip98Auth(pubkey); }, 1500);
|
||||
});
|
||||
|
||||
// Only identity-aware apps open the chooser eagerly. The provider is also
|
||||
// injected into several ordinary app proxies; those stay untouched unless
|
||||
// they actually invoke a NIP-07 method, which lazily creates the broker.
|
||||
if (!embedded && ['indeedhub', 'nostrudel', 'archipelago-source'].indexOf(inferAppId()) !== -1) {
|
||||
createSignerFrame();
|
||||
}
|
||||
|
||||
// The provider is injected in <head>, before framework startup. Waiting for
|
||||
// load makes the first-launch picker meaningful: React/Vue login listeners
|
||||
// and account stores exist before a fast identity choice can be emitted.
|
||||
if (!embedded && !appReady) {
|
||||
window.addEventListener('load', function () {
|
||||
appReady = true;
|
||||
initialiseSignerWhenReady();
|
||||
}, { once: true });
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user