message-nexus 1.1.2 → 1.1.4
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 +105 -44
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +75 -17
- package/dist/index.d.ts +75 -17
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@ 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
|
-
- **Structured Logging**: Supports custom log handlers for easy debugging and production monitoring
|
|
25
|
+
- **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
|
+
|
|
26
27
|
- **Resource Management**: All drivers support the `destroy()` method to properly clean up resources.
|
|
27
28
|
|
|
28
29
|
## Quick Start
|
|
@@ -156,20 +157,39 @@ receiverNexus.handle('SYNC_STATE', (params, context) => {
|
|
|
156
157
|
#### Constructor
|
|
157
158
|
|
|
158
159
|
```typescript
|
|
159
|
-
new MessageNexus<
|
|
160
|
+
new MessageNexus<InvokeMap, NotificationMap>(
|
|
160
161
|
driver: BaseDriver,
|
|
161
162
|
options?: MessageNexusOptions
|
|
162
163
|
)
|
|
163
164
|
```
|
|
164
165
|
|
|
166
|
+
**Generics:**
|
|
167
|
+
|
|
168
|
+
- `InvokeMap`: A record mapping method names to `{ params: any; result: any }`. Defaults to `DefaultRegistry`.
|
|
169
|
+
- `NotificationMap`: A record mapping notification method names to their parameter types. Defaults to `Record<string, any>`.
|
|
170
|
+
|
|
165
171
|
**Options:**
|
|
166
172
|
|
|
167
|
-
| Parameter | Type
|
|
168
|
-
| ------------- |
|
|
169
|
-
| instanceId | string
|
|
170
|
-
| timeout | number
|
|
171
|
-
| logger |
|
|
172
|
-
| loggerEnabled | boolean
|
|
173
|
+
| Parameter | Type | Default Value | Description |
|
|
174
|
+
| ------------- | ---------------------------- | -------------- | ------------------------------------------ |
|
|
175
|
+
| instanceId | string | auto-generated | Instance ID, used for message routing |
|
|
176
|
+
| timeout | number | 10000 | Request timeout (milliseconds) |
|
|
177
|
+
| logger | LoggerInterface \| SimpleLogger | new Logger() | Logger instance or simple logger (e.g. `console`) |
|
|
178
|
+
| loggerEnabled | boolean | false | Whether to enable logging |
|
|
179
|
+
| logLevel | LogLevel | LogLevel.INFO | Minimum log level to report |
|
|
180
|
+
|
|
181
|
+
**LogLevel:** `DEBUG`, `INFO`, `WARN`, `ERROR`
|
|
182
|
+
|
|
183
|
+
**SimpleLogger Interface:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
interface SimpleLogger {
|
|
187
|
+
debug(message: string, metadata?: Record<string, unknown>): void
|
|
188
|
+
info(message: string, metadata?: Record<string, unknown>): void
|
|
189
|
+
warn(message: string, metadata?: Record<string, unknown>): void
|
|
190
|
+
error(message: string, metadata?: Record<string, unknown>): void
|
|
191
|
+
}
|
|
192
|
+
```
|
|
173
193
|
|
|
174
194
|
#### Methods
|
|
175
195
|
|
|
@@ -178,15 +198,17 @@ new MessageNexus<RequestPayload, ResponsePayload>(
|
|
|
178
198
|
Send request and wait for response.
|
|
179
199
|
|
|
180
200
|
```typescript
|
|
181
|
-
nexus.invoke<
|
|
201
|
+
nexus.invoke<K extends keyof InvokeMap>(
|
|
202
|
+
methodOrOptions: K | InvokeOptions<K, InvokeMap[K]['params']>
|
|
203
|
+
): Promise<InvokeMap[K]['result']>
|
|
182
204
|
```
|
|
183
205
|
|
|
184
206
|
**Options:**
|
|
185
207
|
|
|
186
208
|
| Parameter | Type | Required | Description |
|
|
187
209
|
| ---------- | ----------------------- | -------- | ---------------------------- |
|
|
188
|
-
| method |
|
|
189
|
-
| params |
|
|
210
|
+
| method | K | Yes | Message method |
|
|
211
|
+
| params | InvokeMap[K]['params'] | No | Request data |
|
|
190
212
|
| to | string | No | Target instance ID |
|
|
191
213
|
| metadata | Record<string, unknown> | No | Metadata |
|
|
192
214
|
| timeout | number | No | Timeout (overrides global) |
|
|
@@ -215,15 +237,17 @@ const result = await nexus.invoke({
|
|
|
215
237
|
Send a one-way notification (Fire-and-Forget). Does not wait for a response and does not generate an ID. Complies with JSON-RPC 2.0 Notification specification.
|
|
216
238
|
|
|
217
239
|
```typescript
|
|
218
|
-
nexus.notify
|
|
240
|
+
nexus.notify<K extends keyof NotificationMap>(
|
|
241
|
+
methodOrOptions: K | NotificationOptions<K, NotificationMap[K]>
|
|
242
|
+
): void
|
|
219
243
|
```
|
|
220
244
|
|
|
221
245
|
**Options:**
|
|
222
246
|
|
|
223
247
|
| Parameter | Type | Required | Description |
|
|
224
248
|
| --------- | ----------------------- | -------- | ------------------- |
|
|
225
|
-
| method |
|
|
226
|
-
| params |
|
|
249
|
+
| method | K | Yes | Notification method |
|
|
250
|
+
| params | NotificationMap[K] | No | Notification data |
|
|
227
251
|
| to | string | No | Target instance ID |
|
|
228
252
|
| metadata | Record<string, unknown> | No | Metadata |
|
|
229
253
|
|
|
@@ -246,7 +270,10 @@ nexus.notify({
|
|
|
246
270
|
Register a request handler for a specific method. The return value (or resolved value of a returned Promise) is automatically sent back as the response.
|
|
247
271
|
|
|
248
272
|
```typescript
|
|
249
|
-
nexus.handle<
|
|
273
|
+
nexus.handle<K extends keyof InvokeMap>(
|
|
274
|
+
method: K,
|
|
275
|
+
handler: InvokeHandler<InvokeMap[K]['params'], InvokeMap[K]['result']>
|
|
276
|
+
): () => void
|
|
250
277
|
```
|
|
251
278
|
|
|
252
279
|
**Parameters:**
|
|
@@ -280,7 +307,10 @@ unsubscribe()
|
|
|
280
307
|
Register a handler for a specific notification method (one-way messages).
|
|
281
308
|
|
|
282
309
|
```typescript
|
|
283
|
-
nexus.onNotification<
|
|
310
|
+
nexus.onNotification<K extends keyof NotificationMap>(
|
|
311
|
+
method: K,
|
|
312
|
+
handler: NotificationHandler<NotificationMap[K]>
|
|
313
|
+
): () => void
|
|
284
314
|
```
|
|
285
315
|
|
|
286
316
|
**Example:**
|
|
@@ -296,22 +326,56 @@ unsubscribe()
|
|
|
296
326
|
|
|
297
327
|
##### onError()
|
|
298
328
|
|
|
299
|
-
Register error handler.
|
|
329
|
+
Register a global error handler for background errors (e.g., driver failures, invalid incoming messages). For request-specific errors, use `try/catch` with `invoke()`.
|
|
300
330
|
|
|
301
331
|
```typescript
|
|
302
|
-
nexus.onError(handler:
|
|
332
|
+
nexus.onError(handler: (error: Error | NexusError, context?: Record<string, unknown>) => void): () => void
|
|
303
333
|
```
|
|
304
334
|
|
|
305
335
|
**Example:**
|
|
306
336
|
|
|
307
337
|
```typescript
|
|
308
338
|
nexus.onError((error, context) => {
|
|
309
|
-
|
|
339
|
+
if (error instanceof NexusError) {
|
|
340
|
+
console.error(`Bridge error [${error.code}]: ${error.message}`, error.data)
|
|
341
|
+
} else {
|
|
342
|
+
console.error('System error:', error.message)
|
|
343
|
+
}
|
|
310
344
|
// Send to error tracking service
|
|
311
345
|
Sentry.captureException(error, { extra: context })
|
|
312
346
|
})
|
|
313
347
|
```
|
|
314
348
|
|
|
349
|
+
#### Errors
|
|
350
|
+
|
|
351
|
+
MessageNexus provides a structured error system based on the JSON-RPC 2.0 specification.
|
|
352
|
+
|
|
353
|
+
##### NexusError
|
|
354
|
+
|
|
355
|
+
A custom error class that includes a numeric code and optional data.
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
class NexusError<D = any> extends Error {
|
|
359
|
+
code: number // JSON-RPC or Nexus-specific error code
|
|
360
|
+
data?: D // Optional additional error information
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
##### NexusErrorCode
|
|
365
|
+
|
|
366
|
+
Common error codes exported by the library:
|
|
367
|
+
|
|
368
|
+
| Code | Name | Description |
|
|
369
|
+
| --- | --- | --- |
|
|
370
|
+
| -32700 | `ParseError` | Invalid JSON received by the server |
|
|
371
|
+
| -32600 | `InvalidRequest` | The JSON sent is not a valid Request object |
|
|
372
|
+
| -32601 | `MethodNotFound` | The method does not exist / is not registered |
|
|
373
|
+
| -32602 | `InvalidParams` | Invalid method parameter(s) |
|
|
374
|
+
| -32603 | `InternalError` | Internal JSON-RPC error (e.g., handler threw an exception) |
|
|
375
|
+
| -32001 | `Timeout` | Request timed out |
|
|
376
|
+
| -32002 | `SendFailed` | Failed to send message via driver |
|
|
377
|
+
| -32003 | `InvalidResponse` | Received a response that doesn't match the request |
|
|
378
|
+
|
|
315
379
|
##### getMetrics()
|
|
316
380
|
|
|
317
381
|
Get monitoring metrics.
|
|
@@ -548,28 +612,33 @@ function onUserConfirm(id: string) {
|
|
|
548
612
|
|
|
549
613
|
### 1. Type Safety
|
|
550
614
|
|
|
551
|
-
MessageNexus uses TypeScript generics to provide full type inference:
|
|
615
|
+
MessageNexus uses TypeScript generics and Schema mapping to provide full type inference across method names, parameters, and results:
|
|
552
616
|
|
|
553
617
|
```typescript
|
|
554
|
-
|
|
555
|
-
|
|
618
|
+
// 1. Define your protocol schemas
|
|
619
|
+
interface MyInvokeMap {
|
|
620
|
+
'getUser': { params: { id: number }; result: { name: string; age: number } };
|
|
621
|
+
'calculate': { params: { a: number; b: number }; result: number };
|
|
556
622
|
}
|
|
557
623
|
|
|
558
|
-
interface
|
|
559
|
-
|
|
560
|
-
name: string
|
|
624
|
+
interface MyNotificationMap {
|
|
625
|
+
'onLog': { message: string; level: 'info' | 'warn' | 'error' };
|
|
561
626
|
}
|
|
562
627
|
|
|
563
|
-
|
|
628
|
+
// 2. Initialize with your schemas
|
|
629
|
+
const nexus = new MessageNexus<MyInvokeMap, MyNotificationMap>(driver)
|
|
564
630
|
|
|
565
|
-
//
|
|
566
|
-
const response = await nexus.invoke({
|
|
567
|
-
|
|
568
|
-
params: { userId: 123 }, // Type: UserRequest
|
|
569
|
-
})
|
|
631
|
+
// 3. Enjoy full type inference and autocompletion
|
|
632
|
+
const response = await nexus.invoke('getUser', { id: 123 })
|
|
633
|
+
// response Type: { name: string; age: number }
|
|
570
634
|
|
|
571
|
-
|
|
572
|
-
|
|
635
|
+
nexus.notify('onLog', { message: 'Ready', level: 'info' })
|
|
636
|
+
|
|
637
|
+
// 4. Type-safe handlers
|
|
638
|
+
nexus.handle('calculate', (params) => {
|
|
639
|
+
// params Type: { a: number; b: number }
|
|
640
|
+
return params.a + params.b // result Type must be number
|
|
641
|
+
})
|
|
573
642
|
```
|
|
574
643
|
|
|
575
644
|
### 2. Memory Safety
|
|
@@ -582,12 +651,13 @@ console.log(response.name)
|
|
|
582
651
|
- **Driver Lifecycle**: Each driver implements the `destroy()` method to correctly release resources
|
|
583
652
|
- **Emitter Isolation**: Recommended to use `createEmitter()` to create independent instances, avoiding memory leaks caused by shared singletons
|
|
584
653
|
|
|
585
|
-
### 3. Error Recovery
|
|
654
|
+
### 3. Error Recovery & Handling
|
|
586
655
|
|
|
587
656
|
- **Auto Reconnect**: WebSocket automatic reconnection mechanism with exponential backoff strategy
|
|
588
657
|
- **Request Retry**: Automatic retry on request failure, configurable retry counts and delays
|
|
589
658
|
- **Message Queue**: Offline message caching, automatically sent after connection recovery
|
|
590
|
-
- **Error
|
|
659
|
+
- **Structured Error Handling**: Dedicated `NexusError` class and standard codes (JSON-RPC 2.0 compatible) for precise diagnostic and fault recovery
|
|
660
|
+
- **Unified Error Callback**: Global `onError` listener for non-request background errors
|
|
591
661
|
|
|
592
662
|
### 4. Security Hardening
|
|
593
663
|
|
|
@@ -611,15 +681,6 @@ console.log(`Avg latency: ${metrics.averageLatency}ms`)
|
|
|
611
681
|
console.log(`Pending: ${metrics.pendingMessages}, Queued: ${metrics.queuedMessages}`)
|
|
612
682
|
```
|
|
613
683
|
|
|
614
|
-
## Testing
|
|
615
|
-
|
|
616
|
-
Run unit tests:
|
|
617
|
-
|
|
618
|
-
```bash
|
|
619
|
-
cd packages/message-nexus
|
|
620
|
-
pnpm test:run
|
|
621
|
-
```
|
|
622
|
-
|
|
623
684
|
## License
|
|
624
685
|
|
|
625
686
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var J=Object.create;var R=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var F=(r,t)=>{for(var e in t)R(r,e,{get:t[e],enumerable:!0})},x=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of W(t))!j.call(r,n)&&n!==e&&R(r,n,{get:()=>t[n],enumerable:!(s=P(t,n))||s.enumerable});return r};var U=(r,t,e)=>(e=r!=null?J(q(r)):{},x(t||!r||!r.__esModule?R(e,"default",{value:r,enumerable:!0}):e,r)),Q=r=>x(R({},"__esModule",{value:!0}),r);var $={};F($,{BaseDriver:()=>c,BroadcastDriver:()=>p,LogLevel:()=>k,MittDriver:()=>f,PostMessageDriver:()=>v,WebSocketDriver:()=>y,createEmitter:()=>T,default:()=>b});module.exports=Q($);var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var S="message-nexus-v1";function G(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===S}var p=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(!G(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 E=Symbol("message_nexus_internal"),f=class extends c{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 I="message-nexus-v1";function A(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===I}var v=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||!A(n.data))return;let{__messageBridge:i,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:I};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var k=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(k||{});function H(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 _(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 m=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 z(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var w=()=>r=>{let e=`[${z()}] [${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 C="message-nexus-v1",y=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 m("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===C){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:C};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 N=U(require("mitt"),1);function T(){return(0,N.default)()}var b=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 m("MessageNexus",s,n),e?.logger&&_(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,M)=>{let O=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,M(new Error(`Message timeout: ${s} (${e})`))},g);this.pendingTasks.set(e,{resolve:L,reject:M,timer:O,timestamp:Date.now()});let B={jsonrpc:"2.0",method:s,params:n,id:e},D={from:this.instanceId,to:i,metadata:{...a,timestamp:Date.now()},payload:B};this._sendMessage(D)}).catch(L=>{if(l<o)return new Promise(M=>setTimeout(()=>M(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 Error(n.error.message);l.code=n.error.code,l.data=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 Error(`Method not found: ${n.method}`);o.code=-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 Error?s:new Error(String(s)),i={jsonrpc:"2.0",id:t,error:{code:n.code||-32e3,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,PostMessageDriver,WebSocketDriver,createEmitter});
|
|
1
|
+
"use strict";var q=Object.create;var b=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var Q=(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))!G.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?q(F(r)):{},E(t||!r||!r.__esModule?b(e,"default",{value:r,enumerable:!0}):e,r)),A=r=>E(b({},"__esModule",{value:!0}),r);var V={};Q(V,{BaseDriver:()=>c,BroadcastDriver:()=>f,LogLevel:()=>I,MittDriver:()=>v,NexusError:()=>u,NexusErrorCode:()=>T,PostMessageDriver:()=>y,WebSocketDriver:()=>M,createEmitter:()=>O,default:()=>k});module.exports=A(V);var c=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(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(!z(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 X(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||!X(n.data))return;let{__messageBridge:i,...g}=n.data;this.onMessage?.(g)},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 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(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 D="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 h("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}};var C=U(require("mitt"),1);function O(){return(0,C.default)()}var T=(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))(T||{}),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 h("MessageNexus",s,n),e?.logger&&N(e.logger)){let i=e.logger;this.logger.addHandler(g=>{let{level:a,message:o,metadata:d}=g;a==="debug"?i.debug(o,d):a==="info"?i.info(o,d):a==="warn"?i.warn(o,d):a==="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),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,i,g,a,o=0,d=1e3;if(typeof t=="string")s=t,n=void 0,i=void 0,g={},a=this.timeout;else{let l=t;s=l.method,n=l.params,i=l.to,g=l.metadata||{},a=l.timeout??this.timeout,o=l.retryCount??0,d=l.retryDelay??1e3}let p=async l=>new Promise((L,R)=>{let B=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,R(new u(`Message timeout: ${s} (${e})`,-32001))},a);this.pendingTasks.set(e,{resolve:L,reject:R,timer:B,timestamp:Date.now()});let P={jsonrpc:"2.0",method:s,params:n,id:e},J={from:this.instanceId,to:i,metadata:{...g,timestamp:Date.now()},payload:P},K=l>=o;this._sendMessage(J,!K)}).catch(L=>{if(l<o)return new Promise(R=>setTimeout(()=>R(p(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,L});return p(0)}_sendMessage(t,e=!1){let s=t.payload,n="method"in s,i="id"in s?String(s.id):void 0,g=n?s.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,n&&i!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:i,type:g})}catch(a){let o=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:o.message,messageId:i}),this.errorHandler?.(o,{message:t}),e?this.logger.debug("Message failed but skipQueue is true (likely retrying)",{messageId:i}):this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:i,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}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}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 g={jsonrpc:"2.0",method:e,params:s},a={from:this.instanceId,to:n,metadata:{...i,timestamp:Date.now()},payload:g};this._sendMessage(a)}_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:g,reject:a,timer:o,timestamp:d}=this.pendingTasks.get(i);clearTimeout(o),this.pendingTasks.delete(i);let p=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=p,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:i,latency:p}),n.error){let l=new u(n.error.message,n.error.code,n.error.data);a(l)}else g(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 g={messageId:i,from:e.from,to:e.to,metadata:e.metadata},a=this.invokeHandlers.get(n.method);if(a)try{let o=await a(n.params,g);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},g=this.notificationHandlers.get(n.method);g&&g.forEach(a=>{try{a(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}},g={from:this.instanceId,to:e,payload:i};this.driver.send(g)}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});
|
package/dist/index.d.cts
CHANGED
|
@@ -31,6 +31,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
|
|
|
31
31
|
type Message = NexusEnvelope;
|
|
32
32
|
declare class BaseDriver {
|
|
33
33
|
onMessage: ((data: Message) => void) | null;
|
|
34
|
+
onConnect: (() => void) | null;
|
|
35
|
+
onDisconnect: (() => void) | null;
|
|
34
36
|
constructor();
|
|
35
37
|
send(data: Message): void;
|
|
36
38
|
destroy(): void;
|
|
@@ -149,18 +151,47 @@ interface MessageNexusOptions {
|
|
|
149
151
|
loggerEnabled?: boolean;
|
|
150
152
|
logLevel?: LogLevel;
|
|
151
153
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Interface representing a method schema with parameters and results.
|
|
156
|
+
*/
|
|
157
|
+
interface MethodSchema {
|
|
158
|
+
params?: any;
|
|
159
|
+
result?: any;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Default registry for method schemas.
|
|
163
|
+
*/
|
|
164
|
+
type DefaultRegistry = Record<string, MethodSchema>;
|
|
165
|
+
/**
|
|
166
|
+
* Helper to extract params from a schema or return any if not present.
|
|
167
|
+
*/
|
|
168
|
+
type GetParams<T> = T extends {
|
|
169
|
+
params: infer P;
|
|
170
|
+
} ? P : any;
|
|
171
|
+
/**
|
|
172
|
+
* Helper to extract result from a schema or return any if not present.
|
|
173
|
+
*/
|
|
174
|
+
type GetResult<T> = T extends {
|
|
175
|
+
result: infer R;
|
|
176
|
+
} ? R : any;
|
|
177
|
+
/**
|
|
178
|
+
* Options for invoking a method.
|
|
179
|
+
*/
|
|
180
|
+
interface InvokeOptions<K extends string = string, P = any> {
|
|
181
|
+
method: K;
|
|
182
|
+
params?: P;
|
|
155
183
|
to?: string;
|
|
156
184
|
metadata?: Record<string, unknown>;
|
|
157
185
|
timeout?: number;
|
|
158
186
|
retryCount?: number;
|
|
159
187
|
retryDelay?: number;
|
|
160
188
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Options for sending a notification.
|
|
191
|
+
*/
|
|
192
|
+
interface NotificationOptions<K extends string = string, P = any> {
|
|
193
|
+
method: K;
|
|
194
|
+
params?: P;
|
|
164
195
|
to?: string;
|
|
165
196
|
metadata?: Record<string, unknown>;
|
|
166
197
|
}
|
|
@@ -170,9 +201,36 @@ interface InvokeContext {
|
|
|
170
201
|
to?: string;
|
|
171
202
|
metadata?: Record<string, unknown>;
|
|
172
203
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Handler for an invoked method.
|
|
206
|
+
*/
|
|
207
|
+
type InvokeHandler<P = any, R = any> = (params: P, context: InvokeContext) => R | Promise<R>;
|
|
208
|
+
/**
|
|
209
|
+
* Handler for a notification.
|
|
210
|
+
*/
|
|
211
|
+
type NotificationHandler<P = any> = (params: P, context: InvokeContext) => void;
|
|
212
|
+
/**
|
|
213
|
+
* Standard JSON-RPC 2.0 and Nexus-specific error codes.
|
|
214
|
+
*/
|
|
215
|
+
declare enum NexusErrorCode {
|
|
216
|
+
ParseError = -32700,
|
|
217
|
+
InvalidRequest = -32600,
|
|
218
|
+
MethodNotFound = -32601,
|
|
219
|
+
InvalidParams = -32602,
|
|
220
|
+
InternalError = -32603,
|
|
221
|
+
Timeout = -32001,
|
|
222
|
+
SendFailed = -32002,
|
|
223
|
+
InvalidResponse = -32003
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Custom error class for MessageNexus.
|
|
227
|
+
*/
|
|
228
|
+
declare class NexusError<D = any> extends Error {
|
|
229
|
+
readonly code: number;
|
|
230
|
+
readonly data?: D;
|
|
231
|
+
constructor(message: string, code?: number, data?: D);
|
|
232
|
+
}
|
|
233
|
+
type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
|
|
176
234
|
interface Metrics {
|
|
177
235
|
messagesSent: number;
|
|
178
236
|
messagesReceived: number;
|
|
@@ -183,7 +241,7 @@ interface Metrics {
|
|
|
183
241
|
averageLatency: number;
|
|
184
242
|
}
|
|
185
243
|
type MetricsCallback = (metrics: Metrics) => void;
|
|
186
|
-
declare class MessageNexus<
|
|
244
|
+
declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
|
|
187
245
|
driver: BaseDriver;
|
|
188
246
|
pendingTasks: Map<string, {
|
|
189
247
|
resolve: (value: any) => void;
|
|
@@ -204,23 +262,23 @@ declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload
|
|
|
204
262
|
private metrics;
|
|
205
263
|
private metricsCallbacks;
|
|
206
264
|
constructor(driver: BaseDriver, options?: MessageNexusOptions);
|
|
207
|
-
invoke<
|
|
265
|
+
invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
|
|
208
266
|
private _sendMessage;
|
|
209
267
|
onError(handler: ErrorHandler): () => void;
|
|
210
268
|
flushQueue(): void;
|
|
211
|
-
notify(methodOrOptions:
|
|
269
|
+
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
|
|
212
270
|
private _validateMessage;
|
|
213
271
|
_handleIncoming(data: unknown): Promise<void>;
|
|
214
272
|
getMetrics(): Metrics;
|
|
215
273
|
onMetrics(callback: MetricsCallback): () => boolean;
|
|
216
274
|
private _notifyMetrics;
|
|
217
|
-
handle<
|
|
218
|
-
removeHandler(method:
|
|
219
|
-
onNotification<
|
|
220
|
-
offNotification(method:
|
|
275
|
+
handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
|
|
276
|
+
removeHandler(method: keyof InvokeMap): void;
|
|
277
|
+
onNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): () => void;
|
|
278
|
+
offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
|
|
221
279
|
private _reply;
|
|
222
280
|
private _replyError;
|
|
223
281
|
destroy(): void;
|
|
224
282
|
}
|
|
225
283
|
|
|
226
|
-
export { BaseDriver, BroadcastDriver, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type Metrics, type MetricsCallback, MittDriver, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
|
284
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
|
|
|
31
31
|
type Message = NexusEnvelope;
|
|
32
32
|
declare class BaseDriver {
|
|
33
33
|
onMessage: ((data: Message) => void) | null;
|
|
34
|
+
onConnect: (() => void) | null;
|
|
35
|
+
onDisconnect: (() => void) | null;
|
|
34
36
|
constructor();
|
|
35
37
|
send(data: Message): void;
|
|
36
38
|
destroy(): void;
|
|
@@ -149,18 +151,47 @@ interface MessageNexusOptions {
|
|
|
149
151
|
loggerEnabled?: boolean;
|
|
150
152
|
logLevel?: LogLevel;
|
|
151
153
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Interface representing a method schema with parameters and results.
|
|
156
|
+
*/
|
|
157
|
+
interface MethodSchema {
|
|
158
|
+
params?: any;
|
|
159
|
+
result?: any;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Default registry for method schemas.
|
|
163
|
+
*/
|
|
164
|
+
type DefaultRegistry = Record<string, MethodSchema>;
|
|
165
|
+
/**
|
|
166
|
+
* Helper to extract params from a schema or return any if not present.
|
|
167
|
+
*/
|
|
168
|
+
type GetParams<T> = T extends {
|
|
169
|
+
params: infer P;
|
|
170
|
+
} ? P : any;
|
|
171
|
+
/**
|
|
172
|
+
* Helper to extract result from a schema or return any if not present.
|
|
173
|
+
*/
|
|
174
|
+
type GetResult<T> = T extends {
|
|
175
|
+
result: infer R;
|
|
176
|
+
} ? R : any;
|
|
177
|
+
/**
|
|
178
|
+
* Options for invoking a method.
|
|
179
|
+
*/
|
|
180
|
+
interface InvokeOptions<K extends string = string, P = any> {
|
|
181
|
+
method: K;
|
|
182
|
+
params?: P;
|
|
155
183
|
to?: string;
|
|
156
184
|
metadata?: Record<string, unknown>;
|
|
157
185
|
timeout?: number;
|
|
158
186
|
retryCount?: number;
|
|
159
187
|
retryDelay?: number;
|
|
160
188
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Options for sending a notification.
|
|
191
|
+
*/
|
|
192
|
+
interface NotificationOptions<K extends string = string, P = any> {
|
|
193
|
+
method: K;
|
|
194
|
+
params?: P;
|
|
164
195
|
to?: string;
|
|
165
196
|
metadata?: Record<string, unknown>;
|
|
166
197
|
}
|
|
@@ -170,9 +201,36 @@ interface InvokeContext {
|
|
|
170
201
|
to?: string;
|
|
171
202
|
metadata?: Record<string, unknown>;
|
|
172
203
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Handler for an invoked method.
|
|
206
|
+
*/
|
|
207
|
+
type InvokeHandler<P = any, R = any> = (params: P, context: InvokeContext) => R | Promise<R>;
|
|
208
|
+
/**
|
|
209
|
+
* Handler for a notification.
|
|
210
|
+
*/
|
|
211
|
+
type NotificationHandler<P = any> = (params: P, context: InvokeContext) => void;
|
|
212
|
+
/**
|
|
213
|
+
* Standard JSON-RPC 2.0 and Nexus-specific error codes.
|
|
214
|
+
*/
|
|
215
|
+
declare enum NexusErrorCode {
|
|
216
|
+
ParseError = -32700,
|
|
217
|
+
InvalidRequest = -32600,
|
|
218
|
+
MethodNotFound = -32601,
|
|
219
|
+
InvalidParams = -32602,
|
|
220
|
+
InternalError = -32603,
|
|
221
|
+
Timeout = -32001,
|
|
222
|
+
SendFailed = -32002,
|
|
223
|
+
InvalidResponse = -32003
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Custom error class for MessageNexus.
|
|
227
|
+
*/
|
|
228
|
+
declare class NexusError<D = any> extends Error {
|
|
229
|
+
readonly code: number;
|
|
230
|
+
readonly data?: D;
|
|
231
|
+
constructor(message: string, code?: number, data?: D);
|
|
232
|
+
}
|
|
233
|
+
type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
|
|
176
234
|
interface Metrics {
|
|
177
235
|
messagesSent: number;
|
|
178
236
|
messagesReceived: number;
|
|
@@ -183,7 +241,7 @@ interface Metrics {
|
|
|
183
241
|
averageLatency: number;
|
|
184
242
|
}
|
|
185
243
|
type MetricsCallback = (metrics: Metrics) => void;
|
|
186
|
-
declare class MessageNexus<
|
|
244
|
+
declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
|
|
187
245
|
driver: BaseDriver;
|
|
188
246
|
pendingTasks: Map<string, {
|
|
189
247
|
resolve: (value: any) => void;
|
|
@@ -204,23 +262,23 @@ declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload
|
|
|
204
262
|
private metrics;
|
|
205
263
|
private metricsCallbacks;
|
|
206
264
|
constructor(driver: BaseDriver, options?: MessageNexusOptions);
|
|
207
|
-
invoke<
|
|
265
|
+
invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
|
|
208
266
|
private _sendMessage;
|
|
209
267
|
onError(handler: ErrorHandler): () => void;
|
|
210
268
|
flushQueue(): void;
|
|
211
|
-
notify(methodOrOptions:
|
|
269
|
+
notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
|
|
212
270
|
private _validateMessage;
|
|
213
271
|
_handleIncoming(data: unknown): Promise<void>;
|
|
214
272
|
getMetrics(): Metrics;
|
|
215
273
|
onMetrics(callback: MetricsCallback): () => boolean;
|
|
216
274
|
private _notifyMetrics;
|
|
217
|
-
handle<
|
|
218
|
-
removeHandler(method:
|
|
219
|
-
onNotification<
|
|
220
|
-
offNotification(method:
|
|
275
|
+
handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
|
|
276
|
+
removeHandler(method: keyof InvokeMap): void;
|
|
277
|
+
onNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): () => void;
|
|
278
|
+
offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
|
|
221
279
|
private _reply;
|
|
222
280
|
private _replyError;
|
|
223
281
|
destroy(): void;
|
|
224
282
|
}
|
|
225
283
|
|
|
226
|
-
export { BaseDriver, BroadcastDriver, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type Metrics, type MetricsCallback, MittDriver, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
|
|
284
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var k="message-nexus-v1";function O(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===k}var v=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(!O(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:k};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"),y=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 x="message-nexus-v1";function B(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===x}var M=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||!B(n.data))return;let{__messageBridge:r,...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 I(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 H(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(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 D(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var R=()=>i=>{let e=`[${D()}] [${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 _="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(R()),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===_){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:_};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 P(){return J()}var E=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&&I(e.logger))this.logger=e.logger;else if(this.logger=new h("MessageNexus",s,n),e?.logger&&H(e.logger)){let r=e.logger;this.logger.addHandler(a=>{let{level:g,message:o,metadata:d}=a;g==="debug"?r.debug(o,d):g==="info"?r.info(o,d):g==="warn"?r.warn(o,d):g==="error"&&r.error(o,d)})}else n&&this.logger.addHandler(R());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,o=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,o=l.retryCount??0,d=l.retryDelay??1e3}let p=async l=>new Promise((b,f)=>{let C=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,f(new Error(`Message timeout: ${s} (${e})`))},g);this.pendingTasks.set(e,{resolve:b,reject:f,timer:C,timestamp:Date.now()});let N={jsonrpc:"2.0",method:s,params:n,id:e},T={from:this.instanceId,to:r,metadata:{...a,timestamp:Date.now()},payload:N};this._sendMessage(T)}).catch(b=>{if(l<o)return new Promise(f=>setTimeout(()=>f(p(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,b});return p(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 o=t;e=o.method,s=o.params,n=o.to,r=o.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:o,timestamp:d}=this.pendingTasks.get(r);clearTimeout(o),this.pendingTasks.delete(r);let p=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=p,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:r,latency:p}),n.error){let l=new Error(n.error.message);l.code=n.error.code,l.data=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 o=await g(n.params,a);this._reply(r,e.from,o)}catch(o){this._replyError(r,e.from,o)}else{let o=new Error(`Method not found: ${n.method}`);o.code=-32601,this._replyError(r,e.from,o)}}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(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},r={from:this.instanceId,to:e,payload:n};this.driver.send(r)}_replyError(t,e,s){let n=s instanceof Error?s:new Error(String(s)),r={jsonrpc:"2.0",id:t,error:{code:n.code||-32e3,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,v as BroadcastDriver,S as LogLevel,y as MittDriver,M as PostMessageDriver,w as WebSocketDriver,P as createEmitter,E as default};
|
|
1
|
+
var c=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=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 P(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||!P(n.data))return;let{__messageBridge:r,...g}=n.data;this.onMessage?.(g)},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 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(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 J(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var b=()=>o=>{let e=`[${J()}] [${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 p("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,...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"),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}};import K from"mitt";function q(){return K()}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 p("MessageNexus",s,n),e?.logger&&_(e.logger)){let r=e.logger;this.logger.addHandler(g=>{let{level:a,message:i,metadata:d}=g;a==="debug"?r.debug(i,d):a==="info"?r.info(i,d):a==="warn"?r.warn(i,d):a==="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),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,r,g,a,i=0,d=1e3;if(typeof t=="string")s=t,n=void 0,r=void 0,g={},a=this.timeout;else{let l=t;s=l.method,n=l.params,r=l.to,g=l.metadata||{},a=l.timeout??this.timeout,i=l.retryCount??0,d=l.retryDelay??1e3}let f=async l=>new Promise((k,v)=>{let D=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,v(new u(`Message timeout: ${s} (${e})`,-32001))},a);this.pendingTasks.set(e,{resolve:k,reject:v,timer:D,timestamp:Date.now()});let C={jsonrpc:"2.0",method:s,params:n,id:e},O={from:this.instanceId,to:r,metadata:{...g,timestamp:Date.now()},payload:C},T=l>=i;this._sendMessage(O,!T)}).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,e=!1){let s=t.payload,n="method"in s,r="id"in s?String(s.id):void 0,g=n?s.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,n&&r!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:r,type:g})}catch(a){let i=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:i.message,messageId:r}),this.errorHandler?.(i,{message:t}),e?this.logger.debug("Message failed but skipQueue is true (likely retrying)",{messageId:r}):this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:r,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}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}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 g={jsonrpc:"2.0",method:e,params:s},a={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:g};this._sendMessage(a)}_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:g,reject:a,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);a(l)}else g(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 g={messageId:r,from:e.from,to:e.to,metadata:e.metadata},a=this.invokeHandlers.get(n.method);if(a)try{let i=await a(n.params,g);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},g=this.notificationHandlers.get(n.method);g&&g.forEach(a=>{try{a(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}},g={from:this.instanceId,to:e,payload:r};this.driver.send(g)}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,q as createEmitter,x as default};
|