New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

message-bridge-js

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

message-bridge-js - npm Package Compare versions

Comparing version 0.2.2 to 0.9.0

dist/MessageBridgeHelper.d.ts

124

CHANGELOG.md

@@ -1,65 +0,117 @@

## 0.1.12
## 0.9.0
Add onClose
(Close to first 1.0 release)
## 0.1.11
Total rewrite of internals, but the API is almost the same.
Export alle message bridge types from index.js (index.ts),
to avoid multiple import of message bridge elements
- Add promise pattern (in addition to callback)\*
- Add requestMessage to response/resolve callback (Tracked version)\*
- Add full tests for all features, including full flow test.
- Removed _subscribeQuery\*\*_
## 0.1.10
\*Full track for handling of multiple of (the same or different) requests, so it's possible to use Promise.all etc.
Add debugLogging filters:
```ts
// before: (and now! you can still do it this way)
bridge.sendQuery({ name, payload, onSuccess, onError })
- messageReceivedFilter = undefined (undefined | string RegExp)
- sendingMessageFilter = undefined (undefined | string RegExp)
// new option:
const response = await bridge.sendQuery({
name,
payload,
onSuccess,
onError,
})
// new option tracked:
const { response, request, responseMessage, requestMessage } =
await bridge.sendQueryTracked({
name,
payload,
onSuccess,
onError,
})
```
It filter's message names (Only log messages with matching name)
### Breaking changes
```ts
// EX:
messageBridge.debugLogging.messageReceivedFilter = /^area/
```
- Export **MessageBridgeService** has been removed (it was an alias for **SignalRMessageBridgeService**)
## 0.1.9
Use **SignalRMessageBridgeService**, **WebSocketMessageBridgeService** or **ClientSideMessageBridgeService** instead.
module (as optional string) is added to the message interface (and create methods)
Almost no changes to the base: **sendQuery**, **sendCommand**, **sendEvent**, **subscribeEvent**
## 0.1.5 message-bridge-js@commonjs
- **onSuccess** and **onError** methods are identical to the new tracked versions: RequestResponse<TRequest,TResponse>)
Publish a commonjs build under tag @commonjs
```ts
// before
bridge.sendQuery({
name,
payload,
onSuccess(response, responseMsg) {},
})
// after
bridge.sendQuery({
name,
payload,
onSuccess(response, { response, request, responseMsg, requestMsg }) {},
})
// or the new tracked:
bridge.sendQueryTracked({
name,
payload,
onSuccess({ response, request, responseMsg, requestMsg }) {},
})
// or the new tracked promise pattern:
const { response, request, responseMsg, requestMsg } = await bridge.sendQueryTracked({
name,
payload,
})
```
To install `npm i message-bridge-js@commonjs` or `yarn add message-bridge-js@commonjs`
- **subscribeQuery** is removed !
## 0.1.0-4
_\*\*subscribeQuery was more or less unnecessary abstraction of subscribeEvent, and it do not support the new promise pattern._
_There are en example in the example folder for subscribe query is needed_
Split into a base class `MessageBridgeServiceBase` an different network implementations.
- "helper" (static methods, _createMessage_ etc..) has been moved to a separate file _(Not part of the class anymore)_
There are now two network implementations:
- **Message** is no longer a class, but an typescript type
- SignalRMessageBridgeService (MessageBridgeService)
- WebSocketConnectionService
Optimize Message by removing "class" version and only use interface (typescript type).
The exprted `MessageBridgeService` is the SignalRMessageBridgeService - `So no real changes except the onError`
Notes:
### Breaking changes
**trackId** is optional but it will be create by createMessage, createCommandMessage and createQueryMessage. _(Helper methods)_
- onError now take an 'Error' instead of a 'string'
You can still create the id, but it's not recommended.
## 0.0.10
> The **trackId** must be unique for each message, and is generated by the createMessage helper method. _(Using 'uuid' module)_
Add **onError** on subscribeQuery
If you need the message (fx its trackId) before it's sent:
## 0.0.5
```ts
// Normal use
bridge.sendCommand({ name, payload })
Add **options?: IHttpConnectionOptions** on **connect** method
// If trackId is needed before sent (Or other properties from the created message)
const msg = createCommandMessage({ name, payload, module })
bridge.sendMessage({
requestMessage: msg,
})
```
## 0.0.2
### Optional breaking changes
Add cdn build (for direct use in a browser)
Almost all code is rewritten, so if you have extended the base class, there are a lot of changes.
https://unpkg.com/message-bridge-js/cdn-build/MessageBridgeService.js
```ts
// Take a look at the base, but it's important to call super.methodName()
// for most of the protected methods, at least for the ones that are called:
onConnect()
onError()
onClose()
```
## 0.0.1
## < 0.2.0
Initial version
See git history for changes before 0.2.0

@@ -1,6 +0,8 @@

export { SignalRMessageBridgeService as MessageBridgeService } from "./SignalRMessageBridgeService";
export { WebsocketMessageBridgeService } from "./WebsocketMessageBridgeService";
export { SignalRMessageBridgeService } from "./services/SignalRMessageBridgeService";
export { WebsocketMessageBridgeService } from "./services/WebsocketMessageBridgeService";
export { ClientSideMessageBridgeService } from "./services/ClientSideMessageBridgeService";
export { InMemoryClientSideServer } from "./services/InMemoryClientSideServer";
export * from "./MessageBridgeServiceBase";
export * from "./Message";
export * from "./MessageBridgeInterfaces";
export * from "./MessageBridgeHelper";
export * from "./MessageBridgeTypes";
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,2 @@

