@webex/internal-plugin-device 3.11.0 → 3.12.0-next.2
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/dist/config.js +16 -10
- package/dist/config.js.map +1 -1
- package/dist/constants.js +6 -1
- package/dist/constants.js.map +1 -1
- package/dist/device.js +181 -79
- package/dist/device.js.map +1 -1
- package/dist/ipNetworkDetector.js +3 -3
- package/dist/ipNetworkDetector.js.map +1 -1
- package/package.json +10 -10
- package/src/config.js +16 -9
- package/src/constants.js +5 -0
- package/src/device.js +150 -26
- package/src/ipNetworkDetector.ts +1 -1
- package/test/unit/spec/device.js +641 -46
package/src/device.js
CHANGED
|
@@ -6,7 +6,13 @@ import {orderBy} from 'lodash';
|
|
|
6
6
|
import uuid from 'uuid';
|
|
7
7
|
|
|
8
8
|
import METRICS from './metrics';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
FEATURE_COLLECTION_NAMES,
|
|
11
|
+
DEVICE_EVENT_REGISTRATION_SUCCESS,
|
|
12
|
+
MIN_DEVICES_FOR_CLEANUP,
|
|
13
|
+
MAX_DELETION_CONFIRMATION_ATTEMPTS,
|
|
14
|
+
DELETION_CONFIRMATION_DELAY_MS,
|
|
15
|
+
} from './constants';
|
|
10
16
|
import FeaturesModel from './features/features-model';
|
|
11
17
|
import IpNetworkDetector from './ipNetworkDetector';
|
|
12
18
|
import {CatalogDetails} from './types';
|
|
@@ -454,46 +460,117 @@ const Device = WebexPlugin.extend({
|
|
|
454
460
|
});
|
|
455
461
|
},
|
|
456
462
|
/**
|
|
457
|
-
* Fetches
|
|
458
|
-
* @returns {Promise<
|
|
463
|
+
* Fetches devices matching the current device type.
|
|
464
|
+
* @returns {Promise<Array>} filtered device list
|
|
459
465
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
466
|
+
_getDevicesOfCurrentType() {
|
|
467
|
+
const {deviceType} = this._getBody();
|
|
468
|
+
|
|
462
469
|
return this.request({
|
|
463
470
|
method: 'GET',
|
|
464
471
|
service: 'wdm',
|
|
465
472
|
resource: 'devices',
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
}).then((response) => response.body.devices.filter((item) => item.deviceType === deviceType));
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Waits until the server-side device count drops to or below targetCount,
|
|
478
|
+
* polling up to maxAttempts times with a delay between each check.
|
|
479
|
+
* @param {number} targetCount - resolve when device count drops to this value or below
|
|
480
|
+
* @param {number} [attempt=0]
|
|
481
|
+
* @returns {Promise<void>}
|
|
482
|
+
*/
|
|
483
|
+
_waitForDeviceCountBelowLimit(targetCount, attempt = 0) {
|
|
484
|
+
if (attempt >= MAX_DELETION_CONFIRMATION_ATTEMPTS) {
|
|
485
|
+
this.logger.warn('device: max confirmation attempts reached, proceeding anyway');
|
|
486
|
+
|
|
487
|
+
return Promise.resolve();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return new Promise((resolve) => setTimeout(resolve, DELETION_CONFIRMATION_DELAY_MS))
|
|
491
|
+
.then(() => this._getDevicesOfCurrentType())
|
|
492
|
+
.then((devices) => {
|
|
493
|
+
this.logger.info(
|
|
494
|
+
`device: confirmation check ${attempt + 1}/${MAX_DELETION_CONFIRMATION_ATTEMPTS}, ` +
|
|
495
|
+
`${devices.length} devices remaining (target: ≤ ${targetCount})`
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
if (devices.length <= targetCount) {
|
|
499
|
+
this.logger.info('device: device count is now safely below limit');
|
|
469
500
|
|
|
470
|
-
|
|
501
|
+
return Promise.resolve();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return this._waitForDeviceCountBelowLimit(targetCount, attempt + 1);
|
|
505
|
+
})
|
|
506
|
+
.catch((error) => {
|
|
507
|
+
this.logger.warn(
|
|
508
|
+
`device: confirmation check ${attempt + 1} failed, proceeding anyway:`,
|
|
509
|
+
error
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
return Promise.resolve();
|
|
513
|
+
});
|
|
514
|
+
},
|
|
471
515
|
|
|
472
|
-
|
|
473
|
-
|
|
516
|
+
/**
|
|
517
|
+
* Fetches the web devices and deletes the oldest third, then waits
|
|
518
|
+
* for the server to confirm the count is below the limit.
|
|
519
|
+
* @returns {Promise<void>}
|
|
520
|
+
*/
|
|
521
|
+
deleteDevices() {
|
|
522
|
+
let targetCount;
|
|
474
523
|
|
|
524
|
+
return this._getDevicesOfCurrentType()
|
|
525
|
+
.then((webDevices) => {
|
|
475
526
|
const sortedDevices = orderBy(webDevices, [(item) => new Date(item.modificationTime)]);
|
|
476
527
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const countToDelete = Math.ceil(totalItems / 3);
|
|
481
|
-
const urlsToDelete = sortedDevices.slice(0, countToDelete).map((item) => item.url);
|
|
482
|
-
|
|
483
|
-
return Promise.race(
|
|
484
|
-
urlsToDelete.map((url) => {
|
|
485
|
-
return this.request({
|
|
486
|
-
uri: url,
|
|
487
|
-
method: 'DELETE',
|
|
488
|
-
});
|
|
489
|
-
})
|
|
528
|
+
if (sortedDevices.length <= MIN_DEVICES_FOR_CLEANUP) {
|
|
529
|
+
this.logger.info(
|
|
530
|
+
`device: only ${sortedDevices.length} devices found (minimum ${MIN_DEVICES_FOR_CLEANUP}), skipping cleanup`
|
|
490
531
|
);
|
|
532
|
+
|
|
533
|
+
return Promise.resolve();
|
|
491
534
|
}
|
|
492
535
|
|
|
493
|
-
|
|
536
|
+
const devicesToDelete = sortedDevices.slice(0, Math.ceil(sortedDevices.length / 3));
|
|
537
|
+
targetCount = sortedDevices.length - Math.min(5, devicesToDelete.length);
|
|
538
|
+
|
|
539
|
+
this.logger.info(
|
|
540
|
+
`device: deleting ${devicesToDelete.length} of ${webDevices.length} devices`
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
return Promise.all(
|
|
544
|
+
devicesToDelete.map((device) =>
|
|
545
|
+
this.request({uri: device.url, method: 'DELETE'})
|
|
546
|
+
.then(() => ({status: 'fulfilled'}))
|
|
547
|
+
.catch((reason) => ({status: 'rejected', reason}))
|
|
548
|
+
)
|
|
549
|
+
).then((results) => {
|
|
550
|
+
const failed = results.filter((r) => r.status === 'rejected');
|
|
551
|
+
|
|
552
|
+
if (failed.length > 0) {
|
|
553
|
+
this.logger.warn(
|
|
554
|
+
`device: ${failed.length} of ${devicesToDelete.length} deletions failed (best-effort, continuing)`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
this.logger.info(
|
|
558
|
+
`device: deleted ${devicesToDelete.length - failed.length} of ${
|
|
559
|
+
devicesToDelete.length
|
|
560
|
+
} devices`
|
|
561
|
+
);
|
|
562
|
+
});
|
|
563
|
+
})
|
|
564
|
+
.then(() =>
|
|
565
|
+
targetCount !== undefined
|
|
566
|
+
? this._waitForDeviceCountBelowLimit(targetCount, 0)
|
|
567
|
+
: Promise.resolve()
|
|
568
|
+
)
|
|
569
|
+
.then(() => {
|
|
570
|
+
this.logger.info('device: device count confirmed below limit, cleanup successful');
|
|
494
571
|
})
|
|
495
572
|
.catch((error) => {
|
|
496
|
-
this.logger.error('
|
|
573
|
+
this.logger.error('device: failed to delete devices:', error);
|
|
497
574
|
|
|
498
575
|
return Promise.reject(error);
|
|
499
576
|
});
|
|
@@ -519,7 +596,11 @@ const Device = WebexPlugin.extend({
|
|
|
519
596
|
|
|
520
597
|
return this._registerInternal(deviceRegistrationOptions).catch((error) => {
|
|
521
598
|
if (error?.body?.message === 'User has excessive device registrations') {
|
|
599
|
+
this.logger.info('device: excessive device registrations detected, initiating cleanup');
|
|
600
|
+
|
|
522
601
|
return this.deleteDevices().then(() => {
|
|
602
|
+
this.logger.info('device: device cleanup complete, retrying registration');
|
|
603
|
+
|
|
523
604
|
return this._registerInternal(deviceRegistrationOptions);
|
|
524
605
|
});
|
|
525
606
|
}
|
|
@@ -786,6 +867,34 @@ const Device = WebexPlugin.extend({
|
|
|
786
867
|
return Promise.reject(new Error('device: failed to get the current websocket url'));
|
|
787
868
|
},
|
|
788
869
|
|
|
870
|
+
/**
|
|
871
|
+
* Get sanitized processed debug features from session storage
|
|
872
|
+
* these should be JSON encoded and in the form {feature1: true, feature2: false}
|
|
873
|
+
*
|
|
874
|
+
* @returns {Array<Object>} - Array of sanitized debug feature toggles
|
|
875
|
+
*/
|
|
876
|
+
getDebugFeatures() {
|
|
877
|
+
const sanitizedDebugFeatures = [];
|
|
878
|
+
if (this.config.debugFeatureTogglesKey) {
|
|
879
|
+
const debugFeaturesString = this.webex
|
|
880
|
+
.getWindow()
|
|
881
|
+
.sessionStorage.getItem(this.config.debugFeatureTogglesKey);
|
|
882
|
+
if (debugFeaturesString) {
|
|
883
|
+
const debugFeatures = JSON.parse(debugFeaturesString);
|
|
884
|
+
Object.entries(debugFeatures).forEach(([key, value]) => {
|
|
885
|
+
sanitizedDebugFeatures.push({
|
|
886
|
+
key,
|
|
887
|
+
val: value ? 'true' : 'false',
|
|
888
|
+
mutable: true,
|
|
889
|
+
lastModified: new Date().toISOString(),
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return sanitizedDebugFeatures;
|
|
896
|
+
},
|
|
897
|
+
|
|
789
898
|
/**
|
|
790
899
|
* Process a successful device registration.
|
|
791
900
|
*
|
|
@@ -814,6 +923,14 @@ const Device = WebexPlugin.extend({
|
|
|
814
923
|
// When using the etag feature cache, user and entitlement features are still returned
|
|
815
924
|
this.features.user.reset(features.user);
|
|
816
925
|
this.features.entitlement.reset(features.entitlement);
|
|
926
|
+
} else if (this.config.debugFeatureTogglesKey && body?.features?.developer) {
|
|
927
|
+
// Add the debug feature toggles from session storage if available
|
|
928
|
+
try {
|
|
929
|
+
const debugFeatures = this.getDebugFeatures();
|
|
930
|
+
body.features.developer.push(...debugFeatures);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
this.logger.error('Failed to parse debug feature toggles from session storage:', error);
|
|
933
|
+
}
|
|
817
934
|
}
|
|
818
935
|
|
|
819
936
|
// Assign the recieved DTO from **WDM** to this device.
|
|
@@ -946,6 +1063,13 @@ const Device = WebexPlugin.extend({
|
|
|
946
1063
|
// Prototype the extended class in order to preserve the parent member.
|
|
947
1064
|
Reflect.apply(WebexPlugin.prototype.initialize, this, args);
|
|
948
1065
|
|
|
1066
|
+
this.listenToOnce(this.webex, 'change:config', () => {
|
|
1067
|
+
// If debug feature toggles exist, clear the etag to ensure developer feature toggles are fetched
|
|
1068
|
+
if (this.getDebugFeatures(this.config.debugFeatureTogglesKey).length > 0) {
|
|
1069
|
+
this.set('etag', undefined);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
|
|
949
1073
|
// Initialize feature events and listeners.
|
|
950
1074
|
FEATURE_COLLECTION_NAMES.forEach((collectionName) => {
|
|
951
1075
|
this.features.on(`change:${collectionName}`, (model, value, options) => {
|