@grafana/faro-react-native 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +118 -36
- package/android/consumer-proguard-rules.pro +2 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ANRTracker.kt +61 -9
- package/android/src/main/java/com/grafana/faro/reactnative/AnrThreadDumpParser.kt +70 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ApplicationExitTraceReader.kt +142 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrCache.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrReporter.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +243 -50
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +75 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroNativeCrashTrace.kt +39 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +99 -8
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativePackage.kt +1 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroUncaughtExceptionHandler.kt +62 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneBacktraceFormatter.kt +117 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneModels.kt +35 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneWireParser.kt +235 -0
- package/android/src/main/proto/tombstone.proto +62 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrStackFiltersTest.kt +28 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrThreadDumpParserTest.kt +38 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroAnrCacheTest.kt +81 -0
- package/android/src/test/java/com/grafana/faro/reactnative/TombstoneWireParserTest.kt +51 -0
- package/dist/cjs/config/getRNInstrumentations.js +3 -1
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/makeRNConfig.js +48 -9
- package/dist/cjs/config/makeRNConfig.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/initialize.js +14 -8
- package/dist/cjs/initialize.js.map +1 -1
- package/dist/cjs/instrumentations/anr/anrStack.js +19 -0
- package/dist/cjs/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/cjs/instrumentations/anr/instrumentation.js +374 -72
- package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/anr/types.js.map +1 -1
- package/dist/cjs/instrumentations/console/index.js +5 -1
- package/dist/cjs/instrumentations/console/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +110 -103
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +41 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +87 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js +123 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js +12 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +181 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/index.js +35 -2
- package/dist/cjs/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +78 -0
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js +103 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js +48 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +2 -1
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/installationId.js +91 -0
- package/dist/cjs/instrumentations/session/installationId.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionAttributes.js +152 -89
- package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/cjs/metas/appBuildIdentity.js +95 -0
- package/dist/cjs/metas/appBuildIdentity.js.map +1 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js +5 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/cjs/testUtils/setupTests.js +7 -0
- package/dist/cjs/testUtils/setupTests.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +3 -1
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/makeRNConfig.js +49 -10
- package/dist/esm/config/makeRNConfig.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/initialize.js +11 -5
- package/dist/esm/initialize.js.map +1 -1
- package/dist/esm/instrumentations/anr/anrStack.js +16 -0
- package/dist/esm/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/esm/instrumentations/anr/instrumentation.js +260 -67
- package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/anr/types.js.map +1 -1
- package/dist/esm/instrumentations/console/index.js +5 -1
- package/dist/esm/instrumentations/console/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +76 -94
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +18 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js +116 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js +8 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +174 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/index.js +35 -1
- package/dist/esm/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js +98 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js +44 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +2 -1
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/session/installationId.js +48 -0
- package/dist/esm/instrumentations/session/installationId.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionAttributes.js +83 -38
- package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/esm/metas/appBuildIdentity.js +56 -0
- package/dist/esm/metas/appBuildIdentity.js.map +1 -0
- package/dist/esm/testUtils/mockDeviceInfo.js +5 -0
- package/dist/esm/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/esm/testUtils/setupTests.js +7 -0
- package/dist/esm/testUtils/setupTests.js.map +1 -1
- package/dist/types/config/makeRNConfig.d.ts +4 -3
- package/dist/types/config/types.d.ts +2 -1
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/initialize.d.ts +4 -2
- package/dist/types/instrumentations/anr/anrStack.d.ts +7 -0
- package/dist/types/instrumentations/anr/instrumentation.d.ts +23 -28
- package/dist/types/instrumentations/anr/types.d.ts +5 -1
- package/dist/types/instrumentations/appState/index.d.ts +1 -1
- package/dist/types/instrumentations/console/index.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +56 -0
- package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +12 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +26 -0
- package/dist/types/instrumentations/crashReporting/android/crashErrorMessage.d.ts +23 -0
- package/dist/types/instrumentations/crashReporting/android/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/android/nativeTombstoneTrace.d.ts +2 -0
- package/dist/types/instrumentations/crashReporting/android/parseAndroidCrashTrace.d.ts +45 -0
- package/dist/types/instrumentations/crashReporting/index.d.ts +25 -2
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +28 -0
- package/dist/types/instrumentations/crashReporting/ios/crashErrorMessage.d.ts +14 -0
- package/dist/types/instrumentations/crashReporting/ios/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/ios/parseIosCrashTrace.d.ts +38 -0
- package/dist/types/instrumentations/crashReporting/types.d.ts +18 -51
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +1 -1
- package/dist/types/instrumentations/http/index.d.ts +1 -1
- package/dist/types/instrumentations/performance/index.d.ts +1 -1
- package/dist/types/instrumentations/session/index.d.ts +1 -1
- package/dist/types/instrumentations/session/installationId.d.ts +1 -0
- package/dist/types/instrumentations/session/sessionAttributes.d.ts +13 -4
- package/dist/types/instrumentations/startup/index.d.ts +1 -1
- package/dist/types/instrumentations/userActions/index.d.ts +1 -1
- package/dist/types/instrumentations/view/index.d.ts +1 -1
- package/dist/types/instrumentations/xhr/index.d.ts +1 -1
- package/dist/types/metas/appBuildIdentity.d.ts +16 -0
- package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
- package/dist/types/transports/console/transport.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +1 -1
- package/dist/types/transports/offline/transport.d.ts +1 -1
- package/ios/faro-upload-composed-source-map.sh +8 -2
- package/package.json +5 -5
- package/src/config/getRNInstrumentations.ts +5 -1
- package/src/config/makeRNConfig.test.ts +59 -3
- package/src/config/makeRNConfig.ts +48 -9
- package/src/config/types.ts +2 -1
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/index.ts +2 -1
- package/src/initialize.test.ts +71 -15
- package/src/initialize.ts +11 -5
- package/src/instrumentations/anr/anrStack.test.ts +27 -0
- package/src/instrumentations/anr/anrStack.ts +19 -0
- package/src/instrumentations/anr/instrumentation.test.ts +45 -0
- package/src/instrumentations/anr/instrumentation.ts +301 -67
- package/src/instrumentations/anr/types.ts +7 -1
- package/src/instrumentations/console/index.ts +5 -1
- package/src/instrumentations/console/instrumentation.test.ts +5 -0
- package/src/instrumentations/crashReporting/{instrumentation.ts → BaseCrashReportingInstrumentation.ts} +119 -93
- package/src/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.ts +23 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.test.ts +182 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.ts +135 -0
- package/src/instrumentations/crashReporting/android/index.ts +4 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.test.ts +19 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.ts +8 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.test.ts +232 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts +235 -0
- package/src/instrumentations/crashReporting/index.ts +43 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +42 -0
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.test.ts +170 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.ts +111 -0
- package/src/instrumentations/crashReporting/ios/index.ts +4 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.test.ts +86 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts +83 -0
- package/src/instrumentations/crashReporting/types.ts +19 -58
- package/src/instrumentations/errors/index.ts +2 -1
- package/src/instrumentations/errors/instrumentation.test.ts +8 -2
- package/src/instrumentations/errors/stackTraceParser.test.ts +1 -1
- package/src/instrumentations/session/installationId.ts +43 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +260 -30
- package/src/instrumentations/session/sessionAttributes.ts +112 -22
- package/src/metas/appBuildIdentity.test.ts +48 -0
- package/src/metas/appBuildIdentity.ts +50 -0
- package/src/testUtils/mockDeviceInfo.ts +5 -0
- package/src/testUtils/setupTests.ts +7 -0
- package/dist/cjs/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/esm/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/types/instrumentations/crashReporting/instrumentation.d.ts +0 -58
|
@@ -78,6 +78,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
78
78
|
expect(item.payload.type).toBe('Error');
|
|
79
79
|
expect(item.payload.value).toBe('Test error');
|
|
80
80
|
expect(item.payload.context?.mechanism).toBe('uncaught');
|
|
81
|
+
expect(item.payload.fatal).toBe(false);
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
it('should include mechanism in context and use error.name for type (TypeError)', () => {
|
|
@@ -104,7 +105,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
104
105
|
expect(item.payload.context?.mechanism).toBe('uncaught');
|
|
105
106
|
});
|
|
106
107
|
|
|
107
|
-
it('should
|
|
108
|
+
it('should use the React Native fatal signal for global JavaScript errors', () => {
|
|
108
109
|
const transport = new MockTransport();
|
|
109
110
|
let errorHandler: any;
|
|
110
111
|
|
|
@@ -124,7 +125,8 @@ describe('ErrorsInstrumentation', () => {
|
|
|
124
125
|
|
|
125
126
|
expect(transport.items).toHaveLength(1);
|
|
126
127
|
const item = transport.items[0] as TransportItem<ExceptionEvent>;
|
|
127
|
-
expect(item.payload.
|
|
128
|
+
expect(item.payload.fatal).toBe(true);
|
|
129
|
+
expect(item.payload.context?.isFatal).toBeUndefined();
|
|
128
130
|
});
|
|
129
131
|
|
|
130
132
|
it('should call original error handler', () => {
|
|
@@ -399,6 +401,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
399
401
|
expect(item.payload.value).toContain('Unhandled Promise Rejection');
|
|
400
402
|
expect(item.payload.value).toContain('String rejection');
|
|
401
403
|
expect(item.payload.context?.mechanism).toBe('unhandledrejection');
|
|
404
|
+
expect(item.payload.fatal).toBe(false);
|
|
402
405
|
});
|
|
403
406
|
|
|
404
407
|
it('should use actual error type for Error rejections with mechanism', () => {
|
|
@@ -527,6 +530,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
527
530
|
describe('error handling resilience', () => {
|
|
528
531
|
it('should not crash if error reporting fails', () => {
|
|
529
532
|
let errorHandler: any;
|
|
533
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
530
534
|
|
|
531
535
|
mockGlobal.ErrorUtils.setGlobalHandler = jest.fn((handler) => {
|
|
532
536
|
errorHandler = handler;
|
|
@@ -534,6 +538,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
534
538
|
|
|
535
539
|
// Create a transport that throws
|
|
536
540
|
const failingTransport = {
|
|
541
|
+
isBatched: () => false,
|
|
537
542
|
send: jest.fn(() => {
|
|
538
543
|
throw new Error('Transport failed');
|
|
539
544
|
}),
|
|
@@ -548,6 +553,7 @@ describe('ErrorsInstrumentation', () => {
|
|
|
548
553
|
|
|
549
554
|
const testError = new Error('Test error');
|
|
550
555
|
expect(() => errorHandler(testError, false)).not.toThrow();
|
|
556
|
+
consoleErrorSpy.mockRestore();
|
|
551
557
|
});
|
|
552
558
|
|
|
553
559
|
it('should handle errors without stack', () => {
|
|
@@ -205,7 +205,7 @@ describe('Stack Trace Parser', () => {
|
|
|
205
205
|
|
|
206
206
|
it('collapses Hermes absolute .app bundle path to basename so uploads match ingest keys', () => {
|
|
207
207
|
const iosPath =
|
|
208
|
-
'/Users/ci/Library/Developer/CoreSimulator/Devices/ABC/Data/Containers/Bundle/Application/62A2/
|
|
208
|
+
'/Users/ci/Library/Developer/CoreSimulator/Devices/ABC/Data/Containers/Bundle/Application/62A2/MyApp.app/main.jsbundle';
|
|
209
209
|
const parsed = parseStackTraceLine(` at reportHandledException (${iosPath}:1:1249030)`);
|
|
210
210
|
expect(parsed).not.toBeNull();
|
|
211
211
|
const faroFrames = toFaroStackFrames([parsed!], { releaseBundleFilename: 'main.jsbundle' });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
|
|
3
|
+
const INSTALLATION_ID_STORAGE_KEY = '@grafana/faro-react-native/installation_id';
|
|
4
|
+
|
|
5
|
+
function createRandomBytes(length: number): Uint8Array {
|
|
6
|
+
const bytes = new Uint8Array(length);
|
|
7
|
+
const cryptoObject = globalThis.crypto;
|
|
8
|
+
|
|
9
|
+
if (cryptoObject && typeof cryptoObject.getRandomValues === 'function') {
|
|
10
|
+
cryptoObject.getRandomValues(bytes);
|
|
11
|
+
return bytes;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
15
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function generateInstallationId(): string {
|
|
22
|
+
const bytes = createRandomBytes(16);
|
|
23
|
+
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
|
|
24
|
+
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
|
|
25
|
+
|
|
26
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
27
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function getInstallationId(): Promise<string | undefined> {
|
|
31
|
+
try {
|
|
32
|
+
const storedInstallationId = await AsyncStorage.getItem(INSTALLATION_ID_STORAGE_KEY);
|
|
33
|
+
if (storedInstallationId) {
|
|
34
|
+
return storedInstallationId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const installationId = generateInstallationId();
|
|
38
|
+
await AsyncStorage.setItem(INSTALLATION_ID_STORAGE_KEY, installationId);
|
|
39
|
+
return installationId;
|
|
40
|
+
} catch (_error) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
1
2
|
import { Platform } from 'react-native';
|
|
2
3
|
import DeviceInfo from 'react-native-device-info';
|
|
3
4
|
|
|
4
|
-
import { getSessionAttributes } from './sessionAttributes';
|
|
5
|
+
import { getSessionAttributes, loadMobileMetaForInit } from './sessionAttributes';
|
|
6
|
+
|
|
7
|
+
const INSTALLATION_ID_STORAGE_KEY = '@grafana/faro-react-native/installation_id';
|
|
8
|
+
const STORED_INSTALLATION_ID = 'stored-installation-id';
|
|
9
|
+
|
|
10
|
+
function setStoredInstallationId(value = STORED_INSTALLATION_ID): void {
|
|
11
|
+
(global as any).mockAsyncStorage = {
|
|
12
|
+
[INSTALLATION_ID_STORAGE_KEY]: value,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
// Mock react-native-device-info
|
|
7
17
|
jest.mock('react-native-device-info', () => ({
|
|
@@ -10,6 +20,7 @@ jest.mock('react-native-device-info', () => ({
|
|
|
10
20
|
getSystemVersion: jest.fn(),
|
|
11
21
|
getManufacturerSync: jest.fn(),
|
|
12
22
|
getModel: jest.fn(),
|
|
23
|
+
getDeviceId: jest.fn(),
|
|
13
24
|
getDeviceNameSync: jest.fn(),
|
|
14
25
|
getBrand: jest.fn(),
|
|
15
26
|
isEmulatorSync: jest.fn(),
|
|
@@ -20,10 +31,12 @@ jest.mock('react-native-device-info', () => ({
|
|
|
20
31
|
isBatteryCharging: jest.fn(),
|
|
21
32
|
getCarrier: jest.fn(),
|
|
22
33
|
getApiLevel: jest.fn(),
|
|
34
|
+
getBuildId: jest.fn(),
|
|
23
35
|
}));
|
|
24
36
|
|
|
25
37
|
describe('sessionAttributes', () => {
|
|
26
38
|
beforeEach(() => {
|
|
39
|
+
(global as any).mockAsyncStorage = {};
|
|
27
40
|
jest.clearAllMocks();
|
|
28
41
|
});
|
|
29
42
|
|
|
@@ -45,13 +58,14 @@ describe('sessionAttributes', () => {
|
|
|
45
58
|
});
|
|
46
59
|
|
|
47
60
|
it('should collect all iOS device attributes', async () => {
|
|
61
|
+
setStoredInstallationId();
|
|
48
62
|
// Setup mocks for iOS device
|
|
49
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('ios-device-uuid-123');
|
|
50
63
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
51
64
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.0');
|
|
52
65
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
53
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
54
|
-
(DeviceInfo.
|
|
66
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 15 Pro');
|
|
67
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone16,1');
|
|
68
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Vishwan's iPhone");
|
|
55
69
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
56
70
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
57
71
|
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
@@ -69,11 +83,11 @@ describe('sessionAttributes', () => {
|
|
|
69
83
|
device_os_version: '17.0',
|
|
70
84
|
device_os_detail: 'iOS 17.0',
|
|
71
85
|
device_manufacturer: 'apple',
|
|
72
|
-
device_model: '
|
|
73
|
-
device_model_name: 'iPhone
|
|
86
|
+
device_model: 'iPhone 15 Pro',
|
|
87
|
+
device_model_name: "Vishwan's iPhone",
|
|
74
88
|
device_brand: 'Apple',
|
|
75
89
|
device_is_physical: 'true',
|
|
76
|
-
device_id:
|
|
90
|
+
device_id: STORED_INSTALLATION_ID,
|
|
77
91
|
device_type: 'mobile',
|
|
78
92
|
device_memory_total: '4000000000',
|
|
79
93
|
device_memory_used: '2000000000',
|
|
@@ -85,12 +99,12 @@ describe('sessionAttributes', () => {
|
|
|
85
99
|
});
|
|
86
100
|
|
|
87
101
|
it('should identify emulator devices', async () => {
|
|
88
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('emulator-uuid');
|
|
89
102
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
90
103
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.0');
|
|
91
104
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
92
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
93
|
-
(DeviceInfo.
|
|
105
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone Simulator');
|
|
106
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('x86_64');
|
|
107
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('Test iPhone Simulator');
|
|
94
108
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
95
109
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(true);
|
|
96
110
|
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
@@ -100,16 +114,16 @@ describe('sessionAttributes', () => {
|
|
|
100
114
|
const attributes = await getSessionAttributes();
|
|
101
115
|
|
|
102
116
|
expect(attributes.device_is_physical).toBe('false');
|
|
103
|
-
expect(attributes.device_model_name).toBe('iPhone Simulator');
|
|
117
|
+
expect(attributes.device_model_name).toBe('Test iPhone Simulator');
|
|
104
118
|
});
|
|
105
119
|
|
|
106
120
|
it('should handle iOS with different OS versions', async () => {
|
|
107
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('device-uuid');
|
|
108
121
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
109
122
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('16.4');
|
|
110
123
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
111
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
112
|
-
(DeviceInfo.
|
|
124
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 14 Pro');
|
|
125
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone15,2');
|
|
126
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's iPhone");
|
|
113
127
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
114
128
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
115
129
|
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
@@ -140,12 +154,14 @@ describe('sessionAttributes', () => {
|
|
|
140
154
|
});
|
|
141
155
|
|
|
142
156
|
it('should collect all Android device attributes', async () => {
|
|
157
|
+
setStoredInstallationId();
|
|
143
158
|
// Setup mocks for Android device
|
|
144
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('android-device-uuid-456');
|
|
145
159
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
146
160
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('15');
|
|
147
161
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Samsung');
|
|
148
162
|
(DeviceInfo.getModel as jest.Mock).mockReturnValue('SM-A155F');
|
|
163
|
+
// Android getDeviceId() is the board code; structured model_identifier should use Build.MODEL instead.
|
|
164
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('a15');
|
|
149
165
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('SM-A155F');
|
|
150
166
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('samsung');
|
|
151
167
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
@@ -169,7 +185,7 @@ describe('sessionAttributes', () => {
|
|
|
169
185
|
device_model_name: 'SM-A155F',
|
|
170
186
|
device_brand: 'samsung',
|
|
171
187
|
device_is_physical: 'true',
|
|
172
|
-
device_id:
|
|
188
|
+
device_id: STORED_INSTALLATION_ID,
|
|
173
189
|
device_type: 'mobile',
|
|
174
190
|
device_memory_total: '8000000000',
|
|
175
191
|
device_memory_used: '4000000000',
|
|
@@ -181,11 +197,11 @@ describe('sessionAttributes', () => {
|
|
|
181
197
|
});
|
|
182
198
|
|
|
183
199
|
it('should identify Android emulator devices', async () => {
|
|
184
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('emulator-uuid');
|
|
185
200
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
186
201
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('13');
|
|
187
202
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Google');
|
|
188
203
|
(DeviceInfo.getModel as jest.Mock).mockReturnValue('sdk_gphone64_arm64');
|
|
204
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('emu64a');
|
|
189
205
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('Pixel 5');
|
|
190
206
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('google');
|
|
191
207
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(true);
|
|
@@ -201,11 +217,11 @@ describe('sessionAttributes', () => {
|
|
|
201
217
|
});
|
|
202
218
|
|
|
203
219
|
it('should handle Android without API level', async () => {
|
|
204
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('device-uuid');
|
|
205
220
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
206
221
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('12');
|
|
207
222
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Xiaomi');
|
|
208
223
|
(DeviceInfo.getModel as jest.Mock).mockReturnValue('M2101K7AG');
|
|
224
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('camellia');
|
|
209
225
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('M2101K7AG');
|
|
210
226
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('xiaomi');
|
|
211
227
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
@@ -221,6 +237,218 @@ describe('sessionAttributes', () => {
|
|
|
221
237
|
});
|
|
222
238
|
});
|
|
223
239
|
|
|
240
|
+
describe('structured mobile meta', () => {
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
(Platform as any).OS = 'android';
|
|
243
|
+
Object.defineProperty(Platform, 'constants', {
|
|
244
|
+
value: {
|
|
245
|
+
reactNativeVersion: {
|
|
246
|
+
major: 0,
|
|
247
|
+
minor: 75,
|
|
248
|
+
patch: 1,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
writable: true,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should collect structured app, device, and OS meta with flat session attributes', async () => {
|
|
256
|
+
setStoredInstallationId();
|
|
257
|
+
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
258
|
+
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('15');
|
|
259
|
+
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Samsung');
|
|
260
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('SM-A155F');
|
|
261
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('a15');
|
|
262
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('Galaxy A15');
|
|
263
|
+
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('samsung');
|
|
264
|
+
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
265
|
+
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(true);
|
|
266
|
+
(DeviceInfo.getTotalMemorySync as jest.Mock).mockReturnValue(8000000000);
|
|
267
|
+
(DeviceInfo.getUsedMemorySync as jest.Mock).mockReturnValue(4000000000);
|
|
268
|
+
(DeviceInfo.getApiLevel as jest.Mock).mockResolvedValue(35);
|
|
269
|
+
(DeviceInfo.getBuildId as jest.Mock).mockResolvedValue('AP3A.240905.015.A2');
|
|
270
|
+
|
|
271
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
272
|
+
|
|
273
|
+
expect(mobileMeta.meta).toEqual({
|
|
274
|
+
app: {
|
|
275
|
+
installationId: STORED_INSTALLATION_ID,
|
|
276
|
+
},
|
|
277
|
+
device: {
|
|
278
|
+
brand: 'samsung',
|
|
279
|
+
is_physical: true,
|
|
280
|
+
manufacturer: 'Samsung',
|
|
281
|
+
model_identifier: 'SM-A155F',
|
|
282
|
+
model_name: 'SM-A155F',
|
|
283
|
+
type: 'tablet',
|
|
284
|
+
},
|
|
285
|
+
os: {
|
|
286
|
+
build_id: 'AP3A.240905.015.A2',
|
|
287
|
+
detail: 'Android 15 (SDK 35)',
|
|
288
|
+
name: 'Android',
|
|
289
|
+
version: '15',
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
expect(mobileMeta.sessionAttributes).toMatchObject({
|
|
293
|
+
device_id: STORED_INSTALLATION_ID,
|
|
294
|
+
device_model: 'SM-A155F',
|
|
295
|
+
device_os: 'Android',
|
|
296
|
+
device_os_detail: 'Android 15 (SDK 35)',
|
|
297
|
+
device_type: 'tablet',
|
|
298
|
+
});
|
|
299
|
+
expect(AsyncStorage.getItem).toHaveBeenCalledWith(INSTALLATION_ID_STORAGE_KEY);
|
|
300
|
+
expect(AsyncStorage.setItem).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should collect iOS structured app, device, and OS meta with flat session attributes', async () => {
|
|
304
|
+
(Platform as any).OS = 'ios';
|
|
305
|
+
setStoredInstallationId();
|
|
306
|
+
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
307
|
+
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.2');
|
|
308
|
+
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
309
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 15 Pro');
|
|
310
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone16,1');
|
|
311
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Vishwan's iPhone");
|
|
312
|
+
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
313
|
+
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
314
|
+
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
315
|
+
(DeviceInfo.getTotalMemorySync as jest.Mock).mockReturnValue(4000000000);
|
|
316
|
+
(DeviceInfo.getUsedMemorySync as jest.Mock).mockReturnValue(2000000000);
|
|
317
|
+
(DeviceInfo.getBuildId as jest.Mock).mockResolvedValue('21C62');
|
|
318
|
+
|
|
319
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
320
|
+
|
|
321
|
+
expect(mobileMeta.meta).toEqual({
|
|
322
|
+
app: {
|
|
323
|
+
installationId: STORED_INSTALLATION_ID,
|
|
324
|
+
},
|
|
325
|
+
device: {
|
|
326
|
+
brand: 'iPhone',
|
|
327
|
+
is_physical: true,
|
|
328
|
+
manufacturer: 'apple',
|
|
329
|
+
model_identifier: 'iPhone16,1',
|
|
330
|
+
model_name: 'iPhone 15 Pro',
|
|
331
|
+
type: 'mobile',
|
|
332
|
+
},
|
|
333
|
+
os: {
|
|
334
|
+
build_id: '21C62',
|
|
335
|
+
detail: 'iOS 17.2',
|
|
336
|
+
name: 'iOS',
|
|
337
|
+
version: '17.2',
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
expect(mobileMeta.sessionAttributes).toMatchObject({
|
|
341
|
+
device_id: STORED_INSTALLATION_ID,
|
|
342
|
+
device_model: 'iPhone 15 Pro',
|
|
343
|
+
device_os: 'iOS',
|
|
344
|
+
device_os_detail: 'iOS 17.2',
|
|
345
|
+
device_type: 'mobile',
|
|
346
|
+
});
|
|
347
|
+
expect(mobileMeta.meta.device?.model_name).not.toContain('Vishwan');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should classify iPad structured meta as iPad tablet', async () => {
|
|
351
|
+
(Platform as any).OS = 'ios';
|
|
352
|
+
setStoredInstallationId();
|
|
353
|
+
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iPadOS');
|
|
354
|
+
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('18.1');
|
|
355
|
+
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
356
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPad Pro');
|
|
357
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPad14,3');
|
|
358
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's iPad");
|
|
359
|
+
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
360
|
+
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
361
|
+
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(true);
|
|
362
|
+
(DeviceInfo.getTotalMemorySync as jest.Mock).mockReturnValue(8000000000);
|
|
363
|
+
(DeviceInfo.getUsedMemorySync as jest.Mock).mockReturnValue(3000000000);
|
|
364
|
+
(DeviceInfo.getBuildId as jest.Mock).mockResolvedValue('22B83');
|
|
365
|
+
|
|
366
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
367
|
+
|
|
368
|
+
expect(mobileMeta.meta.device).toMatchObject({
|
|
369
|
+
brand: 'iPad',
|
|
370
|
+
manufacturer: 'apple',
|
|
371
|
+
model_identifier: 'iPad14,3',
|
|
372
|
+
model_name: 'iPad Pro',
|
|
373
|
+
type: 'tablet',
|
|
374
|
+
});
|
|
375
|
+
expect(mobileMeta.meta.os).toMatchObject({
|
|
376
|
+
build_id: '22B83',
|
|
377
|
+
detail: 'iPadOS 18.1',
|
|
378
|
+
name: 'iPadOS',
|
|
379
|
+
version: '18.1',
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should omit mobile/tablet type when running on a non-mobile React Native platform', async () => {
|
|
384
|
+
(Platform as any).OS = 'macos';
|
|
385
|
+
setStoredInstallationId();
|
|
386
|
+
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('macOS');
|
|
387
|
+
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('15.0');
|
|
388
|
+
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
389
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('MacBookPro18,3');
|
|
390
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('MacBookPro18,3');
|
|
391
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's MacBook Pro");
|
|
392
|
+
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
393
|
+
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
394
|
+
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
395
|
+
(DeviceInfo.getTotalMemorySync as jest.Mock).mockReturnValue(16000000000);
|
|
396
|
+
(DeviceInfo.getUsedMemorySync as jest.Mock).mockReturnValue(8000000000);
|
|
397
|
+
(DeviceInfo.getBuildId as jest.Mock).mockResolvedValue('24A335');
|
|
398
|
+
|
|
399
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
400
|
+
|
|
401
|
+
expect(mobileMeta.sessionAttributes.device_type).toBeUndefined();
|
|
402
|
+
expect(mobileMeta.meta.device).not.toHaveProperty('type');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should create and persist an SDK installation id when one does not exist yet', async () => {
|
|
406
|
+
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
407
|
+
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('15');
|
|
408
|
+
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Google');
|
|
409
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('sdk_gphone64_arm64');
|
|
410
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('emu64a');
|
|
411
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('Pixel 5');
|
|
412
|
+
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('google');
|
|
413
|
+
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(true);
|
|
414
|
+
(DeviceInfo.isTablet as jest.Mock).mockReturnValue(false);
|
|
415
|
+
(DeviceInfo.getTotalMemorySync as jest.Mock).mockReturnValue(2000000000);
|
|
416
|
+
(DeviceInfo.getUsedMemorySync as jest.Mock).mockReturnValue(1000000000);
|
|
417
|
+
(DeviceInfo.getApiLevel as jest.Mock).mockResolvedValue(35);
|
|
418
|
+
(DeviceInfo.getBuildId as jest.Mock).mockResolvedValue('AP3A.240905.015.A2');
|
|
419
|
+
|
|
420
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
421
|
+
|
|
422
|
+
expect(mobileMeta.meta.app?.installationId).toMatch(
|
|
423
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
|
|
424
|
+
);
|
|
425
|
+
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
|
426
|
+
INSTALLATION_ID_STORAGE_KEY,
|
|
427
|
+
mobileMeta.meta.app?.installationId
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should keep installation id when DeviceInfo collection fails', async () => {
|
|
432
|
+
setStoredInstallationId();
|
|
433
|
+
(DeviceInfo.getSystemName as jest.Mock).mockImplementation(() => {
|
|
434
|
+
throw new Error('Failed to get system name');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const mobileMeta = await loadMobileMetaForInit();
|
|
438
|
+
|
|
439
|
+
expect(mobileMeta).toEqual({
|
|
440
|
+
sessionAttributes: {
|
|
441
|
+
react_native_version: '0.75.1',
|
|
442
|
+
},
|
|
443
|
+
meta: {
|
|
444
|
+
app: {
|
|
445
|
+
installationId: STORED_INSTALLATION_ID,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
224
452
|
describe('React Native version parsing', () => {
|
|
225
453
|
it('should parse version with prerelease', () => {
|
|
226
454
|
Object.defineProperty(Platform, 'constants', {
|
|
@@ -235,12 +463,12 @@ describe('sessionAttributes', () => {
|
|
|
235
463
|
writable: true,
|
|
236
464
|
});
|
|
237
465
|
|
|
238
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('uuid');
|
|
239
466
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
240
467
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.0');
|
|
241
468
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
242
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
243
|
-
(DeviceInfo.
|
|
469
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 15 Pro');
|
|
470
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone16,1');
|
|
471
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's iPhone");
|
|
244
472
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
245
473
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
246
474
|
|
|
@@ -255,12 +483,12 @@ describe('sessionAttributes', () => {
|
|
|
255
483
|
writable: true,
|
|
256
484
|
});
|
|
257
485
|
|
|
258
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('uuid');
|
|
259
486
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
260
487
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.0');
|
|
261
488
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('Apple');
|
|
262
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
263
|
-
(DeviceInfo.
|
|
489
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 15 Pro');
|
|
490
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone16,1');
|
|
491
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's iPhone");
|
|
264
492
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
265
493
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
266
494
|
|
|
@@ -273,7 +501,6 @@ describe('sessionAttributes', () => {
|
|
|
273
501
|
describe('Error handling', () => {
|
|
274
502
|
it('should gracefully handle DeviceInfo errors and return fallback values', async () => {
|
|
275
503
|
// Mock all DeviceInfo methods to throw errors
|
|
276
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockRejectedValue(new Error('Failed to get UUID'));
|
|
277
504
|
(DeviceInfo.getSystemName as jest.Mock).mockImplementation(() => {
|
|
278
505
|
throw new Error('Failed to get system name');
|
|
279
506
|
});
|
|
@@ -286,6 +513,9 @@ describe('sessionAttributes', () => {
|
|
|
286
513
|
(DeviceInfo.getModel as jest.Mock).mockImplementation(() => {
|
|
287
514
|
throw new Error('Failed to get model');
|
|
288
515
|
});
|
|
516
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockImplementation(() => {
|
|
517
|
+
throw new Error('Failed to get device id');
|
|
518
|
+
});
|
|
289
519
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockImplementation(() => {
|
|
290
520
|
throw new Error('Failed to get device name');
|
|
291
521
|
});
|
|
@@ -309,14 +539,14 @@ describe('sessionAttributes', () => {
|
|
|
309
539
|
it('should handle partial DeviceInfo failures', async () => {
|
|
310
540
|
// Some methods work, others fail
|
|
311
541
|
// Note: If any method throws, the entire catch block returns fallback values
|
|
312
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('valid-uuid');
|
|
313
542
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('iOS');
|
|
314
543
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('17.0');
|
|
315
544
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockImplementation(() => {
|
|
316
545
|
throw new Error('Manufacturer unavailable');
|
|
317
546
|
});
|
|
318
|
-
(DeviceInfo.getModel as jest.Mock).mockReturnValue('
|
|
319
|
-
(DeviceInfo.
|
|
547
|
+
(DeviceInfo.getModel as jest.Mock).mockReturnValue('iPhone 15 Pro');
|
|
548
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('iPhone16,1');
|
|
549
|
+
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue("Test's iPhone");
|
|
320
550
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('Apple');
|
|
321
551
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
322
552
|
|
|
@@ -334,11 +564,11 @@ describe('sessionAttributes', () => {
|
|
|
334
564
|
|
|
335
565
|
describe('Manufacturer normalization', () => {
|
|
336
566
|
it('should lowercase manufacturer names', async () => {
|
|
337
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('uuid');
|
|
338
567
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
339
568
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('14');
|
|
340
569
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('SAMSUNG');
|
|
341
570
|
(DeviceInfo.getModel as jest.Mock).mockReturnValue('SM-G998B');
|
|
571
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('p3s');
|
|
342
572
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('Galaxy S21 Ultra');
|
|
343
573
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('samsung');
|
|
344
574
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|
|
@@ -353,11 +583,11 @@ describe('sessionAttributes', () => {
|
|
|
353
583
|
});
|
|
354
584
|
|
|
355
585
|
it('should handle mixed case manufacturer names', async () => {
|
|
356
|
-
(DeviceInfo.getUniqueId as jest.Mock).mockResolvedValue('uuid');
|
|
357
586
|
(DeviceInfo.getSystemName as jest.Mock).mockReturnValue('Android');
|
|
358
587
|
(DeviceInfo.getSystemVersion as jest.Mock).mockReturnValue('14');
|
|
359
588
|
(DeviceInfo.getManufacturerSync as jest.Mock).mockReturnValue('OnePlus');
|
|
360
589
|
(DeviceInfo.getModel as jest.Mock).mockReturnValue('LE2121');
|
|
590
|
+
(DeviceInfo.getDeviceId as jest.Mock).mockReturnValue('lemonade');
|
|
361
591
|
(DeviceInfo.getDeviceNameSync as jest.Mock).mockReturnValue('OnePlus 9 Pro');
|
|
362
592
|
(DeviceInfo.getBrand as jest.Mock).mockReturnValue('OnePlus');
|
|
363
593
|
(DeviceInfo.isEmulatorSync as jest.Mock).mockReturnValue(false);
|