Files
company-tool/scripts/ws-smoke-test.mjs
Torsten Schulz (local) 0e539710c0 feat: Add password reset functionality with request and reset forms
feat: Implement price list import feature with preview and apply options

feat: Create price rules management page with CRUD operations

feat: Develop quotes management page with itemized quotes and status tracking

feat: Introduce organization registration page for new users

feat: Build suppliers management page with detailed supplier information

feat: Create users management page for inviting and managing roles

chore: Add TypeScript configuration for improved type checking

chore: Set up Vite configuration for development server and API proxy

chore: Add Vite environment type definitions for better TypeScript support
2026-06-02 15:28:38 +02:00

140 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
import { webcrypto } from "node:crypto";
const wsUrl = process.argv[2] ?? process.env.WS_URL ?? "ws://localhost:8080/ws";
const protocolVersion = 1;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function bytesToBase64(bytes) {
return Buffer.from(bytes).toString("base64");
}
function base64ToBytes(value) {
return Buffer.from(value, "base64");
}
async function createSession() {
const key = await webcrypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
const rawKey = await webcrypto.subtle.exportKey("raw", key);
return {
key,
keyId: webcrypto.randomUUID(),
exportedKey: bytesToBase64(new Uint8Array(rawKey)),
};
}
async function encryptMessage(session, message) {
const nonce = webcrypto.getRandomValues(new Uint8Array(12));
const plaintext = encoder.encode(JSON.stringify(message));
const ciphertext = await webcrypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce },
session.key,
plaintext,
);
return {
enc: `aes-256-gcm-v${protocolVersion}`,
key_id: session.keyId,
nonce: bytesToBase64(nonce),
ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
};
}
async function decryptMessage(session, envelope) {
const plaintext = await webcrypto.subtle.decrypt(
{ name: "AES-GCM", iv: base64ToBytes(envelope.nonce) },
session.key,
base64ToBytes(envelope.ciphertext),
);
return JSON.parse(decoder.decode(plaintext));
}
function waitForOpen(socket) {
return new Promise((resolve, reject) => {
socket.addEventListener("open", resolve, { once: true });
socket.addEventListener("error", reject, { once: true });
});
}
function waitForMessage(socket, timeoutMs = 5000) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
socket.removeEventListener("message", onMessage);
reject(new Error(`timeout after ${timeoutMs}ms`));
}, timeoutMs);
function onMessage(event) {
clearTimeout(timeout);
resolve(JSON.parse(event.data));
}
socket.addEventListener("message", onMessage, { once: true });
});
}
async function main() {
const session = await createSession();
const socket = new WebSocket(wsUrl);
console.log(`connecting ${wsUrl}`);
await waitForOpen(socket);
console.log("socket open");
socket.send(JSON.stringify({
type: "hello",
payload: {
protocol_version: protocolVersion,
key_id: session.keyId,
session_key: session.exportedKey,
},
}));
const ack = await waitForMessage(socket);
if (ack.type !== "hello_ack") {
throw new Error(`expected hello_ack, got ${JSON.stringify(ack)}`);
}
console.log(`hello_ack protocol=${ack.payload.protocol_version} key=${ack.payload.key_id}`);
const firstEncrypted = await waitForMessage(socket);
if (firstEncrypted.type !== "encrypted") {
throw new Error(`expected encrypted snapshot, got ${JSON.stringify(firstEncrypted)}`);
}
const snapshot = await decryptMessage(session, firstEncrypted.payload);
console.log(`decrypted first server message: ${snapshot.type}`);
const subscribe = await encryptMessage(session, {
type: "subscribe",
payload: { topic: "records" },
});
socket.send(JSON.stringify({ type: "encrypted", payload: subscribe }));
console.log("sent encrypted subscribe");
const ping = await encryptMessage(session, { type: "ping" });
socket.send(JSON.stringify({ type: "encrypted", payload: ping }));
const pongEnvelope = await waitForMessage(socket);
if (pongEnvelope.type !== "encrypted") {
throw new Error(`expected encrypted pong, got ${JSON.stringify(pongEnvelope)}`);
}
const pong = await decryptMessage(session, pongEnvelope.payload);
console.log(`decrypted ping response: ${pong.type}`);
socket.close();
console.log("communication smoke test ok");
}
main().catch((error) => {
console.error(error);
process.exit(1);
});