# angelia.io

WebSockets Server and Client API for Node.js and the browser, with
rooms support.

The goal of this project is to provide a developer friendly API that
just works™.

## Installation

`npm install angelia.io`

## Example

```js
// server.js (node.js)

import Server from 'angelia.io/server'

class FancyChat {
	typing(socket, data, callback) {
		console.log('Im', data ? ' typing' : ' not typing')
	}
	theMessage(socket, data, callback) {
		console.log('the message is', data, socket)
		socket.emit('gotIt', 'thanks')
		callback('yes Im sure')
	}
}

Server.on(FancyChat)

class myConnection {
	connect(socket, request) {
		console.log('socket connected', socket)
	}
	disconnect(socket, code, message) {
		console.log('socket disconnected', code, message, socket)
	}
}
Server.on(myConnection)

Server.listen()

// index.js (browser)

import Client from 'angelia.io/client'

const socket = new Client('ws://localhost:3001')

socket.emit('typing', true)

setTimeout(() => {
	socket.emit('typing', false)
	socket.emit('theMessage', 'hi there!', data => {
		console.log('yep', data)
	})
}, 5000)

socket.on('gotIt', message => {
	console.log('Server got it', message)
})
```

## Server Documentation (Node.js)

### `Server.listen` Options

| name                 | kind    | default   | description                                                                    |
| -------------------- | ------- | --------- | ------------------------------------------------------------------------------ |
| `hostname`           | String  | undefined | the hostname if any                                                            |
| `port`               | Number  | 3001      | the port to use for this server                                                |
| `maxMessageSize`     | Number  | 5         | max size in mb of a message received                                           |
| `maxPostSize`        | Number  | 50        | max size in mb of a POST message                                               |
| `skipUTF8Validation` | Boolean | false     | allows to skip utf8 validation                                                 |
| `timeout`            | Number  | 60000     | time in milliseconds after a socket is considered gone, minimum value is 10000 |

### `Server` Object

The `server` object can be accessed from everywhere

#### List of `Server` Object Properties

| signature                             | kind     | description                                                                                    |
| ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `since`                               | Number   | timestamp of initialization                                                                    |
| `now`                                 | Number   | timestamp that updates every half a second                                                     |
| `port`                                | Number   | port used by this server                                                                       |
| `hostname`                            | String   | the hostname if any                                                                            |
| `maxMessageSize`                      | Number   | maximum message size in mb                                                                     |
| `maxPostSize`                         | Number   | maximum POST size in mb                                                                        |
| `timeout`                             | Number   | after how long the socket is considered gone, in ms                                            |
| `connections`                         | Number   | count of sockets connected                                                                     |
| `served`                              | Number   | count of sockets ever connected                                                                |
| `bytesReceived`                       | Number   | sum of bytes the server has ever received                                                      |
| `bytesSent`                           | Number   | sum of bytes sent by the server                                                                |
| `messagesGarbage`                     | Number   | count of messages without a listener                                                           |
| `messagesReceived`                    | Number   | count of messages ever received                                                                |
| `messagesSent`                        | Number   | count of messages ever sent                                                                    |
| `messagesSentCacheHit`                | Number   | count of messages ever sent that were cached                                                   |
| `serverErrors`                        | Number   | count of server errors                                                                         |
| `socketErrors`                        | Number   | count of socket errors                                                                         |
| `events`                              | Object   | ref to events, ex: server.events.typing() to dispatch typing to anyone listening to it         |
| `on(Class)`                           | Function | attaches all methods of a `Class` as listeners                                                 |
| `emit(key, [value])`                  | Function | emits to all connected sockets                                                                 |
| `once(key, [value])`                  | Function | emits to the sockets and replace if exists a pending message with the same `key`               |
| `broadcast(sender, key, [value])`     | Function | emits to all connected sockets except sender                                                   |
| `broadcastOnce(sender, key, [value])` | Function | emits to the sockets except sender and replace if exists a pending message with the same `key` |
| `sockets`                             | Set      | a Set() with all the current connected sockets                                                 |

### `Socket` Object

The `socket` object is given to you by a listener

#### List of `Socket` Object Properties

| signature                   | kind     | description                                                                   |
| --------------------------- | -------- | ----------------------------------------------------------------------------- |
| `server`                    | Object   | reference to the server                                                       |
| `ip`                        | String   | ip of the socket                                                              |
| `userAgent`                 | String   | user agent of the socket                                                      |
| `params`                    | Object   | the params sent via the client constructor                                    |
| `since`                     | Number   | timestamp of first seen                                                       |
| `seen`                      | Number   | timestamp of last received message                                            |
| `ping`                      | Number   | delay with the socket in milliseconds (full round trip)                       |
| `timedout`                  | Boolean  | whether we lost connection with this socket                                   |
| `bytesSent`                 | Number   | sum of bytes sent to this socket                                              |
| `bytesReceived`             | Number   | sum of bytes received from this socket                                        |
| `messagesSent`              | Number   | count of messages sent to this socket                                         |
| `messagesReceived`          | Number   | count of messages received from this socket                                   |
| `rooms`                     | Set      | a set with the rooms where this socket is in                                  |
| `emit(key, [value])`        | Function | emits to client                                                               |
| `once(key, [value])`        | Function | replace if exists a pending message with the same `key` from emit queue       |
| `disconnect([noReconnect])` | Function | disconnects the socket from the server, pass `true` to prevent re-connections |

### Server Listeners

Listen to an event by creating a class with any name, and give to
methods the name of the things you want to listen to. Then add the
class to the listeners as `Server.on(MyClass)` and you are done.

On user defined listeners, the listener receives three things as sent
by the client: `socket`, `data` and a `callback`; Example:
`class FancyChat { typing(socket, data, callback?) { console.log(socket, data) }}`.

#### List of Predefined `Server` Events

There's a bunch of handy predefined events dispatched

| signature                           | description                                                                               |
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
| `listen()`                          | when the server is about to listen                                                        |
| `connect(socket, request)`          | when a socket connects                                                                    |
| `disconnect(socket, code, message)` | when a socket gets disconnected                                                           |
| `ping(socket)`                      | when we got an update of the ping for a socket                                            |
| `timeout(socket, delay)`            | when we are about to disconnect a timedout socket, gives the delay in milliseconds        |
| `garbage(socket, data)`             | if the client sends a message that the server has no listener this will be dispatched     |
| `incoming(socket, messages)`        | for debugging: ref to array of incoming messages received before dispatching to listeners |
| `outgoing(socket, messages)`        | for debugging: ref to array of outgoing messages before sending to socket                 |

## Client API (Browser)

### `Client` Options

Arguments for the constructor

```js
const socket = new Client({
	url: 'ws://localhost:3001',
	params: function () {
		return { fast: 'data', test: 'a space' }
	},
	noConnect: true,
})
socket.connect()
```

You may also do like this if you don't need any option

```js
const socket = new Client('ws://localhost:3001')
```

### `Client` API

The client API is similar to regular event handling

| signature                      | kind     | description                                                        |
| ------------------------------ | -------- | ------------------------------------------------------------------ |
| `connected`                    | Boolean  | `true` when the socket is connected else `false`                   |
| `connect()`                    | Function | connects to the server, it auto-connects on disconnection          |
| `disconnect([noReconnect])`    | Function | disconnects from the server, pass `true` to prevent re-connections |
| `on(key, callback)`            | Function | listens for an event, returns an `off` function to stop listening  |
| `emit(key, [value, callback])` | Function | emits data to the server                                           |

#### List of Predefined `Client` Events

As in `socket.on('connect', () => console.log('connect happened!'))`

| signature    | description                                                                                              |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| `connect`    | **this happens only once** when we connect to the server, any future connection is a `reconnect`         |
| `reconnect`  | if we were connected at least once, then any re-connection will dispatch this event instead of `connect` |
| `disconnect` | when we disconnect from the server                                                                       |

## Rooms

### Via a `Rooms` list

1. Create a room class that extends `Room`
2. Create a room list that will allow joining sockets
3. Join a socket in a room, a socket may join multiple rooms. Rooms
   are created when the `id` of the room doesn't exists, and deleted
   when there are no sockets in the room and the room doesn't have the
   flag `persistent`
4. A socket will leave automatically all rooms on disconnection
5. `socket.rooms` is a set with all the rooms the socket joined
6. `id` is assigned to the room on creation

