@@ -21,14 +21,41 @@ const crypto = require('crypto');
2121const secp256k1 = require ( '@noble/secp256k1' ) ;
2222
2323// ── CLI Args ─────────────────────────────────────────────────────────────────
24- const args = process . argv . slice ( 2 ) . reduce ( ( acc , arg , i , arr ) => {
25- if ( arg . startsWith ( '--' ) ) acc [ arg . slice ( 2 ) ] = arr [ i + 1 ] ;
24+ function parseCliArgs ( argv ) {
25+ const acc = { } ;
26+ for ( let i = 0 ; i < argv . length ; i ++ ) {
27+ const arg = argv [ i ] ;
28+ if ( ! arg . startsWith ( '--' ) ) continue ;
29+
30+ const key = arg . slice ( 2 ) ;
31+ const value = argv [ i + 1 ] ;
32+
33+ if ( value === undefined || value . startsWith ( '--' ) ) {
34+ console . error ( `Missing value for --${ key } ` ) ;
35+ process . exit ( 1 ) ;
36+ }
37+
38+ acc [ key ] = value ;
39+ i ++ ;
40+ }
2641 return acc ;
27- } , { } ) ;
42+ }
43+
44+ function parseIntegerArg ( value , defaultValue , flagName ) {
45+ if ( value === undefined ) return defaultValue ;
46+ const parsed = parseInt ( value , 10 ) ;
47+ if ( isNaN ( parsed ) ) {
48+ console . error ( `Invalid value for --${ flagName } : ${ value } . Expected an integer.` ) ;
49+ process . exit ( 1 ) ;
50+ }
51+ return parsed ;
52+ }
53+
54+ const args = parseCliArgs ( process . argv . slice ( 2 ) ) ;
2855
2956const RELAY_URL = args . url || 'ws://localhost:8008' ;
30- const TOTAL_ZOMBIES = parseInt ( args . zombies || ' 5000' , 10 ) ;
31- const SPAM_RATE = parseInt ( args [ 'spam-rate' ] || '0' , 10 ) ;
57+ const TOTAL_ZOMBIES = parseIntegerArg ( args . zombies , 5000 , 'zombies' ) ;
58+ const SPAM_RATE = parseIntegerArg ( args [ 'spam-rate' ] , 0 , 'spam-rate' ) ;
3259const BATCH_SIZE = 100 ;
3360const BATCH_DELAY_MS = 50 ;
3461
@@ -70,22 +97,38 @@ function startSpammer() {
7097 const ws = new WebSocket ( RELAY_URL ) ;
7198 const spammerPrivKey = secp256k1 . utils . bytesToHex ( secp256k1 . utils . randomPrivateKey ( ) ) ;
7299 const intervalMs = 1000 / SPAM_RATE ;
100+ let spammerInterval = null ;
101+
102+ function clearSpammerInterval ( ) {
103+ if ( spammerInterval !== null ) {
104+ clearInterval ( spammerInterval ) ;
105+ spammerInterval = null ;
106+ }
107+ }
73108
74109 ws . on ( 'open' , ( ) => {
75110 console . log ( `\n[SPAMMER] Connected. Flooding ${ SPAM_RATE } events/sec...` ) ;
76- setInterval ( async ( ) => {
111+ clearSpammerInterval ( ) ;
112+ spammerInterval = setInterval ( async ( ) => {
113+ if ( ws . readyState !== WebSocket . OPEN ) return ;
114+
77115 const event = await createValidEvent ( spammerPrivKey ) ;
78- ws . send ( JSON . stringify ( [ 'EVENT' , event ] ) ) ;
79- spamSent ++ ;
116+ if ( ws . readyState === WebSocket . OPEN ) {
117+ ws . send ( JSON . stringify ( [ 'EVENT' , event ] ) ) ;
118+ spamSent ++ ;
119+ }
80120 } , intervalMs ) ;
81121 } ) ;
82122
83123 ws . on ( 'close' , ( ) => {
124+ clearSpammerInterval ( ) ;
84125 console . log ( '[SPAMMER] Disconnected. Reconnecting...' ) ;
85126 setTimeout ( startSpammer , 1000 ) ;
86127 } ) ;
87128
88- ws . on ( 'error' , ( ) => { } ) ;
129+ ws . on ( 'error' , ( ) => {
130+ clearSpammerInterval ( ) ;
131+ } ) ;
89132}
90133
91134// ── Zombie Logic ─────────────────────────────────────────────────────────────
@@ -107,6 +150,8 @@ function openZombie() {
107150 if ( ws . _receiver ) {
108151 ws . _receiver . removeAllListeners ( 'ping' ) ;
109152 ws . _receiver . on ( 'ping' , ( ) => { } ) ;
153+ } else {
154+ console . warn ( '[ZOMBIES] Warning: ws._receiver not found. Pong suppression might fail.' ) ;
110155 }
111156 ws . pong = function ( ) { } ;
112157
@@ -117,6 +162,7 @@ function openZombie() {
117162
118163 ws . on ( 'error' , ( err ) => {
119164 errors ++ ;
165+ ws . terminate ( ) ;
120166 resolve ( null ) ;
121167 } ) ;
122168
0 commit comments