#!/usr/bin/env node /** * Optional: full graphql-ws handshake — connection_init → connection_ack over wss:// * Server must expose a single clean upgrade path (standalone `ws` + graphql-ws; remove unused * `@fastify/websocket` on CT 7800 if clients see RSV1 — see ensure-sankofa-phoenix-graphql-ws-remove-fastify-websocket-7800.sh). * * Usage: * node scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs * PHOENIX_GRAPHQL_WSS_URL=wss://host/graphql-ws node scripts/verify/smoke-phoenix-graphql-ws-subscription.mjs */ import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; const require = createRequire(import.meta.url); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); const WebSocket = require(path.join(repoRoot, 'node_modules', 'ws')); const url = process.env.PHOENIX_GRAPHQL_WSS_URL || 'wss://phoenix.sankofa.nexus/graphql-ws'; const timeoutMs = Number(process.env.PHOENIX_WS_SUB_TIMEOUT_MS || 15000); const ws = new WebSocket(url, ['graphql-transport-ws'], { perMessageDeflate: false }); const timer = setTimeout(() => { console.error('TIMEOUT waiting for connection_ack'); ws.terminate(); process.exit(1); }, timeoutMs); ws.on('open', () => { ws.send(JSON.stringify({ type: 'connection_init' })); }); ws.on('message', (data) => { const text = String(data); let msg; try { msg = JSON.parse(text); } catch { console.error('Non-JSON message:', text.slice(0, 200)); return; } if (msg.type === 'connection_ack') { clearTimeout(timer); console.log('OK: graphql-ws connection_ack'); ws.close(1000, 'smoke-ok'); process.exit(0); } if (msg.type === 'ping') { ws.send(JSON.stringify({ type: 'pong' })); return; } console.log('msg:', msg.type, JSON.stringify(msg).slice(0, 200)); }); ws.on('error', (err) => { clearTimeout(timer); console.error('WebSocket error:', err.message); process.exit(2); }); ws.on('close', (code, reason) => { clearTimeout(timer); if (code !== 1000) { console.error('Closed:', code, String(reason)); process.exit(3); } });