@datadog/mobile-react-native
Version:
A client-side React Native module to interact with Datadog
181 lines (147 loc) • 5.44 kB
text/typescript
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/
import DdSdk from '../../../specs/NativeDdSdk';
import { DefaultTimeProvider } from '../../../utils/time-provider/DefaultTimeProvider';
import type { TimeProvider } from '../../../utils/time-provider/TimeProvider';
import type { DdRum } from '../../DdRum';
import { BABEL_PLUGIN_TELEMETRY } from '../../constants';
import type { RumActionType } from '../../types';
import { ActionSource } from '../../types';
const StateErrors = {
ALREADY_INITIALIZED:
'Interaction Tracking singleton already initialized, please use `getInstance`.'
} as const;
type BabelConfig = {
trackInteractions: boolean;
useAccessibilityLabel: boolean;
};
type TargetObject = {
getContent: (() => string[]) | undefined;
options: { useContent: boolean; useNamePrefix: boolean };
handlerArgs: any[];
componentName: string;
'dd-action-name': string[];
accessibilityLabel: string[];
[key: string]: any;
};
export class DdBabelInteractionTracking {
private static instance: DdBabelInteractionTracking | null = null;
static config: BabelConfig = {
trackInteractions: false,
useAccessibilityLabel: true
};
private timeProvider: TimeProvider = new DefaultTimeProvider();
private telemetrySent: boolean = false;
private ddRum: typeof DdRum | null = null;
isInitialized: boolean = false;
private constructor(ddRum?: typeof DdRum) {
if (DdBabelInteractionTracking.instance) {
throw new Error(StateErrors.ALREADY_INITIALIZED);
}
if (ddRum) {
this.ddRum = ddRum;
}
DdBabelInteractionTracking.instance = this;
}
static getInstance(ddRum?: typeof DdRum) {
if (!DdBabelInteractionTracking.instance) {
DdBabelInteractionTracking.instance = new DdBabelInteractionTracking(
ddRum
);
}
return DdBabelInteractionTracking.instance;
}
static getTelemetryConfig() {
return {
babel_plugin: {
enabled: !!globalThis.__DD_RN_BABEL_PLUGIN_ENABLED__,
track_interactions: !!DdBabelInteractionTracking.config
.trackInteractions
}
};
}
private getTargetName(targetObject: TargetObject) {
const {
getContent,
options,
handlerArgs,
componentName,
'dd-action-name': actionName,
accessibilityLabel,
...attrs
} = targetObject;
const { useAccessibilityLabel } = DdBabelInteractionTracking.config;
const tryContent = () => {
const content = getContent?.();
if (content && content.length > 0) {
return content;
}
return null;
};
const getAccessibilityLabel = () =>
useAccessibilityLabel && accessibilityLabel
? accessibilityLabel
: null;
const index = handlerArgs
? handlerArgs.find(x => typeof x === 'number') || 0
: 0;
// Order: content → actionName → actionNameAttribute → accessibilityLabel
const selectedContent =
tryContent() ||
actionName ||
Object.values(attrs)[0] ||
getAccessibilityLabel();
if (!selectedContent) {
return componentName;
}
// Fail-safe in case the our 'index' value turns out to not be a real index
const output =
index + 1 > selectedContent.length || index < 0
? selectedContent[0]
: selectedContent[index];
return options.useNamePrefix
? `${componentName} ("${output}")`
: output;
}
wrapRumAction(
func: (...args: any[]) => any,
action: RumActionType,
targetObject: TargetObject
): (...args: any[]) => any {
return (...args: any[]) => {
const result = func(...args);
if (!this.telemetrySent) {
DdSdk?.sendTelemetryLog(
BABEL_PLUGIN_TELEMETRY,
DdBabelInteractionTracking.getTelemetryConfig(),
{ onlyOnce: true }
);
this.telemetrySent = true;
}
const targetName = this.getTargetName(targetObject);
const { trackInteractions } = DdBabelInteractionTracking.config;
if (trackInteractions) {
this.ddRum
?.addAction(
action,
targetName,
{ '__dd.action_source': ActionSource.BABEL },
this.timeProvider.now()
)
.catch(e => {
if (e instanceof Error) {
DdSdk?.telemetryError(
e.message,
e.stack || '',
'BabelActionTrack'
);
}
});
}
return result;
};
}
}