@symbiote-native/app-metrics 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +183 -2
- package/build/angular/app-metrics-root.d.ts +5 -0
- package/build/angular/app-metrics-root.js +62 -0
- package/build/angular/index.d.ts +3 -0
- package/build/angular/index.js +5 -0
- package/build/angular/inject-network-request-observer.d.ts +5 -0
- package/build/angular/inject-network-request-observer.js +7 -0
- package/build/core/app-metrics.d.ts +24 -0
- package/build/core/app-metrics.js +47 -0
- package/build/core/index.d.ts +5 -0
- package/build/core/index.js +6 -0
- package/build/core/install-error-handler.d.ts +2 -0
- package/build/core/install-error-handler.js +45 -0
- package/build/core/native-module.d.ts +19 -0
- package/build/core/native-module.js +3 -0
- package/build/core/network-request-observer-lifecycle.d.ts +8 -0
- package/build/core/network-request-observer-lifecycle.js +19 -0
- package/build/core/network-request-observer-subscription.d.ts +15 -0
- package/build/core/network-request-observer-subscription.js +22 -0
- package/build/core/network-request-observer.d.ts +7 -0
- package/build/core/network-request-observer.js +1 -0
- package/build/core/report-caught-error.d.ts +2 -0
- package/build/core/report-caught-error.js +20 -0
- package/build/core/session.d.ts +12 -0
- package/build/core/session.js +1 -0
- package/build/core/types.d.ts +168 -0
- package/build/core/types.js +3 -0
- package/build/react/app-metrics-error-boundary.d.ts +26 -0
- package/build/react/app-metrics-error-boundary.js +28 -0
- package/build/react/app-metrics-root.d.ts +12 -0
- package/build/react/app-metrics-root.js +24 -0
- package/build/react/index.d.ts +4 -0
- package/build/react/index.js +4 -0
- package/build/react/use-network-request-observer.d.ts +5 -0
- package/build/react/use-network-request-observer.js +23 -0
- package/build/solid/app-metrics-error-boundary.d.ts +15 -0
- package/build/solid/app-metrics-error-boundary.js +17 -0
- package/build/solid/app-metrics-root.d.ts +9 -0
- package/build/solid/app-metrics-root.js +18 -0
- package/build/solid/index.d.ts +4 -0
- package/build/solid/index.js +4 -0
- package/build/solid/use-network-request-observer.d.ts +5 -0
- package/build/solid/use-network-request-observer.js +6 -0
- package/build/svelte/AppMetricsErrorBoundary.svelte +17 -0
- package/build/svelte/AppMetricsErrorBoundary.svelte.d.ts +4 -0
- package/build/svelte/AppMetricsRoot.svelte +22 -0
- package/build/svelte/AppMetricsRoot.svelte.d.ts +4 -0
- package/build/svelte/app-metrics-error-boundary-props.d.ts +12 -0
- package/build/svelte/app-metrics-error-boundary-props.js +3 -0
- package/build/svelte/app-metrics-root-props.d.ts +9 -0
- package/build/svelte/app-metrics-root-props.js +1 -0
- package/build/svelte/index.d.ts +7 -0
- package/build/svelte/index.js +4 -0
- package/build/svelte/use-network-request-observer.svelte.d.ts +3 -0
- package/build/svelte/use-network-request-observer.svelte.js +14 -0
- package/build/vue/app-metrics-error-boundary.d.ts +14 -0
- package/build/vue/app-metrics-error-boundary.js +27 -0
- package/build/vue/app-metrics-root.d.ts +7 -0
- package/build/vue/app-metrics-root.js +15 -0
- package/build/vue/index.d.ts +4 -0
- package/build/vue/index.js +4 -0
- package/build/vue/use-network-request-observer.d.ts +5 -0
- package/build/vue/use-network-request-observer.js +6 -0
- package/build-ngc/angular/app-metrics-root.d.ts +8 -0
- package/build-ngc/angular/app-metrics-root.js +20 -0
- package/build-ngc/angular/app-metrics-root.js.map +1 -0
- package/build-ngc/angular/index.d.ts +3 -0
- package/build-ngc/angular/index.js +6 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/inject-network-request-observer.d.ts +5 -0
- package/build-ngc/angular/inject-network-request-observer.js +8 -0
- package/build-ngc/angular/inject-network-request-observer.js.map +1 -0
- package/build-ngc/core/app-metrics.d.ts +24 -0
- package/build-ngc/core/app-metrics.js +48 -0
- package/build-ngc/core/app-metrics.js.map +1 -0
- package/build-ngc/core/index.d.ts +5 -0
- package/build-ngc/core/index.js +7 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/install-error-handler.d.ts +2 -0
- package/build-ngc/core/install-error-handler.js +46 -0
- package/build-ngc/core/install-error-handler.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +19 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/network-request-observer-lifecycle.d.ts +8 -0
- package/build-ngc/core/network-request-observer-lifecycle.js +20 -0
- package/build-ngc/core/network-request-observer-lifecycle.js.map +1 -0
- package/build-ngc/core/network-request-observer-subscription.d.ts +15 -0
- package/build-ngc/core/network-request-observer-subscription.js +23 -0
- package/build-ngc/core/network-request-observer-subscription.js.map +1 -0
- package/build-ngc/core/network-request-observer.d.ts +7 -0
- package/build-ngc/core/network-request-observer.js +2 -0
- package/build-ngc/core/network-request-observer.js.map +1 -0
- package/build-ngc/core/report-caught-error.d.ts +2 -0
- package/build-ngc/core/report-caught-error.js +21 -0
- package/build-ngc/core/report-caught-error.js.map +1 -0
- package/build-ngc/core/session.d.ts +12 -0
- package/build-ngc/core/session.js +2 -0
- package/build-ngc/core/session.js.map +1 -0
- package/build-ngc/core/types.d.ts +168 -0
- package/build-ngc/core/types.js +4 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +147 -3
- package/src/angular/app-metrics-root.ts +14 -0
- package/src/angular/index.ts +8 -0
- package/src/angular/inject-network-request-observer.ts +18 -0
- package/src/core/app-metrics.ts +74 -0
- package/src/core/index.ts +52 -0
- package/src/core/install-error-handler.ts +55 -0
- package/src/core/native-module.ts +36 -0
- package/src/core/network-request-observer-lifecycle.ts +37 -0
- package/src/core/network-request-observer-subscription.ts +53 -0
- package/src/core/network-request-observer.ts +15 -0
- package/src/core/report-caught-error.ts +26 -0
- package/src/core/session.ts +17 -0
- package/src/core/types.ts +200 -0
- package/src/react/app-metrics-error-boundary.tsx +67 -0
- package/src/react/app-metrics-root.tsx +53 -0
- package/src/react/index.ts +11 -0
- package/src/react/use-network-request-observer.ts +41 -0
- package/src/solid/app-metrics-error-boundary.tsx +36 -0
- package/src/solid/app-metrics-root.tsx +29 -0
- package/src/solid/index.ts +12 -0
- package/src/solid/use-network-request-observer.ts +17 -0
- package/src/svelte/AppMetricsErrorBoundary.svelte +17 -0
- package/src/svelte/AppMetricsRoot.svelte +21 -0
- package/src/svelte/app-metrics-error-boundary-props.ts +17 -0
- package/src/svelte/app-metrics-root-props.ts +10 -0
- package/src/svelte/index.ts +10 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-network-request-observer.svelte.ts +26 -0
- package/src/vue/app-metrics-error-boundary.ts +56 -0
- package/src/vue/app-metrics-root.ts +32 -0
- package/src/vue/index.ts +12 -0
- package/src/vue/use-network-request-observer.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,150 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/app-metrics",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "expo-app-metrics wrapped for SymbioteNative — one framework-agnostic core plus a React adapter, reachable from every framework's core surface and React's additional AppMetricsRoot/AppMetricsErrorBoundary/useNetworkRequestObserver. App startup, frame rate, memory, crash reporting, and session metrics.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react-native",
|
|
8
|
+
"symbiote-native",
|
|
9
|
+
"expo-app-metrics",
|
|
10
|
+
"react",
|
|
11
|
+
"vue",
|
|
12
|
+
"svelte",
|
|
13
|
+
"solid",
|
|
14
|
+
"angular",
|
|
15
|
+
"metrics",
|
|
16
|
+
"crash-reporting"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
21
|
+
"directory": "packages/app-metrics"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/app-metrics#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
26
|
+
},
|
|
27
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./build/core/index.js",
|
|
30
|
+
"module": "./build/core/index.js",
|
|
31
|
+
"types": "./build/core/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./build/core/index.d.ts",
|
|
35
|
+
"default": "./build/core/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./react": {
|
|
38
|
+
"types": "./build/react/index.d.ts",
|
|
39
|
+
"default": "./build/react/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./vue": {
|
|
42
|
+
"types": "./build/vue/index.d.ts",
|
|
43
|
+
"default": "./build/vue/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./svelte": {
|
|
46
|
+
"types": "./build/svelte/index.d.ts",
|
|
47
|
+
"default": "./build/svelte/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./solid": {
|
|
50
|
+
"types": "./build/solid/index.d.ts",
|
|
51
|
+
"default": "./build/solid/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./angular": {
|
|
54
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
55
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
56
|
+
"default": "./src/angular/index.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"src",
|
|
61
|
+
"build",
|
|
62
|
+
"build-ngc",
|
|
63
|
+
"native-link.json",
|
|
64
|
+
"!src/**/*.test.*",
|
|
65
|
+
"!src/**/*.spec.*",
|
|
66
|
+
"!src/**/*.detox.*"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"expo-app-metrics": "57.0.21",
|
|
73
|
+
"expo-modules-core": "57.0.20"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@angular/core": ">=20",
|
|
77
|
+
"@vue/runtime-core": "^3.5.13",
|
|
78
|
+
"react": ">=19.0.0",
|
|
79
|
+
"react-native": ">=0.86",
|
|
80
|
+
"solid-js": ">=1.9.0",
|
|
81
|
+
"svelte": ">=5.56.0",
|
|
82
|
+
"vue": ">=3.5.0",
|
|
83
|
+
"@symbiote-native/angular": "^3.1.3",
|
|
84
|
+
"@symbiote-native/engine": "^1.4.0",
|
|
85
|
+
"@symbiote-native/solid": "^3.0.5",
|
|
86
|
+
"@symbiote-native/svelte": "^3.0.5",
|
|
87
|
+
"@symbiote-native/vue": "^3.1.0",
|
|
88
|
+
"@symbiote-native/react": "^3.1.0"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@symbiote-native/angular": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@symbiote-native/react": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@symbiote-native/solid": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@symbiote-native/svelte": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@symbiote-native/vue": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@angular/core": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@vue/runtime-core": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"solid-js": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"svelte": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"vue": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@angular/compiler": "~22.0.8",
|
|
127
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
128
|
+
"@angular/core": "~22.0.8",
|
|
129
|
+
"@types/node": "^26.0.0",
|
|
130
|
+
"@types/react": "^19.2.0",
|
|
131
|
+
"@vue/runtime-core": "^3.5.13",
|
|
132
|
+
"react": "19.2.3",
|
|
133
|
+
"solid-js": "^1.9.14",
|
|
134
|
+
"svelte": "^5.56.0",
|
|
135
|
+
"typescript": "~6.0.0",
|
|
136
|
+
"@symbiote-native/angular": "3.1.3",
|
|
137
|
+
"@symbiote-native/react": "3.1.0",
|
|
138
|
+
"@symbiote-native/engine": "1.4.0",
|
|
139
|
+
"@symbiote-native/test-utils": "0.4.5",
|
|
140
|
+
"@symbiote-native/svelte": "3.0.5",
|
|
141
|
+
"@symbiote-native/solid": "3.0.5",
|
|
142
|
+
"@symbiote-native/vue": "3.1.0"
|
|
143
|
+
},
|
|
144
|
+
"scripts": {
|
|
145
|
+
"typecheck": "tsc --build",
|
|
146
|
+
"clean": "rm -rf build-ngc",
|
|
147
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
148
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
149
|
+
}
|
|
6
150
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Component, type OnInit } from '@angular/core';
|
|
2
|
+
import { markFirstRender } from '../core';
|
|
3
|
+
|
|
4
|
+
/** Marks the first render, so time-to-first-render is measured without a manual call */
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'app-metrics-root',
|
|
7
|
+
standalone: true,
|
|
8
|
+
template: '<ng-content />',
|
|
9
|
+
})
|
|
10
|
+
export class AppMetricsRoot implements OnInit {
|
|
11
|
+
ngOnInit(): void {
|
|
12
|
+
markFirstRender();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// No `AppMetricsErrorBoundary` here - blocked on `@angular/core` >=22.2
|
|
2
|
+
// (this package's README "Scope decision")
|
|
3
|
+
export * from '../core';
|
|
4
|
+
export { AppMetricsRoot } from './app-metrics-root';
|
|
5
|
+
export {
|
|
6
|
+
injectNetworkRequestObserver,
|
|
7
|
+
type IUseNetworkRequestObserverOptions,
|
|
8
|
+
} from './inject-network-request-observer';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DestroyRef, effect, inject } from '@angular/core';
|
|
2
|
+
import { createNetworkRequestObserverLifecycle } from '../core/network-request-observer-lifecycle';
|
|
3
|
+
import type { NetworkRequestObserver } from '../core';
|
|
4
|
+
import type { IUseNetworkRequestObserverOptions } from '../core/network-request-observer-lifecycle';
|
|
5
|
+
|
|
6
|
+
export type { IUseNetworkRequestObserverOptions } from '../core/network-request-observer-lifecycle';
|
|
7
|
+
|
|
8
|
+
/** Angular twin of `expo-app-metrics`'s `useNetworkRequestObserver` */
|
|
9
|
+
export function injectNetworkRequestObserver(
|
|
10
|
+
getOptions: () => IUseNetworkRequestObserverOptions = () => ({}),
|
|
11
|
+
): NetworkRequestObserver {
|
|
12
|
+
const destroyRef = inject(DestroyRef);
|
|
13
|
+
return createNetworkRequestObserverLifecycle(
|
|
14
|
+
getOptions,
|
|
15
|
+
filterEffect => effect(filterEffect),
|
|
16
|
+
cleanup => destroyRef.onDestroy(cleanup),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoAppMetrics } from './native-module';
|
|
3
|
+
import type {
|
|
4
|
+
ICrashReport,
|
|
5
|
+
IDebugSession,
|
|
6
|
+
ILogAttributeValue,
|
|
7
|
+
ILogEventOptions,
|
|
8
|
+
IMetricAttributes,
|
|
9
|
+
IReportErrorInput,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
const NATIVE_MODULE_NAME = 'expo-app-metrics';
|
|
13
|
+
|
|
14
|
+
/** Marks the moment the app is ready to measure time to first render */
|
|
15
|
+
export function markFirstRender(): void {
|
|
16
|
+
expoAppMetrics.markFirstRender();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function markInteractive(attributes?: IMetricAttributes): void {
|
|
20
|
+
expoAppMetrics.markInteractive(attributes);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Persisted locally, dispatched on the next flush as an OpenTelemetry log record */
|
|
24
|
+
export function logEvent(name: string, options?: ILogEventOptions): void {
|
|
25
|
+
expoAppMetrics.logEvent(name, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Merged into every subsequent metric and log event, per-record keys win on collision */
|
|
29
|
+
export function setGlobalAttributes(
|
|
30
|
+
attributes?: Record<string, ILogAttributeValue> | null,
|
|
31
|
+
): void {
|
|
32
|
+
expoAppMetrics.setGlobalAttributes(attributes);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function clearStoredEntries(): Promise<void> {
|
|
36
|
+
return expoAppMetrics.clearStoredEntries();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Debug-only, ordered most-recent-first, each session eagerly includes metrics/logs/crash */
|
|
40
|
+
export async function getInactiveSessions(): Promise<IDebugSession[]> {
|
|
41
|
+
return expoAppMetrics.getInactiveSessions();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Debug-only, includes orphan crashes with `sessionId: null`. @platform android */
|
|
45
|
+
export async function getAllCrashReports(): Promise<ICrashReport[]> {
|
|
46
|
+
if (Platform.OS !== 'android' || !expoAppMetrics.getAllCrashReports) {
|
|
47
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getAllCrashReports');
|
|
48
|
+
}
|
|
49
|
+
return expoAppMetrics.getAllCrashReports();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Called by `installErrorHandler`'s global handler and `AppMetricsErrorBoundary` */
|
|
53
|
+
export function reportError(error: IReportErrorInput): void {
|
|
54
|
+
expoAppMetrics.reportError(error);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The per-launch session, a static reference: repeated calls return the same object */
|
|
58
|
+
export function getMainSession() {
|
|
59
|
+
return expoAppMetrics.getMainSession();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Created when the app becomes active, ended when backgrounded, `null` when none is active */
|
|
63
|
+
export async function getForegroundSession() {
|
|
64
|
+
return expoAppMetrics.getForegroundSession();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const NetworkRequestObserver = expoAppMetrics.NetworkRequestObserver;
|
|
68
|
+
export type NetworkRequestObserver = InstanceType<
|
|
69
|
+
typeof NetworkRequestObserver
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
/** Not intended to be constructed from user code, see `getMainSession`/`getForegroundSession` */
|
|
73
|
+
export const Session = expoAppMetrics.Session;
|
|
74
|
+
export type Session = InstanceType<typeof Session>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { installErrorHandler } from './install-error-handler';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
clearStoredEntries,
|
|
5
|
+
getAllCrashReports,
|
|
6
|
+
getForegroundSession,
|
|
7
|
+
getInactiveSessions,
|
|
8
|
+
getMainSession,
|
|
9
|
+
logEvent,
|
|
10
|
+
markFirstRender,
|
|
11
|
+
markInteractive,
|
|
12
|
+
NetworkRequestObserver,
|
|
13
|
+
reportError,
|
|
14
|
+
Session,
|
|
15
|
+
setGlobalAttributes,
|
|
16
|
+
} from './app-metrics';
|
|
17
|
+
export {
|
|
18
|
+
filterKeyOf,
|
|
19
|
+
subscribeNetworkRequestObserverEvents,
|
|
20
|
+
} from './network-request-observer-subscription';
|
|
21
|
+
export type {
|
|
22
|
+
INetworkRequestObserverCallbacks,
|
|
23
|
+
ISubscribableNetworkRequestObserver,
|
|
24
|
+
} from './network-request-observer-subscription';
|
|
25
|
+
export type {
|
|
26
|
+
IAppStartupTimes,
|
|
27
|
+
ICallStack,
|
|
28
|
+
ICallStackFrame,
|
|
29
|
+
ICallStackTree,
|
|
30
|
+
ICrashReport,
|
|
31
|
+
IDebugSession,
|
|
32
|
+
IFrameRateMetrics,
|
|
33
|
+
ILogAttributeValue,
|
|
34
|
+
ILogEventOptions,
|
|
35
|
+
ILogRecord,
|
|
36
|
+
ILogSeverity,
|
|
37
|
+
IMemoryUsageSnapshot,
|
|
38
|
+
IMetric,
|
|
39
|
+
IMetricAttributes,
|
|
40
|
+
IMetricInput,
|
|
41
|
+
INetworkRequestCompletedEvent,
|
|
42
|
+
INetworkRequestFilter,
|
|
43
|
+
INetworkRequestObserverEvents,
|
|
44
|
+
INetworkRequestRedirect,
|
|
45
|
+
INetworkRequestStartedEvent,
|
|
46
|
+
IReportErrorInput,
|
|
47
|
+
ISessionType,
|
|
48
|
+
} from './types';
|
|
49
|
+
export { installErrorHandler } from './install-error-handler';
|
|
50
|
+
|
|
51
|
+
// Live as early as the app pulls this module in, matching upstream's own side effect
|
|
52
|
+
installErrorHandler();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { reportError } from './app-metrics';
|
|
2
|
+
|
|
3
|
+
type IErrorHandlerCallback = (error: unknown, isFatal?: boolean) => void;
|
|
4
|
+
|
|
5
|
+
type IGlobalErrorUtils = {
|
|
6
|
+
getGlobalHandler(): IErrorHandlerCallback | undefined;
|
|
7
|
+
setGlobalHandler(handler: IErrorHandlerCallback): void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let installed = false;
|
|
11
|
+
|
|
12
|
+
// react-native ships its own ambient `ErrorUtils` global, so a package whose tsconfig pulls
|
|
13
|
+
// that in conflicts with a local `declare global` here (core/engine/src/report-error.ts hit
|
|
14
|
+
// this, TS2451) - reading it off `globalThis` through Reflect sidesteps the whole ambient type
|
|
15
|
+
function globalErrorUtils(): IGlobalErrorUtils | null {
|
|
16
|
+
const utils: unknown = Reflect.get(globalThis, 'ErrorUtils');
|
|
17
|
+
if (typeof utils !== 'object' || utils === null) return null;
|
|
18
|
+
const getGlobalHandler: unknown = Reflect.get(utils, 'getGlobalHandler');
|
|
19
|
+
const setGlobalHandler: unknown = Reflect.get(utils, 'setGlobalHandler');
|
|
20
|
+
if (
|
|
21
|
+
typeof getGlobalHandler !== 'function' ||
|
|
22
|
+
typeof setGlobalHandler !== 'function'
|
|
23
|
+
) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
getGlobalHandler: () => Reflect.apply(getGlobalHandler, utils, []),
|
|
28
|
+
setGlobalHandler: handler =>
|
|
29
|
+
Reflect.apply(setGlobalHandler, utils, [handler]),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Wraps React Native's global `ErrorUtils` handler, chaining to whatever ran before it */
|
|
34
|
+
export function installErrorHandler(): void {
|
|
35
|
+
if (installed) return;
|
|
36
|
+
const errorUtils = globalErrorUtils();
|
|
37
|
+
if (!errorUtils) return;
|
|
38
|
+
installed = true;
|
|
39
|
+
|
|
40
|
+
const previousHandler = errorUtils.getGlobalHandler();
|
|
41
|
+
errorUtils.setGlobalHandler((error, isFatal) => {
|
|
42
|
+
try {
|
|
43
|
+
const asError = error instanceof Error ? error : undefined;
|
|
44
|
+
reportError({
|
|
45
|
+
source: 'global',
|
|
46
|
+
type: asError?.name,
|
|
47
|
+
message: asError?.message ?? String(error),
|
|
48
|
+
stacktrace: asError?.stack,
|
|
49
|
+
isFatal: isFatal ?? false,
|
|
50
|
+
});
|
|
51
|
+
} finally {
|
|
52
|
+
previousHandler?.(error, isFatal);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type { NetworkRequestObserver } from './network-request-observer';
|
|
3
|
+
import type { Session } from './session';
|
|
4
|
+
import type {
|
|
5
|
+
ICrashReport,
|
|
6
|
+
IDebugSession,
|
|
7
|
+
ILogAttributeValue,
|
|
8
|
+
ILogEventOptions,
|
|
9
|
+
IMetricAttributes,
|
|
10
|
+
IReportErrorInput,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
const EXPO_APP_METRICS_MODULE_NAME = 'ExpoAppMetrics';
|
|
14
|
+
|
|
15
|
+
export type INativeAppMetricsModule = {
|
|
16
|
+
markFirstRender(): void;
|
|
17
|
+
markInteractive(attributes?: IMetricAttributes): void;
|
|
18
|
+
logEvent(name: string, options?: ILogEventOptions): void;
|
|
19
|
+
setGlobalAttributes(
|
|
20
|
+
attributes?: Record<string, ILogAttributeValue> | null,
|
|
21
|
+
): void;
|
|
22
|
+
clearStoredEntries(): Promise<void>;
|
|
23
|
+
getInactiveSessions(): Promise<IDebugSession[]>;
|
|
24
|
+
/** Android's native module only, app-metrics.ts gates it on Platform.OS */
|
|
25
|
+
getAllCrashReports?(): Promise<ICrashReport[]>;
|
|
26
|
+
reportError(error: IReportErrorInput): void;
|
|
27
|
+
getMainSession(): Session;
|
|
28
|
+
// Typed `@platform ios` upstream, but AppMetricsModule.swift and .kt both implement it
|
|
29
|
+
getForegroundSession(): Promise<Session | null>;
|
|
30
|
+
NetworkRequestObserver: typeof NetworkRequestObserver;
|
|
31
|
+
Session: typeof Session;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const expoAppMetrics = requireNativeModule<INativeAppMetricsModule>(
|
|
35
|
+
EXPO_APP_METRICS_MODULE_NAME,
|
|
36
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NetworkRequestObserver } from './app-metrics';
|
|
2
|
+
import {
|
|
3
|
+
filterKeyOf,
|
|
4
|
+
subscribeNetworkRequestObserverEvents,
|
|
5
|
+
} from './network-request-observer-subscription';
|
|
6
|
+
import type { INetworkRequestObserverCallbacks } from './network-request-observer-subscription';
|
|
7
|
+
import type { INetworkRequestFilter } from './types';
|
|
8
|
+
|
|
9
|
+
export type IUseNetworkRequestObserverOptions =
|
|
10
|
+
INetworkRequestObserverCallbacks & {
|
|
11
|
+
/** Applied natively. Omit or pass `null` to observe every request */
|
|
12
|
+
filter?: INetworkRequestFilter | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Shared by adapters whose reactivity auto-tracks reads inside a plain effect (Vue's
|
|
16
|
+
// `watchEffect`, Solid's `createEffect`); React's render model doesn't, so it keeps its own wiring
|
|
17
|
+
export function createNetworkRequestObserverLifecycle(
|
|
18
|
+
getOptions: () => IUseNetworkRequestObserverOptions,
|
|
19
|
+
runFilterEffect: (effect: () => void) => void,
|
|
20
|
+
registerCleanup: (cleanup: () => void) => void,
|
|
21
|
+
): NetworkRequestObserver {
|
|
22
|
+
const initialFilter = getOptions().filter;
|
|
23
|
+
const observer = new NetworkRequestObserver(initialFilter);
|
|
24
|
+
|
|
25
|
+
let appliedFilterKey = filterKeyOf(initialFilter);
|
|
26
|
+
runFilterEffect(() => {
|
|
27
|
+
const nextKey = filterKeyOf(getOptions().filter);
|
|
28
|
+
if (appliedFilterKey === nextKey) return;
|
|
29
|
+
appliedFilterKey = nextKey;
|
|
30
|
+
observer.setFilter(getOptions().filter ?? null);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
registerCleanup(subscribeNetworkRequestObserverEvents(observer, getOptions));
|
|
34
|
+
registerCleanup(() => observer.release());
|
|
35
|
+
|
|
36
|
+
return observer;
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
INetworkRequestCompletedEvent,
|
|
3
|
+
INetworkRequestFilter,
|
|
4
|
+
INetworkRequestStartedEvent,
|
|
5
|
+
} from './types';
|
|
6
|
+
|
|
7
|
+
// Field-listed rather than a bare JSON.stringify(filter) so `{ hosts, methods }` and
|
|
8
|
+
// `{ methods, hosts }` (the same filter) don't produce different keys
|
|
9
|
+
export function filterKeyOf(
|
|
10
|
+
filter: INetworkRequestFilter | null | undefined,
|
|
11
|
+
): string {
|
|
12
|
+
return JSON.stringify({
|
|
13
|
+
hosts: filter?.hosts ?? null,
|
|
14
|
+
methods: filter?.methods ?? null,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type INetworkRequestObserverCallbacks = {
|
|
19
|
+
onStarted?: (event: INetworkRequestStartedEvent) => void;
|
|
20
|
+
onCompleted?: (event: INetworkRequestCompletedEvent) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ISubscribableNetworkRequestObserver = {
|
|
24
|
+
addListener(
|
|
25
|
+
event: 'requestStarted',
|
|
26
|
+
listener: (event: INetworkRequestStartedEvent) => void,
|
|
27
|
+
): { remove: () => void };
|
|
28
|
+
addListener(
|
|
29
|
+
event: 'requestCompleted',
|
|
30
|
+
listener: (event: INetworkRequestCompletedEvent) => void,
|
|
31
|
+
): { remove: () => void };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// `getCallbacks` is read fresh on every event, so a caller never needs to resubscribe just
|
|
35
|
+
// because its own callbacks changed identity between renders
|
|
36
|
+
export function subscribeNetworkRequestObserverEvents(
|
|
37
|
+
observer: ISubscribableNetworkRequestObserver,
|
|
38
|
+
getCallbacks: () => INetworkRequestObserverCallbacks,
|
|
39
|
+
): () => void {
|
|
40
|
+
const startedSubscription = observer.addListener('requestStarted', event => {
|
|
41
|
+
getCallbacks().onStarted?.(event);
|
|
42
|
+
});
|
|
43
|
+
const completedSubscription = observer.addListener(
|
|
44
|
+
'requestCompleted',
|
|
45
|
+
event => {
|
|
46
|
+
getCallbacks().onCompleted?.(event);
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
return () => {
|
|
50
|
+
startedSubscription.remove();
|
|
51
|
+
completedSubscription.remove();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SharedObject } from 'expo-modules-core';
|
|
2
|
+
import type {
|
|
3
|
+
INetworkRequestFilter,
|
|
4
|
+
INetworkRequestObserverEvents,
|
|
5
|
+
} from './types';
|
|
6
|
+
|
|
7
|
+
// Ambient shape only, matching upstream's own declaration - the real class is a native
|
|
8
|
+
// SharedObject subclass exposed as `NetworkRequestObserver` on the native module, constructed
|
|
9
|
+
// from JS with `new NetworkRequestObserver(filter)` and released like any other SharedObject
|
|
10
|
+
export declare class NetworkRequestObserver extends SharedObject<INetworkRequestObserverEvents> {
|
|
11
|
+
constructor(filter?: INetworkRequestFilter | null);
|
|
12
|
+
|
|
13
|
+
/** Applies atomically, in-flight events are emitted under either the old or new filter */
|
|
14
|
+
setFilter(filter: INetworkRequestFilter | null): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { reportError } from './app-metrics';
|
|
2
|
+
|
|
3
|
+
/** Shared by every adapter's error-boundary equivalent, builds and reports a caught render error */
|
|
4
|
+
export function reportCaughtError(
|
|
5
|
+
error: unknown,
|
|
6
|
+
componentStack?: string,
|
|
7
|
+
): void {
|
|
8
|
+
const caught = error instanceof Error ? error : undefined;
|
|
9
|
+
try {
|
|
10
|
+
reportError({
|
|
11
|
+
source: 'errorBoundary',
|
|
12
|
+
type: caught?.name,
|
|
13
|
+
message: caught?.message ?? String(error),
|
|
14
|
+
stacktrace: caught?.stack,
|
|
15
|
+
componentStack,
|
|
16
|
+
isFatal: false,
|
|
17
|
+
});
|
|
18
|
+
} catch (reportingError) {
|
|
19
|
+
if (__DEV__) {
|
|
20
|
+
console.warn(
|
|
21
|
+
'[app-metrics] Failed to report a caught error:',
|
|
22
|
+
reportingError,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SharedObject } from 'expo-modules-core';
|
|
2
|
+
import type { ILogRecord, IMetric, IMetricInput, ISessionType } from './types';
|
|
3
|
+
|
|
4
|
+
// Ambient shape only, matching upstream's own Session.ts - the real class is a native
|
|
5
|
+
// SharedObject subclass exposed as `Session` on the native module (§10b of
|
|
6
|
+
// symbiote-expo-native-module), never constructed from JS
|
|
7
|
+
export declare class Session extends SharedObject {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly type: ISessionType;
|
|
10
|
+
readonly startDate: string;
|
|
11
|
+
|
|
12
|
+
isActive(): Promise<boolean>;
|
|
13
|
+
getEndDate(): Promise<string | null>;
|
|
14
|
+
getMetrics(): Promise<IMetric[]>;
|
|
15
|
+
getLogs(): Promise<ILogRecord[]>;
|
|
16
|
+
addMetric(metric: IMetricInput): Promise<void>;
|
|
17
|
+
}
|