message-nexus 1.0.0 → 1.1.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/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  PostMessageDriver: () => PostMessageDriver,
37
37
  WebSocketDriver: () => WebSocketDriver,
38
38
  createEmitter: () => createEmitter,
39
- default: () => MessageBridge
39
+ default: () => MessageNexus
40
40
  });
41
41
  module.exports = __toCommonJS(index_exports);
42
42
 
@@ -94,7 +94,7 @@ var BroadcastDriver = class extends BaseDriver {
94
94
  };
95
95
 
96
96
  // src/drivers/MittDriver.ts
97
- var eventIndicator = "message_bridge_message_event";
97
+ var eventIndicator = "message_nexus_message_event";
98
98
  var MittDriver = class extends BaseDriver {
99
99
  constructor(emitter) {
100
100
  super();
@@ -163,11 +163,26 @@ var PostMessageDriver = class extends BaseDriver {
163
163
  };
164
164
 
165
165
  // src/utils/logger.ts
166
+ function isLogger(value) {
167
+ if (value == null || typeof value !== "object") return false;
168
+ const logger = value;
169
+ return typeof logger.addHandler === "function" && typeof logger.setMinLevel === "function" && typeof logger.enable === "function" && typeof logger.disable === "function" && typeof logger.isEnabled === "function" && typeof logger.debug === "function" && typeof logger.info === "function" && typeof logger.warn === "function" && typeof logger.error === "function";
170
+ }
166
171
  var Logger = class {
167
- constructor(context, minLevel = "info" /* INFO */) {
172
+ constructor(context, minLevel = "info" /* INFO */, enabled = false) {
168
173
  this.handlers = [];
169
174
  this.context = context;
170
175
  this.minLevel = minLevel;
176
+ this.enabled = enabled;
177
+ }
178
+ enable() {
179
+ this.enabled = true;
180
+ }
181
+ disable() {
182
+ this.enabled = false;
183
+ }
184
+ isEnabled() {
185
+ return this.enabled;
171
186
  }
172
187
  addHandler(handler) {
173
188
  this.handlers.push(handler);
@@ -180,7 +195,7 @@ var Logger = class {
180
195
  return levels.indexOf(level) >= levels.indexOf(this.minLevel);
181
196
  }
182
197
  log(level, message, metadata) {
183
- if (!this.shouldLog(level)) return;
198
+ if (!this.enabled || !this.shouldLog(level)) return;
184
199
  const entry = {
185
200
  level,
186
201
  timestamp: Date.now(),
@@ -203,9 +218,19 @@ var Logger = class {
203
218
  this.log("error" /* ERROR */, message, metadata);
204
219
  }
205
220
  };
221
+ function genTimestamp() {
222
+ const now = /* @__PURE__ */ new Date();
223
+ return now.toLocaleTimeString("zh-CN", {
224
+ hour12: false,
225
+ hour: "2-digit",
226
+ minute: "2-digit",
227
+ second: "2-digit",
228
+ fractionalSecondDigits: 3
229
+ });
230
+ }
206
231
  var createConsoleHandler = () => {
207
232
  return (entry) => {
208
- const timestamp = new Date(entry.timestamp).toISOString();
233
+ const timestamp = genTimestamp();
209
234
  const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.context || "app"}]`;
210
235
  const logFn = entry.level === "debug" /* DEBUG */ ? console.debug : entry.level === "info" /* INFO */ ? console.info : entry.level === "warn" /* WARN */ ? console.warn : console.error;
211
236
  if (entry.metadata) {
@@ -231,13 +256,16 @@ var WebSocketDriver = class extends BaseDriver {
231
256
  this.retryInterval = (typeof options.reconnect === "object" ? options.reconnect.retryInterval : void 0) ?? 5e3;
232
257
  this.logger = options.logger || new Logger("WebSocketDriver");
233
258
  this.logger.addHandler(createConsoleHandler());
259
+ this.onStatusChange = options.onStatusChange;
234
260
  this.connect();
235
261
  }
236
262
  connect() {
263
+ this.onStatusChange?.("connecting");
237
264
  this.ws = new WebSocket(this.url);
238
265
  this.ws.addEventListener("open", () => {
239
266
  this.logger.info("WebSocket connected", { url: this.url });
240
267
  this.retryCount = 0;
268
+ this.onStatusChange?.("connected");
241
269
  });
242
270
  this.ws.addEventListener("message", (event) => {
243
271
  try {
@@ -255,6 +283,7 @@ var WebSocketDriver = class extends BaseDriver {
255
283
  });
256
284
  this.ws.addEventListener("error", (event) => {
257
285
  this.logger.error("WebSocket error", { event });
286
+ this.onStatusChange?.("error");
258
287
  });
259
288
  this.ws.addEventListener("close", () => {
260
289
  this.logger.info("WebSocket connection closed", {
@@ -264,6 +293,8 @@ var WebSocketDriver = class extends BaseDriver {
264
293
  });
265
294
  if (!this.isManuallyClosed && this.reconnectEnabled && this.retryCount < this.maxRetries) {
266
295
  this.scheduleReconnect();
296
+ } else {
297
+ this.onStatusChange?.("disconnected");
267
298
  }
268
299
  });
269
300
  }
@@ -276,6 +307,7 @@ var WebSocketDriver = class extends BaseDriver {
276
307
  maxRetries: this.maxRetries,
277
308
  url: this.url
278
309
  });
310
+ this.onStatusChange?.("connecting");
279
311
  this.reconnectTimer = window.setTimeout(() => {
280
312
  this.connect();
281
313
  }, delay);
@@ -306,6 +338,7 @@ var WebSocketDriver = class extends BaseDriver {
306
338
  this.ws.close();
307
339
  this.ws = null;
308
340
  }
341
+ this.onStatusChange?.("disconnected");
309
342
  }
310
343
  destroy() {
311
344
  this.close();
@@ -320,7 +353,7 @@ function createEmitter() {
320
353
  }
321
354
 
322
355
  // src/index.ts
323
- var MessageBridge = class {
356
+ var MessageNexus = class {
324
357
  constructor(driver, options) {
325
358
  this.cleanupInterval = null;
326
359
  this.messageQueue = [];
@@ -339,45 +372,45 @@ var MessageBridge = class {
339
372
  this.driver = driver;
340
373
  this.instanceId = options?.instanceId || crypto.randomUUID();
341
374
  this.timeout = options?.timeout ?? 1e4;
342
- this.logger = options?.logger || new Logger("MessageBridge");
343
- this.logger.addHandler(createConsoleHandler());
375
+ if (options?.logger && isLogger(options.logger)) {
376
+ this.logger = options.logger;
377
+ } else {
378
+ this.logger = new Logger("MessageNexus");
379
+ }
380
+ const loggerEnabled = options?.loggerEnabled ?? false;
381
+ if (loggerEnabled) {
382
+ this.logger.enable();
383
+ this.logger.addHandler(createConsoleHandler());
384
+ this.logger.info("MessageNexus initialized", {
385
+ instanceId: this.instanceId,
386
+ timeout: this.timeout
387
+ });
388
+ }
344
389
  this.pendingTasks = /* @__PURE__ */ new Map();
345
- this.incomingMessages = /* @__PURE__ */ new Map();
346
- this.messageHandlers = /* @__PURE__ */ new Set();
390
+ this.invokeHandlers = /* @__PURE__ */ new Map();
391
+ this.notificationHandlers = /* @__PURE__ */ new Map();
347
392
  this.cleanupInterval = null;
348
393
  this.driver.onMessage = (data) => this._handleIncoming(data);
349
- this.logger.info("MessageBridge initialized", {
350
- instanceId: this.instanceId,
351
- timeout: this.timeout
352
- });
353
- this.cleanupInterval = window.setInterval(() => {
354
- const now = Date.now();
355
- for (const [id, msg] of this.incomingMessages.entries()) {
356
- if (now - msg.timestamp > this.timeout * 2) {
357
- this.incomingMessages.delete(id);
358
- }
359
- }
360
- }, 6e4);
361
394
  }
362
- async request(typeOrOptions) {
395
+ async invoke(methodOrOptions) {
363
396
  const id = crypto.randomUUID();
364
- let type;
365
- let payload;
397
+ let method;
398
+ let params;
366
399
  let to;
367
400
  let metadata;
368
401
  let timeout;
369
402
  let retryCount = 0;
370
403
  let retryDelay = 1e3;
371
- if (typeof typeOrOptions === "string") {
372
- type = typeOrOptions;
373
- payload = void 0;
404
+ if (typeof methodOrOptions === "string") {
405
+ method = methodOrOptions;
406
+ params = void 0;
374
407
  to = void 0;
375
408
  metadata = {};
376
409
  timeout = this.timeout;
377
410
  } else {
378
- const opts = typeOrOptions;
379
- type = opts.type;
380
- payload = opts.payload;
411
+ const opts = methodOrOptions;
412
+ method = opts.method;
413
+ params = opts.params;
381
414
  to = opts.to;
382
415
  metadata = opts.metadata || {};
383
416
  timeout = opts.timeout ?? this.timeout;
@@ -390,16 +423,20 @@ var MessageBridge = class {
390
423
  this.pendingTasks.delete(id);
391
424
  this.metrics.messagesFailed++;
392
425
  this.metrics.pendingMessages--;
393
- reject(new Error(`Message timeout: ${type} (${id})`));
426
+ reject(new Error(`Message timeout: ${method} (${id})`));
394
427
  }, timeout);
395
428
  this.pendingTasks.set(id, { resolve, reject, timer, timestamp: Date.now() });
429
+ const rpcRequest = {
430
+ jsonrpc: "2.0",
431
+ method,
432
+ params,
433
+ id
434
+ };
396
435
  const message = {
397
- id,
398
- type,
399
- payload,
400
436
  from: this.instanceId,
401
437
  to,
402
- metadata: { ...metadata, timestamp: Date.now() }
438
+ metadata: { ...metadata, timestamp: Date.now() },
439
+ payload: rpcRequest
403
440
  };
404
441
  this._sendMessage(message);
405
442
  }).catch((error) => {
@@ -416,20 +453,26 @@ var MessageBridge = class {
416
453
  return attempt(0);
417
454
  }
418
455
  _sendMessage(message) {
456
+ const payload = message.payload;
457
+ const isRequest = "method" in payload;
458
+ const messageId = "id" in payload ? String(payload.id) : void 0;
459
+ const typeOrMethod = isRequest ? payload.method : "RESPONSE";
419
460
  try {
420
461
  this.driver.send(message);
421
462
  this.metrics.messagesSent++;
422
- this.metrics.pendingMessages++;
423
- this.logger.debug("Message sent", { messageId: message.id, type: message.type });
463
+ if (isRequest && messageId !== void 0) {
464
+ this.metrics.pendingMessages++;
465
+ }
466
+ this.logger.debug("Message sent", { messageId, type: typeOrMethod });
424
467
  } catch (error) {
425
468
  const err = error instanceof Error ? error : new Error(String(error));
426
469
  this.metrics.messagesFailed++;
427
- this.logger.error("Failed to send message", { error: err.message, messageId: message.id });
470
+ this.logger.error("Failed to send message", { error: err.message, messageId });
428
471
  this.errorHandler?.(err, { message });
429
472
  if (this.messageQueue.length < this.maxQueueSize) {
430
473
  this.messageQueue.push(message);
431
474
  this.logger.debug("Message queued", {
432
- messageId: message.id,
475
+ messageId,
433
476
  queueSize: this.messageQueue.length + 1
434
477
  });
435
478
  } else {
@@ -462,55 +505,145 @@ var MessageBridge = class {
462
505
  }
463
506
  }
464
507
  }
508
+ notify(methodOrOptions) {
509
+ let method;
510
+ let params;
511
+ let to;
512
+ let metadata;
513
+ if (typeof methodOrOptions === "string") {
514
+ method = methodOrOptions;
515
+ params = void 0;
516
+ to = void 0;
517
+ metadata = {};
518
+ } else {
519
+ const opts = methodOrOptions;
520
+ method = opts.method;
521
+ params = opts.params;
522
+ to = opts.to;
523
+ metadata = opts.metadata || {};
524
+ }
525
+ const rpcNotification = {
526
+ jsonrpc: "2.0",
527
+ method,
528
+ params
529
+ };
530
+ const message = {
531
+ from: this.instanceId,
532
+ to,
533
+ metadata: { ...metadata, timestamp: Date.now() },
534
+ payload: rpcNotification
535
+ };
536
+ this._sendMessage(message);
537
+ }
465
538
  _validateMessage(data) {
466
539
  if (!data || typeof data !== "object") return false;
467
- const msg = data;
468
- if (typeof msg.id !== "string") return false;
469
- if (typeof msg.type !== "string") return false;
470
- if (msg.from && typeof msg.from !== "string") return false;
471
- if (msg.to && typeof msg.to !== "string") return false;
472
- if (msg.metadata && typeof msg.metadata !== "object") return false;
473
- if (msg.isResponse !== void 0 && typeof msg.isResponse !== "boolean") return false;
540
+ const env = data;
541
+ if (typeof env.from !== "string") return false;
542
+ if (env.to !== void 0 && typeof env.to !== "string") return false;
543
+ if (env.metadata !== void 0 && typeof env.metadata !== "object") return false;
544
+ const payload = env.payload;
545
+ if (!payload || typeof payload !== "object") return false;
546
+ if (payload.jsonrpc !== "2.0") return false;
547
+ const isRequest = "method" in payload;
548
+ const isResponse = "result" in payload || "error" in payload;
549
+ if (!isRequest && !isResponse) return false;
474
550
  return true;
475
551
  }
476
- _handleIncoming(data) {
552
+ async _handleIncoming(data) {
477
553
  if (!this._validateMessage(data)) {
478
554
  this.logger.error("Invalid message format received", { data });
479
555
  this.errorHandler?.(new Error("Invalid message format received"), { data });
480
556
  this.metrics.messagesFailed++;
481
557
  return;
482
558
  }
483
- const { id, to, type, payload, isResponse, error, from } = data;
484
- if (to && to !== this.instanceId) {
559
+ const envelope = data;
560
+ const payload = envelope.payload;
561
+ if (envelope.to && envelope.to !== this.instanceId) {
485
562
  this.logger.debug("Message filtered: not for this instance", {
486
- messageId: id,
487
- to,
563
+ messageId: "id" in payload ? payload.id : void 0,
564
+ to: envelope.to,
488
565
  instanceId: this.instanceId
489
566
  });
490
567
  return;
491
568
  }
492
- if (isResponse && this.pendingTasks.has(id)) {
493
- const { resolve, reject, timer, timestamp } = this.pendingTasks.get(id);
494
- clearTimeout(timer);
495
- this.pendingTasks.delete(id);
496
- const latency = Date.now() - timestamp;
497
- this.metrics.messagesReceived++;
498
- this.metrics.pendingMessages--;
499
- this.metrics.totalLatency += latency;
500
- this.metrics.averageLatency = this.metrics.totalLatency / this.metrics.messagesReceived;
501
- this.logger.debug("Response received", { messageId: id, latency });
502
- if (error) reject(error);
503
- else resolve(payload);
504
- this._notifyMetrics();
569
+ if ("result" in payload || "error" in payload) {
570
+ const response = payload;
571
+ const id = String(response.id);
572
+ if (this.pendingTasks.has(id)) {
573
+ const { resolve, reject, timer, timestamp } = this.pendingTasks.get(id);
574
+ clearTimeout(timer);
575
+ this.pendingTasks.delete(id);
576
+ const latency = Date.now() - timestamp;
577
+ this.metrics.messagesReceived++;
578
+ this.metrics.pendingMessages--;
579
+ this.metrics.totalLatency += latency;
580
+ this.metrics.averageLatency = this.metrics.totalLatency / this.metrics.messagesReceived;
581
+ this.logger.debug("Response received", { messageId: id, latency });
582
+ if (response.error) {
583
+ const err = new Error(response.error.message);
584
+ err.code = response.error.code;
585
+ err.data = response.error.data;
586
+ reject(err);
587
+ } else {
588
+ resolve(response.result);
589
+ }
590
+ this._notifyMetrics();
591
+ } else {
592
+ this.logger.warn("Orphaned response received", { messageId: id });
593
+ }
505
594
  return;
506
595
  }
507
- if (isResponse) {
508
- this.logger.warn("Orphaned response received", { messageId: id });
509
- return;
596
+ if ("method" in payload) {
597
+ if ("id" in payload) {
598
+ const request = payload;
599
+ const id = String(request.id);
600
+ this.logger.debug("Invoke message received", {
601
+ messageId: id,
602
+ type: request.method,
603
+ from: envelope.from
604
+ });
605
+ const context = {
606
+ messageId: id,
607
+ from: envelope.from,
608
+ to: envelope.to,
609
+ metadata: envelope.metadata
610
+ };
611
+ const handler = this.invokeHandlers.get(request.method);
612
+ if (handler) {
613
+ try {
614
+ const result = await handler(request.params, context);
615
+ this._reply(id, envelope.from, result);
616
+ } catch (error) {
617
+ this._replyError(id, envelope.from, error);
618
+ }
619
+ } else {
620
+ const err = new Error(`Method not found: ${request.method}`);
621
+ err.code = -32601;
622
+ this._replyError(id, envelope.from, err);
623
+ }
624
+ } else {
625
+ const notification = payload;
626
+ this.logger.debug("Notification message received", {
627
+ type: notification.method,
628
+ from: envelope.from
629
+ });
630
+ const context = {
631
+ from: envelope.from,
632
+ to: envelope.to,
633
+ metadata: envelope.metadata
634
+ };
635
+ const handlers = this.notificationHandlers.get(notification.method);
636
+ if (handlers) {
637
+ handlers.forEach((handler) => {
638
+ try {
639
+ handler(notification.params, context);
640
+ } catch (error) {
641
+ this.logger.error("Error in notification handler", { error: String(error) });
642
+ }
643
+ });
644
+ }
645
+ }
510
646
  }
511
- this.logger.debug("Command message received", { messageId: id, type, from });
512
- this.incomingMessages.set(id, { from, type, timestamp: Date.now() });
513
- this.messageHandlers.forEach((handler) => handler(data));
514
647
  }
515
648
  getMetrics() {
516
649
  return { ...this.metrics, pendingMessages: this.pendingTasks.size };
@@ -523,30 +656,65 @@ var MessageBridge = class {
523
656
  const metrics = this.getMetrics();
524
657
  this.metricsCallbacks.forEach((callback) => callback(metrics));
525
658
  }
526
- onCommand(handler) {
527
- this.messageHandlers.add(handler);
528
- return () => this.messageHandlers.delete(handler);
659
+ handle(method, handler) {
660
+ if (this.invokeHandlers.has(method)) {
661
+ this.logger.warn(`Overriding existing handler for method: ${method}`);
662
+ }
663
+ this.invokeHandlers.set(method, handler);
664
+ return () => this.invokeHandlers.delete(method);
665
+ }
666
+ removeHandler(method) {
667
+ this.invokeHandlers.delete(method);
529
668
  }
530
- reply(messageId, payload, error) {
531
- const incoming = this.incomingMessages.get(messageId);
532
- if (!incoming) {
533
- throw new Error(`Message not found: ${messageId}`);
669
+ onNotification(method, handler) {
670
+ if (!this.notificationHandlers.has(method)) {
671
+ this.notificationHandlers.set(method, /* @__PURE__ */ new Set());
672
+ }
673
+ this.notificationHandlers.get(method).add(handler);
674
+ return () => this.offNotification(method, handler);
675
+ }
676
+ offNotification(method, handler) {
677
+ const handlers = this.notificationHandlers.get(method);
678
+ if (handlers) {
679
+ handlers.delete(handler);
680
+ if (handlers.size === 0) {
681
+ this.notificationHandlers.delete(method);
682
+ }
534
683
  }
535
- const responsePayload = payload;
536
- const responseError = error;
537
- this.driver.send({
684
+ }
685
+ _reply(messageId, to, payload) {
686
+ const rpcResponse = {
687
+ jsonrpc: "2.0",
538
688
  id: messageId,
539
- type: `${incoming.type}_RESPONSE`,
540
- payload: responsePayload,
541
- error: responseError,
542
- isResponse: true,
689
+ result: payload
690
+ };
691
+ const message = {
543
692
  from: this.instanceId,
544
- to: incoming.from
545
- });
546
- this.incomingMessages.delete(messageId);
693
+ to,
694
+ payload: rpcResponse
695
+ };
696
+ this.driver.send(message);
697
+ }
698
+ _replyError(messageId, to, error) {
699
+ const err = error instanceof Error ? error : new Error(String(error));
700
+ const rpcResponse = {
701
+ jsonrpc: "2.0",
702
+ id: messageId,
703
+ error: {
704
+ code: err.code || -32e3,
705
+ message: err.message,
706
+ data: err.data
707
+ }
708
+ };
709
+ const message = {
710
+ from: this.instanceId,
711
+ to,
712
+ payload: rpcResponse
713
+ };
714
+ this.driver.send(message);
547
715
  }
548
716
  destroy() {
549
- this.logger.info("MessageBridge destroying", {
717
+ this.logger.info("MessageNexus destroying", {
550
718
  instanceId: this.instanceId,
551
719
  pendingMessages: this.pendingTasks.size,
552
720
  queuedMessages: this.messageQueue.length,
@@ -557,7 +725,8 @@ var MessageBridge = class {
557
725
  clearInterval(this.cleanupInterval);
558
726
  this.cleanupInterval = null;
559
727
  }
560
- this.messageHandlers.clear();
728
+ this.invokeHandlers.clear();
729
+ this.notificationHandlers.clear();
561
730
  this.metricsCallbacks.clear();
562
731
  }
563
732
  };