# WebSocket Events

> Push live updates to web and mobile apps with API Maker - connect a WebSocket with the tokens, subscribe to the answers of tables, custom, system and third party APIs or to your own events with a condition, and emit from your code with emitEventWS.

Source: https://docs.apimaker.dev/v1/docs/pages/web-socket-event-page.html

API Maker runs a WebSocket server (port `38245`, `wss://` behind Caddy). A client connects with its tokens, subscribes to what it wants to hear about, and receives a message every time a matching API succeeds on any server of the cluster, or when your code emits a custom event. No polling, no message broker of your own.

> Diagram : WebSockets : connect with the tokens, register with a condition, get notified from any server
>
> WebSocket notifications in API Maker : a browser or mobile app opens a WebSocket on port 38245 with its tokens in the query string, receives a CONNECTED frame, sends a REGISTER frame naming the APIs, tables or custom events it wants with a condition, and gets NOTIFICATION frames when a matching API succeeds on any server of the cluster or when code emits the event ; Redis carries the notifications between servers.

## 1. Connect

The tokens go in the query string of the WebSocket URL, with the same names as the [request headers](https://docs.apimaker.dev/v1/docs/apis-all/header/requestHeader.html).

```javascript
const ws = new WebSocket(
    'wss://ws.example.com/'                                  // the WebSocket URL of your API Maker
    + '?x-am-authorization=' + encodeURIComponent(apiUserToken)
    + '&x-am-user-authorization=' + encodeURIComponent(personToken)   // when your events need the person
);
```

- `x-am-authorization` : the API user, always. The person token of the [auth provider](https://docs.apimaker.dev/v1/docs/authorization/AMDB.html) of the events you subscribe to : `x-am-user-authorization`, `x-google-authorization`, `x-azure-authorization`, `x-aws-authorization` or `x-custom-authorization`. API Maker finds out which provider a token belongs to ; `&authProviders=name1,name2` narrows it when you want.
- The first message from the server is `CONNECTED` :

```json
{ "type": "CONNECTED", "response": { "connected": true } }
```

- A bad token closes the connection with a `TOKEN_VALIDATION` message which says which header is wrong.

## 2. Subscribe

Send a `REGISTER` message with the events you want, then read the answer : the ones accepted, with their `eventId`, and the ones refused with the reason.

```javascript
ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.type === 'CONNECTED') {
        ws.send(JSON.stringify({
            objType: 'REGISTER',
            onEvents: [
                {   // every successful get all on this table
                    eventType: 'INSTANCES', apiName: 'SCHEMA_GET_ALL',
                    instance: 'mongodb', database: 'shop', collection: 'orders',
                    getEventData: true, select: { order_no: 1, status: 1 },
                },
                {   // a custom event, only when the criteria matches the emitted data
                    eventType: 'CUSTOM_WS_EVENTS', apiName: 'default/orders-updated',
                    condition: { conditionType: 'RESPONSE', criteria: { recipientId: myUserId } },
                    getEventData: true,
                },
            ],
        }));
    } else if (msg.type === 'REGISTER') {
        console.log(msg.response.validOnEvents, msg.response.invalidOnEvents);   // eventId per subscription
    } else if (msg.type === 'NOTIFICATION') {
        render(msg.response.eventId, msg.response.eventData);
    }
};
```

| Key of an event | Meaning |
|---|---|
| `eventType` | `INSTANCES`, `CUSTOM_APIS`, `SYSTEM_APIS`, `THIRD_PARTY_APIS` or `CUSTOM_WS_EVENTS`. |
| `apiName` | The [API id](https://docs.apimaker.dev/v1/docs/apis-all/overview.html#api-ids) for a table (`SCHEMA_GET_ALL`, `GEN_POST_BULK_INSERT`…), the name of the custom, system or third party API, or the name of the custom WebSocket event. |
| `instance`, `database`, `collection` or `table` | The table, for `INSTANCES`. |
| `apiBundleName`, `apiVersion` | The bundle and version, for `THIRD_PARTY_APIS`. |
| `condition` | `{ conditionType: 'RESPONSE', criteria: { field: value } }` : only the answers whose data has these values reach this client. For a custom event, the emitted data must match the criteria exactly, which is how one notification reaches one person. |
| `select` | The fields of the data to send, `{ field: 1 }`. |
| `getEventData` | `true` sends the data ; `false` sends only the fact that it happened. |

- Subscriptions are checked against the groups of the API user and the auth provider of the event : `invalidOnEvents` says why one was refused.

## 3. Receive

```json
{ "type": "NOTIFICATION", "response": { "eventId": "…", "eventType": "INSTANCES", "eventData": [ { "order_no": 1001, "status": "PAID" } ] } }
```

- Notifications come from **any server** of the cluster : Redis passes them between the servers.
- Unsubscribe with the `eventId` of the register answer :

```javascript
ws.send(JSON.stringify({ objType: 'UNREGISTER', onEvents: [ eventId ] }));
```

## When a notification is sent

- After every **successful** call of a subscribed API : the answer of the call is the data, filtered by `condition` and `select`. The call can come over HTTP or from your code (custom APIs, hooks, events…).
- Not when an error happens before the answer exists : a pre hook which throws sends nothing. A post hook which throws does not stop the notification.
- For a custom WebSocket event, when your code emits it :

```typescript
await g.sys.system.emitEventWS('default/orders-updated', { recipientId: '42', type: 'order-updated', order_no: 1001 });
```

```text
POST /api/system-api/admin/emit-event-ws
{ "name": "default/orders-updated", "eventData": { "recipientId": "42", "order_no": 1001 } }
```

- To notify N people, emit once per person with their id in the data ; each client subscribes with its id in `criteria`.

## Custom WebSocket events

`API Info → WebSocket Events` lists the event names of the account. A custom event needs to exist there before it can be subscribed to or emitted.

| Field | Meaning |
|---|---|
| Name | The `apiName` of the subscription and the name given to `emitEventWS`. |
| Auth providers | The provider whose token a client must have validated to subscribe. Empty : the first auth provider of the account. |
| Can user connect code | A function which decides whether a client may subscribe : return `{ canConnect: true }` or `{ canConnect: false, errorText: '…' }`. It sees the tokens in `g.req.auth`. |
| Trigger on API | Optional : an API whose success also emits this event. |

## From your code

- `g.sys.system.emitEventWS(name, data)` from any custom API, hook, event, scheduler or migration.
- The sample custom API `/default/ws-notify` of a new account has the complete client snippet in its comments.
- The [system API](https://docs.apimaker.dev/v1/docs/apis-all/system-apis/system-generated-emit-event-ws-api.html) does the same over HTTP.

## Good to know

- The admin panel and the local client use the same server with their own tokens.
- Groups grant WebSocket events : an API user subscribes to a custom event only when a group allows it.
- Behind Caddy, the install script gives the WebSocket its own host or port : the install prints the WebSocket URL (`BE_WS_HOST_PORT`).
