gennet.js 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cryptagoEU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # gennet.js
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/gennet.js)](https://www.npmjs.com/package/gennet.js)
4
+ [![npm downloads](https://img.shields.io/npm/dm/gennet.js)](https://www.npmjs.com/package/gennet.js)
5
+ [![CI](https://img.shields.io/github/actions/workflow/status/cryptagoEU/gennet.js/ci.yml?label=CI)](https://github.com/cryptagoEU/gennet.js/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/gennet.js)](https://github.com/cryptagoEU/gennet.js/blob/main/LICENSE)
7
+
8
+ [![ES Version](https://img.shields.io/badge/ES-2022-yellow)](https://github.com/cryptagoEU/gennet.js)
9
+ [![Node Version](https://img.shields.io/badge/node-≥22-green)](https://github.com/cryptagoEU/gennet.js)
10
+
3
11
  Client library for [GenNet](https://github.com/cryptagoEU/gennet.js) — interact with GenNet nodes via JSON-RPC.
4
12
 
5
13
  - Zero runtime dependencies
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ class HttpProvider {
38
38
  }
39
39
  return json.result;
40
40
  }
41
- // HTTP unterstützt keine Push-Notifications
41
+ // HTTP unterstützt keine Events
42
42
  on(_event, _listener) {
43
43
  }
44
44
  off(_event, _listener) {
@@ -48,17 +48,32 @@ class HttpProvider {
48
48
  }
49
49
 
50
50
  const DEFAULT_TIMEOUT = 3e4;
51
+ const DEFAULT_RECONNECT = {
52
+ enabled: true,
53
+ maxRetries: 5,
54
+ delay: 1e3,
55
+ maxDelay: 3e4
56
+ };
51
57
  class WebSocketProvider {
52
58
  url;
53
59
  timeout;
60
+ reconnectOpts;
54
61
  ws = null;
55
62
  requestId = 0;
63
+ reconnectAttempts = 0;
64
+ reconnectTimer = null;
65
+ manualDisconnect = false;
56
66
  pending = /* @__PURE__ */ new Map();
57
- listeners = /* @__PURE__ */ new Set();
67
+ // Typisierte Event-Listener
68
+ notificationListeners = /* @__PURE__ */ new Set();
69
+ connectListeners = /* @__PURE__ */ new Set();
70
+ disconnectListeners = /* @__PURE__ */ new Set();
71
+ errorListeners = /* @__PURE__ */ new Set();
58
72
  connectPromise = null;
59
- constructor(url, timeout = DEFAULT_TIMEOUT) {
73
+ constructor(url, options) {
60
74
  this.url = url;
61
- this.timeout = timeout;
75
+ this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
76
+ this.reconnectOpts = { ...DEFAULT_RECONNECT, ...options?.reconnect };
62
77
  }
63
78
  get connected() {
64
79
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -66,26 +81,36 @@ class WebSocketProvider {
66
81
  async connect() {
67
82
  if (this.connected) return;
68
83
  if (this.connectPromise) return this.connectPromise;
84
+ this.manualDisconnect = false;
69
85
  this.connectPromise = new Promise((resolve, reject) => {
70
86
  this.ws = new WebSocket(this.url);
71
87
  this.ws.onopen = () => {
72
88
  this.connectPromise = null;
89
+ this.reconnectAttempts = 0;
90
+ this.emit("connect");
73
91
  resolve();
74
92
  };
75
93
  this.ws.onerror = (ev) => {
76
94
  this.connectPromise = null;
77
- reject(new Error(`WebSocket-Verbindung fehlgeschlagen: ${this.url} (${ev})`));
95
+ const error = new Error(`WebSocket-Verbindung fehlgeschlagen: ${this.url} (${ev})`);
96
+ this.emit("error", error);
97
+ reject(error);
78
98
  };
79
99
  this.ws.onmessage = (ev) => {
80
100
  this.handleMessage(typeof ev.data === "string" ? ev.data : String(ev.data));
81
101
  };
82
102
  this.ws.onclose = () => {
83
103
  this.connectPromise = null;
104
+ this.ws = null;
84
105
  for (const [id, req] of this.pending) {
85
106
  clearTimeout(req.timer);
86
107
  req.reject(new Error("WebSocket-Verbindung geschlossen"));
87
108
  this.pending.delete(id);
88
109
  }
110
+ this.emit("disconnect");
111
+ if (!this.manualDisconnect && this.reconnectOpts.enabled) {
112
+ this.scheduleReconnect();
113
+ }
89
114
  };
90
115
  });
91
116
  return this.connectPromise;
@@ -105,19 +130,71 @@ class WebSocketProvider {
105
130
  this.ws.send(payload);
106
131
  });
107
132
  }
108
- on(_event, listener) {
109
- this.listeners.add(listener);
133
+ on(event, listener) {
134
+ this.getListenerSet(event).add(listener);
110
135
  }
111
- off(_event, listener) {
112
- this.listeners.delete(listener);
136
+ off(event, listener) {
137
+ this.getListenerSet(event).delete(listener);
113
138
  }
114
139
  disconnect() {
140
+ this.manualDisconnect = true;
141
+ if (this.reconnectTimer) {
142
+ clearTimeout(this.reconnectTimer);
143
+ this.reconnectTimer = null;
144
+ }
115
145
  if (this.ws) {
116
146
  this.ws.close();
117
147
  this.ws = null;
118
148
  }
119
149
  }
120
150
  // ── Private ────────────────────────────────────────────────
151
+ scheduleReconnect() {
152
+ if (this.reconnectAttempts >= this.reconnectOpts.maxRetries) {
153
+ this.emit("error", new Error(
154
+ `Reconnect fehlgeschlagen nach ${this.reconnectOpts.maxRetries} Versuchen`
155
+ ));
156
+ return;
157
+ }
158
+ const delay = Math.min(
159
+ this.reconnectOpts.delay * Math.pow(2, this.reconnectAttempts),
160
+ this.reconnectOpts.maxDelay
161
+ );
162
+ this.reconnectAttempts++;
163
+ this.reconnectTimer = setTimeout(() => {
164
+ this.reconnectTimer = null;
165
+ this.connect().catch((err) => {
166
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
167
+ });
168
+ }, delay);
169
+ }
170
+ emit(event, data) {
171
+ switch (event) {
172
+ case "connect":
173
+ for (const l of this.connectListeners) l();
174
+ break;
175
+ case "disconnect":
176
+ for (const l of this.disconnectListeners) l();
177
+ break;
178
+ case "error":
179
+ for (const l of this.errorListeners) l(data);
180
+ break;
181
+ case "notification":
182
+ for (const l of this.notificationListeners) l(data);
183
+ break;
184
+ }
185
+ }
186
+ getListenerSet(event) {
187
+ switch (event) {
188
+ case "notification":
189
+ return this.notificationListeners;
190
+ case "connect":
191
+ return this.connectListeners;
192
+ case "disconnect":
193
+ return this.disconnectListeners;
194
+ case "error":
195
+ return this.errorListeners;
196
+ }
197
+ }
121
198
  handleMessage(raw) {
122
199
  let msg;
123
200
  try {
@@ -141,10 +218,7 @@ class WebSocketProvider {
141
218
  return;
142
219
  }
143
220
  if ("method" in msg && "params" in msg) {
144
- const notification = msg;
145
- for (const listener of this.listeners) {
146
- listener(notification);
147
- }
221
+ this.emit("notification", msg);
148
222
  }
149
223
  }
150
224
  }
@@ -284,6 +358,14 @@ class GenNet {
284
358
  }
285
359
  };
286
360
  }
361
+ /** Event-Listener registrieren (connect, disconnect, error). */
362
+ on(event, listener) {
363
+ this.provider.on(event, listener);
364
+ }
365
+ /** Event-Listener entfernen. */
366
+ off(event, listener) {
367
+ this.provider.off(event, listener);
368
+ }
287
369
  /** Raw JSON-RPC Request (für erweiterte Nutzung). */
288
370
  async request(method, params) {
289
371
  return this.provider.request(method, params);
package/dist/index.d.cts CHANGED
@@ -82,13 +82,15 @@ interface Subscription {
82
82
  id: string;
83
83
  unsubscribe: () => Promise<boolean>;
84
84
  }
85
+ type ProviderEvent = 'notification' | 'connect' | 'disconnect' | 'error';
86
+ type ProviderEventListener<E extends ProviderEvent> = E extends 'notification' ? (notification: JsonRpcNotification) => void : E extends 'connect' ? () => void : E extends 'disconnect' ? () => void : E extends 'error' ? (error: Error) => void : never;
85
87
  interface Provider {
86
88
  /** JSON-RPC Request senden und auf Response warten. */
87
89
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
88
- /** Listener für Push-Notifications (Subscriptions). */
89
- on(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
- /** Listener entfernen. */
91
- off(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
+ /** Event-Listener registrieren. */
91
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
+ /** Event-Listener entfernen. */
93
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
94
  /** Verbindung schließen. */
93
95
  disconnect(): void;
94
96
  /** Verbindung herstellen (bei WebSocket). */
@@ -96,6 +98,16 @@ interface Provider {
96
98
  /** Ob eine aktive Verbindung besteht. */
97
99
  readonly connected: boolean;
98
100
  }
101
+ interface ReconnectOptions {
102
+ /** Auto-Reconnect aktivieren. Default: true */
103
+ enabled?: boolean;
104
+ /** Maximale Anzahl Reconnect-Versuche. Default: 5 */
105
+ maxRetries?: number;
106
+ /** Initiale Wartezeit in ms (verdoppelt sich pro Versuch). Default: 1000 */
107
+ delay?: number;
108
+ /** Maximale Wartezeit in ms. Default: 30000 */
109
+ maxDelay?: number;
110
+ }
99
111
 
100
112
  /** admin Namespace — Node-Administration. */
101
113
  declare class Admin {
@@ -216,6 +228,10 @@ declare class GenNet {
216
228
  * Topics: 'logs', 'messages', 'mempool'.
217
229
  */
218
230
  subscribe(topic: SubscriptionTopic, callback: (data: unknown) => void): Promise<Subscription>;
231
+ /** Event-Listener registrieren (connect, disconnect, error). */
232
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
233
+ /** Event-Listener entfernen. */
234
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
219
235
  /** Raw JSON-RPC Request (für erweiterte Nutzung). */
220
236
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
221
237
  private static createProvider;
@@ -223,23 +239,36 @@ declare class GenNet {
223
239
 
224
240
  /**
225
241
  * WebSocket Provider — nutzt native WebSocket API (Browser + Node 22+).
226
- * Unterstützt Subscriptions via Push-Notifications.
242
+ * Unterstützt Subscriptions via Push-Notifications und Auto-Reconnect.
227
243
  */
228
244
  declare class WebSocketProvider implements Provider {
229
245
  private readonly url;
230
246
  private readonly timeout;
247
+ private readonly reconnectOpts;
231
248
  private ws;
232
249
  private requestId;
250
+ private reconnectAttempts;
251
+ private reconnectTimer;
252
+ private manualDisconnect;
233
253
  private pending;
234
- private listeners;
254
+ private notificationListeners;
255
+ private connectListeners;
256
+ private disconnectListeners;
257
+ private errorListeners;
235
258
  private connectPromise;
236
- constructor(url: string, timeout?: number);
259
+ constructor(url: string, options?: {
260
+ timeout?: number;
261
+ reconnect?: ReconnectOptions;
262
+ });
237
263
  get connected(): boolean;
238
264
  connect(): Promise<void>;
239
265
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
240
- on(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
241
- off(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
266
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
267
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
242
268
  disconnect(): void;
269
+ private scheduleReconnect;
270
+ private emit;
271
+ private getListenerSet;
243
272
  private handleMessage;
244
273
  }
245
274
 
@@ -252,10 +281,10 @@ declare class HttpProvider implements Provider {
252
281
  constructor(url: string);
253
282
  get connected(): boolean;
254
283
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
255
- on(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
256
- off(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
284
+ on<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
285
+ off<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
257
286
  disconnect(): void;
258
287
  }
259
288
 
260
289
  export { Admin, Agent, GenNet, HttpProvider, Mempool, Net, Personal, RpcError, WebSocketProvider };
261
- export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, Subscription, SubscriptionTopic };
290
+ export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, ProviderEvent, ProviderEventListener, ReconnectOptions, Subscription, SubscriptionTopic };
package/dist/index.d.mts CHANGED
@@ -82,13 +82,15 @@ interface Subscription {
82
82
  id: string;
83
83
  unsubscribe: () => Promise<boolean>;
84
84
  }
85
+ type ProviderEvent = 'notification' | 'connect' | 'disconnect' | 'error';
86
+ type ProviderEventListener<E extends ProviderEvent> = E extends 'notification' ? (notification: JsonRpcNotification) => void : E extends 'connect' ? () => void : E extends 'disconnect' ? () => void : E extends 'error' ? (error: Error) => void : never;
85
87
  interface Provider {
86
88
  /** JSON-RPC Request senden und auf Response warten. */
87
89
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
88
- /** Listener für Push-Notifications (Subscriptions). */
89
- on(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
- /** Listener entfernen. */
91
- off(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
+ /** Event-Listener registrieren. */
91
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
+ /** Event-Listener entfernen. */
93
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
94
  /** Verbindung schließen. */
93
95
  disconnect(): void;
94
96
  /** Verbindung herstellen (bei WebSocket). */
@@ -96,6 +98,16 @@ interface Provider {
96
98
  /** Ob eine aktive Verbindung besteht. */
97
99
  readonly connected: boolean;
98
100
  }
101
+ interface ReconnectOptions {
102
+ /** Auto-Reconnect aktivieren. Default: true */
103
+ enabled?: boolean;
104
+ /** Maximale Anzahl Reconnect-Versuche. Default: 5 */
105
+ maxRetries?: number;
106
+ /** Initiale Wartezeit in ms (verdoppelt sich pro Versuch). Default: 1000 */
107
+ delay?: number;
108
+ /** Maximale Wartezeit in ms. Default: 30000 */
109
+ maxDelay?: number;
110
+ }
99
111
 
100
112
  /** admin Namespace — Node-Administration. */
101
113
  declare class Admin {
@@ -216,6 +228,10 @@ declare class GenNet {
216
228
  * Topics: 'logs', 'messages', 'mempool'.
217
229
  */
218
230
  subscribe(topic: SubscriptionTopic, callback: (data: unknown) => void): Promise<Subscription>;
231
+ /** Event-Listener registrieren (connect, disconnect, error). */
232
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
233
+ /** Event-Listener entfernen. */
234
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
219
235
  /** Raw JSON-RPC Request (für erweiterte Nutzung). */
220
236
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
221
237
  private static createProvider;
@@ -223,23 +239,36 @@ declare class GenNet {
223
239
 
224
240
  /**
225
241
  * WebSocket Provider — nutzt native WebSocket API (Browser + Node 22+).
226
- * Unterstützt Subscriptions via Push-Notifications.
242
+ * Unterstützt Subscriptions via Push-Notifications und Auto-Reconnect.
227
243
  */
228
244
  declare class WebSocketProvider implements Provider {
229
245
  private readonly url;
230
246
  private readonly timeout;
247
+ private readonly reconnectOpts;
231
248
  private ws;
232
249
  private requestId;
250
+ private reconnectAttempts;
251
+ private reconnectTimer;
252
+ private manualDisconnect;
233
253
  private pending;
234
- private listeners;
254
+ private notificationListeners;
255
+ private connectListeners;
256
+ private disconnectListeners;
257
+ private errorListeners;
235
258
  private connectPromise;
236
- constructor(url: string, timeout?: number);
259
+ constructor(url: string, options?: {
260
+ timeout?: number;
261
+ reconnect?: ReconnectOptions;
262
+ });
237
263
  get connected(): boolean;
238
264
  connect(): Promise<void>;
239
265
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
240
- on(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
241
- off(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
266
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
267
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
242
268
  disconnect(): void;
269
+ private scheduleReconnect;
270
+ private emit;
271
+ private getListenerSet;
243
272
  private handleMessage;
244
273
  }
245
274
 
@@ -252,10 +281,10 @@ declare class HttpProvider implements Provider {
252
281
  constructor(url: string);
253
282
  get connected(): boolean;
254
283
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
255
- on(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
256
- off(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
284
+ on<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
285
+ off<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
257
286
  disconnect(): void;
258
287
  }
259
288
 
260
289
  export { Admin, Agent, GenNet, HttpProvider, Mempool, Net, Personal, RpcError, WebSocketProvider };
261
- export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, Subscription, SubscriptionTopic };
290
+ export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, ProviderEvent, ProviderEventListener, ReconnectOptions, Subscription, SubscriptionTopic };
package/dist/index.d.ts CHANGED
@@ -82,13 +82,15 @@ interface Subscription {
82
82
  id: string;
83
83
  unsubscribe: () => Promise<boolean>;
84
84
  }
85
+ type ProviderEvent = 'notification' | 'connect' | 'disconnect' | 'error';
86
+ type ProviderEventListener<E extends ProviderEvent> = E extends 'notification' ? (notification: JsonRpcNotification) => void : E extends 'connect' ? () => void : E extends 'disconnect' ? () => void : E extends 'error' ? (error: Error) => void : never;
85
87
  interface Provider {
86
88
  /** JSON-RPC Request senden und auf Response warten. */
87
89
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
88
- /** Listener für Push-Notifications (Subscriptions). */
89
- on(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
- /** Listener entfernen. */
91
- off(event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
90
+ /** Event-Listener registrieren. */
91
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
+ /** Event-Listener entfernen. */
93
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
92
94
  /** Verbindung schließen. */
93
95
  disconnect(): void;
94
96
  /** Verbindung herstellen (bei WebSocket). */
@@ -96,6 +98,16 @@ interface Provider {
96
98
  /** Ob eine aktive Verbindung besteht. */
97
99
  readonly connected: boolean;
98
100
  }
101
+ interface ReconnectOptions {
102
+ /** Auto-Reconnect aktivieren. Default: true */
103
+ enabled?: boolean;
104
+ /** Maximale Anzahl Reconnect-Versuche. Default: 5 */
105
+ maxRetries?: number;
106
+ /** Initiale Wartezeit in ms (verdoppelt sich pro Versuch). Default: 1000 */
107
+ delay?: number;
108
+ /** Maximale Wartezeit in ms. Default: 30000 */
109
+ maxDelay?: number;
110
+ }
99
111
 
100
112
  /** admin Namespace — Node-Administration. */
101
113
  declare class Admin {
@@ -216,6 +228,10 @@ declare class GenNet {
216
228
  * Topics: 'logs', 'messages', 'mempool'.
217
229
  */
218
230
  subscribe(topic: SubscriptionTopic, callback: (data: unknown) => void): Promise<Subscription>;
231
+ /** Event-Listener registrieren (connect, disconnect, error). */
232
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
233
+ /** Event-Listener entfernen. */
234
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
219
235
  /** Raw JSON-RPC Request (für erweiterte Nutzung). */
220
236
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
221
237
  private static createProvider;
@@ -223,23 +239,36 @@ declare class GenNet {
223
239
 
224
240
  /**
225
241
  * WebSocket Provider — nutzt native WebSocket API (Browser + Node 22+).
226
- * Unterstützt Subscriptions via Push-Notifications.
242
+ * Unterstützt Subscriptions via Push-Notifications und Auto-Reconnect.
227
243
  */
228
244
  declare class WebSocketProvider implements Provider {
229
245
  private readonly url;
230
246
  private readonly timeout;
247
+ private readonly reconnectOpts;
231
248
  private ws;
232
249
  private requestId;
250
+ private reconnectAttempts;
251
+ private reconnectTimer;
252
+ private manualDisconnect;
233
253
  private pending;
234
- private listeners;
254
+ private notificationListeners;
255
+ private connectListeners;
256
+ private disconnectListeners;
257
+ private errorListeners;
235
258
  private connectPromise;
236
- constructor(url: string, timeout?: number);
259
+ constructor(url: string, options?: {
260
+ timeout?: number;
261
+ reconnect?: ReconnectOptions;
262
+ });
237
263
  get connected(): boolean;
238
264
  connect(): Promise<void>;
239
265
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
240
- on(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
241
- off(_event: 'notification', listener: (notification: JsonRpcNotification) => void): void;
266
+ on<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
267
+ off<E extends ProviderEvent>(event: E, listener: ProviderEventListener<E>): void;
242
268
  disconnect(): void;
269
+ private scheduleReconnect;
270
+ private emit;
271
+ private getListenerSet;
243
272
  private handleMessage;
244
273
  }
245
274
 
@@ -252,10 +281,10 @@ declare class HttpProvider implements Provider {
252
281
  constructor(url: string);
253
282
  get connected(): boolean;
254
283
  request(method: string, params?: Record<string, unknown> | unknown[]): Promise<unknown>;
255
- on(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
256
- off(_event: 'notification', _listener: (notification: JsonRpcNotification) => void): void;
284
+ on<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
285
+ off<E extends ProviderEvent>(_event: E, _listener: ProviderEventListener<E>): void;
257
286
  disconnect(): void;
258
287
  }
259
288
 
260
289
  export { Admin, Agent, GenNet, HttpProvider, Mempool, Net, Personal, RpcError, WebSocketProvider };
261
- export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, Subscription, SubscriptionTopic };
290
+ export type { AgentResult, GatewayState, IdentityInfo, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse, ModuleInfo, NodeInfo, PeerInfo, Provider, ProviderEvent, ProviderEventListener, ReconnectOptions, Subscription, SubscriptionTopic };
package/dist/index.mjs CHANGED
@@ -36,7 +36,7 @@ class HttpProvider {
36
36
  }
37
37
  return json.result;
38
38
  }
39
- // HTTP unterstützt keine Push-Notifications
39
+ // HTTP unterstützt keine Events
40
40
  on(_event, _listener) {
41
41
  }
42
42
  off(_event, _listener) {
@@ -46,17 +46,32 @@ class HttpProvider {
46
46
  }
47
47
 
48
48
  const DEFAULT_TIMEOUT = 3e4;
49
+ const DEFAULT_RECONNECT = {
50
+ enabled: true,
51
+ maxRetries: 5,
52
+ delay: 1e3,
53
+ maxDelay: 3e4
54
+ };
49
55
  class WebSocketProvider {
50
56
  url;
51
57
  timeout;
58
+ reconnectOpts;
52
59
  ws = null;
53
60
  requestId = 0;
61
+ reconnectAttempts = 0;
62
+ reconnectTimer = null;
63
+ manualDisconnect = false;
54
64
  pending = /* @__PURE__ */ new Map();
55
- listeners = /* @__PURE__ */ new Set();
65
+ // Typisierte Event-Listener
66
+ notificationListeners = /* @__PURE__ */ new Set();
67
+ connectListeners = /* @__PURE__ */ new Set();
68
+ disconnectListeners = /* @__PURE__ */ new Set();
69
+ errorListeners = /* @__PURE__ */ new Set();
56
70
  connectPromise = null;
57
- constructor(url, timeout = DEFAULT_TIMEOUT) {
71
+ constructor(url, options) {
58
72
  this.url = url;
59
- this.timeout = timeout;
73
+ this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
74
+ this.reconnectOpts = { ...DEFAULT_RECONNECT, ...options?.reconnect };
60
75
  }
61
76
  get connected() {
62
77
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
@@ -64,26 +79,36 @@ class WebSocketProvider {
64
79
  async connect() {
65
80
  if (this.connected) return;
66
81
  if (this.connectPromise) return this.connectPromise;
82
+ this.manualDisconnect = false;
67
83
  this.connectPromise = new Promise((resolve, reject) => {
68
84
  this.ws = new WebSocket(this.url);
69
85
  this.ws.onopen = () => {
70
86
  this.connectPromise = null;
87
+ this.reconnectAttempts = 0;
88
+ this.emit("connect");
71
89
  resolve();
72
90
  };
73
91
  this.ws.onerror = (ev) => {
74
92
  this.connectPromise = null;
75
- reject(new Error(`WebSocket-Verbindung fehlgeschlagen: ${this.url} (${ev})`));
93
+ const error = new Error(`WebSocket-Verbindung fehlgeschlagen: ${this.url} (${ev})`);
94
+ this.emit("error", error);
95
+ reject(error);
76
96
  };
77
97
  this.ws.onmessage = (ev) => {
78
98
  this.handleMessage(typeof ev.data === "string" ? ev.data : String(ev.data));
79
99
  };
80
100
  this.ws.onclose = () => {
81
101
  this.connectPromise = null;
102
+ this.ws = null;
82
103
  for (const [id, req] of this.pending) {
83
104
  clearTimeout(req.timer);
84
105
  req.reject(new Error("WebSocket-Verbindung geschlossen"));
85
106
  this.pending.delete(id);
86
107
  }
108
+ this.emit("disconnect");
109
+ if (!this.manualDisconnect && this.reconnectOpts.enabled) {
110
+ this.scheduleReconnect();
111
+ }
87
112
  };
88
113
  });
89
114
  return this.connectPromise;
@@ -103,19 +128,71 @@ class WebSocketProvider {
103
128
  this.ws.send(payload);
104
129
  });
105
130
  }
106
- on(_event, listener) {
107
- this.listeners.add(listener);
131
+ on(event, listener) {
132
+ this.getListenerSet(event).add(listener);
108
133
  }
109
- off(_event, listener) {
110
- this.listeners.delete(listener);
134
+ off(event, listener) {
135
+ this.getListenerSet(event).delete(listener);
111
136
  }
112
137
  disconnect() {
138
+ this.manualDisconnect = true;
139
+ if (this.reconnectTimer) {
140
+ clearTimeout(this.reconnectTimer);
141
+ this.reconnectTimer = null;
142
+ }
113
143
  if (this.ws) {
114
144
  this.ws.close();
115
145
  this.ws = null;
116
146
  }
117
147
  }
118
148
  // ── Private ────────────────────────────────────────────────
149
+ scheduleReconnect() {
150
+ if (this.reconnectAttempts >= this.reconnectOpts.maxRetries) {
151
+ this.emit("error", new Error(
152
+ `Reconnect fehlgeschlagen nach ${this.reconnectOpts.maxRetries} Versuchen`
153
+ ));
154
+ return;
155
+ }
156
+ const delay = Math.min(
157
+ this.reconnectOpts.delay * Math.pow(2, this.reconnectAttempts),
158
+ this.reconnectOpts.maxDelay
159
+ );
160
+ this.reconnectAttempts++;
161
+ this.reconnectTimer = setTimeout(() => {
162
+ this.reconnectTimer = null;
163
+ this.connect().catch((err) => {
164
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
165
+ });
166
+ }, delay);
167
+ }
168
+ emit(event, data) {
169
+ switch (event) {
170
+ case "connect":
171
+ for (const l of this.connectListeners) l();
172
+ break;
173
+ case "disconnect":
174
+ for (const l of this.disconnectListeners) l();
175
+ break;
176
+ case "error":
177
+ for (const l of this.errorListeners) l(data);
178
+ break;
179
+ case "notification":
180
+ for (const l of this.notificationListeners) l(data);
181
+ break;
182
+ }
183
+ }
184
+ getListenerSet(event) {
185
+ switch (event) {
186
+ case "notification":
187
+ return this.notificationListeners;
188
+ case "connect":
189
+ return this.connectListeners;
190
+ case "disconnect":
191
+ return this.disconnectListeners;
192
+ case "error":
193
+ return this.errorListeners;
194
+ }
195
+ }
119
196
  handleMessage(raw) {
120
197
  let msg;
121
198
  try {
@@ -139,10 +216,7 @@ class WebSocketProvider {
139
216
  return;
140
217
  }
141
218
  if ("method" in msg && "params" in msg) {
142
- const notification = msg;
143
- for (const listener of this.listeners) {
144
- listener(notification);
145
- }
219
+ this.emit("notification", msg);
146
220
  }
147
221
  }
148
222
  }
@@ -282,6 +356,14 @@ class GenNet {
282
356
  }
283
357
  };
284
358
  }
359
+ /** Event-Listener registrieren (connect, disconnect, error). */
360
+ on(event, listener) {
361
+ this.provider.on(event, listener);
362
+ }
363
+ /** Event-Listener entfernen. */
364
+ off(event, listener) {
365
+ this.provider.off(event, listener);
366
+ }
285
367
  /** Raw JSON-RPC Request (für erweiterte Nutzung). */
286
368
  async request(method, params) {
287
369
  return this.provider.request(method, params);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gennet.js",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Client Library for GenNet — interact with GenNet nodes via JSON-RPC",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -27,6 +27,7 @@
27
27
  "dev": "unbuild --stub",
28
28
  "test": "vitest",
29
29
  "lint": "eslint src/",
30
+ "changeset": "changeset",
30
31
  "check": "publint && attw --pack",
31
32
  "prepublishOnly": "npm run build && npm run check"
32
33
  },
@@ -52,9 +53,11 @@
52
53
  "homepage": "https://github.com/cryptagoEU/gennet.js#readme",
53
54
  "devDependencies": {
54
55
  "@arethetypeswrong/cli": "^0.17.0",
56
+ "@changesets/changelog-github": "^0.6.0",
57
+ "@changesets/cli": "^2.30.0",
55
58
  "publint": "^0.3.0",
56
59
  "typescript": "^5.4.0",
57
60
  "unbuild": "^3.0.0",
58
61
  "vitest": "^3.0.0"
59
62
  }
60
- }
63
+ }