expo-observe 56.0.6 → 56.0.8
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/src/main/java/expo/modules/observe/OpenTelemetry.kt +5 -0
- package/build/ObserveProvider.d.ts.map +1 -1
- package/build/ObserveProvider.js +3 -2
- package/build/ObserveProvider.js.map +1 -1
- package/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts +5 -0
- package/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts.map +1 -0
- package/build/integrations/expo-router/ObserveRouterIntegrationProvider.js +21 -0
- package/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map +1 -0
- package/build/integrations/expo-router/index.d.ts +1 -0
- package/build/integrations/expo-router/index.d.ts.map +1 -1
- package/build/integrations/expo-router/index.js +1 -0
- package/build/integrations/expo-router/index.js.map +1 -1
- package/build/integrations/expo-router/init.d.ts +5 -0
- package/build/integrations/expo-router/init.d.ts.map +1 -1
- package/build/integrations/expo-router/init.js +83 -0
- package/build/integrations/expo-router/init.js.map +1 -1
- package/build/integrations/expo-router/storage.d.ts +27 -0
- package/build/integrations/expo-router/storage.d.ts.map +1 -0
- package/build/integrations/expo-router/storage.js +10 -0
- package/build/integrations/expo-router/storage.js.map +1 -0
- package/build/integrations/expo-router/useObserveForRouter.d.ts.map +1 -1
- package/build/integrations/expo-router/useObserveForRouter.js +46 -3
- package/build/integrations/expo-router/useObserveForRouter.js.map +1 -1
- package/expo-module.config.json +1 -1
- package/ios/CursorRepair.swift +55 -0
- package/ios/Event.swift +61 -45
- package/ios/Observability.swift +96 -94
- package/ios/ObserveUserDefaults.swift +13 -13
- package/ios/OpenTelemetry.swift +29 -18
- package/ios/Tests/CursorRepairTests.swift +94 -0
- package/ios/Tests/OTAnyValueTests.swift +37 -5
- package/ios/Tests/OpenTelemetryTests.swift +30 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.6/expo.modules.observe-56.0.6-sources.jar → 56.0.8/expo.modules.observe-56.0.8-sources.jar} +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8-sources.jar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8-sources.jar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8-sources.jar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8-sources.jar.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.6/expo.modules.observe-56.0.6.aar → 56.0.8/expo.modules.observe-56.0.8.aar} +0 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.aar.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.aar.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.aar.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.aar.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.6/expo.modules.observe-56.0.6.module → 56.0.8/expo.modules.observe-56.0.8.module} +23 -23
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.module.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.module.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.module.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.module.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/{56.0.6/expo.modules.observe-56.0.6.pom → 56.0.8/expo.modules.observe-56.0.8.pom} +2 -2
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.pom.md5 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.pom.sha1 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.pom.sha256 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.8/expo.modules.observe-56.0.8.pom.sha512 +1 -0
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml +4 -4
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.md5 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha1 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha256 +1 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/maven-metadata.xml.sha512 +1 -1
- package/package.json +4 -4
- package/src/ObserveProvider.tsx +3 -1
- package/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx +32 -0
- package/src/integrations/expo-router/index.ts +1 -0
- package/src/integrations/expo-router/init.ts +98 -0
- package/src/integrations/expo-router/storage.ts +38 -0
- package/src/integrations/expo-router/useObserveForRouter.ts +55 -3
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6-sources.jar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.aar.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.module.sha512 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.md5 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha1 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha256 +0 -1
- package/local-maven-repo/expo/modules/observe/expo.modules.observe/56.0.6/expo.modules.observe-56.0.6.pom.sha512 +0 -1
|
@@ -189,6 +189,11 @@ private val metricNameMap = mapOf(
|
|
|
189
189
|
|
|
190
190
|
// Updates
|
|
191
191
|
(MetricCategory.Updates.categoryName to "updateDownloadTime") to "expo.updates.download_time",
|
|
192
|
+
|
|
193
|
+
// Navigation
|
|
194
|
+
(MetricCategory.Navigation.categoryName to "cold_ttr") to "expo.navigation.cold_ttr",
|
|
195
|
+
(MetricCategory.Navigation.categoryName to "warm_ttr") to "expo.navigation.warm_ttr",
|
|
196
|
+
(MetricCategory.Navigation.categoryName to "tti") to "expo.navigation.tti"
|
|
192
197
|
)
|
|
193
198
|
|
|
194
199
|
fun EASMetric.toOTMetric(): OTMetric {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObserveProvider.d.ts","sourceRoot":"","sources":["../src/ObserveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ObserveProvider.d.ts","sourceRoot":"","sources":["../src/ObserveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAI/C,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAE9D"}
|
package/build/ObserveProvider.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import {} from 'react';
|
|
3
|
+
import { ObserveRouterIntegrationProvider } from './integrations/expo-router/ObserveRouterIntegrationProvider';
|
|
3
4
|
export function ObserveProvider({ children }) {
|
|
4
|
-
return _jsx(
|
|
5
|
+
return _jsx(ObserveRouterIntegrationProvider, { children: children });
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=ObserveProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObserveProvider.js","sourceRoot":"","sources":["../src/ObserveProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAA0B,MAAM,OAAO,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAqB;IAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"ObserveProvider.js","sourceRoot":"","sources":["../src/ObserveProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAA0B,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,gCAAgC,EAAE,MAAM,6DAA6D,CAAC;AAE/G,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,EAAqB;IAC7D,OAAO,KAAC,gCAAgC,cAAE,QAAQ,GAAoC,CAAC;AACzF,CAAC","sourcesContent":["import { type PropsWithChildren } from 'react';\n\nimport { ObserveRouterIntegrationProvider } from './integrations/expo-router/ObserveRouterIntegrationProvider';\n\nexport function ObserveProvider({ children }: PropsWithChildren) {\n return <ObserveRouterIntegrationProvider>{children}</ObserveRouterIntegrationProvider>;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type PropsWithChildren } from 'react';
|
|
2
|
+
import { type RouterIntegrationStorage } from './storage';
|
|
3
|
+
export declare const ObserveRouterIntegrationContext: import("react").Context<RouterIntegrationStorage | null>;
|
|
4
|
+
export declare function ObserveRouterIntegrationProvider({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
//# sourceMappingURL=ObserveRouterIntegrationProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObserveRouterIntegrationProvider.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,iBAAiB,EAA+B,MAAM,OAAO,CAAC;AAI3F,OAAO,EAAkC,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE1F,eAAO,MAAM,+BAA+B,0DAAuD,CAAC;AAEpG,wBAAgB,gCAAgC,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAuB/E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { initListeners, isInitialized } from './init';
|
|
4
|
+
import { optionalRouter } from './router';
|
|
5
|
+
import { createRouterIntegrationStorage } from './storage';
|
|
6
|
+
export const ObserveRouterIntegrationContext = createContext(null);
|
|
7
|
+
export function ObserveRouterIntegrationProvider({ children }) {
|
|
8
|
+
const [storage] = useState(() => isInitialized() ? createRouterIntegrationStorage() : null);
|
|
9
|
+
const [listenersCleanup] = useState(() => {
|
|
10
|
+
if (!storage || !optionalRouter)
|
|
11
|
+
return;
|
|
12
|
+
return initListeners(storage, optionalRouter.unstable_navigationEvents);
|
|
13
|
+
});
|
|
14
|
+
const prevInitialized = useRef(isInitialized());
|
|
15
|
+
if (prevInitialized.current !== isInitialized()) {
|
|
16
|
+
throw new Error(`[expo-observe] Router integration was ${isInitialized() ? 'enabled' : 'disabled'} after application mounted. Call ExpoObserve.configure() before mounting AppMetricsRoot.`);
|
|
17
|
+
}
|
|
18
|
+
useEffect(() => listenersCleanup, [listenersCleanup]);
|
|
19
|
+
return (_jsx(ObserveRouterIntegrationContext.Provider, { value: storage, children: children }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=ObserveRouterIntegrationProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObserveRouterIntegrationProvider.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAA0B,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE3F,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,8BAA8B,EAAiC,MAAM,WAAW,CAAC;AAE1F,MAAM,CAAC,MAAM,+BAA+B,GAAG,aAAa,CAAkC,IAAI,CAAC,CAAC;AAEpG,MAAM,UAAU,gCAAgC,CAAC,EAAE,QAAQ,EAAqB;IAC9E,MAAM,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAkC,GAAG,EAAE,CAC/D,aAAa,EAAE,CAAC,CAAC,CAAC,8BAA8B,EAAE,CAAC,CAAC,CAAC,IAAI,CAC1D,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QACvC,IAAI,CAAC,OAAO,IAAI,CAAC,cAAc;YAAE,OAAO;QACxC,OAAO,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,yBAAyB,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;IAChD,IAAI,eAAe,CAAC,OAAO,KAAK,aAAa,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,yCAAyC,aAAa,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,0FAA0F,CAC5K,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAEtD,OAAO,CACL,KAAC,+BAA+B,CAAC,QAAQ,IAAC,KAAK,EAAE,OAAO,YACrD,QAAQ,GACgC,CAC5C,CAAC;AACJ,CAAC","sourcesContent":["import { createContext, type PropsWithChildren, useEffect, useRef, useState } from 'react';\n\nimport { initListeners, isInitialized } from './init';\nimport { optionalRouter } from './router';\nimport { createRouterIntegrationStorage, type RouterIntegrationStorage } from './storage';\n\nexport const ObserveRouterIntegrationContext = createContext<RouterIntegrationStorage | null>(null);\n\nexport function ObserveRouterIntegrationProvider({ children }: PropsWithChildren) {\n const [storage] = useState<RouterIntegrationStorage | null>(() =>\n isInitialized() ? createRouterIntegrationStorage() : null\n );\n const [listenersCleanup] = useState(() => {\n if (!storage || !optionalRouter) return;\n return initListeners(storage, optionalRouter.unstable_navigationEvents);\n });\n\n const prevInitialized = useRef(isInitialized());\n if (prevInitialized.current !== isInitialized()) {\n throw new Error(\n `[expo-observe] Router integration was ${isInitialized() ? 'enabled' : 'disabled'} after application mounted. Call ExpoObserve.configure() before mounting AppMetricsRoot.`\n );\n }\n\n useEffect(() => listenersCleanup, [listenersCleanup]);\n\n return (\n <ObserveRouterIntegrationContext.Provider value={storage}>\n {children}\n </ObserveRouterIntegrationContext.Provider>\n );\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { isRouterInstalled } from './router';
|
|
2
2
|
export { useObserveForRouter } from './useObserveForRouter';
|
|
3
3
|
export { initRouterIntegration } from './init';
|
|
4
|
+
export { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { isRouterInstalled } from './router';
|
|
2
2
|
export { useObserveForRouter } from './useObserveForRouter';
|
|
3
3
|
export { initRouterIntegration } from './init';
|
|
4
|
+
export { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC","sourcesContent":["export { isRouterInstalled } from './router';\nexport { useObserveForRouter } from './useObserveForRouter';\nexport { initRouterIntegration } from './init';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC","sourcesContent":["export { isRouterInstalled } from './router';\nexport { useObserveForRouter } from './useObserveForRouter';\nexport { initRouterIntegration } from './init';\nexport { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider';\n"]}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { optionalRouter } from './router';
|
|
2
|
+
import { type RouterIntegrationStorage } from './storage';
|
|
1
3
|
export declare const isInitialized: () => boolean;
|
|
2
4
|
export declare function initRouterIntegration(): void;
|
|
5
|
+
type NavigationEvents = NonNullable<typeof optionalRouter>['unstable_navigationEvents'];
|
|
6
|
+
export declare function initListeners(storage: RouterIntegrationStorage, navigationEvents: NavigationEvents): () => void;
|
|
7
|
+
export {};
|
|
3
8
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,eAAoB,CAAC;AAE/C,wBAAgB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAS1D,eAAO,MAAM,aAAa,eAAoB,CAAC;AAE/C,wBAAgB,qBAAqB,SAGpC;AAED,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,cAAc,CAAC,CAAC,2BAA2B,CAAC,CAAC;AAExF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,wBAAwB,EACjC,gBAAgB,EAAE,gBAAgB,GACjC,MAAM,IAAI,CAgFZ"}
|
|
@@ -1,6 +1,89 @@
|
|
|
1
|
+
import AppMetrics from 'expo-app-metrics';
|
|
2
|
+
import { optionalRouter } from './router';
|
|
3
|
+
import {} from './storage';
|
|
4
|
+
// TODO(@ubax): split this module into `.native.ts` / `.web.ts` variants so the
|
|
5
|
+
// web bundle doesn't pull in `expo-app-metrics`' native bridge calls. The web
|
|
6
|
+
// version should be an explicit no-op (return a noop cleanup) rather than
|
|
7
|
+
// relying on the web stubs in `expo-app-metrics/module.web.ts`.
|
|
1
8
|
let initialized = false;
|
|
2
9
|
export const isInitialized = () => initialized;
|
|
3
10
|
export function initRouterIntegration() {
|
|
4
11
|
initialized = true;
|
|
12
|
+
optionalRouter?.unstable_navigationEvents.enable();
|
|
13
|
+
}
|
|
14
|
+
export function initListeners(storage, navigationEvents) {
|
|
15
|
+
const appLaunchTime = performance.now();
|
|
16
|
+
const cleanup = new Set();
|
|
17
|
+
const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event) => {
|
|
18
|
+
// PRELOAD comes from router.prefetch() — a route warm-up, not a user
|
|
19
|
+
// navigation — so it must not seed dispatchTime.
|
|
20
|
+
if (event.actionType === 'PRELOAD')
|
|
21
|
+
return;
|
|
22
|
+
storage.pendingActions.push({
|
|
23
|
+
actionType: event.actionType,
|
|
24
|
+
dispatchTime: performance.now(),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
cleanup.add(unsubscribeAction);
|
|
28
|
+
const unsubscribePreload = navigationEvents.addListener('pagePreloaded', (e) => {
|
|
29
|
+
// The screen rendered as part of a preload. Mark it as already rendered so
|
|
30
|
+
// the eventual `pageFocused` resolves to `warm_ttr` rather than `cold_ttr`.
|
|
31
|
+
storage.renderedScreensIds.add(e.screenId);
|
|
32
|
+
});
|
|
33
|
+
cleanup.add(unsubscribePreload);
|
|
34
|
+
const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e) => {
|
|
35
|
+
// Snapshot both clocks once so every metric written below is stamped with
|
|
36
|
+
// the moment the focus event fired, not the moment `addCustomMetricToSession`
|
|
37
|
+
// happens to run after the awaited `getMainSession()` round-trip.
|
|
38
|
+
const now = performance.now();
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
const isInitial = !storage.renderedScreensIds.has(e.screenId);
|
|
41
|
+
storage.renderedScreensIds.add(e.screenId);
|
|
42
|
+
const name = isInitial ? 'cold_ttr' : 'warm_ttr';
|
|
43
|
+
const mainSessionId = (await AppMetrics.getMainSession())?.id;
|
|
44
|
+
if (!mainSessionId) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!storage.hasRecordedInitialTtr) {
|
|
48
|
+
// Stored in seconds to match the OTel `unit = "s"` convention
|
|
49
|
+
const appLaunchTtrSeconds = (now - appLaunchTime) / 1000;
|
|
50
|
+
storage.hasRecordedInitialTtr = true;
|
|
51
|
+
AppMetrics.addCustomMetricToSession({
|
|
52
|
+
sessionId: mainSessionId,
|
|
53
|
+
timestamp,
|
|
54
|
+
category: 'navigation',
|
|
55
|
+
name,
|
|
56
|
+
routeName: e.pathname,
|
|
57
|
+
value: appLaunchTtrSeconds,
|
|
58
|
+
params: { isAppLaunch: true, routeParams: e.params },
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (storage.pendingActions.length === 0)
|
|
63
|
+
return;
|
|
64
|
+
const last = storage.pendingActions[storage.pendingActions.length - 1];
|
|
65
|
+
if (last) {
|
|
66
|
+
const dispatchTime = last.dispatchTime;
|
|
67
|
+
storage.screenTimes[e.screenId] = {
|
|
68
|
+
...storage.screenTimes[e.screenId],
|
|
69
|
+
dispatchTime,
|
|
70
|
+
};
|
|
71
|
+
AppMetrics.addCustomMetricToSession({
|
|
72
|
+
sessionId: mainSessionId,
|
|
73
|
+
timestamp,
|
|
74
|
+
category: 'navigation',
|
|
75
|
+
name,
|
|
76
|
+
routeName: e.pathname,
|
|
77
|
+
value: (now - dispatchTime) / 1000,
|
|
78
|
+
params: { isAppLaunch: false, routeParams: e.params },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
storage.pendingActions.length = 0;
|
|
82
|
+
});
|
|
83
|
+
cleanup.add(unsubscribeFocus);
|
|
84
|
+
return () => {
|
|
85
|
+
cleanup.forEach((c) => c());
|
|
86
|
+
cleanup.clear();
|
|
87
|
+
};
|
|
5
88
|
}
|
|
6
89
|
//# sourceMappingURL=init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"AAAA,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC;AAE/C,MAAM,UAAU,qBAAqB;IACnC,WAAW,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"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","sourcesContent":["import AppMetrics from 'expo-app-metrics';\n\nimport { optionalRouter } from './router';\nimport { type RouterIntegrationStorage } from './storage';\n\n// TODO(@ubax): split this module into `.native.ts` / `.web.ts` variants so the\n// web bundle doesn't pull in `expo-app-metrics`' native bridge calls. The web\n// version should be an explicit no-op (return a noop cleanup) rather than\n// relying on the web stubs in `expo-app-metrics/module.web.ts`.\n\nlet initialized = false;\n\nexport const isInitialized = () => initialized;\n\nexport function initRouterIntegration() {\n initialized = true;\n optionalRouter?.unstable_navigationEvents.enable();\n}\n\ntype NavigationEvents = NonNullable<typeof optionalRouter>['unstable_navigationEvents'];\n\nexport function initListeners(\n storage: RouterIntegrationStorage,\n navigationEvents: NavigationEvents\n): () => void {\n const appLaunchTime = performance.now();\n const cleanup = new Set<() => void>();\n\n const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event) => {\n // PRELOAD comes from router.prefetch() — a route warm-up, not a user\n // navigation — so it must not seed dispatchTime.\n if (event.actionType === 'PRELOAD') return;\n storage.pendingActions.push({\n actionType: event.actionType,\n dispatchTime: performance.now(),\n });\n });\n cleanup.add(unsubscribeAction);\n\n const unsubscribePreload = navigationEvents.addListener('pagePreloaded', (e) => {\n // The screen rendered as part of a preload. Mark it as already rendered so\n // the eventual `pageFocused` resolves to `warm_ttr` rather than `cold_ttr`.\n storage.renderedScreensIds.add(e.screenId);\n });\n cleanup.add(unsubscribePreload);\n\n const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e) => {\n // Snapshot both clocks once so every metric written below is stamped with\n // the moment the focus event fired, not the moment `addCustomMetricToSession`\n // happens to run after the awaited `getMainSession()` round-trip.\n const now = performance.now();\n const timestamp = new Date().toISOString();\n const isInitial = !storage.renderedScreensIds.has(e.screenId);\n storage.renderedScreensIds.add(e.screenId);\n const name = isInitial ? 'cold_ttr' : 'warm_ttr';\n const mainSessionId = (await AppMetrics.getMainSession())?.id;\n if (!mainSessionId) {\n return;\n }\n\n if (!storage.hasRecordedInitialTtr) {\n // Stored in seconds to match the OTel `unit = \"s\"` convention\n const appLaunchTtrSeconds = (now - appLaunchTime) / 1000;\n storage.hasRecordedInitialTtr = true;\n AppMetrics.addCustomMetricToSession({\n sessionId: mainSessionId,\n timestamp,\n category: 'navigation',\n name,\n routeName: e.pathname,\n value: appLaunchTtrSeconds,\n params: { isAppLaunch: true, routeParams: e.params },\n });\n return;\n }\n\n if (storage.pendingActions.length === 0) return;\n\n const last = storage.pendingActions[storage.pendingActions.length - 1];\n if (last) {\n const dispatchTime = last.dispatchTime;\n storage.screenTimes[e.screenId] = {\n ...storage.screenTimes[e.screenId],\n dispatchTime,\n };\n\n AppMetrics.addCustomMetricToSession({\n sessionId: mainSessionId,\n timestamp,\n category: 'navigation',\n name,\n routeName: e.pathname,\n value: (now - dispatchTime) / 1000,\n params: { isAppLaunch: false, routeParams: e.params },\n });\n }\n storage.pendingActions.length = 0;\n });\n cleanup.add(unsubscribeFocus);\n\n return () => {\n cleanup.forEach((c) => c());\n cleanup.clear();\n };\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ActionDispatchedEvent } from 'expo-router';
|
|
2
|
+
export interface ScreenTimes {
|
|
3
|
+
dispatchTime: number;
|
|
4
|
+
lastInteractiveCall?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PendingAction {
|
|
7
|
+
actionType: ActionDispatchedEvent['actionType'];
|
|
8
|
+
dispatchTime: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RouterIntegrationStorage {
|
|
11
|
+
/**
|
|
12
|
+
* Actions dispatched, but not yet processed by the integration
|
|
13
|
+
*/
|
|
14
|
+
pendingActions: PendingAction[];
|
|
15
|
+
renderedScreensIds: Set<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Wether the app had already recorded the first render of the screen
|
|
18
|
+
*/
|
|
19
|
+
hasRecordedInitialTtr: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Times used to calculate spans from dispatch to certain event
|
|
22
|
+
*/
|
|
23
|
+
screenTimes: Record<string, ScreenTimes>;
|
|
24
|
+
interactiveScreensIds: Set<string>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createRouterIntegrationStorage(): RouterIntegrationStorage;
|
|
27
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAQzE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AA6BA,MAAM,UAAU,8BAA8B;IAC5C,OAAO;QACL,cAAc,EAAE,EAAE;QAClB,kBAAkB,EAAE,IAAI,GAAG,EAAE;QAC7B,qBAAqB,EAAE,KAAK;QAC5B,WAAW,EAAE,EAAE;QACf,qBAAqB,EAAE,IAAI,GAAG,EAAE;KACjC,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionDispatchedEvent } from 'expo-router';\n\nexport interface ScreenTimes {\n dispatchTime: number;\n lastInteractiveCall?: number;\n}\n\nexport interface PendingAction {\n actionType: ActionDispatchedEvent['actionType'];\n dispatchTime: number;\n}\n\nexport interface RouterIntegrationStorage {\n /**\n * Actions dispatched, but not yet processed by the integration\n */\n pendingActions: PendingAction[];\n renderedScreensIds: Set<string>;\n /**\n * Wether the app had already recorded the first render of the screen\n */\n hasRecordedInitialTtr: boolean;\n /**\n * Times used to calculate spans from dispatch to certain event\n */\n screenTimes: Record<string, ScreenTimes>;\n interactiveScreensIds: Set<string>;\n}\n\nexport function createRouterIntegrationStorage(): RouterIntegrationStorage {\n return {\n pendingActions: [],\n renderedScreensIds: new Set(),\n hasRecordedInitialTtr: false,\n screenTimes: {},\n interactiveScreensIds: new Set(),\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObserveForRouter.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAqC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"useObserveForRouter.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAqC,MAAM,kBAAkB,CAAC;AAOrE,KAAK,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAE9D,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CA2G5D"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import AppMetrics, {} from 'expo-app-metrics';
|
|
2
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { use, useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { ObserveRouterIntegrationContext } from './ObserveRouterIntegrationProvider';
|
|
3
4
|
import { isInitialized } from './init';
|
|
4
5
|
import { optionalRouter } from './router';
|
|
5
6
|
export function useObserveForRouter() {
|
|
7
|
+
const storage = use(ObserveRouterIntegrationContext);
|
|
6
8
|
const isMounted = useRef(true);
|
|
7
9
|
const route = optionalRouter?.useRoute();
|
|
8
10
|
const navigation = optionalRouter?.useNavigation();
|
|
9
|
-
const
|
|
11
|
+
const routeInfo = optionalRouter?.useCurrentRouteInfo();
|
|
12
|
+
const { pathname, params: routeParams } = routeInfo ?? {};
|
|
10
13
|
const initializedAtMount = useRef(isInitialized());
|
|
11
14
|
if (initializedAtMount.current !== isInitialized()) {
|
|
12
15
|
throw new Error("[expo-observe] Router integration was toggled during a screen's lifecycle. " +
|
|
@@ -28,6 +31,8 @@ export function useObserveForRouter() {
|
|
|
28
31
|
};
|
|
29
32
|
}, []);
|
|
30
33
|
const markInteractive = useCallback(async (attributes) => {
|
|
34
|
+
const now = performance.now();
|
|
35
|
+
const timestamp = new Date().toISOString();
|
|
31
36
|
if (!isMounted.current) {
|
|
32
37
|
console.warn('[expo-observe] Calling markInteractive on unmounted screen');
|
|
33
38
|
return;
|
|
@@ -42,7 +47,45 @@ export function useObserveForRouter() {
|
|
|
42
47
|
routeName: pathname,
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
|
-
|
|
50
|
+
if (!storage) {
|
|
51
|
+
throw new Error('[expo-observe] markInteractive was called without an active ObserveProvider. Wrap your app in ObserveRoot from expo-observe.');
|
|
52
|
+
}
|
|
53
|
+
// Snapshot times BEFORE writing the new interactive timestamp so the
|
|
54
|
+
// duplicate-detection logic below sees the *previous* call, not this one.
|
|
55
|
+
const currentScreenData = storage.screenTimes[screenId];
|
|
56
|
+
storage.interactiveScreensIds.add(screenId);
|
|
57
|
+
if (storage.screenTimes[screenId]) {
|
|
58
|
+
storage.screenTimes[screenId] = {
|
|
59
|
+
...storage.screenTimes[screenId],
|
|
60
|
+
lastInteractiveCall: now,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (!currentScreenData?.dispatchTime)
|
|
64
|
+
return;
|
|
65
|
+
const previousInteractiveCall = currentScreenData.lastInteractiveCall;
|
|
66
|
+
const previousWasAfterDispatch = previousInteractiveCall != null && currentScreenData.dispatchTime < previousInteractiveCall;
|
|
67
|
+
if (previousWasAfterDispatch) {
|
|
68
|
+
// We only want to record interactive once per navigation
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Stored in seconds to match the OTel `unit = "s"` convention
|
|
72
|
+
const interactiveTimeSeconds = (now - currentScreenData.dispatchTime) / 1000;
|
|
73
|
+
const mainSessionId = (await AppMetrics.getMainSession())?.id;
|
|
74
|
+
// TODO(@ubax): we should count the time against the action which caused the first navigation
|
|
75
|
+
// and add a param stating if during that time there was any navigation
|
|
76
|
+
if (mainSessionId) {
|
|
77
|
+
await AppMetrics.addCustomMetricToSession({
|
|
78
|
+
sessionId: mainSessionId,
|
|
79
|
+
timestamp,
|
|
80
|
+
category: 'navigation',
|
|
81
|
+
// TODO(@ubax): Use segments.join here to get full routeName and pass pathname and params via params
|
|
82
|
+
routeName: pathname,
|
|
83
|
+
name: 'tti',
|
|
84
|
+
value: interactiveTimeSeconds,
|
|
85
|
+
params: { routeParams },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}, [screenId, navigation, pathname, storage, routeParams]);
|
|
46
89
|
return initializedAtMount.current ? markInteractive : null;
|
|
47
90
|
}
|
|
48
91
|
//# sourceMappingURL=useObserveForRouter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObserveForRouter.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,EAAE,EAAyB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useObserveForRouter.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,EAAE,EAAyB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,+BAA+B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAI1C,MAAM,UAAU,mBAAmB;IACjC,MAAM,OAAO,GAAG,GAAG,CAAC,+BAA+B,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,cAAc,EAAE,QAAQ,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,cAAc,EAAE,aAAa,EAAE,CAAC;IACnD,MAAM,SAAS,GAAG,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACxD,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,IAAI,EAAE,CAAC;IAE1D,MAAM,kBAAkB,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;IACnD,IAAI,kBAAkB,CAAC,OAAO,KAAK,aAAa,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,6EAA6E;YAC3E,sGAAsG,CACzG,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,CAAC;IAC5B,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,YAAY,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CACV,2FAA2F,CAC5F,CAAC;QACF,YAAY,CAAC,OAAO,GAAG,QAAQ,CAAC;IAClC,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,4EAA4E;QAC5E,8EAA8E;QAC9E,gEAAgE;QAChE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QACzB,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,OAAO,GAAG,KAAK,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CACjC,KAAK,EAAE,UAA6B,EAAE,EAAE;QACtC,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;YAC3E,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CACV,sHAAsH,CACvH,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,UAAU,EAAE,SAAS,EAAE,EAAE,CAAC;YAC5B,UAAU,CAAC,eAAe,CAAC;gBACzB,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;gBACrB,SAAS,EAAE,QAAQ;aACpB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,8HAA8H,CAC/H,CAAC;QACJ,CAAC;QAED,qEAAqE;QACrE,0EAA0E;QAC1E,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAExD,OAAO,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG;gBAC9B,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC;gBAChC,mBAAmB,EAAE,GAAG;aACzB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,iBAAiB,EAAE,YAAY;YAAE,OAAO;QAE7C,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,mBAAmB,CAAC;QACtE,MAAM,wBAAwB,GAC5B,uBAAuB,IAAI,IAAI,IAAI,iBAAiB,CAAC,YAAY,GAAG,uBAAuB,CAAC;QAE9F,IAAI,wBAAwB,EAAE,CAAC;YAC7B,yDAAyD;YACzD,OAAO;QACT,CAAC;QAED,8DAA8D;QAC9D,MAAM,sBAAsB,GAAG,CAAC,GAAG,GAAG,iBAAiB,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;QAC7E,MAAM,aAAa,GAAG,CAAC,MAAM,UAAU,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9D,6FAA6F;QAC7F,uEAAuE;QACvE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,UAAU,CAAC,wBAAwB,CAAC;gBACxC,SAAS,EAAE,aAAa;gBACxB,SAAS;gBACT,QAAQ,EAAE,YAAY;gBACtB,oGAAoG;gBACpG,SAAS,EAAE,QAAQ;gBACnB,IAAI,EAAE,KAAK;gBACX,KAAK,EAAE,sBAAsB;gBAC7B,MAAM,EAAE,EAAE,WAAW,EAAE;aACxB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,EACD,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,CAAC,CACvD,CAAC;IAEF,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC","sourcesContent":["import AppMetrics, { type MetricAttributes } from 'expo-app-metrics';\nimport { use, useCallback, useEffect, useRef } from 'react';\n\nimport { ObserveRouterIntegrationContext } from './ObserveRouterIntegrationProvider';\nimport { isInitialized } from './init';\nimport { optionalRouter } from './router';\n\ntype MarkInteractive = (typeof AppMetrics)['markInteractive'];\n\nexport function useObserveForRouter(): MarkInteractive | null {\n const storage = use(ObserveRouterIntegrationContext);\n const isMounted = useRef(true);\n const route = optionalRouter?.useRoute();\n const navigation = optionalRouter?.useNavigation();\n const routeInfo = optionalRouter?.useCurrentRouteInfo();\n const { pathname, params: routeParams } = routeInfo ?? {};\n\n const initializedAtMount = useRef(isInitialized());\n if (initializedAtMount.current !== isInitialized()) {\n throw new Error(\n \"[expo-observe] Router integration was toggled during a screen's lifecycle. \" +\n 'Call `ExpoObserve.configure({ disableRouterIntegration })` once at startup before any screen mounts.'\n );\n }\n\n const screenId = route?.key;\n const prevScreenId = useRef(screenId);\n if (prevScreenId.current !== screenId) {\n console.warn(\n '[expo-observe] Screen ID changed between renders. This is most likely an expo-router bug.'\n );\n prevScreenId.current = screenId;\n }\n\n useEffect(() => {\n // Strict-mode mounts the effect twice (mount → cleanup → re-mount). Without\n // restoring isMounted here, the second mount would leave it permanently false\n // and every markInteractive call would warn \"unmounted screen\".\n isMounted.current = true;\n return () => {\n isMounted.current = false;\n };\n }, []);\n\n const markInteractive = useCallback(\n async (attributes?: MetricAttributes) => {\n const now = performance.now();\n const timestamp = new Date().toISOString();\n if (!isMounted.current) {\n console.warn('[expo-observe] Calling markInteractive on unmounted screen');\n return;\n }\n if (!screenId) {\n console.warn(\n '[expo-observe] No metadata available for the current screen. Make sure to call useObserve inside a screen component.'\n );\n return;\n }\n if (navigation?.isFocused()) {\n AppMetrics.markInteractive({\n ...(attributes ?? {}),\n routeName: pathname,\n });\n }\n\n if (!storage) {\n throw new Error(\n '[expo-observe] markInteractive was called without an active ObserveProvider. Wrap your app in ObserveRoot from expo-observe.'\n );\n }\n\n // Snapshot times BEFORE writing the new interactive timestamp so the\n // duplicate-detection logic below sees the *previous* call, not this one.\n const currentScreenData = storage.screenTimes[screenId];\n\n storage.interactiveScreensIds.add(screenId);\n if (storage.screenTimes[screenId]) {\n storage.screenTimes[screenId] = {\n ...storage.screenTimes[screenId],\n lastInteractiveCall: now,\n };\n }\n\n if (!currentScreenData?.dispatchTime) return;\n\n const previousInteractiveCall = currentScreenData.lastInteractiveCall;\n const previousWasAfterDispatch =\n previousInteractiveCall != null && currentScreenData.dispatchTime < previousInteractiveCall;\n\n if (previousWasAfterDispatch) {\n // We only want to record interactive once per navigation\n return;\n }\n\n // Stored in seconds to match the OTel `unit = \"s\"` convention\n const interactiveTimeSeconds = (now - currentScreenData.dispatchTime) / 1000;\n const mainSessionId = (await AppMetrics.getMainSession())?.id;\n // TODO(@ubax): we should count the time against the action which caused the first navigation\n // and add a param stating if during that time there was any navigation\n if (mainSessionId) {\n await AppMetrics.addCustomMetricToSession({\n sessionId: mainSessionId,\n timestamp,\n category: 'navigation',\n // TODO(@ubax): Use segments.join here to get full routeName and pass pathname and params via params\n routeName: pathname,\n name: 'tti',\n value: interactiveTimeSeconds,\n params: { routeParams },\n });\n }\n },\n [screenId, navigation, pathname, storage, routeParams]\n );\n\n return initializedAtMount.current ? markInteractive : null;\n}\n"]}
|
package/expo-module.config.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright 2025-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoAppMetrics
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
Resets a dispatch cursor to `-1` if it has fallen past the largest id currently in its source
|
|
7
|
+
table. The cursors live in UserDefaults; their source tables can be wiped from underneath them
|
|
8
|
+
(notably on a schema-version mismatch in `expo-app-metrics`). Without this check the cursor would
|
|
9
|
+
skip every new row until enough accumulated to pass the stale value.
|
|
10
|
+
|
|
11
|
+
- `signalName`: short human-readable label ("metric" / "log") for log messages.
|
|
12
|
+
- `readCursor`: returns the persisted cursor value.
|
|
13
|
+
- `writeCursor`: persists a new cursor value.
|
|
14
|
+
- `readMaxId`: returns the largest id in the source table, or nil when empty.
|
|
15
|
+
*/
|
|
16
|
+
@AppMetricsActor
|
|
17
|
+
internal func repairCursorIfStale(
|
|
18
|
+
signalName: String,
|
|
19
|
+
readCursor: () -> Int64,
|
|
20
|
+
writeCursor: (Int64) -> Void,
|
|
21
|
+
readMaxId: () throws -> Int64?
|
|
22
|
+
) {
|
|
23
|
+
let cursor = readCursor()
|
|
24
|
+
let maxId: Int64?
|
|
25
|
+
do {
|
|
26
|
+
maxId = try readMaxId()
|
|
27
|
+
} catch {
|
|
28
|
+
observeLogger.warn("[Observe] Failed to read max \(signalName) id while repairing cursor: \(error.localizedDescription)")
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
if cursor > (maxId ?? -1) {
|
|
32
|
+
observeLogger.info("[Observe] Resetting stale \(signalName) dispatch cursor (was \(cursor), max id is \(maxId.map(String.init) ?? "<empty>"))")
|
|
33
|
+
writeCursor(-1)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@AppMetricsActor
|
|
38
|
+
internal func repairMetricCursorIfStale() {
|
|
39
|
+
repairCursorIfStale(
|
|
40
|
+
signalName: "metric",
|
|
41
|
+
readCursor: { ObserveUserDefaults.lastDispatchedMetricId },
|
|
42
|
+
writeCursor: { ObserveUserDefaults.lastDispatchedMetricId = $0 },
|
|
43
|
+
readMaxId: { try AppMetrics.getMaxMetricId() }
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@AppMetricsActor
|
|
48
|
+
internal func repairLogCursorIfStale() {
|
|
49
|
+
repairCursorIfStale(
|
|
50
|
+
signalName: "log",
|
|
51
|
+
readCursor: { ObserveUserDefaults.lastDispatchedLogId },
|
|
52
|
+
writeCursor: { ObserveUserDefaults.lastDispatchedLogId = $0 },
|
|
53
|
+
readMaxId: { try AppMetrics.getMaxLogId() }
|
|
54
|
+
)
|
|
55
|
+
}
|
package/ios/Event.swift
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import ExpoAppMetrics
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
import Foundation
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
An object representing an event providing some app metrics and the information about the app and the device.
|
|
@@ -64,58 +66,72 @@ struct Event: Codable, Sendable {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
67
|
-
|
|
69
|
+
Builds an `Event` from a session row plus its metric/log batch. The session row carries all the
|
|
70
|
+
metadata that used to live on `Entry`/`AppInfo`/`DeviceInfo`; metrics and logs are passed
|
|
71
|
+
separately so a partial dispatch (only the rows past a cursor) can still produce a valid event.
|
|
68
72
|
*/
|
|
69
|
-
static func
|
|
73
|
+
static func from(session: SessionRow, metrics: [MetricRow], logs: [LogRow]) -> Event {
|
|
74
|
+
let updatesInfo = AppInfo.UpdatesInfo(
|
|
75
|
+
updateId: session.appUpdateId,
|
|
76
|
+
runtimeVersion: session.appUpdateRuntimeVersion,
|
|
77
|
+
requestHeaders: decodeRequestHeaders(session.appUpdateRequestHeaders)
|
|
78
|
+
)
|
|
70
79
|
return Event(
|
|
71
80
|
metadata: Metadata(
|
|
72
|
-
appName:
|
|
73
|
-
appIdentifier:
|
|
74
|
-
appVersion:
|
|
75
|
-
appBuildNumber:
|
|
76
|
-
appEasBuildId:
|
|
77
|
-
appUpdatesInfo:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
reactNativeVersion: app.reactNativeVersion,
|
|
85
|
-
expoSdkVersion: app.expoSdkVersion,
|
|
81
|
+
appName: session.appName,
|
|
82
|
+
appIdentifier: session.appIdentifier,
|
|
83
|
+
appVersion: session.appVersion,
|
|
84
|
+
appBuildNumber: session.appBuildNumber,
|
|
85
|
+
appEasBuildId: session.appEasBuildId,
|
|
86
|
+
appUpdatesInfo: updatesInfo.isEmpty ? nil : updatesInfo,
|
|
87
|
+
deviceName: session.deviceName ?? "",
|
|
88
|
+
deviceModel: session.deviceModel ?? "",
|
|
89
|
+
deviceOs: session.deviceOs ?? "",
|
|
90
|
+
deviceOsVersion: session.deviceOsVersion ?? "",
|
|
91
|
+
reactNativeVersion: session.reactNativeVersion ?? "",
|
|
92
|
+
expoSdkVersion: session.expoSdkVersion ?? "",
|
|
86
93
|
clientVersion: ObserveVersions.clientVersion,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
environment: environment
|
|
94
|
+
languageTag: session.languageTag ?? Locale.preferredLanguages.first ?? "en-US",
|
|
95
|
+
environment: session.environment
|
|
90
96
|
),
|
|
91
|
-
metrics:
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
}
|
|
97
|
+
metrics: metrics.map { metric in
|
|
98
|
+
return Metric(
|
|
99
|
+
category: metric.category,
|
|
100
|
+
name: metric.name,
|
|
101
|
+
value: metric.value,
|
|
102
|
+
timestamp: metric.timestamp,
|
|
103
|
+
sessionId: metric.sessionId,
|
|
104
|
+
parentSessionId: nil,
|
|
105
|
+
routeName: metric.routeName,
|
|
106
|
+
updateId: metric.updateId,
|
|
107
|
+
customParams: decodeCustomParams(metric.params)
|
|
108
|
+
)
|
|
105
109
|
},
|
|
106
|
-
logs:
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
}
|
|
110
|
+
logs: logs.map { log in
|
|
111
|
+
return Log(
|
|
112
|
+
name: log.name,
|
|
113
|
+
body: log.body,
|
|
114
|
+
timestamp: log.timestamp,
|
|
115
|
+
severity: Severity(rawValue: log.severity) ?? .info,
|
|
116
|
+
attributes: decodeCustomParams(log.attributes),
|
|
117
|
+
droppedAttributesCount: log.droppedAttributesCount,
|
|
118
|
+
sessionId: log.sessionId
|
|
119
|
+
)
|
|
118
120
|
}
|
|
119
121
|
)
|
|
120
122
|
}
|
|
121
123
|
}
|
|
124
|
+
|
|
125
|
+
private func decodeRequestHeaders(_ json: String?) -> [String: String]? {
|
|
126
|
+
guard let json, let data = json.data(using: .utf8) else {
|
|
127
|
+
return nil
|
|
128
|
+
}
|
|
129
|
+
return try? JSONSerialization.jsonObject(with: data) as? [String: String]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func decodeCustomParams(_ json: String?) -> AnyCodable? {
|
|
133
|
+
guard let json, let data = json.data(using: .utf8) else {
|
|
134
|
+
return nil
|
|
135
|
+
}
|
|
136
|
+
return try? JSONDecoder().decode(AnyCodable.self, from: data)
|
|
137
|
+
}
|