UNPKG

@azure/monitor-opentelemetry

Version:
347 lines 19.1 kB
"use strict"; // 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