UNPKG

10.5 kBJavaScriptView Raw
1'use strict'
2
3const ChatServiceError = require('./ChatServiceError')
4const CommandBinder = require('./CommandBinder')
5const DirectMessaging = require('./DirectMessaging')
6const Promise = require('bluebird')
7const UserAssociations = require('./UserAssociations')
8const _ = require('lodash')
9const { asyncLimit, checkNameSymbols } = require('./utils')
10const { mixin } = require('es6-mixin')
11
12// Client commands implementation.
13class User {
14
15 constructor (server, userName) {
16 this.server = server
17 this.userName = userName
18 this.echoChannel = `echo:${this.userName}`
19 this.state = this.server.state
20 this.transport = this.server.transport
21 this.enableRoomsManagement = this.server.enableRoomsManagement
22 this.enableDirectMessages = this.server.enableDirectMessages
23 this.directMessaging = new DirectMessaging(server, userName)
24 let State = this.server.state.UserState
25 this.userState = new State(this.server, this.userName)
26 this.commandBinder =
27 new CommandBinder(this.server, this.transport, this.userName)
28 let opts = {
29 busAckTimeout: this.server.busAckTimeout,
30 clusterBus: this.server.clusterBus,
31 consistencyFailure: this.consistencyFailure.bind(this),
32 echoChannel: this.echoChannel,
33 lockTTL: this.state.lockTTL,
34 state: this.state,
35 transport: this.transport,
36 userName: this.userName,
37 userState: this.userState
38 }
39 mixin(this, UserAssociations, opts)
40 }
41
42 initState (state) {
43 return this.directMessaging.initState(state)
44 }
45
46 removeState () {
47 return this.directMessaging.removeState()
48 }
49
50 processMessage (msg, setTimestamp = false) {
51 delete msg.id
52 delete msg.timestamp
53 if (setTimestamp) {
54 msg.timestamp = _.now()
55 }
56 msg.author = this.userName || msg.author
57 return msg
58 }
59
60 exec (command, options, args) {
61 let { id } = options
62 let requestsNames = this.server.rpcRequestsNames
63 if (!_.includes(requestsNames, command)) {
64 let error = new ChatServiceError('noCommand', command)
65 return Promise.reject(error)
66 }
67 let requiresSocket = command === 'roomJoin' || command === 'roomLeave'
68 if (!id && requiresSocket) {
69 let error = new ChatServiceError('noSocket', command)
70 return Promise.reject(error)
71 }
72 let fn = this[command].bind(this)
73 let cmd = this.commandBinder.makeCommand(command, fn)
74 return cmd(args, options)
75 }
76
77 checkOnline () {
78 return this.userState.getAllSockets().then(sockets => {
79 if (!sockets || !sockets.length) {
80 let error = new ChatServiceError('noUserOnline', this.userName)
81 return Promise.reject(error)
82 } else {
83 return Promise.resolve()
84 }
85 })
86 }
87
88 consistencyFailure (error, operationInfo) {
89 operationInfo.userName = this.userName
90 let name = operationInfo.opType === 'transportChannel'
91 ? 'transportConsistencyFailure'
92 : 'storeConsistencyFailure'
93 this.server.emit(name, error, operationInfo)
94 }
95
96 registerSocket (id) {
97 return this.state.addSocket(id, this.userName)
98 .then(() => this.userState.addSocket(id, this.server.instanceUID))
99 .then(nconnected => {
100 if (!this.transport.getSocket(id)) {
101 return this.removeUserSocket(id).then(() => {
102 let error = new ChatServiceError('noSocket', 'connection')
103 return Promise.reject(error)
104 })
105 } else {
106 let commands = this.server.rpcRequestsNames
107 for (let cmd of commands) {
108 this.commandBinder.bindCommand(id, cmd, this[cmd].bind(this))
109 }
110 this.commandBinder.bindDisconnect(id, this.removeSocket.bind(this))
111 return this.transport.joinChannel(id, this.echoChannel).then(() => {
112 this.socketConnectEcho(id, nconnected)
113 return Promise.resolve()
114 })
115 }
116 })
117 }
118
119 removeSocket (id) {
120 return this.removeSocketFromServer(id)
121 }
122
123 disconnectInstanceSockets () {
124 return this.userState.getAllSockets().then(sockets => {
125 return Promise.map(
126 sockets,
127 sid => this.transport.disconnectSocket(sid),
128 { concurrency: asyncLimit })
129 })
130 }
131
132 directAddToList (listName, values) {
133 return this.directMessaging.addToList(this.userName, listName, values)
134 .return()
135 }
136
137 directGetAccessList (listName) {
138 return this.directMessaging.getList(this.userName, listName)
139 }
140
141 directGetWhitelistMode () {
142 return this.directMessaging.getMode(this.userName)
143 }
144
145 directMessage (recipientName, msg, {id, bypassPermissions}) {
146 if (!this.enableDirectMessages) {
147 let error = new ChatServiceError('notAllowed')
148 return Promise.reject(error)
149 }
150 this.processMessage(msg, true)
151 return this.server.state.getUser(recipientName).then(recipient => {
152 let channel = recipient.echoChannel
153 return recipient.directMessaging
154 .message(this.userName, msg, bypassPermissions)
155 .then(() => recipient.checkOnline())
156 .then(() => {
157 this.transport.emitToChannel(channel, 'directMessage', msg)
158 this.transport.sendToChannel(
159 id, this.echoChannel, 'directMessageEcho', recipientName, msg)
160 return msg
161 })
162 })
163 }
164
165 directRemoveFromList (listName, values) {
166 return this.directMessaging.removeFromList(this.userName, listName, values)
167 .return()
168 }
169
170 directSetWhitelistMode (mode) {
171 return this.directMessaging.changeMode(this.userName, mode).return()
172 }
173
174 listOwnSockets () {
175 return this.userState.getSocketsToRooms()
176 }
177
178 roomAddToList (roomName, listName, values, {bypassPermissions}) {
179 return this.state.getRoom(roomName).then(room => {
180 return Promise.join(
181 room.addToList(this.userName, listName, values, bypassPermissions),
182 room.roomState.accessListsUpdatesGet(),
183 (userNames, update) => {
184 if (update) {
185 this.transport.emitToChannel(
186 roomName, 'roomAccessListAdded', roomName, listName, values)
187 }
188 return this.removeRoomUsers(roomName, userNames)
189 })
190 }).return()
191 }
192
193 roomCreate (roomName, whitelistOnly, {bypassPermissions}) {
194 if (!this.enableRoomsManagement && !bypassPermissions) {
195 let error = new ChatServiceError('notAllowed')
196 return Promise.reject(error)
197 }
198 let owner = this.userName
199 return checkNameSymbols(roomName)
200 .then(() => this.state.addRoom(roomName, {owner, whitelistOnly}))
201 .return()
202 }
203
204 roomDelete (roomName, {bypassPermissions}) {
205 if (!this.enableRoomsManagement && !bypassPermissions) {
206 let error = new ChatServiceError('notAllowed')
207 return Promise.reject(error)
208 }
209 return this.state.getRoom(roomName).then(room => {
210 return room.checkIsOwner(this.userName, bypassPermissions)
211 .then(() => room.startRemoving())
212 .then(() => room.getUsers())
213 .then(userNames => this.removeRoomUsers(roomName, userNames))
214 .then(() => this.state.removeRoom(roomName))
215 .then(() => room.removeState())
216 .return()
217 })
218 }
219
220 roomGetAccessList (roomName, listName, {bypassPermissions}) {
221 return this.state.getRoom(roomName)
222 .then(room => room.getList(this.userName, listName, bypassPermissions))
223 }
224
225 roomGetOwner (roomName, {bypassPermissions}) {
226 return this.state.getRoom(roomName)
227 .then(room => room.getOwner(this.userName, bypassPermissions))
228 }
229
230 roomGetWhitelistMode (roomName, {bypassPermissions}) {
231 return this.state.getRoom(roomName)
232 .then(room => room.getMode(this.userName, bypassPermissions))
233 }
234
235 roomRecentHistory (roomName, {bypassPermissions}) {
236 return this.state.getRoom(roomName)
237 .then(room => room.getRecentMessages(this.userName, bypassPermissions))
238 }
239
240 roomHistoryGet (roomName, msgid, limit, {bypassPermissions}) {
241 return this.state.getRoom(roomName)
242 .then(room => room.getMessages(
243 this.userName, msgid, limit, bypassPermissions))
244 }
245
246 roomHistoryInfo (roomName, {bypassPermissions}) {
247 return this.state.getRoom(roomName)
248 .then(room => room.getHistoryInfo(this.userName, bypassPermissions))
249 }
250
251 roomJoin (roomName, {id, isLocalCall}) {
252 return this.state.getRoom(roomName)
253 .then(room => this.joinSocketToRoom(id, roomName, isLocalCall))
254 }
255
256 roomLeave (roomName, {id, isLocalCall}) {
257 return this.state.getRoom(roomName)
258 .then(room => this.leaveSocketFromRoom(id, room.roomName, isLocalCall))
259 }
260
261 roomMessage (roomName, msg, {bypassPermissions}) {
262 return this.state.getRoom(roomName).then(room => {
263 this.processMessage(msg)
264 return room.message(this.userName, msg, bypassPermissions)
265 }).then(pmsg => {
266 this.transport.emitToChannel(roomName, 'roomMessage', roomName, pmsg)
267 return pmsg.id
268 })
269 }
270
271 roomNotificationsInfo (roomName, {bypassPermissions}) {
272 return this.state.getRoom(roomName)
273 .then(room => room.getNotificationsInfo(this.userName, bypassPermissions))
274 }
275
276 roomRemoveFromList (roomName, listName, values, {bypassPermissions}) {
277 return this.state.getRoom(roomName).then(room => {
278 return Promise.join(
279 room.removeFromList(this.userName, listName, values, bypassPermissions),
280 room.roomState.accessListsUpdatesGet(),
281 (userNames, update) => {
282 if (update) {
283 this.transport.emitToChannel(
284 roomName, 'roomAccessListRemoved', roomName, listName, values)
285 }
286 return this.removeRoomUsers(roomName, userNames)
287 })
288 }).return()
289 }
290
291 roomSetWhitelistMode (roomName, mode, {bypassPermissions}) {
292 return this.state.getRoom(roomName).then(room => {
293 return Promise.join(
294 room.changeMode(this.userName, mode, bypassPermissions),
295 room.roomState.accessListsUpdatesGet(),
296 ([userNames, mode], update) => {
297 if (update) {
298 this.transport.emitToChannel(
299 roomName, 'roomModeChanged', roomName, mode)
300 }
301 return this.removeRoomUsers(roomName, userNames)
302 })
303 }).return()
304 }
305
306 roomUserSeen (roomName, userName, {bypassPermissions}) {
307 return this.state.getRoom(roomName)
308 .then(room => room.userSeen(this.userName, userName, bypassPermissions))
309 }
310
311 systemMessage (data, {id}) {
312 this.transport.sendToChannel(id, this.echoChannel, 'systemMessage', data)
313 return Promise.resolve()
314 }
315
316}
317
318module.exports = User