UNPKG

react-native-avo-inspector

Version:

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

182 lines (181 loc) 6.9 kB
import AvoGuid from "./AvoGuid"; import { AvoInspector } from "./AvoInspector"; import { AvoStreamId } from "./AvoStreamId"; export class AvoNetworkCallsHandler { constructor(apiKey, envName, appName, appVersion, libVersion, publicEncryptionKey) { this.samplingRate = 1.0; this.sending = false; this.apiKey = apiKey; this.envName = envName; this.appName = appName; this.appVersion = appVersion; this.libVersion = libVersion; this.publicEncryptionKey = publicEncryptionKey; } callInspectorWithBatchBody(inEvents, onCompleted) { 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; } const events = inEvents.filter((x) => x != null); this.fixStreamIds(events); if (events.length === 0) { return; } if (this.shouldDropBySampling()) { if (AvoInspector.shouldLog) { console.log("Avo Inspector: last event schema dropped due to sampling rate."); } return; } if (AvoInspector.shouldLog) { console.log("Avo Inspector: events", events); events.forEach((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, (error) => { this.sending = false; onCompleted(error); }); } fixStreamIds(events) { let knownStreamId = null; events.forEach(function (event) { if (event.streamId !== null && event.streamId !== undefined && event.streamId !== "unknown") { knownStreamId = event.streamId; } }); events.forEach(function (event) { if (event.streamId === "unknown") { if (knownStreamId != null) { event.streamId = knownStreamId; } else { event.streamId = AvoStreamId.streamId; } } }); } bodyForSessionStartedCall() { const sessionBody = this.createBaseCallBody(); sessionBody.type = "sessionStarted"; return sessionBody; } bodyForEventSchemaCall(eventName, eventProperties, eventId, eventHash, eventSpecMetadata, validatedBranchId) { const 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; } createBaseCallBody() { const body = { apiKey: this.apiKey, appName: this.appName, appVersion: this.appVersion, libVersion: this.libVersion, env: this.envName, libPlatform: "web", messageId: AvoGuid.newGuid(), trackingId: "", createdAt: new Date().toISOString(), sessionId: "", streamId: AvoStreamId.streamId, samplingRate: this.samplingRate }; if (this.publicEncryptionKey) { body.publicEncryptionKey = this.publicEncryptionKey; } 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. */ callInspectorImmediately(eventBody, onCompleted) { // Fix stream ID if needed if (eventBody.streamId === "unknown") { eventBody.streamId = AvoStreamId.streamId; } if (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. */ shouldDropBySampling() { return Math.random() > this.samplingRate; } /** * Core Inspector API call logic shared by batch and immediate calls. */ callInspectorApi(events, onCompleted) { const xmlhttp = new XMLHttpRequest(); xmlhttp.open("POST", AvoNetworkCallsHandler.trackingEndpoint, true); xmlhttp.setRequestHeader("Content-Type", "text/plain"); xmlhttp.timeout = AvoInspector.networkTimeout; xmlhttp.send(JSON.stringify(events)); xmlhttp.onload = () => { if (xmlhttp.status !== 200) { onCompleted(new Error(`Error ${xmlhttp.status}: ${xmlhttp.statusText}`)); } else { let response; try { response = JSON.parse(xmlhttp.response); } catch (e) { onCompleted(new Error(`Failed to parse response: ${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 = () => { onCompleted(new Error("Request failed")); }; xmlhttp.ontimeout = () => { onCompleted(new Error("Request timed out")); }; } } AvoNetworkCallsHandler.trackingEndpoint = "https://api.avo.app/inspector/v1/track";