@azure/monitor-opentelemetry
Version:
Azure Monitor OpenTelemetry (Node.js)
347 lines • 19.1 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserSdkLoader = void 0;
const tslib_1 = require("tslib");
/* eslint-disable no-underscore-dangle*/
const node_http_1 = tslib_1.__importDefault(require("node:http"));
const node_https_1 = tslib_1.__importDefault(require("node:https"));
const applicationinsights_web_snippet_1 = require("@microsoft/applicationinsights-web-snippet");
const browserSdkLoaderHelper = tslib_1.__importStar(require("./browserSdkLoaderHelper.js"));
const prefixHelper = tslib_1.__importStar(require("../utils/common.js"));
const zlib = tslib_1.__importStar(require("zlib"));
const connectionStringParser_js_1 = require("../utils/connectionStringParser.js");
const logger_js_1 = require("../shared/logging/logger.js");
const types_js_1 = require("../types.js");
const api_1 = require("@opentelemetry/api");
class BrowserSdkLoader {
constructor(config) {
var _a, _b;
this._isIkeyValid = true;
this._isInitialized = false;
if (BrowserSdkLoader._instance) {
api_1.diag.warn("Browser SDK Loader should be configured from the applicationInsights object");
}
BrowserSdkLoader._instance = this;
// AI URL used to validate if sdk loader already included
BrowserSdkLoader._aiUrl = types_js_1.BROWSER_SDK_LOADER_DEFAULT_SOURCE;
let clientWebIkey;
if ((_a = config.browserSdkLoaderOptions) === null || _a === void 0 ? void 0 : _a.connectionString) {
clientWebIkey = this._getBrowserSdkLoaderIkey((_b = config === null || config === void 0 ? void 0 : config.browserSdkLoaderOptions) === null || _b === void 0 ? void 0 : _b.connectionString);
}
this._browserSdkLoaderIkey =
clientWebIkey ||
connectionStringParser_js_1.ConnectionStringParser.parse(config.azureMonitorExporterOptions.connectionString)
.instrumentationkey;
if (this._isIkeyValid) {
this._initialize();
}
}
isInitialized() {
return this._isInitialized;
}
static getInstance() {
return BrowserSdkLoader._instance;
}
_getBrowserSdkLoaderIkey(connectionString) {
let iKey = null;
try {
const csCode = connectionStringParser_js_1.ConnectionStringParser.parse(connectionString);
const iKeyCode = csCode.instrumentationkey || "";
if (!connectionStringParser_js_1.ConnectionStringParser.validateInstrumentationKey(iKeyCode)) {
this._isIkeyValid = false;
logger_js_1.Logger.getInstance().info("Invalid browser SDK loader connection string, browser SDK loader is not enabled.");
}
else {
this._isIkeyValid = true;
iKey = iKeyCode;
}
}
catch (err) {
logger_js_1.Logger.getInstance().info(`get browser SDK loader ikey error: ${err}`);
}
return iKey;
}
/**
* Gets string to inject into the web page
* @returns The string to inject into the web page
*/
_getBrowserSdkLoaderReplacedStr() {
const osStr = prefixHelper.getOsPrefix();
const rpStr = prefixHelper.getResourceProvider();
const sdkLoaderReplacedStr = `${this._browserSdkLoaderIkey}",\r\n disableIkeyDeprecationMessage: true,\r\n sdkExtension: "${rpStr}${osStr}d_n_`;
const replacedSdkLoader = applicationinsights_web_snippet_1.webSnippet.replace("INSTRUMENTATION_KEY", sdkLoaderReplacedStr);
return replacedSdkLoader;
}
_initialize() {
this._isInitialized = true;
BrowserSdkLoader._sdkLoader = this._getBrowserSdkLoaderReplacedStr();
const originalHttpServer = node_http_1.default.createServer;
const originalHttpsServer = node_https_1.default.createServer;
node_http_1.default.createServer = (requestListener) => {
const originalRequestListener = requestListener;
if (originalRequestListener) {
requestListener = (request, response) => {
// Patch response write method
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalResponseWrite = response.write;
const isGetRequest = request.method === "GET";
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
response.write = function wrap(a, b) {
var _a, _b;
// only patch GET request
try {
if (isGetRequest) {
const headers = browserSdkLoaderHelper.getContentEncodingFromHeaders(response);
let writeBufferType = undefined;
if (typeof b === "string") {
writeBufferType = b;
}
if (headers === null || headers === undefined) {
if ((_a = BrowserSdkLoader._instance) === null || _a === void 0 ? void 0 : _a.ValidateInjection(response, a)) {
// eslint-disable-next-line prefer-rest-params
arguments[0] = BrowserSdkLoader._instance.InjectSdkLoader(response, a, undefined, writeBufferType);
}
}
else if (headers.length) {
const encodeType = headers[0];
// eslint-disable-next-line prefer-rest-params
arguments[0] = (_b = BrowserSdkLoader._instance) === null || _b === void 0 ? void 0 : _b.InjectSdkLoader(response, a, encodeType);
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().warn(`Inject browser sdk loader error: ${err}`);
}
// eslint-disable-next-line prefer-rest-params
return originalResponseWrite.apply(response, arguments);
};
// Patch response end method for cases when HTML is added there
// eslint-disable-next-line @typescript-eslint/unbound-method
const originalResponseEnd = response.end;
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type, @typescript-eslint/no-redundant-type-constituents
response.end = function wrap(a, b) {
var _a, _b;
if (isGetRequest) {
try {
if (isGetRequest) {
const headers = browserSdkLoaderHelper.getContentEncodingFromHeaders(response);
let endBufferType = undefined;
if (typeof b === "string") {
endBufferType = b;
}
if (headers === null || headers === undefined) {
if ((_a = BrowserSdkLoader._instance) === null || _a === void 0 ? void 0 : _a.ValidateInjection(response, a)) {
// eslint-disable-next-line prefer-rest-params
arguments[0] = BrowserSdkLoader._instance.InjectSdkLoader(response, a, undefined, endBufferType);
}
}
else if (headers.length) {
const encodeType = headers[0];
// eslint-disable-next-line prefer-rest-params
arguments[0] = (_b = BrowserSdkLoader._instance) === null || _b === void 0 ? void 0 : _b.InjectSdkLoader(response, a, encodeType);
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().warn(`Inject browser sdk loader error: ${err}`);
}
}
// eslint-disable-next-line prefer-rest-params
return originalResponseEnd.apply(response, arguments);
};
return originalRequestListener(request, response);
};
}
return originalHttpServer(requestListener);
};
node_https_1.default.createServer = function (options, httpsRequestListener) {
const originalHttpsRequestListener = httpsRequestListener;
if (originalHttpsRequestListener) {
httpsRequestListener = function (req, res) {
const isGetHttpsRequest = req.method === "GET";
const originalHttpsResponseWrite = res.write;
const originalHttpsResponseEnd = res.end;
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type, @typescript-eslint/no-redundant-type-constituents
res.write = function wrap(a, b) {
var _a, _b;
try {
if (isGetHttpsRequest) {
const headers = browserSdkLoaderHelper.getContentEncodingFromHeaders(res);
let writeBufferType = undefined;
if (typeof b === "string") {
writeBufferType = b;
}
if (headers === null || headers === undefined) {
if ((_a = BrowserSdkLoader._instance) === null || _a === void 0 ? void 0 : _a.ValidateInjection(res, a)) {
// eslint-disable-next-line prefer-rest-params
arguments[0] = this.InjectSdkLoader(res, a, undefined, writeBufferType);
}
}
else if (headers.length) {
const encodeType = headers[0];
// eslint-disable-next-line prefer-rest-params
arguments[0] = (_b = BrowserSdkLoader._instance) === null || _b === void 0 ? void 0 : _b.InjectSdkLoader(res, a, encodeType);
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().warn(`Inject SDK loader error: ${err}`);
}
// eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-unsafe-return
return originalHttpsResponseWrite.apply(res, arguments);
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type, @typescript-eslint/no-redundant-type-constituents
res.end = function wrap(a, b) {
var _a, _b;
try {
if (isGetHttpsRequest) {
const headers = browserSdkLoaderHelper.getContentEncodingFromHeaders(res);
let endBufferType = undefined;
if (typeof b === "string") {
endBufferType = b;
}
if (headers === null || headers === undefined) {
if ((_a = BrowserSdkLoader._instance) === null || _a === void 0 ? void 0 : _a.ValidateInjection(res, a)) {
// eslint-disable-next-line prefer-rest-params
arguments[0] = BrowserSdkLoader._instance.InjectSdkLoader(res, a, undefined, endBufferType);
}
}
else if (headers.length) {
const encodeType = headers[0];
// eslint-disable-next-line prefer-rest-params
arguments[0] = (_b = BrowserSdkLoader._instance) === null || _b === void 0 ? void 0 : _b.InjectSdkLoader(res, a, encodeType);
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().warn(`Inject SDK loader error: ${err}`);
}
// eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-unsafe-return
return originalHttpsResponseEnd.apply(res, arguments);
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return originalHttpsRequestListener(req, res);
};
return originalHttpsServer(options, httpsRequestListener);
}
};
}
/**
* Validate response and try to inject Browser SDK Loader
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
ValidateInjection(response, input) {
try {
if (!response || !input || response.statusCode !== 200)
return false;
const isContentHtml = browserSdkLoaderHelper.isContentTypeHeaderHtml(response);
if (!isContentHtml)
return false;
const inputStr = input.slice().toString();
if (inputStr.indexOf("<head>") >= 0 && inputStr.indexOf("</head>") >= 0) {
// Check if sdk loader not already present looking for AI Web SDK URL
if (inputStr.indexOf(BrowserSdkLoader._aiUrl) < 0) {
return true;
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().info(`validate injections error: ${err}`);
}
return false;
}
/**
* Inject Browser SDK Loader
*/
InjectSdkLoader(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
response, input, encodeType, bufferEncodeType) {
try {
const isCompressedBuffer = !!encodeType;
if (!isCompressedBuffer) {
const html = input.toString();
const index = html.indexOf("</head>");
if (index < 0)
return input;
const newHtml = browserSdkLoaderHelper.insertBrowserSdkLoaderByIndex(index, html, BrowserSdkLoader._sdkLoader);
if (typeof input === "string") {
response.removeHeader("Content-Length");
if (newHtml) {
input = newHtml;
}
response.setHeader("Content-Length", Buffer.byteLength(input));
}
else if (Buffer.isBuffer(input)) {
const bufferType = bufferEncodeType ? bufferEncodeType : "utf8";
const isValidBufferType = browserSdkLoaderHelper.isBufferType(input, bufferType);
if (isValidBufferType && newHtml) {
response.removeHeader("Content-Length");
const encodedString = Buffer.from(newHtml).toString(bufferType);
input = Buffer.from(encodedString, bufferType);
response.setHeader("Content-Length", input.length);
}
}
}
else {
response.removeHeader("Content-Length");
input = this._getInjectedCompressBuffer(response, input, encodeType);
response.setHeader("Content-Length", input.length);
}
}
catch (ex) {
logger_js_1.Logger.getInstance().warn(`Failed to inject browser sdk loader and change content-length headers. Exception:${ex}`);
}
return input;
}
//* **********************
// should NOT use sync functions here. But currently cannot get async functions to work
// because reponse.write return boolean
// and also this function do not support partial compression as well
// need more investigation
_getInjectedCompressBuffer(response, input, encodeType) {
try {
switch (encodeType) {
case browserSdkLoaderHelper.contentEncodingMethod.GZIP: {
const gunzipBuffer = zlib.gunzipSync(input);
if (this.ValidateInjection(response, gunzipBuffer)) {
const injectedGunzipBuffer = this.InjectSdkLoader(response, gunzipBuffer);
input = zlib.gzipSync(injectedGunzipBuffer);
}
break;
}
case browserSdkLoaderHelper.contentEncodingMethod.DEFLATE: {
const inflateBuffer = zlib.inflateSync(input);
if (this.ValidateInjection(response, inflateBuffer)) {
const injectedInflateBuffer = this.InjectSdkLoader(response, inflateBuffer);
input = zlib.deflateSync(injectedInflateBuffer);
}
break;
}
case browserSdkLoaderHelper.contentEncodingMethod.BR: {
const BrotliDecompressSync = browserSdkLoaderHelper.getBrotliDecompressSync(zlib);
const BrotliCompressSync = browserSdkLoaderHelper.getBrotliCompressSync(zlib);
if (BrotliDecompressSync && BrotliCompressSync) {
const decompressBuffer = BrotliDecompressSync(input);
if (this.ValidateInjection(response, decompressBuffer)) {
const injectedDecompressBuffer = this.InjectSdkLoader(response, decompressBuffer);
input = BrotliCompressSync(injectedDecompressBuffer);
}
break;
}
}
}
}
catch (err) {
logger_js_1.Logger.getInstance().info(`get browser SDK loader compress buffer error: ${err}`);
}
return input;
}
dispose() {
BrowserSdkLoader._instance = null;
this._isInitialized = false;
}
}
exports.BrowserSdkLoader = BrowserSdkLoader;
//# sourceMappingURL=browserSdkLoader.js.map
;