@fullstory/react-native
Version:
The official FullStory React Native plugin
168 lines (165 loc) • 5.78 kB
JavaScript
// When adding new imports, please verify that they are not causing the metro resolver to fail in earlier versions of react-native.
import { NativeModules, Platform } from 'react-native';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
import { isTurboModuleEnabled, LogLevel } from './fullstoryInterface';
const FullStory = isTurboModuleEnabled ? require('./NativeFullStory').default : NativeModules.FullStory;
if (!FullStory) {
console.warn('FullStory: Native module not found. Falling back to stub implementations.');
}
const {
anonymize = () => null,
identify = () => null,
setUserVars = () => null,
onReady: nativeOnReady = () => Promise.resolve({
replayStartUrl: '',
replayNowUrl: '',
sessionId: ''
}),
getCurrentSession = () => Promise.resolve(''),
getCurrentSessionURL = () => Promise.resolve(''),
consent = () => null,
event = () => null,
shutdown = () => null,
restart = () => null,
log = () => null,
resetIdleTimer = () => null,
onSessionStarted = () => ({
remove: () => null
})
} = FullStory ?? {};
function onReady(listener) {
if (!listener) {
return nativeOnReady();
}
if (!isTurboModuleEnabled) {
console.warn('FullStory: onReady with a listener is only supported on the New Architecture.');
return {
remove: () => null
};
}
// Fire immediately if a session is already active
getCurrentSessionURL().then(url => {
if (url) {
getCurrentSession().then(sessionId => {
listener({
replayStartUrl: url,
replayNowUrl: url,
sessionId
});
});
}
});
return onSessionStarted(listener);
}
const FullStoryPrivate = isTurboModuleEnabled ? require('./NativeFullStoryPrivate').default : NativeModules.FullStoryPrivate;
const identifyWithProperties = (uid, userVars = {}) => identify(uid, userVars);
export { FSPage } from './FSPage';
/*
Batching all property commands into a single native call to reduce the window for race conditions
with React Native's rendering scheduler.
*/
const Commands = codegenNativeCommands({
supportedCommands: ['setBatchProperties']
});
let getInternalInstanceHandleFromPublicInstance;
try {
// This import confuses the metro resolver in earlier versions of react-native.
getInternalInstanceHandleFromPublicInstance = require('react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance').getInternalInstanceHandleFromPublicInstance;
} catch (e) {}
export const FS_REF_SYMBOL = Symbol('fullstory.ref');
// Shared wrapper for components without refs (most common case)
function sharedRefWrapper(element) {
if (element && isTurboModuleEnabled && Platform.OS === 'ios' && !Platform.isTV) {
let currentProps;
if (getInternalInstanceHandleFromPublicInstance) {
var _getInternalInstanceH;
currentProps = (_getInternalInstanceH = getInternalInstanceHandleFromPublicInstance(element)) === null || _getInternalInstanceH === void 0 || (_getInternalInstanceH = _getInternalInstanceH.stateNode) === null || _getInternalInstanceH === void 0 ? void 0 : _getInternalInstanceH.canonical.currentProps;
} else {
currentProps = element.currentProps;
}
if (currentProps) {
const batchedProps = {};
const fsClass = currentProps.fsClass;
if (fsClass && typeof fsClass === 'string') {
batchedProps.fsClass = fsClass;
}
const fsAttribute = currentProps.fsAttribute;
if (fsAttribute && typeof fsAttribute === 'object') {
batchedProps.fsAttribute = fsAttribute;
}
const fsTagName = currentProps.fsTagName;
if (fsTagName && typeof fsTagName === 'string') {
batchedProps.fsTagName = fsTagName;
}
const dataElement = currentProps.dataElement;
if (dataElement && typeof dataElement === 'string') {
batchedProps.dataElement = dataElement;
}
const dataComponent = currentProps.dataComponent;
if (dataComponent && typeof dataComponent === 'string') {
batchedProps.dataComponent = dataComponent;
}
const dataSourceFile = currentProps.dataSourceFile;
if (dataSourceFile && typeof dataSourceFile === 'string') {
batchedProps.dataSourceFile = dataSourceFile;
}
// Send all properties as a single batched command
if (Object.keys(batchedProps).length > 0) {
Commands.setBatchProperties(element, batchedProps);
}
}
}
}
Object.defineProperty(sharedRefWrapper, FS_REF_SYMBOL, {
value: true,
enumerable: false,
writable: false,
configurable: false
});
export function applyFSPropertiesWithRef(existingRef, hasDynamicAttributes = true) {
// Return early if already wrapped
if (existingRef && existingRef[FS_REF_SYMBOL]) {
return existingRef;
}
// Use shared wrapper for null/undefined refs or static attributes
if (!existingRef && !hasDynamicAttributes) {
return sharedRefWrapper;
}
function refWrapper(element) {
sharedRefWrapper(element);
if (existingRef) {
if (typeof existingRef === 'function') {
existingRef(element);
} else {
existingRef.current = element;
}
}
}
Object.defineProperty(refWrapper, FS_REF_SYMBOL, {
value: true,
enumerable: false,
writable: false,
configurable: false
});
return refWrapper;
}
const FullstoryAPI = {
anonymize,
identify: identifyWithProperties,
setUserVars,
onReady,
getCurrentSession,
getCurrentSessionURL,
consent,
event,
shutdown,
restart,
log,
resetIdleTimer,
LogLevel
};
export const PrivateInterface = Platform.OS === 'android' ? {
onFSPressForward: FullStoryPrivate.onFSPressForward
} : {};
export default FullstoryAPI;
//# sourceMappingURL=index.js.map