UNPKG

@dynatrace/react-native-plugin

Version:

This plugin gives you the ability to use the Dynatrace Mobile agent in your react native application.

197 lines (196 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ConsoleLogger_1 = require("../../core/logging/ConsoleLogger"); const TimestampProvider_1 = require("../provider/TimestampProvider"); const SendEventValidation_1 = require("./modifier/SendEventValidation"); const logger = new ConsoleLogger_1.ConsoleLogger('Dynatrace'); class HttpRequestEventBuilder { constructor(url, requestMethod) { this.url = url; this.requestMethod = requestMethod; this.duration = 0; this.rawEventProperties = {}; this.traceContextHint = "api_unused"; this.hasDroppedCustomProperties = false; this.hasNfnValues = false; this.triedToOverwriteDuration = false; this.requestMethod = requestMethod.toUpperCase(); } withDuration(duration) { this.duration = duration; this.triedToOverwriteDuration = true; return this; } withStatusCode(statusCode) { this.statusCode = statusCode; return this; } withReasonPhrase(reasonPhrase) { this.reasonPhrase = reasonPhrase; return this; } withError(error) { this.error = error; return this; } withBytesSent(bytesSent) { this.bytesSent = bytesSent; return this; } withBytesReceived(bytesReceived) { this.bytesReceived = bytesReceived; return this; } withTraceparentHeader(traceparentHeader) { this.traceparentHeader = traceparentHeader; this.traceContextHint = "api_set"; return this; } addEventProperty(key, value) { this.rawEventProperties[key] = value; return this; } build() { if (!this.hasValidMandatoryAttriutes()) { logger.debug('HttpRequestEventBuilder dropped invalid event'); return null; } this.sanitizeDuration(); this.sanitizeStatusCode(); this.sanitizeReasonPhrase(); this.sanitizeBytesReceived(); this.sanitizeBytesSent(); const parsedTraceparentHeader = this.traceparentHeader && this.parseTraceparent(this.traceparentHeader); if (!!this.traceparentHeader && !parsedTraceparentHeader) { this.traceContextHint = "invalid"; } const hasFailedRequest = this.isStatusCodeError() || !!this.error; const filteredEventProperties = Object.fromEntries(Object.entries(this.rawEventProperties).filter(this.isEventPropertyKey, this)); const sanitizedEventProperties = SendEventValidation_1.SendEventValidation.modifyEvent(filteredEventProperties); return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, sanitizedEventProperties), { ["url.full"]: this.url, ["network.protocol.name"]: 'http', ["http.request.method"]: this.requestMethod, ["request.trace_context_hint"]: this.traceContextHint, ["duration"]: this.duration, ["start_time"]: TimestampProvider_1.defaultTimestampProvider.getCurrentTimestamp() - this.duration }), (this.triedToOverwriteDuration && { ["dt.support.api.overridden_fields"]: [ 'duration', ], })), this.includeIfDefined("http.response.status_code", this.statusCode)), this.includeIfDefined("http.response.reason_phrase", this.reasonPhrase)), this.includeIfDefined("request.bytes_sent", this.bytesSent)), this.includeIfDefined("request.bytes_received", this.bytesReceived)), { ["characteristics.has_request"]: true, ["characteristics.is_api_reported"]: true }), this.includeIfTrue("characteristics.has_failed_request", hasFailedRequest)), this.includeIfTrue("characteristics.has_error", hasFailedRequest)), (this.error !== undefined && Object.assign({ ["characteristics.has_exception"]: true, ["exception.type"]: this.error.name, ["exception.message"]: this.error.message }, this.includeIfDefined("exception.stack_trace", this.error.stack)))), (parsedTraceparentHeader && { ["trace.id"]: parsedTraceparentHeader.traceId, ["span.id"]: parsedTraceparentHeader.spanId, })), this.includeIfTrue("dt.support.api.has_dropped_custom_properties", this.hasDroppedCustomProperties)), this.includeIfTrue("dt.support.has_nfn_values", this.hasNfnValues)); } hasValidMandatoryAttriutes() { let isValid = true; if (this.isInvalidUrl(this.url)) { logger.debug(`HttpRequestEventBuilder: dropped event since given URL is malformed: ${this.url}`); isValid = false; } if (!HttpRequestEventBuilder.allowedRequestMethods.includes(this.requestMethod)) { logger.debug(`HttpRequestEventBuilder: dropped event since given Request Method is invalid: ${this.requestMethod}`); isValid = false; } return isValid; } isInvalidUrl(url) { try { if (!url.match(/^(https?):\/\/[^\s/$.?#-][^\s]*$/i)) { return true; } new URL(url); return false; } catch (_a) { return true; } } sanitizeStatusCode() { if (this.statusCode == undefined && this.error === undefined) { this.statusCode = 0; } if (this.statusCode && this.statusCode < 0) { logger.debug('HttpRequestEventBuilder: overriding invalid Status Code with 0'); this.statusCode = 0; this.hasDroppedCustomProperties = true; } } sanitizeReasonPhrase() { if (this.reasonPhrase && this.reasonPhrase.length > HttpRequestEventBuilder.maxReasonPhraseLength) { logger.debug(`HttpRequestEventBuilder: trimming too long Reason Phrase to a length of ${HttpRequestEventBuilder.maxReasonPhraseLength}`); this.reasonPhrase = this.reasonPhrase.slice(0, HttpRequestEventBuilder.maxReasonPhraseLength); } } sanitizeDuration() { this.hasNfnValues || (this.hasNfnValues = !Number.isFinite(this.duration)); if (!Number.isFinite(this.duration) || this.duration < 0) { logger.debug('HttpRequestEventBuilder: overriding invalid Duration with 0'); this.duration = 0; this.hasDroppedCustomProperties = true; } } sanitizeBytesSent() { if (this.bytesSent && this.bytesSent < 0) { logger.debug(`HttpRequestEventBuilder: dropping invalid value for Bytes Sent: ${this.bytesSent}`); this.bytesSent = undefined; this.hasDroppedCustomProperties = true; } } sanitizeBytesReceived() { if (this.bytesReceived && this.bytesReceived < 0) { logger.debug(`HttpRequestEventBuilder: dropping invalid value for Bytes Received: ${this.bytesReceived}`); this.bytesReceived = undefined; this.hasDroppedCustomProperties = true; } } isStatusCodeError() { return (this.statusCode && 400 <= this.statusCode && this.statusCode <= 599); } isEventPropertyKey([key, _]) { if (key.startsWith("event_properties")) { return true; } logger.debug(`HttpRequestEventBuilder: dropped event property '$key' as it did not start with prefix ${"event_properties"}`); this.hasDroppedCustomProperties = true; return false; } includeIfDefined(key, value) { return value !== undefined ? { [key]: value } : {}; } includeIfTrue(key, value) { return value === true ? { [key]: value } : {}; } parseTraceparent(header) { const traceparentRegex = /^00-([0-9a-f]{32})-([0-9a-f]{16})-0[01]$/; const match = header.match(traceparentRegex); if (!match) { logger.debug("The provided traceparent header does not match the format: '00-<trace-id-32-HEXDIG>-<parent-id-16-HEXDIG>-<trace-flags-2-HEXDIG>'"); return null; } const [, traceId, spanId] = match; if (this.allZeros(traceId)) { logger.debug('Trace ID in traceparent header must not be all zeros'); return null; } if (this.allZeros(spanId)) { logger.debug('Parent ID in traceparent header must not be all zeros'); return null; } return { traceId, spanId }; } allZeros(str) { return /^0*$/.test(str); } } exports.default = HttpRequestEventBuilder; HttpRequestEventBuilder.allowedRequestMethods = [ 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', ]; HttpRequestEventBuilder.maxReasonPhraseLength = 5000;