@webex/internal-plugin-conversation 2.59.3-next.1 → 2.59.4-next.1

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.
Files changed (49) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +47 -47
  3. package/babel.config.js +3 -3
  4. package/dist/activities.js +6 -6
  5. package/dist/activities.js.map +1 -1
  6. package/dist/activity-thread-ordering.js +38 -38
  7. package/dist/activity-thread-ordering.js.map +1 -1
  8. package/dist/config.js +12 -12
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js.map +1 -1
  11. package/dist/conversation.js +511 -522
  12. package/dist/conversation.js.map +1 -1
  13. package/dist/convo-error.js +4 -4
  14. package/dist/convo-error.js.map +1 -1
  15. package/dist/decryption-transforms.js +163 -161
  16. package/dist/decryption-transforms.js.map +1 -1
  17. package/dist/encryption-transforms.js +19 -19
  18. package/dist/encryption-transforms.js.map +1 -1
  19. package/dist/index.js +13 -15
  20. package/dist/index.js.map +1 -1
  21. package/dist/share-activity.js +67 -70
  22. package/dist/share-activity.js.map +1 -1
  23. package/dist/to-array.js +10 -10
  24. package/dist/to-array.js.map +1 -1
  25. package/jest.config.js +3 -3
  26. package/package.json +14 -14
  27. package/process +1 -1
  28. package/src/activities.js +157 -157
  29. package/src/activity-thread-ordering.js +283 -283
  30. package/src/activity-threading.md +282 -282
  31. package/src/config.js +37 -37
  32. package/src/constants.js +3 -3
  33. package/src/conversation.js +2535 -2535
  34. package/src/convo-error.js +15 -15
  35. package/src/decryption-transforms.js +541 -541
  36. package/src/encryption-transforms.js +345 -345
  37. package/src/index.js +327 -327
  38. package/src/share-activity.js +436 -436
  39. package/src/to-array.js +29 -29
  40. package/test/integration/spec/create.js +290 -290
  41. package/test/integration/spec/encryption.js +333 -333
  42. package/test/integration/spec/get.js +1255 -1255
  43. package/test/integration/spec/mercury.js +94 -94
  44. package/test/integration/spec/share.js +537 -537
  45. package/test/integration/spec/verbs.js +1041 -1041
  46. package/test/unit/spec/conversation.js +823 -823
  47. package/test/unit/spec/decrypt-transforms.js +460 -460
  48. package/test/unit/spec/encryption-transforms.js +93 -93
  49. package/test/unit/spec/share-activity.js +178 -178
