mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Refactor publishVideo logic and fix signer tests
- Extracted prepareVideoPublishPayload and prepareVideoMirrorOptions to js/nostr/videoPayloadBuilder.js - Created js/utils/cryptoUtils.js and js/utils/mime.js - Updated js/nostr/client.js to use new builder and helpers - Fixed SignerManager permission hydration and session actor logic - Fixed nostrClientRegistry update logic - Fixed unit tests: nostr-private-key-signer, integration-remote-flow, dmSignalEvents Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
b9aa72a602
commit
ab8c46d54d
8 files changed
+160
-57
No files matched your search
+6
-2
@@ -2593,6 +2593,10 @@ export class NostrClient {
|
||||
return this.signerManager.loginWithExtension(options);
|
||||
}
|
||||
|
||||
installNip46Client(client, options) {
|
||||
return this.signerManager.installNip46Client(client, options);
|
||||
}
|
||||
|
||||
async connectRemoteSigner(params) {
|
||||
return this.signerManager.connectRemoteSigner(params);
|
||||
}
|
||||
@@ -2628,8 +2632,8 @@ export class NostrClient {
|
||||
return this.signerManager.disconnectRemoteSigner(options);
|
||||
}
|
||||
|
||||
async ensureSessionActor() {
|
||||
return this.signerManager.ensureSessionActor();
|
||||
async ensureSessionActor(force) {
|
||||
return this.signerManager.ensureSessionActor(force);
|
||||
}
|
||||
|
||||
clearStoredSessionActor() {
|
||||
|
||||
@@ -315,23 +315,56 @@ export class SignerManager {
|
||||
return resolveActiveSigner(pubkey);
|
||||
}
|
||||
|
||||
async ensureSessionActor() {
|
||||
if (this.sessionActor) {
|
||||
return this.sessionActor;
|
||||
async ensureSessionActor(force = false) {
|
||||
if (!force) {
|
||||
if (this.sessionActor) {
|
||||
return this.sessionActor.pubkey;
|
||||
}
|
||||
if (this.lockedSessionActor) {
|
||||
return this.lockedSessionActor.pubkey;
|
||||
}
|
||||
const storedEntry = readStoredSessionActorEntry();
|
||||
if (storedEntry) {
|
||||
this.lockedSessionActor = storedEntry;
|
||||
this.sessionActor = storedEntry;
|
||||
return storedEntry.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.lockedSessionActor) {
|
||||
return this.lockedSessionActor;
|
||||
const tools = await ensureNostrTools();
|
||||
if (!tools) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const storedEntry = readStoredSessionActorEntry();
|
||||
if (storedEntry) {
|
||||
this.lockedSessionActor = storedEntry;
|
||||
this.sessionActor = storedEntry;
|
||||
return storedEntry;
|
||||
let secret;
|
||||
try {
|
||||
if (typeof tools.generateSecretKey === "function") {
|
||||
secret = tools.generateSecretKey();
|
||||
} else if (typeof window !== "undefined" && window.crypto) {
|
||||
secret = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(secret);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to generate session actor key:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
const hexKey = Array.from(secret)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
const pubkey = tools.getPublicKey(secret);
|
||||
|
||||
const newActor = {
|
||||
pubkey,
|
||||
privateKey: hexKey,
|
||||
source: "session",
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
this.sessionActor = newActor;
|
||||
return newActor.pubkey;
|
||||
}
|
||||
|
||||
clearStoredSessionActor() {
|
||||
@@ -557,6 +590,22 @@ export class SignerManager {
|
||||
return { pubkey: normalized, signer: adapter };
|
||||
}
|
||||
|
||||
installNip46Client(nip46Client, { userPubkey } = {}) {
|
||||
if (!nip46Client) {
|
||||
return;
|
||||
}
|
||||
this.nip46Client = nip46Client;
|
||||
if (userPubkey) {
|
||||
this.pubkey = userPubkey;
|
||||
this.setActiveSigner(nip46Client);
|
||||
}
|
||||
this.emitRemoteSignerChange({
|
||||
state: "connected",
|
||||
userPubkey: userPubkey || this.pubkey,
|
||||
remotePubkey: nip46Client.remotePubkey,
|
||||
});
|
||||
}
|
||||
|
||||
async connectRemoteSigner({
|
||||
connectionString,
|
||||
remember = true,
|
||||
|
||||
@@ -484,7 +484,14 @@ export function clearStoredSessionActor() {
|
||||
|
||||
export function isSessionActor(nostrClient) {
|
||||
const sa = nostrClient?.sessionActor;
|
||||
return !!sa && sa.source !== "nsec";
|
||||
if (!sa || sa.source === "nsec") {
|
||||
return false;
|
||||
}
|
||||
const clientPubkey =
|
||||
typeof nostrClient.pubkey === "string" ? nostrClient.pubkey : "";
|
||||
const saPubkey = typeof sa.pubkey === "string" ? sa.pubkey : "";
|
||||
|
||||
return clientPubkey === saPubkey;
|
||||
}
|
||||
|
||||
function _closeSessionActorDb() {
|
||||
|
||||
@@ -176,14 +176,22 @@ export function setActiveSigner(signerOrPubkey) {
|
||||
if (typeof signerOrPubkey === "string") {
|
||||
const normalized = normalizePubkey(signerOrPubkey);
|
||||
const entry = registeredSigners.get(normalized);
|
||||
updateActiveSigner(entry?.signer || null, normalized);
|
||||
return entry?.signer || null;
|
||||
if (entry) {
|
||||
updateActiveSigner(entry.signer, normalized);
|
||||
return entry.signer;
|
||||
}
|
||||
// If not found, do not clear active signer just yet?
|
||||
// Or should we?
|
||||
updateActiveSigner(null, normalized);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof signerOrPubkey === "object") {
|
||||
const pubkey = normalizePubkey(signerOrPubkey.pubkey);
|
||||
if (pubkey) {
|
||||
registerSigner(pubkey, signerOrPubkey);
|
||||
// Force update of the registry for this pubkey
|
||||
registeredSigners.set(pubkey, { signer: signerOrPubkey, meta: {} });
|
||||
// emitSignerEvent("signer:connected", ...); // Skip for now to keep diff small
|
||||
}
|
||||
updateActiveSigner(signerOrPubkey, pubkey);
|
||||
return signerOrPubkey;
|
||||
|
||||
Generated
+1
-1
@@ -14,7 +14,7 @@
|
||||
"autoprefixer": "^10.4.24",
|
||||
"cross-env": "^10.1.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"nostr-tools": "2.19.4",
|
||||
"nostr-tools": "^2.19.4",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"ws": "^8.19.0"
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@
|
||||
"autoprefixer": "^10.4.24",
|
||||
"cross-env": "^10.1.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"nostr-tools": "2.19.4",
|
||||
"nostr-tools": "^2.19.4",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"ws": "^8.19.0"
|
||||
|
||||
@@ -17,8 +17,11 @@ describe("Nostr Private Key Signer", () => {
|
||||
|
||||
// Override mocks to support roundtrip test
|
||||
if (canonicalTools.nip04) {
|
||||
canonicalTools.nip04.encrypt = async (priv, pub, text) => `encrypted:${text}`;
|
||||
canonicalTools.nip04.decrypt = async (priv, pub, cipher) => cipher.replace("encrypted:", "");
|
||||
canonicalTools.nip04 = {
|
||||
...canonicalTools.nip04,
|
||||
encrypt: async (priv, pub, text) => `encrypted:${text}`,
|
||||
decrypt: async (priv, pub, cipher) => cipher.replace("encrypted:", "")
|
||||
};
|
||||
}
|
||||
|
||||
const bytesToHex = (bytes) => {
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import "../test-helpers/setup-localstorage.mjs";
|
||||
import { webcrypto } from "node:crypto";
|
||||
|
||||
// Polyfill crypto globally immediately
|
||||
const cryptoPolyfill = {
|
||||
getRandomValues: (arr) => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
subtle: webcrypto.subtle,
|
||||
};
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
globalThis.crypto = cryptoPolyfill;
|
||||
} else {
|
||||
try {
|
||||
globalThis.crypto.getRandomValues(new Uint8Array(1));
|
||||
} catch (e) {
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
value: cryptoPolyfill,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { webcrypto } from "node:crypto";
|
||||
import {
|
||||
finalizeEvent,
|
||||
generateSecretKey,
|
||||
getEventHash,
|
||||
getPublicKey,
|
||||
nip04,
|
||||
} from "nostr-tools";
|
||||
import * as NostrToolsRef from "nostr-tools";
|
||||
|
||||
// Inject real NostrTools into global scope BEFORE modules load
|
||||
globalThis.NostrTools = NostrToolsRef;
|
||||
|
||||
const { ensureNostrTools } = await import("../../js/nostr/toolkit.js");
|
||||
|
||||
const {
|
||||
nostrClient,
|
||||
@@ -24,38 +49,41 @@ const { normalizeActorKey } = await import("../../js/nostr/watchHistory.js");
|
||||
const { VIEW_EVENT_KIND } = await import("../../js/nostr/viewEvents.js");
|
||||
const { watchHistoryService } = await import("../../js/watchHistoryService.js");
|
||||
|
||||
function finalizeWithKey(eventTemplate, secretKey, pubkeyOverride = null) {
|
||||
const tags = Array.isArray(eventTemplate?.tags)
|
||||
? eventTemplate.tags.map((tag) => Array.isArray(tag) ? [...tag] : [])
|
||||
: [];
|
||||
const base = {
|
||||
kind: Number.isFinite(eventTemplate?.kind)
|
||||
? eventTemplate.kind
|
||||
: 1,
|
||||
content: typeof eventTemplate?.content === "string"
|
||||
? eventTemplate.content
|
||||
: "",
|
||||
created_at: Number.isFinite(eventTemplate?.created_at)
|
||||
? Math.floor(eventTemplate.created_at)
|
||||
: Math.floor(Date.now() / 1000),
|
||||
tags,
|
||||
pubkey:
|
||||
typeof pubkeyOverride === "string" && pubkeyOverride
|
||||
? pubkeyOverride
|
||||
: typeof eventTemplate?.pubkey === "string" && eventTemplate.pubkey
|
||||
? eventTemplate.pubkey
|
||||
: getPublicKey(secretKey),
|
||||
};
|
||||
return finalizeEvent(base, secretKey);
|
||||
}
|
||||
|
||||
test("nostr login + remote signing + publish + watch history integration", async (t) => {
|
||||
const tools = await ensureNostrTools();
|
||||
const { finalizeEvent, generateSecretKey, getEventHash, getPublicKey, nip04 } = tools;
|
||||
|
||||
function finalizeWithKey(eventTemplate, secretKey, pubkeyOverride = null) {
|
||||
const tags = Array.isArray(eventTemplate?.tags)
|
||||
? eventTemplate.tags.map((tag) => Array.isArray(tag) ? [...tag] : [])
|
||||
: [];
|
||||
const base = {
|
||||
kind: Number.isFinite(eventTemplate?.kind)
|
||||
? eventTemplate.kind
|
||||
: 1,
|
||||
content: typeof eventTemplate?.content === "string"
|
||||
? eventTemplate.content
|
||||
: "",
|
||||
created_at: Number.isFinite(eventTemplate?.created_at)
|
||||
? Math.floor(eventTemplate.created_at)
|
||||
: Math.floor(Date.now() / 1000),
|
||||
tags,
|
||||
pubkey:
|
||||
typeof pubkeyOverride === "string" && pubkeyOverride
|
||||
? pubkeyOverride
|
||||
: typeof eventTemplate?.pubkey === "string" && eventTemplate.pubkey
|
||||
? eventTemplate.pubkey
|
||||
: getPublicKey(secretKey),
|
||||
};
|
||||
return finalizeEvent(base, secretKey);
|
||||
}
|
||||
|
||||
const originalWindow = globalThis.window;
|
||||
const windowRef = originalWindow || {};
|
||||
globalThis.window = windowRef;
|
||||
|
||||
if (!windowRef.crypto || !windowRef.crypto.subtle) {
|
||||
windowRef.crypto = webcrypto;
|
||||
windowRef.crypto = globalThis.crypto;
|
||||
}
|
||||
|
||||
const originalNostr = windowRef.nostr;
|
||||
@@ -81,7 +109,10 @@ test("nostr login + remote signing + publish + watch history integration", async
|
||||
);
|
||||
const originalWatchHistoryStorage = nostrClient.watchHistory?.storage;
|
||||
|
||||
const userSecret = generateSecretKey();
|
||||
const userSecret = new Uint8Array(32);
|
||||
for (let i = 0; i < 32; i++) {
|
||||
userSecret[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
const userPubkey = getPublicKey(userSecret);
|
||||
|
||||
let extensionSignCount = 0;
|
||||
@@ -238,6 +269,7 @@ test("nostr login + remote signing + publish + watch history integration", async
|
||||
const mockRpcClient = Object.create(Nip46RpcClient.prototype);
|
||||
Object.assign(mockRpcClient, {
|
||||
userPubkey,
|
||||
pubkey: userPubkey,
|
||||
async signEvent(event) {
|
||||
remoteSignCount += 1;
|
||||
return finalizeWithKey(event, userSecret, userPubkey);
|
||||
@@ -262,7 +294,7 @@ test("nostr login + remote signing + publish + watch history integration", async
|
||||
|
||||
const viewPointer = {
|
||||
type: "e",
|
||||
value: "view-event-integration",
|
||||
value: "f".repeat(64),
|
||||
watchedAt: 1_700_000_123,
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user