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)

204 lines (203 loc) 7.49 kB
/** * This file is generated. Internal development changes should be made in the generator * and the file should be re-generated. External contributions are welcome to submit * changes directly to this file, and we'll apply them to the generator internally. */ /** * EventSpecFetcher handles fetching event specifications from the Avo API. * * Endpoint: GET /trackingPlan/eventSpec * Base URL: https://api.avo.app */ export class AvoEventSpecFetcher { constructor(timeout = 2000, shouldLog = false, env, baseUrl = "https://api.avo.app") { this.baseUrl = baseUrl; this.timeout = timeout; this.shouldLog = shouldLog; this.env = env; this.inFlightRequests = new Map(); } /** Generates a unique key for tracking in-flight requests. */ generateRequestKey(params) { return `${params.streamId}:${params.eventName}`; } /** * Fetches an event specification from the API. * * Returns null if: * - The network request fails * - The response has an invalid status code (non-200) * - The response is invalid or malformed * - The request times out * * This method gracefully degrades - failures do not throw errors. * When null is returned, Phase 2 should skip validation for that event. */ async fetch(params) { const requestKey = this.generateRequestKey(params); // Check if there's already an in-flight request for this spec const existingRequest = this.inFlightRequests.get(requestKey); if (existingRequest) { return existingRequest; } // Create and track the new request const requestPromise = this.fetchInternal(params); this.inFlightRequests.set(requestKey, requestPromise); try { const result = await requestPromise; return result; } finally { // Clean up the in-flight request tracking this.inFlightRequests.delete(requestKey); } } /** Internal fetch implementation. */ async fetchInternal(params) { if (!(this.env === "dev" || this.env === "staging")) { return null; } const url = this.buildUrl(params); try { const wireResponse = await this.makeRequest(url); if (!wireResponse) { if (this.shouldLog) { console.warn(`[Avo Inspector] Failed to fetch event spec for: ${params.eventName}`); } return null; } // Basic structure check for wire format if (!this.hasExpectedShape(wireResponse)) { if (this.shouldLog) { console.warn(`[Avo Inspector] Invalid event spec response for: ${params.eventName}`); } return null; } // Parse wire format to internal format const response = AvoEventSpecFetcher.parseEventSpecResponse(wireResponse); return response; } catch (error) { if (this.shouldLog) { console.error(`[Avo Inspector] Error fetching event spec for: ${params.eventName}`, error); } return null; } } /** Builds the complete URL with query parameters. */ buildUrl(params) { const queryParams = new URLSearchParams({ apiKey: params.apiKey, streamId: params.streamId, eventName: params.eventName, }); return `${this.baseUrl}/trackingPlan/eventSpec?${queryParams.toString()}`; } /** * Makes an HTTP GET request using XMLHttpRequest. * Returns the parsed JSON response or null on failure. */ makeRequest(url) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.timeout = this.timeout; xhr.onload = () => { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); resolve(response); } catch (error) { if (this.shouldLog) { console.error("[Avo Inspector] Failed to parse response:", error); } resolve(null); } } else { if (this.shouldLog) { console.warn(`[Avo Inspector] Request failed with status: ${xhr.status}`); } resolve(null); } }; xhr.onerror = () => { if (this.shouldLog) { console.error("[Avo Inspector] Network error occurred"); } resolve(null); }; xhr.ontimeout = () => { if (this.shouldLog) { console.error(`[Avo Inspector] Request timed out after ${this.timeout}ms`); } resolve(null); }; xhr.send(); }); } /** * Basic shape check for wire format - ensures response has the minimum expected structure. * Uses short field names from wire format. */ hasExpectedShape(response) { return (response && typeof response === "object" && Array.isArray(response.events) && response.metadata && typeof response.metadata === "object" && typeof response.metadata.schemaId === "string" && typeof response.metadata.branchId === "string" && typeof response.metadata.latestActionId === "string"); } /** Parses the wire format response into internal format with meaningful field names. */ static parseEventSpecResponse(wire) { return { events: wire.events.map(AvoEventSpecFetcher.parseEventSpecEntry), metadata: wire.metadata, }; } /** Parses a single event spec entry from wire format. */ static parseEventSpecEntry(wire) { const props = {}; for (const entry of Object.entries(wire.p)) { const propName = Reflect.get(entry, "0"); const propWire = Reflect.get(entry, "1"); Reflect.set(props, propName, AvoEventSpecFetcher.parsePropertyConstraints(propWire)); } return { branchId: wire.b, baseEventId: wire.id, variantIds: wire.vids, props: props, }; } /** Parses property constraints from wire format. */ static parsePropertyConstraints(wire) { const result = { type: wire.t, required: wire.r }; if (wire.l) { result.isList = wire.l; } if (wire.p) { result.pinnedValues = wire.p; } if (wire.v) { result.allowedValues = wire.v; } if (wire.rx) { result.regexPatterns = wire.rx; } if (wire.minmax) { result.minMaxRanges = wire.minmax; } if (wire.children) { result.children = {}; for (const [propName, childWire] of Object.entries(wire.children)) { result.children[propName] = AvoEventSpecFetcher.parsePropertyConstraints(childWire); } } return result; } }