feat(wallet): Bitcoin Connect NWC "Connect wallet" flow [LOCAL — flag ON, do not push as-is]

Phase 0+1 of docs/bitcoin-connect-plan.md (#61). Adds Alby's Bitcoin Connect as a
one-tap NWC connect front-end that feeds bitvid's EXISTING wallet pipeline; manual
URI entry stays as the backup.

- Vendored: pinned @getalby/[email protected].3 bundled by
  scripts/build-bitcoin-connect.mjs → vendor/bitcoin-connect.bundle.min.js (esbuild,
  browser ESM). crypto-shim aliases the L402-only `import('crypto')` bitvid never
  calls. `npm run build:bitcoin-connect` rebuilds it.
- Flag: FEATURE_BITCOIN_CONNECT wired like FEATURE_AUDIO_INGEST
  (instance-config → config → constants).
- UI: a "Connect wallet" button in the Wallet Connect pane (hidden unless the flag
  is on); the manual URI field stays below as the backup.
- Controller: ProfileWalletController.handleBitcoinConnect() lazy-imports the bundle
  (only on click — the ~700KB never touches the main bundle), inits NWC-only with
  least-privilege requestMethods and persistConnection:false, then on onConnected
  extracts provider.client.nostrWalletConnectUrl → the SAME validate→save path as
  the manual field → disconnect() so bitvid solely holds the spending secret. The
  URI is never logged.

NOTE: config/instance-config.js FEATURE_BITCOIN_CONNECT is TRUE for local testing
only. Reset to false (shipping default) before pushing/deploying. Local commit;
not pushed, not deployed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-09 14:49:37 -04:00
1 parent 6dd40ce330
commit 69e87c6e15
12 files changed
+1663 -3

No files matched your search

+24
View File
@@ -860,6 +860,30 @@
<p class="text-sm text-muted">
Connect a Nostr Wallet Connect (NWC) compatible wallet to zap creators directly from bitvid.
</p>
<div id="profileWalletBitcoinConnectRow" class="hidden space-y-2">
<button
id="profileWalletBitcoinConnect"
type="button"
class="btn focus-ring inline-flex items-center gap-2"
>
<svg
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z"></path>
</svg>
Connect wallet
</button>
<p class="text-xs text-muted">
One-tap connect via Bitcoin Connect (NWC) — or paste a connection manually below.
</p>
</div>
<div class="space-y-4">
<label class="block text-sm text-muted-strong" for="profileWalletUri">
<span class="mb-2 block font-medium">Wallet Connect URI</span>
+12
View File
@@ -264,6 +264,18 @@ export const FEATURE_NIP71_INGEST = true;
*/
export const FEATURE_AUDIO_INGEST = false;
/**
* Whether this deployment shows the "Connect wallet" button that uses Alby's
* Bitcoin Connect for a smooth NWC connect flow (docs/bitcoin-connect-plan.md,
* TODO #61). When on, the Wallet Connect pane offers Bitcoin Connect alongside
* the manual URI field (which stays as the backup). It only lazy-loads the
* vendored Bitcoin Connect bundle when the user actually clicks Connect.
*
* SHIPPING DEFAULT should be `false` ("off = no trace"). Currently `true` for
* local testing only — reset to `false` before pushing/shipping until verified.
*/
export const FEATURE_BITCOIN_CONNECT = true;
/**
* Card liveness visibility policy — what a non-owner video card does while its
* CDN/WebTorrent liveness probes are still running:
+2
View File
@@ -26,6 +26,7 @@ import {
ALLOW_NSFW_CONTENT,
FEATURE_NIP71_INGEST,
FEATURE_AUDIO_INGEST,
FEATURE_BITCOIN_CONNECT,
CARD_LIVENESS_POLICY,
LIVENESS_PROBE_PREFETCH_MARGIN,
DEFAULT_TRUST_SEED_NPUBS,
@@ -123,6 +124,7 @@ export const ADMIN_COMMUNITY_BLACKLIST_PREFIX = "community:blacklist";
export { ALLOW_NSFW_CONTENT };
export { FEATURE_NIP71_INGEST };
export { FEATURE_AUDIO_INGEST };
export { FEATURE_BITCOIN_CONNECT };
export { CARD_LIVENESS_POLICY };
export { LIVENESS_PROBE_PREFETCH_MARGIN };
export { DEFAULT_TRUST_SEED_NPUBS };
+10
View File
@@ -8,6 +8,7 @@ import {
DEFAULT_PLAYBACK_START_TIMEOUT,
FEATURE_NIP71_INGEST as CONFIG_FEATURE_NIP71_INGEST,
FEATURE_AUDIO_INGEST as CONFIG_FEATURE_AUDIO_INGEST,
FEATURE_BITCOIN_CONNECT as CONFIG_FEATURE_BITCOIN_CONNECT,
CARD_LIVENESS_POLICY as CONFIG_CARD_LIVENESS_POLICY,
LIVENESS_PROBE_PREFETCH_MARGIN as CONFIG_LIVENESS_PROBE_PREFETCH_MARGIN,
IS_DEV_MODE,
@@ -199,6 +200,9 @@ const DEFAULT_FLAGS = Object.freeze({
// Default OFF ("off = no trace") — only an explicit `true` in the instance
// config lights it up.
FEATURE_AUDIO_INGEST: CONFIG_FEATURE_AUDIO_INGEST === true,
// Bitcoin Connect NWC "Connect wallet" flow (docs/bitcoin-connect-plan.md, #61).
// Default OFF; only an explicit `true` in the instance config enables it.
FEATURE_BITCOIN_CONNECT: CONFIG_FEATURE_BITCOIN_CONNECT === true,
FEATURE_HASHTAG_PREFERENCES: false,
FEATURE_TRUST_SEEDS: true, // Rollback: disable to drop baseline trust seeds without code changes.
FEATURE_TRUSTED_HIDE_CONTROLS: true,
@@ -226,6 +230,7 @@ const runtimeFlags = (() => {
FEATURE_NIP71_MIRROR: DEFAULT_FLAGS.FEATURE_NIP71_MIRROR,
FEATURE_NIP71_INGEST: DEFAULT_FLAGS.FEATURE_NIP71_INGEST,
FEATURE_AUDIO_INGEST: DEFAULT_FLAGS.FEATURE_AUDIO_INGEST,
FEATURE_BITCOIN_CONNECT: DEFAULT_FLAGS.FEATURE_BITCOIN_CONNECT,
FEATURE_HASHTAG_PREFERENCES: DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES,
FEATURE_TRUST_SEEDS: DEFAULT_FLAGS.FEATURE_TRUST_SEEDS,
FEATURE_TRUSTED_HIDE_CONTROLS: DEFAULT_FLAGS.FEATURE_TRUSTED_HIDE_CONTROLS,
@@ -276,6 +281,11 @@ export let FEATURE_AUDIO_INGEST = coerceBoolean(
DEFAULT_FLAGS.FEATURE_AUDIO_INGEST
);
export let FEATURE_BITCOIN_CONNECT = coerceBoolean(
runtimeFlags.FEATURE_BITCOIN_CONNECT,
DEFAULT_FLAGS.FEATURE_BITCOIN_CONNECT
);
export let FEATURE_HASHTAG_PREFERENCES = coerceBoolean(
runtimeFlags.FEATURE_HASHTAG_PREFERENCES,
DEFAULT_FLAGS.FEATURE_HASHTAG_PREFERENCES
@@ -1,6 +1,13 @@
import { devLogger } from "../../utils/logger.js";
import { FEATURE_BITCOIN_CONNECT } from "../../constants.js";
import { showConfirm } from "../confirmDialog.js";
import { NWC_URI_SCHEME } from "../profileModalContract.js";
// Vendored, pinned Bitcoin Connect bundle (scripts/build-bitcoin-connect.mjs).
// Lazy-imported only when the user clicks "Connect wallet", so its ~700KB never
// weighs on the main bundle. See docs/bitcoin-connect-plan.md.
const BITCOIN_CONNECT_BUNDLE_URL =
"../../../vendor/bitcoin-connect.bundle.min.js";
import { createWalletSyncService } from "../../services/walletSyncService.js";
import { runWalletZappabilityCheck } from "./walletZappabilityCheck.js";
import {
@@ -32,6 +39,11 @@ export class ProfileWalletController {
this.walletSyncRestoreBtn = null;
this.walletSyncStatus = null;
this._walletSyncService = null;
this.walletBitcoinConnectRow = null;
this.walletBitcoinConnectButton = null;
this._bitcoinConnectModulePromise = null;
this._bitcoinConnectInitialized = false;
}
cacheDomReferences() {
@@ -46,9 +58,27 @@ export class ProfileWalletController {
this.walletSyncToggle = document.getElementById("walletSyncToggle") || null;
this.walletSyncRestoreBtn = document.getElementById("walletSyncRestoreBtn") || null;
this.walletSyncStatus = document.getElementById("walletSyncStatus") || null;
this.walletBitcoinConnectRow =
document.getElementById("profileWalletBitcoinConnectRow") || null;
this.walletBitcoinConnectButton =
document.getElementById("profileWalletBitcoinConnect") || null;
// Backwards compatibility alias if needed by tests/external code
this.mainController.profileWalletStatusText = this.walletStatusText;
this.applyBitcoinConnectVisibility();
}
// Reveal the "Connect wallet" button only when the deployment enables the flag.
// Off ⇒ the row stays hidden and the vendored bundle is never imported.
applyBitcoinConnectVisibility() {
if (!(this.walletBitcoinConnectRow instanceof HTMLElement)) {
return;
}
this.walletBitcoinConnectRow.classList.toggle(
"hidden",
FEATURE_BITCOIN_CONNECT !== true,
);
}
registerEventListeners() {
@@ -90,6 +120,15 @@ export class ProfileWalletController {
});
}
if (
FEATURE_BITCOIN_CONNECT === true &&
this.walletBitcoinConnectButton instanceof HTMLElement
) {
this.walletBitcoinConnectButton.addEventListener("click", () => {
void this.handleBitcoinConnect();
});
}
if (this.walletSyncToggle instanceof HTMLElement) {
this.walletSyncToggle.addEventListener("change", () => {
void this.handleToggleSync();
@@ -521,6 +560,104 @@ export class ProfileWalletController {
return { valid: true, sanitized: value };
}
// Lazy-load + one-time init of the vendored Bitcoin Connect bundle. Restricts
// the modal to NWC and requests only the methods bitvid actually calls. Does
// NOT let Bitcoin Connect persist the connection — bitvid owns the secret.
async ensureBitcoinConnectModule() {
if (this._bitcoinConnectModulePromise) {
return this._bitcoinConnectModulePromise;
}
this._bitcoinConnectModulePromise = (async () => {
const bc = await import(BITCOIN_CONNECT_BUNDLE_URL);
if (!this._bitcoinConnectInitialized) {
bc.init({
appName: "bitvid",
filters: ["nwc"],
persistConnection: false,
providerConfig: {
nwc: {
authorizationUrlOptions: {
requestMethods: [
"pay_invoice",
"get_balance",
"make_invoice",
"lookup_invoice",
],
},
},
},
});
// One persistent handler: extract the NWC URI, hand it to the SAME
// validate→save path as the manual field, then disconnect so bitvid is
// the only holder of the spending secret. NEVER log the URI.
bc.onConnected(async (provider) => {
const uri =
typeof provider?.client?.nostrWalletConnectUrl === "string"
? provider.client.nostrWalletConnectUrl
: "";
try {
if (uri && this.walletUriInput instanceof HTMLElement) {
this.setSecretInputValue(this.walletUriInput, uri);
await this.handleWalletSave();
} else if (!uri) {
this.updateWalletStatus(
"That wallet didn't expose an NWC connection. Paste one manually below.",
"error",
);
}
} catch (error) {
devLogger.error("[wallet] Bitcoin Connect save failed", error);
} finally {
try {
bc.disconnect();
} catch (disconnectError) {
devLogger.warn(
"[wallet] Bitcoin Connect disconnect failed",
disconnectError,
);
}
try {
bc.closeModal?.();
} catch (closeError) {
// non-fatal
}
}
});
this._bitcoinConnectInitialized = true;
}
return bc;
})().catch((error) => {
// Reset so a later click can retry a failed load.
this._bitcoinConnectModulePromise = null;
throw error;
});
return this._bitcoinConnectModulePromise;
}
async handleBitcoinConnect() {
if (FEATURE_BITCOIN_CONNECT !== true || this.isWalletBusy()) {
return;
}
let bc;
try {
bc = await this.ensureBitcoinConnectModule();
} catch (error) {
devLogger.error("[wallet] Failed to load Bitcoin Connect", error);
this.updateWalletStatus(
"Couldn't load the wallet connector. Try again, or paste a connection manually.",
"error",
);
this.mainController.showError?.("Couldn't load the wallet connector.");
return;
}
try {
bc.launchModal();
} catch (error) {
devLogger.error("[wallet] Bitcoin Connect launch failed", error);
this.updateWalletStatus("Couldn't open the wallet connector.", "error");
}
}
async handleWalletSave() {
const { uri, defaultZap, error } = this.getWalletFormValues();
const context = {
+112 -3
View File
@@ -20,6 +20,7 @@
"ws": "^8.19.0"
},
"devDependencies": {
"@getalby/bitcoin-connect": "3.12.3",
"@playwright/test": "^1.58.2",
"cssnano": "^7.1.2",
"fake-indexeddb": "^6.2.5",
@@ -832,6 +833,52 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@getalby/bitcoin-connect": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect/-/bitcoin-connect-3.12.3.tgz",
"integrity": "sha512-2d+tNHBCZBoImKJgZwTUOyK9ypXaTg/hwvtQ8EHbgSKKkuycOrks7SzlUc0iJHthD3JGFVMWZEgOgw4AQVVE4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@getalby/lightning-tools": "^8.1.1",
"@getalby/sdk": "^8.0.3",
"@lightninglabs/lnc-web": "^0.3.6-alpha",
"qrcode-generator": "1.4.4",
"zustand": "^5.0.13"
}
},
"node_modules/@getalby/lightning-tools": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-8.2.0.tgz",
"integrity": "sha512-eL+cnHyzeUARKVzNRuFBRBppAU5RBJOLjhFVzSWt8hDibRzL5+e4hq8I79+RGHfrWWMUDv382Jx8ewOazrBj7w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"type": "lightning",
"url": "lightning:[email protected]"
}
},
"node_modules/@getalby/sdk": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@getalby/sdk/-/sdk-8.0.3.tgz",
"integrity": "sha512-vPEogAWwLHbL55COeXrRN7yRBXMDGDxX1M2A+o3Lqw60FFng6fa9FK9sw8ptdILMD1dQZq8FzPXuabQ7seoDBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@getalby/lightning-tools": "^8.1.1",
"nostr-tools": "^2.23.3"
},
"engines": {
"node": ">=22"
},
"funding": {
"type": "lightning",
"url": "lightning:[email protected]"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -891,6 +938,24 @@
"dev": true,
"license": "MIT"
},
"node_modules/@lightninglabs/lnc-core": {
"version": "0.3.6-alpha",
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.6-alpha.tgz",
"integrity": "sha512-ObzY5wi+PS0GQduFHYDjGJpYZxKD27vtMf3fUVne2gGNMej1mvvJAOwATgef8YV/IZodAzy73Z/F2zfaQ+iE4Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@lightninglabs/lnc-web": {
"version": "0.3.6-alpha",
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.6-alpha.tgz",
"integrity": "sha512-oiL1mKlbEjb1lcmNAHA0Gn+mlx5oQOJ8YVONJR/vgo4QLmL/48EyA12QbZ71mP9li45hGOoGqpYMRwBYtIeFog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@lightninglabs/lnc-core": "^0.3.6-alpha",
"crypto-js": "4.2.0"
}
},
"node_modules/@noble/ciphers": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz",
@@ -2321,6 +2386,13 @@
"node": ">= 8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"dev": true,
"license": "MIT"
},
"node_modules/css-declaration-sorter": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz",
@@ -3789,9 +3861,9 @@
}
},
"node_modules/nostr-tools": {
"version": "2.23.1",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.23.1.tgz",
"integrity": "sha512-Q5SJ1omrseBFXtLwqDhufpFLA6vX3rS/IuBCc974qaYX6YKGwEPxa/ZsyxruUOr+b+5EpWL2hFmCB5AueYrfBw==",
"version": "2.23.9",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.23.9.tgz",
"integrity": "sha512-PBN3TpGh+EszlTZWpQdvcqEMqhcKcY1vTw0Xkkccg+TyP7y/icu9/iXJw72aFZ/ETm35ehU0hneGTXLaTSkImw==",
"license": "Unlicense",
"dependencies": {
"@noble/ciphers": "2.1.1",
@@ -5046,6 +5118,13 @@
"node": ">=20"
}
},
"node_modules/qrcode-generator": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
"integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==",
"dev": true,
"license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -6738,6 +6817,36 @@
"engines": {
"node": ">=8"
}
},
"node_modules/zustand": {
"version": "5.0.14",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz",
"integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}
+2
View File
@@ -14,6 +14,7 @@
"build:css": "postcss css/tailwind.source.css -o css/tailwind.generated.css",
"build:beacon:bundle": "node scripts/build-beacon.mjs",
"build:beacon": "npm run build:beacon:bundle && npm run lint:inline-styles",
"build:bitcoin-connect": "node scripts/build-bitcoin-connect.mjs",
"ci:beacon": "npm run build:beacon",
"format": "prettier --cache --write css/tailwind.source.css css/tokens.css css/docs.css \"docs/**/*.html\" tailwind.config.cjs README.md CONTRIBUTING.md",
"audit": "node scripts/daily-design-system-audit.mjs",
@@ -57,6 +58,7 @@
"lint:pool-access": "node scripts/check-direct-pool-access.mjs"
},
"devDependencies": {
"@getalby/bitcoin-connect": "3.12.3",
"@playwright/test": "^1.58.2",
"cssnano": "^7.1.2",
"fake-indexeddb": "^6.2.5",
+51
View File
@@ -0,0 +1,51 @@
// Bundles Alby's Bitcoin Connect into a single self-contained ESM file that
// bitvid lazy-imports behind FEATURE_BITCOIN_CONNECT (docs/bitcoin-connect-plan.md).
// Mirrors the floating-ui vendor step in build-beacon.mjs. Pinned via the exact
// devDependency version in package.json.
import { build } from "esbuild";
import { mkdir, copyFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const entryPoint = path.join(repoRoot, "scripts/vendor/bitcoin-connect-entry.mjs");
const vendorDir = path.join(repoRoot, "vendor");
const outFile = path.join(vendorDir, "bitcoin-connect.bundle.min.js");
const licenseSrc = path.join(
repoRoot,
"node_modules/@getalby/bitcoin-connect/LICENSE",
);
const licenseOut = path.join(vendorDir, "bitcoin-connect.LICENSE");
await mkdir(vendorDir, { recursive: true });
await build({
bundle: true,
entryPoints: [entryPoint],
outfile: outFile,
format: "esm",
splitting: false,
platform: "browser",
target: ["es2020"],
minify: true,
sourcemap: false,
define: {
"process.env.NODE_ENV": '"production"',
global: "globalThis",
},
// @getalby/lightning-tools lazily `import('crypto')` for L402 macaroons — a
// path bitvid never calls. Alias it to a browser shim so the bundle resolves.
alias: {
crypto: path.join(repoRoot, "scripts/vendor/crypto-shim.mjs"),
"node:crypto": path.join(repoRoot, "scripts/vendor/crypto-shim.mjs"),
},
logLevel: "info",
});
try {
await copyFile(licenseSrc, licenseOut);
} catch (error) {
console.warn("[build-bitcoin-connect] could not copy LICENSE:", error.message);
}
console.log("[build-bitcoin-connect] wrote", path.relative(repoRoot, outFile));
+17
View File
@@ -0,0 +1,17 @@
// Vendor entry for Alby's Bitcoin Connect (getalby/bitcoin-connect), bundled by
// scripts/build-bitcoin-connect.mjs into vendor/bitcoin-connect.bundle.min.js so
// bitvid can lazy-import it behind FEATURE_BITCOIN_CONNECT without a runtime CDN.
//
// Importing the package self-registers its web components (bc-modal, etc.), so a
// single import both loads the API and boots the UI. We re-export only the small
// surface ProfileWalletController needs. See docs/bitcoin-connect-plan.md.
export {
init,
launchModal,
closeModal,
onConnected,
onDisconnected,
disconnect,
getConnectorConfig,
isConnected,
} from "@getalby/bitcoin-connect";
+26
View File
@@ -0,0 +1,26 @@
// Minimal browser shim for node's `crypto`, aliased in when bundling Bitcoin
// Connect (scripts/build-bitcoin-connect.mjs). The only place its deps reach for
// `crypto` is @getalby/lightning-tools' L402 macaroon helpers via a lazy
// `await import('crypto')` — a path bitvid's NWC connect/pay flow never calls.
// timingSafeEqual is implemented properly; the L402-only createHmac is stubbed so
// it fails loudly rather than silently misbehaving if ever reached.
export function timingSafeEqual(a, b) {
const ua = a instanceof Uint8Array ? a : new Uint8Array(a);
const ub = b instanceof Uint8Array ? b : new Uint8Array(b);
if (ua.length !== ub.length) {
return false;
}
let diff = 0;
for (let i = 0; i < ua.length; i += 1) {
diff |= ua[i] ^ ub[i];
}
return diff === 0;
}
export function createHmac() {
throw new Error(
"createHmac (L402 macaroons) is not available in the bitvid browser bundle",
);
}
export default { timingSafeEqual, createHmac };
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Alby contributors <[email protected]> (https://getalby.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large. Load diff