var e=require("@microsoft/signalr"),s=require("uuid");function t(e){if(e&&e.__esModule)return e;var s=Object.create(null);return e&&Object.keys(e).forEach(function(t){if("default"!==t){var r=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(s,t,r.get?r:{enumerable:!0,get:function(){return e[t]}})}}),s.default=e,s}var r,i,o=/*#__PURE__*/t(e);exports.MessageType=void 0,(r=exports.MessageType||(exports.MessageType={})).Command="Command",r.CommandResponse="CommandResponse",r.Query="Query",r.QueryResponse="QueryResponse",r.Event="Event",r.Error="Error",exports.MessageDirection=void 0,(i=exports.MessageDirection||(exports.MessageDirection={})).ToClient="ToClient",i.ToServer="ToServer";class n{constructor(e,t,r,i,o=s.v4(),n=new Date(Date.now()).toJSON(),a=exports.MessageDirection.ToServer,c){this.module=void 0,this.name=void 0,this.type=void 0,this.isError=void 0,this.trackId=void 0,this.created=void 0,this.payload=void 0,this.schema=void 0,this.direction=void 0,this.module=c,this.name=e,this.type=t,this.trackId=o,this.created=n,this.payload=r,this.schema=i,this.direction=a,this.isError=t===exports.MessageType.Error}static create(e){return new n(e.name,e.type,e.payload,e.schema,e.trackId,e.created,e.direction,e.module)}static fromDto(e,s=exports.MessageDirection.ToClient){return new n(e.name,e.type,e.payload,e.schema,e.trackId,e.created,s,e.module)}}class a{constructor(e){this.wsUri=void 0,this.connected=!1,this.connection=void 0,this.debugLogger=window?.console.log||(()=>{}),this.debugLogging={messageReceived:!1,sendingMessage:!1,messageReceivedFilter:void 0,sendingMessageFilter:void 0},this.subscriptionTrackIdList={},this.subscriptionEventList={},this.subscriptionQuery=[],this.history=[],this.bridgeErrors=[],this.wsUri=e}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(s){return this.onError(s),void console.log("Incorrect message received: "+e)}try{const e=n.fromDto(s);if(this.debugLogging.messageReceived){let s=!0;this.debugLogging.messageReceivedFilter&&(s=!!e.name.match(this.debugLogging.messageReceivedFilter)),s&&this.debugLogger("Bridge (messageReceived): ",e)}this.handleIncomingMessage(e)}catch(e){console.log("Error in response handle for message: "+e)}}sendMessage(e,s,t){e.direction=exports.MessageDirection.ToServer,(s||t)&&(this.subscriptionTrackIdList[e.trackId]={onSuccess:s,onError:t}),this.internalSendMessage(e)}internalSendMessage(e){if(this.history.push(e),this.debugLogging.sendingMessage){let s=!0;this.debugLogging.sendingMessageFilter&&(s=!!e.name.match(this.debugLogging.sendingMessageFilter)),s&&this.debugLogger("Bridge (sendingMessage): ",e)}this.sendNetworkMessage(e)}subscribeEvent({name:e,onEvent:s}){return this.subscriptionEventList[e]||(this.subscriptionEventList[e]=[]),this.subscriptionEventList[e].push(s),()=>{const t=this.subscriptionEventList[e].findIndex(e=>e===s);this.subscriptionEventList[e].splice(t,1)}}createCommandMessage(e,s,t=exports.MessageDirection.ToServer,r){return n.create({name:e,type:exports.MessageType.Command,payload:s,direction:t,module:r})}createQueryMessage(e,s,t=exports.MessageDirection.ToServer,r){return n.create({name:e,type:exports.MessageType.Query,payload:s,direction:t,module:r})}createEventMessage(e,s,t=exports.MessageDirection.ToServer,r){return n.create({name:e,type:exports.MessageType.Event,payload:s,direction:t,module:r})}sendCommand({name:e,payload:s,onSuccess:t,onError:r,module:i}){const o=this.createCommandMessage(e,s,void 0,i);return this.sendMessage(o,t,r),o}sendQuery({name:e,payload:s,onSuccess:t,onError:r,module:i}){const o=this.createQueryMessage(e,s,void 0,i);return this.sendMessage(o,t,r),o}sendEvent({name:e,payload:s,module:t}){const r=this.createEventMessage(e,s,void 0,t);return this.sendMessage(r),r}subscribeQuery(e){return this.sendQuery({name:e.name,payload:e.query,onSuccess:e.onUpdate,onError:e.onError,module:e.module}),this.subscriptionQuery.push(e),()=>{const s=this.subscriptionQuery.findIndex(s=>s===e);s>0&&this.subscriptionQuery.splice(s,1)}}onError(e){this.bridgeErrors.push(e)}handleIncomingMessage(e){this.history.push(e),this.subscriptionTrackIdList[e.trackId]&&(e.type===exports.MessageType.Error?this.subscriptionTrackIdList[e.trackId].onError?.(e.payload,e):this.subscriptionTrackIdList[e.trackId].onSuccess?.(e.payload,e),delete this.subscriptionTrackIdList[e.trackId]),e.type===exports.MessageType.Event&&this.receiveEventMessage(e)}receiveEventMessage(e){this.subscriptionEventList[e.name]&&this.subscriptionEventList[e.name].forEach(s=>s(e.payload,e)),this.subscriptionQuery.filter(s=>s.triggers?.some(s=>s===e.name)??!1).forEach(e=>{const s=this.createQueryMessage(e.name,e.query);this.sendMessage(s,e.onUpdate,e.onError)})}}exports.Message=n,exports.MessageBridgeService=class extends a{connect(e={}){return this.connection=(new o.HubConnectionBuilder).withUrl(this.wsUri,e).withAutomaticReconnect().build(),this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.start().then(()=>{this.connected=!0}).catch(e=>{this.onError(e)})}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}},exports.MessageBridgeServiceBase=a,exports.WebsocketMessageBridgeService=class extends a{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.connected=!0,e()})})}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}};
var e,s,t=require("@microsoft/signalr"),r=require("uuid");function o(e,s=exports.MessageDirection.ToClient){return e.isError=e.type===exports.MessageType.Error,e.direction??=s,e}function n(e){return{name:e.name,payload:e.payload,type:e.type,direction:e.direction??exports.MessageDirection.ToClient,trackId:e.trackId??r.v4(),module:e.module,schema:e.schema,created:(new Date).toISOString(),isError:e.type===exports.MessageType.Error}}function i(e){return n({...e,type:exports.MessageType.Command})}function a(e){return n({...e,type:exports.MessageType.Query})}function c(e){return n({...e,type:exports.MessageType.Event,payload:void 0})}exports.MessageType=void 0,(e=exports.MessageType||(exports.MessageType={})).Command="Command",e.CommandResponse="CommandResponse",e.Query="Query",e.QueryResponse="QueryResponse",e.Event="Event",e.Error="Error",exports.MessageDirection=void 0,(s=exports.MessageDirection||(exports.MessageDirection={})).ToClient="ToClient",s.ToServer="ToServer";class d{constructor(e){this.wsUri=void 0,this.connected=!1,this.subscribedTrackIdMap={},this.subscribedEventListMap={},this.history=[],this.bridgeErrors=[],this.options={timeout:void 0,keepHistoryForReceivedMessages:!1,keepHistoryForSendingMessages:!1,logger:()=>console?.log??(()=>{}),logParseIncomingMessageError:!0,timeoutFromBridgeOptionsMessage:e=>`Timeout after ${e}ms (BridgeOptions.timeout)`,timeoutFromRequestOptionsMessage:e=>`Timeout after ${e}ms (RequestOptions.timeout)`,logParseIncomingMessageErrorFormat:e=>["Bridge-Error (parse messageReceived):",e],logMessageReceived:!1,logMessageReceivedFormat:e=>["Bridge (messageReceived):",e],logSendingMessage:!1,logSendingMessageFormat:e=>["Bridge (sendingMessage):",e]},this.wsUri=e}setOptions(e){this.options={...this.options,...e}}getTrackedRequestMessage(e){return this.subscribedTrackIdMap[e]?.requestMessage}onConnect(){this.connected=!0,this.options.onConnect?.()}onError(e,s){void 0!==e&&this.bridgeErrors.push(e),this.options.onError?.(e,s)}onClose(e,s){void 0!==e&&this.bridgeErrors.push(e),this.connected=!1,this.options.onClose?.(e,s)}setOptionalRequestTimeout({requestMessage:e,timeout:s,onTimeout:t}){let r,o;if(void 0!==this.options.timeout&&(r=this.options.timeoutFromBridgeOptionsMessage?.(this.options.timeout)??`timeout after ${this.options.timeout}`,o=this.options.timeout),void 0!==s&&(r=this.options.timeoutFromRequestOptionsMessage?.(s)??`timeout after ${s}`,o=s),void 0!==o)return setTimeout(()=>{t({reason:r,responseMessage:void 0,request:e.payload,requestMessage:e})},o)}sendMessageTracked(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r)=>{e&&this.options.throwOnTrackedError?t(r):s(r)}})}).finally(()=>{delete this.subscribedTrackIdMap[e.requestMessage.trackId]})}sendMessage(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r,o)=>{e?this.options.avoidThrowOnNonTrackedError?s(o?.payload):t(r):s(r.response)}})})}sendMessagePromiseHandler({handleResponseReject:e,requestMessage:s,onSuccess:t,onError:r,timeout:o}){s.direction=exports.MessageDirection.ToServer,this.options.interceptSendMessage&&(s=this.options.interceptSendMessage(s));const n=t=>{i&&clearTimeout(i);const o={response:void 0,responseMessage:void 0,request:s.payload,requestMessage:s,isError:!0,error:t.reason,errorMessage:t.responseMessage};this.onError(o),r?.(o),e(!0,o,t)},i=this.setOptionalRequestTimeout({requestMessage:s,timeout:o,onTimeout:e=>{n(e)}});this.subscribedTrackIdMap[s.trackId]={successTrack:r=>{const o={response:r.payload,responseMessage:r,request:s.payload,requestMessage:s,isError:!1};i&&clearTimeout(i),t?.(o),e(!1,o,void 0)},errorTrack:e=>{n({reason:e?.payload,responseMessage:e,request:s.payload,requestMessage:s})},requestMessage:s},this.internalSendMessage(s)}subscribeEvent({name:e,onEvent:s}){if(Array.isArray(e)){const t=e.map(e=>this.subscribeEvent({name:e,onEvent:s}));return()=>t.forEach(e=>e())}return this.subscribedEventListMap[e]||(this.subscribedEventListMap[e]=[]),this.subscribedEventListMap[e].push(s),()=>{const t=this.subscribedEventListMap[e].findIndex(e=>e===s);this.subscribedEventListMap[e].splice(t,1)}}sendCommand(e){const s=i(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendCommandTracked(e){const s=i(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQuery(e){const s=a(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQueryTracked(e){const s=a(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendEvent(e){let s=c(e);return s.direction=exports.MessageDirection.ToServer,this.options.interceptSendMessage&&(s=this.options.interceptSendMessage(s)),this.internalSendMessage(s),s}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(e){return void this.onError(e)}try{let e=o(s);this.options.interceptReceivedMessage&&(e=this.options.interceptReceivedMessage(e)),this.handleIncomingMessage(e)}catch(e){if(this.onError(e),this.options?.logger&&this.options?.logParseIncomingMessageError){const t=this.options?.logParseIncomingMessageErrorFormat?.(s)??[e];this.options.logger(t)}}}internalSendMessage(e){if(this.options.keepHistoryForSendingMessages&&this.history.push(e),this.options?.logger&&this.options?.logSendingMessage){let s=!0;if(this.options?.logSendingMessageFilter&&(s=!!e.name.match(this.options?.logSendingMessageFilter)),s){const s=this.options?.logSendingMessageFormat?.(e)??[e];this.options.logger(...s)}}this.options.onSend?.(e),this.sendNetworkMessage(e)}handleIncomingMessage(e){if(this.options.keepHistoryForReceivedMessages&&this.history.push(e),this.options?.logger&&this.options?.logMessageReceived){let s=!0;if(this.options?.logMessageReceivedFilter&&(s=!!e.name.match(this.options?.logMessageReceivedFilter)),s){const s=this.options?.logMessageReceivedFormat?.(e)??[e];this.options.logger(...s)}}this.options.onMessage?.(e);let s=e.type!==exports.MessageType.Error;if(e.type===exports.MessageType.Event)return void this.receiveEventMessage(e);const t=this.subscribedTrackIdMap[e.trackId];t&&(e.type===exports.MessageType.Error?(t.errorTrack?.(e),s=!0):t.successTrack?.(e),delete this.subscribedTrackIdMap[e.trackId]),s||this.onError?.(e)}receiveEventMessage(e){this.subscribedEventListMap[e.name]&&this.subscribedEventListMap[e.name].forEach(s=>s(e.payload,e))}}exports.ClientSideMessageBridgeService=class extends d{constructor(...e){super(...e),this.server=void 0}setServer(e){this.server=e}connect(){if(!this.server)throw new Error("No server set");return this.server?.connect(e=>{this.onMessage(e),this.onConnect()}),Promise.resolve()}close(){this.onClose()}sendNetworkMessage(e){setTimeout(()=>{this.server?.onMessage(e)},10)}},exports.InMemoryClientSideServer=class{constructor(){this.store={},this.commands={},this.queries={},this.eventListeners={},this.sendMessage=void 0}saveToLocalStorage(e){localStorage.setItem(e,JSON.stringify(this.store))}loadFromLocalStorage(e){const s=localStorage.getItem(e);s&&(this.store=JSON.parse(s))}connect(e){this.sendMessage=e}sendError(e,s){const t=n({trackId:s,type:exports.MessageType.Error,name:"Error",payload:e,direction:exports.MessageDirection.ToClient});this.sendMessage?.(t)}sendResponse(e,s,t,r){const o=n({trackId:r,type:e,name:s,payload:t,direction:exports.MessageDirection.ToClient});this.sendMessage?.(o)}sendEvent(e,s){const t=n({type:exports.MessageType.Event,name:e,payload:s,direction:exports.MessageDirection.ToClient});this.sendMessage?.(t)}onMessage(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(s){return this.sendError({message:`Error parsing message: ${s}`,request:e,error:s,stack:s?.stack}),void console.error("Error parsing message",s)}if("Command"===e.type){if(!this.commands[e.name])return void this.sendError({message:`Command ${e.name} not found (Register it with addCommand)`,request:e},e.trackId);this.serverHandleCommand(e)}if("Query"===e.type){if(!this.queries[e.name])return void this.sendError({message:`Query ${e.name} not found (Register it with addQuery)`,request:e},e.trackId);this.serverHandleQuery(e)}if("Event"===e.type){if(!this.eventListeners[e.name])return void this.sendError({message:`Event ${e.name} not found (Register it with addEvent)`,request:e},e.trackId);this.serverHandleEvent(e)}}serverHandleCommand(e){const s=this.commands[e.name],t=s=>{this.sendResponse(exports.MessageType.CommandResponse,e.name,s,e.trackId)},r=s=>{this.sendError({message:s,request:e},e.trackId)},o=(e,s)=>{this.sendEvent(e,s)};try{s({requestMessage:e,request:e.payload,store:this.store,error:r,event:o,response:t})}catch(s){r({message:`Error in command handler for '${e.name}'`,requestMessage:e,error:s,stack:s?.stack})}}serverHandleQuery(e){(0,this.queries[e.name])({requestMessage:e,request:e.payload,store:this.store,error:s=>{this.sendError({message:s,request:e},e.trackId)},event:(e,s)=>{this.sendEvent(e,s)},response:s=>{this.sendResponse(exports.MessageType.QueryResponse,e.name,s,e.trackId)}})}serverHandleEvent(e){(0,this.eventListeners[e.name])({requestMessage:e,request:e.payload,store:this.store,error:s=>{this.sendError({message:s,request:e},e.trackId)},event:(e,s)=>{this.sendEvent(e,s)}})}addCommand(e,s){this.commands[e]=s}addQuery(e,s){this.queries[e]=s}addEventListener(e,s){this.eventListeners[e]=s}},exports.MessageBridgeServiceBase=d,exports.SignalRMessageBridgeService=class extends d{constructor(...e){super(...e),this.connection=void 0}connect(e={}){if(this.connection=(new t.HubConnectionBuilder).withUrl(this.wsUri,e).withAutomaticReconnect().build(),!this.connection)throw new Error("Failed to create SignalR connection");return this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.onclose(e=>{this.onClose(e)}),this.connection.start().then(()=>{this.onConnect()}).catch(e=>{this.onError(e)})}close(){this.connection?.stop(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}},exports.WebsocketMessageBridgeService=class extends d{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),this.socket.addEventListener("close",e=>{this.onClose(e.reason,e)}),this.socket.addEventListener("error",e=>{this.onError(e,e)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.onConnect(),e()})})}close(){this.socket?.close(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}},exports.createCommandMessage=i,exports.createEventMessage=c,exports.createMessage=n,exports.createMessageFromDto=o,exports.createQueryMessage=a;
//# sourceMappingURL=index.js.map

@@ -1,2 +0,2 @@

import*as e from"@microsoft/signalr";import{v4 as s}from"uuid";var t,i;!function(e){e.Command="Command",e.CommandResponse="CommandResponse",e.Query="Query",e.QueryResponse="QueryResponse",e.Event="Event",e.Error="Error"}(t||(t={})),function(e){e.ToClient="ToClient",e.ToServer="ToServer"}(i||(i={}));class r{constructor(e,r,n,o,a=s(),c=new Date(Date.now()).toJSON(),d=i.ToServer,h){this.module=void 0,this.name=void 0,this.type=void 0,this.isError=void 0,this.trackId=void 0,this.created=void 0,this.payload=void 0,this.schema=void 0,this.direction=void 0,this.module=h,this.name=e,this.type=r,this.trackId=a,this.created=c,this.payload=n,this.schema=o,this.direction=d,this.isError=r===t.Error}static create(e){return new r(e.name,e.type,e.payload,e.schema,e.trackId,e.created,e.direction,e.module)}static fromDto(e,s=i.ToClient){return new r(e.name,e.type,e.payload,e.schema,e.trackId,e.created,s,e.module)}}class n{constructor(e){this.wsUri=void 0,this.connected=!1,this.connection=void 0,this.debugLogger=window?.console.log||(()=>{}),this.debugLogging={messageReceived:!1,sendingMessage:!1,messageReceivedFilter:void 0,sendingMessageFilter:void 0},this.subscriptionTrackIdList={},this.subscriptionEventList={},this.subscriptionQuery=[],this.history=[],this.bridgeErrors=[],this.wsUri=e}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(s){return this.onError(s),void console.log("Incorrect message received: "+e)}try{const e=r.fromDto(s);if(this.debugLogging.messageReceived){let s=!0;this.debugLogging.messageReceivedFilter&&(s=!!e.name.match(this.debugLogging.messageReceivedFilter)),s&&this.debugLogger("Bridge (messageReceived): ",e)}this.handleIncomingMessage(e)}catch(e){console.log("Error in response handle for message: "+e)}}sendMessage(e,s,t){e.direction=i.ToServer,(s||t)&&(this.subscriptionTrackIdList[e.trackId]={onSuccess:s,onError:t}),this.internalSendMessage(e)}internalSendMessage(e){if(this.history.push(e),this.debugLogging.sendingMessage){let s=!0;this.debugLogging.sendingMessageFilter&&(s=!!e.name.match(this.debugLogging.sendingMessageFilter)),s&&this.debugLogger("Bridge (sendingMessage): ",e)}this.sendNetworkMessage(e)}subscribeEvent({name:e,onEvent:s}){return this.subscriptionEventList[e]||(this.subscriptionEventList[e]=[]),this.subscriptionEventList[e].push(s),()=>{const t=this.subscriptionEventList[e].findIndex(e=>e===s);this.subscriptionEventList[e].splice(t,1)}}createCommandMessage(e,s,n=i.ToServer,o){return r.create({name:e,type:t.Command,payload:s,direction:n,module:o})}createQueryMessage(e,s,n=i.ToServer,o){return r.create({name:e,type:t.Query,payload:s,direction:n,module:o})}createEventMessage(e,s,n=i.ToServer,o){return r.create({name:e,type:t.Event,payload:s,direction:n,module:o})}sendCommand({name:e,payload:s,onSuccess:t,onError:i,module:r}){const n=this.createCommandMessage(e,s,void 0,r);return this.sendMessage(n,t,i),n}sendQuery({name:e,payload:s,onSuccess:t,onError:i,module:r}){const n=this.createQueryMessage(e,s,void 0,r);return this.sendMessage(n,t,i),n}sendEvent({name:e,payload:s,module:t}){const i=this.createEventMessage(e,s,void 0,t);return this.sendMessage(i),i}subscribeQuery(e){return this.sendQuery({name:e.name,payload:e.query,onSuccess:e.onUpdate,onError:e.onError,module:e.module}),this.subscriptionQuery.push(e),()=>{const s=this.subscriptionQuery.findIndex(s=>s===e);s>0&&this.subscriptionQuery.splice(s,1)}}onError(e){this.bridgeErrors.push(e)}handleIncomingMessage(e){this.history.push(e),this.subscriptionTrackIdList[e.trackId]&&(e.type===t.Error?this.subscriptionTrackIdList[e.trackId].onError?.(e.payload,e):this.subscriptionTrackIdList[e.trackId].onSuccess?.(e.payload,e),delete this.subscriptionTrackIdList[e.trackId]),e.type===t.Event&&this.receiveEventMessage(e)}receiveEventMessage(e){this.subscriptionEventList[e.name]&&this.subscriptionEventList[e.name].forEach(s=>s(e.payload,e)),this.subscriptionQuery.filter(s=>s.triggers?.some(s=>s===e.name)??!1).forEach(e=>{const s=this.createQueryMessage(e.name,e.query);this.sendMessage(s,e.onUpdate,e.onError)})}}class o extends n{connect(s={}){return this.connection=(new e.HubConnectionBuilder).withUrl(this.wsUri,s).withAutomaticReconnect().build(),this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.start().then(()=>{this.connected=!0}).catch(e=>{this.onError(e)})}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}}class a extends n{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.connected=!0,e()})})}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}}export{r as Message,o as MessageBridgeService,n as MessageBridgeServiceBase,i as MessageDirection,t as MessageType,a as WebsocketMessageBridgeService};
import{HubConnectionBuilder as e}from"@microsoft/signalr";import{v4 as s}from"uuid";var t,r;function o(e,s=r.ToClient){return e.isError=e.type===t.Error,e.direction??=s,e}function n(e){return{name:e.name,payload:e.payload,type:e.type,direction:e.direction??r.ToClient,trackId:e.trackId??s(),module:e.module,schema:e.schema,created:(new Date).toISOString(),isError:e.type===t.Error}}function i(e){return n({...e,type:t.Command})}function a(e){return n({...e,type:t.Query})}function c(e){return n({...e,type:t.Event,payload:void 0})}!function(e){e.Command="Command",e.CommandResponse="CommandResponse",e.Query="Query",e.QueryResponse="QueryResponse",e.Event="Event",e.Error="Error"}(t||(t={})),function(e){e.ToClient="ToClient",e.ToServer="ToServer"}(r||(r={}));class d{constructor(e){this.wsUri=void 0,this.connected=!1,this.subscribedTrackIdMap={},this.subscribedEventListMap={},this.history=[],this.bridgeErrors=[],this.options={timeout:void 0,keepHistoryForReceivedMessages:!1,keepHistoryForSendingMessages:!1,logger:()=>console?.log??(()=>{}),logParseIncomingMessageError:!0,timeoutFromBridgeOptionsMessage:e=>`Timeout after ${e}ms (BridgeOptions.timeout)`,timeoutFromRequestOptionsMessage:e=>`Timeout after ${e}ms (RequestOptions.timeout)`,logParseIncomingMessageErrorFormat:e=>["Bridge-Error (parse messageReceived):",e],logMessageReceived:!1,logMessageReceivedFormat:e=>["Bridge (messageReceived):",e],logSendingMessage:!1,logSendingMessageFormat:e=>["Bridge (sendingMessage):",e]},this.wsUri=e}setOptions(e){this.options={...this.options,...e}}getTrackedRequestMessage(e){return this.subscribedTrackIdMap[e]?.requestMessage}onConnect(){this.connected=!0,this.options.onConnect?.()}onError(e,s){void 0!==e&&this.bridgeErrors.push(e),this.options.onError?.(e,s)}onClose(e,s){void 0!==e&&this.bridgeErrors.push(e),this.connected=!1,this.options.onClose?.(e,s)}setOptionalRequestTimeout({requestMessage:e,timeout:s,onTimeout:t}){let r,o;if(void 0!==this.options.timeout&&(r=this.options.timeoutFromBridgeOptionsMessage?.(this.options.timeout)??`timeout after ${this.options.timeout}`,o=this.options.timeout),void 0!==s&&(r=this.options.timeoutFromRequestOptionsMessage?.(s)??`timeout after ${s}`,o=s),void 0!==o)return setTimeout(()=>{t({reason:r,responseMessage:void 0,request:e.payload,requestMessage:e})},o)}sendMessageTracked(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r)=>{e&&this.options.throwOnTrackedError?t(r):s(r)}})}).finally(()=>{delete this.subscribedTrackIdMap[e.requestMessage.trackId]})}sendMessage(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r,o)=>{e?this.options.avoidThrowOnNonTrackedError?s(o?.payload):t(r):s(r.response)}})})}sendMessagePromiseHandler({handleResponseReject:e,requestMessage:s,onSuccess:t,onError:o,timeout:n}){s.direction=r.ToServer,this.options.interceptSendMessage&&(s=this.options.interceptSendMessage(s));const i=t=>{a&&clearTimeout(a);const r={response:void 0,responseMessage:void 0,request:s.payload,requestMessage:s,isError:!0,error:t.reason,errorMessage:t.responseMessage};this.onError(r),o?.(r),e(!0,r,t)},a=this.setOptionalRequestTimeout({requestMessage:s,timeout:n,onTimeout:e=>{i(e)}});this.subscribedTrackIdMap[s.trackId]={successTrack:r=>{const o={response:r.payload,responseMessage:r,request:s.payload,requestMessage:s,isError:!1};a&&clearTimeout(a),t?.(o),e(!1,o,void 0)},errorTrack:e=>{i({reason:e?.payload,responseMessage:e,request:s.payload,requestMessage:s})},requestMessage:s},this.internalSendMessage(s)}subscribeEvent({name:e,onEvent:s}){if(Array.isArray(e)){const t=e.map(e=>this.subscribeEvent({name:e,onEvent:s}));return()=>t.forEach(e=>e())}return this.subscribedEventListMap[e]||(this.subscribedEventListMap[e]=[]),this.subscribedEventListMap[e].push(s),()=>{const t=this.subscribedEventListMap[e].findIndex(e=>e===s);this.subscribedEventListMap[e].splice(t,1)}}sendCommand(e){const s=i(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendCommandTracked(e){const s=i(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQuery(e){const s=a(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQueryTracked(e){const s=a(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendEvent(e){let s=c(e);return s.direction=r.ToServer,this.options.interceptSendMessage&&(s=this.options.interceptSendMessage(s)),this.internalSendMessage(s),s}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(e){return void this.onError(e)}try{let e=o(s);this.options.interceptReceivedMessage&&(e=this.options.interceptReceivedMessage(e)),this.handleIncomingMessage(e)}catch(e){if(this.onError(e),this.options?.logger&&this.options?.logParseIncomingMessageError){const t=this.options?.logParseIncomingMessageErrorFormat?.(s)??[e];this.options.logger(t)}}}internalSendMessage(e){if(this.options.keepHistoryForSendingMessages&&this.history.push(e),this.options?.logger&&this.options?.logSendingMessage){let s=!0;if(this.options?.logSendingMessageFilter&&(s=!!e.name.match(this.options?.logSendingMessageFilter)),s){const s=this.options?.logSendingMessageFormat?.(e)??[e];this.options.logger(...s)}}this.options.onSend?.(e),this.sendNetworkMessage(e)}handleIncomingMessage(e){if(this.options.keepHistoryForReceivedMessages&&this.history.push(e),this.options?.logger&&this.options?.logMessageReceived){let s=!0;if(this.options?.logMessageReceivedFilter&&(s=!!e.name.match(this.options?.logMessageReceivedFilter)),s){const s=this.options?.logMessageReceivedFormat?.(e)??[e];this.options.logger(...s)}}this.options.onMessage?.(e);let s=e.type!==t.Error;if(e.type===t.Event)return void this.receiveEventMessage(e);const r=this.subscribedTrackIdMap[e.trackId];r&&(e.type===t.Error?(r.errorTrack?.(e),s=!0):r.successTrack?.(e),delete this.subscribedTrackIdMap[e.trackId]),s||this.onError?.(e)}receiveEventMessage(e){this.subscribedEventListMap[e.name]&&this.subscribedEventListMap[e.name].forEach(s=>s(e.payload,e))}}class h extends d{constructor(...e){super(...e),this.connection=void 0}connect(s={}){if(this.connection=(new e).withUrl(this.wsUri,s).withAutomaticReconnect().build(),!this.connection)throw new Error("Failed to create SignalR connection");return this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.onclose(e=>{this.onClose(e)}),this.connection.start().then(()=>{this.onConnect()}).catch(e=>{this.onError(e)})}close(){this.connection?.stop(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}}class g extends d{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),this.socket.addEventListener("close",e=>{this.onClose(e.reason,e)}),this.socket.addEventListener("error",e=>{this.onError(e,e)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.onConnect(),e()})})}close(){this.socket?.close(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}}class u extends d{constructor(...e){super(...e),this.server=void 0}setServer(e){this.server=e}connect(){if(!this.server)throw new Error("No server set");return this.server?.connect(e=>{this.onMessage(e),this.onConnect()}),Promise.resolve()}close(){this.onClose()}sendNetworkMessage(e){setTimeout(()=>{this.server?.onMessage(e)},10)}}class p{constructor(){this.store={},this.commands={},this.queries={},this.eventListeners={},this.sendMessage=void 0}saveToLocalStorage(e){localStorage.setItem(e,JSON.stringify(this.store))}loadFromLocalStorage(e){const s=localStorage.getItem(e);s&&(this.store=JSON.parse(s))}connect(e){this.sendMessage=e}sendError(e,s){const o=n({trackId:s,type:t.Error,name:"Error",payload:e,direction:r.ToClient});this.sendMessage?.(o)}sendResponse(e,s,t,o){const i=n({trackId:o,type:e,name:s,payload:t,direction:r.ToClient});this.sendMessage?.(i)}sendEvent(e,s){const o=n({type:t.Event,name:e,payload:s,direction:r.ToClient});this.sendMessage?.(o)}onMessage(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(s){return this.sendError({message:`Error parsing message: ${s}`,request:e,error:s,stack:s?.stack}),void console.error("Error parsing message",s)}if("Command"===e.type){if(!this.commands[e.name])return void this.sendError({message:`Command ${e.name} not found (Register it with addCommand)`,request:e},e.trackId);this.serverHandleCommand(e)}if("Query"===e.type){if(!this.queries[e.name])return void this.sendError({message:`Query ${e.name} not found (Register it with addQuery)`,request:e},e.trackId);this.serverHandleQuery(e)}if("Event"===e.type){if(!this.eventListeners[e.name])return void this.sendError({message:`Event ${e.name} not found (Register it with addEvent)`,request:e},e.trackId);this.serverHandleEvent(e)}}serverHandleCommand(e){const s=this.commands[e.name],r=s=>{this.sendResponse(t.CommandResponse,e.name,s,e.trackId)},o=s=>{this.sendError({message:s,request:e},e.trackId)},n=(e,s)=>{this.sendEvent(e,s)};try{s({requestMessage:e,request:e.payload,store:this.store,error:o,event:n,response:r})}catch(s){o({message:`Error in command handler for '${e.name}'`,requestMessage:e,error:s,stack:s?.stack})}}serverHandleQuery(e){(0,this.queries[e.name])({requestMessage:e,request:e.payload,store:this.store,error:s=>{this.sendError({message:s,request:e},e.trackId)},event:(e,s)=>{this.sendEvent(e,s)},response:s=>{this.sendResponse(t.QueryResponse,e.name,s,e.trackId)}})}serverHandleEvent(e){(0,this.eventListeners[e.name])({requestMessage:e,request:e.payload,store:this.store,error:s=>{this.sendError({message:s,request:e},e.trackId)},event:(e,s)=>{this.sendEvent(e,s)}})}addCommand(e,s){this.commands[e]=s}addQuery(e,s){this.queries[e]=s}addEventListener(e,s){this.eventListeners[e]=s}}export{u as ClientSideMessageBridgeService,p as InMemoryClientSideServer,d as MessageBridgeServiceBase,r as MessageDirection,t as MessageType,h as SignalRMessageBridgeService,g as WebsocketMessageBridgeService,i as createCommandMessage,c as createEventMessage,n as createMessage,o as createMessageFromDto,a as createQueryMessage};
//# sourceMappingURL=index.module.js.map

@@ -1,2 +0,2 @@

!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports,require("@microsoft/signalr"),require("uuid")):"function"==typeof define&&define.amd?define(["exports","@microsoft/signalr","uuid"],s):s((e||self).messageBridgeJs={},e.signalR,e.uuid)}(this,function(e,s,t){function i(e){if(e&&e.__esModule)return e;var s=Object.create(null);return e&&Object.keys(e).forEach(function(t){if("default"!==t){var i=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(s,t,i.get?i:{enumerable:!0,get:function(){return e[t]}})}}),s.default=e,s}var r,n,o=/*#__PURE__*/i(s);e.MessageType=void 0,(r=e.MessageType||(e.MessageType={})).Command="Command",r.CommandResponse="CommandResponse",r.Query="Query",r.QueryResponse="QueryResponse",r.Event="Event",r.Error="Error",e.MessageDirection=void 0,(n=e.MessageDirection||(e.MessageDirection={})).ToClient="ToClient",n.ToServer="ToServer";class a{constructor(s,i,r,n,o=t.v4(),a=new Date(Date.now()).toJSON(),c=e.MessageDirection.ToServer,d){this.module=void 0,this.name=void 0,this.type=void 0,this.isError=void 0,this.trackId=void 0,this.created=void 0,this.payload=void 0,this.schema=void 0,this.direction=void 0,this.module=d,this.name=s,this.type=i,this.trackId=o,this.created=a,this.payload=r,this.schema=n,this.direction=c,this.isError=i===e.MessageType.Error}static create(e){return new a(e.name,e.type,e.payload,e.schema,e.trackId,e.created,e.direction,e.module)}static fromDto(s,t=e.MessageDirection.ToClient){return new a(s.name,s.type,s.payload,s.schema,s.trackId,s.created,t,s.module)}}class c{constructor(e){this.wsUri=void 0,this.connected=!1,this.connection=void 0,this.debugLogger=window?.console.log||(()=>{}),this.debugLogging={messageReceived:!1,sendingMessage:!1,messageReceivedFilter:void 0,sendingMessageFilter:void 0},this.subscriptionTrackIdList={},this.subscriptionEventList={},this.subscriptionQuery=[],this.history=[],this.bridgeErrors=[],this.wsUri=e}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(s){return this.onError(s),void console.log("Incorrect message received: "+e)}try{const e=a.fromDto(s);if(this.debugLogging.messageReceived){let s=!0;this.debugLogging.messageReceivedFilter&&(s=!!e.name.match(this.debugLogging.messageReceivedFilter)),s&&this.debugLogger("Bridge (messageReceived): ",e)}this.handleIncomingMessage(e)}catch(e){console.log("Error in response handle for message: "+e)}}sendMessage(s,t,i){s.direction=e.MessageDirection.ToServer,(t||i)&&(this.subscriptionTrackIdList[s.trackId]={onSuccess:t,onError:i}),this.internalSendMessage(s)}internalSendMessage(e){if(this.history.push(e),this.debugLogging.sendingMessage){let s=!0;this.debugLogging.sendingMessageFilter&&(s=!!e.name.match(this.debugLogging.sendingMessageFilter)),s&&this.debugLogger("Bridge (sendingMessage): ",e)}this.sendNetworkMessage(e)}subscribeEvent({name:e,onEvent:s}){return this.subscriptionEventList[e]||(this.subscriptionEventList[e]=[]),this.subscriptionEventList[e].push(s),()=>{const t=this.subscriptionEventList[e].findIndex(e=>e===s);this.subscriptionEventList[e].splice(t,1)}}createCommandMessage(s,t,i=e.MessageDirection.ToServer,r){return a.create({name:s,type:e.MessageType.Command,payload:t,direction:i,module:r})}createQueryMessage(s,t,i=e.MessageDirection.ToServer,r){return a.create({name:s,type:e.MessageType.Query,payload:t,direction:i,module:r})}createEventMessage(s,t,i=e.MessageDirection.ToServer,r){return a.create({name:s,type:e.MessageType.Event,payload:t,direction:i,module:r})}sendCommand({name:e,payload:s,onSuccess:t,onError:i,module:r}){const n=this.createCommandMessage(e,s,void 0,r);return this.sendMessage(n,t,i),n}sendQuery({name:e,payload:s,onSuccess:t,onError:i,module:r}){const n=this.createQueryMessage(e,s,void 0,r);return this.sendMessage(n,t,i),n}sendEvent({name:e,payload:s,module:t}){const i=this.createEventMessage(e,s,void 0,t);return this.sendMessage(i),i}subscribeQuery(e){return this.sendQuery({name:e.name,payload:e.query,onSuccess:e.onUpdate,onError:e.onError,module:e.module}),this.subscriptionQuery.push(e),()=>{const s=this.subscriptionQuery.findIndex(s=>s===e);s>0&&this.subscriptionQuery.splice(s,1)}}onError(e){this.bridgeErrors.push(e)}handleIncomingMessage(s){this.history.push(s),this.subscriptionTrackIdList[s.trackId]&&(s.type===e.MessageType.Error?this.subscriptionTrackIdList[s.trackId].onError?.(s.payload,s):this.subscriptionTrackIdList[s.trackId].onSuccess?.(s.payload,s),delete this.subscriptionTrackIdList[s.trackId]),s.type===e.MessageType.Event&&this.receiveEventMessage(s)}receiveEventMessage(e){this.subscriptionEventList[e.name]&&this.subscriptionEventList[e.name].forEach(s=>s(e.payload,e)),this.subscriptionQuery.filter(s=>s.triggers?.some(s=>s===e.name)??!1).forEach(e=>{const s=this.createQueryMessage(e.name,e.query);this.sendMessage(s,e.onUpdate,e.onError)})}}e.Message=a,e.MessageBridgeService=class extends c{connect(e={}){return this.connection=(new o.HubConnectionBuilder).withUrl(this.wsUri,e).withAutomaticReconnect().build(),this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.start().then(()=>{this.connected=!0}).catch(e=>{this.onError(e)})}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}},e.MessageBridgeServiceBase=c,e.WebsocketMessageBridgeService=class extends c{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.connected=!0,e()})})}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}}});
!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports,require("@microsoft/signalr"),require("uuid")):"function"==typeof define&&define.amd?define(["exports","@microsoft/signalr","uuid"],s):s((e||self).messageBridgeJs={},e.signalr,e.uuid)}(this,function(e,s,t){var r,o;function n(s,t=e.MessageDirection.ToClient){return s.isError=s.type===e.MessageType.Error,s.direction??=t,s}function i(s){return{name:s.name,payload:s.payload,type:s.type,direction:s.direction??e.MessageDirection.ToClient,trackId:s.trackId??t.v4(),module:s.module,schema:s.schema,created:(new Date).toISOString(),isError:s.type===e.MessageType.Error}}function a(s){return i({...s,type:e.MessageType.Command})}function c(s){return i({...s,type:e.MessageType.Query})}function d(s){return i({...s,type:e.MessageType.Event,payload:void 0})}e.MessageType=void 0,(r=e.MessageType||(e.MessageType={})).Command="Command",r.CommandResponse="CommandResponse",r.Query="Query",r.QueryResponse="QueryResponse",r.Event="Event",r.Error="Error",e.MessageDirection=void 0,(o=e.MessageDirection||(e.MessageDirection={})).ToClient="ToClient",o.ToServer="ToServer";class g{constructor(e){this.wsUri=void 0,this.connected=!1,this.subscribedTrackIdMap={},this.subscribedEventListMap={},this.history=[],this.bridgeErrors=[],this.options={timeout:void 0,keepHistoryForReceivedMessages:!1,keepHistoryForSendingMessages:!1,logger:()=>console?.log??(()=>{}),logParseIncomingMessageError:!0,timeoutFromBridgeOptionsMessage:e=>`Timeout after ${e}ms (BridgeOptions.timeout)`,timeoutFromRequestOptionsMessage:e=>`Timeout after ${e}ms (RequestOptions.timeout)`,logParseIncomingMessageErrorFormat:e=>["Bridge-Error (parse messageReceived):",e],logMessageReceived:!1,logMessageReceivedFormat:e=>["Bridge (messageReceived):",e],logSendingMessage:!1,logSendingMessageFormat:e=>["Bridge (sendingMessage):",e]},this.wsUri=e}setOptions(e){this.options={...this.options,...e}}getTrackedRequestMessage(e){return this.subscribedTrackIdMap[e]?.requestMessage}onConnect(){this.connected=!0,this.options.onConnect?.()}onError(e,s){void 0!==e&&this.bridgeErrors.push(e),this.options.onError?.(e,s)}onClose(e,s){void 0!==e&&this.bridgeErrors.push(e),this.connected=!1,this.options.onClose?.(e,s)}setOptionalRequestTimeout({requestMessage:e,timeout:s,onTimeout:t}){let r,o;if(void 0!==this.options.timeout&&(r=this.options.timeoutFromBridgeOptionsMessage?.(this.options.timeout)??`timeout after ${this.options.timeout}`,o=this.options.timeout),void 0!==s&&(r=this.options.timeoutFromRequestOptionsMessage?.(s)??`timeout after ${s}`,o=s),void 0!==o)return setTimeout(()=>{t({reason:r,responseMessage:void 0,request:e.payload,requestMessage:e})},o)}sendMessageTracked(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r)=>{e&&this.options.throwOnTrackedError?t(r):s(r)}})}).finally(()=>{delete this.subscribedTrackIdMap[e.requestMessage.trackId]})}sendMessage(e){return new Promise((s,t)=>{this.sendMessagePromiseHandler({...e,handleResponseReject:(e,r,o)=>{e?this.options.avoidThrowOnNonTrackedError?s(o?.payload):t(r):s(r.response)}})})}sendMessagePromiseHandler({handleResponseReject:s,requestMessage:t,onSuccess:r,onError:o,timeout:n}){t.direction=e.MessageDirection.ToServer,this.options.interceptSendMessage&&(t=this.options.interceptSendMessage(t));const i=e=>{a&&clearTimeout(a);const r={response:void 0,responseMessage:void 0,request:t.payload,requestMessage:t,isError:!0,error:e.reason,errorMessage:e.responseMessage};this.onError(r),o?.(r),s(!0,r,e)},a=this.setOptionalRequestTimeout({requestMessage:t,timeout:n,onTimeout:e=>{i(e)}});this.subscribedTrackIdMap[t.trackId]={successTrack:e=>{const o={response:e.payload,responseMessage:e,request:t.payload,requestMessage:t,isError:!1};a&&clearTimeout(a),r?.(o),s(!1,o,void 0)},errorTrack:e=>{i({reason:e?.payload,responseMessage:e,request:t.payload,requestMessage:t})},requestMessage:t},this.internalSendMessage(t)}subscribeEvent({name:e,onEvent:s}){if(Array.isArray(e)){const t=e.map(e=>this.subscribeEvent({name:e,onEvent:s}));return()=>t.forEach(e=>e())}return this.subscribedEventListMap[e]||(this.subscribedEventListMap[e]=[]),this.subscribedEventListMap[e].push(s),()=>{const t=this.subscribedEventListMap[e].findIndex(e=>e===s);this.subscribedEventListMap[e].splice(t,1)}}sendCommand(e){const s=a(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendCommandTracked(e){const s=a(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQuery(e){const s=c(e);return this.sendMessage({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendQueryTracked(e){const s=c(e);return this.sendMessageTracked({requestMessage:s,onSuccess:e.onSuccess,onError:e.onError,timeout:e.timeout})}sendEvent(s){let t=d(s);return t.direction=e.MessageDirection.ToServer,this.options.interceptSendMessage&&(t=this.options.interceptSendMessage(t)),this.internalSendMessage(t),t}onMessage(e){let s;try{s="string"==typeof e?JSON.parse(e):e}catch(e){return void this.onError(e)}try{let e=n(s);this.options.interceptReceivedMessage&&(e=this.options.interceptReceivedMessage(e)),this.handleIncomingMessage(e)}catch(e){if(this.onError(e),this.options?.logger&&this.options?.logParseIncomingMessageError){const t=this.options?.logParseIncomingMessageErrorFormat?.(s)??[e];this.options.logger(t)}}}internalSendMessage(e){if(this.options.keepHistoryForSendingMessages&&this.history.push(e),this.options?.logger&&this.options?.logSendingMessage){let s=!0;if(this.options?.logSendingMessageFilter&&(s=!!e.name.match(this.options?.logSendingMessageFilter)),s){const s=this.options?.logSendingMessageFormat?.(e)??[e];this.options.logger(...s)}}this.options.onSend?.(e),this.sendNetworkMessage(e)}handleIncomingMessage(s){if(this.options.keepHistoryForReceivedMessages&&this.history.push(s),this.options?.logger&&this.options?.logMessageReceived){let e=!0;if(this.options?.logMessageReceivedFilter&&(e=!!s.name.match(this.options?.logMessageReceivedFilter)),e){const e=this.options?.logMessageReceivedFormat?.(s)??[s];this.options.logger(...e)}}this.options.onMessage?.(s);let t=s.type!==e.MessageType.Error;if(s.type===e.MessageType.Event)return void this.receiveEventMessage(s);const r=this.subscribedTrackIdMap[s.trackId];r&&(s.type===e.MessageType.Error?(r.errorTrack?.(s),t=!0):r.successTrack?.(s),delete this.subscribedTrackIdMap[s.trackId]),t||this.onError?.(s)}receiveEventMessage(e){this.subscribedEventListMap[e.name]&&this.subscribedEventListMap[e.name].forEach(s=>s(e.payload,e))}}e.ClientSideMessageBridgeService=class extends g{constructor(...e){super(...e),this.server=void 0}setServer(e){this.server=e}connect(){if(!this.server)throw new Error("No server set");return this.server?.connect(e=>{this.onMessage(e),this.onConnect()}),Promise.resolve()}close(){this.onClose()}sendNetworkMessage(e){setTimeout(()=>{this.server?.onMessage(e)},10)}},e.InMemoryClientSideServer=class{constructor(){this.store={},this.commands={},this.queries={},this.eventListeners={},this.sendMessage=void 0}saveToLocalStorage(e){localStorage.setItem(e,JSON.stringify(this.store))}loadFromLocalStorage(e){const s=localStorage.getItem(e);s&&(this.store=JSON.parse(s))}connect(e){this.sendMessage=e}sendError(s,t){const r=i({trackId:t,type:e.MessageType.Error,name:"Error",payload:s,direction:e.MessageDirection.ToClient});this.sendMessage?.(r)}sendResponse(s,t,r,o){const n=i({trackId:o,type:s,name:t,payload:r,direction:e.MessageDirection.ToClient});this.sendMessage?.(n)}sendEvent(s,t){const r=i({type:e.MessageType.Event,name:s,payload:t,direction:e.MessageDirection.ToClient});this.sendMessage?.(r)}onMessage(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(s){return this.sendError({message:`Error parsing message: ${s}`,request:e,error:s,stack:s?.stack}),void console.error("Error parsing message",s)}if("Command"===e.type){if(!this.commands[e.name])return void this.sendError({message:`Command ${e.name} not found (Register it with addCommand)`,request:e},e.trackId);this.serverHandleCommand(e)}if("Query"===e.type){if(!this.queries[e.name])return void this.sendError({message:`Query ${e.name} not found (Register it with addQuery)`,request:e},e.trackId);this.serverHandleQuery(e)}if("Event"===e.type){if(!this.eventListeners[e.name])return void this.sendError({message:`Event ${e.name} not found (Register it with addEvent)`,request:e},e.trackId);this.serverHandleEvent(e)}}serverHandleCommand(s){const t=this.commands[s.name],r=t=>{this.sendResponse(e.MessageType.CommandResponse,s.name,t,s.trackId)},o=e=>{this.sendError({message:e,request:s},s.trackId)},n=(e,s)=>{this.sendEvent(e,s)};try{t({requestMessage:s,request:s.payload,store:this.store,error:o,event:n,response:r})}catch(e){o({message:`Error in command handler for '${s.name}'`,requestMessage:s,error:e,stack:e?.stack})}}serverHandleQuery(s){(0,this.queries[s.name])({requestMessage:s,request:s.payload,store:this.store,error:e=>{this.sendError({message:e,request:s},s.trackId)},event:(e,s)=>{this.sendEvent(e,s)},response:t=>{this.sendResponse(e.MessageType.QueryResponse,s.name,t,s.trackId)}})}serverHandleEvent(e){(0,this.eventListeners[e.name])({requestMessage:e,request:e.payload,store:this.store,error:s=>{this.sendError({message:s,request:e},e.trackId)},event:(e,s)=>{this.sendEvent(e,s)}})}addCommand(e,s){this.commands[e]=s}addQuery(e,s){this.queries[e]=s}addEventListener(e,s){this.eventListeners[e]=s}},e.MessageBridgeServiceBase=g,e.SignalRMessageBridgeService=class extends g{constructor(...e){super(...e),this.connection=void 0}connect(e={}){if(this.connection=(new s.HubConnectionBuilder).withUrl(this.wsUri,e).withAutomaticReconnect().build(),!this.connection)throw new Error("Failed to create SignalR connection");return this.connection.on("ReceiveMessage",e=>{this.onMessage(e)}),this.connection.onclose(e=>{this.onClose(e)}),this.connection.start().then(()=>{this.onConnect()}).catch(e=>{this.onError(e)})}close(){this.connection?.stop(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.connection?.invoke("SendMessage",s).catch(e=>(this.onError(e),console.error(e.toString())))}},e.WebsocketMessageBridgeService=class extends g{constructor(...e){super(...e),this.socket=void 0,this.connectedCallback=void 0}connect(){return this.socket=new WebSocket(this.wsUri),this.socket.addEventListener("message",e=>{this.onMessage(e.data)}),this.socket.addEventListener("close",e=>{this.onClose(e.reason,e)}),this.socket.addEventListener("error",e=>{this.onError(e,e)}),new Promise((e,s)=>{this.socket?.addEventListener("open",s=>{this.onConnect(),e()})})}close(){this.socket?.close(),this.onClose()}sendNetworkMessage(e){const s=JSON.stringify(e);this.socket?.send(s)}},e.createCommandMessage=a,e.createEventMessage=d,e.createMessage=i,e.createMessageFromDto=n,e.createQueryMessage=c});
//# sourceMappingURL=index.umd.js.map

@@ -1,61 +0,52 @@

import * as signalR from "@microsoft/signalr";
import { Message } from "./Message";
import { IMessageServiceQuerySubscription, MessageDirection, SubscribeResponse, SubscribeResponseWithCatch } from "./MessageBridgeInterfaces";
/// <reference types="node" />
import { Message, RequestResponse, SubscribeEvent, InternalTrackedSubscribeResponseWithCatch, OmitAndOptional, SubscribeError, RequestOptionsTracked, BridgeOptions, RequestMaybeNoError, SendMessageOptions } from "./MessageBridgeTypes";
export declare abstract class MessageBridgeServiceBase {
wsUri: string;
connected: boolean;
connection?: signalR.HubConnection;
debugLogger: (...data: any[]) => void;
debugLogging: {
messageReceived: boolean;
sendingMessage: boolean;
messageReceivedFilter: string | RegExp | undefined;
sendingMessageFilter: string | RegExp | undefined;
subscribedTrackIdMap: {
[trackId: string]: InternalTrackedSubscribeResponseWithCatch<any, any>;
};
subscribedEventListMap: {
[eventName: string]: SubscribeEvent<any>[];
};
history: Message[];
bridgeErrors: unknown[];
options: BridgeOptions;
constructor(wsUri: string);
abstract connect(options?: unknown): Promise<void>;
abstract close(): void;
abstract sendNetworkMessage(msg: Message): void;
setOptions(opt: BridgeOptions): void;
getTrackedRequestMessage(trackId: string): Message | undefined;
protected onConnect(): void;
protected onError(err?: unknown, eventOrData?: unknown): void;
protected onClose(err?: unknown, eventOrData?: unknown): void;
protected setOptionalRequestTimeout<TRequest = any, TSchema = any>({ requestMessage, timeout, onTimeout, }: {
requestMessage: Message<TRequest, TSchema>;
timeout: number | undefined;
onTimeout: SubscribeError<string, TRequest>;
}): NodeJS.Timeout | undefined;
sendMessageTracked<TRequest = any, TResponse = any, TError = any, TSchema = any>(opt: SendMessageOptions<TRequest, TResponse, TError, TSchema>): Promise<RequestResponse<TRequest, TResponse, TError>>;
/**
* Only resolve the promise with the TResponse (not the tracked/full response)
* onSuccess contains the response in the first argument (and the the tracked/full response in the second argument)
*/
sendMessage<TRequest = any, TResponse = any, TError = any, TSchema = any>(opt: SendMessageOptions<TRequest, TResponse, TError, TSchema>): Promise<TResponse>;
protected sendMessagePromiseHandler<TRequest = any, TResponse = any, TError = any, TSchema = any>({ handleResponseReject, requestMessage, onSuccess, onError, timeout, }: SendMessageOptions<TRequest, TResponse, TError, TSchema> & {
handleResponseReject: (isError: boolean, response: RequestResponse<TRequest, TResponse, TError>, error?: RequestMaybeNoError<any, TRequest>) => void;
}): void;
subscribeEvent<TResponse = any>({ name, onEvent, }: {
name: string | string[];
onEvent: SubscribeEvent<TResponse>;
}): () => void;
sendCommand<TRequest = any, TResponse = any, TSchema = any>(opt: RequestOptionsTracked<TRequest, TResponse>): Promise<TResponse>;
sendCommandTracked<TRequest = any, TResponse = any, TSchema = any>(opt: RequestOptionsTracked<TRequest, TResponse>): Promise<RequestResponse<TRequest, TResponse, TSchema>>;
sendQuery<TRequest = any, TResponse = any, TError = any, TSchema = any>(opt: RequestOptionsTracked<TRequest, TResponse>): Promise<TResponse>;
sendQueryTracked<TRequest = any, TResponse = any, TError = any, TSchema = any>(opt: RequestOptionsTracked<TRequest, TResponse, TError>): Promise<RequestResponse<TRequest, TResponse, TError>>;
sendEvent<TPayload = any>(top: OmitAndOptional<Message<TPayload>, "trackId" | "created" | "isError" | "type", "direction">): Message<undefined, any>;
protected onMessage(messageString: string | Message): void;
protected subscriptionTrackIdList: {
[trackId: string]: SubscribeResponseWithCatch<any>;
};
protected subscriptionEventList: {
[eventName: string]: SubscribeResponse<any>[];
};
protected subscriptionQuery: IMessageServiceQuerySubscription<any, any>[];
history: Message[];
bridgeErrors: (Error | string)[];
sendMessage<TPayload = any, TResponse = any, TSchema = any>(msg: Message<TPayload, TResponse, TSchema>, onSuccess?: SubscribeResponse<TResponse>, onError?: SubscribeResponse<any>): void;
protected internalSendMessage(msg: Message): void;
subscribeEvent<TResponse = any>({ name, onEvent, }: {
name: string;
onEvent: SubscribeResponse<TResponse>;
}): () => void;
createCommandMessage<TPayload = any, TResponse = any, TSchema = any>(name: string, payload: TPayload, direction?: MessageDirection, module?: string): Message<TPayload, TResponse, TSchema>;
createQueryMessage<TPayload = any>(name: string, payload: TPayload, direction?: MessageDirection, module?: string): Message<TPayload, any, any>;
createEventMessage<TPayload = any>(name: string, payload: TPayload, direction?: MessageDirection, module?: string): Message<TPayload, any, any>;
sendCommand<TPayload = any, TResponse = any, TSchema = any>({ name, payload, onSuccess, onError, module, }: {
name: string;
payload: TPayload;
onSuccess?: SubscribeResponse<TResponse>;
onError?: SubscribeResponse<any>;
module?: string;
}): Message<TPayload, any, any>;
sendQuery<TPayload = any, TResponse = any, TSchema = any>({ name, payload, onSuccess, onError, module, }: {
name: string;
payload: TPayload;
onSuccess?: SubscribeResponse<TResponse>;
onError?: SubscribeResponse<any>;
module?: string;
}): Message<TPayload, any, any>;
sendEvent<TPayload = any, TResponse = any, TSchema = any>({ name, payload, module, }: {
name: string;
payload: TPayload;
module?: string;
}): Message<TPayload, any, any>;
subscribeQuery<TPayload = any, TResponse = any>(opt: IMessageServiceQuerySubscription<TPayload, TResponse>): () => void;
onError(err: Error): void;
handleIncomingMessage(msg: Message): void;
protected handleIncomingMessage(msg: Message): void;
protected receiveEventMessage(eventMsg: Message): void;
}
//# sourceMappingURL=MessageBridgeServiceBase.d.ts.map
{
"name": "message-bridge-js",
"version": "0.2.2",
"version": "0.9.0",
"homepage": "https://github.com/alfnielsen/MessageBridgeJS",

@@ -20,3 +20,11 @@ "repository": {

"scripts": {
"test": "yarn jest",
"test:all": "yarn jest --runInBand --detectOpenHandles",
"test:flow": "yarn jest -i tests/fullFlow.test.ts --runInBand --detectOpenHandles",
"test:errors": "yarn jest -i tests/handleErrors.test.ts --runInBand --detectOpenHandles",
"test:options": "yarn jest -i tests/bridgeOptions.test.ts --runInBand --detectOpenHandles",
"test:request": "yarn jest -i tests/requestOptions.test.ts --runInBand --detectOpenHandles",
"test:parallel": "yarn jest -i tests/parallel.test.ts --runInBand --detectOpenHandles",
"test:event": "yarn jest -i tests/sendEvent.test.ts --runInBand --detectOpenHandles",
"test:logger": "yarn jest -i tests/logger.test.ts --runInBand --detectOpenHandles",
"test:intercept": "yarn jest -i tests/intercept.test.ts --runInBand --detectOpenHandles",
"build": "microbundle --format umd,cjs,es",

@@ -26,19 +34,19 @@ "publish": "npm publish"

"dependencies": {
"@microsoft/signalr": "^5.0.9",
"uuid": "^8.3.2"
"@microsoft/signalr": "^7.0.2",
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-typescript": "^7.15.0",
"@types/jest": "^27.0.1",
"@types/uuid": "^8.3.1",
"babel-jest": "^27.0.6",
"jest": "^27.0.6",
"jest-websocket-mock": "^2.2.1",
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@types/jest": "^29.2.5",
"@types/uuid": "^9.0.0",
"babel-jest": "^29.3.1",
"jest": "^29.3.1",
"jest-websocket-mock": "^2.4.0",
"microbundle": "^0.15.1",
"mock-socket": "^9.0.3",
"ts-jest": "^27.0.5",
"typescript": "^4.3.5"
"mock-socket": "^9.1.5",
"ts-jest": "^29.0.5",
"typescript": "^4.9.4"
}
}

@@ -10,69 +10,95 @@ # Message Bridge (JS)

The pattern will remove the needs for controllers\* and hook directly into Commands, Queries and Events in the backend.
_\*This doesn't mean you don't want controller or need them to expose you API for other sources_
![Overview-diagram](docs/Overview-diagram.jpg)
### Bridge commands:
### Examples
Most used commands:
```ts
// See "/tests" in repository for more examples
const bridge = new MessageBridgeService("ws://localhost:8080")
await bridge.connect()
- sendCommand
- sendQuery
- sendEvent _(It's called send because it will send it to the backend - not fire it! )_
- subscribeQuery
- subscribeEvent
- connect
// Command
const command = { name: "Remember to", priority: "low" } as ICreateTodo
const id = await bridge.sendCommand({
module: "todo", // module is optional (But can be used to track different microservices etc..)
name: "CreateTodo", // name of the command
payload: command, // payload (arguments) of the command
})
console.log(`Todo created with id: ${id}`)
Underlying commands _(can sometime be used to fetch trackId ect...)_
// Query
const todo = await bridge.sendQuery({
name: "GetTotoItem", // name of the query
payload: { id: 25 }, // payload (arguments) of the query
})
console.log(`Todo with id:25 has title: ${todo.title}`)
- sendMessage
- createCommandMessage
- createQueryMessage
- createEventMessage
- onError // override to handle errors
- onClose // override to handle close
// Subscribe Event
const unsub = bridge.subscribeEvent({
name: "TotoItemUpdated", // name of the event
onEvent(todo: ITodoItem) {
// event handler
},
})
```
Protected commands
Tracked requests:
- handleIncomingMessage
- receiveEventMessage
- internalSendMessage
```ts
// tracked:
const { response, request, requestMessage } = await bridge.sendQueryTracked({
name: "GetTotoItem",
payload: { id: 25 },
})
console.log(
`${requestMessage.type} ${requestMessage.name}: Todo with id ${request.id} has title '${response.title}'`,
)
// => `Query GetTotoItem: Todo with id 25 has title 'todo1'`
```
### Examples
Multiple parallel requests:
```ts
// See "/examples" in repository for more examples
const bridge = new MessageBridgeService("ws://localhost:8080")
await bridge.connect()
// Command
const command: ICreateTodo = { name: "Remember to", priority: "low" }
bridge.sendCommand({
module: "base-module", // module is optional (in version 0.1.x)
let promise1 = bridge.sendCommand({
name: "CreateTodo",
payload: command,
onSuccess: (id: number) => {
alert(`Todo created with id: ${id}`)
},
//onError (handler error response)
})
// Query (subscribe)
bridge.subscribeQuery({
name: "GetTotoItem",
query: { id: 25 },
triggers: ["UpdatedTotoIem"],
onUpdate: (todo: ITodoItem) => {
// update ui
},
let promise2 = bridge.sendCommand({
name: "CreateTodoNode",
payload: commandNote,
})
const [todo, note] = await Promise.all([promise1, promise2])
```
// Query (subscribe)
bridge.subscribeEvent({
name: "UpdatedTotoIem",
onEvent: (todo: ITodoItem) => {
// do stoff..
},
Multiple parallel tracked requests:
```ts
let requestList = [
{ name: "GetTotoItem", payload: { id: 25 } },
{ name: "GetNote", payload: { search: "remember" } },
{...}
]
// convert to tracked requests
let promiseList = requestList.map(({name, payload}) =>
bridge.sendQueryTracked({ name, payload })
)
const responses = await Promise.all(promiseList)
responses.forEach(({ response, request, requestMessage }) => {
if(requestMessage.name === "GetTotoItem"){
console.log(`Todo: ${response.title}`),
}
if(requestMessage.name === "GetNote"){
console.log(`Note: ${response.title}`)
}
})
```
### Install
See the tests in "/tests" (github) for examples of all features.
## Install
```

@@ -86,56 +112,43 @@ > npm i message-bridge-js

### CDN
## Backend
Use directly in a browser: (CDN)
The backend must handle bridge messages and respond a message with the correct **tractId** and **type**.
The dependencies (@microsoft/signalr and uuid) most be added before this script
Type map:
```html
<!-- @microsoft/signalr -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.4/signalr.min.js"
integrity="sha512-h0xYAfohLfIHQffhHCtxoKLpHronITi3ocJHetJf4K1YCeCeEwAFA3gYsIYCrzFSHftQwXALtXvZIw51RoJ1hw=="
crossorigin="anonymous"
></script>
<!-- uuid -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>
<!-- message-bridge-js -->
<script src="https://unpkg.com/message-bridge-js/cdn-build/MessageBridgeService.js"></script>
```
- Command => CommandResponse
- Query => QueryResponse
- Event => No response
- <= Server Event can also be send from the backend to the frontend (use type Event)
_For a specific version use: (Not updated since version 0.1.5 !):_
https://unpkg.com/message-bridge-js@0.0.1/cdn-build/MessageBridgeService.js
An example of a backend implementation, can be found in the **InMemoryClientSideServer** _(in "/services" folder)_
### Backend
Note it uses the helper methods in _MessageBridgeHelper.ts_ to create the messages. _(Primary **createMessage**)_
On the backend it has a sister library (MessageBridgeCS) that execute Command and Queries and notify the UI on Events.
At some point in the future there will most likely be create official backend implementations for NodeJs and .NET Core.
The general pattern will remove the needs for controllers\* and hook directly into you Commands, Queries and Events in your backend.
> It's suggested to use some kind of CQRS pattern in the backend as well (Fx MediatR for asp.net core)
_\*This doesn't mean you don't want them or need them to expose you API for other sources_
## Events (Responsive behavior)
#### Coexist with Controllers
The Bridge enabled the frontend to listen to events send from processes in the backend.
MessageBridge don't restrict you to implement anything you did'n have before,
and you can choose to use it on only a couple of elements
(fx your admin or support UI)
This enables the frontend to be responsive to changes in the backend, instead of polling.
All command/queries call can have use both the bridge and a controller,
no restrictions.
(But your controllers should in this scenario never do anything
else then just "send" the command/query to the handlers
i the application layer of your backend )
## Technologies (dependencies)
### Events
The primary implementation uses [@microsoft/signalr](https://www.npmjs.com/package/@microsoft/signalr)
The Bridge enabled the frontend to listen to events.
The base bridge class uses [uuid](https://www.npmjs.com/package/uuid)
### Technologies
## Bridge versions
Build on SignalR (Websockets)
The primary and most tested version is the SignalR version: **SignalRMessageBridgeService**
Backend is (currently build on C# and MediatR)
But there is also a websocket version: **WebSocketMessageBridgeService**
#### Internal - how it does it
And an **ClientSideMessageBridgeService** that uses the **InMemoryClientSideServer** to get started fast without a backend.
## Internal - how it does it
The bridge sends BridgeMessage that contains a:

@@ -149,3 +162,3 @@

**Type** that are ont of
**Type** that are one of

@@ -159,24 +172,343 @@ - Command

The frontend will add a **TrackId** which in the backend will be added in the responses.
The frontend will add a **trackId** which in the backend will be added in the responses.
The message is sent to the through a websocket and
deserialize its payload in the backend.
The message is sent through a websocket and deserialize its payload in the backend.
It will then execute the Command/Query, receive the application process (Handlers)
response and create a response message,
that are sent back to the frontend including the **TrackId**
The backend handles the Command/Query/Event and create a response message.
The sent the response message back to the frontend including correct **type** and the **trackId**
The server can also send events to the frontend. _(without any prior request)_
![Overview-diagram](docs/Command-flow.jpg)
#### Not restricted to these Technologies
### Flow diagram
The first version of MessageBridge is designed for a C# backend,
and uses @microsoft/SignalR and its back ends hubs.
![Flow-diagram](docs/CommandServiceDiagram.jpg)
The is not a restriction, but a choice for the first version.
Other implementation using other messageQues then SignalR
and other backends like Javascript (typescript) or Java can be created in the future.
# Bridge commands:
### Flow diagram
Most used commands:
![Flow-diagram](docs/CommandServiceDiagram.jpg)
- sendCommand
- sendQuery
- sendEvent _(It's called send because it will send it to the backend - not fire it! )_
- subscribeEvent
- connect
Tracked versions of requests (They resolve the promise with a full RequestResponse<TRequest,TResponse>)
It includes the request and response messages (So you can track which request data what used to get the response)
- sendCommandTracked
- sendQueryTracked
Underlying commands _(can sometime be used to fetch trackId ect...)_
- sendMessage
- sendMessageTracked
- onError _(override to handle errors)_
- onClose _(override to handle close)_
Helper commands _(advanced use)_
- createCommandMessage
- createQueryMessage
- createEventMessage
- createMessage
- createMessageFromDto
Protected commands _(advanced use)_
- onMessage
- handleIncomingMessage
- receiveEventMessage
- internalSendMessage
## All features are fully tested
**jest** tests:
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>bridgeOptions.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>fullFlow.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>logger.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>sendEvent.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>intercept.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>requestOptions.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>sendQuery.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>sendCommand.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>handleErrors.test.ts
<span style="background: green; color: #333; font-weight:bold; padding: 3px">PASS</span> <span style="color: #666;">tests/</span>parallel.test.ts
Test Suites: <span style="color: green; ">10 passed</span>, 10 total
Tests: <span style="color: green; ">38 passed</span>, 38 total
## Async vs Callback
You can use the bridge in two ways:
- with async/await (default and recommended)
- with callbacks.
```ts
// sendCommand and sendQuery can take a callback instead of awaiting the response
bridge.sendCommand({
name: "CreateTodo",
payload: command,
onSuccess(id) {
console.log(`Todo created with id: ${id}`)
},
onError(error) {
// if the server send an bridge error message
console.log(`Error: ${error}`)
},
})
// or a combination to handle errors without try/catch
const id = await bridge.sendCommand({
name: "CreateTodo",
payload: command,
onError(error) {
// handle error
},
})
```
## Handle errors
There are a couple of ways to handle errors.
By default the bridge will follow normal promise flow for _non-tracked_ requests (using try/catch)
```ts
try {
const response = await bridge.sendCommand({
name: "CreateTodo",
payload: command,
})
} catch (error) {
// handle error
}
```
But this can be changed with the option: **avoidThrowOnNonTrackedError**
Tracked requests will **NOT** by default to throw errors, but instead include error in the _RequestResponse_
If an error is thrown or send from the backend (using the Error type with trackId),
the response will be _undefined_ and the error will be set.
This behavior can be changed with the option: **throwOnTrackedError**
```ts
const { response, error, isError, errorMessage } = await bridge.sendCommandTracked({
name: "CreateTodo",
payload: command,
})
```
## Bridge options
```ts
// Ex:
bridge.setOptions({
timeout: 10_000, // set timeout for all requests to 10 seconds
avoidThrowOnNonTrackedError: true, // avoid throwing errors on non tracked requests
onError: (err, eventOrData) => {
// listen to all errors (manual handling of errors)
},
logMessageReceived: true // log all incoming messages (For debugging)
logSendingMessage: true // log all outgoing messages (For debugging)
})
// BridgeOptions defined in MessageBridgeTypes.ts
export type BridgeOptions = {
// Add listeners:
onMessage?: (msg: Message) => void
onSend?: (msg: Message) => void
onError?: (err?: unknown /*Error*/, eventOrData?: unknown) => void
onClose?: (err?: unknown /*Error*/, eventOrData?: unknown) => void
onConnect?: () => void
// Interception:
// - can be used to generalize behavior (Happens as early as possible in the process)
// Happens just after user options is applied. Before stored in track map and before any other actions.
interceptSendMessage?: (msg: Message) => Message // (default: undefined)
// Happens after message-string parsing, but before stored in history, onMessage and all other actions
// To get request for the message use: getTrackedRequestMessage(trackId: string): Message | undefined
interceptReceivedMessage?: (msg: Message) => Message // (default: undefined)
// Handle errors and timeouts:
avoidThrowOnNonTrackedError?: boolean // (default: undefined)
throwOnTrackedError?: boolean // (default: undefined)
timeout?: number // (default: undefined)
// Debugging options:
timeoutFromBridgeOptionsMessage?: (ms: number) => string // (has default implementation)
timeoutFromRequestOptionsMessage?: (ms: number) => string // (has default implementation)
keepHistoryForReceivedMessages?: boolean // (default: false)
keepHistoryForSendingMessages?: boolean // (default: false)
logger?: (...data: any[]) => void // set custom logger (default: console?.log)
logParseIncomingMessageError?: boolean // (default: true)
logParseIncomingMessageErrorFormat?: (err: unknown) => any[] // (has default implementation)
logMessageReceived?: boolean // log all messages received
logMessageReceivedFormat?: (msg: Message) => any[] // (has default implementation)
logSendingMessage?: boolean // log all messages sent
logSendingMessageFormat?: (msg: Message) => any[] // (has default implementation)
logMessageReceivedFilter?: undefined | string | RegExp // restrict logging to messages matching this filter
logSendingMessageFilter?: undefined | string | RegExp // restrict logging to messages matching this filter
}
```
## Request options
You can set options for each request.
- onSuccess // not recommended for most use cases
- onError // not recommended for most use cases
- timeout // set timeout for this request (overrides bridge timeout\*)
\*_The bridge options has NO timeout as default_
```ts
// Ex:
bridge.sendCommand({
name: "CreateTodo",
payload: command,
timeout: 10_000,
})
```
# Getting started
You can use the included **ClientSideMessageBridgeService** and **InMemoryClientSideServer**
to get started quickly (and later change the bridge to the **SignalR** or **Websocket** version).
See the tests for full examples in the "/tests" folder (Github).
```ts
// TestInterfaces.ts
export enum RequestType {
GetTodoItemQuery = "GetTodoItemQuery",
UpdateTodoItemCommand = "UpdateTodoItemCommand",
TodoItemUpdated = "TodoItemUpdated",
}
export type Store = {
todos: TodoItem[]
}
export type TodoItem = {
id: number
title: string
}
export type UpdateTodoItemCommandResponse = {
done: boolean
}
export type UpdateTodoItemCommand = {
id: number
title: string
throwError?: boolean
sleep?: number
}
export type GetTodoItemQueryResponse = {
items: TodoItem[]
}
export type GetTodoItemQuery = {
search: string
throwError?: boolean
sleep?: number
}
```
```ts
// TestServer.ts
import { InMemoryClientSideServer } from "message-bridge-js"
import { RequestType, Store } from "./TestInterfaces"
let server = new InMemoryClientSideServer<Store>()
server.store.todos = [
{ id: 1, title: "todo1" },
{ id: 2, title: "todo2" },
{ id: 3, title: "todo3" },
]
server.addCommand(RequestType.UpdateTodoItemCommand, ({ event, response }) => {
const todo = server.store.todos.find((t) => t.id === opt.requestMessage.payload.id)
if (todo) {
todo.title = opt.requestMessage.payload.title
}
setTimeout(() => {
event(RequestType.TodoItemUpdated, {
id: opt.requestMessage.payload.id,
title: opt.requestMessage.payload.title,
})
}, 10)
response({ done: true })
})
server.addQuery(RequestType.GetTodoItemQuery, ({ response }) => {
const items = server.store.todos.filter((t) =>
t.title.toLowerCase().includes(opt.requestMessage.payload.search.toLowerCase()),
)
response({ items })
})
export { server as testServer }
```
```ts
// TestClient.ts
import { ClientSideMessageBridgeService } from "message-bridge-js"
import { RequestType } from "./TestInterfaces"
import { server } from "./TestServer"
const bridge = new ClientSideMessageBridgeService("ws://localhost:1234") // dummy url
bridge.server = server
// interact with the (fake inMemory) server
const response = await bridge.sendQuery<GetTodoItemQuery, GetTodoItemQueryResponse>({
name: RequestType.GetTodoItemQuery,
payload: {
search: "todo",
},
})
await bridge.sendCommand<UpdateTodoItemCommand, UpdateTodoItemCommandResponse>({
name: RequestType.UpdateTodoItemCommand,
payload: {
id: 1,
title: "todo1 changed",
},
})
```
## React hook example
The bridge can be used with any frontend framework, but here is an example of how to use it with React hooks.
```ts
function useGetTodo(id: number): Promise<TodoItem | undefined> {
const [todo, setTodo] = useState<TodoItem | undefined>()
useEffect(async () => {
const todos = await bridge.sendQuery<GetTodoItemQuery, GetTodoItemQueryResponse>({
name: RequestType.GetTodoItemQuery,
payload: { id },
})
setTodo(todos?.[0])
const unsub = bridge.subscribeEvent<TodoItemUpdatedEvent>({
name: RequestType.TodoItemUpdated,
onEvent: (todoEvent) => {
if (event.id === id) {
setTodo((todo) => ({
...todo,
title: todoEvent.title,
}))
}
},
})
return () => unsub()
}, [id])
return todo
}
function useUpdateTodo() {
const updateTodo = useCallback(async (id: number, title: string) => {
await bridge.sendCommand<UpdateTodoItemCommand, UpdateTodoItemCommandResponse>({
name: RequestType.UpdateTodoItemCommand,
payload: { id, title },
})
}, [])
return updateTodo
}
```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc