UNPKG

11.4 kBMarkdownView Raw
1
2# Chat Service
3
4[![NPM Version](https://badge.fury.io/js/chat-service.svg)](https://badge.fury.io/js/chat-service)
5[![Build Status](https://travis-ci.org/an-sh/chat-service.svg?branch=master)](https://travis-ci.org/an-sh/chat-service)
6[![Appveyor status](https://ci.appveyor.com/api/projects/status/qy7v2maica2urkss?svg=true)](https://ci.appveyor.com/project/an-sh/chat-service)
7[![Coverage Status](https://codecov.io/gh/an-sh/chat-service/branch/master/graph/badge.svg)](https://codecov.io/gh/an-sh/chat-service)
8[![Dependency Status](https://david-dm.org/an-sh/chat-service.svg)](https://david-dm.org/an-sh/chat-service)
9[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
10
11Room messaging server implementation that is using a bidirectional RPC
12protocol to implement chat-like communication. Designed to handle
13common public network messaging problems like reliable delivery,
14multiple connections from a single user, real-time permissions and
15presence. RPC requests processing and a room messages format are
16customisable via hooks, allowing to implement anything from a
17chat-rooms server to a collaborative application with a complex
18conflict resolution. Room messages also can be used to create public
19APIs or to tunnel M2M communications for IoT devices.
20
21
22## Features
23
24
25- Reliable room messaging using a server side history storage and a
26 synchronisation API.
27
28- Arbitrary messages format via just a validation function (hook),
29 allowing custom/heterogeneous messages formats (including a binary
30 data inside messages).
31
32- Per-room user presence API with notifications.
33
34- Realtime room creation and per-room users permissions management
35 APIs. Supports for blacklist or whitelist based access modes and an
36 optional administrators group.
37
38- Seamless support of multiple users' connections from various devises
39 to any service instance.
40
41- Written as a stateless microservice, uses Redis (also supports
42 cluster configurations) as a state store, can be horizontally scaled
43 on demand.
44
45- Extensive customisation support. Custom functionality can be added
46 via hooks before/after for any client request processing. And
47 requests (commands) handlers can be invoked server side via an API.
48
49- Pluginable networking transport. Client-server communication is done
50 via a bidirectional RPC protocol. Socket.io transport implementation
51 is included.
52
53- Pluginable state store. Memory and Redis stores are included.
54
55- Supports lightweight online user to online user messaging.
56
57- Now fully rewritten in __ES6__, runs natively on Node.js `>= 6` (ES5
58 Babel transpired code is included for Node.js `4.x` and `0.12`
59 compatibility).
60
61
62## Quickstart with socket.io
63
64On a server-side define a socket connection hook, as the service is
65relying on an extern auth implementation. A user just needs to pass an
66auth check, no explicit user adding step is required.
67
68```javascript
69function onConnect (service, id) {
70 // Assuming that auth data is passed in a query string.
71 let { query } = service.transport.getHandshakeData(id)
72 let { userName } = query
73 // Actually check auth data.
74 // ...
75 // Return a promise that resolves with a login string.
76 return Promise.resolve(userName)
77}
78```
79
80Creating a server is a simple object instantiation. Note: `close`
81method _must_ be called to correctly shutdown a service instance (see
82[Failures recovery](#failures-recovery)).
83
84```javascript
85const port = 8000
86const ChatService = require('chat-service')
87const chatService = new ChatService({port}, {onConnect})
88process.on('SIGINT', () => chatService.close().finally(() => process.exit()))
89```
90
91Server is now running on port `8000`, using memory state. By default
92`'/chat-service'` socket.io namespace is used. Add a room with `admin`
93user as the room owner. All rooms must be explicitly created (option
94to allow rooms creation from a client side is also provided).
95
96```javascript
97chatService.addRoom('default', { owner: 'admin' })
98```
99
100On a client just a `socket.io-client` implementation is required. To
101send a request (command) use `emit` method, the result (or an error)
102will be returned in socket.io ack callback. To listen to server
103messages use `on` method.
104
105```javascript
106let io = require('socket.io-client')
107let url = 'ws://localhost:8000/chat-service'
108let userName = 'user'
109let token = 'token' // auth token
110let query = `userName=${userName}&token=${token}`
111let params = { query }
112// Connect to server.
113let socket = io.connect(url, params)
114socket.once('loginConfirmed', userName => {
115 // Auth success.
116 socket.on('roomMessage', (room, msg) => {
117 // Rooms messages handler (own messages are here too).
118 console.log(`${msg.author}: ${msg.textMessage}`)
119 })
120 // Join room 'default'.
121 socket.emit('roomJoin', 'default', (error, data) => {
122 // Check for a command error.
123 if (error) return
124 // Now we will receive 'default' room messages in 'roomMessage' handler.
125 // Now we can also send a message to 'default' room:
126 socket.emit('roomMessage', 'default', { textMessage: 'Hello!' })
127 })
128})
129socket.once('loginRejected', error => {
130 // Auth error handler.
131 console.error(error)
132})
133```
134
135It is a runnable code, files are in `example` directory. For more
136details and advanced usage see [Documentation](#documentation).
137
138
139## Concepts overview
140
141### User multiple connections
142
143Service completely abstracts a connection concept from a user concept,
144so a single user can have more than one connection (including
145connections across different nodes). For user presence the number of
146joined sockets must be just greater than zero. All APIs designed to
147work on the user level, handling seamlessly user's multiple
148connections.
149
150Connections are completely independent, no additional client side
151support is required. But there are info messages and commands that can
152be used to get information about other user's connections. It makes
153possible to realise client-side sync patterns, like keeping all
154connections to be joined to the same rooms.
155
156### Room permissions
157
158Each room has a permissions system. There is a single owner user, that
159has all administrator privileges and can assign users to the
160administrators group. Administrators can manage other users' access
161permissions. Two modes are supported: blacklist and whitelist. After
162access lists/mode modifications, service automatically removes users
163that have lost an access permission.
164
165If `enableRoomsManagement` options is enabled users can create rooms
166via `roomCreate` command. The creator of a room will be it's owner and
167can also delete it via `roomDelete` command.
168
169Before hooks can be used to implement additional permissions systems.
170
171### Reliable messaging and history synchronisation
172
173When a user sends a room message, in RPC reply the message `id` is
174returned. It means that the message has been saved in a store (in an
175append only circular buffer like structure). Room message ids are a
176sequence starting from `1`, that increases by one for each
177successfully sent message in the room. A client can always check the
178last room message id via `roomHistoryInfo` command, and use
179`roomHistoryGet` command to get missing messages. Such approach
180ensures that a message can be received, unless it is deleted due to
181rotation.
182
183### Custom messages format
184
185By default a client can send messages that are limited to just a
186`{textMessage: 'Some string'}`. To enable custom messages format
187provide `directMessagesChecker` or `roomMessagesChecker` hooks. When a
188hook resolves, a message format is accepted. Messages can be arbitrary
189data with a few restrictions. The top level must be an `Object`,
190without `timestamp`, `author` or `id` fields (service will fill this
191fields before sending messages). The nested levels can include
192arbitrary data types (even binary), but no nested objects with a field
193`type` set to `'Buffer'` (used for binary data manipulations).
194
195### Integration and customisations
196
197Each user command supports before and after hook adding, and a client
198connection/disconnection hooks are supported too. Command and hooks
199are executed sequentially: before hook - command - after hook (it will
200be called on command errors too). Sequence termination in before hooks
201is possible. Clients can send additional command arguments, hooks can
202read them, and reply with additional arguments.
203
204To execute an user command server side `execUserCommand` is
205provided. Also there are some more server side only methods provided
206by `ServiceAPI` and `TransportInterface`. Look for some customisation
207cases in [Customisation examples](#customisation-examples).
208
209### Failures recovery
210
211Service keeps user presence and connection data in a store, that may
212be persistent or shared. So if an instance is shutdown incorrectly
213(without calling or waiting for `close` method to finish) or lost
214completely network connection to a store, presence data will become
215incorrect. To fix this case `instanceRecovery` method is provided.
216
217Also there are more subtle cases regarding connection-dependant data
218consistency. Transport communication instances and store instances can
219experience various kind of network, software or hardware failures. In
220some edge cases (like operation on multiple users) such failures can
221cause inconsistencies (for the most part errors will be returned to
222the command's issuers). Such events are reported via instance events
223(like `storeConsistencyFailure`), and data can be sync via
224`RecoveryAPI` methods.
225
226
227## Customisation examples
228
229### Anonymous listeners
230
231By default every user is assumed to have an unique login
232(userName). Instead of managing names generation, an integration with
233a separate transport can be used (or a multiplexed connection, for
234example an another socket.io namespace). Room messages can be
235forwarded from `roomMessage` after hook to a transport, that is
236accessible without a login. And vice versa some service commands can
237be executed by anonymous users via `execUserCommand` with bypassing
238permissions option turned on.
239
240### Explicit multi-device announcements
241
242By default there is no way for other users to know the number and
243types of user connections joined to a room. Such information can be
244passed, for example in a query string and then saved via a connection
245hook. The announcement can be made in `roomJoin` after hook, using
246directly transport `sendToChannel` method. Also additional information
247regarding joined devices types should be sent from `roomGetAccessList`
248after hook (when list name is equal to `'userlist'`).
249
250
251## Documentation
252
253Server-side and RPC APIs documentation is available
254[online](https://an-sh.github.io/chat-service/0.9/).
255
256
257## Debugging
258
259Under normal circumstances all errors that are returned to a service
260user (via request replies, `loginConfirmed` or `loginRejected`
261messages) should be instances of `ChatServiceError`. All other errors
262indicate a bug or a failure in a service infrastructure. To enable
263debug logging of such errors use `export NODE_DEBUG=ChatService`. The
264library is using bluebird `^3.0.0` promises implementation, so to
265enable long stack traces use `export BLUEBIRD_DEBUG=1`. It is highly
266recommended to use promise versions of APIs for hooks.
267
268## Bug reporting
269
270If you encounter a bug in this package, please submit a bug report to
271github repo [issues](https://github.com/an-sh/chat-service/issues).
272
273
274## License
275
276MIT