@webex/plugin-rooms 2.59.3-next.1 → 2.59.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/rooms.js CHANGED
@@ -1,544 +1,544 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {WebexPlugin, Page} from '@webex/webex-core';
6
- import {cloneDeep} from 'lodash';
7
- import {
8
- SDK_EVENT,
9
- createEventEnvelope,
10
- buildHydraPersonId,
11
- buildHydraRoomId,
12
- getHydraClusterString,
13
- getHydraRoomType,
14
- deconstructHydraId,
15
- } from '@webex/common';
16
-
17
- const debug = require('debug')('rooms');
18
-
19
- /**
20
- * @typedef {Object} RoomObject
21
- * @property {string} id - (server generated) Unique identifier for the room
22
- * @property {string} title - The display name for the room. All room members
23
- * will see the title so make it something good
24
- * @property {string} teamId - (optional) The ID of the team to which the room
25
- * belongs
26
- * @property {isoDate} created - (server generated) The date and time that the
27
- * room was created
28
- */
29
-
30
- /**
31
- * Rooms are virtual meeting places for getting stuff done. This resource
32
- * represents the room itself. Check out the {@link Memberships} API to learn
33
- * how to add and remove people from rooms and the {@link Messages} API for
34
- * posting and managing content.
35
- * @class
36
- * @name Rooms
37
- */
38
- const Rooms = WebexPlugin.extend({
39
- /**
40
- * Register to listen for incoming rooms events
41
- * This is an alternate approach to registering for rooms webhooks.
42
- * The events passed to any registered handlers will be similar to the webhook JSON,
43
- * but will omit webhook specific fields such as name, secret, url, etc.
44
- * To utilize the `listen()` method, the authorization token used
45
- * will need to have `spark:all` and `spark:kms` scopes enabled.
46
- * Note that by configuring your application to enable or disable `spark:all`
47
- * via its configuration page will also enable or disable `spark:kms`.
48
- * See the <a href="https://webex.github.io/webex-js-sdk/samples/browser-socket/">Sample App</a>
49
- * for more details.
50
- * @instance
51
- * @memberof Rooms
52
- * @returns {Promise}
53
- * @example
54
- * webex.rooms.listen()
55
- * .then(() => {
56
- * console.log('listening to room events');
57
- * webex.rooms.on('created', (event) => console.log(`Got a room:created event:\n${event}`);
58
- * webex.rooms.on('updated', (event) => console.log(`Got a room:updated event:\n${event}`);
59
- * })
60
- * .catch((e) => console.error(`Unable to register for room events: ${e}`));
61
- * // Some app logic...
62
- * // WHen it is time to cleanup
63
- * webex.rooms.stopListening();
64
- * webex.rooms.off('created');
65
- * webex.rooms.off('updated');
66
- */
67
- listen() {
68
- return createEventEnvelope(this.webex, SDK_EVENT.EXTERNAL.RESOURCE.ROOMS).then((envelope) => {
69
- this.eventEnvelope = envelope;
70
-
71
- return this.webex.internal.mercury.connect().then(() => {
72
- this.listenTo(this.webex.internal.mercury, SDK_EVENT.INTERNAL.WEBEX_ACTIVITY, (event) =>
73
- this.onWebexApiEvent(event)
74
- );
75
- });
76
- });
77
- },
78
-
79
- /**
80
- * Creates a new room. The authenticated user is automatically added as a
81
- * member of the room. See the {@link Memberships} API to learn how to add
82
- * more people to the room.
83
- * @instance
84
- * @memberof Rooms
85
- * @param {RoomObject} room
86
- * @returns {Promise<RoomObject>}
87
- * @example
88
- * webex.rooms.create({title: 'Create Room Example'})
89
- * .then(function(room) {
90
- * var assert = require('assert')
91
- * assert(typeof room.created === 'string');
92
- * assert(typeof room.id === 'string');
93
- * assert(room.title === 'Create Room Example');
94
- * console.log(room.title);
95
- * return 'success';
96
- * });
97
- * // => success
98
- */
99
- create(room) {
100
- return this.request({
101
- method: 'POST',
102
- service: 'hydra',
103
- resource: 'rooms',
104
- body: room,
105
- }).then((res) => res.body);
106
- },
107
-
108
- /**
109
- * Returns a single room.
110
- * @instance
111
- * @memberof Rooms
112
- * @param {RoomObject|string} room
113
- * @param {Object} options
114
- * @returns {Promise<RoomObject>}
115
- * @example
116
- * var room;
117
- * webex.rooms.create({title: 'Get Room Example'})
118
- * .then(function(r) {
119
- * room = r
120
- * return webex.rooms.get(room.id)
121
- * })
122
- * .then(function(r) {
123
- * var assert = require('assert');
124
- * assert.deepEqual(r, room);
125
- * return 'success';
126
- * });
127
- * // => success
128
- */
129
- get(room, options) {
130
- const id = room.id || room;
131
-
132
- return this.request({
133
- service: 'hydra',
134
- resource: `rooms/${id}`,
135
- qs: options,
136
- }).then((res) => res.body.items || res.body);
137
- },
138
-
139
- /**
140
- * Returns a list of rooms. In most cases the results will only contain rooms
141
- * that the authenticated user is a member of.
142
- * @instance
143
- * @memberof Rooms
144
- * @param {Object} options
145
- * @param {Object} options.max Limit the maximum number of rooms in the
146
- * response.
147
- * @returns {Promise<Page<RoomObject>>}
148
- * @example
149
- * var createdRooms;
150
- * Promise.all([
151
- * webex.rooms.create({title: 'List Rooms Example 1'}),
152
- * webex.rooms.create({title: 'List Rooms Example 2'}),
153
- * webex.rooms.create({title: 'List Rooms Example 3'})
154
- * ])
155
- * .then(function(r) {
156
- * createdRooms = r;
157
- * return webex.rooms.list({max: 3})
158
- * .then(function(rooms) {
159
- * var assert = require('assert');
160
- * assert(rooms.length === 3);
161
- * for (var i = 0; i < rooms.items.length; i+= 1) {
162
- * assert(createdRooms.filter(function(room) {
163
- * return room.id === rooms.items[i].id;
164
- * }).length === 1);
165
- * }
166
- * return 'success';
167
- * });
168
- * });
169
- * // => success
170
- */
171
- list(options) {
172
- return this.request({
173
- service: 'hydra',
174
- resource: 'rooms/',
175
- qs: options,
176
- }).then((res) => new Page(res, this.webex));
177
- },
178
-
179
- /**
180
- * Returns a list of rooms with details about the data of the last
181
- * activity in the room, and the date of the users last presences in
182
- * the room. The list is sorted with this with most recent activity first
183
- *
184
- * For rooms where lastActivityDate > lastSeenDate the space
185
- * can be considered to be "unread"
186
- *
187
- * This differs from the rooms.list() function in the following ways:
188
- * -- when called with no parameters it returns an array of all
189
- * spaces, up to 1000, that the user is a member of
190
- * -- pagination is not supported. ALL rooms are returned which
191
- * can result in a large payload
192
- * -- For users with hundreds of spaces, this API can take some time to
193
- * to return, for this reason it supports an optional maxRecent parameter.
194
- * If set this will return only the specified number of spaces with activity
195
- * in the last two weeks. Recommended value is 30. Max supported is 100.
196
- * -- only "id", "type", "lastActivityDate", and "lastSeenDate" are
197
- * guaranteed to be available for each room in the list
198
- * -- "title" is usually returned, but not guaranteed
199
- *
200
- * In general this function should be used only when the client needs to
201
- * access read status info, for example on startup.
202
- * After startup, clients should track message and membership:seen events
203
- * to maintain read status client side.
204
- *
205
- * Since this API can take some time to return up to 1000 spaces, it is
206
- * recommended that custom clients call this first with the maxRecent parameter
207
- * set to 30, so that they can display some of the more recents spaces. Calling
208
- * this API a second time with no parameters will return all the spaces.
209
- *
210
- * Not all spaces may be returned, for example when users in more than 1000
211
- * spaces, or when a new spaces is added after this function is called,
212
- * but before it returns. Custom clients should be prepared to gracefully
213
- * handle cases where an event occurs in a space not returned by this call,
214
- * by querying rooms.getWithReadStatus() with the id of the room in question
215
- *
216
- * This function may be deprecated when this info is provided in the membership
217
- * objects returned in the list function.
218
- * @instance
219
- * @param {int} maxRecent
220
- * @memberof Rooms
221
- * @returns {Promise<RoomInfoObjectList>}
222
- */
223
- async listWithReadStatus(maxRecent = 0) {
224
- const now = new Date();
225
- const options = {
226
- activitiesLimit: 0,
227
- computeTitleIfEmpty: true,
228
- conversationsLimit: 1000,
229
- isActive: true,
230
- };
231
-
232
- if (maxRecent > 0) {
233
- options.conversationsLimit = maxRecent;
234
- options.sinceDate = now.setDate(now.getDate() - 14);
235
- } else if (maxRecent < 0 || maxRecent > 100) {
236
- return Promise.reject(
237
- new Error(
238
- 'rooms.listWithReadStatus: ' +
239
- 'optional maxRecent parameter must be an integer between 1 and 100'
240
- )
241
- );
242
- }
243
-
244
- return this.webex.internal.services
245
- .waitForCatalog('postauth')
246
- .then(() => this.webex.internal.conversation.list(options))
247
- .then((conversations) => buildRoomInfoList(this.webex, conversations));
248
- },
249
-
250
- /**
251
- * Returns a single room object with details about the data of the last
252
- * activity in the room, and the date of the users last presence in
253
- * the room.
254
- *
255
- * For rooms where lastActivityDate > lastSeenDate the room
256
- * can be considered to be "unread"
257
- *
258
- * This differs from the rooms.get() function in the following ways:
259
- * -- it takes a single roomId parameter to fetch
260
- * -- no other options are considered
261
- * -- only "id", "type", "lastActivityDate", and "lastSeenDate" are
262
- * guaranteed to be available in the return object
263
- * -- "title" is usually returned, but not guaranteed
264
- *
265
- * In general clients should use the listWithReadStatus() method on startup
266
- * to get the initial roomStatus and then update their client side copy by
267
- * responding to message, membership and room events.
268
-
269
- * This function allows a custom client to be "nimble" if it is responding
270
- * to an event with a roomId that was not in the original fetch. The
271
- * anticipated behavior is that getWithReadStats is called "just in time",
272
- * with the resulting room object being added to the list of cached room
273
- * objects on the client side.
274
- *
275
- * This function may be deprecated when this info is provided in the room
276
- * object returned in the get function.
277
- * @instance
278
- * @memberof Rooms
279
- * @param {string} roomId
280
- * @returns {Promise<RoomInfoObject>}
281
- */
282
- getWithReadStatus(roomId) {
283
- const deconstructedId = deconstructHydraId(roomId);
284
- const conversation = {
285
- id: deconstructedId.id,
286
- cluster: deconstructedId.cluster,
287
- };
288
-
289
- return this.webex.internal.services.waitForCatalog('postauth').then(() =>
290
- this.webex.internal.conversation
291
- .get(conversation, {
292
- computeTitleIfEmpty: true,
293
- activitiesLimit: 0, // don't send the whole history of activity
294
- })
295
- .then((convo) => buildRoomInfo(this.webex, convo))
296
- );
297
- },
298
-
299
- /**
300
- * Deletes a single room.
301
- * @instance
302
- * @memberof Rooms
303
- * @param {RoomObject|string} room
304
- * @returns {Promise}
305
- * @example
306
- * var room;
307
- * webex.rooms.create({title: 'Remove Room Example'})
308
- * .then(function(r) {
309
- * room = r;
310
- * return webex.rooms.remove(room.id);
311
- * })
312
- * .then(function() {
313
- * return webex.rooms.get(room.id);
314
- * })
315
- * .then(function() {
316
- * var assert = require('assert');
317
- * assert(false, 'the previous get should have failed');
318
- * })
319
- * .catch(function(reason) {
320
- * var assert = require('assert');
321
- * assert.equal(reason.statusCode, 404);
322
- * return 'success'
323
- * });
324
- * // => success
325
- */
326
- remove(room) {
327
- const id = room.id || room;
328
-
329
- return this.request({
330
- method: 'DELETE',
331
- service: 'hydra',
332
- resource: `rooms/${id}`,
333
- }).then((res) => {
334
- // Firefox has some issues with 204s and/or DELETE. This should move to
335
- // http-core
336
- if (res.statusCode === 204) {
337
- return undefined;
338
- }
339
-
340
- return res.body;
341
- });
342
- },
343
-
344
- /**
345
- * Used to update a single room's properties.
346
- * @instance
347
- * @memberof Rooms
348
- * @param {RoomObject} room
349
- * @returns {Promise<RoomObject>}
350
- * @example
351
- * var room;
352
- * webex.rooms.update({title: 'Update Room Example'})
353
- * .then(function(r) {
354
- * room = r;
355
- * room.title = 'Update Room Example (Updated Title)';
356
- * return webex.rooms.update(room);
357
- * })
358
- * .then(function() {
359
- * return webex.rooms.get(room.id);
360
- * })
361
- * .then(function(room) {
362
- * var assert = require('assert');
363
- * assert.equal(room.title, 'Update Room Example (Updated Title)');
364
- * return 'success';
365
- * });
366
- * // => success
367
- */
368
- update(room) {
369
- const {id} = room;
370
-
371
- return this.request({
372
- method: 'PUT',
373
- service: 'hydra',
374
- resource: `rooms/${id}`,
375
- body: room,
376
- }).then((res) => res.body);
377
- },
378
-
379
- /**
380
- * This function is called when an internal membership events fires,
381
- * if the user registered for these events with the listen() function.
382
- * External users of the SDK should not call this function
383
- * @private
384
- * @memberof Rooms
385
- * @param {Object} event
386
- * @returns {void}
387
- */
388
- onWebexApiEvent(event) {
389
- const {activity} = event.data;
390
-
391
- /* eslint-disable no-case-declarations */
392
- switch (activity.verb) {
393
- case SDK_EVENT.INTERNAL.ACTIVITY_VERB.CREATE:
394
- const roomCreatedEvent = this.getRoomEvent(
395
- this.webex,
396
- activity,
397
- SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED
398
- );
399
-
400
- if (roomCreatedEvent) {
401
- debug(`room "created" payload: \
402
- ${JSON.stringify(roomCreatedEvent)}`);
403
- this.trigger(SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED, roomCreatedEvent);
404
- }
405
- break;
406
-
407
- case SDK_EVENT.INTERNAL.ACTIVITY_VERB.UPDATE:
408
- case SDK_EVENT.INTERNAL.ACTIVITY_VERB.LOCK:
409
- case SDK_EVENT.INTERNAL.ACTIVITY_VERB.UNLOCK:
410
- debug(`generating a rooms:updated based on ${activity.verb} activity`);
411
- const roomUpdatedEvent = this.getRoomEvent(
412
- this.webex,
413
- activity,
414
- SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED
415
- );
416
-
417
- if (roomUpdatedEvent) {
418
- debug(`room "updated" payload: \
419
- ${JSON.stringify(roomUpdatedEvent)}`);
420
- this.trigger(SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED, roomUpdatedEvent);
421
- }
422
- break;
423
-
424
- default:
425
- break;
426
- }
427
- },
428
-
429
- /**
430
- * Constructs the data object for an event on the rooms resource,
431
- * adhering to Hydra's Webhook data structure.
432
- * External users of the SDK should not call this function
433
- * @private
434
- * @memberof Rooms
435
- * @param {Object} webex sdk instance
436
- * @param {Object} activity from mercury
437
- * @param {Object} event type of "webhook" event
438
- * @returns {Object} constructed event
439
- */
440
- getRoomEvent(webex, activity, event) {
441
- try {
442
- const sdkEvent = cloneDeep(this.eventEnvelope);
443
- const cluster = getHydraClusterString(webex, activity.url);
444
- let {tags} = activity.object;
445
-
446
- sdkEvent.event = event;
447
- sdkEvent.data.created = activity.published;
448
- sdkEvent.actorId = buildHydraPersonId(activity.actor.entryUUID, cluster);
449
- if (activity.object.id) {
450
- sdkEvent.data.id = buildHydraRoomId(activity.object.id, cluster);
451
- } else {
452
- sdkEvent.data.id = buildHydraRoomId(activity.target.id, cluster);
453
- }
454
-
455
- if (event === SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED) {
456
- sdkEvent.data.creatorId = buildHydraPersonId(activity.actor.entryUUID, cluster);
457
- sdkEvent.data.lastActivity = activity.published;
458
- } else if (event === SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED) {
459
- if (activity.verb === 'update') {
460
- // For some reason the tags are not in the object for an update activity
461
- tags = activity.target.tags;
462
- }
463
- if (activity.object.creatorUUID) {
464
- // This seems to be set in lock/unlock activities but not updated...
465
- debug(`Found a creatorId: ${activity.object.creatorUUID} in a ${activity.verb} event`);
466
- sdkEvent.data.creatorId = buildHydraPersonId(activity.object.creatorUUID, cluster);
467
- }
468
- // Webhook engine team sets this based on lastReadableActivityDate
469
- // in the activity.target object. See: hydra/HydraRoom.java#L51
470
- // This elements seems to be missing from the activity that the SDK is getting
471
- // sdkEvent.data.lastActivity = activity.target.lastReadableActivityDate;
472
- } else {
473
- throw new Error('unexpected event type');
474
- }
475
- sdkEvent.data.type = getHydraRoomType(tags);
476
- sdkEvent.data.isLocked = tags.includes(SDK_EVENT.INTERNAL.ACTIVITY_TAG.LOCKED);
477
-
478
- return sdkEvent;
479
- } catch (e) {
480
- this.webex.logger.error(
481
- `Unable to generate SDK event from mercury socket activity for rooms:${event} event: ${e.message}`
482
- );
483
-
484
- return null;
485
- }
486
- },
487
- });
488
-
489
- export default Rooms;
490
-
491
- /**
492
- * Helper method to build a roomInfo object from a conversation object
493
- * @param {Object} webex sdk object
494
- * @param {Conversation~ConversationObject} conversation
495
- * @returns {Promise<RoomInfoObject>}
496
- */
497
- async function buildRoomInfo(webex, conversation) {
498
- try {
499
- const type = getHydraRoomType(conversation.tags);
500
- const cluster = getHydraClusterString(webex, conversation.url);
501
- const title = conversation.displayName ? conversation.displayName : conversation.computedTitle;
502
- const lastActivityDate = conversation.lastReadableActivityDate
503
- ? conversation.lastReadableActivityDate
504
- : conversation.lastRelevantActivityDate;
505
-
506
- const roomInfo = {
507
- id: buildHydraRoomId(conversation.id, cluster),
508
- type,
509
- ...(title && {title: conversation.displayName}),
510
- ...(lastActivityDate && {lastActivityDate}),
511
- lastSeenActivityDate: conversation.lastSeenActivityDate
512
- ? conversation.lastSeenActivityDate
513
- : // If user has never been seen set the date to "a long time ago"
514
- new Date(0).toISOString(),
515
- };
516
-
517
- return Promise.resolve(roomInfo);
518
- } catch (e) {
519
- return Promise.reject(e);
520
- }
521
- }
522
-
523
- /**
524
- * Helper method to build a list of roomInfo object from conversation list
525
- * @param {Object} webex sdk object
526
- * @param {Conversation~ConversationObjectList} conversations
527
- * @returns {Promise<RoomInfoList>}
528
- */
529
- async function buildRoomInfoList(webex, conversations) {
530
- // Convert each Conversation into a roomInfo object
531
- const roomReadInfo = {items: []};
532
- const roomInfoPromises = [];
533
-
534
- for (const conversation of conversations) {
535
- roomInfoPromises.push(buildRoomInfo(webex, conversation));
536
- }
537
-
538
- return Promise.all(roomInfoPromises).then((roomInfoList) => {
539
- roomReadInfo.items = roomInfoList;
540
- roomReadInfo.items.sort((a, b) => (a.lastActivityDate < b.lastActivityDate ? 1 : -1));
541
-
542
- return roomReadInfo;
543
- });
544
- }
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {WebexPlugin, Page} from '@webex/webex-core';
6
+ import {cloneDeep} from 'lodash';
7
+ import {
8
+ SDK_EVENT,
9
+ createEventEnvelope,
10
+ buildHydraPersonId,
11
+ buildHydraRoomId,
12
+ getHydraClusterString,
13
+ getHydraRoomType,
14
+ deconstructHydraId,
15
+ } from '@webex/common';
16
+
17
+ const debug = require('debug')('rooms');
18
+
19
+ /**
20
+ * @typedef {Object} RoomObject
21
+ * @property {string} id - (server generated) Unique identifier for the room
22
+ * @property {string} title - The display name for the room. All room members
23
+ * will see the title so make it something good
24
+ * @property {string} teamId - (optional) The ID of the team to which the room
25
+ * belongs
26
+ * @property {isoDate} created - (server generated) The date and time that the
27
+ * room was created
28
+ */
29
+
30
+ /**
31
+ * Rooms are virtual meeting places for getting stuff done. This resource
32
+ * represents the room itself. Check out the {@link Memberships} API to learn
33
+ * how to add and remove people from rooms and the {@link Messages} API for
34
+ * posting and managing content.
35
+ * @class
36
+ * @name Rooms
37
+ */
38
+ const Rooms = WebexPlugin.extend({
39
+ /**
40
+ * Register to listen for incoming rooms events
41
+ * This is an alternate approach to registering for rooms webhooks.
42
+ * The events passed to any registered handlers will be similar to the webhook JSON,
43
+ * but will omit webhook specific fields such as name, secret, url, etc.
44
+ * To utilize the `listen()` method, the authorization token used
45
+ * will need to have `spark:all` and `spark:kms` scopes enabled.
46
+ * Note that by configuring your application to enable or disable `spark:all`
47
+ * via its configuration page will also enable or disable `spark:kms`.
48
+ * See the <a href="https://webex.github.io/webex-js-sdk/samples/browser-socket/">Sample App</a>
49
+ * for more details.
50
+ * @instance
51
+ * @memberof Rooms
52
+ * @returns {Promise}
53
+ * @example
54
+ * webex.rooms.listen()
55
+ * .then(() => {
56
+ * console.log('listening to room events');
57
+ * webex.rooms.on('created', (event) => console.log(`Got a room:created event:\n${event}`);
58
+ * webex.rooms.on('updated', (event) => console.log(`Got a room:updated event:\n${event}`);
59
+ * })
60
+ * .catch((e) => console.error(`Unable to register for room events: ${e}`));
61
+ * // Some app logic...
62
+ * // WHen it is time to cleanup
63
+ * webex.rooms.stopListening();
64
+ * webex.rooms.off('created');
65
+ * webex.rooms.off('updated');
66
+ */
67
+ listen() {
68
+ return createEventEnvelope(this.webex, SDK_EVENT.EXTERNAL.RESOURCE.ROOMS).then((envelope) => {
69
+ this.eventEnvelope = envelope;
70
+
71
+ return this.webex.internal.mercury.connect().then(() => {
72
+ this.listenTo(this.webex.internal.mercury, SDK_EVENT.INTERNAL.WEBEX_ACTIVITY, (event) =>
73
+ this.onWebexApiEvent(event)
74
+ );
75
+ });
76
+ });
77
+ },
78
+
79
+ /**
80
+ * Creates a new room. The authenticated user is automatically added as a
81
+ * member of the room. See the {@link Memberships} API to learn how to add
82
+ * more people to the room.
83
+ * @instance
84
+ * @memberof Rooms
85
+ * @param {RoomObject} room
86
+ * @returns {Promise<RoomObject>}
87
+ * @example
88
+ * webex.rooms.create({title: 'Create Room Example'})
89
+ * .then(function(room) {
90
+ * var assert = require('assert')
91
+ * assert(typeof room.created === 'string');
92
+ * assert(typeof room.id === 'string');
93
+ * assert(room.title === 'Create Room Example');
94
+ * console.log(room.title);
95
+ * return 'success';
96
+ * });
97
+ * // => success
98
+ */
99
+ create(room) {
100
+ return this.request({
101
+ method: 'POST',
102
+ service: 'hydra',
103
+ resource: 'rooms',
104
+ body: room,
105
+ }).then((res) => res.body);
106
+ },
107
+
108
+ /**
109
+ * Returns a single room.
110
+ * @instance
111
+ * @memberof Rooms
112
+ * @param {RoomObject|string} room
113
+ * @param {Object} options
114
+ * @returns {Promise<RoomObject>}
115
+ * @example
116
+ * var room;
117
+ * webex.rooms.create({title: 'Get Room Example'})
118
+ * .then(function(r) {
119
+ * room = r
120
+ * return webex.rooms.get(room.id)
121
+ * })
122
+ * .then(function(r) {
123
+ * var assert = require('assert');
124
+ * assert.deepEqual(r, room);
125
+ * return 'success';
126
+ * });
127
+ * // => success
128
+ */
129
+ get(room, options) {
130
+ const id = room.id || room;
131
+
132
+ return this.request({
133
+ service: 'hydra',
134
+ resource: `rooms/${id}`,
135
+ qs: options,
136
+ }).then((res) => res.body.items || res.body);
137
+ },
138
+
139
+ /**
140
+ * Returns a list of rooms. In most cases the results will only contain rooms
141
+ * that the authenticated user is a member of.
142
+ * @instance
143
+ * @memberof Rooms
144
+ * @param {Object} options
145
+ * @param {Object} options.max Limit the maximum number of rooms in the
146
+ * response.
147
+ * @returns {Promise<Page<RoomObject>>}
148
+ * @example
149
+ * var createdRooms;
150
+ * Promise.all([
151
+ * webex.rooms.create({title: 'List Rooms Example 1'}),
152
+ * webex.rooms.create({title: 'List Rooms Example 2'}),
153
+ * webex.rooms.create({title: 'List Rooms Example 3'})
154
+ * ])
155
+ * .then(function(r) {
156
+ * createdRooms = r;
157
+ * return webex.rooms.list({max: 3})
158
+ * .then(function(rooms) {
159
+ * var assert = require('assert');
160
+ * assert(rooms.length === 3);
161
+ * for (var i = 0; i < rooms.items.length; i+= 1) {
162
+ * assert(createdRooms.filter(function(room) {
163
+ * return room.id === rooms.items[i].id;
164
+ * }).length === 1);
165
+ * }
166
+ * return 'success';
167
+ * });
168
+ * });
169
+ * // => success
170
+ */
171
+ list(options) {
172
+ return this.request({
173
+ service: 'hydra',
174
+ resource: 'rooms/',
175
+ qs: options,
176
+ }).then((res) => new Page(res, this.webex));
177
+ },
178
+
179
+ /**
180
+ * Returns a list of rooms with details about the data of the last
181
+ * activity in the room, and the date of the users last presences in
182
+ * the room. The list is sorted with this with most recent activity first
183
+ *
184
+ * For rooms where lastActivityDate > lastSeenDate the space
185
+ * can be considered to be "unread"
186
+ *
187
+ * This differs from the rooms.list() function in the following ways:
188
+ * -- when called with no parameters it returns an array of all
189
+ * spaces, up to 1000, that the user is a member of
190
+ * -- pagination is not supported. ALL rooms are returned which
191
+ * can result in a large payload
192
+ * -- For users with hundreds of spaces, this API can take some time to
193
+ * to return, for this reason it supports an optional maxRecent parameter.
194
+ * If set this will return only the specified number of spaces with activity
195
+ * in the last two weeks. Recommended value is 30. Max supported is 100.
196
+ * -- only "id", "type", "lastActivityDate", and "lastSeenDate" are
197
+ * guaranteed to be available for each room in the list
198
+ * -- "title" is usually returned, but not guaranteed
199
+ *
200
+ * In general this function should be used only when the client needs to
201
+ * access read status info, for example on startup.
202
+ * After startup, clients should track message and membership:seen events
203
+ * to maintain read status client side.
204
+ *
205
+ * Since this API can take some time to return up to 1000 spaces, it is
206
+ * recommended that custom clients call this first with the maxRecent parameter
207
+ * set to 30, so that they can display some of the more recents spaces. Calling
208
+ * this API a second time with no parameters will return all the spaces.
209
+ *
210
+ * Not all spaces may be returned, for example when users in more than 1000
211
+ * spaces, or when a new spaces is added after this function is called,
212
+ * but before it returns. Custom clients should be prepared to gracefully
213
+ * handle cases where an event occurs in a space not returned by this call,
214
+ * by querying rooms.getWithReadStatus() with the id of the room in question
215
+ *
216
+ * This function may be deprecated when this info is provided in the membership
217
+ * objects returned in the list function.
218
+ * @instance
219
+ * @param {int} maxRecent
220
+ * @memberof Rooms
221
+ * @returns {Promise<RoomInfoObjectList>}
222
+ */
223
+ async listWithReadStatus(maxRecent = 0) {
224
+ const now = new Date();
225
+ const options = {
226
+ activitiesLimit: 0,
227
+ computeTitleIfEmpty: true,
228
+ conversationsLimit: 1000,
229
+ isActive: true,
230
+ };
231
+
232
+ if (maxRecent > 0) {
233
+ options.conversationsLimit = maxRecent;
234
+ options.sinceDate = now.setDate(now.getDate() - 14);
235
+ } else if (maxRecent < 0 || maxRecent > 100) {
236
+ return Promise.reject(
237
+ new Error(
238
+ 'rooms.listWithReadStatus: ' +
239
+ 'optional maxRecent parameter must be an integer between 1 and 100'
240
+ )
241
+ );
242
+ }
243
+
244
+ return this.webex.internal.services
245
+ .waitForCatalog('postauth')
246
+ .then(() => this.webex.internal.conversation.list(options))
247
+ .then((conversations) => buildRoomInfoList(this.webex, conversations));
248
+ },
249
+
250
+ /**
251
+ * Returns a single room object with details about the data of the last
252
+ * activity in the room, and the date of the users last presence in
253
+ * the room.
254
+ *
255
+ * For rooms where lastActivityDate > lastSeenDate the room
256
+ * can be considered to be "unread"
257
+ *
258
+ * This differs from the rooms.get() function in the following ways:
259
+ * -- it takes a single roomId parameter to fetch
260
+ * -- no other options are considered
261
+ * -- only "id", "type", "lastActivityDate", and "lastSeenDate" are
262
+ * guaranteed to be available in the return object
263
+ * -- "title" is usually returned, but not guaranteed
264
+ *
265
+ * In general clients should use the listWithReadStatus() method on startup
266
+ * to get the initial roomStatus and then update their client side copy by
267
+ * responding to message, membership and room events.
268
+
269
+ * This function allows a custom client to be "nimble" if it is responding
270
+ * to an event with a roomId that was not in the original fetch. The
271
+ * anticipated behavior is that getWithReadStats is called "just in time",
272
+ * with the resulting room object being added to the list of cached room
273
+ * objects on the client side.
274
+ *
275
+ * This function may be deprecated when this info is provided in the room
276
+ * object returned in the get function.
277
+ * @instance
278
+ * @memberof Rooms
279
+ * @param {string} roomId
280
+ * @returns {Promise<RoomInfoObject>}
281
+ */
282
+ getWithReadStatus(roomId) {
283
+ const deconstructedId = deconstructHydraId(roomId);
284
+ const conversation = {
285
+ id: deconstructedId.id,
286
+ cluster: deconstructedId.cluster,
287
+ };
288
+
289
+ return this.webex.internal.services.waitForCatalog('postauth').then(() =>
290
+ this.webex.internal.conversation
291
+ .get(conversation, {
292
+ computeTitleIfEmpty: true,
293
+ activitiesLimit: 0, // don't send the whole history of activity
294
+ })
295
+ .then((convo) => buildRoomInfo(this.webex, convo))
296
+ );
297
+ },
298
+
299
+ /**
300
+ * Deletes a single room.
301
+ * @instance
302
+ * @memberof Rooms
303
+ * @param {RoomObject|string} room
304
+ * @returns {Promise}
305
+ * @example
306
+ * var room;
307
+ * webex.rooms.create({title: 'Remove Room Example'})
308
+ * .then(function(r) {
309
+ * room = r;
310
+ * return webex.rooms.remove(room.id);
311
+ * })
312
+ * .then(function() {
313
+ * return webex.rooms.get(room.id);
314
+ * })
315
+ * .then(function() {
316
+ * var assert = require('assert');
317
+ * assert(false, 'the previous get should have failed');
318
+ * })
319
+ * .catch(function(reason) {
320
+ * var assert = require('assert');
321
+ * assert.equal(reason.statusCode, 404);
322
+ * return 'success'
323
+ * });
324
+ * // => success
325
+ */
326
+ remove(room) {
327
+ const id = room.id || room;
328
+
329
+ return this.request({
330
+ method: 'DELETE',
331
+ service: 'hydra',
332
+ resource: `rooms/${id}`,
333
+ }).then((res) => {
334
+ // Firefox has some issues with 204s and/or DELETE. This should move to
335
+ // http-core
336
+ if (res.statusCode === 204) {
337
+ return undefined;
338
+ }
339
+
340
+ return res.body;
341
+ });
342
+ },
343
+
344
+ /**
345
+ * Used to update a single room's properties.
346
+ * @instance
347
+ * @memberof Rooms
348
+ * @param {RoomObject} room
349
+ * @returns {Promise<RoomObject>}
350
+ * @example
351
+ * var room;
352
+ * webex.rooms.update({title: 'Update Room Example'})
353
+ * .then(function(r) {
354
+ * room = r;
355
+ * room.title = 'Update Room Example (Updated Title)';
356
+ * return webex.rooms.update(room);
357
+ * })
358
+ * .then(function() {
359
+ * return webex.rooms.get(room.id);
360
+ * })
361
+ * .then(function(room) {
362
+ * var assert = require('assert');
363
+ * assert.equal(room.title, 'Update Room Example (Updated Title)');
364
+ * return 'success';
365
+ * });
366
+ * // => success
367
+ */
368
+ update(room) {
369
+ const {id} = room;
370
+
371
+ return this.request({
372
+ method: 'PUT',
373
+ service: 'hydra',
374
+ resource: `rooms/${id}`,
375
+ body: room,
376
+ }).then((res) => res.body);
377
+ },
378
+
379
+ /**
380
+ * This function is called when an internal membership events fires,
381
+ * if the user registered for these events with the listen() function.
382
+ * External users of the SDK should not call this function
383
+ * @private
384
+ * @memberof Rooms
385
+ * @param {Object} event
386
+ * @returns {void}
387
+ */
388
+ onWebexApiEvent(event) {
389
+ const {activity} = event.data;
390
+
391
+ /* eslint-disable no-case-declarations */
392
+ switch (activity.verb) {
393
+ case SDK_EVENT.INTERNAL.ACTIVITY_VERB.CREATE:
394
+ const roomCreatedEvent = this.getRoomEvent(
395
+ this.webex,
396
+ activity,
397
+ SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED
398
+ );
399
+
400
+ if (roomCreatedEvent) {
401
+ debug(`room "created" payload: \
402
+ ${JSON.stringify(roomCreatedEvent)}`);
403
+ this.trigger(SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED, roomCreatedEvent);
404
+ }
405
+ break;
406
+
407
+ case SDK_EVENT.INTERNAL.ACTIVITY_VERB.UPDATE:
408
+ case SDK_EVENT.INTERNAL.ACTIVITY_VERB.LOCK:
409
+ case SDK_EVENT.INTERNAL.ACTIVITY_VERB.UNLOCK:
410
+ debug(`generating a rooms:updated based on ${activity.verb} activity`);
411
+ const roomUpdatedEvent = this.getRoomEvent(
412
+ this.webex,
413
+ activity,
414
+ SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED
415
+ );
416
+
417
+ if (roomUpdatedEvent) {
418
+ debug(`room "updated" payload: \
419
+ ${JSON.stringify(roomUpdatedEvent)}`);
420
+ this.trigger(SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED, roomUpdatedEvent);
421
+ }
422
+ break;
423
+
424
+ default:
425
+ break;
426
+ }
427
+ },
428
+
429
+ /**
430
+ * Constructs the data object for an event on the rooms resource,
431
+ * adhering to Hydra's Webhook data structure.
432
+ * External users of the SDK should not call this function
433
+ * @private
434
+ * @memberof Rooms
435
+ * @param {Object} webex sdk instance
436
+ * @param {Object} activity from mercury
437
+ * @param {Object} event type of "webhook" event
438
+ * @returns {Object} constructed event
439
+ */
440
+ getRoomEvent(webex, activity, event) {
441
+ try {
442
+ const sdkEvent = cloneDeep(this.eventEnvelope);
443
+ const cluster = getHydraClusterString(webex, activity.url);
444
+ let {tags} = activity.object;
445
+
446
+ sdkEvent.event = event;
447
+ sdkEvent.data.created = activity.published;
448
+ sdkEvent.actorId = buildHydraPersonId(activity.actor.entryUUID, cluster);
449
+ if (activity.object.id) {
450
+ sdkEvent.data.id = buildHydraRoomId(activity.object.id, cluster);
451
+ } else {
452
+ sdkEvent.data.id = buildHydraRoomId(activity.target.id, cluster);
453
+ }
454
+
455
+ if (event === SDK_EVENT.EXTERNAL.EVENT_TYPE.CREATED) {
456
+ sdkEvent.data.creatorId = buildHydraPersonId(activity.actor.entryUUID, cluster);
457
+ sdkEvent.data.lastActivity = activity.published;
458
+ } else if (event === SDK_EVENT.EXTERNAL.EVENT_TYPE.UPDATED) {
459
+ if (activity.verb === 'update') {
460
+ // For some reason the tags are not in the object for an update activity
461
+ tags = activity.target.tags;
462
+ }
463
+ if (activity.object.creatorUUID) {
464
+ // This seems to be set in lock/unlock activities but not updated...
465
+ debug(`Found a creatorId: ${activity.object.creatorUUID} in a ${activity.verb} event`);
466
+ sdkEvent.data.creatorId = buildHydraPersonId(activity.object.creatorUUID, cluster);
467
+ }
468
+ // Webhook engine team sets this based on lastReadableActivityDate
469
+ // in the activity.target object. See: hydra/HydraRoom.java#L51
470
+ // This elements seems to be missing from the activity that the SDK is getting
471
+ // sdkEvent.data.lastActivity = activity.target.lastReadableActivityDate;
472
+ } else {
473
+ throw new Error('unexpected event type');
474
+ }
475
+ sdkEvent.data.type = getHydraRoomType(tags);
476
+ sdkEvent.data.isLocked = tags.includes(SDK_EVENT.INTERNAL.ACTIVITY_TAG.LOCKED);
477
+
478
+ return sdkEvent;
479
+ } catch (e) {
480
+ this.webex.logger.error(
481
+ `Unable to generate SDK event from mercury socket activity for rooms:${event} event: ${e.message}`
482
+ );
483
+
484
+ return null;
485
+ }
486
+ },
487
+ });
488
+
489
+ export default Rooms;
490
+
491
+ /**
492
+ * Helper method to build a roomInfo object from a conversation object
493
+ * @param {Object} webex sdk object
494
+ * @param {Conversation~ConversationObject} conversation
495
+ * @returns {Promise<RoomInfoObject>}
496
+ */
497
+ async function buildRoomInfo(webex, conversation) {
498
+ try {
499
+ const type = getHydraRoomType(conversation.tags);
500
+ const cluster = getHydraClusterString(webex, conversation.url);
501
+ const title = conversation.displayName ? conversation.displayName : conversation.computedTitle;
502
+ const lastActivityDate = conversation.lastReadableActivityDate
503
+ ? conversation.lastReadableActivityDate
504
+ : conversation.lastRelevantActivityDate;
505
+
506
+ const roomInfo = {
507
+ id: buildHydraRoomId(conversation.id, cluster),
508
+ type,
509
+ ...(title && {title: conversation.displayName}),
510
+ ...(lastActivityDate && {lastActivityDate}),
511
+ lastSeenActivityDate: conversation.lastSeenActivityDate
512
+ ? conversation.lastSeenActivityDate
513
+ : // If user has never been seen set the date to "a long time ago"
514
+ new Date(0).toISOString(),
515
+ };
516
+
517
+ return Promise.resolve(roomInfo);
518
+ } catch (e) {
519
+ return Promise.reject(e);
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Helper method to build a list of roomInfo object from conversation list
525
+ * @param {Object} webex sdk object
526
+ * @param {Conversation~ConversationObjectList} conversations
527
+ * @returns {Promise<RoomInfoList>}
528
+ */
529
+ async function buildRoomInfoList(webex, conversations) {
530
+ // Convert each Conversation into a roomInfo object
531
+ const roomReadInfo = {items: []};
532
+ const roomInfoPromises = [];
533
+
534
+ for (const conversation of conversations) {
535
+ roomInfoPromises.push(buildRoomInfo(webex, conversation));
536
+ }
537
+
538
+ return Promise.all(roomInfoPromises).then((roomInfoList) => {
539
+ roomReadInfo.items = roomInfoList;
540
+ roomReadInfo.items.sort((a, b) => (a.lastActivityDate < b.lastActivityDate ? 1 : -1));
541
+
542
+ return roomReadInfo;
543
+ });
544
+ }