@@ -1,333 +1,333 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import WebexCore, {WebexHttpError} from '@webex/webex-core';
6
- import {assert, expect} from '@webex/test-helper-chai';
7
- import testUsers from '@webex/test-helper-test-users';
8
-
9
- describe('plugin-conversation', () => {
10
- let checkov, mccoy, participants, webex, spock;
11
-
12
- before(() =>
13
- testUsers.create({count: 3}).then((users) => {
14
- participants = [spock, mccoy, checkov] = users;
15
-
16
- webex = new WebexCore({
17
- credentials: {
18
- authorization: spock.token,
19
- },
20
- });
21
-
22
- return webex.internal.mercury.connect();
23
- })
24
- );
25
-
26
- after(() => webex && webex.internal.mercury.disconnect());
27
-
28
- describe('when not supplying enough encryption data', () => {
29
- let conversation;
30
-
31
- before(() =>
32
- webex.internal.conversation.create({participants, comment: 'first'}).then((c) => {
33
- conversation = c;
34
- })
35
- );
36
-
37
- it('fetches the conversation and does not alter its key', () =>
38
- webex.internal.conversation
39
- .post({url: conversation.url}, {displayName: 'second'})
40
- .then(() => webex.internal.conversation.get(conversation))
41
- .then((c) =>
42
- assert.equal(
43
- c.defaultActivityEncryptionKeyUrl,
44
- conversation.defaultActivityEncryptionKeyUrl
45
- )
46
- ));
47
- });
48
-
49
- describe('when interacting with a non-encrypted conversation', () => {
50
- before(() => {
51
- mccoy.webex = new WebexCore({
52
- credentials: {
53
- authorization: mccoy.token,
54
- },
55
- });
56
-
57
- checkov.webex = new WebexCore({
58
- credentials: {
59
- authorization: checkov.token,
60
- },
61
- });
62
-
63
- return Promise.all([
64
- checkov.webex.internal.mercury.connect(),
65
- mccoy.webex.internal.mercury.connect(),
66
- ]);
67
- });
68
-
69
- after(() =>
70
- Promise.all([
71
- checkov && checkov.webex && checkov.webex.internal.mercury.disconnect(),
72
- mccoy && mccoy.webex && mccoy.webex.internal.mercury.disconnect(),
73
- ])
74
- );
75
-
76
- let conversation;
77
-
78
- beforeEach(() =>
79
- webex
80
- .request({
81
- method: 'POST',
82
- service: 'conversation',
83
- resource: '/conversations',
84
- noTransform: true,
85
- body: {
86
- objectType: 'conversation',
87
- activities: {
88
- items: [
89
- {
90
- verb: 'create',
91
- actor: {
92
- id: spock.id,
93
- objectType: 'person',
94
- },
95
- },
96
- {
97
- verb: 'add',
98
- actor: {
99
- id: spock.id,
100
- objectType: 'person',
101
- },
102
- object: {
103
- id: spock.id,
104
- objectType: 'person',
105
- },
106
- },
107
- {
108
- verb: 'add',
109
- actor: {
110
- id: spock.id,
111
- objectType: 'person',
112
- },
113
- object: {
114
- id: checkov.id,
115
- objectType: 'person',
116
- },
117
- },
118
- ],
119
- },
120
- },
121
- })
122
- .then((res) => res.body)
123
- .then((c) => {
124
- conversation = c;
125
- assert.notProperty(conversation, 'defaultActivityEncryptionKeyUrl');
126
- })
127
- );
128
-
129
- describe('when the conversation is a grouped conversation', () => {
130
- describe('#add()', () => {
131
- it('adds the specified user', () =>
132
- webex.internal.conversation
133
- .add(conversation, mccoy)
134
- .then(() => mccoy.webex.internal.conversation.get(conversation))
135
- .then((c) =>
136
- assert.property(
137
- c,
138
- 'defaultActivityEncryptionKeyUrl',
139
- 'The conversation was encrypted as a side effect of the add activity'
140
- )
141
- ));
142
- });
143
-
144
- describe('#leave()', () => {
145
- it('removes the current user', () =>
146
- webex.internal.conversation
147
- .leave(conversation)
148
- .then(() => assert.isRejected(webex.internal.conversation.get(conversation)))
149
- .then((reason) => assert.instanceOf(reason, WebexHttpError.NotFound))
150
- .then(() => checkov.webex.internal.conversation.get(conversation))
151
- .then((c) =>
152
- assert.notProperty(
153
- c,
154
- 'defaultActivityEncryptionKeyUrl',
155
- 'The conversation was not encrypted as a side effect of the leave activity'
156
- )
157
- ));
158
-
159
- it('removes the specified user', () =>
160
- webex.internal.conversation
161
- .leave(conversation, checkov)
162
- .then(() => assert.isRejected(checkov.webex.internal.conversation.get(conversation)))
163
- .then((reason) => assert.instanceOf(reason, WebexHttpError.NotFound))
164
- .then(() => webex.internal.conversation.get(conversation))
165
- .then((c) =>
166
- assert.notProperty(
167
- c,
168
- 'defaultActivityEncryptionKeyUrl',
169
- 'The conversation was not encrypted as a side effect of the leave activity'
170
- )
171
- ));
172
- });
173
-
174
- describe('#post()', () => {
175
- it('posts a message', () =>
176
- webex.internal.conversation
177
- .post(conversation, {displayName: 'Ahoy'})
178
- .then(() => checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
179
- .then((c) => {
180
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
181
- assert.equal(c.activities.items[0].object.displayName, 'Ahoy');
182
- }));
183
- });
184
-
185
- describe('#cardAction()', () => {
186
- it('creates a cardAction Activity', () =>
187
- webex.internal.conversation
188
- .post(conversation, {displayName: 'First Message', cards: ['test']})
189
- .then(() => checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
190
- .then((c) => {
191
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
192
- assert.equal(c.activities.items[0].object.displayName, 'First Message');
193
- webex.internal.conversation
194
- .cardAction(
195
- conversation,
196
- {
197
- objectType: 'submit',
198
- inputs: {key: 'value'},
199
- },
200
- c.activities.items[0]
201
- )
202
- .then(() =>
203
- checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1})
204
- )
205
- .then((ci) => {
206
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
207
- /* eslint-disable no-unused-expressions */
208
- expect(ci.activities.items[0].object.inputs).to.not.be.null;
209
- });
210
- }));
211
- });
212
-
213
- describe('#update()', () => {
214
- it("sets the conversation's title", () =>
215
- webex.internal.conversation
216
- .update(conversation, {
217
- displayName: 'New Name!',
218
- objectType: 'conversation',
219
- })
220
- .then(() => checkov.webex.internal.conversation.get(conversation))
221
- .then((c) => {
222
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
223
- assert.property(c, 'encryptionKeyUrl');
224
- assert.equal(c.displayName, 'New Name!');
225
- }));
226
- });
227
-
228
- describe('#updateKey()', () => {
229
- it("sets the conversation's defaultActivityEncryptionKeyUrl", () =>
230
- webex.internal.conversation
231
- .updateKey(conversation)
232
- .then(() => webex.internal.conversation.get(conversation))
233
- .then((c) => {
234
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
235
- })
236
- .then(() => checkov.webex.internal.conversation.get(conversation))
237
- .then((c) => {
238
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
239
- }));
240
-
241
- it("unsets the conversations's defaultActivityEncryptionKeyUrl", () =>
242
- webex.internal.conversation
243
- .updateKey(conversation, {uri: null})
244
- .then(() => checkov.webex.internal.conversation.get(conversation))
245
- .then((c) => {
246
- assert.isUndefined(c.defaultActivityEncryptionKeyUrl);
247
- }));
248
-
249
- describe('when the KMS key has been unset', () => {
250
- it('rotates the key and sends the post', () =>
251
- webex.internal.conversation
252
- .updateKey(conversation, {uri: null})
253
- .then(() => webex.internal.conversation.post(conversation, {displayName: 'Ahoy'}))
254
- .then(() =>
255
- checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1})
256
- )
257
- .then((c) => {
258
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
259
- assert.equal(c.activities.items[0].object.displayName, 'Ahoy');
260
- }));
261
- });
262
- });
263
- });
264
-
265
- describe('when the conversation is a 1:1 conversation', () => {
266
- let conversation;
267
-
268
- beforeEach(() =>
269
- webex
270
- .request({
271
- method: 'POST',
272
- service: 'conversation',
273
- resource: '/conversations',
274
- noTransform: true,
275
- body: {
276
- objectType: 'conversation',
277
- activities: {
278
- items: [
279
- {
280
- verb: 'create',
281
- actor: {
282
- id: spock.id,
283
- objectType: 'person',
284
- },
285
- },
286
- {
287
- verb: 'add',
288
- actor: {
289
- id: spock.id,
290
- objectType: 'person',
291
- },
292
- object: {
293
- id: spock.id,
294
- objectType: 'person',
295
- },
296
- },
297
- {
298
- verb: 'add',
299
- actor: {
300
- id: spock.id,
301
- objectType: 'person',
302
- },
303
- object: {
304
- id: mccoy.id,
305
- objectType: 'person',
306
- },
307
- },
308
- ],
309
- },
310
- tags: ['ONE_ON_ONE'],
311
- },
312
- })
313
- .then((res) => res.body)
314
- .then((c) => {
315
- conversation = c;
316
- assert.notProperty(conversation, 'defaultActivityEncryptionKeyUrl');
317
- assert.include(c.tags, 'ONE_ON_ONE');
318
- })
319
- );
320
-
321
- describe('#post()', () => {
322
- it('posts a message', () =>
323
- webex.internal.conversation
324
- .post(conversation, {displayName: 'First Message'})
325
- .then(() => mccoy.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
326
- .then((c) => {
327
- assert.property(c, 'defaultActivityEncryptionKeyUrl');
328
- assert.equal(c.activities.items[0].object.displayName, 'First Message');
329
- }));
330
- });
331
- });
332
- });
333
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import WebexCore, {WebexHttpError} from '@webex/webex-core';
6
+ import {assert, expect} from '@webex/test-helper-chai';
7
+ import testUsers from '@webex/test-helper-test-users';
8
+
9
+ describe('plugin-conversation', () => {
10
+ let checkov, mccoy, participants, webex, spock;
11
+
12
+ before(() =>
13
+ testUsers.create({count: 3}).then((users) => {
14
+ participants = [spock, mccoy, checkov] = users;
15
+
16
+ webex = new WebexCore({
17
+ credentials: {
18
+ authorization: spock.token,
19
+ },
20
+ });
21
+
22
+ return webex.internal.mercury.connect();
23
+ })
24
+ );
25
+
26
+ after(() => webex && webex.internal.mercury.disconnect());
27
+
28
+ describe('when not supplying enough encryption data', () => {
29
+ let conversation;
30
+
31
+ before(() =>
32
+ webex.internal.conversation.create({participants, comment: 'first'}).then((c) => {
33
+ conversation = c;
34
+ })
35
+ );
36
+
37
+ it('fetches the conversation and does not alter its key', () =>
38
+ webex.internal.conversation
39
+ .post({url: conversation.url}, {displayName: 'second'})
40
+ .then(() => webex.internal.conversation.get(conversation))
41
+ .then((c) =>
42
+ assert.equal(
43
+ c.defaultActivityEncryptionKeyUrl,
44
+ conversation.defaultActivityEncryptionKeyUrl
45
+ )
46
+ ));
47
+ });
48
+
49
+ describe('when interacting with a non-encrypted conversation', () => {
50
+ before(() => {
51
+ mccoy.webex = new WebexCore({
52
+ credentials: {
53
+ authorization: mccoy.token,
54
+ },
55
+ });
56
+
57
+ checkov.webex = new WebexCore({
58
+ credentials: {
59
+ authorization: checkov.token,
60
+ },
61
+ });
62
+
63
+ return Promise.all([
64
+ checkov.webex.internal.mercury.connect(),
65
+ mccoy.webex.internal.mercury.connect(),
66
+ ]);
67
+ });
68
+
69
+ after(() =>
70
+ Promise.all([
71
+ checkov && checkov.webex && checkov.webex.internal.mercury.disconnect(),
72
+ mccoy && mccoy.webex && mccoy.webex.internal.mercury.disconnect(),
73
+ ])
74
+ );
75
+
76
+ let conversation;
77
+
78
+ beforeEach(() =>
79
+ webex
80
+ .request({
81
+ method: 'POST',
82
+ service: 'conversation',
83
+ resource: '/conversations',
84
+ noTransform: true,
85
+ body: {
86
+ objectType: 'conversation',
87
+ activities: {
88
+ items: [
89
+ {
90
+ verb: 'create',
91
+ actor: {
92
+ id: spock.id,
93
+ objectType: 'person',
94
+ },
95
+ },
96
+ {
97
+ verb: 'add',
98
+ actor: {
99
+ id: spock.id,
100
+ objectType: 'person',
101
+ },
102
+ object: {
103
+ id: spock.id,
104
+ objectType: 'person',
105
+ },
106
+ },
107
+ {
108
+ verb: 'add',
109
+ actor: {
110
+ id: spock.id,
111
+ objectType: 'person',
112
+ },
113
+ object: {
114
+ id: checkov.id,
115
+ objectType: 'person',
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ },
121
+ })
122
+ .then((res) => res.body)
123
+ .then((c) => {
124
+ conversation = c;
125
+ assert.notProperty(conversation, 'defaultActivityEncryptionKeyUrl');
126
+ })
127
+ );
128
+
129
+ describe('when the conversation is a grouped conversation', () => {
130
+ describe('#add()', () => {
131
+ it('adds the specified user', () =>
132
+ webex.internal.conversation
133
+ .add(conversation, mccoy)
134
+ .then(() => mccoy.webex.internal.conversation.get(conversation))
135
+ .then((c) =>
136
+ assert.property(
137
+ c,
138
+ 'defaultActivityEncryptionKeyUrl',
139
+ 'The conversation was encrypted as a side effect of the add activity'
140
+ )
141
+ ));
142
+ });
143
+
144
+ describe('#leave()', () => {
145
+ it('removes the current user', () =>
146
+ webex.internal.conversation
147
+ .leave(conversation)
148
+ .then(() => assert.isRejected(webex.internal.conversation.get(conversation)))
149
+ .then((reason) => assert.instanceOf(reason, WebexHttpError.NotFound))
150
+ .then(() => checkov.webex.internal.conversation.get(conversation))
151
+ .then((c) =>
152
+ assert.notProperty(
153
+ c,
154
+ 'defaultActivityEncryptionKeyUrl',
155
+ 'The conversation was not encrypted as a side effect of the leave activity'
156
+ )
157
+ ));
158
+
159
+ it('removes the specified user', () =>
160
+ webex.internal.conversation
161
+ .leave(conversation, checkov)
162
+ .then(() => assert.isRejected(checkov.webex.internal.conversation.get(conversation)))
163
+ .then((reason) => assert.instanceOf(reason, WebexHttpError.NotFound))
164
+ .then(() => webex.internal.conversation.get(conversation))
165
+ .then((c) =>
166
+ assert.notProperty(
167
+ c,
168
+ 'defaultActivityEncryptionKeyUrl',
169
+ 'The conversation was not encrypted as a side effect of the leave activity'
170
+ )
171
+ ));
172
+ });
173
+
174
+ describe('#post()', () => {
175
+ it('posts a message', () =>
176
+ webex.internal.conversation
177
+ .post(conversation, {displayName: 'Ahoy'})
178
+ .then(() => checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
179
+ .then((c) => {
180
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
181
+ assert.equal(c.activities.items[0].object.displayName, 'Ahoy');
182
+ }));
183
+ });
184
+
185
+ describe('#cardAction()', () => {
186
+ it('creates a cardAction Activity', () =>
187
+ webex.internal.conversation
188
+ .post(conversation, {displayName: 'First Message', cards: ['test']})
189
+ .then(() => checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
190
+ .then((c) => {
191
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
192
+ assert.equal(c.activities.items[0].object.displayName, 'First Message');
193
+ webex.internal.conversation
194
+ .cardAction(
195
+ conversation,
196
+ {
197
+ objectType: 'submit',
198
+ inputs: {key: 'value'},
199
+ },
200
+ c.activities.items[0]
201
+ )
202
+ .then(() =>
203
+ checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1})
204
+ )
205
+ .then((ci) => {
206
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
207
+ /* eslint-disable no-unused-expressions */
208
+ expect(ci.activities.items[0].object.inputs).to.not.be.null;
209
+ });
210
+ }));
211
+ });
212
+
213
+ describe('#update()', () => {
214
+ it("sets the conversation's title", () =>
215
+ webex.internal.conversation
216
+ .update(conversation, {
217
+ displayName: 'New Name!',
218
+ objectType: 'conversation',
219
+ })
220
+ .then(() => checkov.webex.internal.conversation.get(conversation))
221
+ .then((c) => {
222
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
223
+ assert.property(c, 'encryptionKeyUrl');
224
+ assert.equal(c.displayName, 'New Name!');
225
+ }));
226
+ });
227
+
228
+ describe('#updateKey()', () => {
229
+ it("sets the conversation's defaultActivityEncryptionKeyUrl", () =>
230
+ webex.internal.conversation
231
+ .updateKey(conversation)
232
+ .then(() => webex.internal.conversation.get(conversation))
233
+ .then((c) => {
234
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
235
+ })
236
+ .then(() => checkov.webex.internal.conversation.get(conversation))
237
+ .then((c) => {
238
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
239
+ }));
240
+
241
+ it("unsets the conversations's defaultActivityEncryptionKeyUrl", () =>
242
+ webex.internal.conversation
243
+ .updateKey(conversation, {uri: null})
244
+ .then(() => checkov.webex.internal.conversation.get(conversation))
245
+ .then((c) => {
246
+ assert.isUndefined(c.defaultActivityEncryptionKeyUrl);
247
+ }));
248
+
249
+ describe('when the KMS key has been unset', () => {
250
+ it('rotates the key and sends the post', () =>
251
+ webex.internal.conversation
252
+ .updateKey(conversation, {uri: null})
253
+ .then(() => webex.internal.conversation.post(conversation, {displayName: 'Ahoy'}))
254
+ .then(() =>
255
+ checkov.webex.internal.conversation.get(conversation, {activitiesLimit: 1})
256
+ )
257
+ .then((c) => {
258
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
259
+ assert.equal(c.activities.items[0].object.displayName, 'Ahoy');
260
+ }));
261
+ });
262
+ });
263
+ });
264
+
265
+ describe('when the conversation is a 1:1 conversation', () => {
266
+ let conversation;
267
+
268
+ beforeEach(() =>
269
+ webex
270
+ .request({
271
+ method: 'POST',
272
+ service: 'conversation',
273
+ resource: '/conversations',
274
+ noTransform: true,
275
+ body: {
276
+ objectType: 'conversation',
277
+ activities: {
278
+ items: [
279
+ {
280
+ verb: 'create',
281
+ actor: {
282
+ id: spock.id,
283
+ objectType: 'person',
284
+ },
285
+ },
286
+ {
287
+ verb: 'add',
288
+ actor: {
289
+ id: spock.id,
290
+ objectType: 'person',
291
+ },
292
+ object: {
293
+ id: spock.id,
294
+ objectType: 'person',
295
+ },
296
+ },
297
+ {
298
+ verb: 'add',
299
+ actor: {
300
+ id: spock.id,
301
+ objectType: 'person',
302
+ },
303
+ object: {
304
+ id: mccoy.id,
305
+ objectType: 'person',
306
+ },
307
+ },
308
+ ],
309
+ },
310
+ tags: ['ONE_ON_ONE'],
311
+ },
312
+ })
313
+ .then((res) => res.body)
314
+ .then((c) => {
315
+ conversation = c;
316
+ assert.notProperty(conversation, 'defaultActivityEncryptionKeyUrl');
317
+ assert.include(c.tags, 'ONE_ON_ONE');
318
+ })
319
+ );
320
+
321
+ describe('#post()', () => {
322
+ it('posts a message', () =>
323
+ webex.internal.conversation
324
+ .post(conversation, {displayName: 'First Message'})
325
+ .then(() => mccoy.webex.internal.conversation.get(conversation, {activitiesLimit: 1}))
326
+ .then((c) => {
327
+ assert.property(c, 'defaultActivityEncryptionKeyUrl');
328
+ assert.equal(c.activities.items[0].object.displayName, 'First Message');
329
+ }));
330
+ });
331
+ });
332
+ });
333
+ });