react-native-avo-inspector
Version:
[](https://badge.fury.io/js/react-native-avo-inspector)
182 lines (181 loc) • 6.9 kB
JavaScript
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";