applicationinsights
Version:
Microsoft Application Insights module for Node.js
362 lines • 18.8 kB
JavaScript
"use strict";
var http = require("http");
var https = require("https");
var zlib = require("zlib");
var Logging = require("../Library/Logging");
var snippetInjectionHelper = require("../Library/SnippetInjectionHelper");
var prefixHelper = require("../Library/PrefixHelper");
var Constants = require("../Declarations/Constants");
var ConnectionStringParser = require("../Library/ConnectionStringParser");
var applicationinsights_web_snippet_1 = require("@microsoft/applicationinsights-web-snippet");
var WebSnippet = /** @class */ (function () {
function WebSnippet(client) {
var _a;
this._isIkeyValid = true;
if (!!WebSnippet.INSTANCE) {
throw new Error("Web snippet injection should be configured from the applicationInsights object");
}
WebSnippet.INSTANCE = this;
// AI URL used to validate if snippet already included
WebSnippet._aiUrl = Constants.WEB_INSTRUMENTATION_DEFAULT_SOURCE;
WebSnippet._aiDeprecatedUrl = Constants.WEB_INSTRUMENTATION_DEPRECATED_SOURCE;
var clientWebIkey = this._getWebSnippetIkey((_a = client.config) === null || _a === void 0 ? void 0 : _a.webInstrumentationConnectionString);
this._webInstrumentationIkey = clientWebIkey || client.config.instrumentationKey;
this._clientWebInstrumentationConfig = client.config.webInstrumentationConfig;
this._clientWebInstrumentationSrc = client.config.webInstrumentationSrc;
this._statsbeat = client.getStatsbeat();
}
WebSnippet.prototype.enable = function (isEnabled, webInstrumentationConnectionString) {
this._isEnabled = isEnabled;
this._webInstrumentationIkey = this._getWebSnippetIkey(webInstrumentationConnectionString) || this._webInstrumentationIkey;
WebSnippet._snippet = this._getWebInstrumentationReplacedStr();
if (this._isEnabled && !this._isInitialized && this._isIkeyValid) {
if (this._statsbeat) {
this._statsbeat.addFeature(Constants.StatsbeatFeature.WEB_SNIPPET);
}
this._initialize();
}
else if (!this._isEnabled) {
if (this._statsbeat) {
this._statsbeat.removeFeature(Constants.StatsbeatFeature.WEB_SNIPPET);
}
}
};
WebSnippet.prototype.isInitialized = function () {
return this._isInitialized;
};
WebSnippet.prototype._getWebSnippetIkey = function (connectionString) {
var iKey = null;
try {
var csCode = ConnectionStringParser.parse(connectionString);
var iKeyCode = csCode.instrumentationkey || "";
if (!ConnectionStringParser.isIkeyValid(iKeyCode)) {
this._isIkeyValid = false;
Logging.info("Invalid web Instrumentation connection string, web Instrumentation is not enabled.");
}
else {
this._isIkeyValid = true;
iKey = iKeyCode;
}
}
catch (err) {
Logging.info("get web snippet ikey error: " + err);
}
return iKey;
};
WebSnippet.prototype._getWebInstrumentationReplacedStr = function () {
var configStr = this._getClientWebInstrumentationConfigStr(this._clientWebInstrumentationConfig);
var osStr = prefixHelper.getOsPrefix();
var rpStr = prefixHelper.getResourceProvider();
var snippetReplacedStr = this._webInstrumentationIkey + "\",\r\n" + configStr + " disableIkeyDeprecationMessage: true,\r\n sdkExtension: \"" + rpStr + osStr + "d_n_";
var replacedSnippet = applicationinsights_web_snippet_1.webSnippet.replace("INSTRUMENTATION_KEY", snippetReplacedStr);
if (this._clientWebInstrumentationSrc) {
return replacedSnippet.replace(Constants.WEB_INSTRUMENTATION_DEFAULT_SOURCE + ".2.min.js", this._clientWebInstrumentationSrc);
}
return replacedSnippet;
};
// Do not use string replace here, because double quote should be kept.
// we want to transfer all values of config to the web snippet in the following way:
// cfg: {
// config1: "config1 string value",
// config2: true,
// config3: 1,
// ...
//}});
WebSnippet.prototype._getClientWebInstrumentationConfigStr = function (config) {
var configStr = "";
try {
if (config != undefined && config.length > 0) {
config.forEach(function (item) {
var key = item.name;
if (key === undefined)
return;
var val = item.value;
var entry = "";
// NOTE: users should convert object/function to string themselves
// Type "function" and "object" will be skipped!
switch (typeof val) {
case "function":
break;
case "object":
break;
case "string":
entry = " " + key + ": \"" + val + "\",\r\n";
configStr += entry;
break;
default:
entry = " " + key + ": " + val + ",\r\n";
configStr += entry;
break;
}
});
}
}
catch (e) {
// if has any errors here, web Instrumentation will be disabled.
this._isEnabled = false;
Logging.info("Parse client web instrumentation error. Web Instrumentation is disabled");
}
return configStr;
};
WebSnippet.prototype._initialize = function () {
this._isInitialized = true;
var originalHttpServer = http.createServer;
var originalHttpsServer = https.createServer;
var isEnabled = this._isEnabled;
http.createServer = function (requestListener) {
var originalRequestListener = requestListener;
if (originalRequestListener) {
requestListener = function (request, response) {
// Patch response write method
var originalResponseWrite = response.write;
var isGetRequest = request.method == "GET";
response.write = function wrap(a, b, c) {
//only patch GET request
try {
if (isEnabled && isGetRequest) {
var headers = snippetInjectionHelper.getContentEncodingFromHeaders(response);
var writeBufferType = undefined;
if (typeof b === "string") {
writeBufferType = b;
}
if (headers === null || headers === undefined) {
if (WebSnippet.INSTANCE.ValidateInjection(response, a)) {
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(response, a, undefined, writeBufferType);
}
}
else if (headers.length) {
var encodeType = headers[0];
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(response, a, encodeType);
}
}
}
catch (err) {
Logging.warn("Inject snippet error: " + err);
}
return originalResponseWrite.apply(response, arguments);
};
// Patch response end method for cases when HTML is added there
var originalResponseEnd = response.end;
response.end = function wrap(a, b, c) {
if (isEnabled && isGetRequest) {
try {
if (isEnabled && isGetRequest) {
var headers = snippetInjectionHelper.getContentEncodingFromHeaders(response);
var endBufferType = undefined;
if (typeof b === "string") {
endBufferType = b;
}
if (headers === null || headers === undefined) {
if (WebSnippet.INSTANCE.ValidateInjection(response, a)) {
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(response, a, undefined, endBufferType);
}
}
else if (headers.length) {
var encodeType = headers[0];
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(response, a, encodeType);
}
}
}
catch (err) {
Logging.warn("Inject snipet error: " + err);
}
}
return originalResponseEnd.apply(response, arguments);
};
return originalRequestListener(request, response);
};
}
return originalHttpServer(requestListener);
};
https.createServer = function (options, httpsRequestListener) {
var originalHttpsRequestListener = httpsRequestListener;
if (originalHttpsRequestListener) {
httpsRequestListener = function (req, res) {
var isGetHttpsRequest = req.method == "GET";
var originalHttpsResponseWrite = res.write;
var originalHttpsResponseEnd = res.end;
res.write = function wrap(a, b, c) {
try {
if (isEnabled && isGetHttpsRequest) {
var headers = snippetInjectionHelper.getContentEncodingFromHeaders(res);
var writeBufferType = undefined;
if (typeof b === "string") {
writeBufferType = b;
}
if (headers === null || headers === undefined) {
if (WebSnippet.INSTANCE.ValidateInjection(res, a)) {
arguments[0] = this.InjectWebSnippet(res, a, undefined, writeBufferType);
}
}
else if (headers.length) {
var encodeType = headers[0];
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(res, a, encodeType);
}
}
}
catch (err) {
Logging.warn("Inject snippet error: " + err);
}
return originalHttpsResponseWrite.apply(res, arguments);
};
res.end = function wrap(a, b, c) {
try {
if (isEnabled && isGetHttpsRequest) {
var headers = snippetInjectionHelper.getContentEncodingFromHeaders(res);
var endBufferType = undefined;
if (typeof b === "string") {
endBufferType = b;
}
if (headers === null || headers === undefined) {
if (WebSnippet.INSTANCE.ValidateInjection(res, a)) {
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(res, a, undefined, endBufferType);
}
}
else if (headers.length) {
var encodeType = headers[0];
arguments[0] = WebSnippet.INSTANCE.InjectWebSnippet(res, a, encodeType);
}
}
}
catch (err) {
Logging.warn("Inject snippet error: " + err);
}
return originalHttpsResponseEnd.apply(res, arguments);
};
return originalHttpsRequestListener(req, res);
};
return originalHttpsServer(options, httpsRequestListener);
}
};
};
/**
* Validate response and try to inject Web snippet
*/
WebSnippet.prototype.ValidateInjection = function (response, input) {
try {
if (!response || !input || response.statusCode != 200)
return false;
var isContentHtml = snippetInjectionHelper.isContentTypeHeaderHtml(response);
if (!isContentHtml)
return false;
var inputStr = input.slice().toString();
if (inputStr.indexOf("<head>") >= 0 && inputStr.indexOf("</head>") >= 0) {
// Check if snippet not already present looking for AI Web SDK URL
if (inputStr.indexOf(WebSnippet._aiUrl) < 0 && inputStr.indexOf(WebSnippet._aiDeprecatedUrl) < 0) {
return true;
}
}
}
catch (err) {
Logging.info("validate injections error: " + err);
}
return false;
};
/**
* Inject Web snippet
*/
WebSnippet.prototype.InjectWebSnippet = function (response, input, encodeType, bufferEncodeType) {
try {
var isCompressedBuffer = !!encodeType;
if (!isCompressedBuffer) {
var html = input.toString();
var index = html.indexOf("</head>");
if (index < 0)
return input;
var newHtml = snippetInjectionHelper.insertSnippetByIndex(index, html, WebSnippet._snippet);
if (typeof input === "string") {
response.removeHeader("Content-Length");
input = newHtml;
response.setHeader("Content-Length", Buffer.byteLength(input));
}
else if (Buffer.isBuffer(input)) {
var bufferType = bufferEncodeType ? bufferEncodeType : "utf8";
var isValidBufferType = snippetInjectionHelper.isBufferType(input, bufferType);
if (isValidBufferType) {
response.removeHeader("Content-Length");
var 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) {
Logging.warn("Failed to inject web snippet and change content-lenght 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
WebSnippet.prototype._getInjectedCompressBuffer = function (response, input, encodeType) {
try {
switch (encodeType) {
case snippetInjectionHelper.contentEncodingMethod.GZIP:
var gunzipBuffer = zlib.gunzipSync(input);
if (this.ValidateInjection(response, gunzipBuffer)) {
var injectedGunzipBuffer = this.InjectWebSnippet(response, gunzipBuffer);
input = zlib.gzipSync(injectedGunzipBuffer);
}
break;
case snippetInjectionHelper.contentEncodingMethod.DEFLATE:
var inflateBuffer = zlib.inflateSync(input);
if (this.ValidateInjection(response, inflateBuffer)) {
var injectedInflateBuffer = this.InjectWebSnippet(response, inflateBuffer);
input = zlib.deflateSync(injectedInflateBuffer);
}
break;
case snippetInjectionHelper.contentEncodingMethod.BR:
var BrotliDecompressSync = snippetInjectionHelper.getBrotliDecompressSync(zlib);
var BrotliCompressSync = snippetInjectionHelper.getBrotliCompressSync(zlib);
if (BrotliDecompressSync && BrotliCompressSync) {
var decompressBuffer = BrotliDecompressSync(input);
if (this.ValidateInjection(response, decompressBuffer)) {
var injectedDecompressBuffer = this.InjectWebSnippet(response, decompressBuffer);
input = BrotliCompressSync(injectedDecompressBuffer);
}
break;
}
}
}
catch (err) {
Logging.info("get web injection compress buffer error: " + err);
}
return input;
};
WebSnippet.prototype.dispose = function () {
WebSnippet.INSTANCE = null;
this.enable(false);
this._isInitialized = false;
};
return WebSnippet;
}());
module.exports = WebSnippet;
//# sourceMappingURL=WebSnippet.js.map