/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable comma-dangle */ /* Read Me- https://www.dynatrace.com/support/help/extend-dynatrace/openkit/dynatrace-openkit-api-methods#tabgroup--technologies14--javascript */ import { OpenKitBuilder, Action, Session, CrashReportingLevel, DataCollectionLevel, LogLevel } from "@dynatrace/openkit-js"; import { IdtOpenKit, TracerAction } from "../types/dtOpenKit"; import { deviceInformation } from "./deviceInformation"; import { getMachineId } from "./helperUtillities"; import { isNumeric, isUndefined, isDebugEnabled } from "./utilities"; class dtOpenKit implements IdtOpenKit { session: Session; endUserName: string; isDebugDisabled: boolean; constructor(endUserName: string) { this.isDebugDisabled = !isDebugEnabled(); const deviceInfo = deviceInformation(); const applicationID = process.env.DynatraceAppId as string; const deviceID = getMachineId(); const endpointURL = process.env.DynatraceAppEndpoint as string; const applicationVersion = isUndefined(window.appData.metadata.version) ? "0.0.0.0" : window.appData.metadata.version; try { debugger; const openkit = new OpenKitBuilder(endpointURL, applicationID, deviceID) .withApplicationVersion(applicationVersion) .withOperatingSystem(deviceInfo.description) .withManufacturer("Self Dev") .withScreenResolution(deviceInfo.screen_width, deviceInfo.screen_height) .withScreenOrientation(deviceInfo.orientation) .withLogLevel(LogLevel.Error) // set a data collection level (user allowed you to capture performance and personal data) .withDataCollectionLevel(DataCollectionLevel.UserBehavior) // allow crash reporting (user allowed you to collect information on crashes) .withCrashReportingLevel(CrashReportingLevel.OptInCrashes) .build(); // create a new session this.session = openkit.createSession(); // identify the user this.session.identifyUser(endUserName); this.endUserName = endUserName; } catch (e) { console.error(e); } } async reportError(name: string, code: number) { try { this.session.reportError(name, code); } catch (e) { console.error(e); } } async reportCrash(e: Error) { try { // and now report the application crash via the session this.session.reportCrash(e.name, e.message, (e.stack = "")); } catch (e) { console.error(e); } } async createActions(actionName: string) { try { if (this.isDebugDisabled) { return {}; } return this.session.enterAction(actionName); } catch (e) { console.error(e); return {}; } } async createTracerActions( actionName: string, endpoint: string ): Promise { try { if (this.isDebugDisabled) { return { action: {}, tracer: {}, }; } const action = this.session.enterAction(actionName); // get the tracer const tracer = action.traceWebRequest(endpoint); // start timing for web request tracer.start(); return { action: action, tracer: tracer, }; } catch (e) { console.error(e); return { action: {}, tracer: {}, }; } } async createChildActions(rootAction: any, actionName: string) { try { if (this.isDebugDisabled) { return {}; } return rootAction.enterAction(actionName); } catch (e) { console.error(e); return {}; } } async endChildActions(action: Action) { try { return action.leaveAction(); } catch (e) { console.error(e); return {}; } } async endRootActions(parentAction: Action) { try { if (this.isDebugDisabled) { return; } parentAction.leaveAction(); } catch (e) { console.error(e); } } async reportInfo(action: Action, keyName: string, keyValue: string) { try { if (this.isDebugDisabled) { return; } action.reportValue(keyName, keyValue); } catch (e) { console.error(e); } } async reportEvent(action: Action, eventName: string) { try { if (this.isDebugDisabled) { return; } action.reportEvent(eventName); } catch (e) { console.error(e); } } async startTraceWebRequest(action: Action, endpoint: string) { try { if (this.isDebugDisabled) { return {}; } // get the tracer const tracer = action.traceWebRequest(endpoint); // start timing for web request tracer.start(); return tracer; } catch (e) { console.error(e); return {}; } } async stopTraceWebRequest( tracer: any, response: any, responseStatus: string ) { try { if (this.isDebugDisabled) { return; } if (isNumeric(responseStatus)) { responseStatus = responseStatus == "0000" ? "200" : responseStatus; } tracer .setBytesReceived(await this.approximateResponseBytes(response)) // bytes processed .stop(responseStatus); // stop the tracer } catch (e) { console.error(e); } } async endTracerAction( tracerAction: TracerAction, response: any, responseStatus: string ) { try { if (this.isDebugDisabled) { return; } tracerAction.action.leaveAction(); responseStatus = responseStatus == "0000" ? "200" : responseStatus; tracerAction.tracer .setBytesReceived(await this.approximateResponseBytes(response)) // bytes processed .stop(Number(responseStatus)); // stop the tracer } catch (e) { console.error(e); } } async endSession() { try { this.session.end(); } catch (e) { console.error(e); } } /** * Estimates the number of bytes of the decompressed response. * Note: There can be some restrictions on which headers can be accessed due to CORS, so the calculation might be slightly off. * @param response Fetch response * @returns {Promise} Approximated size of response in bytes */ private async approximateResponseBytes(response: any) { try { let bytesReceived = 0; // #1: HTTP Version + Status code + Status message\r\n bytesReceived += byteLength(`HTTP/X.Y ${response.status} ${response.statusText}`) + 4; try { // Headers assuming the following format: // key: value\r\n response.headers.forEach( (value: any, key: any) => (bytesReceived += byteLength(`${key}: ${value}`) + 4) ); // Empty line separating headers & message bytesReceived += 2; // The message itself const contentLength = response.headers.get("Content-Length"); if (contentLength !== null && contentLength !== undefined) { bytesReceived += contentLength; } else { const responseBuffer = await response.arrayBuffer(); bytesReceived += responseBuffer.byteLength; } } catch { } return bytesReceived; } catch (e) { console.error(e); return 0; } } } const textEncoder = new TextEncoder("utf-8"); const byteLength = (str: string) => textEncoder.encode(str).length; export default dtOpenKit;