expo-observe 58.0.13 → 58.0.15

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/build/integrations/expo-router/ObserveRouterIntegrationProvider.js +1 -1
  3. package/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map +1 -1
  4. package/build/integrations/expo-router/init.d.ts +2 -4
  5. package/build/integrations/expo-router/init.js +48 -29
  6. package/build/integrations/expo-router/init.js.map +1 -1
  7. package/build/integrations/expo-router/router.d.ts +30 -11
  8. package/build/integrations/expo-router/router.js.map +1 -1
  9. package/build/integrations/expo-router/storage.d.ts +5 -1
  10. package/build/integrations/expo-router/storage.js +1 -0
  11. package/build/integrations/expo-router/storage.js.map +1 -1
  12. package/local-maven-repo/expo/modules/observe/expo.modules.observe/{58.0.13/expo.modules.observe-58.0.13-sources.jar → 58.0.15/expo.modules.observe-58.0.15-sources.jar} +0 -0
  13. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15-sources.jar.md5 +1 -0
  14. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15-sources.jar.sha1 +1 -0
  15. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15-sources.jar.sha256 +1 -0
  16. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15-sources.jar.sha512 +1 -0
  17. package/local-maven-repo/expo/modules/observe/expo.modules.observe/{58.0.13/expo.modules.observe-58.0.13.aar → 58.0.15/expo.modules.observe-58.0.15.aar} +0 -0
  18. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.aar.md5 +1 -0
  19. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.aar.sha1 +1 -0
  20. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.aar.sha256 +1 -0
  21. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.aar.sha512 +1 -0
  22. package/local-maven-repo/expo/modules/observe/expo.modules.observe/{58.0.13/expo.modules.observe-58.0.13.module → 58.0.15/expo.modules.observe-58.0.15.module} +22 -22
  23. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.module.md5 +1 -0
  24. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.module.sha1 +1 -0
  25. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.module.sha256 +1 -0
  26. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.module.sha512 +1 -0
  27. package/local-maven-repo/expo/modules/observe/expo.modules.observe/{58.0.13/expo.modules.observe-58.0.13.pom → 58.0.15/expo.modules.observe-58.0.15.pom} +2 -2
  28. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.pom.md5 +1 -0
  29. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.pom.sha1 +1 -0
  30. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.pom.sha256 +1 -0
  31. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.15/expo.modules.observe-58.0.15.pom.sha512 +1 -0
  32. package/package.json +4 -4
  33. package/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx +1 -1
  34. package/src/integrations/expo-router/init.ts +54 -34
  35. package/src/integrations/expo-router/router.ts +27 -17
  36. package/src/integrations/expo-router/storage.ts +7 -1
  37. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13-sources.jar.md5 +0 -1
  38. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13-sources.jar.sha1 +0 -1
  39. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13-sources.jar.sha256 +0 -1
  40. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13-sources.jar.sha512 +0 -1
  41. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.aar.md5 +0 -1
  42. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.aar.sha1 +0 -1
  43. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.aar.sha256 +0 -1
  44. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.aar.sha512 +0 -1
  45. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.module.md5 +0 -1
  46. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.module.sha1 +0 -1
  47. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.module.sha256 +0 -1
  48. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.module.sha512 +0 -1
  49. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.pom.md5 +0 -1
  50. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.pom.sha1 +0 -1
  51. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.pom.sha256 +0 -1
  52. package/local-maven-repo/expo/modules/observe/expo.modules.observe/58.0.13/expo.modules.observe-58.0.13.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 58.0.15
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies. ([#51609](https://github.com/expo/expo/pull/51609))
8
+ - expo-app-metrics@58.0.13
9
+
10
+ ## 58.0.14
11
+
12
+ ### Patch Changes
13
+
14
+ - Adjust router integration to use `unstable_PerformanceObserver` instead of `unstable_navigationEvents` ([#51068](https://github.com/expo/expo/pull/51068) by [@Ubax](https://github.com/Ubax))
15
+ - Measure Expo Router navigation TTR and TTI from when the navigation action is queued. ([#51485](https://github.com/expo/expo/pull/51485) by [@Ubax](https://github.com/Ubax))
16
+ - Updated dependencies. ([#51538](https://github.com/expo/expo/pull/51538))
17
+ - expo-app-metrics@58.0.12
18
+
3
19
  ## 58.0.13
4
20
 
5
21
  ### Patch Changes
@@ -8,7 +8,7 @@ export function ObserveRouterIntegrationProvider({ children }) {
8
8
  const [storage] = useState(()=>isInitialized() ? createRouterIntegrationStorage() : null);
9
9
  const [listenersCleanup] = useState(()=>{
10
10
  if (!storage || !optionalRouter) return;
11
- return initListeners(storage, optionalRouter.unstable_navigationEvents);
11
+ return initListeners(storage, optionalRouter.unstable_PerformanceObserver);
12
12
  });
13
13
  const prevInitialized = useRef(isInitialized());
14
14
  if (prevInitialized.current !== isInitialized()) {
@@ -1 +1 @@
1
- {"version":3,"sources":["integrations/expo-router/ObserveRouterIntegrationProvider.tsx"],"sourcesContent":[null],"names":["createContext","useEffect","useRef","useState","initListeners","isInitialized","optionalRouter","createRouterIntegrationStorage","ObserveRouterIntegrationContext","ObserveRouterIntegrationProvider","children","storage","listenersCleanup","unstable_navigationEvents","prevInitialized","current","Error","Provider","value"],"mappings":";AAAA,SAASA,aAAa,EAA0BC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAE3F,SAASC,aAAa,EAAEC,aAAa,QAAQ,SAAS;AACtD,SAASC,cAAc,QAAQ,WAAW;AAC1C,SAASC,8BAA8B,QAAuC,YAAY;AAE1F,OAAO,MAAMC,gDAAkCR,cAA+C,MAAM;AAEpG,OAAO,SAASS,iCAAiC,EAAEC,QAAQ,EAAqB;IAC9E,MAAM,CAACC,QAAQ,GAAGR,SAA0C,IAC1DE,kBAAkBE,mCAAmC;IAEvD,MAAM,CAACK,iBAAiB,GAAGT,SAAS;QAClC,IAAI,CAACQ,WAAW,CAACL,gBAAgB;QACjC,OAAOF,cAAcO,SAASL,eAAeO,yBAAyB;IACxE;IAEA,MAAMC,kBAAkBZ,OAAOG;IAC/B,IAAIS,gBAAgBC,OAAO,KAAKV,iBAAiB;QAC/C,MAAM,IAAIW,MACR,CAAC,sCAAsC,EAAEX,kBAAkB,YAAY,WAAW,oFAAoF,CAAC;IAE3K;IAEAJ,UAAU,IAAMW,kBAAkB;QAACA;KAAiB;IAEpD,qBACE,KAACJ,gCAAgCS,QAAQ;QAACC,OAAOP;kBAC9CD;;AAGP"}
1
+ {"version":3,"sources":["integrations/expo-router/ObserveRouterIntegrationProvider.tsx"],"sourcesContent":[null],"names":["createContext","useEffect","useRef","useState","initListeners","isInitialized","optionalRouter","createRouterIntegrationStorage","ObserveRouterIntegrationContext","ObserveRouterIntegrationProvider","children","storage","listenersCleanup","unstable_PerformanceObserver","prevInitialized","current","Error","Provider","value"],"mappings":";AAAA,SAASA,aAAa,EAA0BC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAE3F,SAASC,aAAa,EAAEC,aAAa,QAAQ,SAAS;AACtD,SAASC,cAAc,QAAQ,WAAW;AAC1C,SAASC,8BAA8B,QAAuC,YAAY;AAE1F,OAAO,MAAMC,gDAAkCR,cAA+C,MAAM;AAEpG,OAAO,SAASS,iCAAiC,EAAEC,QAAQ,EAAqB;IAC9E,MAAM,CAACC,QAAQ,GAAGR,SAA0C,IAC1DE,kBAAkBE,mCAAmC;IAEvD,MAAM,CAACK,iBAAiB,GAAGT,SAAS;QAClC,IAAI,CAACQ,WAAW,CAACL,gBAAgB;QACjC,OAAOF,cAAcO,SAASL,eAAeO,4BAA4B;IAC3E;IAEA,MAAMC,kBAAkBZ,OAAOG;IAC/B,IAAIS,gBAAgBC,OAAO,KAAKV,iBAAiB;QAC/C,MAAM,IAAIW,MACR,CAAC,sCAAsC,EAAEX,kBAAkB,YAAY,WAAW,oFAAoF,CAAC;IAE3K;IAEAJ,UAAU,IAAMW,kBAAkB;QAACA;KAAiB;IAEpD,qBACE,KAACJ,gCAAgCS,QAAQ;QAACC,OAAOP;kBAC9CD;;AAGP"}
@@ -1,9 +1,7 @@
1
1
  import type { ObserveIntegrationsConfig } from '../../types';
2
- import { optionalRouter } from './router';
2
+ import { type RouterPerformanceObserver } from './router';
3
3
  import { type RouterIntegrationStorage } from './storage';
4
4
  export declare const isInitialized: () => boolean;
5
5
  export declare const getRouterIntegrationConfig: () => boolean | import("../..").ObserveNavigationIntegrationConfig | undefined;
6
6
  export declare function initRouterIntegration(config?: ObserveIntegrationsConfig['expo-router']): void;
7
- type NavigationEvents = NonNullable<typeof optionalRouter>['unstable_navigationEvents'];
8
- export declare function initListeners(storage: RouterIntegrationStorage, navigationEvents: NavigationEvents): () => void;
9
- export {};
7
+ export declare function initListeners(storage: RouterIntegrationStorage, PerformanceObserver: RouterPerformanceObserver): () => void;
@@ -7,6 +7,7 @@ import { optionalRouter } from './router';
7
7
  // web bundle doesn't pull in `expo-app-metrics`' native bridge calls. The web
8
8
  // version should be an explicit no-op (return a noop cleanup) rather than
9
9
  // relying on the web stubs in `expo-app-metrics/module.web.ts`.
10
+ const MAX_ENQUEUE_TIMES = 100;
10
11
  let initialized = false;
11
12
  let routerIntegrationConfig;
12
13
  export const isInitialized = ()=>initialized;
@@ -14,32 +15,54 @@ export const getRouterIntegrationConfig = ()=>routerIntegrationConfig;
14
15
  export function initRouterIntegration(config) {
15
16
  initialized = true;
16
17
  routerIntegrationConfig = config;
17
- optionalRouter?.unstable_navigationEvents.enable();
18
+ optionalRouter?.unstable_enablePerformanceIntegration();
18
19
  }
19
- export function initListeners(storage, navigationEvents) {
20
+ export function initListeners(storage, PerformanceObserver) {
20
21
  const appLaunchTime = performance.now();
21
- const cleanup = new Set();
22
- const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event)=>{
23
- // PRELOAD comes from router.prefetch() — a route warm-up, not a user
24
- // navigation — so it must not seed dispatchTime.
25
- if (event.actionType === 'PRELOAD') return;
26
- storage.pendingActions.push({
27
- actionType: event.actionType,
28
- dispatchTime: performance.now()
29
- });
22
+ const observer = new PerformanceObserver((list)=>{
23
+ for (const performanceEntry of list.getEntries()){
24
+ // Marks with the names handled below always have the matching `detail`.
25
+ const entry = performanceEntry;
26
+ switch(entry.name){
27
+ case 'expo-router:action-enqueued':
28
+ storage.enqueueTimes.set(entry.detail.actionId, entry.startTime);
29
+ // Ids grow and the map keeps insertion order, so the first key is the oldest. The cap
30
+ // bounds the map, because some actions never dispatch.
31
+ if (storage.enqueueTimes.size > MAX_ENQUEUE_TIMES) {
32
+ storage.enqueueTimes.delete(storage.enqueueTimes.keys().next().value);
33
+ }
34
+ break;
35
+ case 'expo-router:action-dispatched':
36
+ {
37
+ // Look up by id so an action that never dispatched can't become the TTR start.
38
+ const { actionId } = entry.detail;
39
+ const enqueueTime = actionId === undefined ? undefined : storage.enqueueTimes.get(actionId);
40
+ // PRELOAD comes from router.prefetch() — a route warm-up, not a user
41
+ // navigation — so it must not become a TTR start.
42
+ if (entry.detail.actionType === 'PRELOAD') break;
43
+ storage.pendingActions.push({
44
+ actionType: entry.detail.actionType,
45
+ startTime: enqueueTime ?? entry.startTime
46
+ });
47
+ break;
48
+ }
49
+ case 'expo-router:page-preloaded':
50
+ // The screen rendered as part of a preload. Mark it as already rendered so
51
+ // the eventual page focus resolves to `warm_ttr` rather than `cold_ttr`.
52
+ storage.renderedScreensIds.add(entry.detail.screenId);
53
+ break;
54
+ case 'expo-router:page-focused':
55
+ onPageFocused(entry.detail, entry.startTime);
56
+ break;
57
+ }
58
+ }
30
59
  });
31
- cleanup.add(unsubscribeAction);
32
- const unsubscribePreload = navigationEvents.addListener('pagePreloaded', (e)=>{
33
- // The screen rendered as part of a preload. Mark it as already rendered so
34
- // the eventual `pageFocused` resolves to `warm_ttr` rather than `cold_ttr`.
35
- storage.renderedScreensIds.add(e.screenId);
60
+ observer.observe({
61
+ type: 'mark'
36
62
  });
37
- cleanup.add(unsubscribePreload);
38
- const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e)=>{
39
- // Snapshot both clocks once so every metric written below is stamped with
40
- // the moment the focus event fired, not the moment `addMetric` happens to run
41
- // after the surrounding async work.
42
- const now = performance.now();
63
+ async function onPageFocused(e, now) {
64
+ // Stamp every metric written below with the moment of the focus mark, not
65
+ // the moment `addMetric` happens to run after the surrounding async work.
43
66
  const timestamp = new Date().toISOString();
44
67
  // Snapshot BEFORE seeding dispatchTime below so the deferred-TTI check
45
68
  // sees the pre-focus state of this screen. A pending interactive means
@@ -61,7 +84,7 @@ export function initListeners(storage, navigationEvents) {
61
84
  } else {
62
85
  const last = storage.pendingActions[storage.pendingActions.length - 1];
63
86
  if (!last) return;
64
- dispatchTime = last.dispatchTime;
87
+ dispatchTime = last.startTime;
65
88
  isAppLaunch = false;
66
89
  storage.pendingActions.length = 0;
67
90
  }
@@ -108,12 +131,8 @@ export function initListeners(storage, navigationEvents) {
108
131
  config: routerIntegrationConfig
109
132
  });
110
133
  }
111
- });
112
- cleanup.add(unsubscribeFocus);
113
- return ()=>{
114
- cleanup.forEach((c)=>c());
115
- cleanup.clear();
116
- };
134
+ }
135
+ return ()=>observer.disconnect();
117
136
  }
118
137
 
119
138
  //# sourceMappingURL=init.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["integrations/expo-router/init.ts"],"sourcesContent":[null],"names":["AppMetrics","getNavigationMetricParams","emitTTI","buildRoutePattern","optionalRouter","initialized","routerIntegrationConfig","isInitialized","getRouterIntegrationConfig","initRouterIntegration","config","unstable_navigationEvents","enable","initListeners","storage","navigationEvents","appLaunchTime","performance","now","cleanup","Set","unsubscribeAction","addListener","event","actionType","pendingActions","push","dispatchTime","add","unsubscribePreload","e","renderedScreensIds","screenId","unsubscribeFocus","timestamp","Date","toISOString","existingFocusedScreenTimes","screenTimes","hasPendingInteractive","lastInteractiveCall","isInitial","has","name","routePattern","segments","isAppLaunch","hasRecordedInitialTtr","last","length","ttrSeconds","interactiveScreensIds","mainSession","getMainSession","navigationParams","params","pathname","addMetric","category","routeName","value","session","routeParams","url","forEach","c","clear"],"mappings":"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"}
1
+ {"version":3,"sources":["integrations/expo-router/init.ts"],"sourcesContent":[null],"names":["AppMetrics","getNavigationMetricParams","emitTTI","buildRoutePattern","optionalRouter","MAX_ENQUEUE_TIMES","initialized","routerIntegrationConfig","isInitialized","getRouterIntegrationConfig","initRouterIntegration","config","unstable_enablePerformanceIntegration","initListeners","storage","PerformanceObserver","appLaunchTime","performance","now","observer","list","performanceEntry","getEntries","entry","name","enqueueTimes","set","detail","actionId","startTime","size","delete","keys","next","value","enqueueTime","undefined","get","actionType","pendingActions","push","renderedScreensIds","add","screenId","onPageFocused","observe","type","e","timestamp","Date","toISOString","existingFocusedScreenTimes","screenTimes","hasPendingInteractive","lastInteractiveCall","dispatchTime","isInitial","has","routePattern","segments","isAppLaunch","hasRecordedInitialTtr","last","length","ttrSeconds","interactiveScreensIds","mainSession","getMainSession","navigationParams","params","pathname","addMetric","category","routeName","session","routeParams","url","disconnect"],"mappings":"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"}
@@ -1,19 +1,38 @@
1
- interface RouterPageEvent {
1
+ export interface RouterPageMarkDetail {
2
2
  pathname: string;
3
- params: Record<string, string | string[]>;
3
+ params: Record<string, string | string[] | undefined>;
4
4
  screenId: string;
5
5
  segments: string[];
6
6
  }
7
- interface RouterActionDispatchedEvent {
8
- actionType: string;
9
- }
10
- interface RouterNavigationEvents {
11
- addListener(eventType: 'actionDispatched', callback: (event: RouterActionDispatchedEvent) => void): () => void;
12
- addListener(eventType: 'pagePreloaded' | 'pageFocused', callback: (event: RouterPageEvent) => void): () => void;
13
- enable(): void;
14
- }
7
+ export type RouterPerformanceMark = {
8
+ name: 'expo-router:action-enqueued';
9
+ startTime: number;
10
+ detail: {
11
+ actionId: number;
12
+ };
13
+ } | {
14
+ name: 'expo-router:action-dispatched';
15
+ startTime: number;
16
+ detail: {
17
+ actionType: string;
18
+ actionId?: number;
19
+ };
20
+ } | {
21
+ name: 'expo-router:page-preloaded' | 'expo-router:page-focused' | 'expo-router:page-blurred' | 'expo-router:page-removed';
22
+ startTime: number;
23
+ detail: RouterPageMarkDetail;
24
+ };
25
+ export type RouterPerformanceObserver = new (callback: (list: {
26
+ getEntries(): Pick<PerformanceEntry, 'name' | 'startTime'>[];
27
+ }) => void) => {
28
+ observe(options: {
29
+ type: 'mark';
30
+ }): void;
31
+ disconnect(): void;
32
+ };
15
33
  interface OptionalRouter {
16
- unstable_navigationEvents: RouterNavigationEvents;
34
+ unstable_enablePerformanceIntegration(): void;
35
+ unstable_PerformanceObserver: RouterPerformanceObserver;
17
36
  useCurrentRouteInfo(): {
18
37
  pathname: string;
19
38
  params: Record<string, string | string[]>;
@@ -1 +1 @@
1
- {"version":3,"sources":["integrations/expo-router/router.ts"],"sourcesContent":[null],"names":["optionalRouter","require","isRouterInstalled"],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAmC7E,IAAIA;AACJ,IAAI;IACFA,iBAAiBC,QAAQ;AAC3B,EAAE,OAAM;AACN,oDAAoD;AACtD;AACA,MAAMC,oBAAoB,CAAC,CAACF;AAE5B,SAASA,cAAc,EAAEE,iBAAiB,GAAG"}
1
+ {"version":3,"sources":["integrations/expo-router/router.ts"],"sourcesContent":[null],"names":["optionalRouter","require","isRouterInstalled"],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AA6C7E,IAAIA;AACJ,IAAI;IACFA,iBAAiBC,QAAQ;AAC3B,EAAE,OAAM;AACN,oDAAoD;AACtD;AACA,MAAMC,oBAAoB,CAAC,CAACF;AAE5B,SAASA,cAAc,EAAEE,iBAAiB,GAAG"}
@@ -5,13 +5,17 @@ export interface ScreenTimes {
5
5
  }
6
6
  export interface PendingAction {
7
7
  actionType: string;
8
- dispatchTime: number;
8
+ startTime: number;
9
9
  }
10
10
  export interface RouterIntegrationStorage {
11
11
  /**
12
12
  * Actions dispatched, but not yet processed by the integration
13
13
  */
14
14
  pendingActions: PendingAction[];
15
+ /**
16
+ * Enqueue times by `actionId`. The oldest id is dropped when the map is full
17
+ */
18
+ enqueueTimes: Map<number, number>;
15
19
  renderedScreensIds: Set<string>;
16
20
  /**
17
21
  * Whether the app had already recorded the first render of the screen
@@ -1,6 +1,7 @@
1
1
  export function createRouterIntegrationStorage() {
2
2
  return {
3
3
  pendingActions: [],
4
+ enqueueTimes: new Map(),
4
5
  renderedScreensIds: new Set(),
5
6
  hasRecordedInitialTtr: false,
6
7
  screenTimes: {},
@@ -1 +1 @@
1
- {"version":3,"sources":["integrations/expo-router/storage.ts"],"sourcesContent":[null],"names":["createRouterIntegrationStorage","pendingActions","renderedScreensIds","Set","hasRecordedInitialTtr","screenTimes","interactiveScreensIds"],"mappings":"AA4BA,OAAO,SAASA;IACd,OAAO;QACLC,gBAAgB,EAAE;QAClBC,oBAAoB,IAAIC;QACxBC,uBAAuB;QACvBC,aAAa,CAAC;QACdC,uBAAuB,IAAIH;IAC7B;AACF"}
1
+ {"version":3,"sources":["integrations/expo-router/storage.ts"],"sourcesContent":[null],"names":["createRouterIntegrationStorage","pendingActions","enqueueTimes","Map","renderedScreensIds","Set","hasRecordedInitialTtr","screenTimes","interactiveScreensIds"],"mappings":"AAiCA,OAAO,SAASA;IACd,OAAO;QACLC,gBAAgB,EAAE;QAClBC,cAAc,IAAIC;QAClBC,oBAAoB,IAAIC;QACxBC,uBAAuB;QACvBC,aAAa,CAAC;QACdC,uBAAuB,IAAIH;IAC7B;AACF"}
@@ -0,0 +1 @@
1
+ c02a8b50f70ece1b1191edd827ad9b52620d889a417165944738528dd66553f7
@@ -0,0 +1 @@
1
+ 7a4bbc7979433522800d4baeb436dbf6a0a3601a5bda49c817816e3713904c42535c35084593e01871e5d1ba19d44379a16cbbaa4000f521bacdd3136d1ef9f0
@@ -0,0 +1 @@
1
+ 05fe1ce8955cfebb5ad5b9248cf9738de2787fd7790e128612d5afa0938545d9
@@ -0,0 +1 @@
1
+ 3b99f79444ac74ffab58045e87c7bf908bd088f33cf806ed911ef3488f82b126ab55c51191045d5363db8b609afe0fd156bcb8998e9c33b3e1331ecba6f9774c
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "expo.modules.observe",
5
5
  "module": "expo.modules.observe",
6
- "version": "58.0.13",
6
+ "version": "58.0.15",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "files": [
26
26
  {
27
- "name": "expo.modules.observe-58.0.13.aar",
28
- "url": "expo.modules.observe-58.0.13.aar",
29
- "size": 302597,
30
- "sha512": "ea11a3c8ff1b0068d999f4903f1efd2a6d95328adc7f6761277fe031fad5bba601076defb05b0f840fae495c7e5783bc1bab9d81bafd96965ec9c898c346e897",
31
- "sha256": "44496217f172c4a1a972ee8cb5e80975f153fbd46df70f71def2369495f9893b",
32
- "sha1": "7551496d19872ae167cac6cc24b148193f7fe5b1",
33
- "md5": "bfe711d63f4b276c8f45c8bfdda55c82"
27
+ "name": "expo.modules.observe-58.0.15.aar",
28
+ "url": "expo.modules.observe-58.0.15.aar",
29
+ "size": 302595,
30
+ "sha512": "3b99f79444ac74ffab58045e87c7bf908bd088f33cf806ed911ef3488f82b126ab55c51191045d5363db8b609afe0fd156bcb8998e9c33b3e1331ecba6f9774c",
31
+ "sha256": "05fe1ce8955cfebb5ad5b9248cf9738de2787fd7790e128612d5afa0938545d9",
32
+ "sha1": "7b75ba665fc7dace594e3ccbe2d3fe6cfab1da98",
33
+ "md5": "7e21fda796d8c5d26e9a5cca8c9b4480"
34
34
  }
35
35
  ]
36
36
  },
@@ -61,7 +61,7 @@
61
61
  "group": "expo.modules.appmetrics",
62
62
  "module": "expo.modules.appmetrics",
63
63
  "version": {
64
- "requires": "58.0.11"
64
+ "requires": "58.0.13"
65
65
  }
66
66
  },
67
67
  {
@@ -120,13 +120,13 @@
120
120
  ],
121
121
  "files": [
122
122
  {
123
- "name": "expo.modules.observe-58.0.13.aar",
124
- "url": "expo.modules.observe-58.0.13.aar",
125
- "size": 302597,
126
- "sha512": "ea11a3c8ff1b0068d999f4903f1efd2a6d95328adc7f6761277fe031fad5bba601076defb05b0f840fae495c7e5783bc1bab9d81bafd96965ec9c898c346e897",
127
- "sha256": "44496217f172c4a1a972ee8cb5e80975f153fbd46df70f71def2369495f9893b",
128
- "sha1": "7551496d19872ae167cac6cc24b148193f7fe5b1",
129
- "md5": "bfe711d63f4b276c8f45c8bfdda55c82"
123
+ "name": "expo.modules.observe-58.0.15.aar",
124
+ "url": "expo.modules.observe-58.0.15.aar",
125
+ "size": 302595,
126
+ "sha512": "3b99f79444ac74ffab58045e87c7bf908bd088f33cf806ed911ef3488f82b126ab55c51191045d5363db8b609afe0fd156bcb8998e9c33b3e1331ecba6f9774c",
127
+ "sha256": "05fe1ce8955cfebb5ad5b9248cf9738de2787fd7790e128612d5afa0938545d9",
128
+ "sha1": "7b75ba665fc7dace594e3ccbe2d3fe6cfab1da98",
129
+ "md5": "7e21fda796d8c5d26e9a5cca8c9b4480"
130
130
  }
131
131
  ]
132
132
  },
@@ -140,13 +140,13 @@
140
140
  },
141
141
  "files": [
142
142
  {
143
- "name": "expo.modules.observe-58.0.13-sources.jar",
144
- "url": "expo.modules.observe-58.0.13-sources.jar",
143
+ "name": "expo.modules.observe-58.0.15-sources.jar",
144
+ "url": "expo.modules.observe-58.0.15-sources.jar",
145
145
  "size": 27883,
146
- "sha512": "932944f468a6cac663389d69143847a72f707f1cef1fe7a1d387720cda2fbce7c7d2f97e2a753db818b551be5775e2a57b87de07e7ee37e259ebe439640d08c2",
147
- "sha256": "d8556490b2358f9a799f840d3f1f58b7c27604bc295b77d2c02a2e56aa1f6cd5",
148
- "sha1": "4b89134bf052a72a4717311b2bcded0ccf0a901c",
149
- "md5": "d1786b64c18bbc1b9d244f7b9e14f38e"
146
+ "sha512": "7a4bbc7979433522800d4baeb436dbf6a0a3601a5bda49c817816e3713904c42535c35084593e01871e5d1ba19d44379a16cbbaa4000f521bacdd3136d1ef9f0",
147
+ "sha256": "c02a8b50f70ece1b1191edd827ad9b52620d889a417165944738528dd66553f7",
148
+ "sha1": "72f7812f7086633b4c5331076e8690489f96dc1a",
149
+ "md5": "a0876aa95376b387ad9ef3eefafe1acc"
150
150
  }
151
151
  ]
152
152
  }
@@ -0,0 +1 @@
1
+ a1d050543088b73d78b20604ab3ae64d564f3556a714605c94435ed09a471f8c
@@ -0,0 +1 @@
1
+ bc29f0255499459175045cccc96a99411856784438ce4c37e7f48c3bc35dd091c29169e8b1eeaae76ccf45773d705f87edbbad533897faf6e6d37c2c6c572775
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>expo.modules.observe</groupId>
11
11
  <artifactId>expo.modules.observe</artifactId>
12
- <version>58.0.13</version>
12
+ <version>58.0.15</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.observe</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -52,7 +52,7 @@
52
52
  <dependency>
53
53
  <groupId>expo.modules.appmetrics</groupId>
54
54
  <artifactId>expo.modules.appmetrics</artifactId>
55
- <version>58.0.11</version>
55
+ <version>58.0.13</version>
56
56
  <scope>runtime</scope>
57
57
  </dependency>
58
58
  <dependency>
@@ -0,0 +1 @@
1
+ f304fb019425adb6b01a2e1525618ac6a799bbefe94c311ed6d0fb14eb63eb31
@@ -0,0 +1 @@
1
+ 96f2bbc77510e35cfcb27f8c57d9db5dd1cc6f009714159696f34adab091cdd516214a79452ad8be99f8549aee6d9138a620ea33278587907328412d325f739b
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-observe",
3
- "version": "58.0.13",
3
+ "version": "58.0.15",
4
4
  "description": "Expo module that dispatches collected app metrics to EAS Observe",
5
5
  "keywords": [
6
6
  "EAS",
@@ -42,15 +42,15 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "expo-app-metrics": "~58.0.11",
45
+ "expo-app-metrics": "~58.0.13",
46
46
  "expo-eas-client": "~58.0.2"
47
47
  },
48
48
  "devDependencies": {
49
+ "@expo/internal-scripts": "58.0.3",
49
50
  "@react-navigation/native": "^7.1.33",
50
51
  "@testing-library/react-native": "^14.0.1",
51
52
  "@types/react": "~19.3.0",
52
- "expo": "58.0.2",
53
- "expo-module-scripts": "56.0.5"
53
+ "expo": "58.0.13"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@react-navigation/native": "^7.0.0",
@@ -12,7 +12,7 @@ export function ObserveRouterIntegrationProvider({ children }: PropsWithChildren
12
12
  );
13
13
  const [listenersCleanup] = useState(() => {
14
14
  if (!storage || !optionalRouter) return;
15
- return initListeners(storage, optionalRouter.unstable_navigationEvents);
15
+ return initListeners(storage, optionalRouter.unstable_PerformanceObserver);
16
16
  });
17
17
 
18
18
  const prevInitialized = useRef(isInitialized());
@@ -4,7 +4,12 @@ import type { ObserveIntegrationsConfig } from '../../types';
4
4
  import { getNavigationMetricParams } from '../navigationConfig';
5
5
  import { emitTTI } from './emitTTI';
6
6
  import { buildRoutePattern } from './routeName';
7
- import { optionalRouter } from './router';
7
+ import {
8
+ optionalRouter,
9
+ type RouterPageMarkDetail,
10
+ type RouterPerformanceMark,
11
+ type RouterPerformanceObserver,
12
+ } from './router';
8
13
  import { type RouterIntegrationStorage } from './storage';
9
14
 
10
15
  // TODO(@ubax): split this module into `.native.ts` / `.web.ts` variants so the
@@ -12,6 +17,8 @@ import { type RouterIntegrationStorage } from './storage';
12
17
  // version should be an explicit no-op (return a noop cleanup) rather than
13
18
  // relying on the web stubs in `expo-app-metrics/module.web.ts`.
14
19
 
20
+ const MAX_ENQUEUE_TIMES = 100;
21
+
15
22
  let initialized = false;
16
23
  let routerIntegrationConfig: ObserveIntegrationsConfig['expo-router'];
17
24
 
@@ -21,41 +28,58 @@ export const getRouterIntegrationConfig = () => routerIntegrationConfig;
21
28
  export function initRouterIntegration(config?: ObserveIntegrationsConfig['expo-router']) {
22
29
  initialized = true;
23
30
  routerIntegrationConfig = config;
24
- optionalRouter?.unstable_navigationEvents.enable();
31
+ optionalRouter?.unstable_enablePerformanceIntegration();
25
32
  }
26
33
 
27
- type NavigationEvents = NonNullable<typeof optionalRouter>['unstable_navigationEvents'];
28
-
29
34
  export function initListeners(
30
35
  storage: RouterIntegrationStorage,
31
- navigationEvents: NavigationEvents
36
+ PerformanceObserver: RouterPerformanceObserver
32
37
  ): () => void {
33
38
  const appLaunchTime = performance.now();
34
- const cleanup = new Set<() => void>();
35
-
36
- const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event) => {
37
- // PRELOAD comes from router.prefetch() — a route warm-up, not a user
38
- // navigation — so it must not seed dispatchTime.
39
- if (event.actionType === 'PRELOAD') return;
40
- storage.pendingActions.push({
41
- actionType: event.actionType,
42
- dispatchTime: performance.now(),
43
- });
44
- });
45
- cleanup.add(unsubscribeAction);
46
39
 
47
- const unsubscribePreload = navigationEvents.addListener('pagePreloaded', (e) => {
48
- // The screen rendered as part of a preload. Mark it as already rendered so
49
- // the eventual `pageFocused` resolves to `warm_ttr` rather than `cold_ttr`.
50
- storage.renderedScreensIds.add(e.screenId);
40
+ const observer = new PerformanceObserver((list) => {
41
+ for (const performanceEntry of list.getEntries()) {
42
+ // Marks with the names handled below always have the matching `detail`.
43
+ const entry = performanceEntry as RouterPerformanceMark;
44
+ switch (entry.name) {
45
+ case 'expo-router:action-enqueued':
46
+ storage.enqueueTimes.set(entry.detail.actionId, entry.startTime);
47
+ // Ids grow and the map keeps insertion order, so the first key is the oldest. The cap
48
+ // bounds the map, because some actions never dispatch.
49
+ if (storage.enqueueTimes.size > MAX_ENQUEUE_TIMES) {
50
+ storage.enqueueTimes.delete(storage.enqueueTimes.keys().next().value!);
51
+ }
52
+ break;
53
+ case 'expo-router:action-dispatched': {
54
+ // Look up by id so an action that never dispatched can't become the TTR start.
55
+ const { actionId } = entry.detail;
56
+ const enqueueTime =
57
+ actionId === undefined ? undefined : storage.enqueueTimes.get(actionId);
58
+ // PRELOAD comes from router.prefetch() — a route warm-up, not a user
59
+ // navigation — so it must not become a TTR start.
60
+ if (entry.detail.actionType === 'PRELOAD') break;
61
+ storage.pendingActions.push({
62
+ actionType: entry.detail.actionType,
63
+ startTime: enqueueTime ?? entry.startTime,
64
+ });
65
+ break;
66
+ }
67
+ case 'expo-router:page-preloaded':
68
+ // The screen rendered as part of a preload. Mark it as already rendered so
69
+ // the eventual page focus resolves to `warm_ttr` rather than `cold_ttr`.
70
+ storage.renderedScreensIds.add(entry.detail.screenId);
71
+ break;
72
+ case 'expo-router:page-focused':
73
+ onPageFocused(entry.detail, entry.startTime);
74
+ break;
75
+ }
76
+ }
51
77
  });
52
- cleanup.add(unsubscribePreload);
78
+ observer.observe({ type: 'mark' });
53
79
 
54
- const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e) => {
55
- // Snapshot both clocks once so every metric written below is stamped with
56
- // the moment the focus event fired, not the moment `addMetric` happens to run
57
- // after the surrounding async work.
58
- const now = performance.now();
80
+ async function onPageFocused(e: RouterPageMarkDetail, now: number) {
81
+ // Stamp every metric written below with the moment of the focus mark, not
82
+ // the moment `addMetric` happens to run after the surrounding async work.
59
83
  const timestamp = new Date().toISOString();
60
84
 
61
85
  // Snapshot BEFORE seeding dispatchTime below so the deferred-TTI check
@@ -82,7 +106,7 @@ export function initListeners(
82
106
  } else {
83
107
  const last = storage.pendingActions[storage.pendingActions.length - 1];
84
108
  if (!last) return;
85
- dispatchTime = last.dispatchTime;
109
+ dispatchTime = last.startTime;
86
110
  isAppLaunch = false;
87
111
  storage.pendingActions.length = 0;
88
112
  }
@@ -132,11 +156,7 @@ export function initListeners(
132
156
  config: routerIntegrationConfig,
133
157
  });
134
158
  }
135
- });
136
- cleanup.add(unsubscribeFocus);
159
+ }
137
160
 
138
- return () => {
139
- cleanup.forEach((c) => c());
140
- cleanup.clear();
141
- };
161
+ return () => observer.disconnect();
142
162
  }
@@ -1,30 +1,40 @@
1
1
  // expo-router is an optional peerDependency of expo-observe. When the host app
2
2
  // hasn't installed it, `require` throws and the integration becomes a no-op.
3
- interface RouterPageEvent {
3
+ export interface RouterPageMarkDetail {
4
4
  pathname: string;
5
- params: Record<string, string | string[]>;
5
+ params: Record<string, string | string[] | undefined>;
6
6
  screenId: string;
7
7
  segments: string[];
8
8
  }
9
9
 
10
- interface RouterActionDispatchedEvent {
11
- actionType: string;
12
- }
10
+ export type RouterPerformanceMark =
11
+ | { name: 'expo-router:action-enqueued'; startTime: number; detail: { actionId: number } }
12
+ | {
13
+ name: 'expo-router:action-dispatched';
14
+ startTime: number;
15
+ // `actionId` is missing in expo-router versions without the action-enqueued mark.
16
+ detail: { actionType: string; actionId?: number };
17
+ }
18
+ | {
19
+ name:
20
+ | 'expo-router:page-preloaded'
21
+ | 'expo-router:page-focused'
22
+ | 'expo-router:page-blurred'
23
+ | 'expo-router:page-removed';
24
+ startTime: number;
25
+ detail: RouterPageMarkDetail;
26
+ };
13
27
 
14
- interface RouterNavigationEvents {
15
- addListener(
16
- eventType: 'actionDispatched',
17
- callback: (event: RouterActionDispatchedEvent) => void
18
- ): () => void;
19
- addListener(
20
- eventType: 'pagePreloaded' | 'pageFocused',
21
- callback: (event: RouterPageEvent) => void
22
- ): () => void;
23
- enable(): void;
24
- }
28
+ export type RouterPerformanceObserver = new (
29
+ callback: (list: { getEntries(): Pick<PerformanceEntry, 'name' | 'startTime'>[] }) => void
30
+ ) => {
31
+ observe(options: { type: 'mark' }): void;
32
+ disconnect(): void;
33
+ };
25
34
 
26
35
  interface OptionalRouter {
27
- unstable_navigationEvents: RouterNavigationEvents;
36
+ unstable_enablePerformanceIntegration(): void;
37
+ unstable_PerformanceObserver: RouterPerformanceObserver;
28
38
  useCurrentRouteInfo(): {
29
39
  pathname: string;
30
40
  params: Record<string, string | string[]>;
@@ -6,7 +6,8 @@ export interface ScreenTimes {
6
6
 
7
7
  export interface PendingAction {
8
8
  actionType: string;
9
- dispatchTime: number;
9
+ // When the action was enqueued, or dispatched if expo-router did not mark the enqueue.
10
+ startTime: number;
10
11
  }
11
12
 
12
13
  export interface RouterIntegrationStorage {
@@ -14,6 +15,10 @@ export interface RouterIntegrationStorage {
14
15
  * Actions dispatched, but not yet processed by the integration
15
16
  */
16
17
  pendingActions: PendingAction[];
18
+ /**
19
+ * Enqueue times by `actionId`. The oldest id is dropped when the map is full
20
+ */
21
+ enqueueTimes: Map<number, number>;
17
22
  renderedScreensIds: Set<string>;
18
23
  /**
19
24
  * Whether the app had already recorded the first render of the screen
@@ -29,6 +34,7 @@ export interface RouterIntegrationStorage {
29
34
  export function createRouterIntegrationStorage(): RouterIntegrationStorage {
30
35
  return {
31
36
  pendingActions: [],
37
+ enqueueTimes: new Map(),
32
38
  renderedScreensIds: new Set(),
33
39
  hasRecordedInitialTtr: false,
34
40
  screenTimes: {},
@@ -1 +0,0 @@
1
- d8556490b2358f9a799f840d3f1f58b7c27604bc295b77d2c02a2e56aa1f6cd5
@@ -1 +0,0 @@
1
- 932944f468a6cac663389d69143847a72f707f1cef1fe7a1d387720cda2fbce7c7d2f97e2a753db818b551be5775e2a57b87de07e7ee37e259ebe439640d08c2
@@ -1 +0,0 @@
1
- 44496217f172c4a1a972ee8cb5e80975f153fbd46df70f71def2369495f9893b
@@ -1 +0,0 @@
1
- ea11a3c8ff1b0068d999f4903f1efd2a6d95328adc7f6761277fe031fad5bba601076defb05b0f840fae495c7e5783bc1bab9d81bafd96965ec9c898c346e897
@@ -1 +0,0 @@
1
- 1f8035bc03dff06a61cd1a63d0f948142a58cd147d469811739798b362b0082a
@@ -1 +0,0 @@
1
- 42a58abd8a98bb822b082e409d9a57ba11ac2b5d3816a759f300761dc96c9ad86368c55088d4c3fa8b5e8fa7f3c24c435e076ef6f0503fc24d31f342220eacbe
@@ -1 +0,0 @@
1
- 72e714733b2aa35135661176fc1d48584979c0cf28f863de25725fa2691f0046
@@ -1 +0,0 @@
1
- 60909890d99316e5b80890cd807842caffb6e59d05ec2727803541866543f6deee96d63f244abbfac76eee67e9cd763756015e77f598b4f56f3b53f592094966