I have updated the nostr-tools usage and resolved the UI bugs. Here are the details of the changes:

- Updated `js/nostr/adapters/nsecAdapter.js` to ensure private keys are `Uint8Array` bytes before passing to `getPublicKey`, fixing compatibility with `nostr-tools` v2.
- Updated `tests/nostr/nip46Client.test.js` to use byte arrays for keys in crypto operations.
- Implemented missing `setupModalMorePopover`, `setupModalSharePopover`, and `refreshModalMoreMenuPanel` in `VideoModal.js`.
- Fixed a bug in `VideoModal.js` where `setCommentComposerState` was overriding `CommentsController` send button logic; it now delegates to the controller if available.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-27 02:14:29 +00:00
1 parent 7427961a69
commit a649c341e0
3 files changed
+148 -36

No files matched your search

+22 -6
View File
@@ -6,8 +6,6 @@ import { ensureNostrTools, getCachedNostrTools } from "../toolkit.js";
import { normalizeActorKey } from "../watchHistory.js";
import { HEX64_REGEX } from "../../utils/hex.js";
function normalizePrivateKey(privateKey) {
if (typeof privateKey !== "string") {
return "";
@@ -31,10 +29,28 @@ async function resolvePublicKey(privateKey, pubkey) {
const tools = (await ensureNostrTools()) || getCachedNostrTools();
if (tools?.getPublicKey && typeof tools.getPublicKey === "function") {
const derived = tools.getPublicKey(privateKey);
const normalized = normalizeActorKey(derived);
if (normalized && HEX64_REGEX.test(normalized)) {
return normalized;
try {
// nostr-tools v2 requires Uint8Array
let keyInput = privateKey;
if (typeof privateKey === "string" && tools.utils?.hexToBytes) {
keyInput = tools.utils.hexToBytes(privateKey);
}
const derived = tools.getPublicKey(keyInput);
const normalized = normalizeActorKey(derived);
if (normalized && HEX64_REGEX.test(normalized)) {
return normalized;
}
} catch (e) {
// Fallback for v1 or other errors
try {
const derived = tools.getPublicKey(privateKey);
const normalized = normalizeActorKey(derived);
if (normalized && HEX64_REGEX.test(normalized)) {
return normalized;
}
} catch (e2) {
return "";
}
}
}
+103 -7
View File
@@ -3025,20 +3025,23 @@ export class VideoModal {
setCommentComposerState(state) {
this._commentComposerState = state;
if (this.commentsController) {
// Also update the internal controller to keep them in sync if possible,
// but primarily we trust the caller (which might be a test controller)
// Delegate entirely to the controller if it exists to avoid fighting over DOM state
// (especially submit button enablement which depends on input value)
if (typeof this.commentsController.setCommentComposerState === 'function') {
// Avoid infinite recursion if controller calls back
if (this.commentsController.composerState !== state) {
this.commentsController.setCommentComposerState(state);
}
}
return;
}
// Reflect state to DOM immediately if elements exist
// Fallback: Manually reflect state to DOM if no controller is attached (e.g. in isolated tests)
if (this.commentsInput) {
this.commentsInput.disabled = !!state?.disabled;
}
if (this.commentsSubmitButton) {
// Note: This naive check doesn't account for empty input,
// but without a controller we can't easily validte.
this.commentsSubmitButton.disabled = !!state?.disabled;
}
if (this.commentsComposer) {
@@ -3169,14 +3172,107 @@ export class VideoModal {
}
setupModalMorePopover() {
// Stub
if (!this.playerModal || !this.modalMoreBtn) {
return;
}
if (this.modalMorePopover?.destroy) {
this.modalMorePopover.destroy();
}
this.modalMorePopover = createPopover({
trigger: this.modalMoreBtn,
content: (container) => {
if (!this.activeVideo) {
container.innerHTML = "";
return;
}
const panel = createVideoMoreMenuPanel({
document: this.document,
video: this.activeVideo,
pointerInfo: this.modalMoreMenuContext.pointerInfo,
playbackUrl: this.modalMoreMenuContext.playbackUrl,
playbackMagnet: this.modalMoreMenuContext.playbackMagnet,
canManageBlacklist: this.modalMoreMenuContext.canManageBlacklist,
context: "modal",
});
if (panel) {
container.appendChild(panel);
this.modalMoreMenuPanel = panel;
}
},
placement: "bottom-end",
offset: 8,
onOpen: () => {
if (this.activeVideo) {
this.syncMoreMenuData({
currentVideo: this.activeVideo,
canManageBlacklist: this.modalMoreMenuContext.canManageBlacklist,
});
}
},
});
}
setupModalSharePopover() {
// Stub
if (!this.playerModal || !this.shareBtn) {
return;
}
// Reuse popover engine for share button
// Using a simpler on-click binding for now to match legacy behavior,
// or instantiate a popover if we want the full menu experience.
// The E2E test expects a popover with [data-menu="video-share"].
// We'll create a local property for share popover to clean up later
if (this.modalSharePopover?.destroy) {
this.modalSharePopover.destroy();
}
this.modalSharePopover = createPopover({
trigger: this.shareBtn,
content: (container) => {
if (!this.activeVideo) {
container.innerHTML = "";
return;
}
const panel = createVideoShareMenuPanel({
document: this.document,
video: this.activeVideo,
isLoggedIn: this.shareNostrAuthState.isLoggedIn,
hasSigner: this.shareNostrAuthState.hasSigner,
hasMagnet: Boolean(this.modalMoreMenuContext.playbackMagnet), // Re-use magnet from context
hasCdn: Boolean(this.modalMoreMenuContext.playbackUrl),
});
if (panel) {
container.appendChild(panel);
}
},
placement: "top", // or "top-start" based on layout
offset: 8,
});
}
refreshModalMoreMenuPanel() {
// Stub
if (this.modalMoreMenuPanel && this.activeVideo) {
// Re-render panel content if open
const container = this.modalMoreMenuPanel.parentElement;
if(container) {
container.innerHTML = "";
const panel = createVideoMoreMenuPanel({
document: this.document,
video: this.activeVideo,
pointerInfo: this.modalMoreMenuContext.pointerInfo,
playbackUrl: this.modalMoreMenuContext.playbackUrl,
playbackMagnet: this.modalMoreMenuContext.playbackMagnet,
canManageBlacklist: this.modalMoreMenuContext.canManageBlacklist,
context: "modal",
});
if (panel) {
container.appendChild(panel);
this.modalMoreMenuPanel = panel;
}
}
}
}
}
+23 -23
View File
@@ -25,8 +25,8 @@ test("Nip46RpcClient encrypts payloads with nip44 conversation keys", async () =
const clientSecret = utils.bytesToHex(generateSecretKey());
const remoteSecret = utils.bytesToHex(generateSecretKey());
const clientPublicKey = getPublicKey(clientSecret);
const remotePubkey = getPublicKey(remoteSecret);
const clientPublicKey = getPublicKey(utils.hexToBytes(clientSecret));
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret));
const rpcClient = new Nip46RpcClient({
nostrClient: { relays: [] },
@@ -43,8 +43,8 @@ test("Nip46RpcClient encrypts payloads with nip44 conversation keys", async () =
const conversationKey =
typeof nip44.v2.getConversationKey === "function"
? nip44.v2.getConversationKey(clientSecret, remotePubkey)
: nip44.v2.utils.getConversationKey(clientSecret, remotePubkey);
? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey)
: nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey);
const decryptedWithTools = nip44.v2.decrypt(ciphertext, conversationKey);
assert.equal(
decryptedWithTools,
@@ -71,13 +71,13 @@ test("decryptNip46PayloadWithKeys handles nip44.v2 ciphertext", async () => {
const clientSecret = utils.bytesToHex(generateSecretKey());
const remoteSecret = utils.bytesToHex(generateSecretKey());
const remotePubkey = getPublicKey(remoteSecret);
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret));
const serialized = JSON.stringify({ message: "hello", count: 42 });
const conversationKey =
typeof nip44.v2.getConversationKey === "function"
? nip44.v2.getConversationKey(clientSecret, remotePubkey)
: nip44.v2.utils.getConversationKey(clientSecret, remotePubkey);
? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey)
: nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey);
const ciphertext = nip44.v2.encrypt(serialized, conversationKey);
const { plaintext, algorithm } = await decryptNip46PayloadWithKeys(
@@ -104,13 +104,13 @@ test("decryptNip46PayloadWithKeys coerces structured handshake payloads", async
const clientSecret = utils.bytesToHex(generateSecretKey());
const remoteSecret = utils.bytesToHex(generateSecretKey());
const remotePubkey = getPublicKey(remoteSecret);
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret));
const serialized = JSON.stringify({ message: "hello", count: 42 });
const conversationKey =
typeof nip44.v2.getConversationKey === "function"
? nip44.v2.getConversationKey(clientSecret, remotePubkey)
: nip44.v2.utils.getConversationKey(clientSecret, remotePubkey);
? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey)
: nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey);
const ciphertext = nip44.v2.encrypt(serialized, conversationKey);
const [ciphertextPart, nonce] = ciphertext.split("\n");
@@ -154,13 +154,13 @@ test("decryptNip46PayloadWithKeys decodes buffer-based handshake payloads", asyn
const clientSecret = utils.bytesToHex(generateSecretKey());
const remoteSecret = utils.bytesToHex(generateSecretKey());
const remotePubkey = getPublicKey(remoteSecret);
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret));
const serialized = JSON.stringify({ message: "hello", count: 42 });
const conversationKey =
typeof nip44.v2.getConversationKey === "function"
? nip44.v2.getConversationKey(clientSecret, remotePubkey)
: nip44.v2.utils.getConversationKey(clientSecret, remotePubkey);
? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey)
: nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remotePubkey);
const ciphertext = nip44.v2.encrypt(serialized, conversationKey);
const [ciphertextPart, nonce] = ciphertext.split("\n");
const ciphertextBuffer = Buffer.from(ciphertext, "utf8");
@@ -197,7 +197,7 @@ test("decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers", asy
const clientSecret = utils.bytesToHex(generateSecretKey());
const remoteSecret = utils.bytesToHex(generateSecretKey());
const remotePubkey = getPublicKey(remoteSecret);
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret));
const serialized = JSON.stringify({ message: "hello", count: 42 });
const ciphertext = await nip04.encrypt(clientSecret, remotePubkey, serialized);
@@ -254,8 +254,8 @@ test("parseNip46ConnectionString handles remote signer key hints", async () => {
const signerSecret = utils.bytesToHex(generateSecretKey());
const userSecret = utils.bytesToHex(generateSecretKey());
const signerPubkey = getPublicKey(signerSecret).toLowerCase();
const userPubkey = getPublicKey(userSecret).toLowerCase();
const signerPubkey = getPublicKey(utils.hexToBytes(signerSecret)).toLowerCase();
const userPubkey = getPublicKey(utils.hexToBytes(userSecret)).toLowerCase();
const signerNpub = nip19.npubEncode(signerPubkey);
const userNpub = nip19.npubEncode(userPubkey);
@@ -295,13 +295,13 @@ test(
const remoteSignerSecret = utils.bytesToHex(generateSecretKey());
const userSecret = utils.bytesToHex(generateSecretKey());
const remoteSignerPubkey = getPublicKey(remoteSignerSecret).toLowerCase();
const userPubkey = getPublicKey(userSecret).toLowerCase();
const remoteSignerPubkey = getPublicKey(utils.hexToBytes(remoteSignerSecret)).toLowerCase();
const userPubkey = getPublicKey(utils.hexToBytes(userSecret)).toLowerCase();
const conversationKey =
typeof nip44.v2.getConversationKey === "function"
? nip44.v2.getConversationKey(clientSecret, remoteSignerPubkey)
: nip44.v2.utils.getConversationKey(clientSecret, remoteSignerPubkey);
? nip44.v2.getConversationKey(utils.hexToBytes(clientSecret), remoteSignerPubkey)
: nip44.v2.utils.getConversationKey(utils.hexToBytes(clientSecret), remoteSignerPubkey);
const payload = JSON.stringify({ id: "ack", result: "ok" });
const ciphertext = nip44.v2.encrypt(payload, conversationKey);
@@ -333,12 +333,12 @@ test(
const { attemptDecryptNip46HandshakePayload } = await loadNip46Module();
const clientSecret = utils.bytesToHex(generateSecretKey());
const clientPubkey = getPublicKey(clientSecret).toLowerCase();
const clientPubkey = getPublicKey(utils.hexToBytes(clientSecret)).toLowerCase();
const remoteSecret = utils.bytesToHex(generateSecretKey());
const remotePubkey = getPublicKey(remoteSecret).toLowerCase();
const remotePubkey = getPublicKey(utils.hexToBytes(remoteSecret)).toLowerCase();
const payload = JSON.stringify({ id: "ack", result: "ack" });
const ciphertext = nip04.encrypt(remoteSecret, clientPubkey, payload);
const ciphertext = await nip04.encrypt(remoteSecret, clientPubkey, payload);
const [ciphertextPart, ivPart] = ciphertext.split("?iv=");
assert.ok(ivPart, "nip04 encryption should emit an iv segment");