UNPKG

@segment/analytics-react-native

Version:

The hassle-free way to add Segment analytics to your React-Native app.

154 lines (134 loc) 4.97 kB
import { DestinationPlugin } from '../plugin'; import { PluginType, SegmentAPIIntegration, SegmentAPISettings, SegmentEvent, UpdateType, } from '../types'; import { chunk, createPromise, getURL } from '../util'; import { uploadEvents } from '../api'; import type { SegmentClient } from '../analytics'; import { DestinationMetadataEnrichment } from './DestinationMetadataEnrichment'; import { QueueFlushingPlugin } from './QueueFlushingPlugin'; import { defaultApiHost } from '../constants'; import { checkResponseForErrors, translateHTTPError } from '../errors'; import { defaultConfig } from '../constants'; const MAX_EVENTS_PER_BATCH = 100; const MAX_PAYLOAD_SIZE_IN_KB = 500; export const SEGMENT_DESTINATION_KEY = 'Segment.io'; export class SegmentDestination extends DestinationPlugin { type = PluginType.destination; key = SEGMENT_DESTINATION_KEY; private apiHost?: string; private settingsResolve: () => void; private settingsPromise: Promise<void>; constructor() { super(); // We don't timeout this promise. We strictly need the response from Segment before sending things const { promise, resolve } = createPromise<void>(); this.settingsPromise = promise; this.settingsResolve = resolve; } private sendEvents = async (events: SegmentEvent[]): Promise<void> => { if (events.length === 0) { return Promise.resolve(); } // We're not sending events until Segment has loaded all settings await this.settingsPromise; const config = this.analytics?.getConfig() ?? defaultConfig; const chunkedEvents: SegmentEvent[][] = chunk( events, config.maxBatchSize ?? MAX_EVENTS_PER_BATCH, MAX_PAYLOAD_SIZE_IN_KB ); let sentEvents: SegmentEvent[] = []; let numFailedEvents = 0; await Promise.all( chunkedEvents.map(async (batch: SegmentEvent[]) => { try { const res = await uploadEvents({ writeKey: config.writeKey, url: this.getEndpoint(), events: batch, }); checkResponseForErrors(res); sentEvents = sentEvents.concat(batch); } catch (e) { this.analytics?.reportInternalError(translateHTTPError(e)); this.analytics?.logger.warn(e); numFailedEvents += batch.length; } finally { await this.queuePlugin.dequeue(sentEvents); } }) ); if (sentEvents.length) { if (config.debug === true) { this.analytics?.logger.info(`Sent ${sentEvents.length} events`); } } if (numFailedEvents) { this.analytics?.logger.error(`Failed to send ${numFailedEvents} events.`); } return Promise.resolve(); }; private readonly queuePlugin = new QueueFlushingPlugin(this.sendEvents); private getEndpoint(): string { const config = this.analytics?.getConfig(); const hasProxy = !!(config?.proxy ?? ''); const useSegmentEndpoints = Boolean(config?.useSegmentEndpoints); let baseURL = ''; let endpoint = ''; if (hasProxy) { //baseURL is always config?.proxy if hasProxy baseURL = config?.proxy ?? ''; if (useSegmentEndpoints) { const isProxyEndsWithSlash = baseURL.endsWith('/'); endpoint = isProxyEndsWithSlash ? 'b' : '/b'; } } else { baseURL = this.apiHost ?? defaultApiHost; } try { return getURL(baseURL, endpoint); } catch (error) { console.error('Error in getEndpoint:', `fallback to ${defaultApiHost}`); return defaultApiHost; } } configure(analytics: SegmentClient): void { super.configure(analytics); // If the client has a proxy we don't need to await for settings apiHost, we can send events directly // Important! If new settings are required in the future you probably want to change this! if (analytics.getConfig().proxy !== undefined) { this.settingsResolve(); } // Enrich events with the Destination metadata this.add(new DestinationMetadataEnrichment(SEGMENT_DESTINATION_KEY)); this.add(this.queuePlugin); } // We block sending stuff to segment until we get the settings update(settings: SegmentAPISettings, _type: UpdateType): void { const segmentSettings = settings.integrations[ this.key ] as SegmentAPIIntegration; if ( segmentSettings?.apiHost !== undefined && segmentSettings?.apiHost !== null ) { //assign the api host from segment settings (domain/v1) this.apiHost = `https://${segmentSettings.apiHost}/b`; } this.settingsResolve(); } execute(event: SegmentEvent): Promise<SegmentEvent | undefined> { // Execute the internal timeline here, the queue plugin will pick up the event and add it to the queue automatically const enrichedEvent = super.execute(event); return enrichedEvent; } async flush() { // Wait until the queue is done restoring before flushing return this.queuePlugin.flush(); } }