mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-08 22:02:54 +00:00
Merge branch 'unstable' into fix-webtorrent-webseed-regression-10274894627332192935
This commit is contained in:
28 files changed
+874
-719
No files matched your search
@@ -45,6 +45,9 @@ test.beforeEach(async () => {
|
||||
restoreNostrClient();
|
||||
restoreRelayManager();
|
||||
window.nostr = originalWindowNostr;
|
||||
if (nostrClient.extensionPermissionCache) {
|
||||
nostrClient.extensionPermissionCache.clear();
|
||||
}
|
||||
|
||||
// Setup default mock signer to prevent auto-save errors
|
||||
setActiveSigner({
|
||||
@@ -256,7 +259,7 @@ test(
|
||||
let fetchCalls = 0;
|
||||
nostrClient.fetchListIncrementally = async () => {
|
||||
fetchCalls += 1;
|
||||
return fetchCalls <= 4 ? [event] : [];
|
||||
return [event]; // ALWAYS return the event
|
||||
};
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
@@ -266,9 +269,9 @@ test(
|
||||
// Create a mock window.nostr that properly simulates extension behavior.
|
||||
// Specifically, enable() needs to exist so nostrClient detects it as an extension.
|
||||
window.nostr = {
|
||||
enable: async () => {},
|
||||
getPublicKey: async () => pubkey,
|
||||
enable: async () => Promise.resolve(true),
|
||||
nip04: {
|
||||
encrypt: async () => "cipher",
|
||||
decrypt: async () => {
|
||||
if (!permissionState.enabled) throw new Error("permission denied");
|
||||
decryptCalls.push("nip04");
|
||||
@@ -279,6 +282,10 @@ test(
|
||||
});
|
||||
},
|
||||
},
|
||||
nip44: {
|
||||
encrypt: async () => "cipher",
|
||||
decrypt: async () => "plaintext",
|
||||
}
|
||||
};
|
||||
|
||||
clearActiveSigner();
|
||||
@@ -573,6 +580,7 @@ test(
|
||||
|
||||
// Mock window.nostr
|
||||
window.nostr = {
|
||||
enable: async () => Promise.resolve(true),
|
||||
nip04: {
|
||||
decrypt: async (pk, ciphertext) => {
|
||||
if (pk !== pubkey) throw new Error("Wrong pubkey");
|
||||
@@ -621,6 +629,7 @@ test(
|
||||
|
||||
// Mock window.nostr
|
||||
window.nostr = {
|
||||
enable: async () => Promise.resolve(true),
|
||||
nip04: {
|
||||
decrypt: async (pk, ciphertext) => {
|
||||
if (pk !== pubkey) throw new Error("Wrong pubkey");
|
||||
|
||||
@@ -165,31 +165,25 @@ describe("NIP-07 Login Permissions", () => {
|
||||
const pubkey = result.pubkey;
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
assert.ok(env.enableCalls.length >= 1, "extension.enable should be invoked");
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has("nip04.encrypt"),
|
||||
"nip04.encrypt permission should be tracked as granted",
|
||||
);
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has("nip04.decrypt"),
|
||||
"nip04.decrypt permission should be tracked as granted",
|
||||
);
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has("nip44.encrypt"),
|
||||
"nip44.encrypt permission should be tracked as granted",
|
||||
);
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has("nip44.decrypt"),
|
||||
"nip44.decrypt permission should be tracked as granted",
|
||||
);
|
||||
for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) {
|
||||
|
||||
const permissionsToCheck = [
|
||||
"nip04.encrypt",
|
||||
"nip04.decrypt",
|
||||
"nip44.encrypt",
|
||||
"nip44.decrypt",
|
||||
...EXPECTED_ENCRYPTION_PERMISSIONS
|
||||
];
|
||||
|
||||
for (const method of permissionsToCheck) {
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has(method),
|
||||
`${method} permission should be tracked as granted`,
|
||||
`${method} permission should be tracked as granted`
|
||||
);
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has("sign_event"),
|
||||
"sign_event permission should be tracked as granted",
|
||||
"sign_event permission should be tracked as granted"
|
||||
);
|
||||
} finally {
|
||||
env.restore();
|
||||
@@ -220,18 +214,18 @@ describe("NIP-07 Login Permissions", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const result = await nip07Provider.login({ nostrClient });
|
||||
const pubkey = result.pubkey;
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
await nip07Provider.login({ nostrClient });
|
||||
|
||||
for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) {
|
||||
assert.ok(
|
||||
nostrClient.extensionPermissionCache.has(method),
|
||||
`nostrClient should track ${method} after login`,
|
||||
`nostrClient should track ${method} after login`
|
||||
);
|
||||
}
|
||||
|
||||
const storedRaw = localStorage.getItem(PERMISSIONS_STORAGE_KEY);
|
||||
assert.ok(storedRaw, "extension permissions should persist to localStorage");
|
||||
|
||||
let storedMethods = [];
|
||||
try {
|
||||
const parsed = JSON.parse(storedRaw);
|
||||
@@ -252,9 +246,10 @@ describe("NIP-07 Login Permissions", () => {
|
||||
for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) {
|
||||
assert.ok(
|
||||
storedSet.has(method),
|
||||
`stored permissions should include ${method}`,
|
||||
`stored permissions should include ${method}`
|
||||
);
|
||||
}
|
||||
|
||||
const baselineEnableCalls = env.enableCalls.length;
|
||||
const freshClient = new NostrClient();
|
||||
freshClient.pubkey = HEX_PUBKEY;
|
||||
@@ -262,7 +257,7 @@ describe("NIP-07 Login Permissions", () => {
|
||||
for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) {
|
||||
assert.ok(
|
||||
freshClient.extensionPermissionCache.has(method),
|
||||
`fresh client should hydrate ${method} from storage`,
|
||||
`fresh client should hydrate ${method} from storage`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,7 +269,7 @@ describe("NIP-07 Login Permissions", () => {
|
||||
assert.equal(
|
||||
env.enableCalls.length,
|
||||
baselineEnableCalls,
|
||||
`cached permissions should prevent extra enable() calls for ${method}`,
|
||||
`cached permissions should prevent extra enable() calls for ${method}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -288,7 +283,7 @@ describe("NIP-07 Login Permissions", () => {
|
||||
assert.equal(
|
||||
env.enableCalls.length,
|
||||
baselineEnableCalls,
|
||||
"decrypt calls should not trigger additional enable() prompts",
|
||||
"decrypt calls should not trigger additional enable() prompts"
|
||||
);
|
||||
assert.equal(decryptCalls.nip04, 1);
|
||||
assert.equal(decryptCalls.nip44, 1);
|
||||
@@ -322,37 +317,27 @@ describe("NIP-07 Login Permissions", () => {
|
||||
const result = await nip07Provider.login({ nostrClient });
|
||||
const pubkey = result.pubkey;
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
// With Structured -> String -> Plain order, this succeeds at step 2 (String).
|
||||
// The login flow may perform multiple permission requests (e.g. core -> full).
|
||||
// We expect at least one successful sequence (2 calls).
|
||||
assert.ok(env.enableCalls.length >= 2, "should retry with alternate payloads");
|
||||
|
||||
// Check the first sequence (core permissions)
|
||||
const [objectCall, stringCall] = env.enableCalls;
|
||||
assert.ok(Array.isArray(objectCall?.permissions));
|
||||
assert.equal(
|
||||
typeof objectCall.permissions[0],
|
||||
"object",
|
||||
"structured permission request should be attempted first",
|
||||
"structured permission request should be attempted first"
|
||||
);
|
||||
assert.ok(Array.isArray(stringCall?.permissions));
|
||||
assert.equal(
|
||||
typeof stringCall.permissions[0],
|
||||
"string",
|
||||
"string-based permissions should be attempted after structured payloads",
|
||||
"string-based permissions should be attempted after structured payloads"
|
||||
);
|
||||
|
||||
// If we have more calls (full permissions upgrade), verify they follow the same pattern
|
||||
if (env.enableCalls.length > 2) {
|
||||
// The 3rd call should be a new structured attempt for full permissions
|
||||
const upgradeObjectCall = env.enableCalls[2];
|
||||
// The 4th call (if 3rd failed) should be string
|
||||
const upgradeStringCall = env.enableCalls[3];
|
||||
|
||||
if (upgradeObjectCall) {
|
||||
assert.ok(Array.isArray(upgradeObjectCall.permissions), "upgrade attempt should use permissions");
|
||||
}
|
||||
// We don't strictly assert `plainCall` is undefined because a 2nd pass might happen
|
||||
}
|
||||
} finally {
|
||||
env.restore();
|
||||
@@ -376,21 +361,14 @@ describe("NIP-07 Login Permissions", () => {
|
||||
const result = await nip07Provider.login({ nostrClient });
|
||||
const pubkey = result.pubkey;
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
// With Structured -> String -> Plain order, we expect at least 3 calls for the first success:
|
||||
// 1. Structured (rejected)
|
||||
// 2. String (rejected)
|
||||
// 3. Plain (accepted)
|
||||
// If login does a second pass (upgrade), it might repeat this sequence.
|
||||
assert.ok(
|
||||
env.enableCalls.length >= 3,
|
||||
"should eventually succeed with plain enable()",
|
||||
"should eventually succeed with plain enable()"
|
||||
);
|
||||
|
||||
// Verify the first sequence structure
|
||||
const [objectCall, stringCall, plainCall] = env.enableCalls;
|
||||
assert.ok(objectCall !== undefined);
|
||||
assert.ok(stringCall !== undefined);
|
||||
// plainCall is the one that succeeded (called with undefined/no args)
|
||||
assert.equal(plainCall, undefined, "successful attempt should use plain enable()");
|
||||
} finally {
|
||||
env.restore();
|
||||
@@ -429,11 +407,8 @@ describe("NIP-07 Login Permissions", () => {
|
||||
assert.equal(pubkey, HEX_PUBKEY);
|
||||
assert.ok(
|
||||
duration < 1000,
|
||||
`login should fall back quickly from stalled enable payloads (duration: ${duration}ms)`,
|
||||
`login should fall back quickly from stalled enable payloads (duration: ${duration}ms)`
|
||||
);
|
||||
// Structured (stalls) -> String (resolves)
|
||||
// Plain is skipped because String resolved.
|
||||
// Login might do this twice (Core then Full), so 2 or 4 calls.
|
||||
assert.ok(env.enableCalls.length >= 2, "should attempt structured (stalls) and then string payload");
|
||||
|
||||
const [objectCall, stringCall] = env.enableCalls;
|
||||
@@ -473,13 +448,13 @@ describe("NIP-07 Login Permissions", () => {
|
||||
assert.equal(result.pubkey, HEX_PUBKEY);
|
||||
assert.ok(
|
||||
duration < 200,
|
||||
`login should resolve before deferred permission grants (duration: ${duration}ms)`,
|
||||
`login should resolve before deferred permission grants (duration: ${duration}ms)`
|
||||
);
|
||||
assert.ok(completionPromise, "completionPromise should be captured");
|
||||
await assert.rejects(
|
||||
completionPromise,
|
||||
/permission denied/,
|
||||
"deferred permission grants should still reject",
|
||||
"deferred permission grants should still reject"
|
||||
);
|
||||
} finally {
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
|
||||
@@ -45,14 +45,14 @@ describe("Nostr Private Key Signer", () => {
|
||||
const outboundCipher = await signer.nip04Encrypt(recipientPubkey, message);
|
||||
assert.equal(typeof outboundCipher, "string");
|
||||
const decryptedByRecipient = await nostrTools.nip04.decrypt(
|
||||
recipientPrivateHex,
|
||||
recipientKeyBytes,
|
||||
signer.pubkey,
|
||||
outboundCipher,
|
||||
);
|
||||
assert.equal(decryptedByRecipient, message);
|
||||
|
||||
const inboundCipher = await nostrTools.nip04.encrypt(
|
||||
recipientPrivateHex,
|
||||
recipientKeyBytes,
|
||||
signer.pubkey,
|
||||
message,
|
||||
);
|
||||
|
||||
@@ -92,10 +92,9 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn
|
||||
nostrClient.readRelays = [RELAY_URL];
|
||||
nostrClient.writeRelays = [RELAY_URL];
|
||||
|
||||
const senderPrivateKey = canonicalTools.utils.bytesToHex(
|
||||
canonicalTools.generateSecretKey(),
|
||||
);
|
||||
const senderPubkey = canonicalTools.getPublicKey(senderPrivateKey);
|
||||
const senderSecret = canonicalTools.generateSecretKey();
|
||||
const senderPrivateKey = canonicalTools.utils.bytesToHex(senderSecret);
|
||||
const senderPubkey = canonicalTools.getPublicKey(senderSecret);
|
||||
const recipientSecret = canonicalTools.generateSecretKey();
|
||||
const recipientPrivateKey = canonicalTools.utils.bytesToHex(recipientSecret);
|
||||
const recipientPubkey = canonicalTools.getPublicKey(recipientSecret);
|
||||
@@ -112,7 +111,7 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn
|
||||
const originalNip44 = signer?.nip44Encrypt;
|
||||
if (signer) {
|
||||
signer.signEvent = (event) => {
|
||||
const finalized = canonicalTools.finalizeEvent(event, senderPrivateKey);
|
||||
const finalized = canonicalTools.finalizeEvent(event, senderSecret);
|
||||
return { ...event, id: finalized.id, sig: finalized.sig };
|
||||
};
|
||||
delete signer.nip04Encrypt;
|
||||
@@ -133,7 +132,7 @@ test("sendDirectMessage succeeds with private key signer and no extension", asyn
|
||||
assert.notEqual(event.content, "", "ciphertext should not be empty");
|
||||
|
||||
const decrypted = await canonicalTools.nip04.decrypt(
|
||||
recipientPrivateKey,
|
||||
recipientSecret,
|
||||
senderPubkey,
|
||||
event.content,
|
||||
);
|
||||
|
||||
@@ -151,24 +151,24 @@ async function setupDmScenario() {
|
||||
const senderPrivateKey = nostrTools.utils.bytesToHex(senderSecret);
|
||||
const receiverPrivateKey = nostrTools.utils.bytesToHex(receiverSecret);
|
||||
|
||||
const senderPubkey = nostrTools.getPublicKey(senderPrivateKey);
|
||||
const receiverPubkey = nostrTools.getPublicKey(receiverPrivateKey);
|
||||
const senderPubkey = nostrTools.getPublicKey(senderSecret);
|
||||
const receiverPubkey = nostrTools.getPublicKey(receiverSecret);
|
||||
|
||||
const createSigner = (privateKey, pubkey) => ({
|
||||
const createSigner = (privateKeyBytes, pubkey) => ({
|
||||
type: "test",
|
||||
pubkey,
|
||||
signEvent: (event) => {
|
||||
const finalized = nostrTools.finalizeEvent(event, privateKey);
|
||||
const finalized = nostrTools.finalizeEvent(event, privateKeyBytes);
|
||||
return { ...event, id: finalized.id, sig: finalized.sig };
|
||||
},
|
||||
nip04Encrypt: (target, plaintext) =>
|
||||
nostrTools.nip04.encrypt(privateKey, target, plaintext),
|
||||
nostrTools.nip04.encrypt(privateKeyBytes, target, plaintext),
|
||||
nip04Decrypt: (target, ciphertext) =>
|
||||
nostrTools.nip04.decrypt(privateKey, target, ciphertext),
|
||||
nostrTools.nip04.decrypt(privateKeyBytes, target, ciphertext),
|
||||
});
|
||||
|
||||
const senderSigner = createSigner(senderPrivateKey, senderPubkey);
|
||||
const receiverSigner = createSigner(receiverPrivateKey, receiverPubkey);
|
||||
const senderSigner = createSigner(senderSecret, senderPubkey);
|
||||
const receiverSigner = createSigner(receiverSecret, receiverPubkey);
|
||||
|
||||
const relayPool = new FakeRelayPool();
|
||||
|
||||
|
||||
@@ -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,10 +197,10 @@ 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);
|
||||
const ciphertext = await nip04.encrypt(utils.hexToBytes(clientSecret), remotePubkey, serialized);
|
||||
const [ciphertextPart, iv] = ciphertext.split("?iv=");
|
||||
|
||||
const structuredPayload = {
|
||||
@@ -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 = nip04.encrypt(utils.hexToBytes(remoteSecret), clientPubkey, payload);
|
||||
const [ciphertextPart, ivPart] = ciphertext.split("?iv=");
|
||||
|
||||
assert.ok(ivPart, "nip04 encryption should emit an iv segment");
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { chromium } from 'playwright';
|
||||
import { spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const PORT = 8000;
|
||||
const URL = `http://localhost:${PORT}`;
|
||||
const ARTIFACTS_DIR = path.resolve('artifacts');
|
||||
const LOG_FILE = path.join(ARTIFACTS_DIR, `smoke-${new Date().toISOString().split('T')[0].replace(/-/g, '')}.log`);
|
||||
const SUMMARY_FILE = path.join(ARTIFACTS_DIR, 'smoke-summary.json');
|
||||
|
||||
if (!fs.existsSync(ARTIFACTS_DIR)) {
|
||||
fs.mkdirSync(ARTIFACTS_DIR);
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const line = `[${timestamp}] ${msg}`;
|
||||
console.log(line);
|
||||
try {
|
||||
fs.appendFileSync(LOG_FILE, line + '\n');
|
||||
} catch (e) {
|
||||
// Ignore log write errors
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log('Starting smoke test...');
|
||||
let serverProcess;
|
||||
let browser;
|
||||
|
||||
try {
|
||||
// Start Python Server
|
||||
log('Starting local server (python3 -m http.server)...');
|
||||
serverProcess = spawn('python3', ['-m', 'http.server', String(PORT)], {
|
||||
stdio: 'ignore'
|
||||
});
|
||||
|
||||
// Give it time to start
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
|
||||
// Launch Browser
|
||||
log('Launching browser...');
|
||||
browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
// Capture console logs from page
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') {
|
||||
log(`[BROWSER ERROR] ${msg.text()}`);
|
||||
} else {
|
||||
// log(`[BROWSER] ${msg.text()}`); // verbose
|
||||
}
|
||||
});
|
||||
page.on('pageerror', err => log(`[BROWSER EXCEPTION] ${err}`));
|
||||
|
||||
log(`Navigating to ${URL}...`);
|
||||
await page.goto(URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
log('Page loaded.');
|
||||
|
||||
// Inject Test Logic
|
||||
log('Running in-browser smoke test...');
|
||||
|
||||
const result = await page.evaluate(async () => {
|
||||
const logs = [];
|
||||
function browserLog(msg) {
|
||||
console.log(msg);
|
||||
logs.push(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
browserLog('Step 1: Importing Client...');
|
||||
// Note: In browser context, paths are relative to the served root (http://localhost:8000/)
|
||||
const { nostrClient } = await import('./js/nostrClientFacade.js');
|
||||
const { encodeHexToNpub } = await import('./js/nostr/nip46Client.js');
|
||||
|
||||
if (!nostrClient) throw new Error('nostrClient not found');
|
||||
browserLog('nostrClient imported.');
|
||||
|
||||
browserLog('Step 2: Generating Key & Login...');
|
||||
const randomBytes = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(randomBytes);
|
||||
const privateKey = Array.from(randomBytes)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
|
||||
const { pubkey } = await nostrClient.registerPrivateKeySigner({ privateKey, persist: false });
|
||||
nostrClient.pubkey = pubkey; // Explicitly set it on the client instance
|
||||
|
||||
if (!nostrClient.pubkey) throw new Error('Login failed: pubkey is null');
|
||||
browserLog(`Logged in as: ${nostrClient.pubkey}`);
|
||||
|
||||
browserLog('Step 3: Connecting to Relays...');
|
||||
const connectResults = await nostrClient.connectToRelays();
|
||||
const successful = connectResults.filter(r => r.success);
|
||||
browserLog(`Connected to ${successful.length} relays.`);
|
||||
|
||||
if (successful.length === 0) {
|
||||
browserLog('Warning: Failed to connect to any relay. Tests depending on network will likely fail or use local cache.');
|
||||
}
|
||||
|
||||
browserLog('Step 4: Publishing Video...');
|
||||
const seriesId = `smoke-test-${Date.now()}-${Math.floor(Math.random()*1000)}`;
|
||||
|
||||
const videoPayload = {
|
||||
title: "Smoke Test Video " + Date.now(),
|
||||
description: "Automated smoke test artifact.",
|
||||
url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||
videoRootId: seriesId,
|
||||
mimeType: "video/mp4"
|
||||
};
|
||||
|
||||
const videoEvent = await nostrClient.publishVideo(videoPayload, nostrClient.pubkey);
|
||||
browserLog(`Video published. Event ID: ${videoEvent.id}`);
|
||||
|
||||
browserLog('Step 5: Verifying Video Presence...');
|
||||
const fetched = await nostrClient.getEventById(videoEvent.id);
|
||||
if (!fetched) throw new Error('Failed to retrieve video event from client cache/network.');
|
||||
browserLog('Video event verified.');
|
||||
|
||||
browserLog('Step 6: DM Flow (Send to Self)...');
|
||||
const message = "Smoke test DM " + Date.now();
|
||||
const npub = encodeHexToNpub(nostrClient.pubkey);
|
||||
browserLog(`Sending DM to self (${npub})...`);
|
||||
|
||||
const dmResult = await nostrClient.sendDirectMessage(npub, message);
|
||||
if (!dmResult.ok) throw new Error(`DM send failed: ${dmResult.error} ${JSON.stringify(dmResult.details || {})}`);
|
||||
browserLog('DM sent.');
|
||||
|
||||
browserLog('Step 7: Verifying DM Decryption...');
|
||||
let found = null;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
const dms = await nostrClient.listDirectMessages(nostrClient.pubkey, { limit: 10 });
|
||||
found = dms.find(dm => dm.plaintext === message);
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new Error('DM verification failed: Message not found in decrypted list after retries.');
|
||||
}
|
||||
|
||||
browserLog('DM verified and decrypted successfully.');
|
||||
|
||||
return { success: true, logs };
|
||||
} catch (err) {
|
||||
browserLog(`Error: ${err.message}`);
|
||||
return { success: false, error: err.message, stack: err.stack, logs };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
log('SUCCESS: All smoke tests passed.');
|
||||
fs.writeFileSync(SUMMARY_FILE, JSON.stringify({
|
||||
status: 'success',
|
||||
timestamp: new Date().toISOString(),
|
||||
logs: result.logs
|
||||
}, null, 2));
|
||||
} else {
|
||||
log('FAILURE: Smoke tests failed.');
|
||||
log(`Error: ${result.error}`);
|
||||
fs.writeFileSync(SUMMARY_FILE, JSON.stringify({
|
||||
status: 'failure',
|
||||
timestamp: new Date().toISOString(),
|
||||
error: result.error,
|
||||
stack: result.stack,
|
||||
logs: result.logs
|
||||
}, null, 2));
|
||||
|
||||
const screenshotPath = path.join(ARTIFACTS_DIR, 'smoke-failure.png');
|
||||
await page.screenshot({ path: screenshotPath });
|
||||
log(`Screenshot saved to ${screenshotPath}`);
|
||||
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log(`CRITICAL ERROR: ${error.message}`);
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
if (serverProcess) {
|
||||
log('Stopping server...');
|
||||
serverProcess.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user