```js
import { Room, Rooms } from 'angelia.io/server'

class GameRoom extends Room {
	persistent = false

	onCreate(socket) {
		console.log('creating room', this.id, socket)
	}
	onDelete(socket) {
		console.log('deleting room', this.id, socket)
	}

	onJoin(socket) {
		console.log('socket joined room', this.id, socket)
		socket.game = this
		this.emit('user joined', 'data here')
	}
	onLeave(socket) {
		console.log('socket left room', this.id, socket)
		socket.game = undefined
		this.emit('user left', 'data here')
	}
}

// games is a room list
const games = new Rooms(GameRoom)

class Connection {
	connect(socket) {
		games.join(socket, 'room id here')
		socket.game.id === 'room id here'
		console.log(socket.rooms)
		games.leave(socket, 'room id here')

		games.join(socket, 'a different room')
		socket.game.id === 'a different room'
		games.leave(socket, 'a different room')
	}
}

Server.on(Connection)
Server.listen()
```

#### `Rooms` Class

Iterating returns the rooms

| signature                             | kind     | description                                   |
| ------------------------------------- | -------- | --------------------------------------------- |
| `get(id)`                             | method   | gets room with such an `id`                   |
| `join(socket, [id])`                  | method   | adds socket to room `id`                      |
| `leave(socket, [id])`                 | method   | removes socket from room `id`                 |
| `connections`                         | number   | number of sockets in this room list           |
| `sockets`                             | Iterator | sockets in this room list                     |
| `emit(key, [value])`                  | method   | emits to all sockets in this room list        |
| `once(key, [value])`                  | method   | emits once to all sockets in this room list   |
| `broadcast(socket, key, [value])`     | method   | emits to other sockets in this room list      |
| `broadcastOnce(socket, key, [value])` | method   | emits once to other sockets in this room list |
| `map(fn)`                             | method   | returns mapped array                          |
| `filter(fn)`                          | method   | returns mapped filtered array                 |

### Via a `Room`

1. Create a room instance that extends `Room`
2. Join a socket in a room, a socket may join multiple rooms.
3. A socket will leave automatically all rooms on disconnection
4. `socket.rooms` is a set with all the rooms the socket joined
5. Keep in mind that `onCreate` and `onDelete` are not dispatched when
   the room is manged by yourself. The construtor could be used for
   simulating `onCreate` and `onDelete` only you will know when to
   dispose such a variable.

```js
import { Room } from 'angelia.io/server'

class Lobby extends Room {
	persistent = true
	id = 'lobby'
	onJoin(socket) {
		console.log('socket joined room', this.id, socket)
	}
}

// lobby is just 1 room
const lobby = new Lobby()

class Connection {
	connect(socket) {
		lobby.join(socket)

		lobby.leave(socket)
	}
}

Server.on(Connection)
Server.listen()
```

#### `Room` Class

Iterating returns the sockets

| signature                             | kind    | description                                      |
| ------------------------------------- | ------- | ------------------------------------------------ |
| `onCreate(socket)`                    | method  | dispatched when the room is created              |
| `onDelete(socket)`                    | method  | dispatched when the room is deleted              |
| `onJoin(socket)`                      | method  | dispatched when a socket joins the room          |
| `onLeave(socket)`                     | method  | dispatched when a socket leaves the room         |
| `persistent`                          | boolean | to not delete rooms when there are no sockets in |
| `connections`                         | number  | number of sockets in the room                    |
| `sockets`                             | Set     | sockets in the room                              |
| `emit(key, [value])`                  | method  | emits to all sockets in the room                 |
| `once(key, [value])`                  | method  | emits once to all sockets in the room            |
| `broadcast(socket, key, [value])`     | method  | emits to other sockets in the room               |
| `broadcastOnce(socket, key, [value])` | method  | emits once to other sockets in the room          |
| `id`                                  | any     | the room id                                      |
| `join(socket)`                        | method  | adds a socket to the room                        |
| `leave(socket)`                       | method  | remves a socket from the room                    |

### `sockets` Object in `Room` and `Rooms`

Like `Set` but with two aditional methods

| signature    | kind   | description                   |
| ------------ | ------ | ----------------------------- |
| `map(fn)`    | method | returns mapped array          |
| `filter(fn)` | method | returns mapped filtered array |

## Authors

- Tito Bouzout https://github.com/titoBouzout
- Anthony K. https://github.com/boredofnames

## URLs

- https://github.com/titoBouzout/angelia.io
- https://www.npmjs.com/package/angelia.io
