Options
All
  • Public
  • Public/Protected
  • All
Menu

Actyx-HTTP-Connector

Need a connector to talk to an Actyx swarm from external applications via HTTP?

Actyx-HTTP-Connector allows you to build one in a few minutes while keeping the embedded HTTP server fully customizable.

✨ Features

The HTTP connector allows you to interact with a swarm of Actyx nodes by providing an HTTP interface to systems not running Actyx. You can use it to ...

  • ... inject data from other systems that will be injected into the Actyx swarm in the form of events (think webhooks)
  • ... query Fish state from non-Actyx applications to show data from Actyx in other apps (e.g. legacy web apps)
  • ... get updates from the Actyx swarm via WebSockets

The connector provides hooks you can use to influence the behavior of the underlying HTTP server/web framework, Express. You can use these hooks to ...

  • ... add middleware like encryption, body-parsers or authentication
  • ... add static file resources
  • ... provide additional routes or catch-all route handlers

Additionally, you can query information like your local source ID, the [Pond](https://developer.actyx.com/docs/pond/introduction/ state, and whether the node running the HTTP connector is in sync with the swarm to be able to deal with error conditions better.

⚖️ Trade-Offs

Note, however, that web apps running on the HTTP connector do not provide the same level of resilience that Actyx apps do.

This is typically not an issue when interfacing with external systems (top floor, back office). But you should probably not use it to build applications that run on the shop floor and need to be highly available and resilient.

📦 Installation

Actyx-HTTP-Connector is available as an npm package.

npm install @actyx-contrib/actyx-http-connector

📖 Documentation

The complete API documentation and related examples are available at https://actyx-contrib.github.io/actyx-HTTP-Connector

Detailed Examples

You can find sample applications on GitHub.

The simple example exposes the possibility to query Fish state and emit events to the Pond directly. The advanced example adds WebSocket communication, uses event emitters and adds authentication. Both projects come with a simple React based app. Note that these apps do not directly talk to an Actyx node but interface through the HTTP connector.

Make sure you have an Axtyx node running on your machine before starting the examples. You can get the binaries from our download site.

You can start the examples using npm i && npm run example:simple or npm i && npm run example:advanced, respectively. The apps are accessible at http://localhost:1234. If that port is already allocated, the build picks another one at random. Check the build's console output to be sure.

🤓 Quick start

To have access to your Actyx Fish definitions and the Pond, it is best to create your HTTP server as part of an (probably already existing) axp project.

$ cd <my project folder>
$ axp init # only if you do not already have an Actyx project in place
$ axp add node --appName http-api
$ npm install @actyx-contrib/actyx-http-connector --save

🔌 Add httpConnector

In your http-api app's index.ts, import the HTTP connector, then use httpConnector() to create a server instance as shown below. For further details, please refer to the docs and the examples.

import { httpConnector, registryEntry } from '@actyx-contrib/actyx-http-connector'
import { Pond } from '@actyx/pond'

Pond.default().then(pond => {
  httpConnector({
    // The pond instance is required for the HTTP-Connector
    pond,
    // Allows the user of the HTTP-Connector to emit events directly into actyx.
    // It is not recommended to use this feature.
    // Please use `eventEmitters` or at least add an authentication with `preSetup`
    allowEmit: true,
    // Propagate which fish you like to access from external programs.
    // The fish will be published over the HTTP get request or can be observed with the websocket
    registry: { someFish: registryEntry(SomeFish.of) },
  })

🤓 Examples

Minimal example

The minimal example simply allows you to emit events directly into actyx using an HTTP API.

import { httpConnector, registryEntry } from '../../src'
import { Pond } from '@actyx/pond'
// Api Server
Pond.default().then(pond => {
  httpConnector({
    // The pond instance is required for the HTTP-Connector
    pond,
    // Allows the user of the HTTP-Connector to emit events directly into actyx.
    // It is not recommended to use this feature.
    // Please use `eventEmitters` or at least add an authentication with `preSetup`
    allowEmit: true,
    // Propagate which fish you like to access from external programs.
    // The fish will be published over the HTTP get request or can be observed with the websocket
    registry: { someFish: registryEntry(SomeFish.of) },
  })
})

Complete example

The complete example shows also shows how to hook in middlewares and the usage of event emitters.

import { httpConnector, registryEntry } from '../../src'
import { Pond } from '@actyx/pond'
// Api Server
Pond.default().then(pond => {
  httpConnector({
    // The pond instance is required for the HTTP-Connector
    pond,
    // Propagate which fish you like to access from external programs.
    // The fish will be published over the HTTP get request or can be observed with the websocket
    registry: {
      someFish: registryEntry(SomeFish.of),
    },
    // Add event emitters.
    // This methode is much safer than the `allowEmit: true`, you are in control which event
    // are emitted and you can verify the event with TypeScript or io-TS
    eventEmitters: {
      startSomeFish: async (pond, _payload) => {
        await SomeFish.emitStart(pond)
        return EmitResult.reply(204)
      },
    },
    // Add the authentication layer before the routes are created.
    // this hook is added after urlencoded, json, cors, you could add XML parser,
    // cookie parser and other middleware you like
    preSetup: app => {
      app.use(xmlparser())
      // add Authentication
    },
    // Add a handler after the routes are added to express.
    // This could be used for a default "404 not-Found" page or a redirect to your documentation
    postSetup: app => {
      app.use((_req, res, _next) => res.redirect('https://community.actyx.com'))
    },
  })
})

Index

Type aliases

Emit

Emit: { eventType: string }

parameter Type for the emitter route

Type declaration

  • eventType: string

EmitResult

EmitResult: { code: number; payload?: string | Buffer }

return type of the emitter function with the required information to create an reply to the client

Type declaration

  • code: number

    http code (200, 204, or 401, 403, 500)

  • Optional payload?: string | Buffer

    optional payload as reply to the client

HttpConnectorConfig

HttpConnectorConfig: { allowEmit?: undefined | false | true; endpoint?: undefined | { host: string; port: number }; eventEmitters?: undefined | {}; pond: Pond; postSetup?: undefined | ((app: expressWs.Application) => void); preSetup?: undefined | ((app: expressWs.Application) => void); registry?: undefined | {} }

Configuration of the HttpConnector

example:

httpConnector({
  pond,
  allowEmit: false,
  registry: { someFish: registryEntry(SomeFish.of) },
  eventEmitters: {
    startSomeFish: async (pond, _payload) => {
      await SomeFish.emitStart(pond)
      return EmitResult.reply(204)
    },
  },
  preSetup: app => {
    app.use(xmlparser())
    // add Authentication
  },
  postSetup: app => {
    app.use((_req, res, _next) =>
      res.redirect('https://community.actyx.com')
    )
  },
})

Type declaration

  • Optional allowEmit?: undefined | false | true

    Allows the user of the Http-Connector to emit events directly into actyx.

    It is not recommended to use this feature.

    Please use eventEmitters or at least add an authentication with preSetup

  • Optional endpoint?: undefined | { host: string; port: number }

    settings to overwrite the default configuration

    default: 0.0.0.0:4242

  • Optional eventEmitters?: undefined | {}

    Add safer and easier event emitters to the http-connector.

    This methode is much safer than the allowEmit: true, you are in control which event are emitted and you can verify the event with TypeScript or io-TS

    the emitter can be triggered with:

    • POST: /emit/:eventType | Body: Payload
    • e.g.: /emit/machineState | Body: {"machine": "M1", "state"; "idle"}

    example:

    type MachineStatePayload = {
      machine: string
      state: 'emergency' | 'disabled' | 'idle'
    }
    
    const isMachineStatePayload = (payload: unknown): payload is MachineStatePayload =>
      typeof payload === 'object' &&
      hasProperty(payload, 'machine') &&
      typeof payload.machine == 'string' &&
      hasProperty(payload, 'state') &&
      typeof payload.state == 'string' &&
      ['emergency', 'disabled', 'idle'].includes(payload.state)
    
    httpConnector({
      //[...]
      eventEmitters: {
        machineState: async (pond, payload) => {
          if (isMachineStatePayload(payload)) {
            await MachineFish.emitMachineState(pond, payload.machine, payload.state)
            return EmitResult.reply(204)
          } else {
            return EmitResult.reply(403, 'wrong parameter')
          }
        },
      }
    })
  • pond: Pond

    pond instance to observe fish

  • Optional postSetup?: undefined | ((app: expressWs.Application) => void)

    Add a handler after the routes are added to express, This could be used for a default "not-Found" page or a redirect to your documentation

  • Optional preSetup?: undefined | ((app: expressWs.Application) => void)

    Add the authentication layer before the routes are created. This hook is added after urlencoded, json, and cors, you could add XML parser, cookie parser and other middleware you like

  • Optional registry?: undefined | {}

    Propagate which fish you like to access from external programs. The fish will be accessible over the http get request or can be observed with the websocket.

    The route will be: /state/<key>/[property?]

    You will find a list of all routes when you connect to the http-connector directly e.g.: http://localhost:4242/

Params

Params: { fish: string; props: string }

parameter Type for the fish state requests

Type declaration

  • fish: string
  • props: string

RegistryEntry

RegistryEntry<Event, Props>: { fish: ((p: Props) => Fish<Event, any>) | Fish<Event, any>; props?: Props }

Entry for the fish registry. All fish in this registry will be exposed via the http interface

Type parameters

  • Event

  • Props

Type declaration

  • fish: ((p: Props) => Fish<Event, any>) | Fish<Event, any>

    Fish or fish-factory to create the desired fish

  • Optional props?: Props

    In the case that the fish is a fish-factory, this will be the default parameter if no parameter is provided by the caller

RequestCtx

RequestCtx: { fish: ((p: any) => Fish<any, any>) | Fish<any, any>; props: string }

request context for local to stage the get request with proper error handling

Type declaration

  • fish: ((p: any) => Fish<any, any>) | Fish<any, any>
  • props: string

TestSetup

TestSetup: { emitSpy: Spy; infoSpy: Spy; logSpy: Spy; observeSpy: Spy; pond: TestPond; postSetup: Mock<unknown, unknown[]>; preSetup: Mock<unknown, unknown[]> }

Type declaration

  • emitSpy: Spy
  • infoSpy: Spy
  • logSpy: Spy
  • observeSpy: Spy
  • pond: TestPond
  • postSetup: Mock<unknown, unknown[]>
  • preSetup: Mock<unknown, unknown[]>

WsProtocol

Data that the Websocket could return

WsProtocolData

WsProtocolData: { data: unknown; fish: string; props: string; type: "data" }

WebSocket data, if the websocket could not be established

Type declaration

  • data: unknown

    data, containing the current state of the fish

  • fish: string

    fish that should be observed

  • props: string

    optional properties to observe the fish

  • type: "data"

    indicator that this message contains some data

WsProtocolError

WsProtocolError: { data: { message: string }; fish: string; props: string; type: "error" }

WebSocket data, if the websocket could not be established

Type declaration

  • data: { message: string }

    Data, containing the error message

    • message: string
  • fish: string

    Fish that should be observed

  • props: string

    Optional properties to observe the fish

  • type: "error"

    Indicator that this message is an error

Functions

Const get

  • get(route: string): Promise<any>

Const hasProperty

  • hasProperty<T, Key>(object: T, property: string): object is T & Record<Key, unknown>
  • Helper to verify if a property exists in a given object This function is type safe and in case that the property exists, the object type is enhanced.

    Type parameters

    • T: object | null

    • Key: string

    Parameters

    • object: T

      object to check if a given property exists

    • property: string

      property that should exist in the given object

    Returns object is T & Record<Key, unknown>

    returns true if the object has the property

Const httpConnector

  • Initialize the http-Connector. This could be part of an existing application or be the main purpose of an new actyx app

    example:

    httpConnector({
      pond,
      allowEmit: false,
      registry: { someFish: registryEntry(SomeFish.of) },
      eventEmitters: {
        startSomeFish: async (pond, _payload) => {
          await SomeFish.emitStart(pond)
          return EmitResult.reply(204)
        },
      },
      preSetup: app => {
        app.use(xmlparser())
        // add Authentication
      },
      postSetup: app => {
        app.use((_req, res, _next) =>
          res.redirect('https://community.actyx.com')
        )
      },
    })

    Parameters

    Returns expressWs.Application

    Express-ws instance for further use

Const mkData

  • factory function to create an data message containing the fish state

    Parameters

    • fish: string

      fish for the reply

    • props: string

      props used to create the fish

    • data: unknown

      current state of the fish

    Returns WsProtocolData

    Data that could send over the webSocket

Const mkError

  • factory function to create an error message

    Parameters

    • fish: string

      fish for the reply

    • props: string

      props used to create the fish

    • message: string

      Error message for the user

    Returns WsProtocolError

    Data that could send over the webSocket

Const post

  • post(route: string, data: unknown): Promise<any>
  • Parameters

    • route: string
    • data: unknown

    Returns Promise<any>

Const registryEntry

  • registryEntry<Event, Props>(fish: ((p: Props) => Fish<Event, any>) | Fish<Event, any>, props?: Props): RegistryEntry<Event, Props>
  • helper function to create a RegistryEntry

    Type parameters

    • Event

    • Props

    Parameters

    • fish: ((p: Props) => Fish<Event, any>) | Fish<Event, any>

      Fish or fish-factory to create the desired fish

    • Optional props: Props

      In the case that the fish is a fish-factory, this will be the default parameter if no parameter is provided by the caller

    Returns RegistryEntry<Event, Props>

    A RegistryEntry for the http-connector::registry

Const setupTest

Const toEmitResult

  • internal function to handle the emitter as async and sync function

    Parameters

    Returns Promise<EmitResult>

    unified Promise

Object literals

Const EmitResult

EmitResult: object

Helper to create the EmitResult

reply

  • reply(code: number, payload?: string | Buffer): { code: number; payload: undefined | string | Buffer }
  • Returns an EmitResult with the required information to create an reply to the client

    Parameters

    • code: number

      http code (200, 204, or 401, 403, 500)

    • Optional payload: string | Buffer

      optional payload as reply to the client

    Returns { code: number; payload: undefined | string | Buffer }

    EmitResult for the emitter function

    • code: number
    • payload: undefined | string | Buffer

Legend

Generated using TypeDoc