From 93ff19f5b3a519a464bf5960c72f510aa22781e4 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 18 Apr 2024 14:39:12 +0200 Subject: [PATCH 1/5] Fullstory integration. - React-Native Android - React-Native IOS - Web Functionality included: - Session recording - Essential Identity resolution Functionality added: - FSPage - Mobile Pages API Platforms affected: - Android - IOs Typescript types definitions added. Unit test fixed, fixed eslint errors. Signed-off-by: Oleksii Vagin --- android/app/build.gradle | 10 ++- android/build.gradle | 6 +- babel.config.js | 17 +++++ ios/NewExpensify.xcodeproj/project.pbxproj | 4 + ios/NewExpensify/Info.plist | 5 ++ ios/Podfile.lock | 28 +++++++ jest/setup.ts | 2 + jest/setupMockFullstoryLib.ts | 18 +++++ package-lock.json | 48 ++++++++++++ package.json | 2 + src/libs/Navigation/NavigationRoot.tsx | 3 + src/libs/actions/Session/index.ts | 6 ++ src/libs/fullstory/index.native.ts | 60 +++++++++++++++ src/libs/fullstory/index.ts | 89 ++++++++++++++++++++++ src/libs/fullstory/types.ts | 10 +++ 15 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 jest/setupMockFullstoryLib.ts create mode 100644 src/libs/fullstory/index.native.ts create mode 100644 src/libs/fullstory/index.ts create mode 100644 src/libs/fullstory/types.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 0db4b032ec9d..9c07d9d0a500 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -2,12 +2,20 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" apply plugin: "com.google.firebase.firebase-perf" +apply plugin: "fullstory" apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. */ + +/* Fullstory settings */ +fullstory { + org 'o-1WN56P-na1' + enabledVariants 'all' +} + react { /* Folders */ // The root of your project, i.e. where "package.json" lives. Default is '..' @@ -162,7 +170,7 @@ android { signingConfig null // buildTypes take precedence over productFlavors when it comes to the signing configuration, // thus we need to manually set the signing config, so that the e2e uses the debug config again. - // In other words, the signingConfig setting above will be ignored when we build the flavor in release mode. + // In other words, the signingConfig setting above will be ignored when we build the flavor in release mode. productFlavors.all { flavor -> // All release builds should be signed with the release config ... flavor.signingConfig signingConfigs.release diff --git a/android/build.gradle b/android/build.gradle index 10600480d8bb..7ecd482b38f0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -20,6 +20,7 @@ buildscript { repositories { google() mavenCentral() + maven {url "https://maven.fullstory.com"} } dependencies { classpath("com.android.tools.build:gradle") @@ -27,6 +28,9 @@ buildscript { classpath("com.google.gms:google-services:4.3.4") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.firebase:perf-plugin:1.4.1") + // Fullstory integration + classpath ("com.fullstory:gradle-plugin-local:1.45.1") + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") @@ -70,7 +74,7 @@ allprojects { // 'mapbox' is the fixed username for Mapbox's Maven repository. username = 'mapbox' - // The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property. + // The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property. // Run "npm run setup-mapbox-sdk" to set this property in «USER_HOME»/.gradle/gradle.properties // Example gradle.properties entry: diff --git a/babel.config.js b/babel.config.js index 9f8b7a711d78..0660cdb452fb 100644 --- a/babel.config.js +++ b/babel.config.js @@ -14,6 +14,23 @@ const defaultPlugins = [ // source code transformation as we do not use class property assignment. 'transform-class-properties', + /* Fullstory */ + [ + '@fullstory/react-native', + { + version: '1.4.0', + org: 'o-1WN56P-na1', + enabledVariants: 'all', + }, + ], + [ + '@fullstory/babel-plugin-annotate-react', + { + native: true, + setFSTagName: true, + }, + ], + // Keep it last 'react-native-reanimated/plugin', ]; diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 7f50db5da85a..e7ce320f65d6 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -681,6 +681,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework", "${BUILT_PRODUCTS_DIR}/Turf/Turf.framework", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/FullStory/FullStory.framework/FullStory", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents", @@ -692,6 +693,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FullStory.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", @@ -735,6 +737,7 @@ "${PODS_ROOT}/Target Support Files/Pods-NewExpensify/Pods-NewExpensify-frameworks.sh", "${BUILT_PRODUCTS_DIR}/MapboxMaps/MapboxMaps.framework", "${BUILT_PRODUCTS_DIR}/Turf/Turf.framework", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/FullStory/FullStory.framework/FullStory", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCommon/MapboxCommon.framework/MapboxCommon", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxCoreMaps/MapboxCoreMaps.framework/MapboxCoreMaps", "${PODS_XCFRAMEWORKS_BUILD_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework/MapboxMobileEvents", @@ -746,6 +749,7 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMaps.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Turf.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FullStory.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCommon.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxCoreMaps.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index ddcc64604581..8f7fa5605164 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -104,6 +104,11 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + FullStory + + OrgId + o-1WN56P-na1 + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f564bfd931e4..404fdfd3ffaf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -138,6 +138,27 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (6.2.1) + - FullStory (1.43.1) + - fullstory_react-native (1.4.2): + - FullStory (~> 1.14) + - glog + - hermes-engine + - RCT-Folly (= 2022.05.16.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - glog (0.3.5) - GoogleAppMeasurement (8.8.0): - GoogleAppMeasurement/AdIdSupport (= 8.8.0) @@ -2196,6 +2217,7 @@ SPEC REPOS: - FirebasePerformance - FirebaseRemoteConfig - fmt + - FullStory - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -2244,6 +2266,10 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-modules-core" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" + fullstory_react-native: + :path: "../node_modules/@fullstory/react-native" + FBReactNativeSpec: + :path: "../node_modules/react-native/React/FBReactNativeSpec" glog: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: @@ -2461,6 +2487,8 @@ SPEC CHECKSUMS: FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 + FullStory: e035758fef275fb59c6471f61b179652aeca452b + fullstory_react-native: a56e2bb52753b69f01aab3ae876087db08488034 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a diff --git a/jest/setup.ts b/jest/setup.ts index 488e3e36a1d3..b024d687b054 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -3,8 +3,10 @@ import 'react-native-gesture-handler/jestSetup'; import mockStorage from 'react-native-onyx/dist/storage/__mocks__'; import 'setimmediate'; import setupMockImages from './setupMockImages'; +import mockFSLibrary from './setupMockFullstoryLib'; setupMockImages(); +mockFSLibrary(); // This mock is required as per setup instructions for react-navigation testing // https://reactnavigation.org/docs/testing/#mocking-native-modules diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts new file mode 100644 index 000000000000..cc9c113ecdc5 --- /dev/null +++ b/jest/setupMockFullstoryLib.ts @@ -0,0 +1,18 @@ +export default function mockFSLibrary() { + jest.mock('@fullstory/react-native', () => { + class Fullstory { + consent = jest.fn(); + + anonymize = jest.fn(); + + identify = jest.fn(); + } + + return { + FSPage(){ + this.start = jest.fn() + }, + default: Fullstory, + }; + }); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 920fefc8242b..902f571b31f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/browser": "^2.0.3", + "@fullstory/react-native": "^1.4.0", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", @@ -5590,6 +5592,52 @@ "tslib": "^2.4.0" } }, + "node_modules/@fullstory/babel-plugin-annotate-react": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@fullstory/babel-plugin-annotate-react/-/babel-plugin-annotate-react-2.3.0.tgz", + "integrity": "sha512-gYLUL6Tu0exbvTIhK9nSCaztmqBlQAm07Fvtl/nKTc+lxwFkcX9vR8RrdTbyjJZKbPaA5EMlExQ6GeLCXkfm5g==" + }, + "node_modules/@fullstory/babel-plugin-react-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fullstory/babel-plugin-react-native/-/babel-plugin-react-native-1.1.0.tgz", + "integrity": "sha512-BqfSUdyrrYrZM286GzdHd3qCdbitxUAIM0Z+HpoOTGWVTLDpkFNNaRw5juq8YhYbcPm6BAtK0RMGY7CvcMNarA==", + "dependencies": { + "@babel/parser": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@fullstory/browser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@fullstory/browser/-/browser-2.0.3.tgz", + "integrity": "sha512-usjH8FB1O2LiSWoblsuKhFhlYDGpIPuyQVOx4JXtxm9QmQARdKZdNq1vPijxuDvOGjhwtVZa4JmhvByRRuDPnQ==", + "dependencies": { + "@fullstory/snippet": "2.0.3" + } + }, + "node_modules/@fullstory/react-native": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@fullstory/react-native/-/react-native-1.4.2.tgz", + "integrity": "sha512-Ig85ghn5UN+Tc1JWL/y4hY9vleeaVHL3f6qH9W4odDNP4XAv29+G82nIYQhBOQGoVnIQ4oQFQftir/dqAbidSw==", + "dependencies": { + "@fullstory/babel-plugin-annotate-react": "^2.2.0", + "@fullstory/babel-plugin-react-native": "^1.1.0" + }, + "peerDependencies": { + "expo": ">=47.0.0", + "react": "*", + "react-native": ">=0.61.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@fullstory/snippet": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@fullstory/snippet/-/snippet-2.0.3.tgz", + "integrity": "sha512-EaCuTQSLv5FvnjHLbTxErn3sS1+nLqf1p6sA/c4PV49stBtkUakA0eLhJJdaw0WLdXyEzZXf86lRNsjEzrgGPw==" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "license": "MIT" diff --git a/package.json b/package.json index 20d066eabebe..832bcd94dfab 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,8 @@ "@formatjs/intl-locale": "^3.3.0", "@formatjs/intl-numberformat": "^8.5.0", "@formatjs/intl-pluralrules": "^5.2.2", + "@fullstory/browser": "^2.0.3", + "@fullstory/react-native": "^1.4.0", "@gorhom/portal": "^1.0.14", "@invertase/react-native-apple-authentication": "^2.2.2", "@kie/act-js": "^2.6.0", diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 506eae2bdfd2..5760959c1b02 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -6,6 +6,7 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useTheme from '@hooks/useTheme'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {FSPage} from '@libs/fullstory'; import Log from '@libs/Log'; import {getPathFromURL} from '@libs/Url'; import {updateLastVisitedPath} from '@userActions/App'; @@ -57,6 +58,8 @@ function parseAndLogRoute(state: NavigationState) { } Navigation.setIsNavigationReady(); + // Fullstory Page navigation tracking + new FSPage(focusedRoute?.name ?? '', {path: currentPath}).start(); } function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: NavigationRootProps) { diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 7f7531a094fa..52cd4469f253 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -23,6 +23,7 @@ import type SignInUserParams from '@libs/API/parameters/SignInUserParams'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as Authentication from '@libs/Authentication'; import * as ErrorUtils from '@libs/ErrorUtils'; +import Fullstory from '@libs/fullstory'; import HttpUtils from '@libs/HttpUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; @@ -63,6 +64,11 @@ Onyx.connect({ }, }); +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: Fullstory.consentAndIdentify, +}); + let stashedSession: Session = {}; Onyx.connect({ key: ONYXKEYS.STASHED_SESSION, diff --git a/src/libs/fullstory/index.native.ts b/src/libs/fullstory/index.native.ts new file mode 100644 index 000000000000..d905cff2a62d --- /dev/null +++ b/src/libs/fullstory/index.native.ts @@ -0,0 +1,60 @@ +import FullStory, {FSPage} from '@fullstory/react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import type Session from '@src/types/onyx/Session'; +import type {UserSession} from './types'; + +/** + * Fullstory React-Native lib adapter + * Proxy function calls to React-Native lib + * */ +const FS = { + /** + * Sets the identity as anonymous using the FullStory library. + */ + anonymize: () => FullStory.anonymize(), + + /** + * Sets the identity consent status using the FullStory library. + */ + consent: (c: boolean) => FullStory.consent(c), + + /** + * Initializes the FullStory session with the provided session information. + */ + consentAndIdentify: (value: OnyxEntry) => { + try { + const session: UserSession = { + email: value?.email, + accountID: value?.accountID, + }; + // set consent + FullStory.consent(true); + FS.fsIdentify(session); + } catch (e) { + // error handler + } + }, + + /** + * Sets the FullStory user identity based on the provided session information. + * If the session is null or the email is 'undefined', the user identity is anonymized. + * If the session contains an email, the user identity is defined with the email and account ID. + */ + fsIdentify: (session: UserSession) => { + if (!session || session.email === 'undefined') { + // anonymize FullStory user identity session + FullStory.anonymize(); + } else { + // define FullStory user identity + FullStory.identify(String(session.accountID), { + properties: { + displayName: session.email, + email: session.email, + }, + }); + } + }, +}; + +export default FS; +export {FSPage} diff --git a/src/libs/fullstory/index.ts b/src/libs/fullstory/index.ts new file mode 100644 index 000000000000..02fef101f421 --- /dev/null +++ b/src/libs/fullstory/index.ts @@ -0,0 +1,89 @@ +import {FullStory, init, isInitialized} from '@fullstory/browser'; +import type {OnyxEntry} from 'react-native-onyx'; +import type Session from '@src/types/onyx/Session'; +import type {NavigationProperties, UserSession} from './types'; + +// Placeholder Browser API does not support Manual Page definition +class FSPage { + private pageName; + + private properties; + + constructor(name: string, properties: NavigationProperties) { + this.pageName = name; + this.properties = properties; + } + + start() {} +} + +/** + * Web does not use Fullstory React-Native lib + * Proxy function calls to Browser Snippet instance + * */ +const FS = { + /** + * Executes a function when the FullStory library is ready, either by initialization or by observing the start event. + */ + onReady: () => new Promise((resolve) => { + // Initialised via HEAD snippet + if (isInitialized()) { + init({orgId: ''}, resolve); + } else { + FullStory('observe', {type: 'start', callback: resolve}); + } + }), + + /** + * Sets the identity as anonymous using the FullStory library. + */ + anonymize: () => FullStory('setIdentity', {anonymous: true}), + + /** + * Sets the identity consent status using the FullStory library. + */ + consent: (c: boolean) => FullStory('setIdentity', {consent: c}), + + /** + * Initializes the FullStory session with the provided session information. + */ + consentAndIdentify: (value: OnyxEntry) => { + try { + FS.onReady().then(() => { + const session: UserSession = { + email: value?.email, + accountID: value?.accountID, + }; + // set consent + FS.consent(true); + FS.fsIdentify(session); + }); + } catch (e) { + // error handler + } + }, + + /** + * Sets the FullStory user identity based on the provided session information. + * If the session does not contain an email, the user identity is anonymized. + * If the session contains an email, the user identity is defined with the email and account ID. + */ + fsIdentify: (session: UserSession) => { + if (typeof session.email === 'undefined') { + // anonymize FullStory user identity session + FS.anonymize(); + } else { + // define FullStory user identity + FullStory('setIdentity', { + uid: String(session.accountID), + properties: { + displayName: session.email, + email: session.email, + }, + }); + } + }, +}; + +export default FS; +export {FSPage}; diff --git a/src/libs/fullstory/types.ts b/src/libs/fullstory/types.ts new file mode 100644 index 000000000000..386e35536d97 --- /dev/null +++ b/src/libs/fullstory/types.ts @@ -0,0 +1,10 @@ +type UserSession = { + email: string | undefined; + accountID: number | undefined; +}; + +type NavigationProperties = { + path: string; +}; + +export type {UserSession, NavigationProperties}; From 039884d8c88fbf6aa1e1c542f38af9b08735f6c9 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 18 Apr 2024 17:11:06 +0200 Subject: [PATCH 2/5] Eslint errors fixed --- jest/setup.ts | 2 +- jest/setupMockFullstoryLib.ts | 6 +++--- src/libs/fullstory/index.native.ts | 2 +- src/libs/fullstory/index.ts | 17 +++++++++-------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/jest/setup.ts b/jest/setup.ts index b024d687b054..174e59a7e493 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -2,8 +2,8 @@ import '@shopify/flash-list/jestSetup'; import 'react-native-gesture-handler/jestSetup'; import mockStorage from 'react-native-onyx/dist/storage/__mocks__'; import 'setimmediate'; -import setupMockImages from './setupMockImages'; import mockFSLibrary from './setupMockFullstoryLib'; +import setupMockImages from './setupMockImages'; setupMockImages(); mockFSLibrary(); diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts index cc9c113ecdc5..001449370e42 100644 --- a/jest/setupMockFullstoryLib.ts +++ b/jest/setupMockFullstoryLib.ts @@ -9,10 +9,10 @@ export default function mockFSLibrary() { } return { - FSPage(){ - this.start = jest.fn() + FSPage() { + this.start = jest.fn(); }, default: Fullstory, }; }); -} \ No newline at end of file +} diff --git a/src/libs/fullstory/index.native.ts b/src/libs/fullstory/index.native.ts index d905cff2a62d..e1781e6fed2d 100644 --- a/src/libs/fullstory/index.native.ts +++ b/src/libs/fullstory/index.native.ts @@ -57,4 +57,4 @@ const FS = { }; export default FS; -export {FSPage} +export {FSPage}; diff --git a/src/libs/fullstory/index.ts b/src/libs/fullstory/index.ts index 02fef101f421..3f42005e859b 100644 --- a/src/libs/fullstory/index.ts +++ b/src/libs/fullstory/index.ts @@ -25,14 +25,15 @@ const FS = { /** * Executes a function when the FullStory library is ready, either by initialization or by observing the start event. */ - onReady: () => new Promise((resolve) => { - // Initialised via HEAD snippet - if (isInitialized()) { - init({orgId: ''}, resolve); - } else { - FullStory('observe', {type: 'start', callback: resolve}); - } - }), + onReady: () => + new Promise((resolve) => { + // Initialised via HEAD snippet + if (isInitialized()) { + init({orgId: ''}, resolve); + } else { + FullStory('observe', {type: 'start', callback: resolve}); + } + }), /** * Sets the identity as anonymous using the FullStory library. From 9a991fccc5d02bbd41a5794c5edecc0b4e99ac6a Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 18 Apr 2024 17:38:25 +0200 Subject: [PATCH 3/5] Typecheck errors fixed --- jest/setupMockFullstoryLib.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts index 001449370e42..0aaf308e8d6e 100644 --- a/jest/setupMockFullstoryLib.ts +++ b/jest/setupMockFullstoryLib.ts @@ -1,3 +1,7 @@ +interface FSPageInterface { + start: jest.Mock; +} + export default function mockFSLibrary() { jest.mock('@fullstory/react-native', () => { class Fullstory { @@ -9,8 +13,10 @@ export default function mockFSLibrary() { } return { - FSPage() { - this.start = jest.fn(); + FSPage(): FSPageInterface { + return { + start: jest.fn(), + }; }, default: Fullstory, }; From 6fcb514a17ed0a7cf8c06fcb16c64ecaf3acb548 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 18 Apr 2024 18:03:30 +0200 Subject: [PATCH 4/5] Eslint errors fixed --- jest/setupMockFullstoryLib.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts index 0aaf308e8d6e..58f5e3a35932 100644 --- a/jest/setupMockFullstoryLib.ts +++ b/jest/setupMockFullstoryLib.ts @@ -1,4 +1,4 @@ -interface FSPageInterface { +type FSPageInterface = { start: jest.Mock; } From 78522e997823e46b71f42cbba103059b460ff051 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 18 Apr 2024 18:24:48 +0200 Subject: [PATCH 5/5] Eslint errors fixed --- jest/setupMockFullstoryLib.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest/setupMockFullstoryLib.ts b/jest/setupMockFullstoryLib.ts index 58f5e3a35932..9edfccab9441 100644 --- a/jest/setupMockFullstoryLib.ts +++ b/jest/setupMockFullstoryLib.ts @@ -1,6 +1,6 @@ type FSPageInterface = { start: jest.Mock; -} +}; export default function mockFSLibrary() { jest.mock('@fullstory/react-native', () => {