message-nexus 1.1.3 → 1.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/README.md +35 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ pnpm add message-nexus
|
|
|
22
22
|
- **Retry Mechanism**: Automatic retry on request failure, configurable retry counts and delays
|
|
23
23
|
- **Message Validation**: Runtime message format validation to prevent illegal messages
|
|
24
24
|
- **Monitoring Metrics**: Built-in message statistics and performance monitoring
|
|
25
|
+
- **Message Interceptors**: Hook into the outgoing request and incoming response pipelines to modify payloads, inject metadata, or implement global logging
|
|
26
|
+
- **Cross-Context Errors**: Preserves error `name` and `stack` traces across communication bridges for seamless debugging
|
|
25
27
|
- **Structured Logging**: Supports adjustable log levels (DEBUG, INFO, WARN, ERROR) and custom log handlers or simple loggers (like `console`) for easy debugging and production monitoring.
|
|
26
|
-
|
|
27
28
|
- **Resource Management**: All drivers support the `destroy()` method to properly clean up resources.
|
|
28
29
|
|
|
29
30
|
## Quick Start
|
|
@@ -346,18 +347,50 @@ nexus.onError((error, context) => {
|
|
|
346
347
|
})
|
|
347
348
|
```
|
|
348
349
|
|
|
350
|
+
##### useRequestInterceptor()
|
|
351
|
+
|
|
352
|
+
Register a hook to intercept and potentially modify outgoing messages before they are sent to the driver. Interceptors can be synchronous or asynchronous.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
nexus.useRequestInterceptor(
|
|
356
|
+
(message: Message) => Message | Promise<Message>
|
|
357
|
+
): () => void
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Example:**
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const unsubscribe = nexus.useRequestInterceptor(async (message) => {
|
|
364
|
+
// Inject an authentication token into the metadata
|
|
365
|
+
message.metadata = { ...message.metadata, token: await getAuthToken() }
|
|
366
|
+
return message
|
|
367
|
+
})
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
##### useResponseInterceptor()
|
|
371
|
+
|
|
372
|
+
Register a hook to intercept and modify incoming messages before they are processed by handlers or resolve pending invokes.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
nexus.useResponseInterceptor(
|
|
376
|
+
(message: Message) => Message | Promise<Message>
|
|
377
|
+
): () => void
|
|
378
|
+
```
|
|
379
|
+
|
|
349
380
|
#### Errors
|
|
350
381
|
|
|
351
382
|
MessageNexus provides a structured error system based on the JSON-RPC 2.0 specification.
|
|
352
383
|
|
|
353
384
|
##### NexusError
|
|
354
385
|
|
|
355
|
-
A custom error class that includes a numeric code and optional data.
|
|
386
|
+
A custom error class that includes a numeric code and optional data. `MessageNexus` automatically preserves and serializes the `name` and `stack` properties of errors thrown in handlers, enabling seamless cross-context debugging.
|
|
356
387
|
|
|
357
388
|
```typescript
|
|
358
389
|
class NexusError<D = any> extends Error {
|
|
359
390
|
code: number // JSON-RPC or Nexus-specific error code
|
|
360
391
|
data?: D // Optional additional error information
|
|
392
|
+
name: string // Preserved error name from original context
|
|
393
|
+
stack?: string // Preserved stack trace from original context
|
|
361
394
|
}
|
|
362
395
|
```
|
|
363
396
|
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var K=Object.create;var b=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var G=(r,t)=>{for(var e in t)b(r,e,{get:t[e],enumerable:!0})},E=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!F.call(r,n)&&n!==e&&b(r,n,{get:()=>t[n],enumerable:!(s=W(t,n))||s.enumerable});return r};var U=(r,t,e)=>(e=r!=null?K(q(r)):{},E(t||!r||!r.__esModule?b(e,"default",{value:r,enumerable:!0}):e,r)),Q=r=>E(b({},"__esModule",{value:!0}),r);var X={};G(X,{BaseDriver:()=>c,BroadcastDriver:()=>f,LogLevel:()=>I,MittDriver:()=>v,NexusError:()=>u,NexusErrorCode:()=>B,PostMessageDriver:()=>y,WebSocketDriver:()=>M,createEmitter:()=>C,default:()=>k});module.exports=Q(X);var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var S="message-nexus-v1";function A(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===S}var f=class extends c{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!A(s.data))return;let{__messageBridge:n,...i}=s.data;this.onMessage?.(i)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:S};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var x=Symbol("message_nexus_internal"),v=class extends c{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(x,e),this.listener=()=>{this.emitter.off(x,e)}}send(t){this.emitter.emit(x,t)}destroy(){this.listener(),this.onMessage=null}};var H="message-nexus-v1";function z(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===H}var y=class extends c{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!z(n.data))return;let{__messageBridge:i,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:H};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var I=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(I||{});function _(r){if(r==null||typeof r!="object")return!1;let t=r;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function N(r){if(r==null||typeof r!="object")return!1;let t=r;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var p=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(i=>i(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function $(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var w=()=>r=>{let e=`[${$()}] [${r.level.toUpperCase()}] [${r.context||"app"}]`,s=r.level==="debug"?console.debug:r.level==="info"?console.info:r.level==="warn"?console.warn:console.error;r.metadata?s(e,r.message,r.metadata):s(e,r.message)};var O="message-nexus-v1",M=class extends c{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new p("WebSocketDriver"),this.logger.addHandler(w()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected")}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===O){let{__messageBridge:n,...i}=s;this.logger.debug("Message received",{data:i}),this.onMessage?.(i)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected")})}scheduleReconnect(){this.retryCount++;let e=this.retryInterval*this.retryCount;this.logger.info("Reconnecting scheduled",{delay:e,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},e)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:O};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected")}destroy(){this.close(),this.onMessage=null}};var T=U(require("mitt"),1);function C(){return(0,T.default)()}var B=(o=>(o[o.ParseError=-32700]="ParseError",o[o.InvalidRequest=-32600]="InvalidRequest",o[o.MethodNotFound=-32601]="MethodNotFound",o[o.InvalidParams=-32602]="InvalidParams",o[o.InternalError=-32603]="InternalError",o[o.Timeout=-32001]="Timeout",o[o.SendFailed=-32002]="SendFailed",o[o.InvalidResponse=-32003]="InvalidResponse",o))(B||{}),u=class r extends Error{constructor(t,e=-32603,s){super(t),this.name="NexusError",this.code=e,this.data=s,Object.setPrototypeOf(this,r.prototype)}},k=class{constructor(t,e){this.cleanupInterval=null;this.messageQueue=[];this.maxQueueSize=100;this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&_(e.logger))this.logger=e.logger;else if(this.logger=new p("MessageNexus",s,n),e?.logger&&N(e.logger)){let i=e.logger;this.logger.addHandler(a=>{let{level:g,message:o,metadata:d}=a;g==="debug"?i.debug(o,d):g==="info"?i.info(o,d):g==="warn"?i.warn(o,d):g==="error"&&i.error(o,d)})}else n&&this.logger.addHandler(w());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.cleanupInterval=null,this.driver.onMessage=i=>this._handleIncoming(i)}async invoke(t){let e=crypto.randomUUID(),s,n,i,a,g,o=0,d=1e3;if(typeof t=="string")s=t,n=void 0,i=void 0,a={},g=this.timeout;else{let l=t;s=l.method,n=l.params,i=l.to,a=l.metadata||{},g=l.timeout??this.timeout,o=l.retryCount??0,d=l.retryDelay??1e3}let h=async l=>new Promise((L,R)=>{let D=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,R(new u(`Message timeout: ${s} (${e})`,-32001))},g);this.pendingTasks.set(e,{resolve:L,reject:R,timer:D,timestamp:Date.now()});let P={jsonrpc:"2.0",method:s,params:n,id:e},J={from:this.instanceId,to:i,metadata:{...a,timestamp:Date.now()},payload:P};this._sendMessage(J)}).catch(L=>{if(l<o)return new Promise(R=>setTimeout(()=>R(h(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,L});return h(0)}_sendMessage(t){let e=t.payload,s="method"in e,n="id"in e?String(e.id):void 0,i=s?e.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,s&&n!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:n,type:i})}catch(a){let g=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:g.message,messageId:n}),this.errorHandler?.(g,{message:t}),this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:n,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(t))}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch{this.messageQueue.unshift(t);break}}}notify(t){let e,s,n,i;if(typeof t=="string")e=t,s=void 0,n=void 0,i={};else{let o=t;e=o.method,s=o.params,n=o.to,i=o.metadata||{}}let a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...i,timestamp:Date.now()},payload:a};this._sendMessage(g)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,i="result"in s||"error"in s;return!(!n&&!i)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t,s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,i=String(n.id);if(this.pendingTasks.has(i)){let{resolve:a,reject:g,timer:o,timestamp:d}=this.pendingTasks.get(i);clearTimeout(o),this.pendingTasks.delete(i);let h=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=h,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:i,latency:h}),n.error){let l=new u(n.error.message,n.error.code,n.error.data);g(l)}else a(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:i});return}if("method"in s)if("id"in s){let n=s,i=String(n.id);this.logger.debug("Invoke message received",{messageId:i,type:n.method,from:e.from});let a={messageId:i,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let o=await g(n.params,a);this._reply(i,e.from,o)}catch(o){this._replyError(i,e.from,o)}else{let o=new u(`Method not found: ${n.method}`,-32601);this._replyError(i,e.from,o)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let i={from:e.from,to:e.to,metadata:e.metadata},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(n.params,i)}catch(o){this.logger.error("Error in notification handler",{error:String(o)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}_reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},i={from:this.instanceId,to:e,payload:n};this.driver.send(i)}_replyError(t,e,s){let n=s instanceof u?s:s instanceof Error?new u(s.message,-32603):new u(String(s),-32603),i={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data}},a={from:this.instanceId,to:e,payload:i};this.driver.send(a)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};0&&(module.exports={BaseDriver,BroadcastDriver,LogLevel,MittDriver,NexusError,NexusErrorCode,PostMessageDriver,WebSocketDriver,createEmitter});
|
|
1
|
+
"use strict";var K=Object.create;var w=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var Q=(i,t)=>{for(var e in t)w(i,e,{get:t[e],enumerable:!0})},x=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!G.call(i,n)&&n!==e&&w(i,n,{get:()=>t[n],enumerable:!(s=W(t,n))||s.enumerable});return i};var U=(i,t,e)=>(e=i!=null?K(F(i)):{},x(t||!i||!i.__esModule?w(e,"default",{value:i,enumerable:!0}):e,i)),A=i=>x(w({},"__esModule",{value:!0}),i);var V={};Q(V,{BaseDriver:()=>l,BroadcastDriver:()=>f,LogLevel:()=>L,MittDriver:()=>v,NexusError:()=>u,NexusErrorCode:()=>T,PostMessageDriver:()=>y,WebSocketDriver:()=>M,createEmitter:()=>C,default:()=>k});module.exports=A(V);var l=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(t){throw new Error("Not implemented")}destroy(){}};var S="message-nexus-v1";function z(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===S}var f=class extends l{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!z(s.data))return;let{__messageBridge:n,...o}=s.data;this.onMessage?.(o)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:S};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var I=Symbol("message_nexus_internal"),v=class extends l{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(I,e),this.listener=()=>{this.emitter.off(I,e)}}send(t){this.emitter.emit(I,t)}destroy(){this.listener(),this.onMessage=null}};var H="message-nexus-v1";function X(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===H}var y=class extends l{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!X(n.data))return;let{__messageBridge:o,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:H};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var L=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(L||{});function _(i){if(i==null||typeof i!="object")return!1;let t=i;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function D(i){if(i==null||typeof i!="object")return!1;let t=i;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var h=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(o=>o(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function $(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var b=()=>i=>{let e=`[${$()}] [${i.level.toUpperCase()}] [${i.context||"app"}]`,s=i.level==="debug"?console.debug:i.level==="info"?console.info:i.level==="warn"?console.warn:console.error;i.metadata?s(e,i.message,i.metadata):s(e,i.message)};var N="message-nexus-v1",M=class extends l{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new h("WebSocketDriver"),this.logger.addHandler(b()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===N){let{__messageBridge:n,...o}=s;this.logger.debug("Message received",{data:o}),this.onMessage?.(o)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let e=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,e);this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:N};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};var O=U(require("mitt"),1);function C(){return(0,O.default)()}var T=(r=>(r[r.ParseError=-32700]="ParseError",r[r.InvalidRequest=-32600]="InvalidRequest",r[r.MethodNotFound=-32601]="MethodNotFound",r[r.InvalidParams=-32602]="InvalidParams",r[r.InternalError=-32603]="InternalError",r[r.Timeout=-32001]="Timeout",r[r.SendFailed=-32002]="SendFailed",r[r.InvalidResponse=-32003]="InvalidResponse",r))(T||{}),u=class i extends Error{constructor(t,e=-32603,s,n,o){super(t),this.name=n||"NexusError",this.code=e,this.data=s,o&&(this.stack=o),Object.setPrototypeOf(this,i.prototype)}},k=class{constructor(t,e){this.messageQueue=[];this.maxQueueSize=100;this.requestInterceptors=[];this.responseInterceptors=[];this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&_(e.logger))this.logger=e.logger;else if(this.logger=new h("MessageNexus",s,n),e?.logger&&D(e.logger)){let o=e.logger;this.logger.addHandler(a=>{let{level:g,message:r,metadata:d}=a;g==="debug"?o.debug(r,d):g==="info"?o.info(r,d):g==="warn"?o.warn(r,d):g==="error"&&o.error(r,d)})}else n&&this.logger.addHandler(b());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.driver.onMessage=o=>this._handleIncoming(o),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,o,a,g,r=0,d=1e3;if(typeof t=="string")s=t,n=void 0,o=void 0,a={},g=this.timeout;else{let c=t;s=c.method,n=c.params,o=c.to,a=c.metadata||{},g=c.timeout??this.timeout,r=c.retryCount??0,d=c.retryDelay??1e3}let m=async c=>new Promise((E,R)=>{let B=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,R(new u(`Message timeout: ${s} (${e})`,-32001))},g);this.pendingTasks.set(e,{resolve:E,reject:R,timer:B,timestamp:Date.now()});let q={jsonrpc:"2.0",method:s,params:n,id:e},P={from:this.instanceId,to:o,metadata:{...a,timestamp:Date.now()},payload:q},J=c>=r;this._sendMessage(P,!J).catch(()=>{})}).catch(E=>{if(c<r)return new Promise(R=>setTimeout(()=>R(m(c+1)),d*(c+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,E});return m(0)}async _sendMessage(t,e=!1){let s=t;try{for(let r of this.requestInterceptors)s=await r(s)}catch(r){this.logger.error("Request interceptor failed",{error:String(r)}),this.metrics.messagesFailed++,this.errorHandler?.(r instanceof Error?r:new Error(String(r)),{message:s});return}let n=s.payload,o="method"in n,a="id"in n?String(n.id):void 0,g=o?n.method:"RESPONSE";try{this.driver.send(s),this.metrics.messagesSent++,o&&a!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:a,type:g})}catch(r){let d=r instanceof Error?r:new Error(String(r));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:d.message,messageId:a}),this.errorHandler?.(d,{message:s});let m=typeof DOMException<"u"&&r instanceof DOMException&&r.name==="DataCloneError";!e&&!m?this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(s),this.logger.debug("Message queued",{messageId:a,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(s)):this.logger.debug(m?"Message dropped due to data error":"Message failed but skipQueue is true (likely retrying)",{messageId:a})}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}useRequestInterceptor(t){return this.requestInterceptors.push(t),()=>{this.requestInterceptors=this.requestInterceptors.filter(e=>e!==t)}}useResponseInterceptor(t){return this.responseInterceptors.push(t),()=>{this.responseInterceptors=this.responseInterceptors.filter(e=>e!==t)}}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch(e){if(typeof DOMException<"u"&&e instanceof DOMException&&e.name==="DataCloneError"){this.logger.error("Message payload cannot be cloned during flush, dropping",{error:e.message});continue}this.messageQueue.unshift(t);break}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}async notify(t){let e,s,n,o;if(typeof t=="string")e=t,s=void 0,n=void 0,o={};else{let r=t;e=r.method,s=r.params,n=r.to,o=r.metadata||{}}let a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...o,timestamp:Date.now()},payload:a};await this._sendMessage(g)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,o="result"in s||"error"in s;return!(!n&&!o)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t;try{for(let n of this.responseInterceptors)e=await n(e)}catch(n){this.logger.error("Response interceptor failed",{error:String(n)}),this.metrics.messagesFailed++,this.errorHandler?.(n instanceof Error?n:new Error(String(n)),{message:e});return}let s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,o=String(n.id);if(this.pendingTasks.has(o)){let{resolve:a,reject:g,timer:r,timestamp:d}=this.pendingTasks.get(o);clearTimeout(r),this.pendingTasks.delete(o);let m=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=m,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:o,latency:m}),n.error){let c=new u(n.error.message,n.error.code,n.error.data,n.error.name,n.error.stack);g(c)}else a(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:o});return}if("method"in s)if("id"in s){let n=s,o=String(n.id);this.logger.debug("Invoke message received",{messageId:o,type:n.method,from:e.from});let a={messageId:o,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let r=await g(n.params,a);await this._reply(o,e.from,r)}catch(r){await this._replyError(o,e.from,r)}else{let r=new u(`Method not found: ${n.method}`,-32601);await this._replyError(o,e.from,r)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let o={from:e.from,to:e.to,metadata:e.metadata},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(n.params,o)}catch(r){this.logger.error("Error in notification handler",{error:String(r)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}async _reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},o={from:this.instanceId,to:e,payload:n};await this._sendMessage(o)}async _replyError(t,e,s){let n=s instanceof u?s:s instanceof Error?new u(s.message,-32603,void 0,s.name,s.stack):new u(String(s),-32603),o={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data,name:n.name,stack:n.stack}},a={from:this.instanceId,to:e,payload:o};await this._sendMessage(a)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};0&&(module.exports={BaseDriver,BroadcastDriver,LogLevel,MittDriver,NexusError,NexusErrorCode,PostMessageDriver,WebSocketDriver,createEmitter});
|
package/dist/index.d.cts
CHANGED
|
@@ -19,6 +19,8 @@ interface JsonRpcResponse {
|
|
|
19
19
|
code: number;
|
|
20
20
|
message: string;
|
|
21
21
|
data?: unknown;
|
|
22
|
+
name?: string;
|
|
23
|
+
stack?: string;
|
|
22
24
|
};
|
|
23
25
|
id: JsonRpcId;
|
|
24
26
|
}
|
|
@@ -31,6 +33,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
|
|
|
31
33
|
type Message = NexusEnvelope;
|
|
32
34
|
declare class BaseDriver {
|
|
33
35
|
onMessage: ((data: Message) => void) | null;
|
|
36
|
+
onConnect: (() => void) | null;
|
|
37
|
+
onDisconnect: (() => void) | null;
|
|
34
38
|
constructor();
|
|
35
39
|
send(data: Message): void;
|
|
36
40
|
destroy(): void;
|
|
@@ -226,9 +230,11 @@ declare enum NexusErrorCode {
|
|
|
226
230
|
declare class NexusError<D = any> extends Error {
|
|
227
231
|
readonly code: number;
|
|
228
232
|
readonly data?: D;
|
|
229
|
-
constructor(message: string, code?: number, data?: D);
|
|
233
|
+
constructor(message: string, code?: number, data?: D, name?: string, stack?: string);
|
|
230
234
|
}
|
|
231
235
|
type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
|
|
236
|
+
type RequestInterceptor = (message: Message) => Message | Promise<Message>;
|
|
237
|
+
type ResponseInterceptor = (message: Message) => Message | Promise<Message>;
|
|
232
238
|
interface Metrics {
|
|
233
239
|
messagesSent: number;
|
|
234
240
|
messagesReceived: number;
|
|
@@ -252,9 +258,10 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
252
258
|
notificationHandlers: Map<string, Set<NotificationHandler>>;
|
|
253
259
|
timeout: number;
|
|
254
260
|
instanceId: string;
|
|
255
|
-
private cleanupInterval;
|
|
256
261
|
private messageQueue;
|
|
257
262
|
private maxQueueSize;
|
|
263
|
+
private requestInterceptors;
|
|
264
|
+
private responseInterceptors;
|
|
258
265
|
private errorHandler;
|
|
259
266
|
private logger;
|
|
260
267
|
private metrics;
|
|
@@ -262,9 +269,11 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
262
269
|
constructor(driver: BaseDriver, options?: MessageNexusOptions);
|
|
263
270
|
invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
|
|
264
271
|
private _sendMessage;
|
|
272
|
+
useRequestInterceptor(interceptor: RequestInterceptor): () => void;
|
|
273
|
+
useResponseInterceptor(interceptor: ResponseInterceptor): () => void;
|
|
265
274
|
onError(handler: ErrorHandler): () => void;
|
|
266
275
|
flushQueue(): void;
|
|
267
|
-
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void
|
|
276
|
+
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): Promise<void>;
|
|
268
277
|
private _validateMessage;
|
|
269
278
|
_handleIncoming(data: unknown): Promise<void>;
|
|
270
279
|
getMetrics(): Metrics;
|
|
@@ -279,4 +288,4 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
279
288
|
destroy(): void;
|
|
280
289
|
}
|
|
281
290
|
|
|
282
|
-
export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
|
291
|
+
export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type RequestInterceptor, type ResponseInterceptor, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ interface JsonRpcResponse {
|
|
|
19
19
|
code: number;
|
|
20
20
|
message: string;
|
|
21
21
|
data?: unknown;
|
|
22
|
+
name?: string;
|
|
23
|
+
stack?: string;
|
|
22
24
|
};
|
|
23
25
|
id: JsonRpcId;
|
|
24
26
|
}
|
|
@@ -31,6 +33,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
|
|
|
31
33
|
type Message = NexusEnvelope;
|
|
32
34
|
declare class BaseDriver {
|
|
33
35
|
onMessage: ((data: Message) => void) | null;
|
|
36
|
+
onConnect: (() => void) | null;
|
|
37
|
+
onDisconnect: (() => void) | null;
|
|
34
38
|
constructor();
|
|
35
39
|
send(data: Message): void;
|
|
36
40
|
destroy(): void;
|
|
@@ -226,9 +230,11 @@ declare enum NexusErrorCode {
|
|
|
226
230
|
declare class NexusError<D = any> extends Error {
|
|
227
231
|
readonly code: number;
|
|
228
232
|
readonly data?: D;
|
|
229
|
-
constructor(message: string, code?: number, data?: D);
|
|
233
|
+
constructor(message: string, code?: number, data?: D, name?: string, stack?: string);
|
|
230
234
|
}
|
|
231
235
|
type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
|
|
236
|
+
type RequestInterceptor = (message: Message) => Message | Promise<Message>;
|
|
237
|
+
type ResponseInterceptor = (message: Message) => Message | Promise<Message>;
|
|
232
238
|
interface Metrics {
|
|
233
239
|
messagesSent: number;
|
|
234
240
|
messagesReceived: number;
|
|
@@ -252,9 +258,10 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
252
258
|
notificationHandlers: Map<string, Set<NotificationHandler>>;
|
|
253
259
|
timeout: number;
|
|
254
260
|
instanceId: string;
|
|
255
|
-
private cleanupInterval;
|
|
256
261
|
private messageQueue;
|
|
257
262
|
private maxQueueSize;
|
|
263
|
+
private requestInterceptors;
|
|
264
|
+
private responseInterceptors;
|
|
258
265
|
private errorHandler;
|
|
259
266
|
private logger;
|
|
260
267
|
private metrics;
|
|
@@ -262,9 +269,11 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
262
269
|
constructor(driver: BaseDriver, options?: MessageNexusOptions);
|
|
263
270
|
invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
|
|
264
271
|
private _sendMessage;
|
|
272
|
+
useRequestInterceptor(interceptor: RequestInterceptor): () => void;
|
|
273
|
+
useResponseInterceptor(interceptor: ResponseInterceptor): () => void;
|
|
265
274
|
onError(handler: ErrorHandler): () => void;
|
|
266
275
|
flushQueue(): void;
|
|
267
|
-
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void
|
|
276
|
+
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): Promise<void>;
|
|
268
277
|
private _validateMessage;
|
|
269
278
|
_handleIncoming(data: unknown): Promise<void>;
|
|
270
279
|
getMetrics(): Metrics;
|
|
@@ -279,4 +288,4 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
|
|
|
279
288
|
destroy(): void;
|
|
280
289
|
}
|
|
281
290
|
|
|
282
|
-
export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
|
291
|
+
export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type RequestInterceptor, type ResponseInterceptor, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var I="message-nexus-v1";function B(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===I}var y=class extends c{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!B(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:I};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var L=Symbol("message_nexus_internal"),M=class extends c{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(L,e),this.listener=()=>{this.emitter.off(L,e)}}send(t){this.emitter.emit(L,t)}destroy(){this.listener(),this.onMessage=null}};var E="message-nexus-v1";function D(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===E}var R=class extends c{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!D(n.data))return;let{__messageBridge:r,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:E};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var S=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(S||{});function H(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function _(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var h=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(r=>r(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function P(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var b=()=>o=>{let e=`[${P()}] [${o.level.toUpperCase()}] [${o.context||"app"}]`,s=o.level==="debug"?console.debug:o.level==="info"?console.info:o.level==="warn"?console.warn:console.error;o.metadata?s(e,o.message,o.metadata):s(e,o.message)};var N="message-nexus-v1",w=class extends c{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new h("WebSocketDriver"),this.logger.addHandler(b()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected")}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===N){let{__messageBridge:n,...r}=s;this.logger.debug("Message received",{data:r}),this.onMessage?.(r)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected")})}scheduleReconnect(){this.retryCount++;let e=this.retryInterval*this.retryCount;this.logger.info("Reconnecting scheduled",{delay:e,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},e)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:N};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected")}destroy(){this.close(),this.onMessage=null}};import J from"mitt";function K(){return J()}var W=(i=>(i[i.ParseError=-32700]="ParseError",i[i.InvalidRequest=-32600]="InvalidRequest",i[i.MethodNotFound=-32601]="MethodNotFound",i[i.InvalidParams=-32602]="InvalidParams",i[i.InternalError=-32603]="InternalError",i[i.Timeout=-32001]="Timeout",i[i.SendFailed=-32002]="SendFailed",i[i.InvalidResponse=-32003]="InvalidResponse",i))(W||{}),u=class o extends Error{constructor(t,e=-32603,s){super(t),this.name="NexusError",this.code=e,this.data=s,Object.setPrototypeOf(this,o.prototype)}},x=class{constructor(t,e){this.cleanupInterval=null;this.messageQueue=[];this.maxQueueSize=100;this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&H(e.logger))this.logger=e.logger;else if(this.logger=new h("MessageNexus",s,n),e?.logger&&_(e.logger)){let r=e.logger;this.logger.addHandler(a=>{let{level:g,message:i,metadata:d}=a;g==="debug"?r.debug(i,d):g==="info"?r.info(i,d):g==="warn"?r.warn(i,d):g==="error"&&r.error(i,d)})}else n&&this.logger.addHandler(b());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.cleanupInterval=null,this.driver.onMessage=r=>this._handleIncoming(r)}async invoke(t){let e=crypto.randomUUID(),s,n,r,a,g,i=0,d=1e3;if(typeof t=="string")s=t,n=void 0,r=void 0,a={},g=this.timeout;else{let l=t;s=l.method,n=l.params,r=l.to,a=l.metadata||{},g=l.timeout??this.timeout,i=l.retryCount??0,d=l.retryDelay??1e3}let f=async l=>new Promise((k,v)=>{let O=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,v(new u(`Message timeout: ${s} (${e})`,-32001))},g);this.pendingTasks.set(e,{resolve:k,reject:v,timer:O,timestamp:Date.now()});let T={jsonrpc:"2.0",method:s,params:n,id:e},C={from:this.instanceId,to:r,metadata:{...a,timestamp:Date.now()},payload:T};this._sendMessage(C)}).catch(k=>{if(l<i)return new Promise(v=>setTimeout(()=>v(f(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,k});return f(0)}_sendMessage(t){let e=t.payload,s="method"in e,n="id"in e?String(e.id):void 0,r=s?e.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,s&&n!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:n,type:r})}catch(a){let g=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:g.message,messageId:n}),this.errorHandler?.(g,{message:t}),this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:n,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(t))}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch{this.messageQueue.unshift(t);break}}}notify(t){let e,s,n,r;if(typeof t=="string")e=t,s=void 0,n=void 0,r={};else{let i=t;e=i.method,s=i.params,n=i.to,r=i.metadata||{}}let a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:a};this._sendMessage(g)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,r="result"in s||"error"in s;return!(!n&&!r)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t,s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,r=String(n.id);if(this.pendingTasks.has(r)){let{resolve:a,reject:g,timer:i,timestamp:d}=this.pendingTasks.get(r);clearTimeout(i),this.pendingTasks.delete(r);let f=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=f,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:r,latency:f}),n.error){let l=new u(n.error.message,n.error.code,n.error.data);g(l)}else a(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:r});return}if("method"in s)if("id"in s){let n=s,r=String(n.id);this.logger.debug("Invoke message received",{messageId:r,type:n.method,from:e.from});let a={messageId:r,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let i=await g(n.params,a);this._reply(r,e.from,i)}catch(i){this._replyError(r,e.from,i)}else{let i=new u(`Method not found: ${n.method}`,-32601);this._replyError(r,e.from,i)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let r={from:e.from,to:e.to,metadata:e.metadata},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(n.params,r)}catch(i){this.logger.error("Error in notification handler",{error:String(i)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}_reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},r={from:this.instanceId,to:e,payload:n};this.driver.send(r)}_replyError(t,e,s){let n=s instanceof u?s:s instanceof Error?new u(s.message,-32603):new u(String(s),-32603),r={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data}},a={from:this.instanceId,to:e,payload:r};this.driver.send(a)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};export{c as BaseDriver,y as BroadcastDriver,S as LogLevel,M as MittDriver,u as NexusError,W as NexusErrorCode,R as PostMessageDriver,w as WebSocketDriver,K as createEmitter,x as default};
|
|
1
|
+
var d=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(t){throw new Error("Not implemented")}destroy(){}};var L="message-nexus-v1";function B(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===L}var y=class extends d{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!B(s.data))return;let{__messageBridge:n,...i}=s.data;this.onMessage?.(i)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:L};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var E=Symbol("message_nexus_internal"),M=class extends d{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(E,e),this.listener=()=>{this.emitter.off(E,e)}}send(t){this.emitter.emit(E,t)}destroy(){this.listener(),this.onMessage=null}};var x="message-nexus-v1";function q(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===x}var R=class extends d{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!q(n.data))return;let{__messageBridge:i,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:x};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var S=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(S||{});function H(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function _(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var f=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(i=>i(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function P(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var w=()=>o=>{let e=`[${P()}] [${o.level.toUpperCase()}] [${o.context||"app"}]`,s=o.level==="debug"?console.debug:o.level==="info"?console.info:o.level==="warn"?console.warn:console.error;o.metadata?s(e,o.message,o.metadata):s(e,o.message)};var D="message-nexus-v1",b=class extends d{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new f("WebSocketDriver"),this.logger.addHandler(w()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===D){let{__messageBridge:n,...i}=s;this.logger.debug("Message received",{data:i}),this.onMessage?.(i)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let e=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,e);this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:D};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};import J from"mitt";function K(){return J()}var W=(r=>(r[r.ParseError=-32700]="ParseError",r[r.InvalidRequest=-32600]="InvalidRequest",r[r.MethodNotFound=-32601]="MethodNotFound",r[r.InvalidParams=-32602]="InvalidParams",r[r.InternalError=-32603]="InternalError",r[r.Timeout=-32001]="Timeout",r[r.SendFailed=-32002]="SendFailed",r[r.InvalidResponse=-32003]="InvalidResponse",r))(W||{}),m=class o extends Error{constructor(t,e=-32603,s,n,i){super(t),this.name=n||"NexusError",this.code=e,this.data=s,i&&(this.stack=i),Object.setPrototypeOf(this,o.prototype)}},I=class{constructor(t,e){this.messageQueue=[];this.maxQueueSize=100;this.requestInterceptors=[];this.responseInterceptors=[];this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&H(e.logger))this.logger=e.logger;else if(this.logger=new f("MessageNexus",s,n),e?.logger&&_(e.logger)){let i=e.logger;this.logger.addHandler(a=>{let{level:g,message:r,metadata:l}=a;g==="debug"?i.debug(r,l):g==="info"?i.info(r,l):g==="warn"?i.warn(r,l):g==="error"&&i.error(r,l)})}else n&&this.logger.addHandler(w());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.driver.onMessage=i=>this._handleIncoming(i),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,i,a,g,r=0,l=1e3;if(typeof t=="string")s=t,n=void 0,i=void 0,a={},g=this.timeout;else{let c=t;s=c.method,n=c.params,i=c.to,a=c.metadata||{},g=c.timeout??this.timeout,r=c.retryCount??0,l=c.retryDelay??1e3}let u=async c=>new Promise((k,v)=>{let N=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,v(new m(`Message timeout: ${s} (${e})`,-32001))},g);this.pendingTasks.set(e,{resolve:k,reject:v,timer:N,timestamp:Date.now()});let O={jsonrpc:"2.0",method:s,params:n,id:e},C={from:this.instanceId,to:i,metadata:{...a,timestamp:Date.now()},payload:O},T=c>=r;this._sendMessage(C,!T).catch(()=>{})}).catch(k=>{if(c<r)return new Promise(v=>setTimeout(()=>v(u(c+1)),l*(c+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,k});return u(0)}async _sendMessage(t,e=!1){let s=t;try{for(let r of this.requestInterceptors)s=await r(s)}catch(r){this.logger.error("Request interceptor failed",{error:String(r)}),this.metrics.messagesFailed++,this.errorHandler?.(r instanceof Error?r:new Error(String(r)),{message:s});return}let n=s.payload,i="method"in n,a="id"in n?String(n.id):void 0,g=i?n.method:"RESPONSE";try{this.driver.send(s),this.metrics.messagesSent++,i&&a!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:a,type:g})}catch(r){let l=r instanceof Error?r:new Error(String(r));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:l.message,messageId:a}),this.errorHandler?.(l,{message:s});let u=typeof DOMException<"u"&&r instanceof DOMException&&r.name==="DataCloneError";!e&&!u?this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(s),this.logger.debug("Message queued",{messageId:a,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(s)):this.logger.debug(u?"Message dropped due to data error":"Message failed but skipQueue is true (likely retrying)",{messageId:a})}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}useRequestInterceptor(t){return this.requestInterceptors.push(t),()=>{this.requestInterceptors=this.requestInterceptors.filter(e=>e!==t)}}useResponseInterceptor(t){return this.responseInterceptors.push(t),()=>{this.responseInterceptors=this.responseInterceptors.filter(e=>e!==t)}}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch(e){if(typeof DOMException<"u"&&e instanceof DOMException&&e.name==="DataCloneError"){this.logger.error("Message payload cannot be cloned during flush, dropping",{error:e.message});continue}this.messageQueue.unshift(t);break}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}async notify(t){let e,s,n,i;if(typeof t=="string")e=t,s=void 0,n=void 0,i={};else{let r=t;e=r.method,s=r.params,n=r.to,i=r.metadata||{}}let a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...i,timestamp:Date.now()},payload:a};await this._sendMessage(g)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,i="result"in s||"error"in s;return!(!n&&!i)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t;try{for(let n of this.responseInterceptors)e=await n(e)}catch(n){this.logger.error("Response interceptor failed",{error:String(n)}),this.metrics.messagesFailed++,this.errorHandler?.(n instanceof Error?n:new Error(String(n)),{message:e});return}let s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,i=String(n.id);if(this.pendingTasks.has(i)){let{resolve:a,reject:g,timer:r,timestamp:l}=this.pendingTasks.get(i);clearTimeout(r),this.pendingTasks.delete(i);let u=Date.now()-l;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=u,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:i,latency:u}),n.error){let c=new m(n.error.message,n.error.code,n.error.data,n.error.name,n.error.stack);g(c)}else a(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:i});return}if("method"in s)if("id"in s){let n=s,i=String(n.id);this.logger.debug("Invoke message received",{messageId:i,type:n.method,from:e.from});let a={messageId:i,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let r=await g(n.params,a);await this._reply(i,e.from,r)}catch(r){await this._replyError(i,e.from,r)}else{let r=new m(`Method not found: ${n.method}`,-32601);await this._replyError(i,e.from,r)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let i={from:e.from,to:e.to,metadata:e.metadata},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(n.params,i)}catch(r){this.logger.error("Error in notification handler",{error:String(r)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}async _reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},i={from:this.instanceId,to:e,payload:n};await this._sendMessage(i)}async _replyError(t,e,s){let n=s instanceof m?s:s instanceof Error?new m(s.message,-32603,void 0,s.name,s.stack):new m(String(s),-32603),i={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data,name:n.name,stack:n.stack}},a={from:this.instanceId,to:e,payload:i};await this._sendMessage(a)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};export{d as BaseDriver,y as BroadcastDriver,S as LogLevel,M as MittDriver,m as NexusError,W as NexusErrorCode,R as PostMessageDriver,b as WebSocketDriver,K as createEmitter,I as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "message-nexus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A unified, type-safe, multi-protocol cross-context message communication library",
|
|
6
6
|
"author": "wuyax",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^24.10.9",
|
|
40
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
40
41
|
"tsup": "^8.4.0",
|
|
41
42
|
"typescript": "~5.9.3",
|
|
42
43
|
"vitest": "^4.0.18",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
"dev": "tsup --watch",
|
|
59
60
|
"test": "vitest",
|
|
60
61
|
"test:run": "vitest run",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
61
63
|
"type-check": "tsc --noEmit"
|
|
62
64
|
}
|
|
63
65
|
}
|