message-bridge-js
Advanced tools
Comparing version 0.2.2 to 0.9.0
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" | ||
} | ||
} |
528
README.md
@@ -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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
218727
511
26
376
1
+ Added@microsoft/signalr@7.0.14(transitive)
+ Addedeventsource@2.0.2(transitive)
+ Addedfetch-cookie@2.2.0(transitive)
+ Addedquerystringify@2.2.0(transitive)
+ Addedrequires-port@1.0.0(transitive)
+ Addedset-cookie-parser@2.7.1(transitive)
+ Addedtough-cookie@4.1.4(transitive)
+ Addeduniversalify@0.2.0(transitive)
+ Addedurl-parse@1.5.10(transitive)
+ Addeduuid@9.0.1(transitive)
+ Addedws@7.5.10(transitive)
- Removed@microsoft/signalr@5.0.17(transitive)
- Removedasync-limiter@1.0.1(transitive)
- Removedes6-denodeify@0.1.5(transitive)
- Removedeventsource@1.1.2(transitive)
- Removedfetch-cookie@0.7.3(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removeduuid@8.3.2(transitive)
- Removedws@6.2.3(transitive)
Updated@microsoft/signalr@^7.0.2
Updateduuid@^9.0.0