UNPKG

avo-inspector

Version:

[![npm version](https://badge.fury.io/js/avo-inspector.svg)](https://badge.fury.io/js/avo-inspector)

274 lines (273 loc) 12.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AvoNetworkCallsHandler = exports.AvoNetworkCallsHandlerLite = void 0; // LITE COPY of src/AvoNetworkCallsHandler.ts — Sync: review src/AvoNetworkCallsHandler.ts changes for applicability here var AvoGuid_1 = require("../AvoGuid"); var AvoInspectorLite_1 = require("./AvoInspectorLite"); /** Bodies smaller than this are sent uncompressed — gzip overhead outweighs the gain. */ var gzipMinBodyLength = 1024; /** * Gzips a string body using the browser-native CompressionStream API. * Returns null if compression fails for any reason. */ function gzip(body) { return __awaiter(this, void 0, void 0, function () { var stream, writer, reader, chunks, totalLength, _a, done, value, result_1, offset_1, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: _c.trys.push([0, 4, , 5]); stream = new CompressionStream("gzip"); writer = stream.writable.getWriter(); writer.write(new TextEncoder().encode(body)).catch(function () { }); writer.close().catch(function () { }); reader = stream.readable.getReader(); chunks = []; totalLength = 0; _c.label = 1; case 1: if (!true) return [3 /*break*/, 3]; return [4 /*yield*/, reader.read()]; case 2: _a = _c.sent(), done = _a.done, value = _a.value; if (done) { return [3 /*break*/, 3]; } chunks.push(value); totalLength += value.length; return [3 /*break*/, 1]; case 3: result_1 = new Uint8Array(totalLength); offset_1 = 0; chunks.forEach(function (chunk) { result_1.set(chunk, offset_1); offset_1 += chunk.length; }); return [2 /*return*/, result_1]; case 4: _b = _c.sent(); return [2 /*return*/, null]; case 5: return [2 /*return*/]; } }); }); } var AvoNetworkCallsHandlerLite = /** @class */ (function () { function AvoNetworkCallsHandlerLite(apiKey, envName, appName, appVersion, libVersion) { this.samplingRate = 1.0; this.sending = false; this.apiKey = apiKey; this.envName = envName; this.appName = appName; this.appVersion = appVersion; this.libVersion = libVersion; } AvoNetworkCallsHandlerLite.prototype.callInspectorWithBatchBody = function (inEvents, onCompleted) { var _this = this; if (this.sending) { onCompleted(new Error("Batch sending cancelled because another batch sending is in progress. Your events will be sent with next batch.")); return; } var events = inEvents.filter(function (x) { return x != null; }); this.fixStreamIds(events); if (events.length === 0) { return; } if (this.shouldDropBySampling()) { if (AvoInspectorLite_1.AvoInspector.shouldLog) { console.log("Avo Inspector: last event schema dropped due to sampling rate."); } return; } if (AvoInspectorLite_1.AvoInspector.shouldLog) { console.log("Avo Inspector: events", events); events.forEach(function (event) { if (event.type === "sessionStarted") { console.log("Avo Inspector: sending session started event."); } else if (event.type === "event") { console.log("Avo Inspector: sending event " + event.eventName + " with schema " + JSON.stringify(event.eventProperties)); } }); } this.sending = true; this.callInspectorApi(events, function (error) { _this.sending = false; onCompleted(error); }); }; AvoNetworkCallsHandlerLite.prototype.fixStreamIds = function (_events) { // No-op in lite build: streamId is a dev/staging debugger feature }; AvoNetworkCallsHandlerLite.prototype.bodyForSessionStartedCall = function () { var sessionBody = this.createBaseCallBody(); sessionBody.type = "sessionStarted"; return sessionBody; }; AvoNetworkCallsHandlerLite.prototype.bodyForEventSchemaCall = function (eventName, eventProperties, eventId, eventHash, eventSpecMetadata, validatedBranchId) { var eventSchemaBody = this.createBaseCallBody(); eventSchemaBody.type = "event"; eventSchemaBody.eventName = eventName; eventSchemaBody.eventProperties = eventProperties; if (eventId != null) { eventSchemaBody.avoFunction = true; eventSchemaBody.eventId = eventId; eventSchemaBody.eventHash = eventHash; } else { eventSchemaBody.avoFunction = false; eventSchemaBody.eventId = null; eventSchemaBody.eventHash = null; } // Set metadata on base body if provided if (eventSpecMetadata) { eventSchemaBody.eventSpecMetadata = eventSpecMetadata; } // Set validated branch ID if value validation was performed if (validatedBranchId) { eventSchemaBody.validatedBranchId = validatedBranchId; } return eventSchemaBody; }; AvoNetworkCallsHandlerLite.prototype.createBaseCallBody = function () { var body = { apiKey: this.apiKey, appName: this.appName, appVersion: this.appVersion, libVersion: this.libVersion, env: this.envName, libPlatform: "web", messageId: AvoGuid_1.default.newGuid(), trackingId: "", createdAt: new Date().toISOString(), sessionId: "", streamId: "", samplingRate: this.samplingRate }; return body; }; /** * Calls Inspector API immediately with a single event (bypasses batching). * Used when event spec validation is available. * Note: Does not drop due to sampling - validated events are always sent. */ AvoNetworkCallsHandlerLite.prototype.callInspectorImmediately = function (eventBody, onCompleted) { if (AvoInspectorLite_1.AvoInspector.shouldLog) { console.log("Avo Inspector: calling inspector immediately (with validation)", eventBody.eventName); console.log("Avo Inspector: event body", eventBody); } this.callInspectorApi([eventBody], onCompleted); }; /** * Check if event should be dropped based on sampling rate. */ AvoNetworkCallsHandlerLite.prototype.shouldDropBySampling = function () { return Math.random() > this.samplingRate; }; /** * Core Inspector API call logic shared by batch and immediate calls. * * Bodies large enough to be worth it are gzipped (with Content-Encoding: gzip) * to cut network volume ~10x. When CompressionStream is unavailable or * compression fails, the body is sent uncompressed exactly as before — * synchronously in the unavailable case, so legacy timing is preserved. */ AvoNetworkCallsHandlerLite.prototype.callInspectorApi = function (events, onCompleted) { var _this = this; var body = JSON.stringify(events); if (typeof CompressionStream === "undefined" || body.length < gzipMinBodyLength) { this.sendTrackingRequest(body, false, onCompleted); return; } gzip(body).then(function (compressed) { if (compressed !== null) { _this.sendTrackingRequest(compressed, true, onCompleted); } else { _this.sendTrackingRequest(body, false, onCompleted); } }); }; AvoNetworkCallsHandlerLite.prototype.sendTrackingRequest = function (body, isGzipped, onCompleted) { var _this = this; var xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", AvoNetworkCallsHandlerLite.trackingEndpoint, true); xmlhttp.setRequestHeader("Content-Type", "text/plain"); if (isGzipped) { xmlhttp.setRequestHeader("Content-Encoding", "gzip"); } xmlhttp.timeout = AvoInspectorLite_1.AvoInspector.networkTimeout; xmlhttp.send(body); xmlhttp.onload = function () { if (xmlhttp.status !== 200) { onCompleted(new Error("Error ".concat(xmlhttp.status, ": ").concat(xmlhttp.statusText))); } else { var response = void 0; try { response = JSON.parse(xmlhttp.response); } catch (e) { onCompleted(new Error("Failed to parse response: ".concat(e instanceof Error ? e.message : String(e)))); return; } if (response != null && typeof response.samplingRate === "number" && !isNaN(response.samplingRate)) { _this.samplingRate = response.samplingRate; } onCompleted(null); } }; xmlhttp.onerror = function () { onCompleted(new Error("Request failed")); }; xmlhttp.ontimeout = function () { onCompleted(new Error("Request timed out")); }; }; AvoNetworkCallsHandlerLite.trackingEndpoint = "https://api.avo.app/inspector/v1/track"; return AvoNetworkCallsHandlerLite; }()); exports.AvoNetworkCallsHandlerLite = AvoNetworkCallsHandlerLite; exports.AvoNetworkCallsHandler = AvoNetworkCallsHandlerLite;