diff --git a/js/adminListStore.js b/js/adminListStore.js index e7437336..8e47e41b 100644 --- a/js/adminListStore.js +++ b/js/adminListStore.js @@ -10,9 +10,9 @@ import { isDevMode, } from "./config.js"; import { - nostrClient, + getRegisteredNostrClient, requestDefaultExtensionPermissions, -} from "./nostr.js"; +} from "./nostrClientRegistry.js"; import { devLogger, userLogger } from "./utils/logger.js"; import { buildAdminListEvent, @@ -40,6 +40,17 @@ function createError(code, message, cause) { return error; } +function requireNostrClient() { + const client = getRegisteredNostrClient(); + if (!client) { + throw createError( + "nostr-unavailable", + "Nostr relay pool is not initialized.", + ); + } + return client; +} + function normalizeNpub(value) { return typeof value === "string" ? value.trim() : ""; } @@ -350,10 +361,12 @@ function clearLegacyStorageFor(listKey) { } function ensureNostrReady() { - if (!nostrClient || !nostrClient.pool) { + const nostrClient = requireNostrClient(); + + if (!nostrClient.pool) { throw createError( "nostr-unavailable", - "Nostr relay pool is not initialized." + "Nostr relay pool is not initialized.", ); } @@ -578,6 +591,7 @@ function extractNpubsFromEvent(event) { } async function fetchLatestListEvent(filter, contextLabel = "admin-list") { + const nostrClient = requireNostrClient(); const relays = ensureNostrReady(); const normalizedFilter = {}; @@ -876,6 +890,7 @@ function publishListWithFirstAcceptance(pool, relays, event, options = {}) { } async function persistNostrState(actorNpub, updates = {}) { + const nostrClient = requireNostrClient(); ensureNostrReady(); const permissionResult = await requestDefaultExtensionPermissions(); if (!permissionResult.ok) { diff --git a/js/nostr.js b/js/nostr.js index 55d6491e..554f1b61 100644 --- a/js/nostr.js +++ b/js/nostr.js @@ -21,6 +21,10 @@ import { VIEW_FILTER_INCLUDE_LEGACY_VIDEO, } from "./constants.js"; import { accessControl } from "./accessControl.js"; +import { + registerNostrClient, + requestDefaultExtensionPermissions as requestRegisteredPermissions, +} from "./nostrClientRegistry.js"; // 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable import { deriveTitleFromEvent, magnetFromText } from "./videoEventUtils.js"; import { extractMagnetHints } from "./magnet.js"; @@ -12011,8 +12015,13 @@ function isNip04EncryptedWatchHistoryEvent(pointerEvent, ciphertext) { export const nostrClient = new NostrClient(); +registerNostrClient(nostrClient, { + requestPermissions: () => + nostrClient.ensureExtensionPermissions(DEFAULT_NIP07_PERMISSION_METHODS), +}); + export function requestDefaultExtensionPermissions() { - return nostrClient.ensureExtensionPermissions( + return requestRegisteredPermissions( DEFAULT_NIP07_PERMISSION_METHODS, ); } diff --git a/js/nostrClientRegistry.js b/js/nostrClientRegistry.js new file mode 100644 index 00000000..ab0cb7f4 --- /dev/null +++ b/js/nostrClientRegistry.js @@ -0,0 +1,50 @@ +// js/nostrClientRegistry.js + +let registeredNostrClient = null; +let requestPermissionsDelegate = null; + +export function registerNostrClient(client, options = {}) { + registeredNostrClient = client || null; + + if (typeof options.requestPermissions === "function") { + requestPermissionsDelegate = options.requestPermissions; + return; + } + + if ( + registeredNostrClient && + typeof registeredNostrClient.ensureExtensionPermissions === "function" + ) { + requestPermissionsDelegate = (...args) => + registeredNostrClient.ensureExtensionPermissions(...args); + return; + } + + requestPermissionsDelegate = null; +} + +export function getRegisteredNostrClient() { + return registeredNostrClient; +} + +export function clearNostrClientRegistration() { + registeredNostrClient = null; + requestPermissionsDelegate = null; +} + +export function requestDefaultExtensionPermissions(...args) { + if (typeof requestPermissionsDelegate === "function") { + try { + return requestPermissionsDelegate(...args); + } catch (error) { + return Promise.resolve({ ok: false, error }); + } + } + + const error = new Error("Nostr client is not registered."); + return Promise.resolve({ ok: false, error }); +} + +export const __nostrClientRegistryTestHooks = { + clearNostrClientRegistration, +}; diff --git a/js/utils/qrcode.js b/js/utils/qrcode.js index 21d3ebc3..f86daa2a 100644 --- a/js/utils/qrcode.js +++ b/js/utils/qrcode.js @@ -156,21 +156,25 @@ var QRCode; } // android 2.x doesn't support Data-URI spec - function _getAndroid() { - var android = false; - var sAgent = navigator.userAgent; - - if (/android/i.test(sAgent)) { // android - android = true; - var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i); - - if (aMat && aMat[1]) { - android = parseFloat(aMat[1]); - } - } - - return android; - } + function _getAndroid() { + if (typeof navigator === "undefined" || !navigator.userAgent) { + return false; + } + + var android = false; + var sAgent = navigator.userAgent; + + if (/android/i.test(sAgent)) { // android + android = true; + var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i); + + if (aMat && aMat[1]) { + android = parseFloat(aMat[1]); + } + } + + return android; + } var svgDrawer = (function() { @@ -279,26 +283,42 @@ var QRCode; this._elCanvas.style.display = "none"; } - // Android 2.1 bug workaround - // http://code.google.com/p/android/issues/detail?id=5141 - if (this._android && this._android <= 2.1) { - var factor = 1 / window.devicePixelRatio; - var drawImage = CanvasRenderingContext2D.prototype.drawImage; - CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { - if (("nodeName" in image) && /img/i.test(image.nodeName)) { - for (var i = arguments.length - 1; i >= 1; i--) { - arguments[i] = arguments[i] * factor; - } - } else if (typeof dw == "undefined") { - arguments[1] *= factor; - arguments[2] *= factor; - arguments[3] *= factor; - arguments[4] *= factor; - } - - drawImage.apply(this, arguments); - }; - } + // Android 2.1 bug workaround + // http://code.google.com/p/android/issues/detail?id=5141 + var legacyAndroidVersion = (function () { + try { + return _getAndroid(); + } catch (error) { + return false; + } + })(); + + if ( + legacyAndroidVersion && + legacyAndroidVersion <= 2.1 && + typeof CanvasRenderingContext2D !== "undefined" + ) { + var pixelRatio = + typeof window !== "undefined" && window.devicePixelRatio + ? window.devicePixelRatio + : 1; + var factor = pixelRatio ? 1 / pixelRatio : 1; + var drawImage = CanvasRenderingContext2D.prototype.drawImage; + CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { + if (("nodeName" in image) && /img/i.test(image.nodeName)) { + for (var i = arguments.length - 1; i >= 1; i--) { + arguments[i] = arguments[i] * factor; + } + } else if (typeof dw == "undefined") { + arguments[1] *= factor; + arguments[2] *= factor; + arguments[3] *= factor; + arguments[4] *= factor; + } + + drawImage.apply(this, arguments); + }; + } /** * Check whether the user's browser supports Data URI or not