210 lines
8.3 KiB
JavaScript
210 lines
8.3 KiB
JavaScript
/*! @azure/msal-common v14.4.0 2023-11-07 */
|
|
'use strict';
|
|
import { CacheOutcome, Constants, SERVER_TELEM_CONSTANTS, Separators } from '../../utils/Constants.mjs';
|
|
import { AuthError } from '../../error/AuthError.mjs';
|
|
|
|
/*
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License.
|
|
*/
|
|
/** @internal */
|
|
class ServerTelemetryManager {
|
|
constructor(telemetryRequest, cacheManager) {
|
|
this.cacheOutcome = CacheOutcome.NOT_APPLICABLE;
|
|
this.cacheManager = cacheManager;
|
|
this.apiId = telemetryRequest.apiId;
|
|
this.correlationId = telemetryRequest.correlationId;
|
|
this.wrapperSKU = telemetryRequest.wrapperSKU || Constants.EMPTY_STRING;
|
|
this.wrapperVer = telemetryRequest.wrapperVer || Constants.EMPTY_STRING;
|
|
this.telemetryCacheKey =
|
|
SERVER_TELEM_CONSTANTS.CACHE_KEY +
|
|
Separators.CACHE_KEY_SEPARATOR +
|
|
telemetryRequest.clientId;
|
|
}
|
|
/**
|
|
* API to add MSER Telemetry to request
|
|
*/
|
|
generateCurrentRequestHeaderValue() {
|
|
const request = `${this.apiId}${SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR}${this.cacheOutcome}`;
|
|
const platformFields = [this.wrapperSKU, this.wrapperVer].join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const regionDiscoveryFields = this.getRegionDiscoveryFields();
|
|
const requestWithRegionDiscoveryFields = [
|
|
request,
|
|
regionDiscoveryFields,
|
|
].join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
return [
|
|
SERVER_TELEM_CONSTANTS.SCHEMA_VERSION,
|
|
requestWithRegionDiscoveryFields,
|
|
platformFields,
|
|
].join(SERVER_TELEM_CONSTANTS.CATEGORY_SEPARATOR);
|
|
}
|
|
/**
|
|
* API to add MSER Telemetry for the last failed request
|
|
*/
|
|
generateLastRequestHeaderValue() {
|
|
const lastRequests = this.getLastRequests();
|
|
const maxErrors = ServerTelemetryManager.maxErrorsToSend(lastRequests);
|
|
const failedRequests = lastRequests.failedRequests
|
|
.slice(0, 2 * maxErrors)
|
|
.join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const errors = lastRequests.errors
|
|
.slice(0, maxErrors)
|
|
.join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
const errorCount = lastRequests.errors.length;
|
|
// Indicate whether this header contains all data or partial data
|
|
const overflow = maxErrors < errorCount
|
|
? SERVER_TELEM_CONSTANTS.OVERFLOW_TRUE
|
|
: SERVER_TELEM_CONSTANTS.OVERFLOW_FALSE;
|
|
const platformFields = [errorCount, overflow].join(SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR);
|
|
return [
|
|
SERVER_TELEM_CONSTANTS.SCHEMA_VERSION,
|
|
lastRequests.cacheHits,
|
|
failedRequests,
|
|
errors,
|
|
platformFields,
|
|
].join(SERVER_TELEM_CONSTANTS.CATEGORY_SEPARATOR);
|
|
}
|
|
/**
|
|
* API to cache token failures for MSER data capture
|
|
* @param error
|
|
*/
|
|
cacheFailedRequest(error) {
|
|
const lastRequests = this.getLastRequests();
|
|
if (lastRequests.errors.length >=
|
|
SERVER_TELEM_CONSTANTS.MAX_CACHED_ERRORS) {
|
|
// Remove a cached error to make room, first in first out
|
|
lastRequests.failedRequests.shift(); // apiId
|
|
lastRequests.failedRequests.shift(); // correlationId
|
|
lastRequests.errors.shift();
|
|
}
|
|
lastRequests.failedRequests.push(this.apiId, this.correlationId);
|
|
if (error instanceof Error && !!error && error.toString()) {
|
|
if (error instanceof AuthError) {
|
|
if (error.subError) {
|
|
lastRequests.errors.push(error.subError);
|
|
}
|
|
else if (error.errorCode) {
|
|
lastRequests.errors.push(error.errorCode);
|
|
}
|
|
else {
|
|
lastRequests.errors.push(error.toString());
|
|
}
|
|
}
|
|
else {
|
|
lastRequests.errors.push(error.toString());
|
|
}
|
|
}
|
|
else {
|
|
lastRequests.errors.push(SERVER_TELEM_CONSTANTS.UNKNOWN_ERROR);
|
|
}
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests);
|
|
return;
|
|
}
|
|
/**
|
|
* Update server telemetry cache entry by incrementing cache hit counter
|
|
*/
|
|
incrementCacheHits() {
|
|
const lastRequests = this.getLastRequests();
|
|
lastRequests.cacheHits += 1;
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, lastRequests);
|
|
return lastRequests.cacheHits;
|
|
}
|
|
/**
|
|
* Get the server telemetry entity from cache or initialize a new one
|
|
*/
|
|
getLastRequests() {
|
|
const initialValue = {
|
|
failedRequests: [],
|
|
errors: [],
|
|
cacheHits: 0,
|
|
};
|
|
const lastRequests = this.cacheManager.getServerTelemetry(this.telemetryCacheKey);
|
|
return lastRequests || initialValue;
|
|
}
|
|
/**
|
|
* Remove server telemetry cache entry
|
|
*/
|
|
clearTelemetryCache() {
|
|
const lastRequests = this.getLastRequests();
|
|
const numErrorsFlushed = ServerTelemetryManager.maxErrorsToSend(lastRequests);
|
|
const errorCount = lastRequests.errors.length;
|
|
if (numErrorsFlushed === errorCount) {
|
|
// All errors were sent on last request, clear Telemetry cache
|
|
this.cacheManager.removeItem(this.telemetryCacheKey);
|
|
}
|
|
else {
|
|
// Partial data was flushed to server, construct a new telemetry cache item with errors that were not flushed
|
|
const serverTelemEntity = {
|
|
failedRequests: lastRequests.failedRequests.slice(numErrorsFlushed * 2),
|
|
errors: lastRequests.errors.slice(numErrorsFlushed),
|
|
cacheHits: 0,
|
|
};
|
|
this.cacheManager.setServerTelemetry(this.telemetryCacheKey, serverTelemEntity);
|
|
}
|
|
}
|
|
/**
|
|
* Returns the maximum number of errors that can be flushed to the server in the next network request
|
|
* @param serverTelemetryEntity
|
|
*/
|
|
static maxErrorsToSend(serverTelemetryEntity) {
|
|
let i;
|
|
let maxErrors = 0;
|
|
let dataSize = 0;
|
|
const errorCount = serverTelemetryEntity.errors.length;
|
|
for (i = 0; i < errorCount; i++) {
|
|
// failedRequests parameter contains pairs of apiId and correlationId, multiply index by 2 to preserve pairs
|
|
const apiId = serverTelemetryEntity.failedRequests[2 * i] ||
|
|
Constants.EMPTY_STRING;
|
|
const correlationId = serverTelemetryEntity.failedRequests[2 * i + 1] ||
|
|
Constants.EMPTY_STRING;
|
|
const errorCode = serverTelemetryEntity.errors[i] || Constants.EMPTY_STRING;
|
|
// Count number of characters that would be added to header, each character is 1 byte. Add 3 at the end to account for separators
|
|
dataSize +=
|
|
apiId.toString().length +
|
|
correlationId.toString().length +
|
|
errorCode.length +
|
|
3;
|
|
if (dataSize < SERVER_TELEM_CONSTANTS.MAX_LAST_HEADER_BYTES) {
|
|
// Adding this entry to the header would still keep header size below the limit
|
|
maxErrors += 1;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
return maxErrors;
|
|
}
|
|
/**
|
|
* Get the region discovery fields
|
|
*
|
|
* @returns string
|
|
*/
|
|
getRegionDiscoveryFields() {
|
|
const regionDiscoveryFields = [];
|
|
regionDiscoveryFields.push(this.regionUsed || Constants.EMPTY_STRING);
|
|
regionDiscoveryFields.push(this.regionSource || Constants.EMPTY_STRING);
|
|
regionDiscoveryFields.push(this.regionOutcome || Constants.EMPTY_STRING);
|
|
return regionDiscoveryFields.join(",");
|
|
}
|
|
/**
|
|
* Update the region discovery metadata
|
|
*
|
|
* @param regionDiscoveryMetadata
|
|
* @returns void
|
|
*/
|
|
updateRegionDiscoveryMetadata(regionDiscoveryMetadata) {
|
|
this.regionUsed = regionDiscoveryMetadata.region_used;
|
|
this.regionSource = regionDiscoveryMetadata.region_source;
|
|
this.regionOutcome = regionDiscoveryMetadata.region_outcome;
|
|
}
|
|
/**
|
|
* Set cache outcome
|
|
*/
|
|
setCacheOutcome(cacheOutcome) {
|
|
this.cacheOutcome = cacheOutcome;
|
|
}
|
|
}
|
|
|
|
export { ServerTelemetryManager };
|
|
//# sourceMappingURL=ServerTelemetryManager.mjs.map
|