import type { SegmentClient } from './analytics'; import { Timeline } from './timeline'; import { AliasEventType, EventType, GroupEventType, IdentifyEventType, PluginType, ScreenEventType, SegmentAPISettings, SegmentEvent, TrackEventType, UpdateType, } from './types'; export class Plugin { // default to utility to avoid automatic processing type: PluginType = PluginType.utility; analytics?: SegmentClient = undefined; configure(analytics: SegmentClient) { this.analytics = analytics; } update(_settings: SegmentAPISettings, _type: UpdateType) { // do nothing by default, user can override. } execute( event: SegmentEvent ): Promise | SegmentEvent | undefined { // do nothing. return event; } shutdown() { // do nothing by default, user can override. } } export class EventPlugin extends Plugin { execute( event: SegmentEvent ): Promise | SegmentEvent | undefined { if (event === undefined) { return event; } let result: Promise | SegmentEvent | undefined = event; switch (result.type) { case EventType.IdentifyEvent: result = this.identify(result); break; case EventType.TrackEvent: result = this.track(result); break; case EventType.ScreenEvent: result = this.screen(result); break; case EventType.AliasEvent: result = this.alias(result); break; case EventType.GroupEvent: result = this.group(result); break; } return result; } // Default implementations that forward the event. This gives plugin // implementors the chance to interject on an event. identify( event: IdentifyEventType ): Promise | IdentifyEventType | undefined { return event; } track( event: TrackEventType ): Promise | TrackEventType | undefined { return event; } screen( event: ScreenEventType ): Promise | ScreenEventType | undefined { return event; } alias( event: AliasEventType ): Promise | AliasEventType | undefined { return event; } group( event: GroupEventType ): Promise | GroupEventType | undefined { return event; } flush(): void | Promise { return; } reset(): void | Promise { return; } } export class DestinationPlugin extends EventPlugin { // default to destination type = PluginType.destination; key = ''; timeline = new Timeline(); // eslint-disable-next-line @typescript-eslint/no-explicit-any store: any; private hasSettings() { return this.analytics?.settings.get()?.[this.key] !== undefined; } protected isEnabled(event: SegmentEvent): boolean { let customerDisabled = false; if (event.integrations?.[this.key] === false) { customerDisabled = true; } return this.hasSettings() && !customerDisabled; } /** Adds a new plugin to the currently loaded set. - Parameter plugin: The plugin to be added. - Returns: Returns the name of the supplied plugin. */ add(plugin: Plugin) { const analytics = this.analytics; if (analytics) { plugin.configure(analytics); } if (analytics && plugin instanceof WaitingPlugin) { analytics.pauseEventProcessingForPlugin(plugin); } this.timeline.add(plugin); return plugin; } /** Applies the supplied closure to the currently loaded set of plugins. - Parameter closure: A closure that takes an plugin to be operated on as a parameter. */ apply(closure: (plugin: Plugin) => void) { this.timeline.apply(closure); } configure(analytics: SegmentClient) { this.analytics = analytics; this.apply((plugin) => { plugin.configure(analytics); }); } /** Removes and unloads plugins with a matching name from the system. - Parameter pluginName: An plugin name. */ remove(plugin: Plugin) { this.timeline.remove(plugin); } async execute(event: SegmentEvent): Promise { if (!this.isEnabled(event)) { return; } // Apply before and enrichment plugins const beforeResult = await this.timeline.applyPlugins({ type: PluginType.before, event, }); if (beforeResult === undefined) { return; } const enrichmentResult = await this.timeline.applyPlugins({ type: PluginType.enrichment, event: beforeResult, }); if (enrichmentResult === undefined) { return; } // Now send the event to the destination by executing the normal flow of an EventPlugin await super.execute(enrichmentResult); // apply .after plugins const afterResult = await this.timeline.applyPlugins({ type: PluginType.after, event: enrichmentResult, }); return afterResult; } } export class UtilityPlugin extends EventPlugin {} // For internal platform-specific bits export class PlatformPlugin extends Plugin {} export { PluginType }; /** * WaitingPlugin - A base class for plugins that need to pause event processing * until an asynchronous operation completes. * * When a WaitingPlugin is added to the Analytics client, it automatically pauses * event processing. Events are buffered in memory until the plugin calls resume(). * If resume() is not called within 30 seconds, event processing automatically resumes. * * @example * ```typescript * class IDFAPlugin extends WaitingPlugin { * type = PluginType.enrichment; * * configure(analytics: SegmentClient) { * super.configure(analytics); * // Request IDFA permission * requestTrackingPermission().then((status) => { * if (status === 'authorized') { * // Add IDFA to context * } * this.resume(); // Resume event processing * }); * } * * track(event: SegmentEvent) { * // Enrich event with IDFA if available * return event; * } * } * ``` * * Common use cases: * - Waiting for user permissions (IDFA, location, notifications) * - Initializing native SDKs that provide enrichment data * - Loading remote configuration required for event processing * - Waiting for authentication state before sending events * * @remarks * Multiple WaitingPlugins can be active simultaneously. Event processing * only resumes when ALL waiting plugins have called resume() or timed out. * * WaitingPlugins can be added at any plugin type (before, enrichment, destination). * They can also be added to DestinationPlugins to pause only that destination's * event processing. */ export class WaitingPlugin extends Plugin { constructor() { super(); } /** * Configure the plugin with the Analytics client. * Automatically pauses event processing when called. * Override this method to perform async initialization, then call resume(). * * @param analytics - The Analytics client instance */ configure(analytics: SegmentClient) { super.configure(analytics); } /** * Manually pause event processing. * Generally not needed as adding a WaitingPlugin automatically pauses processing. */ pause() { this.analytics?.pauseEventProcessingForPlugin(this); } /** * Resume event processing for this plugin. * Call this method when your async operation completes. * If all WaitingPlugins have resumed, buffered events will be processed. */ async resume() { await this.analytics?.resumeEventProcessingForPlugin(this); } }