@webex/plugin-meetings 3.0.0-beta.259 → 3.0.0-beta.260

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.
@@ -1029,3 +1029,5 @@ export declare const IP_VERSION: {
1029
1029
  readonly ipv4_and_ipv6: 1;
1030
1030
  };
1031
1031
  export type IP_VERSION = (typeof IP_VERSION)[keyof typeof IP_VERSION];
1032
+ export declare const MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC = 30;
1033
+ export declare const MEETING_PERMISSION_TOKEN_REFRESH_REASON = "ttl-join";
@@ -964,7 +964,10 @@ export default class Meeting extends StatelessWebexPlugin {
964
964
  joinWithMedia(options?: {
965
965
  joinOptions?: any;
966
966
  mediaOptions?: AddMediaOptions;
967
- }): any;
967
+ }): Promise<{
968
+ join: any;
969
+ media: any;
970
+ }>;
968
971
  /**
969
972
  * Initiates the reconnection of the media in the meeting
970
973
  *
@@ -1029,7 +1032,7 @@ export default class Meeting extends StatelessWebexPlugin {
1029
1032
  * if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
1030
1033
  * Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
1031
1034
  */
1032
- join(options?: any): any;
1035
+ join(options?: any): Promise<any>;
1033
1036
  /**
1034
1037
  * Connects to low latency mercury and reconnects if the address has changed
1035
1038
  * It will also disconnect if called when the meeting has ended
@@ -1547,4 +1550,13 @@ export default class Meeting extends StatelessWebexPlugin {
1547
1550
  * @returns {number} time left in seconds
1548
1551
  */
1549
1552
  getPermissionTokenTimeLeftInSec(): number | undefined;
1553
+ /**
1554
+ * Check if there is enough time left till the permission token expires
1555
+ * If not - refresh the permission token
1556
+ *
1557
+ * @param {number} threshold - time in seconds
1558
+ * @param {string} reason - reason for refreshing the permission token
1559
+ * @returns {Promise<void>}
1560
+ */
1561
+ checkAndRefreshPermissionToken(threshold: number, reason: string): Promise<void>;
1550
1562
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "3.0.0-beta.259",
3
+ "version": "3.0.0-beta.260",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
@@ -32,12 +32,12 @@
32
32
  "build": "yarn run -T tsc --declaration true --declarationDir ./dist/types"
33
33
  },
34
34
  "devDependencies": {
35
- "@webex/plugin-meetings": "3.0.0-beta.259",
36
- "@webex/test-helper-chai": "3.0.0-beta.259",
37
- "@webex/test-helper-mocha": "3.0.0-beta.259",
38
- "@webex/test-helper-mock-webex": "3.0.0-beta.259",
39
- "@webex/test-helper-retry": "3.0.0-beta.259",
40
- "@webex/test-helper-test-users": "3.0.0-beta.259",
35
+ "@webex/plugin-meetings": "3.0.0-beta.260",
36
+ "@webex/test-helper-chai": "3.0.0-beta.260",
37
+ "@webex/test-helper-mocha": "3.0.0-beta.260",
38
+ "@webex/test-helper-mock-webex": "3.0.0-beta.260",
39
+ "@webex/test-helper-retry": "3.0.0-beta.260",
40
+ "@webex/test-helper-test-users": "3.0.0-beta.260",
41
41
  "chai": "^4.3.4",
42
42
  "chai-as-promised": "^7.1.1",
43
43
  "jsdom-global": "3.0.2",
@@ -46,19 +46,19 @@
46
46
  "typescript": "^4.7.4"
47
47
  },
