1 | 'use strict'
|
2 |
|
3 | const ChatServiceError = require('./ChatServiceError')
|
4 | const CommandBinder = require('./CommandBinder')
|
5 | const DirectMessaging = require('./DirectMessaging')
|
6 | const Promise = require('bluebird')
|
7 | const UserAssociations = require('./UserAssociations')
|
8 | const _ = require('lodash')
|
9 | const { asyncLimit, checkNameSymbols } = require('./utils')
|
10 | const { mixin } = require('es6-mixin')
|
11 |
|
12 |
|
13 | class 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 |
|
318 | module.exports = User
|