@segment/analytics-react-native
Version:
The hassle-free way to add Segment analytics to your React-Native app.
137 lines (123 loc) • 3.6 kB
text/typescript
import { PluginType, SegmentEvent, UpdateType } from './types';
import type { DestinationPlugin, Plugin } from './plugin';
import { getAllPlugins } from './util';
import { ErrorType, SegmentError } from './errors';
type TimelinePlugins = {
[key in PluginType]?: Plugin[];
};
const PLUGIN_ORDER = [
PluginType.before,
PluginType.enrichment,
PluginType.destination,
PluginType.after,
];
export class Timeline {
plugins: TimelinePlugins = {
before: [],
enrichment: [],
destination: [],
after: [],
};
add(plugin: Plugin) {
const { type } = plugin;
if (this.plugins[type]) {
this.plugins[type]?.push(plugin);
} else {
this.plugins[type] = [plugin];
}
const settings = plugin.analytics?.settings.get();
let hasInitialSettings = false;
if (settings !== undefined) {
plugin.update({ integrations: settings }, UpdateType.initial);
hasInitialSettings = true;
}
plugin.analytics?.settings.onChange((newSettings) => {
if (newSettings !== undefined) {
plugin.update(
{ integrations: newSettings },
hasInitialSettings ? UpdateType.refresh : UpdateType.initial
);
hasInitialSettings = true;
}
});
}
remove(plugin: Plugin) {
const plugins = this.plugins[plugin.type];
if (plugins) {
const index = plugins.findIndex((f) => f === plugin);
if (index > -1) {
plugins.splice(index, 1);
}
}
}
apply(closure: (plugin: Plugin) => void) {
getAllPlugins(this).forEach((plugin) => closure(plugin));
}
async process(
incomingEvent: SegmentEvent
): Promise<SegmentEvent | undefined> {
let result: SegmentEvent | undefined = incomingEvent;
for (const key of PLUGIN_ORDER) {
const pluginResult: SegmentEvent | undefined = await this.applyPlugins({
type: key,
event: result!,
});
if (key !== PluginType.destination) {
if (result === undefined) {
return;
} else if (
key === PluginType.enrichment &&
pluginResult?.enrichment &&
typeof pluginResult.enrichment === 'function'
) {
result = pluginResult.enrichment(pluginResult);
} else {
result = pluginResult;
}
}
}
return result;
}
async applyPlugins({
type,
event,
}: {
type: PluginType;
event: SegmentEvent;
}): Promise<SegmentEvent | undefined> {
let result: SegmentEvent | undefined = event;
const plugins = this.plugins[type];
if (plugins) {
for (const plugin of plugins) {
if (result) {
try {
const pluginResult = plugin.execute(result);
// Each destination is independent from each other, so we don't roll over changes caused internally in each one of their processing
if (type !== PluginType.destination) {
result = await pluginResult;
if (result === undefined) {
break;
}
} else {
await pluginResult;
}
} catch (error) {
plugin.analytics?.reportInternalError(
new SegmentError(
ErrorType.PluginError,
JSON.stringify(error),
error
)
);
plugin.analytics?.logger.warn(
`Destination ${
(plugin as DestinationPlugin).key
} failed to execute: ${JSON.stringify(error)}`
);
}
}
}
}
return result;
}
}