48
48
  "dependencies": {
49
- "@webex/common": "3.0.0-beta.259",
49
+ "@webex/common": "3.0.0-beta.260",
50
50
  "@webex/internal-media-core": "2.0.4",
51
- "@webex/internal-plugin-conversation": "3.0.0-beta.259",
52
- "@webex/internal-plugin-device": "3.0.0-beta.259",
53
- "@webex/internal-plugin-llm": "3.0.0-beta.259",
54
- "@webex/internal-plugin-mercury": "3.0.0-beta.259",
55
- "@webex/internal-plugin-metrics": "3.0.0-beta.259",
56
- "@webex/internal-plugin-support": "3.0.0-beta.259",
57
- "@webex/internal-plugin-user": "3.0.0-beta.259",
58
- "@webex/media-helpers": "3.0.0-beta.259",
59
- "@webex/plugin-people": "3.0.0-beta.259",
60
- "@webex/plugin-rooms": "3.0.0-beta.259",
61
- "@webex/webex-core": "3.0.0-beta.259",
51
+ "@webex/internal-plugin-conversation": "3.0.0-beta.260",
52
+ "@webex/internal-plugin-device": "3.0.0-beta.260",
53
+ "@webex/internal-plugin-llm": "3.0.0-beta.260",
54
+ "@webex/internal-plugin-mercury": "3.0.0-beta.260",
55
+ "@webex/internal-plugin-metrics": "3.0.0-beta.260",
56
+ "@webex/internal-plugin-support": "3.0.0-beta.260",
57
+ "@webex/internal-plugin-user": "3.0.0-beta.260",
58
+ "@webex/media-helpers": "3.0.0-beta.260",
59
+ "@webex/plugin-people": "3.0.0-beta.260",
60
+ "@webex/plugin-rooms": "3.0.0-beta.260",
61
+ "@webex/webex-core": "3.0.0-beta.260",
62
62
  "ampersand-collection": "^2.0.2",
63
63
  "bowser": "^2.11.0",
64
64
  "btoa": "^1.2.1",
package/src/constants.ts CHANGED
@@ -1249,3 +1249,7 @@ export const IP_VERSION = {
1249
1249
  } as const;
1250
1250
 
1251
1251
  export type IP_VERSION = (typeof IP_VERSION)[keyof typeof IP_VERSION];
1252
+
1253
+ // constant for if the permissionToken is about to expire in the next 30 seconds, refresh it
1254
+ export const MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC = 30;
1255
+ export const MEETING_PERMISSION_TOKEN_REFRESH_REASON = 'ttl-join';
@@ -97,6 +97,8 @@ import {
97
97
  SELF_ROLES,
98
98
  INTERPRETATION,
99
99
  SELF_POLICY,
100
+ MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
101
+ MEETING_PERMISSION_TOKEN_REFRESH_REASON,
100
102
  } from '../constants';
101
103
  import BEHAVIORAL_METRICS from '../metrics/constants';
102
104
  import ParameterError from '../common/errors/parameter';
@@ -4437,7 +4439,7 @@ export default class Meeting extends StatelessWebexPlugin {
4437
4439
  * if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
4438
4440
  * Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
4439
4441
  */
4440
- public join(options: any = {}) {
4442
+ public async join(options: any = {}) {
4441
4443
  // @ts-ignore - fix type
4442
4444
  if (!this.webex.meetings.registered) {
4443
4445
  const errorMessage = 'Meeting:index#join --> Device not registered';
@@ -4548,6 +4550,43 @@ export default class Meeting extends StatelessWebexPlugin {
4548
4550
 
4549
4551
  this.isMultistream = !!options.enableMultistream;
4550
4552
 
4553
+ try {
4554
+ // refresh the permission token if its about to expire in 10sec
4555
+ await this.checkAndRefreshPermissionToken(
4556
+ MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
4557
+ MEETING_PERMISSION_TOKEN_REFRESH_REASON
4558
+ );
4559
+ } catch (error) {
4560
+ LoggerProxy.logger.error('Meeting:index#join --> Failed to refresh permission token:', error);
4561
+
4562
+ if (
4563
+ error instanceof CaptchaError ||
4564
+ error instanceof PasswordError ||
4565
+ error instanceof PermissionError
4566
+ ) {
4567
+ this.meetingFiniteStateMachine.fail(error);
4568
+
4569
+ // Upload logs on refreshpermissionToken refresh Failure
4570
+ Trigger.trigger(
4571
+ this,
4572
+ {
4573
+ file: 'meeting/index',
4574
+ function: 'join',
4575
+ },
4576
+ EVENTS.REQUEST_UPLOAD_LOGS,
4577
+ this
4578
+ );
4579
+
4580
+ joinFailed(error);
4581
+
4582
+ this.deferJoin = undefined;
4583
+
4584
+ // if refresh permission token requires captcha, password or permission, we are throwing the errors
4585
+ // and bubble it up to client
4586
+ return Promise.reject(error);
4587
+ }
4588
+ }
4589
+
4551
4590
  return MeetingUtil.joinMeetingOptions(this, options)
4552
4591
  .then((join) => {
4553
4592
  this.meetingFiniteStateMachine.join();
@@ -7401,4 +7440,22 @@ export default class Meeting extends StatelessWebexPlugin {
7401
7440
  // (permissionTokenExp is a epoch timestamp, not a time to live duration)
7402
7441
  return (permissionTokenExpValue - now) / 1000;
7403
7442
  }
7443
+
7444
+ /**
7445
+ * Check if there is enough time left till the permission token expires
7446
+ * If not - refresh the permission token
7447
+ *
7448
+ * @param {number} threshold - time in seconds
7449
+ * @param {string} reason - reason for refreshing the permission token
7450
+ * @returns {Promise<void>}
7451
+ */
7452
+ public checkAndRefreshPermissionToken(threshold: number, reason: string): Promise<void> {
7453
+ const permissionTokenTimeLeft = this.getPermissionTokenTimeLeftInSec();
7454
+
7455
+ if (permissionTokenTimeLeft !== undefined && permissionTokenTimeLeft <= threshold) {
7456
+ return this.refreshPermissionToken(reason);
7457
+ }
7458
+
7459
+ return Promise.resolve();
7460
+ }
7404
7461
  }
@@ -29,6 +29,7 @@ import {
29
29
  DISPLAY_HINTS,
30
30
  SELF_POLICY,
31
31
  IP_VERSION,
32
+ ERROR_DICTIONARY,
32
33
  } from '@webex/plugin-meetings/src/constants';
33
34
  import * as InternalMediaCoreModule from '@webex/internal-media-core';
34
35
  import {
@@ -798,6 +799,7 @@ describe('plugin-meetings', () => {
798
799
  webex.meetings.registered = true;
799
800
  meeting.updateLLMConnection = sinon.stub();
800
801
  });
802
+
801
803
  describe('successful', () => {
802
804
  beforeEach(() => {
803
805
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
@@ -918,7 +920,7 @@ describe('plugin-meetings', () => {
918
920
  });
919
921
  });
920
922
  });
921
- describe('lmm and transcription decoupling', () => {
923
+ describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
922
924
  beforeEach(() => {
923
925
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
924
926
  });
@@ -1021,7 +1023,6 @@ describe('plugin-meetings', () => {
1021
1023
  try {
1022
1024
  await defer.promise;
1023
1025
  } catch (err) {
1024
- console.log(Metrics.sendBehavioralMetric.getCalls())
1025
1026
  assert.deepEqual(Metrics.sendBehavioralMetric.getCalls()[0].args, [
1026
1027
  BEHAVIORAL_METRICS.JOIN_SUCCESS, {correlation_id: meeting.correlationId}
1027
1028
  ])
@@ -1036,6 +1037,76 @@ describe('plugin-meetings', () => {
1036
1037
  }
1037
1038
  })
1038
1039
  })
1040
+
1041
+ describe('refreshPermissionToken', () => {
1042
+ it('should continue if permissionTokenRefresh fails with a generic error', async () => {
1043
+ meeting.checkAndRefreshPermissionToken = sinon.stub().rejects(new Error('bad day'));
1044
+ const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
1045
+
1046
+ try {
1047
+ const result = await meeting.join();
1048
+ assert.notCalled(stateMachineFailSpy);
1049
+ assert.equal(result, joinMeetingResult);
1050
+ assert.calledOnceWithExactly(meeting.checkAndRefreshPermissionToken, 30, 'ttl-join');
1051
+ } catch (error) {
1052
+ assert.fail('join should not throw an Error');
1053
+ }
1054
+ })
1055
+
1056
+ it('should throw if permissionTokenRefresh fails with a captcha error', async () => {
1057
+ meeting.checkAndRefreshPermissionToken = sinon.stub().rejects(new CaptchaError('bad captcha'));
1058
+ const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
1059
+ const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
1060
+
1061
+ try {
1062
+ await meeting.join();
1063
+ assert.fail('join should have thrown a Captcha Error.');
1064
+ } catch (error) {
1065
+ assert.calledOnce(stateMachineFailSpy);
1066
+ assert.calledOnceWithExactly(meeting.checkAndRefreshPermissionToken, 30, 'ttl-join');
1067
+ assert.instanceOf(error, CaptchaError);
1068
+ assert.equal(error.message, 'bad captcha');
1069
+ // should not get to the end promise chain, which does do the join
1070
+ assert.notCalled(joinMeetingOptionsSpy);
1071
+ }
1072
+ })
1073
+
1074
+ it('should throw if permissionTokenRefresh fails with a password error', async () => {
1075
+ meeting.checkAndRefreshPermissionToken = sinon.stub().rejects(new PasswordError('bad password'));
1076
+ const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
1077
+ const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
1078
+
1079
+ try {
1080
+ await meeting.join();
1081
+ assert.fail('join should have thrown a Password Error.');
1082
+ } catch (error) {
1083
+ assert.calledOnce(stateMachineFailSpy);
1084
+ assert.calledOnceWithExactly(meeting.checkAndRefreshPermissionToken, 30, 'ttl-join');
1085
+ assert.instanceOf(error, PasswordError);
1086
+ assert.equal(error.message, 'bad password');
1087
+ // should not get to the end promise chain, which does do the join
1088
+ assert.notCalled(joinMeetingOptionsSpy);
1089
+ }
1090
+ })
1091
+
1092
+ it('should throw if permissionTokenRefresh fails with a permission error', async () => {
1093
+ meeting.checkAndRefreshPermissionToken = sinon.stub().rejects(new PermissionError('bad permission'));
1094
+ const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
1095
+ const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
1096
+
1097
+ try {
1098
+ await meeting.join();
1099
+ assert.fail('join should have thrown a Permission Error.');
1100
+ } catch (error) {
1101
+ assert.calledOnce(stateMachineFailSpy);
1102
+ assert.calledOnceWithExactly(meeting.checkAndRefreshPermissionToken, 30, 'ttl-join');
1103
+ assert.instanceOf(error, PermissionError);
1104
+ assert.equal(error.message, 'bad permission');
1105
+ // should not get to the end promise chain, which does do the join
1106
+ assert.notCalled(joinMeetingOptionsSpy);
1107
+ }
1108
+ })
1109
+ })
1039
1110
  })
1040
1111
  });
1041
1112
 
@@ -8309,4 +8380,50 @@ describe('plugin-meetings', () => {
8309
8380
  assert.equal(meeting.getPermissionTokenTimeLeftInSec(), -1);
8310
8381
  });
8311
8382
  });
8383
+
8384
+ describe('#checkAndRefreshPermissionToken', () => {
8385
+ it('should not fire refreshPermissionToken if permissionToken is not defined', async() => {
8386
+ meeting.getPermissionTokenTimeLeftInSec = sinon.stub().returns(undefined)
8387
+ meeting.refreshPermissionToken = sinon.stub().returns(Promise.resolve('test return value'));
8388
+
8389
+ const returnValue = await meeting.checkAndRefreshPermissionToken(10, 'ttl-join');
8390
+
8391
+ assert.calledOnce(meeting.getPermissionTokenTimeLeftInSec);
8392
+ assert.notCalled(meeting.refreshPermissionToken);
8393
+ assert.equal(returnValue, undefined);
8394
+ });
8395
+
8396
+ it('should fire refreshPermissionToken if time left is below 10sec', async() => {
8397
+ meeting.getPermissionTokenTimeLeftInSec = sinon.stub().returns(9)
8398
+ meeting.refreshPermissionToken = sinon.stub().returns(Promise.resolve('test return value'));
8399
+
8400
+ const returnValue = await meeting.checkAndRefreshPermissionToken(10, 'ttl-join');
8401
+
8402
+ assert.calledOnce(meeting.getPermissionTokenTimeLeftInSec);
8403
+ assert.calledOnceWithExactly(meeting.refreshPermissionToken, 'ttl-join');
8404
+ assert.equal(returnValue, 'test return value');
8405
+ });
8406
+
8407
+ it('should fire refreshPermissionToken if time left is equal 10sec', async () => {
8408
+ meeting.getPermissionTokenTimeLeftInSec = sinon.stub().returns(10)
8409
+ meeting.refreshPermissionToken = sinon.stub().returns(Promise.resolve('test return value'));
8410
+
8411
+ const returnValue = await meeting.checkAndRefreshPermissionToken(10, 'ttl-join');
8412
+
8413
+ assert.calledOnce(meeting.getPermissionTokenTimeLeftInSec);
8414
+ assert.calledOnceWithExactly(meeting.refreshPermissionToken, 'ttl-join');
8415
+ assert.equal(returnValue, 'test return value');
8416
+ });
8417
+
8418
+ it('should not fire refreshPermissionToken if time left is higher than 10sec', async () => {
8419
+ meeting.getPermissionTokenTimeLeftInSec = sinon.stub().returns(11)
8420
+ meeting.refreshPermissionToken = sinon.stub().returns(Promise.resolve('test return value'));
8421
+
8422
+ const returnValue = await meeting.checkAndRefreshPermissionToken(10, 'ttl-join');
8423
+
8424
+ assert.calledOnce(meeting.getPermissionTokenTimeLeftInSec);
8425
+ assert.notCalled(meeting.refreshPermissionToken);
8426
+ assert.equal(returnValue, undefined);
8427
+ });
8428
+ });
8312
8429
  });