Skip to content

Latest commit

History

History
107 lines (76 loc) 路 3.47 KB

README.md

File metadata and controls

107 lines (76 loc) 路 3.47 KB
lastmod
2021-12-03T17:39:51.376Z

Messaging

Different parts of an application or various locations of services and clients need to communicate with each other. Event emitter are a proven concept for in-app communication. But pretty soon tasks become more complex or transport for communication is limited or insecure. These tools try to simplify this task.

We differentiate between the following parts:

  • Emitter: Classic local event handling
  • Channel: Simple postMessage interface for basic data transport without a protocol
  • PubSub: Simple one way messages, similar to Emitter but using Channel as transport
  • Messages: RPC like interface for communication via Channel, awaits response from other side
  • Encoder: Transform data into a special format for transport like e.g. JSON, encrypted data, etc.

Channel

Channels are a uniform abstraction for sending and receiving data usually in binary format. It uses the commonly known MessageEvent pattern, with send via postMessage and listening on message. It is extended to optionally reflect connection states.

channel.postMessage('Hello World')
channel.on('message', (msg) => {
  log(`Received data=${data}`)
})

PubSub

Same API as Emitter for easily sending event like messages via a Channel. It is type safe, if you pass an interface as the generic.

interface MyProtocol {
  doSomething: (using: string, count: number) => void
}

const hub = usePubSub<MyProtocol>({ channel })

hub.emit('doSomething', 'hello', 2)
hub.on('doSomething', (using, count) => {
  // ...
})

You can also use the alternative more PubSub like syntax ;)

hub.publish('doSomething', 'hello', 2)
hub.subscribe('doSomething', (using, count) => {})

Encoder

Usually data is sent in a binary form. Therefore, an encoding has to transform objects into a form, that both ends can understand. Specific encoders can also apply additional transforms like encryption.

Messages

Messages are a high level abstraction for communicating through channels. They provide additional benefits:

  • They always return a response, so the sender knows if the message reached the other participant
  • Messages are packed as Promise, so you can await results
  • The message and the payload are wrapped in a method like structure resulting in a codings style close to locally calling methods on an object
  • Timeouts can be set and will throw an error on failure
  • If the channel is broken, it is possible to retry sending the message

Messages are defined via interface. Typescript checks for valid calls:

interface MyMessages {
  echo: (data: any) => Promise<any>
  pong: (data: any) => Promise<void>
}

Using the messages is easy:

const hub = useMessageHub({ channel }).send<MyMessages>()
const echoResponse = await hub.echo({ hello: 'world' })

On the receiver part implementation is also straight forward:

useMessageHub({ channel }).listen<MyMessages>({
  async echo(data) {
    return data
  }
})

Various transports and their properties

https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects

  • WebSocket: Supports binary channel, see zerva-websocket
  • WebRTC: Supports binary channel
  • HTTP: Supports binary channel
  • WebWorker: Supports ArrayBuffer
  • IFrame
  • BroadcastCannel

Reference

Other projects I learned from, check them out: