From 786e429732c1dff9774bef2858a4ba9a26f15562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 12 May 2026 10:55:06 +0100 Subject: [PATCH 01/11] [expo-router] Name suspense boundary used in useScreens (#45501) # Why Naming suspense boundaries is generally a good practice for debugging. Also, with transition tracing and other tracing solutions for React, suspense boundary names can help identify the source of an async operation. # How Just added the name in the suspense boundary, using the route name if available. # Test Plan CI # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- packages/expo-router/CHANGELOG.md | 2 ++ packages/expo-router/build/useScreens.d.ts.map | 2 +- packages/expo-router/build/useScreens.js | 2 +- packages/expo-router/build/useScreens.js.map | 2 +- packages/expo-router/src/useScreens.tsx | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index 3dd905f5b2a718..63ba07774d6607 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- Add a name to injected suspense boundaries for easier debugging ([#45501](https://github.com/expo/expo/pull/45501) by [@rubennorte](https://github.com/rubennorte)) + ### 🐛 Bug fixes ### 💡 Others diff --git a/packages/expo-router/build/useScreens.d.ts.map b/packages/expo-router/build/useScreens.d.ts.map index 795ab229af2d57..7660d1e4f1a5c0 100644 --- a/packages/expo-router/build/useScreens.d.ts.map +++ b/packages/expo-router/build/useScreens.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-router/build/useScreens.js b/packages/expo-router/build/useScreens.js index 3c9b5d74e88774..14801e7e8e47d5 100644 --- a/packages/expo-router/build/useScreens.js +++ b/packages/expo-router/build/useScreens.js @@ -248,7 +248,7 @@ function getQualifiedRouteComponent(value) { }, [navigation]); const isRouteType = value.type === 'route'; const hasRouteKey = !!route?.key; - return ((0, jsx_runtime_1.jsx)(Route_1.Route, { node: value, params: route?.params, children: (0, jsx_runtime_1.jsxs)(Route_1.SuspenseFallbackContext, { value: providedSuspenseFallback, children: [navigationEvents_1.unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && ((0, jsx_runtime_1.jsx)(AnalyticsListeners, { navigation: navigation, screenId: route.key })), (0, jsx_runtime_1.jsxs)(zoom_transition_context_providers_1.ZoomTransitionTargetContextProvider, { route: route, children: [(0, jsx_runtime_1.jsx)(ZoomTransitionEnabler_1.ZoomTransitionEnabler, { route: route }), (0, jsx_runtime_1.jsx)(react_2.default.Suspense, { fallback: (0, jsx_runtime_1.jsx)(ResolvedSuspenseFallback, { route: value.contextKey, params: (route?.params ?? {}) }), children: (0, jsx_runtime_1.jsx)(WrappedScreenComponent, { ...props, + return ((0, jsx_runtime_1.jsx)(Route_1.Route, { node: value, params: route?.params, children: (0, jsx_runtime_1.jsxs)(Route_1.SuspenseFallbackContext, { value: providedSuspenseFallback, children: [navigationEvents_1.unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && ((0, jsx_runtime_1.jsx)(AnalyticsListeners, { navigation: navigation, screenId: route.key })), (0, jsx_runtime_1.jsxs)(zoom_transition_context_providers_1.ZoomTransitionTargetContextProvider, { route: route, children: [(0, jsx_runtime_1.jsx)(ZoomTransitionEnabler_1.ZoomTransitionEnabler, { route: route }), (0, jsx_runtime_1.jsx)(react_2.default.Suspense, { name: route ? `Route(${route.name})` : undefined, fallback: (0, jsx_runtime_1.jsx)(ResolvedSuspenseFallback, { route: value.contextKey, params: (route?.params ?? {}) }), children: (0, jsx_runtime_1.jsx)(WrappedScreenComponent, { ...props, // Expose the template segment path, e.g. `(home)`, `[foo]`, `index` // the intention is to make it possible to deduce shared routes. segment: value.route }) })] })] }) })); diff --git a/packages/expo-router/build/useScreens.js.map b/packages/expo-router/build/useScreens.js.map index 3ffe1b7c4ba8ef..4f38cf8b380892 100644 --- a/packages/expo-router/build/useScreens.js.map +++ b/packages/expo-router/build/useScreens.js.map @@ -1 +1 @@ -{"version":3,"file":"useScreens.js","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":";AAAA,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsKb,4CAiCC;AAuDD,gEA8IC;AA+ED,oDAwBC;AAED,sCAcC;AAED,sCAaC;;;AAhhBD,+CAAuD;AAGvD,mCAA8F;AAC9F,8DAAiE;AACjE,gDAAqE;AACrE,gEAAoD;AACpD,6EAA0E;AAC1E,qGAAoG;AACpG,yDAA+D;AAC/D,oDAAoG;AACpG,yDAI4B;AAC5B,6CAAsC;AAEtC,sDASmC;AAGnC,mDAAgD;AAChD,+DAGkC;AAClC,qCAAkC;AAmClC,SAAS,iBAAiB,CACxB,QAAqB,EACrB,QAAuB,EAAE,EACzB,gBAAyB;IAEzB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,QAAQ;aACZ,IAAI,CAAC,IAAA,6BAAqB,EAAC,gBAAgB,CAAC,CAAC;aAC7C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;IAE9B,MAAM,OAAO,GAAG,KAAK;SAClB,GAAG,CACF,CAAC,EACC,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,SAAS,EACT,OAAO,EACP,KAAK,EACL,mBAAmB,EAAE,QAAQ,GAC9B,EAAE,EAAE;QACH,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CACV,uDAAuD,IAAI,kBAAkB,CAC9E,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAClC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,IAAI,QAAQ,CACnE,CAAC;QACF,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CACV,sCAAsC,IAAI,8BAA8B,EACxE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CACnC,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;aAAM,CAAC;YACN,oCAAoC;YACpC,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;YAClC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YAE9B,qDAAqD;YACrD,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBACjC,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;gBAC3E,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,IAAI,CACV,sCAAsC,IAAI,iEAAiE,CAC5G,CAAC;gBACF,IAAI,QAAQ,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CACV,UAAU,IAAI,0DAA0D,CACzE,CAAC;gBACJ,CAAC;YACH,CAAC;iBAAM,IAAI,QAAQ,EAAE,CAAC;gBACpB,oDAAoD;gBACpD,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBACjC,KAAK,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC;gBACzB,CAAC;qBAAM,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,IAAI,EAAE,CAAC;oBAClD,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBAC5D,CAAC;qBAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,IAAI,EAAE,CAAC;oBACrC,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACpD,CAAC;YACH,CAAC;YAED,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;aACpD,CAAC;QACJ,CAAC;IACH,CAAC,CACF;SACA,MAAM,CAAC,OAAO,CAGd,CAAC;IAEJ,6BAA6B;IAC7B,OAAO,CAAC,IAAI,CACV,GAAG,OAAO,CAAC,IAAI,CAAC,IAAA,6BAAqB,EAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAChG,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB,CAC9B,KAAoB,EACpB,gBAA6B,EAC7B,4BAAqC,KAAK;IAE1C,MAAM,IAAI,GAAG,IAAA,oBAAY,GAAE,CAAC;IAE5B,MAAM,YAAY,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,yBAAyB;QACxC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAC5B,KAAK,CAAC,IAAI,CACR,CAAC,iBAAiB,EAAE,EAAE,CACpB,iBAAiB,CAAC,IAAI,KAAK,KAAK,CAAC,KAAK;YACtC,GAAG,iBAAiB,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,KAAK,CACpD,CACF;QACH,CAAC,CAAC,YAAY,CAAC;IAEjB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,OAAO,eAAK,CAAC,OAAO,CAClB,GAAG,EAAE,CACH,MAAM;SACH,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAC/B,OAAO,CACL,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CACrF,CAAC;IACJ,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,OAAO,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC,CAAC,EACN,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAC3B,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,KAAgB,EAChB,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,SAAS,EAAe;IAE9D,gLAAgL;IAChL,IAAI,SAAS,EAAE,OAAO,IAAI,OAAO,EAAE,CAAC;QAClC,SAAS,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC;IAChG,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,eAAK,CAAC,UAAU,CAAC,CAAC,KAAU,EAAE,GAAQ,EAAE,EAAE;YACxD,MAAM,QAAQ,GAAG,eAAK,CAAC,aAAa,CAAC,SAAS,CAAC,OAAO,IAAI,uBAAU,EAAE;gBACpE,GAAG,KAAK;gBACR,GAAG;aACJ,CAAC,CAAC;YACH,OAAO,uBAAC,SAAG,IAAC,KAAK,EAAE,aAAa,YAAG,QAAQ,GAAO,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,WAAW,GAAG,iBAAiB,KAAK,CAAC,UAAU,GAAG,CAAC;QAC7D,CAAC;QAED,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,gBAAgB;SACjB,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QAC1C,IACE,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;YACrC,SAAS,CAAC,OAAO;YACjB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAC3C,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,uBAAU,EAAE,gBAAgB,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,eAAe,CAAC,KAAgB,EAAE,GAAgB;IACzD,IAAI,CAAC,CAAC,GAAG,YAAY,OAAO,CAAC,EAAE,CAAC;QAC9B,OAAO,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,qDAAqD;AACrD,2DAA2D;AAC3D,MAAM,cAAc,GAAG,IAAI,OAAO,EAAuC,CAAC;AAE1E,mFAAmF;AACnF,SAAgB,0BAA0B,CAAC,KAAgB;IACzD,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC;IACpC,CAAC;IAED,IAAI,eAEyC,CAAC;IAE9C,IAAI,sBAA8E,CAAC;IAEnF,sEAAsE;IACtE,IAAI,qBAAuB,KAAK,MAAM,EAAE,CAAC;QACvC,eAAe,GAAG,eAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACtC,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,eAAe,CAAC,KAAK,EAAE,GAAG,CAE/B,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,OAAO,EAAE,CAAC;YACZ,eAAe,CAAC,WAAW,GAAG,cAAc,KAAK,CAAC,KAAK,GAAG,CAAC;QAC7D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,eAAe,GAAG,MAAM,CAAC,OAAQ,CAAC;QAClC,sBAAsB,GAAG,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,CAAC;IACD,MAAM,sBAAsB,GAA2B,CAAC,KAAa,EAAE,EAAE;QACvE,IAAA,qCAA6B,GAAE,CAAC;QAChC,OAAO,uBAAC,eAAe,OAAK,KAAK,GAAI,CAAC;IACxC,CAAC,CAAC;IACF,SAAS,SAAS,CAAC;IACjB,yCAAyC;IACzC,2EAA2E;IAC3E,KAAK,EACL,UAAU;IAEV,wCAAwC;IACxC,GAAG,KAAK,EAgBT;QACC,MAAM,YAAY,GAAG,IAAA,wBAAe,GAAE,CAAC;QACvC,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAA,iCAAkB,GAAE,CAAC;QACnC,MAAM,yBAAyB,GAAG,IAAA,WAAG,EAAC,+BAAuB,CAAC,CAAC;QAE/D,MAAM,wBAAwB,GAC5B,qBAAuB,KAAK,MAAM;YAChC,CAAC,CAAC,mCAAuB;YACzB,CAAC,CAAC,CAAC,sBAAsB,IAAI,yBAAyB,IAAI,mCAAuB,CAAC,CAAC;QACvF,MAAM,wBAAwB,GAC5B,KAAK,CAAC,IAAI,KAAK,QAAQ;YACrB,CAAC,CAAC,CAAC,sBAAsB,IAAI,yBAAyB,CAAC;YACvD,CAAC,CAAC,yBAAyB,CAAC;QAEhC,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;YACjE,IAAI,MAAM,IAAI,YAAY;gBAAE,KAAK,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;QAClE,CAAC;QAED,IAAA,iBAAS,EACP,GAAG,EAAE,CACH,UAAU,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE;YACnC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;YACjE,uFAAuF;YACvF,sEAAsE;YACtE,4DAA4D;YAC5D,kDAAkD;YAClD,IAAI,MAAM,IAAI,YAAY;gBAAE,KAAK,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC;QAClE,CAAC,CAAC,EACJ,CAAC,UAAU,CAAC,CACb,CAAC;QAEF,IAAA,iBAAS,EAAC,GAAG,EAAE;YACb,OAAO,UAAU,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE;gBACnD,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;oBACtB,qFAAqF;oBACrF,6DAA6D;oBAC7D,IAAI,IAAA,2BAAQ,EAAC,KAAK,EAAE,MAAM,EAAE,+DAA4C,CAAC,EAAE,CAAC;wBAC1E,UAAU,CAAC,aAAa,CACtB,IAAA,+BAAY,EAAC,KAAK,EAAE,MAAM,EAAE,CAAC,+DAA4C,CAAC,CAAC,CAC5E,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAEjB,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;QAC3C,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC;QAEjC,OAAO,CACL,uBAAC,aAAK,IAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YACvC,wBAAC,+BAAuB,IAAC,KAAK,EAAE,wBAAwB,aACrD,4CAAyB,CAAC,SAAS,EAAE,IAAI,WAAW,IAAI,WAAW,IAAI,CACtE,uBAAC,kBAAkB,IAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,GAAI,CACpE,EACD,wBAAC,uEAAmC,IAAC,KAAK,EAAE,KAAK,aAC/C,uBAAC,6CAAqB,IAAC,KAAK,EAAE,KAAK,GAAI,EACvC,uBAAC,eAAK,CAAC,QAAQ,IACb,QAAQ,EACN,uBAAC,wBAAwB,IACvB,KAAK,EAAE,KAAK,CAAC,UAAU,EACvB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAoC,GAChE,YAEJ,uBAAC,sBAAsB,OACjB,KAAK;oCACT,oEAAoE;oCACpE,gEAAgE;oCAChE,OAAO,EAAE,KAAK,CAAC,KAAK,GACpB,GACa,IACmB,IACd,GACpB,CACT,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,SAAS,CAAC,WAAW,GAAG,SAAS,KAAK,CAAC,KAAK,GAAG,CAAC;IAClD,CAAC;IAED,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACrC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,kBAAkB,CAAC,EAC1B,UAAU,EACV,QAAQ,GAMT;IACC,MAAM,YAAY,GAAG,IAAA,wBAAe,GAAE,CAAC;IACvC,MAAM,gBAAgB,GAAG,eAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,aAAa,GAAG,eAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAA,iCAAyB,EAAC,YAAY,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEzF,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;QAC7B,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;QACjC,IAAI,SAAS,EAAE,CAAC;YACd,4CAAyB,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBAC/C,GAAG,IAAA,qCAA6B,EAAC,SAAS,CAAC;gBAC3C,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,GAAG,EAAE;gBACV,4CAAyB,CAAC,IAAI,CAAC,aAAa,EAAE;oBAC5C,GAAG,IAAA,qCAA6B,EAAC,SAAS,CAAC;oBAC3C,QAAQ;iBACT,CAAC,CAAC;YACL,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1B,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;IAEzC,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;QAC3B,4CAAyB,CAAC,IAAI,CAAC,aAAa,EAAE;YAC5C,GAAG,IAAA,qCAA6B,EAAC,SAAS,CAAC;YAC3C,QAAQ;SACT,CAAC,CAAC;QACH,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;IAChC,CAAC;IAED,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE;gBACtD,0DAA0D;gBAC1D,oEAAoE;gBACpE,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;oBAC1B,4CAAyB,CAAC,IAAI,CAAC,aAAa,EAAE;wBAC5C,GAAG,IAAA,qCAA6B,EAAC,SAAS,CAAC;wBAC3C,QAAQ;qBACT,CAAC,CAAC;oBACH,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC;gBAChC,CAAC;YACH,CAAC,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE;gBACpD,4CAAyB,CAAC,IAAI,CAAC,aAAa,EAAE;oBAC5C,GAAG,IAAA,qCAA6B,EAAC,SAAS,CAAC;oBAC3C,QAAQ;iBACT,CAAC,CAAC;gBACH,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAC/B,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,EAAE;gBACV,UAAU,EAAE,CAAC;gBACb,SAAS,EAAE,CAAC;YACd,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEtC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAgB,oBAAoB,CAClC,KAAgB,EAChB,OAAgC;IAEhC,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,uCAAuC;QACvC,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,MAAM,YAAY,GAAG,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QAC/F,MAAM,aAAa,GAAG,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAChF,MAAM,MAAM,GAAG;YACb,GAAG,YAAY;YACf,GAAG,aAAa;SACjB,CAAC;QAEF,4DAA4D;QAC5D,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,CAAC,eAAe,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC7C,MAAM,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;YACjC,qFAAqF;YACrF,MAAM,CAAC,eAAe,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAC1D,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,aAAa,CAC3B,KAAgB,EAChB,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,KAA2B,EAAE;IAEvD,OAAO,CACL,2BAAC,mBAAM,OACD,KAAK,EACT,IAAI,EAAE,KAAK,CAAC,KAAK,EACjB,GAAG,EAAE,KAAK,CAAC,KAAK,EAChB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,EAC7C,YAAY,EAAE,GAAG,EAAE,CAAC,0BAA0B,CAAC,KAAK,CAAC,GACrD,CACH,CAAC;AACJ,CAAC;AAED,SAAgB,aAAa,CAAC,IAAY,EAAE,UAA+B,EAAE;IAC3E,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QACf,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC;QACtE,CAAC;aAAM,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5D,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC","sourcesContent":["'use client';\n\nimport React, { use, useEffect, useMemo } from 'react';\n\nimport type { LoadedRoute, RouteNode } from './Route';\nimport { SuspenseFallbackContext, Route, sortRoutesWithInitial, useRouteNode } from './Route';\nimport { useExpoRouterStore } from './global-state/storeContext';\nimport { useColorSchemeChangesIfNeeded } from './global-state/utils';\nimport EXPO_ROUTER_IMPORT_MODE from './import-mode';\nimport { ZoomTransitionEnabler } from './link/zoom/ZoomTransitionEnabler';\nimport { ZoomTransitionTargetContextProvider } from './link/zoom/zoom-transition-context-providers';\nimport { unstable_navigationEvents } from './navigationEvents';\nimport { generateStringUrlForState, getPathAndParamsFromStringUrl } from './navigationEvents/utils';\nimport {\n hasParam,\n INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME,\n removeParams,\n} from './navigationParams';\nimport { Screen } from './primitives';\nimport type { BottomTabNavigationEventMap } from './react-navigation/bottom-tabs';\nimport {\n useStateForPath,\n type EventConsumer,\n type EventMapBase,\n type NavigationProp,\n type NavigationState,\n type ParamListBase,\n type RouteProp,\n type ScreenListeners,\n} from './react-navigation/native';\nimport type { NativeStackNavigationEventMap } from './react-navigation/native-stack';\nimport type { UnknownOutputParams } from './types';\nimport { EmptyRoute } from './views/EmptyRoute';\nimport {\n SuspenseFallback as DefaultSuspenseFallback,\n type SuspenseFallbackProps,\n} from './views/SuspenseFallback';\nimport { Try } from './views/Try';\n\nexport type ScreenProps<\n TOptions extends Record = Record,\n TState extends NavigationState = NavigationState,\n TEventMap extends EventMapBase = EventMapBase,\n> = {\n /** Name is required when used inside a Layout component. */\n name?: string;\n /**\n * Redirect to the nearest sibling route.\n * If all children are `redirect={true}`, the layout will render `null` as there are no children to render.\n */\n redirect?: boolean;\n initialParams?: Record;\n options?:\n | TOptions\n | ((prop: { route: RouteProp; navigation: any }) => TOptions);\n\n listeners?:\n | ScreenListeners\n | ((prop: {\n route: RouteProp;\n navigation: any;\n }) => ScreenListeners);\n\n getId?: ({ params }: { params?: Record }) => string | undefined;\n\n dangerouslySingular?: SingularOptions;\n};\n\nexport type SingularOptions =\n | boolean\n | ((name: string, params: UnknownOutputParams) => string | undefined);\n\nfunction getSortedChildren(\n children: RouteNode[],\n order: ScreenProps[] = [],\n initialRouteName?: string\n): { route: RouteNode; props: Partial }[] {\n if (!order?.length) {\n return children\n .sort(sortRoutesWithInitial(initialRouteName))\n .map((route) => ({ route, props: {} }));\n }\n const entries = [...children];\n\n const ordered = order\n .map(\n ({\n name,\n redirect,\n initialParams,\n listeners,\n options,\n getId,\n dangerouslySingular: singular,\n }) => {\n if (!entries.length) {\n console.warn(\n `[Layout children]: Too many screens defined. Route \"${name}\" is extraneous.`\n );\n return null;\n }\n const matchIndex = entries.findIndex(\n (child) => child.route === name || child.route === `${name}/index`\n );\n if (matchIndex === -1) {\n console.warn(\n `[Layout children]: No route named \"${name}\" exists in nested children:`,\n children.map(({ route }) => route)\n );\n return null;\n } else {\n // Get match and remove from entries\n const match = entries[matchIndex];\n entries.splice(matchIndex, 1);\n\n // Ensure to return null after removing from entries.\n if (redirect) {\n if (typeof redirect === 'string') {\n throw new Error(`Redirecting to a specific route is not supported yet.`);\n }\n return null;\n }\n\n if (getId) {\n console.warn(\n `Deprecated: prop 'getId' on screen ${name} is deprecated. Please rename the prop to 'dangerouslySingular'`\n );\n if (singular) {\n console.warn(\n `Screen ${name} cannot use both getId and dangerouslySingular together.`\n );\n }\n } else if (singular) {\n // If singular is set, use it as the getId function.\n if (typeof singular === 'string') {\n getId = () => singular;\n } else if (typeof singular === 'function' && name) {\n getId = (options) => singular(name, options.params || {});\n } else if (singular === true && name) {\n getId = (options) => getSingularId(name, options);\n }\n }\n\n return {\n route: match,\n props: { initialParams, listeners, options, getId },\n };\n }\n }\n )\n .filter(Boolean) as {\n route: RouteNode;\n props: Partial;\n }[];\n\n // Add any remaining children\n ordered.push(\n ...entries.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }))\n );\n\n return ordered;\n}\n\n/**\n * @returns React Navigation screens sorted by the `route` property.\n */\nexport function useSortedScreens(\n order: ScreenProps[],\n protectedScreens: Set,\n useOnlyUserDefinedScreens: boolean = false\n): React.ReactNode[] {\n const node = useRouteNode();\n\n const nodeChildren = node?.children ?? [];\n const children = useOnlyUserDefinedScreens\n ? nodeChildren.filter((child) =>\n order.some(\n (userDefinedScreen) =>\n userDefinedScreen.name === child.route ||\n `${userDefinedScreen.name}/index` === child.route\n )\n )\n : nodeChildren;\n\n const sorted = children.length ? getSortedChildren(children, order, node?.initialRouteName) : [];\n return React.useMemo(\n () =>\n sorted\n .filter((item) => {\n const route = item.route.route;\n return (\n !protectedScreens.has(route) && !protectedScreens.has(route.replace(/\\/index$/, ''))\n );\n })\n .map((value) => {\n return routeToScreen(value.route, value.props);\n }),\n [sorted, protectedScreens]\n );\n}\n\nfunction fromImport(\n value: RouteNode,\n { ErrorBoundary, SuspenseFallback, ...component }: LoadedRoute\n) {\n // If possible, add a more helpful display name for the component stack to improve debugging of React errors such as `Text strings must be rendered within a component.`.\n if (component?.default && __DEV__) {\n component.default.displayName ??= `${component.default.name ?? 'Route'}(${value.contextKey})`;\n }\n\n if (ErrorBoundary) {\n const Wrapped = React.forwardRef((props: any, ref: any) => {\n const children = React.createElement(component.default || EmptyRoute, {\n ...props,\n ref,\n });\n return {children};\n });\n\n if (__DEV__) {\n Wrapped.displayName = `ErrorBoundary(${value.contextKey})`;\n }\n\n return {\n default: Wrapped,\n SuspenseFallback,\n };\n }\n if (process.env.NODE_ENV !== 'production') {\n if (\n typeof component.default === 'object' &&\n component.default &&\n Object.keys(component.default).length === 0\n ) {\n return { default: EmptyRoute, SuspenseFallback };\n }\n }\n\n return { default: component.default, SuspenseFallback };\n}\n\nfunction fromLoadedRoute(value: RouteNode, res: LoadedRoute) {\n if (!(res instanceof Promise)) {\n return fromImport(value, res);\n }\n\n return res.then(fromImport.bind(null, value));\n}\n\n// TODO: Maybe there's a more React-y way to do this?\n// Without this store, the process enters a recursive loop.\nconst qualifiedStore = new WeakMap>();\n\n/** Wrap the component with various enhancements and add access to child routes. */\nexport function getQualifiedRouteComponent(value: RouteNode) {\n if (qualifiedStore.has(value)) {\n return qualifiedStore.get(value)!;\n }\n\n let ScreenComponent:\n | React.ForwardRefExoticComponent>\n | React.ComponentType<{ segment?: string }>;\n\n let LayoutSuspenseFallback: React.ComponentType | undefined;\n\n // TODO: This ensures sync doesn't use React.lazy, but it's not ideal.\n if (EXPO_ROUTER_IMPORT_MODE === 'lazy') {\n ScreenComponent = React.lazy(async () => {\n const res = value.loadRoute();\n return fromLoadedRoute(value, res) as Promise<{\n default: React.ComponentType;\n }>;\n });\n\n if (__DEV__) {\n ScreenComponent.displayName = `AsyncRoute(${value.route})`;\n }\n } else {\n const res = value.loadRoute();\n const result = fromImport(value, res);\n ScreenComponent = result.default!;\n LayoutSuspenseFallback = value.type === 'layout' ? result.SuspenseFallback : undefined;\n }\n const WrappedScreenComponent: typeof ScreenComponent = (props: object) => {\n useColorSchemeChangesIfNeeded();\n return ;\n };\n function BaseRoute({\n // Remove these React Navigation props to\n // enforce usage of expo-router hooks (where the query params are correct).\n route,\n navigation,\n\n // Pass all other props to the component\n ...props\n }: {\n route?: RouteProp;\n navigation: Omit<\n NavigationProp<\n ParamListBase,\n string,\n undefined,\n NavigationState,\n object,\n NativeStackNavigationEventMap | BottomTabNavigationEventMap\n >,\n 'getState'\n > & {\n getState(): NavigationState | undefined;\n };\n }) {\n const stateForPath = useStateForPath();\n const isFocused = navigation.isFocused();\n const store = useExpoRouterStore();\n const InheritedSuspenseFallback = use(SuspenseFallbackContext);\n\n const ResolvedSuspenseFallback =\n EXPO_ROUTER_IMPORT_MODE === 'lazy'\n ? DefaultSuspenseFallback\n : (LayoutSuspenseFallback ?? InheritedSuspenseFallback ?? DefaultSuspenseFallback);\n const providedSuspenseFallback =\n value.type === 'layout'\n ? (LayoutSuspenseFallback ?? InheritedSuspenseFallback)\n : InheritedSuspenseFallback;\n\n if (isFocused) {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }\n\n useEffect(\n () =>\n navigation.addListener('focus', () => {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n // Because setFocusedState caches the route info, this call will only trigger rerenders\n // if the component itself didn’t rerender and the route info changed.\n // Otherwise, the update from the `if` above will handle it,\n // and this won’t cause a redundant second update.\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }),\n [navigation]\n );\n\n useEffect(() => {\n return navigation.addListener('transitionEnd', (e) => {\n if (!e?.data?.closing) {\n // When navigating to a screen, remove the no animation param to re-enable animations\n // Otherwise the navigation back would also have no animation\n if (hasParam(route?.params, INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME)) {\n navigation.replaceParams(\n removeParams(route?.params, [INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME])\n );\n }\n }\n });\n }, [navigation]);\n\n const isRouteType = value.type === 'route';\n const hasRouteKey = !!route?.key;\n\n return (\n \n \n {unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && (\n \n )}\n \n \n \n }>\n \n \n \n \n \n );\n }\n\n if (__DEV__) {\n BaseRoute.displayName = `Route(${value.route})`;\n }\n\n qualifiedStore.set(value, BaseRoute);\n return BaseRoute;\n}\n\nfunction AnalyticsListeners({\n navigation,\n screenId,\n}: {\n navigation: EventConsumer & {\n isFocused(): boolean;\n };\n screenId: string;\n}) {\n const stateForPath = useStateForPath();\n const isFirstRenderRef = React.useRef(true);\n const hasBlurredRef = React.useRef(true);\n const stringUrl = useMemo(() => generateStringUrlForState(stateForPath), [stateForPath]);\n\n if (isFirstRenderRef.current) {\n isFirstRenderRef.current = false;\n if (stringUrl) {\n unstable_navigationEvents.emit('pageWillRender', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n }\n }\n\n useEffect(() => {\n if (stringUrl) {\n return () => {\n unstable_navigationEvents.emit('pageRemoved', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n };\n }\n return () => {};\n }, [stringUrl, screenId]);\n\n const isFocused = navigation.isFocused();\n\n if (isFocused && stringUrl) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n\n useEffect(() => {\n if (stringUrl) {\n const cleanFocus = navigation.addListener('focus', () => {\n // If the screen was not blurred, don't emit focused again\n // hasBlurredRef will be false when the screen was initially focused\n if (hasBlurredRef.current) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n });\n const cleanBlur = navigation.addListener('blur', () => {\n unstable_navigationEvents.emit('pageBlurred', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = true;\n });\n return () => {\n cleanFocus();\n cleanBlur();\n };\n }\n return () => {};\n }, [navigation, stringUrl, screenId]);\n\n return null;\n}\n\nexport function screenOptionsFactory(\n route: RouteNode,\n options?: ScreenProps['options']\n): ScreenProps['options'] {\n return (args) => {\n // Only eager load generated components\n const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null;\n const staticResult = typeof staticOptions === 'function' ? staticOptions(args) : staticOptions;\n const dynamicResult = typeof options === 'function' ? options?.(args) : options;\n const output = {\n ...staticResult,\n ...dynamicResult,\n };\n\n // Prevent generated screens from showing up in the tab bar.\n if (route.internal) {\n output.tabBarItemStyle = { display: 'none' };\n output.tabBarButton = () => null;\n // TODO: React Navigation doesn't provide a way to prevent rendering the drawer item.\n output.drawerItemStyle = { height: 0, display: 'none' };\n }\n\n return output;\n };\n}\n\nexport function routeToScreen(\n route: RouteNode,\n { options, getId, ...props }: Partial = {}\n) {\n return (\n getQualifiedRouteComponent(route)}\n />\n );\n}\n\nexport function getSingularId(name: string, options: Record = {}) {\n return name\n .split('/')\n .map((segment) => {\n if (segment.startsWith('[...')) {\n return options.params?.[segment.slice(4, -1)]?.join('/') || segment;\n } else if (segment.startsWith('[') && segment.endsWith(']')) {\n return options.params?.[segment.slice(1, -1)] || segment;\n } else {\n return segment;\n }\n })\n .join('/');\n}\n"]} \ No newline at end of file +{"version":3,"file":"useScreens.js","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport React, { use, useEffect, useMemo } from 'react';\n\nimport type { LoadedRoute, RouteNode } from './Route';\nimport { SuspenseFallbackContext, Route, sortRoutesWithInitial, useRouteNode } from './Route';\nimport { useExpoRouterStore } from './global-state/storeContext';\nimport { useColorSchemeChangesIfNeeded } from './global-state/utils';\nimport EXPO_ROUTER_IMPORT_MODE from './import-mode';\nimport { ZoomTransitionEnabler } from './link/zoom/ZoomTransitionEnabler';\nimport { ZoomTransitionTargetContextProvider } from './link/zoom/zoom-transition-context-providers';\nimport { unstable_navigationEvents } from './navigationEvents';\nimport { generateStringUrlForState, getPathAndParamsFromStringUrl } from './navigationEvents/utils';\nimport {\n hasParam,\n INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME,\n removeParams,\n} from './navigationParams';\nimport { Screen } from './primitives';\nimport type { BottomTabNavigationEventMap } from './react-navigation/bottom-tabs';\nimport {\n useStateForPath,\n type EventConsumer,\n type EventMapBase,\n type NavigationProp,\n type NavigationState,\n type ParamListBase,\n type RouteProp,\n type ScreenListeners,\n} from './react-navigation/native';\nimport type { NativeStackNavigationEventMap } from './react-navigation/native-stack';\nimport type { UnknownOutputParams } from './types';\nimport { EmptyRoute } from './views/EmptyRoute';\nimport {\n SuspenseFallback as DefaultSuspenseFallback,\n type SuspenseFallbackProps,\n} from './views/SuspenseFallback';\nimport { Try } from './views/Try';\n\nexport type ScreenProps<\n TOptions extends Record = Record,\n TState extends NavigationState = NavigationState,\n TEventMap extends EventMapBase = EventMapBase,\n> = {\n /** Name is required when used inside a Layout component. */\n name?: string;\n /**\n * Redirect to the nearest sibling route.\n * If all children are `redirect={true}`, the layout will render `null` as there are no children to render.\n */\n redirect?: boolean;\n initialParams?: Record;\n options?:\n | TOptions\n | ((prop: { route: RouteProp; navigation: any }) => TOptions);\n\n listeners?:\n | ScreenListeners\n | ((prop: {\n route: RouteProp;\n navigation: any;\n }) => ScreenListeners);\n\n getId?: ({ params }: { params?: Record }) => string | undefined;\n\n dangerouslySingular?: SingularOptions;\n};\n\nexport type SingularOptions =\n | boolean\n | ((name: string, params: UnknownOutputParams) => string | undefined);\n\nfunction getSortedChildren(\n children: RouteNode[],\n order: ScreenProps[] = [],\n initialRouteName?: string\n): { route: RouteNode; props: Partial }[] {\n if (!order?.length) {\n return children\n .sort(sortRoutesWithInitial(initialRouteName))\n .map((route) => ({ route, props: {} }));\n }\n const entries = [...children];\n\n const ordered = order\n .map(\n ({\n name,\n redirect,\n initialParams,\n listeners,\n options,\n getId,\n dangerouslySingular: singular,\n }) => {\n if (!entries.length) {\n console.warn(\n `[Layout children]: Too many screens defined. Route \"${name}\" is extraneous.`\n );\n return null;\n }\n const matchIndex = entries.findIndex(\n (child) => child.route === name || child.route === `${name}/index`\n );\n if (matchIndex === -1) {\n console.warn(\n `[Layout children]: No route named \"${name}\" exists in nested children:`,\n children.map(({ route }) => route)\n );\n return null;\n } else {\n // Get match and remove from entries\n const match = entries[matchIndex];\n entries.splice(matchIndex, 1);\n\n // Ensure to return null after removing from entries.\n if (redirect) {\n if (typeof redirect === 'string') {\n throw new Error(`Redirecting to a specific route is not supported yet.`);\n }\n return null;\n }\n\n if (getId) {\n console.warn(\n `Deprecated: prop 'getId' on screen ${name} is deprecated. Please rename the prop to 'dangerouslySingular'`\n );\n if (singular) {\n console.warn(\n `Screen ${name} cannot use both getId and dangerouslySingular together.`\n );\n }\n } else if (singular) {\n // If singular is set, use it as the getId function.\n if (typeof singular === 'string') {\n getId = () => singular;\n } else if (typeof singular === 'function' && name) {\n getId = (options) => singular(name, options.params || {});\n } else if (singular === true && name) {\n getId = (options) => getSingularId(name, options);\n }\n }\n\n return {\n route: match,\n props: { initialParams, listeners, options, getId },\n };\n }\n }\n )\n .filter(Boolean) as {\n route: RouteNode;\n props: Partial;\n }[];\n\n // Add any remaining children\n ordered.push(\n ...entries.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }))\n );\n\n return ordered;\n}\n\n/**\n * @returns React Navigation screens sorted by the `route` property.\n */\nexport function useSortedScreens(\n order: ScreenProps[],\n protectedScreens: Set,\n useOnlyUserDefinedScreens: boolean = false\n): React.ReactNode[] {\n const node = useRouteNode();\n\n const nodeChildren = node?.children ?? [];\n const children = useOnlyUserDefinedScreens\n ? nodeChildren.filter((child) =>\n order.some(\n (userDefinedScreen) =>\n userDefinedScreen.name === child.route ||\n `${userDefinedScreen.name}/index` === child.route\n )\n )\n : nodeChildren;\n\n const sorted = children.length ? getSortedChildren(children, order, node?.initialRouteName) : [];\n return React.useMemo(\n () =>\n sorted\n .filter((item) => {\n const route = item.route.route;\n return (\n !protectedScreens.has(route) && !protectedScreens.has(route.replace(/\\/index$/, ''))\n );\n })\n .map((value) => {\n return routeToScreen(value.route, value.props);\n }),\n [sorted, protectedScreens]\n );\n}\n\nfunction fromImport(\n value: RouteNode,\n { ErrorBoundary, SuspenseFallback, ...component }: LoadedRoute\n) {\n // If possible, add a more helpful display name for the component stack to improve debugging of React errors such as `Text strings must be rendered within a component.`.\n if (component?.default && __DEV__) {\n component.default.displayName ??= `${component.default.name ?? 'Route'}(${value.contextKey})`;\n }\n\n if (ErrorBoundary) {\n const Wrapped = React.forwardRef((props: any, ref: any) => {\n const children = React.createElement(component.default || EmptyRoute, {\n ...props,\n ref,\n });\n return {children};\n });\n\n if (__DEV__) {\n Wrapped.displayName = `ErrorBoundary(${value.contextKey})`;\n }\n\n return {\n default: Wrapped,\n SuspenseFallback,\n };\n }\n if (process.env.NODE_ENV !== 'production') {\n if (\n typeof component.default === 'object' &&\n component.default &&\n Object.keys(component.default).length === 0\n ) {\n return { default: EmptyRoute, SuspenseFallback };\n }\n }\n\n return { default: component.default, SuspenseFallback };\n}\n\nfunction fromLoadedRoute(value: RouteNode, res: LoadedRoute) {\n if (!(res instanceof Promise)) {\n return fromImport(value, res);\n }\n\n return res.then(fromImport.bind(null, value));\n}\n\n// TODO: Maybe there's a more React-y way to do this?\n// Without this store, the process enters a recursive loop.\nconst qualifiedStore = new WeakMap>();\n\n/** Wrap the component with various enhancements and add access to child routes. */\nexport function getQualifiedRouteComponent(value: RouteNode) {\n if (qualifiedStore.has(value)) {\n return qualifiedStore.get(value)!;\n }\n\n let ScreenComponent:\n | React.ForwardRefExoticComponent>\n | React.ComponentType<{ segment?: string }>;\n\n let LayoutSuspenseFallback: React.ComponentType | undefined;\n\n // TODO: This ensures sync doesn't use React.lazy, but it's not ideal.\n if (EXPO_ROUTER_IMPORT_MODE === 'lazy') {\n ScreenComponent = React.lazy(async () => {\n const res = value.loadRoute();\n return fromLoadedRoute(value, res) as Promise<{\n default: React.ComponentType;\n }>;\n });\n\n if (__DEV__) {\n ScreenComponent.displayName = `AsyncRoute(${value.route})`;\n }\n } else {\n const res = value.loadRoute();\n const result = fromImport(value, res);\n ScreenComponent = result.default!;\n LayoutSuspenseFallback = value.type === 'layout' ? result.SuspenseFallback : undefined;\n }\n const WrappedScreenComponent: typeof ScreenComponent = (props: object) => {\n useColorSchemeChangesIfNeeded();\n return ;\n };\n function BaseRoute({\n // Remove these React Navigation props to\n // enforce usage of expo-router hooks (where the query params are correct).\n route,\n navigation,\n\n // Pass all other props to the component\n ...props\n }: {\n route?: RouteProp;\n navigation: Omit<\n NavigationProp<\n ParamListBase,\n string,\n undefined,\n NavigationState,\n object,\n NativeStackNavigationEventMap | BottomTabNavigationEventMap\n >,\n 'getState'\n > & {\n getState(): NavigationState | undefined;\n };\n }) {\n const stateForPath = useStateForPath();\n const isFocused = navigation.isFocused();\n const store = useExpoRouterStore();\n const InheritedSuspenseFallback = use(SuspenseFallbackContext);\n\n const ResolvedSuspenseFallback =\n EXPO_ROUTER_IMPORT_MODE === 'lazy'\n ? DefaultSuspenseFallback\n : (LayoutSuspenseFallback ?? InheritedSuspenseFallback ?? DefaultSuspenseFallback);\n const providedSuspenseFallback =\n value.type === 'layout'\n ? (LayoutSuspenseFallback ?? InheritedSuspenseFallback)\n : InheritedSuspenseFallback;\n\n if (isFocused) {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }\n\n useEffect(\n () =>\n navigation.addListener('focus', () => {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n // Because setFocusedState caches the route info, this call will only trigger rerenders\n // if the component itself didn’t rerender and the route info changed.\n // Otherwise, the update from the `if` above will handle it,\n // and this won’t cause a redundant second update.\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }),\n [navigation]\n );\n\n useEffect(() => {\n return navigation.addListener('transitionEnd', (e) => {\n if (!e?.data?.closing) {\n // When navigating to a screen, remove the no animation param to re-enable animations\n // Otherwise the navigation back would also have no animation\n if (hasParam(route?.params, INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME)) {\n navigation.replaceParams(\n removeParams(route?.params, [INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME])\n );\n }\n }\n });\n }, [navigation]);\n\n const isRouteType = value.type === 'route';\n const hasRouteKey = !!route?.key;\n\n return (\n \n \n {unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && (\n \n )}\n \n \n \n }>\n \n \n \n \n \n );\n }\n\n if (__DEV__) {\n BaseRoute.displayName = `Route(${value.route})`;\n }\n\n qualifiedStore.set(value, BaseRoute);\n return BaseRoute;\n}\n\nfunction AnalyticsListeners({\n navigation,\n screenId,\n}: {\n navigation: EventConsumer & {\n isFocused(): boolean;\n };\n screenId: string;\n}) {\n const stateForPath = useStateForPath();\n const isFirstRenderRef = React.useRef(true);\n const hasBlurredRef = React.useRef(true);\n const stringUrl = useMemo(() => generateStringUrlForState(stateForPath), [stateForPath]);\n\n if (isFirstRenderRef.current) {\n isFirstRenderRef.current = false;\n if (stringUrl) {\n unstable_navigationEvents.emit('pageWillRender', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n }\n }\n\n useEffect(() => {\n if (stringUrl) {\n return () => {\n unstable_navigationEvents.emit('pageRemoved', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n };\n }\n return () => {};\n }, [stringUrl, screenId]);\n\n const isFocused = navigation.isFocused();\n\n if (isFocused && stringUrl) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n\n useEffect(() => {\n if (stringUrl) {\n const cleanFocus = navigation.addListener('focus', () => {\n // If the screen was not blurred, don't emit focused again\n // hasBlurredRef will be false when the screen was initially focused\n if (hasBlurredRef.current) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n });\n const cleanBlur = navigation.addListener('blur', () => {\n unstable_navigationEvents.emit('pageBlurred', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = true;\n });\n return () => {\n cleanFocus();\n cleanBlur();\n };\n }\n return () => {};\n }, [navigation, stringUrl, screenId]);\n\n return null;\n}\n\nexport function screenOptionsFactory(\n route: RouteNode,\n options?: ScreenProps['options']\n): ScreenProps['options'] {\n return (args) => {\n // Only eager load generated components\n const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null;\n const staticResult = typeof staticOptions === 'function' ? staticOptions(args) : staticOptions;\n const dynamicResult = typeof options === 'function' ? options?.(args) : options;\n const output = {\n ...staticResult,\n ...dynamicResult,\n };\n\n // Prevent generated screens from showing up in the tab bar.\n if (route.internal) {\n output.tabBarItemStyle = { display: 'none' };\n output.tabBarButton = () => null;\n // TODO: React Navigation doesn't provide a way to prevent rendering the drawer item.\n output.drawerItemStyle = { height: 0, display: 'none' };\n }\n\n return output;\n };\n}\n\nexport function routeToScreen(\n route: RouteNode,\n { options, getId, ...props }: Partial = {}\n) {\n return (\n getQualifiedRouteComponent(route)}\n />\n );\n}\n\nexport function getSingularId(name: string, options: Record = {}) {\n return name\n .split('/')\n .map((segment) => {\n if (segment.startsWith('[...')) {\n return options.params?.[segment.slice(4, -1)]?.join('/') || segment;\n } else if (segment.startsWith('[') && segment.endsWith(']')) {\n return options.params?.[segment.slice(1, -1)] || segment;\n } else {\n return segment;\n }\n })\n .join('/');\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/src/useScreens.tsx b/packages/expo-router/src/useScreens.tsx index 18e531010f20b7..54b17432749448 100644 --- a/packages/expo-router/src/useScreens.tsx +++ b/packages/expo-router/src/useScreens.tsx @@ -369,6 +369,7 @@ export function getQualifiedRouteComponent(value: RouteNode) { Date: Tue, 12 May 2026 16:29:43 +0530 Subject: [PATCH 02/11] [docs] Fix Props rendering on API reference pages (#45662) # Why The `Props` block on API reference pages renders as a bare list of rows separated by thin top borders, without the rounded outer card that wraps every other API section (Constants, Methods, Types, Enums). The mismatch is visible on `/versions/v56.0.0/sdk/router/experimental-stack/`, where the Props block sits right above a Types card and reads as visually unfinished. CleanShot 2026-05-11 at 16 19 12@2x # How Fix Props rendering on API reference pages by using `STYLES_APIBOX`. # Test Plan CleanShot 2026-05-12 at 13 29 53@2x # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../plugins/__snapshots__/APISection.test.tsx.snap | 2 +- docs/components/plugins/api/APISectionProps.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/components/plugins/__snapshots__/APISection.test.tsx.snap b/docs/components/plugins/__snapshots__/APISection.test.tsx.snap index 5099f98557cb8d..91c9ce2267c0ea 100644 --- a/docs/components/plugins/__snapshots__/APISection.test.tsx.snap +++ b/docs/components/plugins/__snapshots__/APISection.test.tsx.snap @@ -372,7 +372,7 @@ for more details.

arr.findIndex(t => t?.name === dec?.name) === i); return ( -
+
*:last-child]:mb-0!')}> {propsDeclarations?.map(prop => prop ? renderProp( From 853c4af94e64d3a09a3e37427623e54d837103c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20=C5=9Awierk?= <58403334+sswrk@users.noreply.github.com> Date: Tue, 12 May 2026 13:50:58 +0200 Subject: [PATCH 03/11] [docs] Correct the Gradle JVM args in build infrastructure documentation (#45248) # Why This PR partly addresses this issue: https://github.com/expo/eas-cli/issues/3682. In the issue, the user reports that the documentation of Gradle JVM args doesn't match the actual `GRADLE_OPTS` set in builds. # How I have compared the documentation with the worker code (https://github.com/expo/eas-cli/blob/8229dc826513845d82ac4279d0b62394623935cf/packages/worker/src/env.ts#L84-L105), and confirmed the discrepancy. To clarify the Gradle opts, I've created a dedicated sub-section in "Build reference -> Infrastructure" documentation. Apart from clarifying the `GRADLE_OPTS` value, I have also added a warning about those opts overriding JVM args set in a project's `gradle.properties`; I have also provided an information about the possibility to override the `GRADLE_OPTS` by specifying them in `env` manually. # Test Plan Tested manually image # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- docs/pages/build-reference/infrastructure.mdx | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/pages/build-reference/infrastructure.mdx b/docs/pages/build-reference/infrastructure.mdx index 3070d258b324ac..97bd80a2ae2e4f 100644 --- a/docs/pages/build-reference/infrastructure.mdx +++ b/docs/pages/build-reference/infrastructure.mdx @@ -1,5 +1,5 @@ --- -modificationDate: September 17th, 2025 +modificationDate: May 12th, 2026 title: Build server infrastructure sidebar_title: Server infrastructure maxHeadingDepth: 4 @@ -44,15 +44,7 @@ Android builders run on virtual machines in an isolated environment. Every build - [npm cache deployed with Kubernetes](/build-reference/caching/#javascript-dependencies) - [Maven cache deployed with Kubernetes](/build-reference/caching/#android-dependencies) -- Global Gradle configuration in **~/.gradle/gradle.properties**: - - ```ini ~/.gradle/gradle.properties - org.gradle.jvmargs=-Xmx14g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - org.gradle.parallel=true - org.gradle.configureondemand=true - org.gradle.daemon=false - ``` - +- Gradle JVM args are injected via the `GRADLE_OPTS` environment variable when the build environment is provisioned. See [Gradle JVM args](#gradle-jvm-args) below. - Global npm configuration in **~/.npmrc**: ```ini ~/.npmrc @@ -68,6 +60,32 @@ Android builders run on virtual machines in an isolated environment. Every build enableImmutableInstalls: false ``` +### Gradle JVM args + +EAS Build sets the `GRADLE_OPTS` environment variable on the build VM (the worker) before Gradle runs. The values depend on the [resource class](/eas/json/#resourceclass) you select: + +| Resource class | `-Xmx` (max heap) | +| -------------- | ----------------- | +| `medium` | `4g` | +| `large` | `8g` | + +In addition to `-Xmx`, the worker passes the following JVM args to the Gradle build JVM via `-Dorg.gradle.jvmargs`: + +- `-XX:MaxMetaspaceSize=1g` +- `-XX:+HeapDumpOnOutOfMemoryError` +- `-Dfile.encoding=UTF-8` + +The worker also sets these top-level Gradle properties on `GRADLE_OPTS`: + +- `-Dorg.gradle.parallel=true` +- `-Dorg.gradle.daemon=false` + +> **warning** The worker sets `org.gradle.jvmargs` via `GRADLE_OPTS`, which overrides any `org.gradle.jvmargs` defined in your project's **gradle.properties**. + +#### Overriding `GRADLE_OPTS` + +You can replace the worker default by setting `GRADLE_OPTS` under a build profile's [`env`](/eas/json/#env) in **eas.json**, in a [workflow file](/eas/workflows/syntax/#jobsjob_idenv), or with [EAS Environment Variables](/eas/environment-variables/). Project environment values take precedence over the worker's default values. + ### Android server images #### `ubuntu-24.04-jdk-17-ndk-r27b-sdk-55` (`latest`, `sdk-55`) From f6e4c1e291f6601ce96114258552d165abb49637 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Tue, 12 May 2026 14:02:03 +0200 Subject: [PATCH 04/11] [expo-observe] add expo-router integration (#45534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Wire `expo-observe` up to `expo-router` so navigation produces the metrics App Metrics needs out of the box: time-to-render (TTR) for every screen plus time-to-interactive (TTI) for screens that call `useObserve().markInteractive()`. Previously the integration scaffolding existed but emitted nothing — no listeners, no metrics. # How ```mermaid sequenceDiagram participant App as App launch participant Router as expo-router participant Init as init.ts (listeners) participant Hook as useObserveForRouter participant Metrics as AppMetrics App->>Init: appLaunchTime = Date.now() Router->>Init: pageFocused (first) Init->>Metrics: TTR { isAppLaunch: true } Router->>Init: actionDispatched (NAVIGATE) Note right of Init: store dispatchTime Router->>Init: pageFocused Init->>Metrics: TTR { isAppLaunch: false } Hook->>Metrics: markInteractive() → TTI (now − dispatchTime) ``` # Test Plan - Observe tester -> check session for navigation category - Click house Screenshot 2026-05-08 at 20 59 08 # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../__e2e__/native-navigation/app/_layout.tsx | 1 - .../modules/appmetrics/AppMetricsModule.kt | 29 +-- .../java/expo/modules/appmetrics/Metrics.kt | 3 +- .../logevents/AttributeValidation.kt | 12 -- .../appmetrics/storage/SessionMappers.kt | 25 ++- .../expo/modules/appmetrics/utils/JsonAny.kt | 10 + .../modules/appmetrics/utils/JsonAnyTest.kt | 41 ++++ .../expo-app-metrics/build/module.web.d.ts | 8 +- .../build/module.web.d.ts.map | 2 +- packages/expo-app-metrics/build/module.web.js | 2 +- .../expo-app-metrics/build/module.web.js.map | 2 +- packages/expo-app-metrics/build/types.d.ts | 8 +- .../expo-app-metrics/build/types.d.ts.map | 2 +- packages/expo-app-metrics/build/types.js.map | 2 +- packages/expo-app-metrics/src/module.web.ts | 7 +- packages/expo-app-metrics/src/types.ts | 11 +- .../expo/modules/observe/OpenTelemetry.kt | 4 + .../expo/modules/observe/OpenTelemetryTest.kt | 32 +++ .../build/ObserveProvider.d.ts.map | 2 +- .../expo-observe/build/ObserveProvider.js | 5 +- .../expo-observe/build/ObserveProvider.js.map | 2 +- .../ObserveRouterIntegrationProvider.d.ts | 5 + .../ObserveRouterIntegrationProvider.d.ts.map | 1 + .../ObserveRouterIntegrationProvider.js | 20 ++ .../ObserveRouterIntegrationProvider.js.map | 1 + .../build/integrations/expo-router/index.d.ts | 1 + .../integrations/expo-router/index.d.ts.map | 2 +- .../build/integrations/expo-router/index.js | 1 + .../integrations/expo-router/index.js.map | 2 +- .../build/integrations/expo-router/init.d.ts | 5 + .../integrations/expo-router/init.d.ts.map | 2 +- .../build/integrations/expo-router/init.js | 76 +++++++ .../integrations/expo-router/init.js.map | 2 +- .../integrations/expo-router/storage.d.ts | 27 +++ .../integrations/expo-router/storage.d.ts.map | 1 + .../build/integrations/expo-router/storage.js | 10 + .../integrations/expo-router/storage.js.map | 1 + .../expo-router/useObserveForRouter.d.ts.map | 2 +- .../expo-router/useObserveForRouter.js | 52 ++++- .../expo-router/useObserveForRouter.js.map | 2 +- packages/expo-observe/src/ObserveProvider.tsx | 4 +- .../ObserveRouterIntegrationProvider.tsx | 31 +++ ...eRouterIntegrationProvider.test.native.tsx | 130 +++++++++++ .../__tests__/init.test.android.ts | 203 ++++++++++++++++++ .../expo-router/__tests__/init.test.ios.ts | 85 ++++++++ .../expo-router/__tests__/storage.test.ts | 22 ++ .../useObserveForRouter.test.android.tsx | 67 +++++- .../useObserveForRouter.test.ios.tsx | 86 ++++++++ .../src/integrations/expo-router/index.ts | 1 + .../src/integrations/expo-router/init.ts | 89 ++++++++ .../src/integrations/expo-router/storage.ts | 38 ++++ .../expo-router/useObserveForRouter.ts | 61 +++++- packages/expo-router/build/exports.d.ts | 1 + packages/expo-router/build/exports.d.ts.map | 2 +- packages/expo-router/build/exports.js.map | 2 +- .../build/global-state/store.d.ts.map | 2 +- .../expo-router/build/global-state/store.js | 2 + .../build/global-state/store.js.map | 2 +- .../native-tabs/NativeTabsView.shared.d.ts | 4 +- .../NativeTabsView.shared.d.ts.map | 2 +- .../build/navigationEvents/index.d.ts | 9 +- .../build/navigationEvents/index.d.ts.map | 2 +- .../build/navigationEvents/index.js | 43 +--- .../build/navigationEvents/index.js.map | 2 +- .../build/navigationEvents/navigation.d.ts | 2 + .../navigationEvents/navigation.d.ts.map | 1 + .../build/navigationEvents/navigation.js | 21 ++ .../build/navigationEvents/navigation.js.map | 1 + .../build/navigationEvents/types.d.ts | 11 +- .../build/navigationEvents/types.d.ts.map | 2 +- .../build/navigationEvents/types.js.map | 2 +- .../expo-router/build/useScreens.d.ts.map | 2 +- packages/expo-router/build/useScreens.js | 32 +-- packages/expo-router/build/useScreens.js.map | 2 +- packages/expo-router/src/exports.ts | 8 + .../expo-router/src/global-state/store.ts | 2 + .../expo-router/src/navigationEvents/index.ts | 68 ++---- .../src/navigationEvents/navigation.ts | 18 ++ .../expo-router/src/navigationEvents/types.ts | 13 +- packages/expo-router/src/useScreens.tsx | 34 +-- 80 files changed, 1296 insertions(+), 236 deletions(-) create mode 100644 packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/JsonAnyTest.kt create mode 100644 packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts create mode 100644 packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts.map create mode 100644 packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js create mode 100644 packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map create mode 100644 packages/expo-observe/build/integrations/expo-router/storage.d.ts create mode 100644 packages/expo-observe/build/integrations/expo-router/storage.d.ts.map create mode 100644 packages/expo-observe/build/integrations/expo-router/storage.js create mode 100644 packages/expo-observe/build/integrations/expo-router/storage.js.map create mode 100644 packages/expo-observe/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx create mode 100644 packages/expo-observe/src/integrations/expo-router/__tests__/ObserveRouterIntegrationProvider.test.native.tsx create mode 100644 packages/expo-observe/src/integrations/expo-router/__tests__/init.test.android.ts create mode 100644 packages/expo-observe/src/integrations/expo-router/__tests__/init.test.ios.ts create mode 100644 packages/expo-observe/src/integrations/expo-router/__tests__/storage.test.ts create mode 100644 packages/expo-observe/src/integrations/expo-router/storage.ts create mode 100644 packages/expo-router/build/navigationEvents/navigation.d.ts create mode 100644 packages/expo-router/build/navigationEvents/navigation.d.ts.map create mode 100644 packages/expo-router/build/navigationEvents/navigation.js create mode 100644 packages/expo-router/build/navigationEvents/navigation.js.map create mode 100644 packages/expo-router/src/navigationEvents/navigation.ts diff --git a/apps/router-e2e/__e2e__/native-navigation/app/_layout.tsx b/apps/router-e2e/__e2e__/native-navigation/app/_layout.tsx index d85425ee4f334e..cccc8e148960af 100644 --- a/apps/router-e2e/__e2e__/native-navigation/app/_layout.tsx +++ b/apps/router-e2e/__e2e__/native-navigation/app/_layout.tsx @@ -14,7 +14,6 @@ unstable_navigationEvents.enable(); ); }); }); -unstable_navigationEvents.saveCurrentPathname(); export default function Layout() { return ( diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt index 3793021a44b75d..9eba8a9804bb5d 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt @@ -4,17 +4,18 @@ import android.content.Context import expo.modules.appmetrics.appstartup.AppStartupManager import expo.modules.appmetrics.logevents.LogEventOptions import expo.modules.appmetrics.logevents.Severity -import expo.modules.appmetrics.logevents.attributesToJsonObject import expo.modules.appmetrics.logevents.sanitizeLogEventAttributes import expo.modules.appmetrics.logevents.validateEventBody import expo.modules.appmetrics.logevents.validateEventName import expo.modules.appmetrics.memory.MemoryMetricsManager +import expo.modules.appmetrics.storage.JsMetric import expo.modules.appmetrics.storage.JsSession import expo.modules.appmetrics.storage.LogRecord import expo.modules.appmetrics.storage.Metric import expo.modules.appmetrics.storage.SessionManager import expo.modules.appmetrics.updates.UpdatesMonitoring import expo.modules.appmetrics.updates.UpdatesStateEvent +import expo.modules.appmetrics.utils.JsonAny import expo.modules.appmetrics.utils.TimeUtils import expo.modules.interfaces.constants.ConstantsInterface import expo.modules.kotlin.exception.Exceptions @@ -30,7 +31,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json class AppMetricsModule : Module(), UpdatesStateChangeListener { private val context: Context @@ -97,7 +97,7 @@ class AppMetricsModule : Module(), UpdatesStateChangeListener { name = validatedName, body = validatedBody, severity = severity.rawValue, - attributes = sanitized.attributes?.let { Json.encodeToString(attributesToJsonObject(it)) }, + attributes = sanitized.attributes?.let { JsonAny.encodeMapToJsonString(it) }, droppedAttributesCount = sanitized.droppedCount ) ), @@ -192,8 +192,8 @@ class AppMetricsModule : Module(), UpdatesStateChangeListener { } } - AsyncFunction("addCustomMetricToSession") Coroutine { sessionId: String, metric: PartialMetric -> - sessionManager.addMetrics(listOf(metric.toMetric(sessionId)), sessionId = sessionId) + AsyncFunction("addCustomMetricToSession") Coroutine { metric: JsMetric -> + sessionManager.addMetrics(listOf(metric.toMetric()), sessionId = metric.sessionId) } AsyncFunction("getMainSession") Coroutine { -> @@ -237,25 +237,6 @@ class AppMetricsModule : Module(), UpdatesStateChangeListener { } } -data class PartialMetric( - @Field val category: String, - @Field val name: String, - @Field val value: Double, - @Field val routeName: String? = null, - @Field val params: Map? = null -) : Record { - fun toMetric(sessionId: String): Metric = - Metric( - sessionId = sessionId, - timestamp = TimeUtils.getCurrentTimestampInISOFormat(), - category = category, - name = name, - value = value, - routeName = routeName, - params = params?.let { Json.encodeToString(it) } - ) -} - data class MetricAttributes( @Field val routeName: String? = null, @Field val params: Map? = null diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/Metrics.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/Metrics.kt index 0483ba1f868699..1c0becc9734b05 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/Metrics.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/Metrics.kt @@ -48,5 +48,6 @@ enum class MetricCategory( AppStartup("appStartup"), FrameRate("frameRate"), Memory("memory"), - Updates("updates") + Updates("updates"), + Navigation("navigation") } diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/logevents/AttributeValidation.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/logevents/AttributeValidation.kt index 8a40ffed392702..05ed57b275d0ca 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/logevents/AttributeValidation.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/logevents/AttributeValidation.kt @@ -2,8 +2,6 @@ package expo.modules.appmetrics.logevents import android.util.Log import expo.modules.appmetrics.TAG -import expo.modules.appmetrics.utils.JsonAny -import kotlinx.serialization.json.JsonObject /** * Patterns that match attribute keys reserved by the SDK. Caller-provided @@ -115,13 +113,3 @@ internal fun sanitizeLogEventAttributes(attributes: Map?): Sanitiz droppedCount = emptyKeyDrops + reservedKeyDrops.size + overflowDrops ) } - -/** - * Converts a sanitized attribute map to a `JsonObject` for storage. Values - * whose type cannot be represented in OTLP (e.g. `Date`, NaN/Infinity doubles) - * are encoded as JSON `null` here; the typed-attribute encoder at dispatch - * time will skip them and add to `droppedAttributesCount` accordingly. - */ -internal fun attributesToJsonObject(attributes: Map): JsonObject { - return JsonObject(attributes.mapValues { (_, value) -> JsonAny.toElement(value) }) -} diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/storage/SessionMappers.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/storage/SessionMappers.kt index 43b8bac389e6cd..6cdcf45579e894 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/storage/SessionMappers.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/storage/SessionMappers.kt @@ -1,8 +1,10 @@ package expo.modules.appmetrics.storage import expo.modules.appmetrics.utils.JsonAny +import expo.modules.appmetrics.utils.TimeUtils import expo.modules.kotlin.records.Field import expo.modules.kotlin.records.Record +import java.util.UUID import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject @@ -39,16 +41,29 @@ data class JsSession( } data class JsMetric( - @Field val metricId: String, @Field val sessionId: String, - @Field val timestamp: String, @Field val category: String, @Field val name: String, @Field val value: Double, - @Field val routeName: String?, - @Field val updateId: String?, - @Field val params: Map? + @Field val metricId: String? = UUID.randomUUID().toString(), + @Field val timestamp: String = TimeUtils.getCurrentTimestampInISOFormat(), + @Field val routeName: String? = null, + @Field val updateId: String? = null, + @Field val params: Map? = null ) : Record { + fun toMetric(): Metric = + Metric( + metricId = metricId ?: UUID.randomUUID().toString(), + sessionId = sessionId, + timestamp = timestamp, + category = category, + name = name, + value = value, + routeName = routeName, + updateId = updateId, + params = params?.let { JsonAny.encodeMapToJsonString(it) } + ) + companion object { fun fromMetric(metric: Metric): JsMetric = JsMetric( diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/JsonAny.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/JsonAny.kt index 3e265d086b1da1..89286e61200eba 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/JsonAny.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/utils/JsonAny.kt @@ -1,5 +1,6 @@ package expo.modules.appmetrics.utils +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull @@ -56,4 +57,13 @@ object JsonAny { is JsonArray -> element.map { fromElement(it) } } } + + // `Json.encodeToString(map)` would fail at runtime with "Serializer for class + // 'Any' is not found" — kotlinx.serialization has no built-in `Any` + // serializer, so we route every value through `toElement` first. + fun encodeMapToJsonString(map: Map): String = + Json.encodeToString( + JsonObject.serializer(), + JsonObject(map.mapValues { (_, v) -> toElement(v) }) + ) } diff --git a/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/JsonAnyTest.kt b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/JsonAnyTest.kt new file mode 100644 index 00000000000000..407194043b005a --- /dev/null +++ b/packages/expo-app-metrics/android/src/test/java/expo/modules/appmetrics/utils/JsonAnyTest.kt @@ -0,0 +1,41 @@ +package expo.modules.appmetrics.utils + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, sdk = [28]) +class JsonAnyTest { + @Test + fun `encodeMapToJsonString round-trips primitives, booleans and nested structures`() { + val encoded = JsonAny.encodeMapToJsonString( + mapOf( + "isInitial" to true, + "isAppLaunch" to false, + "screen" to "Home", + "attempt" to 3, + "ratio" to 1.5, + "missing" to null, + "tags" to listOf("a", "b"), + "nested" to mapOf("k" to true) + ) + ) + + @Suppress("UNCHECKED_CAST") + val decoded = JsonAny.fromElement(Json.decodeFromString(encoded)) as Map + assertEquals(true, decoded["isInitial"]) + assertEquals(false, decoded["isAppLaunch"]) + assertEquals("Home", decoded["screen"]) + assertEquals(3L, decoded["attempt"]) + assertEquals(1.5, decoded["ratio"]) + assertNull(decoded["missing"]) + assertEquals(listOf("a", "b"), decoded["tags"]) + assertEquals(mapOf("k" to true), decoded["nested"]) + } +} diff --git a/packages/expo-app-metrics/build/module.web.d.ts b/packages/expo-app-metrics/build/module.web.d.ts index bec19e61032dd8..27eab1950936b8 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts +++ b/packages/expo-app-metrics/build/module.web.d.ts @@ -1,12 +1,8 @@ import { NativeModule } from 'expo'; -import type { ExpoAppMetricsModuleType, LogEventOptions, MetricAttributes } from './types'; +import type { ExpoAppMetricsModuleType, LogEventOptions, Metric, MetricAttributes } from './types'; export * from './types'; declare class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType { - addCustomMetricToSession(sessionId: string, metric: { - category: string; - name: string; - value: number; - }): Promise; + addCustomMetricToSession(metric: Metric): Promise; markFirstRender(): Promise; markInteractive(attributes?: MetricAttributes): Promise; logEvent(name: string, options?: LogEventOptions): void; diff --git a/packages/expo-app-metrics/build/module.web.d.ts.map b/packages/expo-app-metrics/build/module.web.d.ts.map index e972f55bbf4361..abc89bc923cf30 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts.map +++ b/packages/expo-app-metrics/build/module.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3F,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC;IAGV,eAAe;IACf,eAAe,CAAC,UAAU,CAAC,EAAE,gBAAgB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAC1C,gBAAgB;IAGhB,kBAAkB;IAClB,cAAc;IAGpB,mBAAmB;IACnB,YAAY;IACZ,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAG9B,WAAW,CAAC,SAAS,EAAE,MAAM;IACvB,cAAc;CAGrB;;AAED,wBAAyE"} \ No newline at end of file +{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEnG,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAGjD,eAAe;IACf,eAAe,CAAC,UAAU,CAAC,EAAE,gBAAgB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAC1C,gBAAgB;IAGhB,kBAAkB;IAClB,cAAc;IAGpB,mBAAmB;IACnB,YAAY;IACZ,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAG9B,WAAW,CAAC,SAAS,EAAE,MAAM;IACvB,cAAc;CAGrB;;AAED,wBAAyE"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.web.js b/packages/expo-app-metrics/build/module.web.js index 32e3ab37c8acd5..e3a64647e81134 100644 --- a/packages/expo-app-metrics/build/module.web.js +++ b/packages/expo-app-metrics/build/module.web.js @@ -1,7 +1,7 @@ import { NativeModule, registerWebModule } from 'expo'; export * from './types'; class ExpoAppMetricsModule extends NativeModule { - addCustomMetricToSession(sessionId, metric) { + addCustomMetricToSession(metric) { throw new Error('Method not implemented.'); } async markFirstRender() { } diff --git a/packages/expo-app-metrics/build/module.web.js.map b/packages/expo-app-metrics/build/module.web.js.map index b9322b23d36867..835d197cc84e78 100644 --- a/packages/expo-app-metrics/build/module.web.js.map +++ b/packages/expo-app-metrics/build/module.web.js.map @@ -1 +1 @@ -{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CACtB,SAAiB,EACjB,MAAyD;QAEzD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,CAAC,UAA6B,IAAG,CAAC;IACvD,QAAQ,CAAC,IAAY,EAAE,OAAyB,IAAG,CAAC;IACpD,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,KAAK,CAAC,cAAc;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,mBAAmB,KAAI,CAAC;IACxB,YAAY,KAAI,CAAC;IACjB,YAAY,CAAC,QAAiB;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,WAAW,CAAC,SAAiB,IAAG,CAAC;IACjC,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType, LogEventOptions, MetricAttributes } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(\n sessionId: string,\n metric: { category: string; name: string; value: number }\n ): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive(attributes?: MetricAttributes) {}\n logEvent(name: string, options?: LogEventOptions) {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n async getAllSessions() {\n return [];\n }\n simulateCrashReport() {}\n triggerCrash() {}\n startSession(metadata?: string) {\n return '';\n }\n stopSession(sessionId: string) {}\n async getMainSession() {\n return null;\n }\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file +{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CAAC,MAAc;QACrC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,CAAC,UAA6B,IAAG,CAAC;IACvD,QAAQ,CAAC,IAAY,EAAE,OAAyB,IAAG,CAAC;IACpD,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,KAAK,CAAC,cAAc;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,mBAAmB,KAAI,CAAC;IACxB,YAAY,KAAI,CAAC;IACjB,YAAY,CAAC,QAAiB;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,WAAW,CAAC,SAAiB,IAAG,CAAC;IACjC,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType, LogEventOptions, Metric, MetricAttributes } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(metric: Metric): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive(attributes?: MetricAttributes) {}\n logEvent(name: string, options?: LogEventOptions) {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n async getAllSessions() {\n return [];\n }\n simulateCrashReport() {}\n triggerCrash() {}\n startSession(metadata?: string) {\n return '';\n }\n stopSession(sessionId: string) {}\n async getMainSession() {\n return null;\n }\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.d.ts b/packages/expo-app-metrics/build/types.d.ts index a4ee6610e361e2..9fd82b23de3d51 100644 --- a/packages/expo-app-metrics/build/types.d.ts +++ b/packages/expo-app-metrics/build/types.d.ts @@ -294,13 +294,7 @@ export interface ExpoAppMetricsModuleType { * @private This API is unstable and may change without notice. * @platform android */ - addCustomMetricToSession(sessionId: string, metric: { - category: string; - name: string; - value: number; - routeName?: string; - params?: Record; - }): Promise; + addCustomMetricToSession(metric: Metric): Promise; /** * Returns the current main session, including its metrics. Resolves to * `null` if the session row hasn't been persisted yet. diff --git a/packages/expo-app-metrics/build/types.d.ts.map b/packages/expo-app-metrics/build/types.d.ts.map index b919c2e4dae608..9dc6d1589c26ae 100644 --- a/packages/expo-app-metrics/build/types.d.ts.map +++ b/packages/expo-app-metrics/build/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.js.map b/packages/expo-app-metrics/build/types.js.map index 7aa98299ad3c92..b4f2ab0325129c 100644 --- a/packages/expo-app-metrics/build/types.js.map +++ b/packages/expo-app-metrics/build/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport type MetricAttributes = {\n /**\n * Name of the route associated with the metric. Some metrics populate this\n * with a sensible default when omitted — for example, the TTI metric falls\n * back to the initial route name detected from the router.\n */\n routeName?: string;\n /**\n * Custom parameters to attach to the metric.\n */\n params?: Record;\n};\n\n/**\n * Severity of a log event, ordered from least to most severe:\n *\n * - `\"trace\"` — Fine-grained tracing, typically only useful while reproducing\n * a specific issue.\n * - `\"debug\"` — Diagnostic detail useful during development; usually filtered\n * out in production.\n * - `\"info\"` — Routine, expected events that record normal app behavior.\n * - `\"warn\"` — Unexpected but recoverable conditions worth investigating.\n * - `\"error\"` — An operation failed; the app continues running but is in a\n * degraded state.\n * - `\"fatal\"` — A severe failure, often immediately followed by app termination.\n */\nexport type LogSeverity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * Value types accepted in a log event's `attributes` map. Strings, numbers,\n * and booleans are stored as typed primitives; arrays and nested maps preserve\n * their structure. Other JS values (functions, `Date`, `undefined`, etc.) are\n * not supported and may be dropped by downstream consumers.\n */\nexport type LogAttributeValue =\n | string\n | number\n | boolean\n | LogAttributeValue[]\n | { [key: string]: LogAttributeValue };\n\n/**\n * A single log event collected during a session.\n */\nexport type LogRecord = {\n /**\n * ISO 8601 timestamp of when the record was created.\n */\n timestamp: string;\n /**\n * Event name.\n */\n name: string;\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n */\n severity: LogSeverity;\n};\n\n/**\n * Optional configuration accepted by `logEvent`. The event name is passed as\n * the first positional argument since it's required and the only field most\n * callers set.\n */\nexport type LogEventOptions = {\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n *\n * @default \"info\"\n */\n severity?: LogSeverity | null;\n};\n\nexport type SessionType = 'main' | 'foreground' | 'screen' | 'custom' | 'unknown';\n\nexport type CrashKind =\n | 'badAccess'\n | 'fatalError'\n | 'divideByZero'\n | 'forceUnwrapNil'\n | 'arrayOutOfBounds'\n | 'objcException'\n | 'stackOverflow';\n\ntype SessionBase = {\n id: string;\n startDate: string;\n endDate?: string | null;\n metrics: Metric[];\n logs: LogRecord[];\n};\n\nexport type MainSession = SessionBase & {\n type: 'main';\n crashReport?: CrashReport | null;\n};\n\nexport type GenericSession = SessionBase & {\n type: Exclude;\n};\n\nexport type Session = MainSession | GenericSession;\n\nexport type CallStackFrame = {\n binaryName?: string | null;\n binaryUUID?: string | null;\n address?: number | null;\n offsetIntoBinaryTextSegment?: number | null;\n sampleCount?: number | null;\n subFrames?: CallStackFrame[] | null;\n /**\n * Resolved symbol from on-device `dladdr` symbolication. Swift and Itanium-ABI C++\n * names are demangled; Objective-C selectors and plain C symbols are returned as-is.\n * `null` when the binary is not loaded in this process or `dladdr` could not resolve it.\n *\n * @platform ios\n */\n symbol?: string | null;\n};\n\nexport type CallStack = {\n threadAttributed?: boolean | null;\n callStackRootFrames?: CallStackFrame[] | null;\n};\n\nexport type CallStackTree = {\n callStacks?: CallStack[] | null;\n};\n\nexport type CrashReport = {\n exceptionType?: number | null;\n exceptionCode?: number | null;\n signal?: number | null;\n terminationReason?: string | null;\n virtualMemoryRegionInfo?: string | null;\n exceptionReason?: {\n composedMessage: string;\n formatString: string;\n arguments: string[];\n exceptionType: string;\n className: string;\n exceptionName: string;\n } | null;\n callStackTree?: CallStackTree | null;\n timestampBegin: string;\n timestampEnd: string;\n ingestedAt: string;\n};\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(attributes?: MetricAttributes): void;\n /**\n * Records a log event against the current main session. The event is\n * persisted locally and dispatched on the next `dispatchEvents()` flush as an\n * OpenTelemetry log record sent to the `/v1/logs` endpoint.\n *\n * Severity defaults to `\"info\"` when not provided.\n *\n * @param name Event name. Maps to the OpenTelemetry `event.name` attribute.\n * @param options Optional body, attributes, and severity overrides.\n */\n logEvent(name: string, options?: LogEventOptions): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n /**\n * Returns all sessions across the current and historical entries,\n * ordered with the current launch first.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n getAllSessions(): Promise;\n\n /**\n * Simulates a crash report, attributing it to the current main session.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n simulateCrashReport(): void;\n\n /**\n * Intentionally crashes the app to produce a real MetricKit diagnostic.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n triggerCrash(kind: CrashKind): void;\n\n /**\n * Starts a new app metrics session. Returns the session ID.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n startSession(): string;\n /**\n * Stops the app metrics session with the given session ID.\n *\n * @platform android\n */\n stopSession(sessionId: string): void;\n /**\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n addCustomMetricToSession(\n sessionId: string,\n metric: {\n category: string;\n name: string;\n value: number;\n routeName?: string;\n params?: Record;\n }\n ): Promise;\n /**\n * Returns the current main session, including its metrics. Resolves to\n * `null` if the session row hasn't been persisted yet.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n getMainSession(): Promise;\n}\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport type MetricAttributes = {\n /**\n * Name of the route associated with the metric. Some metrics populate this\n * with a sensible default when omitted — for example, the TTI metric falls\n * back to the initial route name detected from the router.\n */\n routeName?: string;\n /**\n * Custom parameters to attach to the metric.\n */\n params?: Record;\n};\n\n/**\n * Severity of a log event, ordered from least to most severe:\n *\n * - `\"trace\"` — Fine-grained tracing, typically only useful while reproducing\n * a specific issue.\n * - `\"debug\"` — Diagnostic detail useful during development; usually filtered\n * out in production.\n * - `\"info\"` — Routine, expected events that record normal app behavior.\n * - `\"warn\"` — Unexpected but recoverable conditions worth investigating.\n * - `\"error\"` — An operation failed; the app continues running but is in a\n * degraded state.\n * - `\"fatal\"` — A severe failure, often immediately followed by app termination.\n */\nexport type LogSeverity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * Value types accepted in a log event's `attributes` map. Strings, numbers,\n * and booleans are stored as typed primitives; arrays and nested maps preserve\n * their structure. Other JS values (functions, `Date`, `undefined`, etc.) are\n * not supported and may be dropped by downstream consumers.\n */\nexport type LogAttributeValue =\n | string\n | number\n | boolean\n | LogAttributeValue[]\n | { [key: string]: LogAttributeValue };\n\n/**\n * A single log event collected during a session.\n */\nexport type LogRecord = {\n /**\n * ISO 8601 timestamp of when the record was created.\n */\n timestamp: string;\n /**\n * Event name.\n */\n name: string;\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n */\n severity: LogSeverity;\n};\n\n/**\n * Optional configuration accepted by `logEvent`. The event name is passed as\n * the first positional argument since it's required and the only field most\n * callers set.\n */\nexport type LogEventOptions = {\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n *\n * @default \"info\"\n */\n severity?: LogSeverity | null;\n};\n\nexport type SessionType = 'main' | 'foreground' | 'screen' | 'custom' | 'unknown';\n\nexport type CrashKind =\n | 'badAccess'\n | 'fatalError'\n | 'divideByZero'\n | 'forceUnwrapNil'\n | 'arrayOutOfBounds'\n | 'objcException'\n | 'stackOverflow';\n\ntype SessionBase = {\n id: string;\n startDate: string;\n endDate?: string | null;\n metrics: Metric[];\n logs: LogRecord[];\n};\n\nexport type MainSession = SessionBase & {\n type: 'main';\n crashReport?: CrashReport | null;\n};\n\nexport type GenericSession = SessionBase & {\n type: Exclude;\n};\n\nexport type Session = MainSession | GenericSession;\n\nexport type CallStackFrame = {\n binaryName?: string | null;\n binaryUUID?: string | null;\n address?: number | null;\n offsetIntoBinaryTextSegment?: number | null;\n sampleCount?: number | null;\n subFrames?: CallStackFrame[] | null;\n /**\n * Resolved symbol from on-device `dladdr` symbolication. Swift and Itanium-ABI C++\n * names are demangled; Objective-C selectors and plain C symbols are returned as-is.\n * `null` when the binary is not loaded in this process or `dladdr` could not resolve it.\n *\n * @platform ios\n */\n symbol?: string | null;\n};\n\nexport type CallStack = {\n threadAttributed?: boolean | null;\n callStackRootFrames?: CallStackFrame[] | null;\n};\n\nexport type CallStackTree = {\n callStacks?: CallStack[] | null;\n};\n\nexport type CrashReport = {\n exceptionType?: number | null;\n exceptionCode?: number | null;\n signal?: number | null;\n terminationReason?: string | null;\n virtualMemoryRegionInfo?: string | null;\n exceptionReason?: {\n composedMessage: string;\n formatString: string;\n arguments: string[];\n exceptionType: string;\n className: string;\n exceptionName: string;\n } | null;\n callStackTree?: CallStackTree | null;\n timestampBegin: string;\n timestampEnd: string;\n ingestedAt: string;\n};\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(attributes?: MetricAttributes): void;\n /**\n * Records a log event against the current main session. The event is\n * persisted locally and dispatched on the next `dispatchEvents()` flush as an\n * OpenTelemetry log record sent to the `/v1/logs` endpoint.\n *\n * Severity defaults to `\"info\"` when not provided.\n *\n * @param name Event name. Maps to the OpenTelemetry `event.name` attribute.\n * @param options Optional body, attributes, and severity overrides.\n */\n logEvent(name: string, options?: LogEventOptions): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n /**\n * Returns all sessions across the current and historical entries,\n * ordered with the current launch first.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n getAllSessions(): Promise;\n\n /**\n * Simulates a crash report, attributing it to the current main session.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n simulateCrashReport(): void;\n\n /**\n * Intentionally crashes the app to produce a real MetricKit diagnostic.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n triggerCrash(kind: CrashKind): void;\n\n /**\n * Starts a new app metrics session. Returns the session ID.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n startSession(): string;\n /**\n * Stops the app metrics session with the given session ID.\n *\n * @platform android\n */\n stopSession(sessionId: string): void;\n /**\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n addCustomMetricToSession(metric: Metric): Promise;\n /**\n * Returns the current main session, including its metrics. Resolves to\n * `null` if the session row hasn't been persisted yet.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n getMainSession(): Promise;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/src/module.web.ts b/packages/expo-app-metrics/src/module.web.ts index 2d7ad6db5962fa..6c03faea8e5cdc 100644 --- a/packages/expo-app-metrics/src/module.web.ts +++ b/packages/expo-app-metrics/src/module.web.ts @@ -1,14 +1,11 @@ import { NativeModule, registerWebModule } from 'expo'; -import type { ExpoAppMetricsModuleType, LogEventOptions, MetricAttributes } from './types'; +import type { ExpoAppMetricsModuleType, LogEventOptions, Metric, MetricAttributes } from './types'; export * from './types'; class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType { - addCustomMetricToSession( - sessionId: string, - metric: { category: string; name: string; value: number } - ): Promise { + addCustomMetricToSession(metric: Metric): Promise { throw new Error('Method not implemented.'); } async markFirstRender() {} diff --git a/packages/expo-app-metrics/src/types.ts b/packages/expo-app-metrics/src/types.ts index 1d8003d27e6cd4..c8f539301b295e 100644 --- a/packages/expo-app-metrics/src/types.ts +++ b/packages/expo-app-metrics/src/types.ts @@ -326,16 +326,7 @@ export interface ExpoAppMetricsModuleType { * @private This API is unstable and may change without notice. * @platform android */ - addCustomMetricToSession( - sessionId: string, - metric: { - category: string; - name: string; - value: number; - routeName?: string; - params?: Record; - } - ): Promise; + addCustomMetricToSession(metric: Metric): Promise; /** * Returns the current main session, including its metrics. Resolves to * `null` if the session row hasn't been persisted yet. diff --git a/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt b/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt index dd9d3e5408dbc8..0bdbf2cfdee72b 100644 --- a/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt +++ b/packages/expo-observe/android/src/main/java/expo/modules/observe/OpenTelemetry.kt @@ -189,6 +189,10 @@ private val metricNameMap = mapOf( // Updates (MetricCategory.Updates.categoryName to "updateDownloadTime") to "expo.updates.download_time", + + // Navigation + (MetricCategory.Navigation.categoryName to "ttr") to "expo.navigation.ttr", + (MetricCategory.Navigation.categoryName to "tti") to "expo.navigation.tti" ) fun EASMetric.toOTMetric(): OTMetric { diff --git a/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt b/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt index b2049d79bff944..f02bc2007aeec8 100644 --- a/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt +++ b/packages/expo-observe/android/src/test/java/expo/modules/observe/OpenTelemetryTest.kt @@ -106,6 +106,14 @@ class OpenTelemetryTest { ) } + @Test + fun `toOTMetric maps every known navigation pair to its OTel name`() { + val navigation = MetricCategory.Navigation.categoryName + + assertEquals("expo.navigation.tti", nameFor(navigation, "tti")) + assertEquals("expo.navigation.ttr", nameFor(navigation, "ttr")) + } + @Test fun `toOTMetric falls back to expo_unknown for an unmapped name in a known category`() { // Known category, name is not in the map. @@ -139,6 +147,30 @@ class OpenTelemetryTest { ) } + fun `navigation metric carries route name and custom params attributes`() { + val metric = EASMetric( + sessionId = testSessionId, + timestamp = "2026-01-01T00:00:00.000Z", + category = "navigation", + name = "ttr", + value = 0.25, + routeName = "/home", + customParams = JsonObject( + mapOf( + "isInitial" to JsonPrimitive(true), + "isAppLaunch" to JsonPrimitive(false) + ) + ) + ) + val attrs = metric.toOTMetric().gauge.dataPoints[0].attributes + .associate { it.key to it.value.stringValue } + + assertEquals("/home", attrs["expo.route_name"]) + val parsed = Json.parseToJsonElement(attrs["expo.custom_params"]!!).jsonObject + assertEquals(true, parsed["isInitial"]!!.jsonPrimitive.content.toBoolean()) + assertEquals(false, parsed["isAppLaunch"]!!.jsonPrimitive.content.toBoolean()) + } + // -- Metric structure -- @Test diff --git a/packages/expo-observe/build/ObserveProvider.d.ts.map b/packages/expo-observe/build/ObserveProvider.d.ts.map index e06d4bb392979e..757f2c5f14a4c8 100644 --- a/packages/expo-observe/build/ObserveProvider.d.ts.map +++ b/packages/expo-observe/build/ObserveProvider.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ObserveProvider.d.ts","sourceRoot":"","sources":["../src/ObserveProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE/C,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CAE9D"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/packages/expo-observe/build/ObserveProvider.js b/packages/expo-observe/build/ObserveProvider.js index 71d0902067a750..7fe3fc9c03e4fb 100644 --- a/packages/expo-observe/build/ObserveProvider.js +++ b/packages/expo-observe/build/ObserveProvider.js @@ -1,6 +1,7 @@ -import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; +import { jsx as _jsx } from "react/jsx-runtime"; import {} from 'react'; +import { ObserveRouterIntegrationProvider } from './integrations/expo-router/ObserveRouterIntegrationProvider'; export function ObserveProvider({ children }) { - return _jsx(_Fragment, { children: children }); + return _jsx(ObserveRouterIntegrationProvider, { children: children }); } //# sourceMappingURL=ObserveProvider.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/ObserveProvider.js.map b/packages/expo-observe/build/ObserveProvider.js.map index ee8dc503ea2d21..05294d58af21ca 100644 --- a/packages/expo-observe/build/ObserveProvider.js.map +++ b/packages/expo-observe/build/ObserveProvider.js.map @@ -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,4BAAG,QAAQ,GAAI,CAAC;AACzB,CAAC","sourcesContent":["import { type PropsWithChildren } from 'react';\n\nexport function ObserveProvider({ children }: PropsWithChildren) {\n return <>{children};\n}\n"]} \ No newline at end of file +{"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 {children};\n}\n"]} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts new file mode 100644 index 00000000000000..a67453c281f846 --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts @@ -0,0 +1,5 @@ +import { type PropsWithChildren } from 'react'; +import { type RouterIntegrationStorage } from './storage'; +export declare const ObserveRouterIntegrationContext: import("react").Context; +export declare function ObserveRouterIntegrationProvider({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element; +//# sourceMappingURL=ObserveRouterIntegrationProvider.d.ts.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts.map b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts.map new file mode 100644 index 00000000000000..769b1f09feb0fe --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.d.ts.map @@ -0,0 +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,2CAsB/E"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js new file mode 100644 index 00000000000000..831bd494b7c60a --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js @@ -0,0 +1,20 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import { createContext, useEffect, useRef, useState } from 'react'; +import { initListeners, isInitialized } from './init'; +import { optionalRouter } from './router'; +import { createRouterIntegrationStorage } from './storage'; +export const ObserveRouterIntegrationContext = createContext(null); +export function ObserveRouterIntegrationProvider({ children }) { + const [storage] = useState(() => isInitialized() ? createRouterIntegrationStorage() : null); + const prevInitialized = useRef(isInitialized()); + if (prevInitialized.current !== isInitialized()) { + throw new Error(`[expo-observe] Router integration was ${isInitialized() ? 'enabled' : 'disabled'} after application mounted. Call ExpoObserve.configure() before mounting AppMetricsRoot.`); + } + useEffect(() => { + if (!storage || !optionalRouter) + return; + return initListeners(storage, optionalRouter.unstable_navigationEvents); + }, [storage]); + return (_jsx(ObserveRouterIntegrationContext.Provider, { value: storage, children: children })); +} +//# sourceMappingURL=ObserveRouterIntegrationProvider.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map new file mode 100644 index 00000000000000..d4a5459720d730 --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/ObserveRouterIntegrationProvider.js.map @@ -0,0 +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;IAEF,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;QACb,IAAI,CAAC,OAAO,IAAI,CAAC,cAAc;YAAE,OAAO;QACxC,OAAO,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,yBAAyB,CAAC,CAAC;IAC1E,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,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(null);\n\nexport function ObserveRouterIntegrationProvider({ children }: PropsWithChildren) {\n const [storage] = useState(() =>\n isInitialized() ? createRouterIntegrationStorage() : null\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(() => {\n if (!storage || !optionalRouter) return;\n return initListeners(storage, optionalRouter.unstable_navigationEvents);\n }, [storage]);\n\n return (\n \n {children}\n \n );\n}\n"]} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/index.d.ts b/packages/expo-observe/build/integrations/expo-router/index.d.ts index 4cf39199526ce0..851f6163d06a63 100644 --- a/packages/expo-observe/build/integrations/expo-router/index.d.ts +++ b/packages/expo-observe/build/integrations/expo-router/index.d.ts @@ -1,4 +1,5 @@ export { isRouterInstalled } from './router'; export { useObserveForRouter } from './useObserveForRouter'; export { initRouterIntegration } from './init'; +export { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider'; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/index.d.ts.map b/packages/expo-observe/build/integrations/expo-router/index.d.ts.map index 772d45ae9ee301..bb57eb4aa0af4f 100644 --- a/packages/expo-observe/build/integrations/expo-router/index.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/index.d.ts.map @@ -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"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/index.js b/packages/expo-observe/build/integrations/expo-router/index.js index f08d54773c01ea..8f470bbd516dd0 100644 --- a/packages/expo-observe/build/integrations/expo-router/index.js +++ b/packages/expo-observe/build/integrations/expo-router/index.js @@ -1,4 +1,5 @@ export { isRouterInstalled } from './router'; export { useObserveForRouter } from './useObserveForRouter'; export { initRouterIntegration } from './init'; +export { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider'; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/index.js.map b/packages/expo-observe/build/integrations/expo-router/index.js.map index 612a8fb4675e74..6e5092ecf61b11 100644 --- a/packages/expo-observe/build/integrations/expo-router/index.js.map +++ b/packages/expo-observe/build/integrations/expo-router/index.js.map @@ -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"]} \ No newline at end of file +{"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"]} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/init.d.ts b/packages/expo-observe/build/integrations/expo-router/init.d.ts index a155cac7c92683..53f6f5748b7c36 100644 --- a/packages/expo-observe/build/integrations/expo-router/init.d.ts +++ b/packages/expo-observe/build/integrations/expo-router/init.d.ts @@ -1,3 +1,8 @@ +import { optionalRouter } from './router'; +import { type RouterIntegrationStorage } from './storage'; export declare const isInitialized: () => boolean; export declare function initRouterIntegration(): void; +type NavigationEvents = NonNullable['unstable_navigationEvents']; +export declare function initListeners(storage: RouterIntegrationStorage, navigationEvents: NavigationEvents): () => void; +export {}; //# sourceMappingURL=init.d.ts.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/init.d.ts.map b/packages/expo-observe/build/integrations/expo-router/init.d.ts.map index c739b1bfa334e7..e418e2d432b462 100644 --- a/packages/expo-observe/build/integrations/expo-router/init.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/init.d.ts.map @@ -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,SAEpC"} \ No newline at end of file +{"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;AAI1D,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,CA4EZ"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/init.js b/packages/expo-observe/build/integrations/expo-router/init.js index d32e90536a2f0f..dfdb0cc9cb3cb9 100644 --- a/packages/expo-observe/build/integrations/expo-router/init.js +++ b/packages/expo-observe/build/integrations/expo-router/init.js @@ -1,6 +1,82 @@ +import AppMetrics from 'expo-app-metrics'; +import { optionalRouter } from './router'; +import {} from './storage'; let initialized = false; export const isInitialized = () => initialized; export function initRouterIntegration() { initialized = true; + optionalRouter?.unstable_navigationEvents.enable(); +} +export function initListeners(storage, navigationEvents) { + const appLaunchTime = performance.now(); + const cleanup = new Set(); + const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event) => { + // TODO(@ubax): Handle screen preloading + // PRELOAD comes from router.prefetch() — a route warm-up, not a user + // navigation — so it must not seed dispatchTime. + if (event.actionType === 'PRELOAD') + return; + storage.pendingActions.push({ + actionType: event.actionType, + dispatchTime: performance.now(), + }); + }); + cleanup.add(unsubscribeAction); + const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e) => { + // Snapshot both clocks once so every metric written below is stamped with + // the moment the focus event fired, not the moment `addCustomMetricToSession` + // happens to run after the awaited `getMainSession()` round-trip. + const now = performance.now(); + const timestamp = new Date().toISOString(); + const isInitial = !storage.renderedScreensIds.has(e.screenId); + storage.renderedScreensIds.add(e.screenId); + if (process.env.EXPO_OS !== 'android') { + return; + } + const mainSessionId = (await AppMetrics.getMainSession())?.id; + if (!mainSessionId) { + return; + } + if (!storage.hasRecordedInitialTtr) { + // Stored in seconds to match the OTel `unit = "s"` convention + const appLaunchTtrSeconds = (now - appLaunchTime) / 1000; + storage.hasRecordedInitialTtr = true; + AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + name: 'ttr', + routeName: e.pathname, + value: appLaunchTtrSeconds, + params: { isInitial, isAppLaunch: true, routeParams: e.params }, + }); + return; + } + if (storage.pendingActions.length === 0) + return; + const last = storage.pendingActions[storage.pendingActions.length - 1]; + if (last) { + const dispatchTime = last.dispatchTime; + storage.screenTimes[e.screenId] = { + ...storage.screenTimes[e.screenId], + dispatchTime, + }; + AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + name: 'ttr', + routeName: e.pathname, + value: (now - dispatchTime) / 1000, + params: { isInitial, isAppLaunch: false, routeParams: e.params }, + }); + } + storage.pendingActions.length = 0; + }); + cleanup.add(unsubscribeFocus); + return () => { + cleanup.forEach((c) => c()); + cleanup.clear(); + }; } //# sourceMappingURL=init.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/init.js.map b/packages/expo-observe/build/integrations/expo-router/init.js.map index bd35f87018a822..ab99b9effcea18 100644 --- a/packages/expo-observe/build/integrations/expo-router/init.js.map +++ b/packages/expo-observe/build/integrations/expo-router/init.js.map @@ -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;AACrB,CAAC","sourcesContent":["let initialized = false;\n\nexport const isInitialized = () => initialized;\n\nexport function initRouterIntegration() {\n initialized = true;\n}\n"]} \ No newline at end of file +{"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\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['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 // TODO(@ubax): Handle screen preloading\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 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 if (process.env.EXPO_OS !== 'android') {\n return;\n }\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: 'ttr',\n routeName: e.pathname,\n value: appLaunchTtrSeconds,\n params: { isInitial, 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: 'ttr',\n routeName: e.pathname,\n value: (now - dispatchTime) / 1000,\n params: { isInitial, 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"]} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts b/packages/expo-observe/build/integrations/expo-router/storage.d.ts new file mode 100644 index 00000000000000..6beb2565038d99 --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts @@ -0,0 +1,27 @@ +import type { ActionDispatchedEvent } from 'expo-router'; +export interface ScreenTimes { + dispatchTime: number; + lastInteractiveCall?: number; +} +export interface PendingAction { + actionType: ActionDispatchedEvent['actionType']; + dispatchTime: number; +} +export interface RouterIntegrationStorage { + /** + * Actions dispatched, but not yet processed by the integration + */ + pendingActions: PendingAction[]; + renderedScreensIds: Set; + /** + * Wether the app had already recorded the first render of the screen + */ + hasRecordedInitialTtr: boolean; + /** + * Times used to calculate spans from dispatch to certain event + */ + screenTimes: Record; + interactiveScreensIds: Set; +} +export declare function createRouterIntegrationStorage(): RouterIntegrationStorage; +//# sourceMappingURL=storage.d.ts.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map new file mode 100644 index 00000000000000..718b9a4d2056dc --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map @@ -0,0 +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"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/storage.js b/packages/expo-observe/build/integrations/expo-router/storage.js new file mode 100644 index 00000000000000..4afe519b4a7069 --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/storage.js @@ -0,0 +1,10 @@ +export function createRouterIntegrationStorage() { + return { + pendingActions: [], + renderedScreensIds: new Set(), + hasRecordedInitialTtr: false, + screenTimes: {}, + interactiveScreensIds: new Set(), + }; +} +//# sourceMappingURL=storage.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/storage.js.map b/packages/expo-observe/build/integrations/expo-router/storage.js.map new file mode 100644 index 00000000000000..455bfab2925956 --- /dev/null +++ b/packages/expo-observe/build/integrations/expo-router/storage.js.map @@ -0,0 +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;\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;\n interactiveScreensIds: Set;\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"]} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map index d1263be6820ee2..4778980905632d 100644 --- a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useObserveForRouter.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAqC,MAAM,kBAAkB,CAAC;AAMrE,KAAK,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAE9D,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CAwD5D"} \ No newline at end of file +{"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,CA8G5D"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js index c5a6d2a99614f5..dae34909569b0e 100644 --- a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js +++ b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js @@ -1,12 +1,15 @@ import AppMetrics, {} from 'expo-app-metrics'; -import { useCallback, useEffect, useRef } from 'react'; +import { use, useCallback, useEffect, useRef } from 'react'; +import { ObserveRouterIntegrationContext } from './ObserveRouterIntegrationProvider'; import { isInitialized } from './init'; import { optionalRouter } from './router'; export function useObserveForRouter() { + const storage = use(ObserveRouterIntegrationContext); const isMounted = useRef(true); const route = optionalRouter?.useRoute(); const navigation = optionalRouter?.useNavigation(); - const pathname = optionalRouter?.useCurrentRouteInfo()?.pathname; + const routeInfo = optionalRouter?.useCurrentRouteInfo(); + const { pathname, params: routeParams } = routeInfo ?? {}; const initializedAtMount = useRef(isInitialized()); if (initializedAtMount.current !== isInitialized()) { throw new Error("[expo-observe] Router integration was toggled during a screen's lifecycle. " + @@ -28,6 +31,8 @@ export function useObserveForRouter() { }; }, []); const markInteractive = useCallback(async (attributes) => { + const now = performance.now(); + const timestamp = new Date().toISOString(); if (!isMounted.current) { console.warn('[expo-observe] Calling markInteractive on unmounted screen'); return; @@ -42,7 +47,48 @@ export function useObserveForRouter() { routeName: pathname, }); } - }, [screenId, navigation, pathname]); + if (process.env.EXPO_OS !== 'android') { + return; + } + if (!storage) { + throw new Error('[expo-observe] markInteractive was called without an active ObserveProvider. Wrap your app in ObserveRoot from expo-observe.'); + } + // Snapshot times BEFORE writing the new interactive timestamp so the + // duplicate-detection logic below sees the *previous* call, not this one. + const currentScreenData = storage.screenTimes[screenId]; + storage.interactiveScreensIds.add(screenId); + if (storage.screenTimes[screenId]) { + storage.screenTimes[screenId] = { + ...storage.screenTimes[screenId], + lastInteractiveCall: now, + }; + } + if (!currentScreenData?.dispatchTime) + return; + const previousInteractiveCall = currentScreenData.lastInteractiveCall; + const previousWasAfterDispatch = previousInteractiveCall != null && currentScreenData.dispatchTime < previousInteractiveCall; + if (previousWasAfterDispatch) { + // We only want to record interactive once per navigation + return; + } + // Stored in seconds to match the OTel `unit = "s"` convention + const interactiveTimeSeconds = (now - currentScreenData.dispatchTime) / 1000; + const mainSessionId = (await AppMetrics.getMainSession())?.id; + // TODO(@ubax): we should count the time against the action which caused the first navigation + // and add a param stating if during that time there was any navigation + if (mainSessionId) { + await AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + // TODO(@ubax): Use segments.join here to get full routeName and pass pathname and params via params + routeName: pathname, + name: 'tti', + value: interactiveTimeSeconds, + params: { routeParams }, + }); + } + }, [screenId, navigation, pathname, storage, routeParams]); return initializedAtMount.current ? markInteractive : null; } //# sourceMappingURL=useObserveForRouter.js.map \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js.map b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js.map index 986fb74c74bd02..a7dc09884110f4 100644 --- a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js.map +++ b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.js.map @@ -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;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAI1C,MAAM,UAAU,mBAAmB;IACjC,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,QAAQ,GAAG,cAAc,EAAE,mBAAmB,EAAE,EAAE,QAAQ,CAAC;IAEjE,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,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;IACH,CAAC,EACD,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC,CACjC,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 { useCallback, useEffect, useRef } from 'react';\n\nimport { isInitialized } from './init';\nimport { optionalRouter } from './router';\n\ntype MarkInteractive = (typeof AppMetrics)['markInteractive'];\n\nexport function useObserveForRouter(): MarkInteractive | null {\n const isMounted = useRef(true);\n const route = optionalRouter?.useRoute();\n const navigation = optionalRouter?.useNavigation();\n const pathname = optionalRouter?.useCurrentRouteInfo()?.pathname;\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 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 [screenId, navigation, pathname]\n );\n\n return initializedAtMount.current ? markInteractive : null;\n}\n"]} \ No newline at end of file +{"version":3,"file":"useObserveForRouter.js","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"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","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 (process.env.EXPO_OS !== 'android') {\n return;\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"]} \ No newline at end of file diff --git a/packages/expo-observe/src/ObserveProvider.tsx b/packages/expo-observe/src/ObserveProvider.tsx index 3a267894c72350..94e8871c3906a4 100644 --- a/packages/expo-observe/src/ObserveProvider.tsx +++ b/packages/expo-observe/src/ObserveProvider.tsx @@ -1,5 +1,7 @@ import { type PropsWithChildren } from 'react'; +import { ObserveRouterIntegrationProvider } from './integrations/expo-router/ObserveRouterIntegrationProvider'; + export function ObserveProvider({ children }: PropsWithChildren) { - return <>{children}; + return {children}; } diff --git a/packages/expo-observe/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx b/packages/expo-observe/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx new file mode 100644 index 00000000000000..85ae8ea87fc68d --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/ObserveRouterIntegrationProvider.tsx @@ -0,0 +1,31 @@ +import { createContext, type PropsWithChildren, useEffect, useRef, useState } from 'react'; + +import { initListeners, isInitialized } from './init'; +import { optionalRouter } from './router'; +import { createRouterIntegrationStorage, type RouterIntegrationStorage } from './storage'; + +export const ObserveRouterIntegrationContext = createContext(null); + +export function ObserveRouterIntegrationProvider({ children }: PropsWithChildren) { + const [storage] = useState(() => + isInitialized() ? createRouterIntegrationStorage() : null + ); + + const prevInitialized = useRef(isInitialized()); + if (prevInitialized.current !== isInitialized()) { + throw new Error( + `[expo-observe] Router integration was ${isInitialized() ? 'enabled' : 'disabled'} after application mounted. Call ExpoObserve.configure() before mounting AppMetricsRoot.` + ); + } + + useEffect(() => { + if (!storage || !optionalRouter) return; + return initListeners(storage, optionalRouter.unstable_navigationEvents); + }, [storage]); + + return ( + + {children} + + ); +} diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/ObserveRouterIntegrationProvider.test.native.tsx b/packages/expo-observe/src/integrations/expo-router/__tests__/ObserveRouterIntegrationProvider.test.native.tsx new file mode 100644 index 00000000000000..5bba15b8388630 --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/ObserveRouterIntegrationProvider.test.native.tsx @@ -0,0 +1,130 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import { render } from '@testing-library/react-native'; +import { use } from 'react'; +import { Text } from 'react-native'; + +import { + ObserveRouterIntegrationContext, + ObserveRouterIntegrationProvider, +} from '../ObserveRouterIntegrationProvider'; +import * as initModule from '../init'; + +jest.mock('expo-app-metrics', () => ({ + __esModule: true, + default: { + markInteractive: jest.fn(), + getMainSessionId: jest.fn(() => 'session-1'), + addCustomMetricToSession: jest.fn(), + }, +})); + +jest.mock('../init', () => { + const initListenersCleanup = jest.fn(); + return { + __esModule: true, + isInitialized: jest.fn(() => true), + initListeners: jest.fn(() => initListenersCleanup), + initRouterIntegration: jest.fn(), + __initListenersCleanup: initListenersCleanup, + }; +}); + +jest.mock('../router', () => ({ + optionalRouter: { unstable_navigationEvents: { addListener: jest.fn(), emit: jest.fn() } }, + isRouterInstalled: true, +})); + +const mockIsInitialized = initModule.isInitialized as jest.Mock; +const mockInitListeners = initModule.initListeners as jest.Mock; +const mockInitListenersCleanup = (initModule as unknown as { __initListenersCleanup: jest.Mock }) + .__initListenersCleanup; + +function StorageProbe({ onRead }: { onRead: (storage: unknown) => void }) { + const storage = use(ObserveRouterIntegrationContext); + onRead(storage); + return probe; +} + +beforeEach(() => { + mockIsInitialized.mockReturnValue(true); + mockIsInitialized.mockClear(); + mockInitListeners.mockClear(); + mockInitListenersCleanup.mockClear(); + jest.spyOn(console, 'warn').mockImplementation(() => {}); +}); + +describe('ObserveRouterIntegrationProvider', () => { + it('exposes a non-null storage on first render when isInitialized() is true at mount', () => { + const reads: unknown[] = []; + render( + + reads.push(s)} /> + + ); + expect(reads[0]).toBeTruthy(); + expect( + (reads[0] as { interactiveScreensIds: Set }).interactiveScreensIds + ).toBeInstanceOf(Set); + }); + + it('keeps storage null and does not attach listeners when isInitialized() is false at mount', () => { + mockIsInitialized.mockReturnValue(false); + const reads: unknown[] = []; + render( + + reads.push(s)} /> + + ); + expect(reads[0]).toBeNull(); + expect(mockInitListeners).not.toHaveBeenCalled(); + }); + + it('calls initListeners with storage in useEffect and runs cleanup on unmount', () => { + const { unmount } = render( + + child + + ); + expect(mockInitListeners).toHaveBeenCalledTimes(1); + const [storageArg] = mockInitListeners.mock.calls[0]; + expect( + (storageArg as { interactiveScreensIds: Set }).interactiveScreensIds + ).toBeInstanceOf(Set); + + expect(mockInitListenersCleanup).not.toHaveBeenCalled(); + unmount(); + expect(mockInitListenersCleanup).toHaveBeenCalledTimes(1); + }); + + it('throws when isInitialized() flips during the provider lifetime', () => { + mockIsInitialized.mockReturnValue(false); + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const { rerender } = render( + + child + + ); + + mockIsInitialized.mockReturnValue(true); + expect(() => + rerender( + + child + + ) + ).toThrow( + '[expo-observe] Router integration was enabled after application mounted. Call ExpoObserve.configure() before mounting AppMetricsRoot.' + ); + }); + + it('renders children when storage is null (router not installed scenario)', () => { + mockIsInitialized.mockReturnValue(false); + const { getByText } = render( + + visible + + ); + expect(getByText('visible')).toBeTruthy(); + }); +}); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.android.ts b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.android.ts new file mode 100644 index 00000000000000..4d500a1704ba3f --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.android.ts @@ -0,0 +1,203 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import AppMetrics from 'expo-app-metrics'; +import type { ActionDispatchedEvent, PageFocusedEvent } from 'expo-router'; + +import { initListeners } from '../init'; +import { createRouterIntegrationStorage, type RouterIntegrationStorage } from '../storage'; + +jest.mock('expo-app-metrics', () => { + const addCustomMetricToSession = jest.fn(); + const getMainSession = jest.fn(async () => ({ id: 'session-1' })); + return { + __esModule: true, + default: { + markInteractive: jest.fn(), + getMainSession, + addCustomMetricToSession, + }, + }; +}); + +jest.mock('../router', () => ({ optionalRouter: undefined, isRouterInstalled: false })); + +const mockGetMainSession = AppMetrics.getMainSession as jest.Mock; +const mockAddCustomMetric = AppMetrics.addCustomMetricToSession as jest.Mock; +const mockSessionId = 'session-1'; + +type Listener = (event: T) => void; + +interface FakeNavigationEvents { + addListener(type: string, cb: Listener): () => void; + emit(type: string, event: T): void; +} + +function createFakeNavigationEvents(): FakeNavigationEvents { + const listeners: Record>> = {}; + return { + addListener(type, cb) { + listeners[type] = listeners[type] ?? new Set(); + listeners[type].add(cb); + return () => listeners[type].delete(cb); + }, + emit(type, event) { + listeners[type]?.forEach((cb) => cb(event)); + }, + }; +} + +function dispatch(events: FakeNavigationEvents, actionType: string) { + events.emit>('actionDispatched', { + type: 'actionDispatched', + actionType: actionType as ActionDispatchedEvent['actionType'], + }); +} + +function focus(events: FakeNavigationEvents, screenId: string) { + events.emit>('pageFocused', { + type: 'pageFocused', + screenId, + pathname: `/${screenId}`, + params: {}, + }); +} + +function flushAsync() { + return new Promise((resolve) => setImmediate(resolve)); +} + +let storage: RouterIntegrationStorage; +let events: FakeNavigationEvents; +let cleanup: () => void; +let logSpy: jest.SpyInstance; +let warnSpy: jest.SpyInstance; + +beforeEach(() => { + jest.clearAllMocks(); + logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + storage = createRouterIntegrationStorage(); + events = createFakeNavigationEvents(); + cleanup = initListeners(storage, events as any); +}); + +afterEach(() => { + cleanup?.(); + expect(logSpy).not.toHaveBeenCalled(); + expect(warnSpy).not.toHaveBeenCalled(); + jest.clearAllMocks(); +}); + +describe('initListeners (android)', () => { + it('records TTR with isAppLaunch=true on the first focus after a non-PRELOAD action', async () => { + const now = performance.now(); + jest.spyOn(performance, 'now').mockReturnValue(now + 100); + focus(events, 'a'); + await flushAsync(); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(1); + expect(mockAddCustomMetric).toHaveBeenCalledWith({ + sessionId: mockSessionId, + timestamp: expect.any(String), + category: 'navigation', + name: 'ttr', + routeName: '/a', + value: expect.closeTo(0.1, 2), + params: { isInitial: true, isAppLaunch: true, routeParams: {} }, + }); + }); + + it('records TTR with isAppLaunch=false on subsequent focuses', async () => { + dispatch(events, 'NAVIGATE'); + focus(events, 'a'); + await flushAsync(); + mockAddCustomMetric.mockClear(); + + dispatch(events, 'NAVIGATE'); + focus(events, 'b'); + await flushAsync(); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(1); + expect(mockAddCustomMetric.mock.calls[0][0].params).toEqual({ + isInitial: true, + isAppLaunch: false, + routeParams: {}, + }); + }); + + it('records TTR with isInitial=false when revisiting a previously rendered screen', async () => { + focus(events, 'a'); + await flushAsync(); + + dispatch(events, 'NAVIGATE'); + focus(events, 'b'); + await flushAsync(); + + dispatch(events, 'NAVIGATE'); + focus(events, 'a'); + await flushAsync(); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(3); + expect(mockAddCustomMetric.mock.calls[0][0].params).toEqual({ + isInitial: true, + isAppLaunch: true, + routeParams: {}, + }); + expect(mockAddCustomMetric.mock.calls[1][0].params).toEqual({ + isInitial: true, + isAppLaunch: false, + routeParams: {}, + }); + expect(mockAddCustomMetric.mock.calls[2][0].params).toEqual({ + isInitial: false, + isAppLaunch: false, + routeParams: {}, + }); + }); + + it('does not record a TTR for a PRELOAD action', async () => { + storage.hasRecordedInitialTtr = true; + + dispatch(events, 'PRELOAD'); + focus(events, 'a'); + await flushAsync(); + expect(mockAddCustomMetric).not.toHaveBeenCalled(); + }); + + it('cleanup unsubscribes both listeners', async () => { + cleanup(); + dispatch(events, 'NAVIGATE'); + focus(events, 'a'); + await flushAsync(); + expect(mockAddCustomMetric).not.toHaveBeenCalled(); + expect(storage.pendingActions).toHaveLength(0); + cleanup = () => {}; + }); + + it('uses getMainSession from AppMetrics for the metric session id', async () => { + mockGetMainSession.mockResolvedValueOnce({ id: 'custom-session' }); + dispatch(events, 'NAVIGATE'); + focus(events, 'a'); + await new Promise((resolve) => setImmediate(resolve)); + expect(mockAddCustomMetric).toHaveBeenCalledWith( + expect.objectContaining({ sessionId: 'custom-session' }) + ); + }); +}); + +describe('isInitialized + initRouterIntegration', () => { + it('flips initialized when initRouterIntegration is called and stays decoupled from initListeners', () => { + jest.isolateModules(() => { + const init = require('../init'); + expect(init.isInitialized()).toBe(false); + + const fresh = createRouterIntegrationStorage(); + const fakeEvents = createFakeNavigationEvents(); + const dispose = init.initListeners(fresh, fakeEvents); + expect(init.isInitialized()).toBe(false); + dispose(); + + init.initRouterIntegration(); + expect(init.isInitialized()).toBe(true); + }); + }); +}); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.ios.ts b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.ios.ts new file mode 100644 index 00000000000000..6ddc69057e99fc --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.ios.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import AppMetrics from 'expo-app-metrics'; +import type { ActionDispatchedEvent, PageFocusedEvent } from 'expo-router'; + +import { initListeners } from '../init'; +import { createRouterIntegrationStorage, type RouterIntegrationStorage } from '../storage'; + +jest.mock('expo-app-metrics', () => ({ + __esModule: true, + default: { + markInteractive: jest.fn(), + getMainSessionId: jest.fn(() => 'session-1'), + addCustomMetricToSession: jest.fn(), + }, +})); + +jest.mock('../router', () => ({ optionalRouter: undefined, isRouterInstalled: false })); + +const mockAddCustomMetric = AppMetrics.addCustomMetricToSession as jest.Mock; + +type Listener = (event: T) => void; + +interface FakeNavigationEvents { + addListener(type: string, cb: Listener): () => void; + emit(type: string, event: T): void; +} + +function createFakeNavigationEvents(): FakeNavigationEvents { + const listeners: Record>> = {}; + return { + addListener(type, cb) { + listeners[type] = listeners[type] ?? new Set(); + listeners[type].add(cb); + return () => listeners[type].delete(cb); + }, + emit(type, event) { + listeners[type]?.forEach((cb) => cb(event)); + }, + }; +} + +function dispatch(events: FakeNavigationEvents, actionType: string) { + events.emit>('actionDispatched', { + type: 'actionDispatched', + actionType: actionType as ActionDispatchedEvent['actionType'], + }); +} + +function focus(events: FakeNavigationEvents, screenId: string) { + events.emit>('pageFocused', { + type: 'pageFocused', + screenId, + pathname: `/${screenId}`, + params: {}, + }); +} + +let storage: RouterIntegrationStorage; +let events: FakeNavigationEvents; +let cleanup: () => void; +let warnSpy: jest.SpyInstance; +let logSpy: jest.SpyInstance; + +beforeEach(() => { + jest.clearAllMocks(); + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + storage = createRouterIntegrationStorage(); + events = createFakeNavigationEvents(); + cleanup = initListeners(storage, events as any); +}); + +afterEach(() => { + cleanup?.(); +}); + +describe('initListeners (ios)', () => { + it('does not record the TTR metric on iOS', () => { + dispatch(events, 'NAVIGATE'); + focus(events, 'a'); + expect(mockAddCustomMetric).not.toHaveBeenCalled(); + expect(logSpy).not.toHaveBeenCalled(); + expect(warnSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/storage.test.ts b/packages/expo-observe/src/integrations/expo-router/__tests__/storage.test.ts new file mode 100644 index 00000000000000..dd1b2250e985f3 --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/storage.test.ts @@ -0,0 +1,22 @@ +import { createRouterIntegrationStorage } from '../storage'; + +describe('createRouterIntegrationStorage', () => { + it('returns isolated instances', () => { + const a = createRouterIntegrationStorage(); + const b = createRouterIntegrationStorage(); + expect(a).not.toBe(b); + expect(a.screenTimes).not.toBe(b.screenTimes); + expect(a.interactiveScreensIds).not.toBe(b.interactiveScreensIds); + expect(a.renderedScreensIds).not.toBe(b.renderedScreensIds); + expect(a.pendingActions).not.toBe(b.pendingActions); + }); + + it('initializes empty collections and default flags', () => { + const storage = createRouterIntegrationStorage(); + expect(storage.pendingActions).toEqual([]); + expect(storage.renderedScreensIds.size).toBe(0); + expect(storage.hasRecordedInitialTtr).toBe(false); + expect(storage.screenTimes).toEqual({}); + expect(storage.interactiveScreensIds.size).toBe(0); + }); +}); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.android.tsx b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.android.tsx index 56352558327ecc..2a5d7881cf34e0 100644 --- a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.android.tsx +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.android.tsx @@ -1,8 +1,11 @@ /* eslint-disable @typescript-eslint/no-require-imports */ import { renderHook, act } from '@testing-library/react-native'; import AppMetrics from 'expo-app-metrics'; +import { type ReactNode } from 'react'; +import { ObserveRouterIntegrationContext } from '../ObserveRouterIntegrationProvider'; import * as routerModule from '../router'; +import { createRouterIntegrationStorage, type RouterIntegrationStorage } from '../storage'; import { useObserveForRouter } from '../useObserveForRouter'; jest.mock('expo-app-metrics', () => ({ @@ -17,6 +20,7 @@ jest.mock('expo-app-metrics', () => ({ jest.mock('../init', () => ({ __esModule: true, isInitialized: jest.fn(() => true), + initListeners: jest.fn(() => () => {}), initRouterIntegration: jest.fn(), })); @@ -33,6 +37,7 @@ jest.mock('../router', () => { useRoute, useNavigation, useCurrentRouteInfo, + unstable_navigationEvents: { addListener: jest.fn(), emit: jest.fn() }, }, isRouterInstalled: true, __useRoute: useRoute, @@ -40,19 +45,53 @@ jest.mock('../router', () => { }; }); +const mockAddCustomMetric = AppMetrics.addCustomMetricToSession as jest.Mock; const mockUseRoute = (routerModule as unknown as { __useRoute: jest.Mock }).__useRoute; const mockUseNavigation = (routerModule as unknown as { __useNavigation: jest.Mock }) .__useNavigation; +function wrapper(storage: RouterIntegrationStorage | null) { + return ({ children }: { children: ReactNode }) => ( + + {children} + + ); +} + +let storage: RouterIntegrationStorage; + beforeEach(() => { jest.clearAllMocks(); mockUseRoute.mockReturnValue({ key: 'screen-a' }); mockUseNavigation.mockReturnValue({ isFocused: () => true }); + storage = createRouterIntegrationStorage(); }); describe('useObserveForRouter (android)', () => { + it('records TTI from dispatchTime on the first call when focused', async () => { + storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + jest.spyOn(performance, 'now').mockReturnValue(1300); + + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + await act(async () => { + await result.current!(); + }); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(1); + expect(mockAddCustomMetric).toHaveBeenCalledWith({ + sessionId: 'session-1', + timestamp: expect.any(String), + category: 'navigation', + routeName: '/test', + name: 'tti', + value: 0.3, + params: { routeParams: { x: '1' } }, + }); + }); + it('calls AppMetrics.markInteractive when the screen is focused', async () => { - const { result } = renderHook(() => useObserveForRouter()); + storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); const arg = { params: { x: 'payload' } }; await act(async () => { await result.current!({ ...arg }); @@ -60,15 +99,35 @@ describe('useObserveForRouter (android)', () => { expect(AppMetrics.markInteractive).toHaveBeenCalledWith({ ...arg, routeName: '/test' }); }); - it('does not call AppMetrics.markInteractive when the screen is not focused', async () => { + it('does not call AppMetrics.markInteractive when the screen is not focused, but still computes TTI', async () => { mockUseNavigation.mockReturnValue({ isFocused: () => false }); - jest.spyOn(Date, 'now').mockReturnValue(1300); + storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + jest.spyOn(performance, 'now').mockReturnValue(1300); - const { result } = renderHook(() => useObserveForRouter()); + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); await act(async () => { await result.current!(); }); expect(AppMetrics.markInteractive).not.toHaveBeenCalled(); + expect(mockAddCustomMetric).toHaveBeenCalledWith({ + sessionId: 'session-1', + timestamp: expect.any(String), + category: 'navigation', + routeName: '/test', + name: 'tti', + value: 0.3, + params: { routeParams: { x: '1' } }, + }); + }); + + it('skips TTI calculation silently when no dispatchTime is recorded for the screen', async () => { + const warnSpy = jest.spyOn(console, 'warn'); + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + await act(async () => { + await result.current!(); + }); + expect(mockAddCustomMetric).not.toHaveBeenCalled(); + expect(warnSpy).not.toHaveBeenCalled(); }); }); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.ios.tsx b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.ios.tsx index 961735dce6dd0a..ca346d160b5c7d 100644 --- a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.ios.tsx +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.ios.tsx @@ -1,8 +1,11 @@ /* eslint-disable @typescript-eslint/no-require-imports */ import { renderHook, act } from '@testing-library/react-native'; import AppMetrics from 'expo-app-metrics'; +import { type ReactNode } from 'react'; +import { ObserveRouterIntegrationContext } from '../ObserveRouterIntegrationProvider'; import * as routerModule from '../router'; +import { createRouterIntegrationStorage, type RouterIntegrationStorage } from '../storage'; import { useObserveForRouter } from '../useObserveForRouter'; jest.mock('expo-app-metrics', () => ({ @@ -17,6 +20,7 @@ jest.mock('expo-app-metrics', () => ({ jest.mock('../init', () => ({ __esModule: true, isInitialized: jest.fn(() => true), + initListeners: jest.fn(() => () => {}), initRouterIntegration: jest.fn(), })); @@ -33,6 +37,7 @@ jest.mock('../router', () => { useRoute, useNavigation, useCurrentRouteInfo, + unstable_navigationEvents: { addListener: jest.fn(), emit: jest.fn() }, }, isRouterInstalled: true, __useRoute: useRoute, @@ -40,14 +45,28 @@ jest.mock('../router', () => { }; }); +const mockAddCustomMetric = AppMetrics.addCustomMetricToSession as jest.Mock; const mockUseRoute = (routerModule as unknown as { __useRoute: jest.Mock }).__useRoute; const mockUseNavigation = (routerModule as unknown as { __useNavigation: jest.Mock }) .__useNavigation; +function wrapper(storage: RouterIntegrationStorage | null) { + return ({ children }: { children: ReactNode }) => ( + + {children} + + ); +} + +let storage: RouterIntegrationStorage; + beforeEach(() => { jest.clearAllMocks(); + jest.spyOn(console, 'log').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); mockUseRoute.mockReturnValue({ key: 'screen-a' }); mockUseNavigation.mockReturnValue({ isFocused: () => true }); + storage = createRouterIntegrationStorage(); }); describe('useObserveForRouter (ios)', () => { @@ -71,4 +90,71 @@ describe('useObserveForRouter (ios)', () => { expect(AppMetrics.markInteractive).not.toHaveBeenCalled(); }); + + it('does not record the per-screen TTI metric on iOS', async () => { + storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + await act(async () => { + await result.current!(); + }); + expect(mockAddCustomMetric).not.toHaveBeenCalled(); + }); + + it('warns and skips when called on an unmounted screen', async () => { + const warnSpy = jest.spyOn(console, 'warn'); + const { result, unmount } = renderHook(() => useObserveForRouter(), { + wrapper: wrapper(storage), + }); + const fn = result.current!; + unmount(); + await act(async () => { + await fn(); + }); + expect(warnSpy).toHaveBeenCalledWith( + '[expo-observe] Calling markInteractive on unmounted screen' + ); + expect(AppMetrics.markInteractive).not.toHaveBeenCalled(); + }); + + it('warns when there is no screenId on the route', async () => { + mockUseRoute.mockReturnValue({ key: undefined }); + const warnSpy = jest.spyOn(console, 'warn'); + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + await act(async () => { + await result.current!(); + }); + expect(warnSpy).toHaveBeenCalledWith( + '[expo-observe] No metadata available for the current screen. Make sure to call useObserve inside a screen component.' + ); + expect(AppMetrics.markInteractive).not.toHaveBeenCalled(); + }); + + it('warns when the screen ID changes between renders', () => { + const warnSpy = jest.spyOn(console, 'warn'); + mockUseRoute.mockReturnValue({ key: 'screen-a' }); + const { rerender } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + + mockUseRoute.mockReturnValue({ key: 'screen-b' }); + rerender(undefined); + + expect(warnSpy).toHaveBeenCalledWith( + '[expo-observe] Screen ID changed between renders. This is most likely an expo-router bug.' + ); + }); + + it('throws when isInitialized() flips mid-lifetime', () => { + const init = require('../init') as typeof import('../init'); + const isInitMock = init.isInitialized as jest.Mock; + isInitMock.mockReturnValue(false); + // Suppress React's own console.error for the unhandled render error so the + // test output stays focused on the assertion. + jest.spyOn(console, 'error').mockImplementation(() => {}); + + const { rerender } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + + isInitMock.mockReturnValue(true); + expect(() => rerender(undefined)).toThrow( + "[expo-observe] Router integration was toggled during a screen's lifecycle. Call `ExpoObserve.configure({ disableRouterIntegration })` once at startup before any screen mounts." + ); + }); }); diff --git a/packages/expo-observe/src/integrations/expo-router/index.ts b/packages/expo-observe/src/integrations/expo-router/index.ts index 92a8602e977ecf..928cc54cac23dd 100644 --- a/packages/expo-observe/src/integrations/expo-router/index.ts +++ b/packages/expo-observe/src/integrations/expo-router/index.ts @@ -1,3 +1,4 @@ export { isRouterInstalled } from './router'; export { useObserveForRouter } from './useObserveForRouter'; export { initRouterIntegration } from './init'; +export { ObserveRouterIntegrationProvider } from './ObserveRouterIntegrationProvider'; diff --git a/packages/expo-observe/src/integrations/expo-router/init.ts b/packages/expo-observe/src/integrations/expo-router/init.ts index 37d510c7d8ed59..5ba6aa95e375d0 100644 --- a/packages/expo-observe/src/integrations/expo-router/init.ts +++ b/packages/expo-observe/src/integrations/expo-router/init.ts @@ -1,7 +1,96 @@ +import AppMetrics from 'expo-app-metrics'; + +import { optionalRouter } from './router'; +import { type RouterIntegrationStorage } from './storage'; + let initialized = false; export const isInitialized = () => initialized; export function initRouterIntegration() { initialized = true; + optionalRouter?.unstable_navigationEvents.enable(); +} + +type NavigationEvents = NonNullable['unstable_navigationEvents']; + +export function initListeners( + storage: RouterIntegrationStorage, + navigationEvents: NavigationEvents +): () => void { + const appLaunchTime = performance.now(); + const cleanup = new Set<() => void>(); + + const unsubscribeAction = navigationEvents.addListener('actionDispatched', (event) => { + // TODO(@ubax): Handle screen preloading + // PRELOAD comes from router.prefetch() — a route warm-up, not a user + // navigation — so it must not seed dispatchTime. + if (event.actionType === 'PRELOAD') return; + storage.pendingActions.push({ + actionType: event.actionType, + dispatchTime: performance.now(), + }); + }); + cleanup.add(unsubscribeAction); + + const unsubscribeFocus = navigationEvents.addListener('pageFocused', async (e) => { + // Snapshot both clocks once so every metric written below is stamped with + // the moment the focus event fired, not the moment `addCustomMetricToSession` + // happens to run after the awaited `getMainSession()` round-trip. + const now = performance.now(); + const timestamp = new Date().toISOString(); + const isInitial = !storage.renderedScreensIds.has(e.screenId); + storage.renderedScreensIds.add(e.screenId); + if (process.env.EXPO_OS !== 'android') { + return; + } + const mainSessionId = (await AppMetrics.getMainSession())?.id; + if (!mainSessionId) { + return; + } + + if (!storage.hasRecordedInitialTtr) { + // Stored in seconds to match the OTel `unit = "s"` convention + const appLaunchTtrSeconds = (now - appLaunchTime) / 1000; + storage.hasRecordedInitialTtr = true; + AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + name: 'ttr', + routeName: e.pathname, + value: appLaunchTtrSeconds, + params: { isInitial, isAppLaunch: true, routeParams: e.params }, + }); + return; + } + + if (storage.pendingActions.length === 0) return; + + const last = storage.pendingActions[storage.pendingActions.length - 1]; + if (last) { + const dispatchTime = last.dispatchTime; + storage.screenTimes[e.screenId] = { + ...storage.screenTimes[e.screenId], + dispatchTime, + }; + + AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + name: 'ttr', + routeName: e.pathname, + value: (now - dispatchTime) / 1000, + params: { isInitial, isAppLaunch: false, routeParams: e.params }, + }); + } + storage.pendingActions.length = 0; + }); + cleanup.add(unsubscribeFocus); + + return () => { + cleanup.forEach((c) => c()); + cleanup.clear(); + }; } diff --git a/packages/expo-observe/src/integrations/expo-router/storage.ts b/packages/expo-observe/src/integrations/expo-router/storage.ts new file mode 100644 index 00000000000000..0a4656efd09e95 --- /dev/null +++ b/packages/expo-observe/src/integrations/expo-router/storage.ts @@ -0,0 +1,38 @@ +import type { ActionDispatchedEvent } from 'expo-router'; + +export interface ScreenTimes { + dispatchTime: number; + lastInteractiveCall?: number; +} + +export interface PendingAction { + actionType: ActionDispatchedEvent['actionType']; + dispatchTime: number; +} + +export interface RouterIntegrationStorage { + /** + * Actions dispatched, but not yet processed by the integration + */ + pendingActions: PendingAction[]; + renderedScreensIds: Set; + /** + * Wether the app had already recorded the first render of the screen + */ + hasRecordedInitialTtr: boolean; + /** + * Times used to calculate spans from dispatch to certain event + */ + screenTimes: Record; + interactiveScreensIds: Set; +} + +export function createRouterIntegrationStorage(): RouterIntegrationStorage { + return { + pendingActions: [], + renderedScreensIds: new Set(), + hasRecordedInitialTtr: false, + screenTimes: {}, + interactiveScreensIds: new Set(), + }; +} diff --git a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts index 7f18f9c3ce9ee4..929238faca600d 100644 --- a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts +++ b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts @@ -1,16 +1,19 @@ import AppMetrics, { type MetricAttributes } from 'expo-app-metrics'; -import { useCallback, useEffect, useRef } from 'react'; +import { use, useCallback, useEffect, useRef } from 'react'; +import { ObserveRouterIntegrationContext } from './ObserveRouterIntegrationProvider'; import { isInitialized } from './init'; import { optionalRouter } from './router'; type MarkInteractive = (typeof AppMetrics)['markInteractive']; export function useObserveForRouter(): MarkInteractive | null { + const storage = use(ObserveRouterIntegrationContext); const isMounted = useRef(true); const route = optionalRouter?.useRoute(); const navigation = optionalRouter?.useNavigation(); - const pathname = optionalRouter?.useCurrentRouteInfo()?.pathname; + const routeInfo = optionalRouter?.useCurrentRouteInfo(); + const { pathname, params: routeParams } = routeInfo ?? {}; const initializedAtMount = useRef(isInitialized()); if (initializedAtMount.current !== isInitialized()) { @@ -41,6 +44,8 @@ export function useObserveForRouter(): MarkInteractive | null { const markInteractive = useCallback( async (attributes?: MetricAttributes) => { + const now = performance.now(); + const timestamp = new Date().toISOString(); if (!isMounted.current) { console.warn('[expo-observe] Calling markInteractive on unmounted screen'); return; @@ -57,8 +62,58 @@ export function useObserveForRouter(): MarkInteractive | null { routeName: pathname, }); } + + if (process.env.EXPO_OS !== 'android') { + return; + } + if (!storage) { + throw new Error( + '[expo-observe] markInteractive was called without an active ObserveProvider. Wrap your app in ObserveRoot from expo-observe.' + ); + } + + // Snapshot times BEFORE writing the new interactive timestamp so the + // duplicate-detection logic below sees the *previous* call, not this one. + const currentScreenData = storage.screenTimes[screenId]; + + storage.interactiveScreensIds.add(screenId); + if (storage.screenTimes[screenId]) { + storage.screenTimes[screenId] = { + ...storage.screenTimes[screenId], + lastInteractiveCall: now, + }; + } + + if (!currentScreenData?.dispatchTime) return; + + const previousInteractiveCall = currentScreenData.lastInteractiveCall; + const previousWasAfterDispatch = + previousInteractiveCall != null && currentScreenData.dispatchTime < previousInteractiveCall; + + if (previousWasAfterDispatch) { + // We only want to record interactive once per navigation + return; + } + + // Stored in seconds to match the OTel `unit = "s"` convention + const interactiveTimeSeconds = (now - currentScreenData.dispatchTime) / 1000; + const mainSessionId = (await AppMetrics.getMainSession())?.id; + // TODO(@ubax): we should count the time against the action which caused the first navigation + // and add a param stating if during that time there was any navigation + if (mainSessionId) { + await AppMetrics.addCustomMetricToSession({ + sessionId: mainSessionId, + timestamp, + category: 'navigation', + // TODO(@ubax): Use segments.join here to get full routeName and pass pathname and params via params + routeName: pathname, + name: 'tti', + value: interactiveTimeSeconds, + params: { routeParams }, + }); + } }, - [screenId, navigation, pathname] + [screenId, navigation, pathname, storage, routeParams] ); return initializedAtMount.current ? markInteractive : null; diff --git a/packages/expo-router/build/exports.d.ts b/packages/expo-router/build/exports.d.ts index 7b8f9ae993be89..4b45ef4e8c3f8a 100644 --- a/packages/expo-router/build/exports.d.ts +++ b/packages/expo-router/build/exports.d.ts @@ -31,6 +31,7 @@ export type { SingularOptions } from './useScreens'; export type * from './types'; export { Badge, type BadgeProps, Icon, type IconProps, Label, type LabelProps, VectorIcon, type VectorIconProps, } from './primitives'; export { unstable_navigationEvents } from './navigationEvents'; +export type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved, ActionDispatchedEvent, AnalyticsEvent, } from './navigationEvents'; /** * @deprecated Use `import { Tabs } from 'expo-router/js-tabs'` instead. */ diff --git a/packages/expo-router/build/exports.d.ts.map b/packages/expo-router/build/exports.d.ts.map index 969fcafd57299f..882d8e73089488 100644 --- a/packages/expo-router/build/exports.d.ts.map +++ b/packages/expo-router/build/exports.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,WAAW,EACX,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;GAEG;AACH,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,gDAAgD,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,+CAA+C,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5D,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,mBAAmB,SAAS,CAAC;AAE7B,OAAO,EACL,KAAK,EACL,KAAK,UAAU,EACf,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,EACL,KAAK,UAAU,EACf,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D;;GAEG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EACV,kCAAkC,EAClC,mCAAmC,EACnC,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,8BAA8B,CAAC"} \ No newline at end of file +{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,WAAW,EACX,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,MAAM,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;GAEG;AACH,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,gDAAgD,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,+CAA+C,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5D,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,mBAAmB,SAAS,CAAC;AAE7B,OAAO,EACL,KAAK,EACL,KAAK,UAAU,EACf,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,EACL,KAAK,UAAU,EACf,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,cAAc,GACf,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EACV,kCAAkC,EAClC,mCAAmC,EACnC,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,8BAA8B,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/exports.js.map b/packages/expo-router/build/exports.js.map index 0ccc24115efe4f..f2b6aaa92e6a9a 100644 --- a/packages/expo-router/build/exports.js.map +++ b/packages/expo-router/build/exports.js.map @@ -1 +1 @@ -{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kBAAkB;AAClB,iDAAoD;AAmB3C,0FAnBA,qBAAS,OAmBA;AAAE,qFAnBA,gBAAI,OAmBA;AAjBxB,iCAYiB;AAXf,kGAAA,SAAS,OAAA;AACT,8GAAA,qBAAqB,OAAA;AACrB,oGAAA,WAAW,OAAA;AACX,kHAAA,yBAAyB,OAAA;AACzB,8GAAA,qBAAqB,OAAA;AACrB,6GAAA,oBAAoB,OAAA;AACpB,oGAAA,WAAW,OAAA;AACX,0GAAA,iBAAiB,OAAA;AACjB,+GAAA,sBAAsB,OAAA;AACtB,sGAAA,aAAa,OAAA;AACb,4GAAA,mBAAmB,OAAA;AAGrB,mDAAiE;AAAxD,wGAAA,MAAM,OAAA;AAEf,iEAAgE;AAAvD,sHAAA,iBAAiB,OAAA;AAG1B,oBAAoB;AACpB,uCAAsC;AAA7B,oGAAA,QAAQ,OAAA;AACjB,+CAA8C;AAArC,sGAAA,SAAS,OAAA;AAClB,2CAA0C;AAAjC,kGAAA,OAAO,OAAA;AAChB,iDAAkE;AAAzD,wGAAA,UAAU,OAAA;AAEnB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA;AACtB,6DAAwF;AAA/E,oHAAA,gBAAgB,OAAA;AAGzB,WAAW;AACX;;GAEG;AACH,+DAA+C;AAE/C,mBAAmB;AACnB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,mDAAuE;AAA9D,gHAAA,cAAc,OAAA;AACvB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAGrB,yEAAwE;AAA/D,sGAAA,SAAS,OAAA;AAClB,+EAA8E;AAArE,4GAAA,YAAY,OAAA;AACrB,+EAA8E;AAArE,8GAAA,aAAa,OAAA;AACtB,qEAAoE;AAA3D,oGAAA,QAAQ,OAAA;AACjB,uEAAsE;AAA7D,4GAAA,YAAY,OAAA;AACrB,2EAA0E;AAAjE,gHAAA,cAAc,OAAA;AACvB,6DAA4D;AAAnD,oGAAA,QAAQ,OAAA;AAOjB,2CASsB;AARpB,mGAAA,KAAK,OAAA;AAEL,kGAAA,IAAI,OAAA;AAEJ,mGAAA,KAAK,OAAA;AAEL,wGAAA,UAAU,OAAA;AAIZ,uDAA+D;AAAtD,6HAAA,yBAAyB,OAAA;AAElC;;GAEG;AACH,uCAAsC;AAA7B,4FAAA,IAAI,OAAA;AAEb,mEAAiE;AAAxD,uHAAA,iBAAiB,OAAA","sourcesContent":["// Expo Router API\nimport { Navigator, Slot } from './views/Navigator';\n\nexport {\n useRouter,\n useUnstableGlobalHref,\n usePathname,\n useNavigationContainerRef,\n useGlobalSearchParams,\n useLocalSearchParams,\n useSegments,\n useRootNavigation,\n useRootNavigationState,\n useLoaderData,\n useCurrentRouteInfo,\n} from './hooks';\n\nexport { router, type ImperativeRouter } from './imperative-api';\n\nexport { withLayoutContext } from './layouts/withLayoutContext';\nexport { Navigator, Slot };\n\n// Expo Router Views\nexport { ExpoRoot } from './ExpoRoot';\nexport { Unmatched } from './views/Unmatched';\nexport { Sitemap } from './views/Sitemap';\nexport { useSitemap, type SitemapType } from './views/useSitemap';\nexport type { ErrorBoundaryProps } from './views/Try';\nexport { ErrorBoundary } from './views/ErrorBoundary';\nexport { SuspenseFallback, type SuspenseFallbackProps } from './views/SuspenseFallback';\nexport type { ScreenProps } from './useScreens';\n\n// Platform\n/**\n * @hidden\n */\nexport * as SplashScreen from './views/Splash';\n\n// React Navigation\nexport { useNavigation } from './useNavigation';\nexport { useFocusEffect, type EffectCallback } from './useFocusEffect';\nexport { useIsFocused } from './useIsFocused';\nexport type { ResultState } from './fork/getStateFromPath';\n\nexport { DarkTheme } from './react-navigation/native/theming/DarkTheme';\nexport { DefaultTheme } from './react-navigation/native/theming/DefaultTheme';\nexport { ThemeProvider } from './react-navigation/core/theming/ThemeProvider';\nexport { useTheme } from './react-navigation/core/theming/useTheme';\nexport { useRoutePath } from './react-navigation/native/useRoutePath';\nexport { useScrollToTop } from './react-navigation/native/useScrollToTop';\nexport { useRoute } from './react-navigation/core/useRoute';\n\nexport type { RedirectConfig } from './getRoutesCore';\nexport type { SingularOptions } from './useScreens';\n\nexport type * from './types';\n\nexport {\n Badge,\n type BadgeProps,\n Icon,\n type IconProps,\n Label,\n type LabelProps,\n VectorIcon,\n type VectorIconProps,\n} from './primitives';\n\nexport { unstable_navigationEvents } from './navigationEvents';\n\n/**\n * @deprecated Use `import { Tabs } from 'expo-router/js-tabs'` instead.\n */\nexport { Tabs } from './layouts/Tabs';\n\nexport { ExperimentalStack } from './layouts/experimental-stack';\nexport type {\n ExperimentalStackNavigationOptions,\n ExperimentalStackNavigationEventMap,\n ExperimentalStackNavigationProp,\n ExperimentalStackScreenProps,\n} from './layouts/experimental-stack';\n"]} \ No newline at end of file +{"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kBAAkB;AAClB,iDAAoD;AAmB3C,0FAnBA,qBAAS,OAmBA;AAAE,qFAnBA,gBAAI,OAmBA;AAjBxB,iCAYiB;AAXf,kGAAA,SAAS,OAAA;AACT,8GAAA,qBAAqB,OAAA;AACrB,oGAAA,WAAW,OAAA;AACX,kHAAA,yBAAyB,OAAA;AACzB,8GAAA,qBAAqB,OAAA;AACrB,6GAAA,oBAAoB,OAAA;AACpB,oGAAA,WAAW,OAAA;AACX,0GAAA,iBAAiB,OAAA;AACjB,+GAAA,sBAAsB,OAAA;AACtB,sGAAA,aAAa,OAAA;AACb,4GAAA,mBAAmB,OAAA;AAGrB,mDAAiE;AAAxD,wGAAA,MAAM,OAAA;AAEf,iEAAgE;AAAvD,sHAAA,iBAAiB,OAAA;AAG1B,oBAAoB;AACpB,uCAAsC;AAA7B,oGAAA,QAAQ,OAAA;AACjB,+CAA8C;AAArC,sGAAA,SAAS,OAAA;AAClB,2CAA0C;AAAjC,kGAAA,OAAO,OAAA;AAChB,iDAAkE;AAAzD,wGAAA,UAAU,OAAA;AAEnB,uDAAsD;AAA7C,8GAAA,aAAa,OAAA;AACtB,6DAAwF;AAA/E,oHAAA,gBAAgB,OAAA;AAGzB,WAAW;AACX;;GAEG;AACH,+DAA+C;AAE/C,mBAAmB;AACnB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,mDAAuE;AAA9D,gHAAA,cAAc,OAAA;AACvB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAGrB,yEAAwE;AAA/D,sGAAA,SAAS,OAAA;AAClB,+EAA8E;AAArE,4GAAA,YAAY,OAAA;AACrB,+EAA8E;AAArE,8GAAA,aAAa,OAAA;AACtB,qEAAoE;AAA3D,oGAAA,QAAQ,OAAA;AACjB,uEAAsE;AAA7D,4GAAA,YAAY,OAAA;AACrB,2EAA0E;AAAjE,gHAAA,cAAc,OAAA;AACvB,6DAA4D;AAAnD,oGAAA,QAAQ,OAAA;AAOjB,2CASsB;AARpB,mGAAA,KAAK,OAAA;AAEL,kGAAA,IAAI,OAAA;AAEJ,mGAAA,KAAK,OAAA;AAEL,wGAAA,UAAU,OAAA;AAIZ,uDAA+D;AAAtD,6HAAA,yBAAyB,OAAA;AAUlC;;GAEG;AACH,uCAAsC;AAA7B,4FAAA,IAAI,OAAA;AAEb,mEAAiE;AAAxD,uHAAA,iBAAiB,OAAA","sourcesContent":["// Expo Router API\nimport { Navigator, Slot } from './views/Navigator';\n\nexport {\n useRouter,\n useUnstableGlobalHref,\n usePathname,\n useNavigationContainerRef,\n useGlobalSearchParams,\n useLocalSearchParams,\n useSegments,\n useRootNavigation,\n useRootNavigationState,\n useLoaderData,\n useCurrentRouteInfo,\n} from './hooks';\n\nexport { router, type ImperativeRouter } from './imperative-api';\n\nexport { withLayoutContext } from './layouts/withLayoutContext';\nexport { Navigator, Slot };\n\n// Expo Router Views\nexport { ExpoRoot } from './ExpoRoot';\nexport { Unmatched } from './views/Unmatched';\nexport { Sitemap } from './views/Sitemap';\nexport { useSitemap, type SitemapType } from './views/useSitemap';\nexport type { ErrorBoundaryProps } from './views/Try';\nexport { ErrorBoundary } from './views/ErrorBoundary';\nexport { SuspenseFallback, type SuspenseFallbackProps } from './views/SuspenseFallback';\nexport type { ScreenProps } from './useScreens';\n\n// Platform\n/**\n * @hidden\n */\nexport * as SplashScreen from './views/Splash';\n\n// React Navigation\nexport { useNavigation } from './useNavigation';\nexport { useFocusEffect, type EffectCallback } from './useFocusEffect';\nexport { useIsFocused } from './useIsFocused';\nexport type { ResultState } from './fork/getStateFromPath';\n\nexport { DarkTheme } from './react-navigation/native/theming/DarkTheme';\nexport { DefaultTheme } from './react-navigation/native/theming/DefaultTheme';\nexport { ThemeProvider } from './react-navigation/core/theming/ThemeProvider';\nexport { useTheme } from './react-navigation/core/theming/useTheme';\nexport { useRoutePath } from './react-navigation/native/useRoutePath';\nexport { useScrollToTop } from './react-navigation/native/useScrollToTop';\nexport { useRoute } from './react-navigation/core/useRoute';\n\nexport type { RedirectConfig } from './getRoutesCore';\nexport type { SingularOptions } from './useScreens';\n\nexport type * from './types';\n\nexport {\n Badge,\n type BadgeProps,\n Icon,\n type IconProps,\n Label,\n type LabelProps,\n VectorIcon,\n type VectorIconProps,\n} from './primitives';\n\nexport { unstable_navigationEvents } from './navigationEvents';\nexport type {\n PageWillRender,\n PageFocusedEvent,\n PageBlurredEvent,\n PageRemoved,\n ActionDispatchedEvent,\n AnalyticsEvent,\n} from './navigationEvents';\n\n/**\n * @deprecated Use `import { Tabs } from 'expo-router/js-tabs'` instead.\n */\nexport { Tabs } from './layouts/Tabs';\n\nexport { ExperimentalStack } from './layouts/experimental-stack';\nexport type {\n ExperimentalStackNavigationOptions,\n ExperimentalStackNavigationEventMap,\n ExperimentalStackNavigationProp,\n ExperimentalStackScreenProps,\n} from './layouts/experimental-stack';\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/global-state/store.d.ts.map b/packages/expo-router/build/global-state/store.d.ts.map index 9cd7fddc1ff4e6..fd983727b3e28f 100644 --- a/packages/expo-router/build/global-state/store.d.ts.map +++ b/packages/expo-router/build/global-state/store.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/global-state/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGrD,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,CAAC;AAEvC,KAAK,QAAQ,GAAG;IACd,aAAa,EAAE,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAChF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,QAAQ;aACJ,QAAQ;CACxB,CAAC;AAKF,wBAAgB,6BAA6B,uBAE5C;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,QAEtE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,QAEzD;AAED,eAAO,MAAM,KAAK;;;;;oBAaA,SAAS;;;0BASH,IAAI,YAAY,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;2BAS5B,iBAAiB;;4BAehB,oBAAoB,GAAG,SAAS;;CAwCzD,CAAC"} \ No newline at end of file +{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/global-state/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACf,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGrD,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,CAAC;AAEvC,KAAK,QAAQ,GAAG;IACd,aAAa,EAAE,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IAChF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,GAAG,CAAC;IACZ,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,QAAQ;aACJ,QAAQ;CACxB,CAAC;AAKF,wBAAgB,6BAA6B,uBAE5C;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,QAEtE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,QAEzD;AAED,eAAO,MAAM,KAAK;;;;;oBAaA,SAAS;;;0BASH,IAAI,YAAY,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;2BAS5B,iBAAiB;;4BAgBhB,oBAAoB,GAAG,SAAS;;CAwCzD,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/global-state/store.js b/packages/expo-router/build/global-state/store.js index d4f77233209304..4a8c1286d52a20 100644 --- a/packages/expo-router/build/global-state/store.js +++ b/packages/expo-router/build/global-state/store.js @@ -40,6 +40,7 @@ exports.setHasAttemptedToHideSplash = setHasAttemptedToHideSplash; const getRouteInfoFromState_1 = require("./getRouteInfoFromState"); const routeInfoCache_1 = require("./routeInfoCache"); const href_1 = require("../link/href"); +const navigation_1 = require("../navigationEvents/navigation"); const SplashScreen = __importStar(require("../views/Splash")); exports.storeRef = { current: {}, @@ -90,6 +91,7 @@ exports.store = { exports.storeRef.current.routeInfo = routeInfo; }, onReady() { + (0, navigation_1.handleNavigationOnReady)(); if (!hasAttemptedToHideSplash) { setHasAttemptedToHideSplash(true); // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially. diff --git a/packages/expo-router/build/global-state/store.js.map b/packages/expo-router/build/global-state/store.js.map index d0eb21dd56db72..0f978f3ade18c3 100644 --- a/packages/expo-router/build/global-state/store.js.map +++ b/packages/expo-router/build/global-state/store.js.map @@ -1 +1 @@ -{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/global-state/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\n\nimport { defaultRouteInfo, type UrlObject } from './getRouteInfoFromState';\nimport { getCachedRouteInfo, routeInfoSubscribers } from './routeInfoCache';\nimport type {\n FocusedRouteState,\n LinkToOptions,\n ReactNavigationState,\n StoreRedirects,\n} from './types';\nimport type { RouteNode } from '../Route';\nimport type { ExpoLinkingOptions } from '../getLinkingConfig';\nimport { resolveHref, resolveHrefStringWithSegments } from '../link/href';\nimport type { NavigationContainerRefWithCurrent } from '../react-navigation/native';\nimport type { RequireContext, Href } from '../types';\nimport * as SplashScreen from '../views/Splash';\n\nexport type RouterStore = typeof store;\n\ntype StoreRef = {\n navigationRef: NavigationContainerRefWithCurrent;\n routeNode: RouteNode | null;\n rootComponent: ComponentType;\n state?: ReactNavigationState;\n linking?: ExpoLinkingOptions;\n config: any;\n redirects: StoreRedirects[];\n routeInfo?: UrlObject;\n context?: RequireContext;\n};\n\nexport const storeRef = {\n current: {} as StoreRef,\n};\n\nlet splashScreenAnimationFrame: number | undefined;\nlet hasAttemptedToHideSplash = false;\n\nexport function getSplashScreenAnimationFrame() {\n return splashScreenAnimationFrame;\n}\n\nexport function setSplashScreenAnimationFrame(value: number | undefined) {\n splashScreenAnimationFrame = value;\n}\n\nexport function setHasAttemptedToHideSplash(value: boolean) {\n hasAttemptedToHideSplash = value;\n}\n\nexport const store = {\n shouldShowTutorial() {\n return !storeRef.current.routeNode && process.env.NODE_ENV === 'development';\n },\n get state() {\n return storeRef.current.state;\n },\n get navigationRef() {\n return storeRef.current.navigationRef;\n },\n get routeNode() {\n return storeRef.current.routeNode;\n },\n getRouteInfo(): UrlObject {\n return storeRef.current.routeInfo || defaultRouteInfo;\n },\n get redirects() {\n return storeRef.current.redirects || [];\n },\n get rootComponent() {\n return storeRef.current.rootComponent;\n },\n getStateForHref(href: Href, options?: LinkToOptions) {\n href = resolveHref(href);\n\n href = resolveHrefStringWithSegments(href, store.getRouteInfo(), options);\n return this.linking?.getStateFromPath!(href, this.linking.config);\n },\n get linking() {\n return storeRef.current.linking;\n },\n setFocusedState(state: FocusedRouteState) {\n const routeInfo = getCachedRouteInfo(state);\n storeRef.current.routeInfo = routeInfo;\n },\n onReady() {\n if (!hasAttemptedToHideSplash) {\n setHasAttemptedToHideSplash(true);\n // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially.\n setSplashScreenAnimationFrame(\n requestAnimationFrame(() => {\n SplashScreen._internal_maybeHideAsync?.();\n })\n );\n }\n },\n onStateChange(newState: ReactNavigationState | undefined) {\n if (!newState) {\n return;\n }\n if (process.env.NODE_ENV === 'development') {\n let isStale: boolean | undefined = false;\n let state: ReactNavigationState | undefined = newState;\n\n while (!isStale && state) {\n isStale = state.stale;\n state =\n state.routes?.[\n 'index' in state && typeof state.index === 'number'\n ? state.index\n : state.routes.length - 1\n ]?.state;\n }\n if (isStale) {\n // This should never happen, as onStateChange should provide a full state. However, adding this check to catch any undocumented behavior.\n console.error(\n 'Detected stale state in onStateChange. This is likely a bug in Expo Router.'\n );\n }\n }\n\n storeRef.current.state = newState;\n\n storeRef.current.routeInfo = getCachedRouteInfo(newState);\n\n for (const callback of routeInfoSubscribers) {\n callback();\n }\n },\n assertIsReady() {\n if (!storeRef.current.navigationRef.isReady()) {\n throw new Error(\n 'Attempted to navigate before mounting the Root Layout component. Ensure the Root Layout component is rendering a Slot, or other navigator on the first render.'\n );\n }\n },\n};\n"]} \ No newline at end of file +{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/global-state/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentType } from 'react';\n\nimport { defaultRouteInfo, type UrlObject } from './getRouteInfoFromState';\nimport { getCachedRouteInfo, routeInfoSubscribers } from './routeInfoCache';\nimport type {\n FocusedRouteState,\n LinkToOptions,\n ReactNavigationState,\n StoreRedirects,\n} from './types';\nimport type { RouteNode } from '../Route';\nimport type { ExpoLinkingOptions } from '../getLinkingConfig';\nimport { resolveHref, resolveHrefStringWithSegments } from '../link/href';\nimport { handleNavigationOnReady } from '../navigationEvents/navigation';\nimport type { NavigationContainerRefWithCurrent } from '../react-navigation/native';\nimport type { RequireContext, Href } from '../types';\nimport * as SplashScreen from '../views/Splash';\n\nexport type RouterStore = typeof store;\n\ntype StoreRef = {\n navigationRef: NavigationContainerRefWithCurrent;\n routeNode: RouteNode | null;\n rootComponent: ComponentType;\n state?: ReactNavigationState;\n linking?: ExpoLinkingOptions;\n config: any;\n redirects: StoreRedirects[];\n routeInfo?: UrlObject;\n context?: RequireContext;\n};\n\nexport const storeRef = {\n current: {} as StoreRef,\n};\n\nlet splashScreenAnimationFrame: number | undefined;\nlet hasAttemptedToHideSplash = false;\n\nexport function getSplashScreenAnimationFrame() {\n return splashScreenAnimationFrame;\n}\n\nexport function setSplashScreenAnimationFrame(value: number | undefined) {\n splashScreenAnimationFrame = value;\n}\n\nexport function setHasAttemptedToHideSplash(value: boolean) {\n hasAttemptedToHideSplash = value;\n}\n\nexport const store = {\n shouldShowTutorial() {\n return !storeRef.current.routeNode && process.env.NODE_ENV === 'development';\n },\n get state() {\n return storeRef.current.state;\n },\n get navigationRef() {\n return storeRef.current.navigationRef;\n },\n get routeNode() {\n return storeRef.current.routeNode;\n },\n getRouteInfo(): UrlObject {\n return storeRef.current.routeInfo || defaultRouteInfo;\n },\n get redirects() {\n return storeRef.current.redirects || [];\n },\n get rootComponent() {\n return storeRef.current.rootComponent;\n },\n getStateForHref(href: Href, options?: LinkToOptions) {\n href = resolveHref(href);\n\n href = resolveHrefStringWithSegments(href, store.getRouteInfo(), options);\n return this.linking?.getStateFromPath!(href, this.linking.config);\n },\n get linking() {\n return storeRef.current.linking;\n },\n setFocusedState(state: FocusedRouteState) {\n const routeInfo = getCachedRouteInfo(state);\n storeRef.current.routeInfo = routeInfo;\n },\n onReady() {\n handleNavigationOnReady();\n if (!hasAttemptedToHideSplash) {\n setHasAttemptedToHideSplash(true);\n // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially.\n setSplashScreenAnimationFrame(\n requestAnimationFrame(() => {\n SplashScreen._internal_maybeHideAsync?.();\n })\n );\n }\n },\n onStateChange(newState: ReactNavigationState | undefined) {\n if (!newState) {\n return;\n }\n if (process.env.NODE_ENV === 'development') {\n let isStale: boolean | undefined = false;\n let state: ReactNavigationState | undefined = newState;\n\n while (!isStale && state) {\n isStale = state.stale;\n state =\n state.routes?.[\n 'index' in state && typeof state.index === 'number'\n ? state.index\n : state.routes.length - 1\n ]?.state;\n }\n if (isStale) {\n // This should never happen, as onStateChange should provide a full state. However, adding this check to catch any undocumented behavior.\n console.error(\n 'Detected stale state in onStateChange. This is likely a bug in Expo Router.'\n );\n }\n }\n\n storeRef.current.state = newState;\n\n storeRef.current.routeInfo = getCachedRouteInfo(newState);\n\n for (const callback of routeInfoSubscribers) {\n callback();\n }\n },\n assertIsReady() {\n if (!storeRef.current.navigationRef.isReady()) {\n throw new Error(\n 'Attempted to navigate before mounting the Root Layout component. Ensure the Root Layout component is rendering a Slot, or other navigator on the first render.'\n );\n }\n },\n};\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts b/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts index aa53493c6ba56a..47edd7562ebf68 100644 --- a/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts +++ b/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts @@ -32,6 +32,8 @@ export declare function useSharedScreenProps(props: InternalTabScreenProps): { onWillAppear?: import("react-native-screens").TabsScreenEventHandler | undefined; onWillDisappear?: import("react-native-screens").TabsScreenEventHandler | undefined; orientation?: import("react-native-screens").TabsScreenOrientation | undefined; + onDidAppear?: import("react-native-screens").TabsScreenEventHandler | undefined; + onDidDisappear?: import("react-native-screens").TabsScreenEventHandler | undefined; preventNativeSelection?: boolean | undefined | undefined; badgeValue?: string | undefined | undefined; specialEffects?: { @@ -42,8 +44,6 @@ export declare function useSharedScreenProps(props: InternalTabScreenProps): { } | undefined | undefined; tabBarItemTestID?: string | undefined | undefined; tabBarItemAccessibilityLabel?: string | undefined | undefined; - onDidAppear?: import("react-native-screens").TabsScreenEventHandler | undefined; - onDidDisappear?: import("react-native-screens").TabsScreenEventHandler | undefined; }; screenKey: string; icon: { diff --git a/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts.map b/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts.map index 804c881e6d7766..f10352e7c2efde 100644 --- a/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts.map +++ b/packages/expo-router/build/native-tabs/NativeTabsView.shared.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"NativeTabsView.shared.d.ts","sourceRoot":"","sources":["../../src/native-tabs/NativeTabsView.shared.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAIrE,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,CAAC,GAAG;IACrE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB,CAgBA;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,mBAAmB,CAAC,aAAa,CAAC,GAC9C,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAU7C;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IAEb,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,eAAe,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACxC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,sBAAsB;;mBAgBV,UAAU,GAAG,MAAM;;;;;;;;;;;;;;;;gCAZhE,CAAC;yBAGN,CAAA;2BAGE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAeR;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,eAAe,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACxC,2CAcA"} \ No newline at end of file +{"version":3,"file":"NativeTabsView.shared.d.ts","sourceRoot":"","sources":["../../src/native-tabs/NativeTabsView.shared.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAIrE,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,UAAU,EACV,IAAI,GACL,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,GAAG,YAAY,GAAG,MAAM,CAAC,GAAG;IACrE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB,CAgBA;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,mBAAmB,CAAC,aAAa,CAAC,GAC9C,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAU7C;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IAEb,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,eAAe,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACxC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,sBAAsB;;mBAgBV,UAAU,GAAG,MAAM;;;;;;;;;;;;;;;;;;gCAZhE,CAAC;yBAGN,CAAA;2BAGE,CAAC;;;;;;;;;;;;;;;;;;;;;;;EAeR;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,gBAAgB,CAAC;IAC1B,eAAe,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACxC,2CAcA"} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/index.d.ts b/packages/expo-router/build/navigationEvents/index.d.ts index 1c665d5a03f512..ae78f28574647c 100644 --- a/packages/expo-router/build/navigationEvents/index.d.ts +++ b/packages/expo-router/build/navigationEvents/index.d.ts @@ -1,6 +1,6 @@ -import type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types'; -export type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types'; -export type AnalyticsEvent = PageWillRender | PageFocusedEvent | PageBlurredEvent | PageRemoved; +import type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved, ActionDispatchedEvent } from './types'; +export type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved, ActionDispatchedEvent, } from './types'; +export type AnalyticsEvent = PageWillRender | PageFocusedEvent | PageBlurredEvent | PageRemoved | ActionDispatchedEvent; type EventTypeName = AnalyticsEvent['type']; type Payload = Omit void; isEnabled: () => boolean; - saveCurrentPathname: () => void; - readonly currentPathname: string | undefined; - readonly currentParams: Record | undefined; }; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/index.d.ts.map b/packages/expo-router/build/navigationEvents/index.d.ts.map index 1632af3dd0fc5b..820aad49ecb1f2 100644 --- a/packages/expo-router/build/navigationEvents/index.d.ts.map +++ b/packages/expo-router/build/navigationEvents/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigationEvents/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/F,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/F,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAShG,KAAK,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;AAC5C,KAAK,OAAO,CAAC,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAM3F,iBAAS,WAAW,CAAC,SAAS,SAAS,aAAa,EAClD,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,cAe9C;AAED,wBAAgB,IAAI,CAAC,SAAS,SAAS,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,QAO/F;AAQD,eAAO,MAAM,yBAAyB;;;;;;;;CAsBrC,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigationEvents/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GACtB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,gBAAgB,GAChB,gBAAgB,GAChB,WAAW,GACX,qBAAqB,CAAC;AAU1B,KAAK,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;AAC5C,KAAK,OAAO,CAAC,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAM3F,iBAAS,WAAW,CAAC,SAAS,SAAS,aAAa,EAClD,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,cAe9C;AAED,wBAAgB,IAAI,CAAC,SAAS,SAAS,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,QAO/F;AAID,eAAO,MAAM,yBAAyB;;;;;CASrC,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/index.js b/packages/expo-router/build/navigationEvents/index.js index db2e124823dcdf..4773606a221bad 100644 --- a/packages/expo-router/build/navigationEvents/index.js +++ b/packages/expo-router/build/navigationEvents/index.js @@ -7,6 +7,7 @@ const availableEvents = [ 'pageFocused', 'pageBlurred', 'pageRemoved', + 'actionDispatched', ]; const subscribers = {}; function addListener(eventType, callback) { @@ -33,9 +34,6 @@ function emit(type, event) { } } let enabled = false; -let currentPathname = undefined; -let currentParams = undefined; -let currentPathnameListener = undefined; exports.unstable_navigationEvents = { addListener, emit, @@ -45,44 +43,5 @@ exports.unstable_navigationEvents = { isEnabled: () => { return enabled; }, - saveCurrentPathname: () => { - if (!enabled || currentPathnameListener) - return; - currentPathnameListener = addListener('pageFocused', (event) => { - currentPathname = event.pathname; - currentParams = event.params; - }); - }, - get currentPathname() { - return currentPathname; - }, - get currentParams() { - return currentParams; - }, }; -if (globalThis.expo) { - globalThis.expo.router = globalThis.expo.router || {}; - if (!('navigationEvents' in globalThis.expo.router)) { - Object.defineProperties(globalThis.expo.router, { - navigationEvents: { - get() { - return exports.unstable_navigationEvents; - }, - enumerable: true, - }, - currentPathname: { - get() { - return currentPathname; - }, - enumerable: true, - }, - currentParams: { - get() { - return currentParams; - }, - enumerable: true, - }, - }); - } -} //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/index.js.map b/packages/expo-router/build/navigationEvents/index.js.map index 6edf5a2d815dda..93dca6cb5dae0d 100644 --- a/packages/expo-router/build/navigationEvents/index.js.map +++ b/packages/expo-router/build/navigationEvents/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigationEvents/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types';\n\nexport type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types';\n\nexport type AnalyticsEvent = PageWillRender | PageFocusedEvent | PageBlurredEvent | PageRemoved;\n\nconst availableEvents: AnalyticsEvent['type'][] = [\n 'pageWillRender',\n 'pageFocused',\n 'pageBlurred',\n 'pageRemoved',\n];\n\ntype EventTypeName = AnalyticsEvent['type'];\ntype Payload = Omit, 'type'>;\n\nconst subscribers: {\n [K in EventTypeName]?: Set<(event: Payload) => void>;\n} = {};\n\nfunction addListener(\n eventType: EventType,\n callback: (event: Payload) => void\n) {\n if (!availableEvents.includes(eventType)) {\n throw new Error(`Unsupported event type: ${eventType}`);\n }\n if (!subscribers[eventType]) {\n subscribers[eventType] = new Set() as (typeof subscribers)[EventType];\n }\n subscribers[eventType]!.add(callback);\n return () => {\n subscribers[eventType]!.delete(callback);\n if (subscribers[eventType]!.size === 0) {\n delete subscribers[eventType];\n }\n };\n}\n\nexport function emit(type: EventType, event: Payload) {\n const subscribersForEvent = subscribers[type];\n if (subscribersForEvent) {\n for (const callback of subscribersForEvent) {\n callback(event);\n }\n }\n}\n\nlet enabled = false;\n\nlet currentPathname: string | undefined = undefined;\nlet currentParams: Record | undefined = undefined;\nlet currentPathnameListener: ReturnType | undefined = undefined;\n\nexport const unstable_navigationEvents = {\n addListener,\n emit,\n enable: () => {\n enabled = true;\n },\n isEnabled: () => {\n return enabled;\n },\n saveCurrentPathname: () => {\n if (!enabled || currentPathnameListener) return;\n currentPathnameListener = addListener('pageFocused', (event) => {\n currentPathname = event.pathname;\n currentParams = event.params;\n });\n },\n get currentPathname() {\n return currentPathname;\n },\n get currentParams() {\n return currentParams;\n },\n};\n\nif (globalThis.expo) {\n globalThis.expo.router = globalThis.expo.router || {};\n\n if (!('navigationEvents' in globalThis.expo.router)) {\n Object.defineProperties(globalThis.expo.router, {\n navigationEvents: {\n get() {\n return unstable_navigationEvents;\n },\n enumerable: true,\n },\n currentPathname: {\n get() {\n return currentPathname;\n },\n enumerable: true,\n },\n currentParams: {\n get() {\n return currentParams;\n },\n enumerable: true,\n },\n });\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigationEvents/index.ts"],"names":[],"mappings":";;;AAyDA,oBAOC;AAzCD,MAAM,eAAe,GAA6B;IAChD,gBAAgB;IAChB,aAAa;IACb,aAAa;IACb,aAAa;IACb,kBAAkB;CACnB,CAAC;AAKF,MAAM,WAAW,GAEb,EAAE,CAAC;AAEP,SAAS,WAAW,CAClB,SAAoB,EACpB,QAA6C;IAE7C,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,2BAA2B,SAAS,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,WAAW,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,EAAqC,CAAC;IACxE,CAAC;IACD,WAAW,CAAC,SAAS,CAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,OAAO,GAAG,EAAE;QACV,WAAW,CAAC,SAAS,CAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,WAAW,CAAC,SAAS,CAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvC,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAgB,IAAI,CAAkC,IAAe,EAAE,KAAyB;IAC9F,MAAM,mBAAmB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,mBAAmB,EAAE,CAAC;QACxB,KAAK,MAAM,QAAQ,IAAI,mBAAmB,EAAE,CAAC;YAC3C,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,OAAO,GAAG,KAAK,CAAC;AAEP,QAAA,yBAAyB,GAAG;IACvC,WAAW;IACX,IAAI;IACJ,MAAM,EAAE,GAAG,EAAE;QACX,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAC","sourcesContent":["import type {\n PageWillRender,\n PageFocusedEvent,\n PageBlurredEvent,\n PageRemoved,\n ActionDispatchedEvent,\n} from './types';\n\nexport type {\n PageWillRender,\n PageFocusedEvent,\n PageBlurredEvent,\n PageRemoved,\n ActionDispatchedEvent,\n} from './types';\n\nexport type AnalyticsEvent =\n | PageWillRender\n | PageFocusedEvent\n | PageBlurredEvent\n | PageRemoved\n | ActionDispatchedEvent;\n\nconst availableEvents: AnalyticsEvent['type'][] = [\n 'pageWillRender',\n 'pageFocused',\n 'pageBlurred',\n 'pageRemoved',\n 'actionDispatched',\n];\n\ntype EventTypeName = AnalyticsEvent['type'];\ntype Payload = Omit, 'type'>;\n\nconst subscribers: {\n [K in EventTypeName]?: Set<(event: Payload) => void>;\n} = {};\n\nfunction addListener(\n eventType: EventType,\n callback: (event: Payload) => void\n) {\n if (!availableEvents.includes(eventType)) {\n throw new Error(`Unsupported event type: ${eventType}`);\n }\n if (!subscribers[eventType]) {\n subscribers[eventType] = new Set() as (typeof subscribers)[EventType];\n }\n subscribers[eventType]!.add(callback);\n return () => {\n subscribers[eventType]!.delete(callback);\n if (subscribers[eventType]!.size === 0) {\n delete subscribers[eventType];\n }\n };\n}\n\nexport function emit(type: EventType, event: Payload) {\n const subscribersForEvent = subscribers[type];\n if (subscribersForEvent) {\n for (const callback of subscribersForEvent) {\n callback(event);\n }\n }\n}\n\nlet enabled = false;\n\nexport const unstable_navigationEvents = {\n addListener,\n emit,\n enable: () => {\n enabled = true;\n },\n isEnabled: () => {\n return enabled;\n },\n};\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/navigation.d.ts b/packages/expo-router/build/navigationEvents/navigation.d.ts new file mode 100644 index 00000000000000..22fe6cf60183b0 --- /dev/null +++ b/packages/expo-router/build/navigationEvents/navigation.d.ts @@ -0,0 +1,2 @@ +export declare function handleNavigationOnReady(): void; +//# sourceMappingURL=navigation.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/navigation.d.ts.map b/packages/expo-router/build/navigationEvents/navigation.d.ts.map new file mode 100644 index 00000000000000..3aa06381af4307 --- /dev/null +++ b/packages/expo-router/build/navigationEvents/navigation.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/navigationEvents/navigation.ts"],"names":[],"mappings":"AAKA,wBAAgB,uBAAuB,SAYtC"} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/navigation.js b/packages/expo-router/build/navigationEvents/navigation.js new file mode 100644 index 00000000000000..e7eb7c5565aada --- /dev/null +++ b/packages/expo-router/build/navigationEvents/navigation.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handleNavigationOnReady = handleNavigationOnReady; +const _1 = require("."); +const store_1 = require("../global-state/store"); +let unsubscribe; +function handleNavigationOnReady() { + if (unsubscribe) + unsubscribe(); + unsubscribe = store_1.storeRef.current.navigationRef.addListener('__unsafe_action__', (e) => { + if (!e.data.noop && store_1.storeRef.current.state) { + const action = e.data.action; + (0, _1.emit)('actionDispatched', { + actionType: action.type, + payload: action.payload, + state: store_1.storeRef.current.state, + }); + } + }); +} +//# sourceMappingURL=navigation.js.map \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/navigation.js.map b/packages/expo-router/build/navigationEvents/navigation.js.map new file mode 100644 index 00000000000000..90a0f5e4bb3b2c --- /dev/null +++ b/packages/expo-router/build/navigationEvents/navigation.js.map @@ -0,0 +1 @@ +{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/navigationEvents/navigation.ts"],"names":[],"mappings":";;AAKA,0DAYC;AAjBD,wBAAyB;AACzB,iDAAiD;AAEjD,IAAI,WAAqC,CAAC;AAE1C,SAAgB,uBAAuB;IACrC,IAAI,WAAW;QAAE,WAAW,EAAE,CAAC;IAC/B,WAAW,GAAG,gBAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE;QAClF,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,gBAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;YAC7B,IAAA,OAAI,EAAC,kBAAkB,EAAE;gBACvB,UAAU,EAAE,MAAM,CAAC,IAAI;gBACvB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,KAAK,EAAE,gBAAQ,CAAC,OAAO,CAAC,KAAK;aAC9B,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { emit } from '.';\nimport { storeRef } from '../global-state/store';\n\nlet unsubscribe: (() => void) | undefined;\n\nexport function handleNavigationOnReady() {\n if (unsubscribe) unsubscribe();\n unsubscribe = storeRef.current.navigationRef.addListener('__unsafe_action__', (e) => {\n if (!e.data.noop && storeRef.current.state) {\n const action = e.data.action;\n emit('actionDispatched', {\n actionType: action.type,\n payload: action.payload,\n state: storeRef.current.state,\n });\n }\n });\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/types.d.ts b/packages/expo-router/build/navigationEvents/types.d.ts index 1a6d6c1f74b8e8..eadfd8b99c7666 100644 --- a/packages/expo-router/build/navigationEvents/types.d.ts +++ b/packages/expo-router/build/navigationEvents/types.d.ts @@ -1,6 +1,8 @@ +import type { ReactNavigationState } from '../global-state/types'; +import type { NavigationAction } from '../react-navigation'; export interface BasePageEvent { pathname: string; - params: Record; + params: Record; screenId: string; } /** @@ -20,4 +22,11 @@ export interface PageBlurredEvent extends BasePageEvent { export interface PageRemoved extends BasePageEvent { type: 'pageRemoved'; } +export interface ActionDispatchedEvent { + type: 'actionDispatched'; + /** The action type from the dispatched NavigationAction (e.g. `NAVIGATE`). */ + actionType: NavigationAction['type']; + payload: NavigationAction['payload']; + state: ReactNavigationState; +} //# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/types.d.ts.map b/packages/expo-router/build/navigationEvents/types.d.ts.map index f5e74ba9b06d44..c1946a4977564b 100644 --- a/packages/expo-router/build/navigationEvents/types.d.ts.map +++ b/packages/expo-router/build/navigationEvents/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigationEvents/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,IAAI,EAAE,aAAa,CAAC;CACrB"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigationEvents/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,kBAAkB,CAAC;IACzB,8EAA8E;IAC9E,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACrC,KAAK,EAAE,oBAAoB,CAAC;CAC7B"} \ No newline at end of file diff --git a/packages/expo-router/build/navigationEvents/types.js.map b/packages/expo-router/build/navigationEvents/types.js.map index a6cc22377167f2..79d2680924738d 100644 --- a/packages/expo-router/build/navigationEvents/types.js.map +++ b/packages/expo-router/build/navigationEvents/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/navigationEvents/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface BasePageEvent {\n pathname: string;\n params: Record;\n screenId: string;\n}\n\n/**\n * The rendering of the page started\n *\n * This can happen if screen is to be focused for the first time or when the screen is preloaded\n */\nexport interface PageWillRender extends BasePageEvent {\n type: 'pageWillRender';\n}\n\nexport interface PageFocusedEvent extends BasePageEvent {\n type: 'pageFocused';\n}\n\nexport interface PageBlurredEvent extends BasePageEvent {\n type: 'pageBlurred';\n}\n\nexport interface PageRemoved extends BasePageEvent {\n type: 'pageRemoved';\n}\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/navigationEvents/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReactNavigationState } from '../global-state/types';\nimport type { NavigationAction } from '../react-navigation';\n\nexport interface BasePageEvent {\n pathname: string;\n params: Record;\n screenId: string;\n}\n\n/**\n * The rendering of the page started\n *\n * This can happen if screen is to be focused for the first time or when the screen is preloaded\n */\nexport interface PageWillRender extends BasePageEvent {\n type: 'pageWillRender';\n}\n\nexport interface PageFocusedEvent extends BasePageEvent {\n type: 'pageFocused';\n}\n\nexport interface PageBlurredEvent extends BasePageEvent {\n type: 'pageBlurred';\n}\n\nexport interface PageRemoved extends BasePageEvent {\n type: 'pageRemoved';\n}\n\nexport interface ActionDispatchedEvent {\n type: 'actionDispatched';\n /** The action type from the dispatched NavigationAction (e.g. `NAVIGATE`). */\n actionType: NavigationAction['type'];\n payload: NavigationAction['payload'];\n state: ReactNavigationState;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/useScreens.d.ts.map b/packages/expo-router/build/useScreens.d.ts.map index 7660d1e4f1a5c0..319fbf72db98af 100644 --- a/packages/expo-router/build/useScreens.d.ts.map +++ b/packages/expo-router/build/useScreens.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-router/build/useScreens.js b/packages/expo-router/build/useScreens.js index 14801e7e8e47d5..660b3925e6519d 100644 --- a/packages/expo-router/build/useScreens.js +++ b/packages/expo-router/build/useScreens.js @@ -48,11 +48,11 @@ const react_2 = __importStar(require("react")); const Route_1 = require("./Route"); const storeContext_1 = require("./global-state/storeContext"); const utils_1 = require("./global-state/utils"); +const hooks_1 = require("./hooks"); const import_mode_1 = __importDefault(require("./import-mode")); const ZoomTransitionEnabler_1 = require("./link/zoom/ZoomTransitionEnabler"); const zoom_transition_context_providers_1 = require("./link/zoom/zoom-transition-context-providers"); const navigationEvents_1 = require("./navigationEvents"); -const utils_2 = require("./navigationEvents/utils"); const navigationParams_1 = require("./navigationParams"); const primitives_1 = require("./primitives"); const native_1 = require("./react-navigation/native"); @@ -260,46 +260,49 @@ function getQualifiedRouteComponent(value) { return BaseRoute; } function AnalyticsListeners({ navigation, screenId, }) { - const stateForPath = (0, native_1.useStateForPath)(); const isFirstRenderRef = react_2.default.useRef(true); const hasBlurredRef = react_2.default.useRef(true); - const stringUrl = (0, react_2.useMemo)(() => (0, utils_2.generateStringUrlForState)(stateForPath), [stateForPath]); + const routeInfo = (0, hooks_1.useCurrentRouteInfo)(); if (isFirstRenderRef.current) { isFirstRenderRef.current = false; - if (stringUrl) { + if (routeInfo) { navigationEvents_1.unstable_navigationEvents.emit('pageWillRender', { - ...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); } } (0, react_2.useEffect)(() => { - if (stringUrl) { + if (routeInfo) { return () => { navigationEvents_1.unstable_navigationEvents.emit('pageRemoved', { - ...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); }; } return () => { }; - }, [stringUrl, screenId]); + }, [routeInfo?.params, routeInfo?.pathname, screenId]); const isFocused = navigation.isFocused(); - if (isFocused && stringUrl) { + if (isFocused && routeInfo) { navigationEvents_1.unstable_navigationEvents.emit('pageFocused', { - ...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = false; } (0, react_2.useEffect)(() => { - if (stringUrl) { + if (routeInfo) { const cleanFocus = navigation.addListener('focus', () => { // If the screen was not blurred, don't emit focused again // hasBlurredRef will be false when the screen was initially focused if (hasBlurredRef.current) { navigationEvents_1.unstable_navigationEvents.emit('pageFocused', { - ...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = false; @@ -307,7 +310,8 @@ function AnalyticsListeners({ navigation, screenId, }) { }); const cleanBlur = navigation.addListener('blur', () => { navigationEvents_1.unstable_navigationEvents.emit('pageBlurred', { - ...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = true; @@ -318,7 +322,7 @@ function AnalyticsListeners({ navigation, screenId, }) { }; } return () => { }; - }, [navigation, stringUrl, screenId]); + }, [navigation, routeInfo?.pathname, routeInfo?.params, screenId]); return null; } function screenOptionsFactory(route, options) { diff --git a/packages/expo-router/build/useScreens.js.map b/packages/expo-router/build/useScreens.js.map index 4f38cf8b380892..df6a406c3ba2e1 100644 --- a/packages/expo-router/build/useScreens.js.map +++ b/packages/expo-router/build/useScreens.js.map @@ -1 +1 @@ -{"version":3,"file":"useScreens.js","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport React, { use, useEffect, useMemo } from 'react';\n\nimport type { LoadedRoute, RouteNode } from './Route';\nimport { SuspenseFallbackContext, Route, sortRoutesWithInitial, useRouteNode } from './Route';\nimport { useExpoRouterStore } from './global-state/storeContext';\nimport { useColorSchemeChangesIfNeeded } from './global-state/utils';\nimport EXPO_ROUTER_IMPORT_MODE from './import-mode';\nimport { ZoomTransitionEnabler } from './link/zoom/ZoomTransitionEnabler';\nimport { ZoomTransitionTargetContextProvider } from './link/zoom/zoom-transition-context-providers';\nimport { unstable_navigationEvents } from './navigationEvents';\nimport { generateStringUrlForState, getPathAndParamsFromStringUrl } from './navigationEvents/utils';\nimport {\n hasParam,\n INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME,\n removeParams,\n} from './navigationParams';\nimport { Screen } from './primitives';\nimport type { BottomTabNavigationEventMap } from './react-navigation/bottom-tabs';\nimport {\n useStateForPath,\n type EventConsumer,\n type EventMapBase,\n type NavigationProp,\n type NavigationState,\n type ParamListBase,\n type RouteProp,\n type ScreenListeners,\n} from './react-navigation/native';\nimport type { NativeStackNavigationEventMap } from './react-navigation/native-stack';\nimport type { UnknownOutputParams } from './types';\nimport { EmptyRoute } from './views/EmptyRoute';\nimport {\n SuspenseFallback as DefaultSuspenseFallback,\n type SuspenseFallbackProps,\n} from './views/SuspenseFallback';\nimport { Try } from './views/Try';\n\nexport type ScreenProps<\n TOptions extends Record = Record,\n TState extends NavigationState = NavigationState,\n TEventMap extends EventMapBase = EventMapBase,\n> = {\n /** Name is required when used inside a Layout component. */\n name?: string;\n /**\n * Redirect to the nearest sibling route.\n * If all children are `redirect={true}`, the layout will render `null` as there are no children to render.\n */\n redirect?: boolean;\n initialParams?: Record;\n options?:\n | TOptions\n | ((prop: { route: RouteProp; navigation: any }) => TOptions);\n\n listeners?:\n | ScreenListeners\n | ((prop: {\n route: RouteProp;\n navigation: any;\n }) => ScreenListeners);\n\n getId?: ({ params }: { params?: Record }) => string | undefined;\n\n dangerouslySingular?: SingularOptions;\n};\n\nexport type SingularOptions =\n | boolean\n | ((name: string, params: UnknownOutputParams) => string | undefined);\n\nfunction getSortedChildren(\n children: RouteNode[],\n order: ScreenProps[] = [],\n initialRouteName?: string\n): { route: RouteNode; props: Partial }[] {\n if (!order?.length) {\n return children\n .sort(sortRoutesWithInitial(initialRouteName))\n .map((route) => ({ route, props: {} }));\n }\n const entries = [...children];\n\n const ordered = order\n .map(\n ({\n name,\n redirect,\n initialParams,\n listeners,\n options,\n getId,\n dangerouslySingular: singular,\n }) => {\n if (!entries.length) {\n console.warn(\n `[Layout children]: Too many screens defined. Route \"${name}\" is extraneous.`\n );\n return null;\n }\n const matchIndex = entries.findIndex(\n (child) => child.route === name || child.route === `${name}/index`\n );\n if (matchIndex === -1) {\n console.warn(\n `[Layout children]: No route named \"${name}\" exists in nested children:`,\n children.map(({ route }) => route)\n );\n return null;\n } else {\n // Get match and remove from entries\n const match = entries[matchIndex];\n entries.splice(matchIndex, 1);\n\n // Ensure to return null after removing from entries.\n if (redirect) {\n if (typeof redirect === 'string') {\n throw new Error(`Redirecting to a specific route is not supported yet.`);\n }\n return null;\n }\n\n if (getId) {\n console.warn(\n `Deprecated: prop 'getId' on screen ${name} is deprecated. Please rename the prop to 'dangerouslySingular'`\n );\n if (singular) {\n console.warn(\n `Screen ${name} cannot use both getId and dangerouslySingular together.`\n );\n }\n } else if (singular) {\n // If singular is set, use it as the getId function.\n if (typeof singular === 'string') {\n getId = () => singular;\n } else if (typeof singular === 'function' && name) {\n getId = (options) => singular(name, options.params || {});\n } else if (singular === true && name) {\n getId = (options) => getSingularId(name, options);\n }\n }\n\n return {\n route: match,\n props: { initialParams, listeners, options, getId },\n };\n }\n }\n )\n .filter(Boolean) as {\n route: RouteNode;\n props: Partial;\n }[];\n\n // Add any remaining children\n ordered.push(\n ...entries.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }))\n );\n\n return ordered;\n}\n\n/**\n * @returns React Navigation screens sorted by the `route` property.\n */\nexport function useSortedScreens(\n order: ScreenProps[],\n protectedScreens: Set,\n useOnlyUserDefinedScreens: boolean = false\n): React.ReactNode[] {\n const node = useRouteNode();\n\n const nodeChildren = node?.children ?? [];\n const children = useOnlyUserDefinedScreens\n ? nodeChildren.filter((child) =>\n order.some(\n (userDefinedScreen) =>\n userDefinedScreen.name === child.route ||\n `${userDefinedScreen.name}/index` === child.route\n )\n )\n : nodeChildren;\n\n const sorted = children.length ? getSortedChildren(children, order, node?.initialRouteName) : [];\n return React.useMemo(\n () =>\n sorted\n .filter((item) => {\n const route = item.route.route;\n return (\n !protectedScreens.has(route) && !protectedScreens.has(route.replace(/\\/index$/, ''))\n );\n })\n .map((value) => {\n return routeToScreen(value.route, value.props);\n }),\n [sorted, protectedScreens]\n );\n}\n\nfunction fromImport(\n value: RouteNode,\n { ErrorBoundary, SuspenseFallback, ...component }: LoadedRoute\n) {\n // If possible, add a more helpful display name for the component stack to improve debugging of React errors such as `Text strings must be rendered within a component.`.\n if (component?.default && __DEV__) {\n component.default.displayName ??= `${component.default.name ?? 'Route'}(${value.contextKey})`;\n }\n\n if (ErrorBoundary) {\n const Wrapped = React.forwardRef((props: any, ref: any) => {\n const children = React.createElement(component.default || EmptyRoute, {\n ...props,\n ref,\n });\n return {children};\n });\n\n if (__DEV__) {\n Wrapped.displayName = `ErrorBoundary(${value.contextKey})`;\n }\n\n return {\n default: Wrapped,\n SuspenseFallback,\n };\n }\n if (process.env.NODE_ENV !== 'production') {\n if (\n typeof component.default === 'object' &&\n component.default &&\n Object.keys(component.default).length === 0\n ) {\n return { default: EmptyRoute, SuspenseFallback };\n }\n }\n\n return { default: component.default, SuspenseFallback };\n}\n\nfunction fromLoadedRoute(value: RouteNode, res: LoadedRoute) {\n if (!(res instanceof Promise)) {\n return fromImport(value, res);\n }\n\n return res.then(fromImport.bind(null, value));\n}\n\n// TODO: Maybe there's a more React-y way to do this?\n// Without this store, the process enters a recursive loop.\nconst qualifiedStore = new WeakMap>();\n\n/** Wrap the component with various enhancements and add access to child routes. */\nexport function getQualifiedRouteComponent(value: RouteNode) {\n if (qualifiedStore.has(value)) {\n return qualifiedStore.get(value)!;\n }\n\n let ScreenComponent:\n | React.ForwardRefExoticComponent>\n | React.ComponentType<{ segment?: string }>;\n\n let LayoutSuspenseFallback: React.ComponentType | undefined;\n\n // TODO: This ensures sync doesn't use React.lazy, but it's not ideal.\n if (EXPO_ROUTER_IMPORT_MODE === 'lazy') {\n ScreenComponent = React.lazy(async () => {\n const res = value.loadRoute();\n return fromLoadedRoute(value, res) as Promise<{\n default: React.ComponentType;\n }>;\n });\n\n if (__DEV__) {\n ScreenComponent.displayName = `AsyncRoute(${value.route})`;\n }\n } else {\n const res = value.loadRoute();\n const result = fromImport(value, res);\n ScreenComponent = result.default!;\n LayoutSuspenseFallback = value.type === 'layout' ? result.SuspenseFallback : undefined;\n }\n const WrappedScreenComponent: typeof ScreenComponent = (props: object) => {\n useColorSchemeChangesIfNeeded();\n return ;\n };\n function BaseRoute({\n // Remove these React Navigation props to\n // enforce usage of expo-router hooks (where the query params are correct).\n route,\n navigation,\n\n // Pass all other props to the component\n ...props\n }: {\n route?: RouteProp;\n navigation: Omit<\n NavigationProp<\n ParamListBase,\n string,\n undefined,\n NavigationState,\n object,\n NativeStackNavigationEventMap | BottomTabNavigationEventMap\n >,\n 'getState'\n > & {\n getState(): NavigationState | undefined;\n };\n }) {\n const stateForPath = useStateForPath();\n const isFocused = navigation.isFocused();\n const store = useExpoRouterStore();\n const InheritedSuspenseFallback = use(SuspenseFallbackContext);\n\n const ResolvedSuspenseFallback =\n EXPO_ROUTER_IMPORT_MODE === 'lazy'\n ? DefaultSuspenseFallback\n : (LayoutSuspenseFallback ?? InheritedSuspenseFallback ?? DefaultSuspenseFallback);\n const providedSuspenseFallback =\n value.type === 'layout'\n ? (LayoutSuspenseFallback ?? InheritedSuspenseFallback)\n : InheritedSuspenseFallback;\n\n if (isFocused) {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }\n\n useEffect(\n () =>\n navigation.addListener('focus', () => {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n // Because setFocusedState caches the route info, this call will only trigger rerenders\n // if the component itself didn’t rerender and the route info changed.\n // Otherwise, the update from the `if` above will handle it,\n // and this won’t cause a redundant second update.\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }),\n [navigation]\n );\n\n useEffect(() => {\n return navigation.addListener('transitionEnd', (e) => {\n if (!e?.data?.closing) {\n // When navigating to a screen, remove the no animation param to re-enable animations\n // Otherwise the navigation back would also have no animation\n if (hasParam(route?.params, INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME)) {\n navigation.replaceParams(\n removeParams(route?.params, [INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME])\n );\n }\n }\n });\n }, [navigation]);\n\n const isRouteType = value.type === 'route';\n const hasRouteKey = !!route?.key;\n\n return (\n \n \n {unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && (\n \n )}\n \n \n \n }>\n \n \n \n \n \n );\n }\n\n if (__DEV__) {\n BaseRoute.displayName = `Route(${value.route})`;\n }\n\n qualifiedStore.set(value, BaseRoute);\n return BaseRoute;\n}\n\nfunction AnalyticsListeners({\n navigation,\n screenId,\n}: {\n navigation: EventConsumer & {\n isFocused(): boolean;\n };\n screenId: string;\n}) {\n const stateForPath = useStateForPath();\n const isFirstRenderRef = React.useRef(true);\n const hasBlurredRef = React.useRef(true);\n const stringUrl = useMemo(() => generateStringUrlForState(stateForPath), [stateForPath]);\n\n if (isFirstRenderRef.current) {\n isFirstRenderRef.current = false;\n if (stringUrl) {\n unstable_navigationEvents.emit('pageWillRender', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n }\n }\n\n useEffect(() => {\n if (stringUrl) {\n return () => {\n unstable_navigationEvents.emit('pageRemoved', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n };\n }\n return () => {};\n }, [stringUrl, screenId]);\n\n const isFocused = navigation.isFocused();\n\n if (isFocused && stringUrl) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n\n useEffect(() => {\n if (stringUrl) {\n const cleanFocus = navigation.addListener('focus', () => {\n // If the screen was not blurred, don't emit focused again\n // hasBlurredRef will be false when the screen was initially focused\n if (hasBlurredRef.current) {\n unstable_navigationEvents.emit('pageFocused', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = false;\n }\n });\n const cleanBlur = navigation.addListener('blur', () => {\n unstable_navigationEvents.emit('pageBlurred', {\n ...getPathAndParamsFromStringUrl(stringUrl),\n screenId,\n });\n hasBlurredRef.current = true;\n });\n return () => {\n cleanFocus();\n cleanBlur();\n };\n }\n return () => {};\n }, [navigation, stringUrl, screenId]);\n\n return null;\n}\n\nexport function screenOptionsFactory(\n route: RouteNode,\n options?: ScreenProps['options']\n): ScreenProps['options'] {\n return (args) => {\n // Only eager load generated components\n const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null;\n const staticResult = typeof staticOptions === 'function' ? staticOptions(args) : staticOptions;\n const dynamicResult = typeof options === 'function' ? options?.(args) : options;\n const output = {\n ...staticResult,\n ...dynamicResult,\n };\n\n // Prevent generated screens from showing up in the tab bar.\n if (route.internal) {\n output.tabBarItemStyle = { display: 'none' };\n output.tabBarButton = () => null;\n // TODO: React Navigation doesn't provide a way to prevent rendering the drawer item.\n output.drawerItemStyle = { height: 0, display: 'none' };\n }\n\n return output;\n };\n}\n\nexport function routeToScreen(\n route: RouteNode,\n { options, getId, ...props }: Partial = {}\n) {\n return (\n getQualifiedRouteComponent(route)}\n />\n );\n}\n\nexport function getSingularId(name: string, options: Record = {}) {\n return name\n .split('/')\n .map((segment) => {\n if (segment.startsWith('[...')) {\n return options.params?.[segment.slice(4, -1)]?.join('/') || segment;\n } else if (segment.startsWith('[') && segment.endsWith(']')) {\n return options.params?.[segment.slice(1, -1)] || segment;\n } else {\n return segment;\n }\n })\n .join('/');\n}\n"]} \ No newline at end of file +{"version":3,"file":"useScreens.js","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport React, { use, useEffect } from 'react';\n\nimport type { LoadedRoute, RouteNode } from './Route';\nimport { SuspenseFallbackContext, Route, sortRoutesWithInitial, useRouteNode } from './Route';\nimport { useExpoRouterStore } from './global-state/storeContext';\nimport { useColorSchemeChangesIfNeeded } from './global-state/utils';\nimport { useCurrentRouteInfo } from './hooks';\nimport EXPO_ROUTER_IMPORT_MODE from './import-mode';\nimport { ZoomTransitionEnabler } from './link/zoom/ZoomTransitionEnabler';\nimport { ZoomTransitionTargetContextProvider } from './link/zoom/zoom-transition-context-providers';\nimport { unstable_navigationEvents } from './navigationEvents';\nimport {\n hasParam,\n INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME,\n removeParams,\n} from './navigationParams';\nimport { Screen } from './primitives';\nimport type { BottomTabNavigationEventMap } from './react-navigation/bottom-tabs';\nimport {\n useStateForPath,\n type EventConsumer,\n type EventMapBase,\n type NavigationProp,\n type NavigationState,\n type ParamListBase,\n type RouteProp,\n type ScreenListeners,\n} from './react-navigation/native';\nimport type { NativeStackNavigationEventMap } from './react-navigation/native-stack';\nimport type { UnknownOutputParams } from './types';\nimport { EmptyRoute } from './views/EmptyRoute';\nimport {\n SuspenseFallback as DefaultSuspenseFallback,\n type SuspenseFallbackProps,\n} from './views/SuspenseFallback';\nimport { Try } from './views/Try';\n\nexport type ScreenProps<\n TOptions extends Record = Record,\n TState extends NavigationState = NavigationState,\n TEventMap extends EventMapBase = EventMapBase,\n> = {\n /** Name is required when used inside a Layout component. */\n name?: string;\n /**\n * Redirect to the nearest sibling route.\n * If all children are `redirect={true}`, the layout will render `null` as there are no children to render.\n */\n redirect?: boolean;\n initialParams?: Record;\n options?:\n | TOptions\n | ((prop: { route: RouteProp; navigation: any }) => TOptions);\n\n listeners?:\n | ScreenListeners\n | ((prop: {\n route: RouteProp;\n navigation: any;\n }) => ScreenListeners);\n\n getId?: ({ params }: { params?: Record }) => string | undefined;\n\n dangerouslySingular?: SingularOptions;\n};\n\nexport type SingularOptions =\n | boolean\n | ((name: string, params: UnknownOutputParams) => string | undefined);\n\nfunction getSortedChildren(\n children: RouteNode[],\n order: ScreenProps[] = [],\n initialRouteName?: string\n): { route: RouteNode; props: Partial }[] {\n if (!order?.length) {\n return children\n .sort(sortRoutesWithInitial(initialRouteName))\n .map((route) => ({ route, props: {} }));\n }\n const entries = [...children];\n\n const ordered = order\n .map(\n ({\n name,\n redirect,\n initialParams,\n listeners,\n options,\n getId,\n dangerouslySingular: singular,\n }) => {\n if (!entries.length) {\n console.warn(\n `[Layout children]: Too many screens defined. Route \"${name}\" is extraneous.`\n );\n return null;\n }\n const matchIndex = entries.findIndex(\n (child) => child.route === name || child.route === `${name}/index`\n );\n if (matchIndex === -1) {\n console.warn(\n `[Layout children]: No route named \"${name}\" exists in nested children:`,\n children.map(({ route }) => route)\n );\n return null;\n } else {\n // Get match and remove from entries\n const match = entries[matchIndex];\n entries.splice(matchIndex, 1);\n\n // Ensure to return null after removing from entries.\n if (redirect) {\n if (typeof redirect === 'string') {\n throw new Error(`Redirecting to a specific route is not supported yet.`);\n }\n return null;\n }\n\n if (getId) {\n console.warn(\n `Deprecated: prop 'getId' on screen ${name} is deprecated. Please rename the prop to 'dangerouslySingular'`\n );\n if (singular) {\n console.warn(\n `Screen ${name} cannot use both getId and dangerouslySingular together.`\n );\n }\n } else if (singular) {\n // If singular is set, use it as the getId function.\n if (typeof singular === 'string') {\n getId = () => singular;\n } else if (typeof singular === 'function' && name) {\n getId = (options) => singular(name, options.params || {});\n } else if (singular === true && name) {\n getId = (options) => getSingularId(name, options);\n }\n }\n\n return {\n route: match,\n props: { initialParams, listeners, options, getId },\n };\n }\n }\n )\n .filter(Boolean) as {\n route: RouteNode;\n props: Partial;\n }[];\n\n // Add any remaining children\n ordered.push(\n ...entries.sort(sortRoutesWithInitial(initialRouteName)).map((route) => ({ route, props: {} }))\n );\n\n return ordered;\n}\n\n/**\n * @returns React Navigation screens sorted by the `route` property.\n */\nexport function useSortedScreens(\n order: ScreenProps[],\n protectedScreens: Set,\n useOnlyUserDefinedScreens: boolean = false\n): React.ReactNode[] {\n const node = useRouteNode();\n\n const nodeChildren = node?.children ?? [];\n const children = useOnlyUserDefinedScreens\n ? nodeChildren.filter((child) =>\n order.some(\n (userDefinedScreen) =>\n userDefinedScreen.name === child.route ||\n `${userDefinedScreen.name}/index` === child.route\n )\n )\n : nodeChildren;\n\n const sorted = children.length ? getSortedChildren(children, order, node?.initialRouteName) : [];\n return React.useMemo(\n () =>\n sorted\n .filter((item) => {\n const route = item.route.route;\n return (\n !protectedScreens.has(route) && !protectedScreens.has(route.replace(/\\/index$/, ''))\n );\n })\n .map((value) => {\n return routeToScreen(value.route, value.props);\n }),\n [sorted, protectedScreens]\n );\n}\n\nfunction fromImport(\n value: RouteNode,\n { ErrorBoundary, SuspenseFallback, ...component }: LoadedRoute\n) {\n // If possible, add a more helpful display name for the component stack to improve debugging of React errors such as `Text strings must be rendered within a component.`.\n if (component?.default && __DEV__) {\n component.default.displayName ??= `${component.default.name ?? 'Route'}(${value.contextKey})`;\n }\n\n if (ErrorBoundary) {\n const Wrapped = React.forwardRef((props: any, ref: any) => {\n const children = React.createElement(component.default || EmptyRoute, {\n ...props,\n ref,\n });\n return {children};\n });\n\n if (__DEV__) {\n Wrapped.displayName = `ErrorBoundary(${value.contextKey})`;\n }\n\n return {\n default: Wrapped,\n SuspenseFallback,\n };\n }\n if (process.env.NODE_ENV !== 'production') {\n if (\n typeof component.default === 'object' &&\n component.default &&\n Object.keys(component.default).length === 0\n ) {\n return { default: EmptyRoute, SuspenseFallback };\n }\n }\n\n return { default: component.default, SuspenseFallback };\n}\n\nfunction fromLoadedRoute(value: RouteNode, res: LoadedRoute) {\n if (!(res instanceof Promise)) {\n return fromImport(value, res);\n }\n\n return res.then(fromImport.bind(null, value));\n}\n\n// TODO: Maybe there's a more React-y way to do this?\n// Without this store, the process enters a recursive loop.\nconst qualifiedStore = new WeakMap>();\n\n/** Wrap the component with various enhancements and add access to child routes. */\nexport function getQualifiedRouteComponent(value: RouteNode) {\n if (qualifiedStore.has(value)) {\n return qualifiedStore.get(value)!;\n }\n\n let ScreenComponent:\n | React.ForwardRefExoticComponent>\n | React.ComponentType<{ segment?: string }>;\n\n let LayoutSuspenseFallback: React.ComponentType | undefined;\n\n // TODO: This ensures sync doesn't use React.lazy, but it's not ideal.\n if (EXPO_ROUTER_IMPORT_MODE === 'lazy') {\n ScreenComponent = React.lazy(async () => {\n const res = value.loadRoute();\n return fromLoadedRoute(value, res) as Promise<{\n default: React.ComponentType;\n }>;\n });\n\n if (__DEV__) {\n ScreenComponent.displayName = `AsyncRoute(${value.route})`;\n }\n } else {\n const res = value.loadRoute();\n const result = fromImport(value, res);\n ScreenComponent = result.default!;\n LayoutSuspenseFallback = value.type === 'layout' ? result.SuspenseFallback : undefined;\n }\n const WrappedScreenComponent: typeof ScreenComponent = (props: object) => {\n useColorSchemeChangesIfNeeded();\n return ;\n };\n function BaseRoute({\n // Remove these React Navigation props to\n // enforce usage of expo-router hooks (where the query params are correct).\n route,\n navigation,\n\n // Pass all other props to the component\n ...props\n }: {\n route?: RouteProp;\n navigation: Omit<\n NavigationProp<\n ParamListBase,\n string,\n undefined,\n NavigationState,\n object,\n NativeStackNavigationEventMap | BottomTabNavigationEventMap\n >,\n 'getState'\n > & {\n getState(): NavigationState | undefined;\n };\n }) {\n const stateForPath = useStateForPath();\n const isFocused = navigation.isFocused();\n const store = useExpoRouterStore();\n const InheritedSuspenseFallback = use(SuspenseFallbackContext);\n\n const ResolvedSuspenseFallback =\n EXPO_ROUTER_IMPORT_MODE === 'lazy'\n ? DefaultSuspenseFallback\n : (LayoutSuspenseFallback ?? InheritedSuspenseFallback ?? DefaultSuspenseFallback);\n const providedSuspenseFallback =\n value.type === 'layout'\n ? (LayoutSuspenseFallback ?? InheritedSuspenseFallback)\n : InheritedSuspenseFallback;\n\n if (isFocused) {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }\n\n useEffect(\n () =>\n navigation.addListener('focus', () => {\n const state = navigation.getState();\n const isLeaf = !(state && 'state' in state.routes[state.index]!);\n // Because setFocusedState caches the route info, this call will only trigger rerenders\n // if the component itself didn’t rerender and the route info changed.\n // Otherwise, the update from the `if` above will handle it,\n // and this won’t cause a redundant second update.\n if (isLeaf && stateForPath) store.setFocusedState(stateForPath);\n }),\n [navigation]\n );\n\n useEffect(() => {\n return navigation.addListener('transitionEnd', (e) => {\n if (!e?.data?.closing) {\n // When navigating to a screen, remove the no animation param to re-enable animations\n // Otherwise the navigation back would also have no animation\n if (hasParam(route?.params, INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME)) {\n navigation.replaceParams(\n removeParams(route?.params, [INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME])\n );\n }\n }\n });\n }, [navigation]);\n\n const isRouteType = value.type === 'route';\n const hasRouteKey = !!route?.key;\n\n return (\n \n \n {unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && (\n \n )}\n \n \n \n }>\n \n \n \n \n \n );\n }\n\n if (__DEV__) {\n BaseRoute.displayName = `Route(${value.route})`;\n }\n\n qualifiedStore.set(value, BaseRoute);\n return BaseRoute;\n}\n\nfunction AnalyticsListeners({\n navigation,\n screenId,\n}: {\n navigation: EventConsumer & {\n isFocused(): boolean;\n };\n screenId: string;\n}) {\n const isFirstRenderRef = React.useRef(true);\n const hasBlurredRef = React.useRef(true);\n const routeInfo = useCurrentRouteInfo();\n\n if (isFirstRenderRef.current) {\n isFirstRenderRef.current = false;\n if (routeInfo) {\n unstable_navigationEvents.emit('pageWillRender', {\n pathname: routeInfo.pathname,\n params: routeInfo.params,\n screenId,\n });\n }\n }\n\n useEffect(() => {\n if (routeInfo) {\n return () => {\n unstable_navigationEvents.emit('pageRemoved', {\n pathname: routeInfo.pathname,\n params: routeInfo.params,\n screenId,\n });\n };\n }\n return () => {};\n }, [routeInfo?.params, routeInfo?.pathname, screenId]);\n\n const isFocused = navigation.isFocused();\n\n if (isFocused && routeInfo) {\n unstable_navigationEvents.emit('pageFocused', {\n pathname: routeInfo.pathname,\n params: routeInfo.params,\n screenId,\n });\n hasBlurredRef.current = false;\n }\n\n useEffect(() => {\n if (routeInfo) {\n const cleanFocus = navigation.addListener('focus', () => {\n // If the screen was not blurred, don't emit focused again\n // hasBlurredRef will be false when the screen was initially focused\n if (hasBlurredRef.current) {\n unstable_navigationEvents.emit('pageFocused', {\n pathname: routeInfo.pathname,\n params: routeInfo.params,\n screenId,\n });\n hasBlurredRef.current = false;\n }\n });\n const cleanBlur = navigation.addListener('blur', () => {\n unstable_navigationEvents.emit('pageBlurred', {\n pathname: routeInfo.pathname,\n params: routeInfo.params,\n screenId,\n });\n hasBlurredRef.current = true;\n });\n return () => {\n cleanFocus();\n cleanBlur();\n };\n }\n return () => {};\n }, [navigation, routeInfo?.pathname, routeInfo?.params, screenId]);\n\n return null;\n}\n\nexport function screenOptionsFactory(\n route: RouteNode,\n options?: ScreenProps['options']\n): ScreenProps['options'] {\n return (args) => {\n // Only eager load generated components\n const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null;\n const staticResult = typeof staticOptions === 'function' ? staticOptions(args) : staticOptions;\n const dynamicResult = typeof options === 'function' ? options?.(args) : options;\n const output = {\n ...staticResult,\n ...dynamicResult,\n };\n\n // Prevent generated screens from showing up in the tab bar.\n if (route.internal) {\n output.tabBarItemStyle = { display: 'none' };\n output.tabBarButton = () => null;\n // TODO: React Navigation doesn't provide a way to prevent rendering the drawer item.\n output.drawerItemStyle = { height: 0, display: 'none' };\n }\n\n return output;\n };\n}\n\nexport function routeToScreen(\n route: RouteNode,\n { options, getId, ...props }: Partial = {}\n) {\n return (\n getQualifiedRouteComponent(route)}\n />\n );\n}\n\nexport function getSingularId(name: string, options: Record = {}) {\n return name\n .split('/')\n .map((segment) => {\n if (segment.startsWith('[...')) {\n return options.params?.[segment.slice(4, -1)]?.join('/') || segment;\n } else if (segment.startsWith('[') && segment.endsWith(']')) {\n return options.params?.[segment.slice(1, -1)] || segment;\n } else {\n return segment;\n }\n })\n .join('/');\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/src/exports.ts b/packages/expo-router/src/exports.ts index a2be7815493b8c..f2f399f0ad71d6 100644 --- a/packages/expo-router/src/exports.ts +++ b/packages/expo-router/src/exports.ts @@ -67,6 +67,14 @@ export { } from './primitives'; export { unstable_navigationEvents } from './navigationEvents'; +export type { + PageWillRender, + PageFocusedEvent, + PageBlurredEvent, + PageRemoved, + ActionDispatchedEvent, + AnalyticsEvent, +} from './navigationEvents'; /** * @deprecated Use `import { Tabs } from 'expo-router/js-tabs'` instead. diff --git a/packages/expo-router/src/global-state/store.ts b/packages/expo-router/src/global-state/store.ts index 4d9483c52cbd45..1c270a9dfc36cc 100644 --- a/packages/expo-router/src/global-state/store.ts +++ b/packages/expo-router/src/global-state/store.ts @@ -11,6 +11,7 @@ import type { import type { RouteNode } from '../Route'; import type { ExpoLinkingOptions } from '../getLinkingConfig'; import { resolveHref, resolveHrefStringWithSegments } from '../link/href'; +import { handleNavigationOnReady } from '../navigationEvents/navigation'; import type { NavigationContainerRefWithCurrent } from '../react-navigation/native'; import type { RequireContext, Href } from '../types'; import * as SplashScreen from '../views/Splash'; @@ -84,6 +85,7 @@ export const store = { storeRef.current.routeInfo = routeInfo; }, onReady() { + handleNavigationOnReady(); if (!hasAttemptedToHideSplash) { setHasAttemptedToHideSplash(true); // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially. diff --git a/packages/expo-router/src/navigationEvents/index.ts b/packages/expo-router/src/navigationEvents/index.ts index 673626732e2e6b..528ed4ce9e1df7 100644 --- a/packages/expo-router/src/navigationEvents/index.ts +++ b/packages/expo-router/src/navigationEvents/index.ts @@ -1,14 +1,32 @@ -import type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types'; +import type { + PageWillRender, + PageFocusedEvent, + PageBlurredEvent, + PageRemoved, + ActionDispatchedEvent, +} from './types'; -export type { PageWillRender, PageFocusedEvent, PageBlurredEvent, PageRemoved } from './types'; +export type { + PageWillRender, + PageFocusedEvent, + PageBlurredEvent, + PageRemoved, + ActionDispatchedEvent, +} from './types'; -export type AnalyticsEvent = PageWillRender | PageFocusedEvent | PageBlurredEvent | PageRemoved; +export type AnalyticsEvent = + | PageWillRender + | PageFocusedEvent + | PageBlurredEvent + | PageRemoved + | ActionDispatchedEvent; const availableEvents: AnalyticsEvent['type'][] = [ 'pageWillRender', 'pageFocused', 'pageBlurred', 'pageRemoved', + 'actionDispatched', ]; type EventTypeName = AnalyticsEvent['type']; @@ -48,10 +66,6 @@ export function emit(type: EventType, event: Pa let enabled = false; -let currentPathname: string | undefined = undefined; -let currentParams: Record | undefined = undefined; -let currentPathnameListener: ReturnType | undefined = undefined; - export const unstable_navigationEvents = { addListener, emit, @@ -61,44 +75,4 @@ export const unstable_navigationEvents = { isEnabled: () => { return enabled; }, - saveCurrentPathname: () => { - if (!enabled || currentPathnameListener) return; - currentPathnameListener = addListener('pageFocused', (event) => { - currentPathname = event.pathname; - currentParams = event.params; - }); - }, - get currentPathname() { - return currentPathname; - }, - get currentParams() { - return currentParams; - }, }; - -if (globalThis.expo) { - globalThis.expo.router = globalThis.expo.router || {}; - - if (!('navigationEvents' in globalThis.expo.router)) { - Object.defineProperties(globalThis.expo.router, { - navigationEvents: { - get() { - return unstable_navigationEvents; - }, - enumerable: true, - }, - currentPathname: { - get() { - return currentPathname; - }, - enumerable: true, - }, - currentParams: { - get() { - return currentParams; - }, - enumerable: true, - }, - }); - } -} diff --git a/packages/expo-router/src/navigationEvents/navigation.ts b/packages/expo-router/src/navigationEvents/navigation.ts new file mode 100644 index 00000000000000..cefd9e681cfedf --- /dev/null +++ b/packages/expo-router/src/navigationEvents/navigation.ts @@ -0,0 +1,18 @@ +import { emit } from '.'; +import { storeRef } from '../global-state/store'; + +let unsubscribe: (() => void) | undefined; + +export function handleNavigationOnReady() { + if (unsubscribe) unsubscribe(); + unsubscribe = storeRef.current.navigationRef.addListener('__unsafe_action__', (e) => { + if (!e.data.noop && storeRef.current.state) { + const action = e.data.action; + emit('actionDispatched', { + actionType: action.type, + payload: action.payload, + state: storeRef.current.state, + }); + } + }); +} diff --git a/packages/expo-router/src/navigationEvents/types.ts b/packages/expo-router/src/navigationEvents/types.ts index 227f7517b9ec14..838016d74507a2 100644 --- a/packages/expo-router/src/navigationEvents/types.ts +++ b/packages/expo-router/src/navigationEvents/types.ts @@ -1,6 +1,9 @@ +import type { ReactNavigationState } from '../global-state/types'; +import type { NavigationAction } from '../react-navigation'; + export interface BasePageEvent { pathname: string; - params: Record; + params: Record; screenId: string; } @@ -24,3 +27,11 @@ export interface PageBlurredEvent extends BasePageEvent { export interface PageRemoved extends BasePageEvent { type: 'pageRemoved'; } + +export interface ActionDispatchedEvent { + type: 'actionDispatched'; + /** The action type from the dispatched NavigationAction (e.g. `NAVIGATE`). */ + actionType: NavigationAction['type']; + payload: NavigationAction['payload']; + state: ReactNavigationState; +} diff --git a/packages/expo-router/src/useScreens.tsx b/packages/expo-router/src/useScreens.tsx index 54b17432749448..a57c2574b14a55 100644 --- a/packages/expo-router/src/useScreens.tsx +++ b/packages/expo-router/src/useScreens.tsx @@ -1,16 +1,16 @@ 'use client'; -import React, { use, useEffect, useMemo } from 'react'; +import React, { use, useEffect } from 'react'; import type { LoadedRoute, RouteNode } from './Route'; import { SuspenseFallbackContext, Route, sortRoutesWithInitial, useRouteNode } from './Route'; import { useExpoRouterStore } from './global-state/storeContext'; import { useColorSchemeChangesIfNeeded } from './global-state/utils'; +import { useCurrentRouteInfo } from './hooks'; import EXPO_ROUTER_IMPORT_MODE from './import-mode'; import { ZoomTransitionEnabler } from './link/zoom/ZoomTransitionEnabler'; import { ZoomTransitionTargetContextProvider } from './link/zoom/zoom-transition-context-providers'; import { unstable_navigationEvents } from './navigationEvents'; -import { generateStringUrlForState, getPathAndParamsFromStringUrl } from './navigationEvents/utils'; import { hasParam, INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME, @@ -406,51 +406,54 @@ function AnalyticsListeners({ }; screenId: string; }) { - const stateForPath = useStateForPath(); const isFirstRenderRef = React.useRef(true); const hasBlurredRef = React.useRef(true); - const stringUrl = useMemo(() => generateStringUrlForState(stateForPath), [stateForPath]); + const routeInfo = useCurrentRouteInfo(); if (isFirstRenderRef.current) { isFirstRenderRef.current = false; - if (stringUrl) { + if (routeInfo) { unstable_navigationEvents.emit('pageWillRender', { - ...getPathAndParamsFromStringUrl(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); } } useEffect(() => { - if (stringUrl) { + if (routeInfo) { return () => { unstable_navigationEvents.emit('pageRemoved', { - ...getPathAndParamsFromStringUrl(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); }; } return () => {}; - }, [stringUrl, screenId]); + }, [routeInfo?.params, routeInfo?.pathname, screenId]); const isFocused = navigation.isFocused(); - if (isFocused && stringUrl) { + if (isFocused && routeInfo) { unstable_navigationEvents.emit('pageFocused', { - ...getPathAndParamsFromStringUrl(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = false; } useEffect(() => { - if (stringUrl) { + if (routeInfo) { const cleanFocus = navigation.addListener('focus', () => { // If the screen was not blurred, don't emit focused again // hasBlurredRef will be false when the screen was initially focused if (hasBlurredRef.current) { unstable_navigationEvents.emit('pageFocused', { - ...getPathAndParamsFromStringUrl(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = false; @@ -458,7 +461,8 @@ function AnalyticsListeners({ }); const cleanBlur = navigation.addListener('blur', () => { unstable_navigationEvents.emit('pageBlurred', { - ...getPathAndParamsFromStringUrl(stringUrl), + pathname: routeInfo.pathname, + params: routeInfo.params, screenId, }); hasBlurredRef.current = true; @@ -469,7 +473,7 @@ function AnalyticsListeners({ }; } return () => {}; - }, [navigation, stringUrl, screenId]); + }, [navigation, routeInfo?.pathname, routeInfo?.params, screenId]); return null; } From 4416d9066d534063a2110fb9c631972b6590fc26 Mon Sep 17 00:00:00 2001 From: Christian Falch <875252+chrfalch@users.noreply.github.com> Date: Tue, 12 May 2026 14:30:49 +0200 Subject: [PATCH 05/11] fix(precompile) add fallback to source for missing framework slice (#45664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why We were seeing tvOS builds try to link an iOS-only precompiled Expo XCFramework. With `EXPO_USE_PRECOMPILED_MODULES=1`, CocoaPods treated the prebuilt XApplication.tar.gz as available just because the tarball existed. It did not check whether EXApplication.xcframework/Info.plist contained a tvos slice. So for a tvOS target, CocoaPods vendored the iOS-only EXApplication.xcframework, and the Xcode build later failed at link time with errors like: ``` ld: framework 'EXApplication' not found ``` The fix makes prebuilt resolution platform-aware, so tvOS falls back to source when the prebuilt XCFramework only contains iOS slices. # How Instead of using a prebuilt XCFramework just because its tarball exists, precompiled_modules.rb now reads the XCFramework Info.plist and checks AvailableLibraries[*].SupportedPlatform against the current CocoaPods target platform. osx is normalized to macos. AutolinkingManager passes the target platform into PrecompiledModules, so tvOS/macOS/etc. fall back to source when a prebuilt only contains iOS slices. We also cached the slice checks to avoid repeatedly unpacking tarballs. # Test Plan ✅ Ran temp tvOS project with `EXPO_USE_PRECOMPILED_MODULES=1` and verified the error. Applied the fix and reran. ✅ Verified BareExpo ✅ Verified Minimal Tester # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). --- .../expo-modules-autolinking/CHANGELOG.md | 2 + .../scripts/ios/autolinking_manager.rb | 12 ++- .../scripts/ios/precompiled_modules.rb | 94 ++++++++++++++++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md index 56500cde83dbe6..d78cdbbd0301c9 100644 --- a/packages/expo-modules-autolinking/CHANGELOG.md +++ b/packages/expo-modules-autolinking/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +[iOS] Added fallback to source for missing framework slice. ([#45664](https://github.com/expo/expo/pull/45664) by [@chrfalch](https://github.com/chrfalch)) + ### 💡 Others ## 56.0.4 — 2026-05-11 diff --git a/packages/expo-modules-autolinking/scripts/ios/autolinking_manager.rb b/packages/expo-modules-autolinking/scripts/ios/autolinking_manager.rb index 20a79ddbee0814..716e45f0c7bb9b 100644 --- a/packages/expo-modules-autolinking/scripts/ios/autolinking_manager.rb +++ b/packages/expo-modules-autolinking/scripts/ios/autolinking_manager.rb @@ -22,16 +22,18 @@ class AutolinkingManager validate_target_definition() - # Clear stale CocoaPods download cache for precompiled pods. - Expo::PrecompiledModules.clear_cocoapods_cache - resolve_result = resolve() Expo::PackagesConfig.instance.coreFeatures = resolve_result['coreFeatures'] - # Pass buildFromSource configuration to PrecompiledModules configuration = resolve_result['configuration'] || {} - Expo::PrecompiledModules.build_from_source = configuration['buildFromSource'] || [] + Expo::PrecompiledModules.configure( + target_platform: @target_definition.platform, + build_from_source: configuration['buildFromSource'] || [] + ) + + # Clear stale CocoaPods download cache for precompiled pods. + Expo::PrecompiledModules.clear_cocoapods_cache @packages = resolve_result['modules'].map { |json_package| Package.new(json_package) } @extraPods = resolve_result['extraDependencies'] diff --git a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb index 16ebc15f5a8ec3..2dc8e68f67bccd 100644 --- a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb +++ b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb @@ -29,7 +29,9 @@ require 'fileutils' require 'json' require 'net/http' +require 'open3' require 'set' +require 'tempfile' require 'uri' module Expo @@ -76,6 +78,8 @@ module PrecompiledModules @framework_owner_map = nil # Hash: framework_name -> owning_pod_name @failed_remote_downloads = Set.new @warned_no_prebuilt_react = false + @target_platform = nil + @xcframework_slice_cache = nil class << self # Returns the build flavor (debug/release) for precompiled modules. @@ -113,13 +117,26 @@ def enabled? false end - # Sets the list of package name patterns that should be built from source - # instead of using precompiled xcframeworks. Patterns are treated as regexes - # (e.g., ".*" for all, "expo-audio" for exact match, "expo-.*" for prefix). + def configure(target_platform: nil, build_from_source: nil) + self.target_platform = target_platform unless target_platform.nil? + self.build_from_source = build_from_source unless build_from_source.nil? + end + def build_from_source=(patterns) @build_from_source_patterns = (patterns || []).map { |p| Regexp.new("^#{p}$") } end + def target_platform=(platform) + normalized = normalize_xcframework_platform(platform) + return if @target_platform == normalized + + @target_platform = normalized + @all_bundled_frameworks = nil + @claimed_vendored_frameworks = nil + @framework_owner_map = nil + @xcframework_slice_cache = nil + end + # Checks if a pod is configured to be built from source via buildFromSource. # Matches against both the pod name and the npm package name. def build_from_source?(pod_name) @@ -1785,6 +1802,7 @@ def resolve_own_prebuilt_info(pod_name) product_name = pod_info[:product_name] || pod_name tarball = resolve_prebuilt_tarball(pod_info, product_name, build_flavor, pod_name) return { available: false, reason: :missing_tarball, path: tarball } unless File.exist?(tarball) + return { available: false, reason: :missing_platform_slice, path: tarball } unless xcframework_supports_target_platform?(tarball) { available: true, resolved: [pod_info, product_name, tarball] } end @@ -1842,6 +1860,74 @@ def resolve_prebuilt_tarball(pod_info, product_name, flavor, pod_name = nil) remote_tarball end + def normalize_xcframework_platform(platform) + name = platform.respond_to?(:name) ? platform.name : platform + name = name.string_name if name.respond_to?(:string_name) + normalized = name.to_s.downcase + normalized == 'osx' ? 'macos' : normalized + end + + def xcframework_supports_target_platform?(path) + return true unless @target_platform + + @xcframework_slice_cache ||= {} + cache_key = [path, @target_platform] + return @xcframework_slice_cache[cache_key] if @xcframework_slice_cache.key?(cache_key) + + @xcframework_slice_cache[cache_key] = begin + info_plists = File.directory?(path) ? [read_plist(File.join(path, 'Info.plist'))] : read_xcframework_info_plists_from_tarball(path) + info_plists.any? && info_plists.all? { |info_plist| info_plist_supports_target_platform?(info_plist) } + end + end + + def info_plist_supports_target_platform?(info_plist) + return false unless info_plist + + available_libraries = info_plist['AvailableLibraries'] + return false unless available_libraries.is_a?(Array) + + available_libraries.any? do |library| + normalize_xcframework_platform(library['SupportedPlatform']) == @target_platform + end + end + + def read_xcframework_info_plists_from_tarball(tarball) + entries_output, status = Open3.capture2e('tar', 'tzf', tarball) + unless status.success? + Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{entries_output.strip}" + return [] + end + + entries = entries_output.lines.map(&:strip) + plist_entries = entries.select { |entry| entry.end_with?('.xcframework/Info.plist') } + Pod::UI.warn "[Expo-precompiled] No XCFramework Info.plist found in #{File.basename(tarball)}" if plist_entries.empty? + + plist_entries.filter_map do |entry| + plist_data, plist_status = Open3.capture2e('tar', 'xOzf', tarball, entry) + unless plist_status.success? + Pod::UI.warn "[Expo-precompiled] Failed to extract #{entry} from #{File.basename(tarball)}: #{plist_data.strip}" + next + end + + Tempfile.create(['expo-xcframework-info', '.plist']) do |file| + file.binmode + file.write(plist_data) + file.flush + read_plist(file.path) + end + end + rescue StandardError => e + Pod::UI.warn "[Expo-precompiled] Failed to inspect #{File.basename(tarball)}: #{e.message}" + [] + end + + def read_plist(path) + Xcodeproj::Plist.read_from_path(path) + rescue StandardError => e + Pod::UI.warn "[Expo-precompiled] Failed to read #{File.basename(path)}: #{e.message}" + nil + end + def failed_remote_downloads @failed_remote_downloads ||= Set.new end @@ -2120,6 +2206,8 @@ def format_prebuilt_unavailable_reason(info) 'prebuilt config not found' when :missing_tarball 'prebuilt tarball not found' + when :missing_platform_slice + "prebuilt xcframework does not contain a slice for #{@target_platform}" when :dependency_unavailable reason = format_prebuilt_unavailable_reason(reason: info[:dependency_reason], path: info[:dependency_path]) "dependency #{info[:dependency]} is not using prebuilt: #{reason}" From 1a89c487ab1a7e677120f008d9d94ebe2539d9c9 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 12 May 2026 14:43:28 +0200 Subject: [PATCH 06/11] [docs] fix expo-ui drop-in replacement listing (#45672) # Why The Expo UI overview page listed only `DateTimePicker` under "Drop-in replacements", while the dedicated `drop-in-replacements` page listed the full set. Two lists drifted out of sync. # How # Test Plan # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- docs/pages/versions/unversioned/sdk/ui/index.mdx | 4 +--- docs/pages/versions/v55.0.0/sdk/ui/index.mdx | 4 +--- docs/pages/versions/v56.0.0/sdk/ui/index.mdx | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/pages/versions/unversioned/sdk/ui/index.mdx b/docs/pages/versions/unversioned/sdk/ui/index.mdx index 54e1277061ae11..ff7724b0e7a6ef 100644 --- a/docs/pages/versions/unversioned/sdk/ui/index.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/index.mdx @@ -20,6 +20,4 @@ Components are available for the following platforms: ## Drop-in replacements -API-compatible replacements for popular React Native community libraries: - -- **[DateTimePicker](drop-in-replacements/datetimepicker)**: Compatible with `@react-native-community/datetimepicker` +See **[Drop-in replacements](drop-in-replacements)** for API-compatible replacements for popular React Native community libraries. diff --git a/docs/pages/versions/v55.0.0/sdk/ui/index.mdx b/docs/pages/versions/v55.0.0/sdk/ui/index.mdx index 6fabc06332113b..017e3b5948f0eb 100644 --- a/docs/pages/versions/v55.0.0/sdk/ui/index.mdx +++ b/docs/pages/versions/v55.0.0/sdk/ui/index.mdx @@ -19,6 +19,4 @@ Components are available for the following platforms: ## Drop-in replacements -API-compatible replacements for popular React Native community libraries: - -- **[DateTimePicker](drop-in-replacements/datetimepicker)**: Compatible with `@react-native-community/datetimepicker` +See **[Drop-in replacements](drop-in-replacements)** for API-compatible replacements for popular React Native community libraries. diff --git a/docs/pages/versions/v56.0.0/sdk/ui/index.mdx b/docs/pages/versions/v56.0.0/sdk/ui/index.mdx index 8b2b1c99f49b47..0ae8e85ef86c83 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/index.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/index.mdx @@ -20,6 +20,4 @@ Components are available for the following platforms: ## Drop-in replacements -API-compatible replacements for popular React Native community libraries: - -- **[DateTimePicker](drop-in-replacements/datetimepicker)**: Compatible with `@react-native-community/datetimepicker` +See **[Drop-in replacements](drop-in-replacements)** for API-compatible replacements for popular React Native community libraries. From 8ff528f5aba117c0f1a11dc01cda5416d0a5342d Mon Sep 17 00:00:00 2001 From: HubertBer <115428831+HubertBer@users.noreply.github.com> Date: Tue, 12 May 2026 14:48:37 +0200 Subject: [PATCH 07/11] [expo-type-information] add readme, update cli description (#45663) --- packages/expo-type-information/README.md | 32 ++++++++++++++++++- packages/expo-type-information/build/cli.js | 2 +- .../expo-type-information/build/cli.js.map | 2 +- packages/expo-type-information/src/cli.ts | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/expo-type-information/README.md b/packages/expo-type-information/README.md index a3f559f0664d1e..4965bfe7e899eb 100644 --- a/packages/expo-type-information/README.md +++ b/packages/expo-type-information/README.md @@ -1 +1,31 @@ -# expo-type-information +# expo-type-information + +> [!WARNING] +> This package only works on macOS. + +This package provides a CLI tool and exports functions which can: + +- Parse Swift expo modules and retrieve type information from them +- Emit typescript code (types, wrapper functions, mocks) + +## Installation + +First, add the package to your npm dependencies: + +``` +npm install expo-type-information +``` + +To use this package, you need to have `sourcekitten` installed. You can install it using Homebrew: + +``` +brew install sourcekitten +``` + +## Usage + +With `sourcekitten` installed you can run the CLI tool and see the available commands: + +``` +npx expo-type-information +``` diff --git a/packages/expo-type-information/build/cli.js b/packages/expo-type-information/build/cli.js index 7d9042d8a31d1e..477874a481c0de 100644 --- a/packages/expo-type-information/build/cli.js +++ b/packages/expo-type-information/build/cli.js @@ -19,7 +19,7 @@ async function main(args) { cli .name('expo-type-information') .version(require('../package.json').version) - .description('CLI commands for retrieving type information from native files.'); + .description('Retrieve type information from Swift Expo modules to generate TypeScript.'); (0, moduleInterfaceCommand_1.moduleInterfaceCommand)(cli); (0, inlineModulesInterfaceCommand_1.inlineModulesInterfaceCommand)(cli); (0, shortModuleInterfaceCommand_1.shortModuleInterfaceCommand)(cli); diff --git a/packages/expo-type-information/build/cli.js.map b/packages/expo-type-information/build/cli.js.map index e17661d964aa89..6ef44c11c2eb74 100644 --- a/packages/expo-type-information/build/cli.js.map +++ b/packages/expo-type-information/build/cli.js.map @@ -1 +1 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;AAAA,yCAAoC;AAEpC,0DAAkE;AAClE,0FAAgF;AAChF,wFAAqF;AACrF,sFAAmF;AACnF,kFAA+E;AAC/E,4FAAyF;AACzF,8EAA2E;AAC3E,wFAAqF;AACrF,8EAA2E;AAE3E,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,IAAI,CAAC,IAAA,sCAAuB,GAAE,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;QACvF,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,mBAAO,EAAE,CAAC;IAC1B,GAAG;SACA,IAAI,CAAC,uBAAuB,CAAC;SAC7B,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC;SAC3C,WAAW,CAAC,iEAAiE,CAAC,CAAC;IAElF,IAAA,+CAAsB,EAAC,GAAG,CAAC,CAAC;IAC5B,IAAA,6DAA6B,EAAC,GAAG,CAAC,CAAC;IACnC,IAAA,yDAA2B,EAAC,GAAG,CAAC,CAAC;IACjC,IAAA,yDAA2B,EAAC,GAAG,CAAC,CAAC;IAEjC,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,oCAAoC,CAAC,CAAC;IAC7F,IAAA,+CAAsB,EAAC,aAAa,CAAC,CAAC;IACtC,IAAA,uDAA0B,EAAC,aAAa,CAAC,CAAC;IAC1C,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;IACxC,IAAA,oDAAqB,EAAC,aAAa,CAAC,CAAC;IAErC,MAAM,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"} \ No newline at end of file +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;AAAA,yCAAoC;AAEpC,0DAAkE;AAClE,0FAAgF;AAChF,wFAAqF;AACrF,sFAAmF;AACnF,kFAA+E;AAC/E,4FAAyF;AACzF,8EAA2E;AAC3E,wFAAqF;AACrF,8EAA2E;AAE3E,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,IAAI,CAAC,IAAA,sCAAuB,GAAE,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;QACvF,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,mBAAO,EAAE,CAAC;IAC1B,GAAG;SACA,IAAI,CAAC,uBAAuB,CAAC;SAC7B,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC;SAC3C,WAAW,CAAC,2EAA2E,CAAC,CAAC;IAE5F,IAAA,+CAAsB,EAAC,GAAG,CAAC,CAAC;IAC5B,IAAA,6DAA6B,EAAC,GAAG,CAAC,CAAC;IACnC,IAAA,yDAA2B,EAAC,GAAG,CAAC,CAAC;IACjC,IAAA,yDAA2B,EAAC,GAAG,CAAC,CAAC;IAEjC,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,oCAAoC,CAAC,CAAC;IAC7F,IAAA,+CAAsB,EAAC,aAAa,CAAC,CAAC;IACtC,IAAA,uDAA0B,EAAC,aAAa,CAAC,CAAC;IAC1C,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;IACxC,IAAA,oDAAqB,EAAC,aAAa,CAAC,CAAC;IAErC,MAAM,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/expo-type-information/src/cli.ts b/packages/expo-type-information/src/cli.ts index bd4053df965771..333bde6503b805 100644 --- a/packages/expo-type-information/src/cli.ts +++ b/packages/expo-type-information/src/cli.ts @@ -19,7 +19,7 @@ async function main(args: string[]) { cli .name('expo-type-information') .version(require('../package.json').version) - .description('CLI commands for retrieving type information from native files.'); + .description('Retrieve type information from Swift Expo modules to generate TypeScript.'); moduleInterfaceCommand(cli); inlineModulesInterfaceCommand(cli); From cfae0318e1ff727e12c1e47b0e56a6cc0f21e820 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Tue, 12 May 2026 14:56:18 +0200 Subject: [PATCH 08/11] [expo-app-metrics][android] remove start and stop session functions (#45633) # Why Turns out that `startSession` and `stopSession` are not needed for router integration anymore. # How 1. Remove android native implementation 2. Update types 3. Update module.web # Test Plan CI # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../modules/appmetrics/AppMetricsModule.kt | 22 ------------------- .../expo-app-metrics/build/module.web.d.ts | 2 -- .../build/module.web.d.ts.map | 2 +- packages/expo-app-metrics/build/module.web.js | 4 ---- .../expo-app-metrics/build/module.web.js.map | 2 +- packages/expo-app-metrics/build/types.d.ts | 13 ----------- .../expo-app-metrics/build/types.d.ts.map | 2 +- packages/expo-app-metrics/build/types.js.map | 2 +- packages/expo-app-metrics/src/module.web.ts | 4 ---- packages/expo-app-metrics/src/types.ts | 13 ----------- 10 files changed, 4 insertions(+), 62 deletions(-) diff --git a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt index 9eba8a9804bb5d..8cc97260c0742f 100644 --- a/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt +++ b/packages/expo-app-metrics/android/src/main/java/expo/modules/appmetrics/AppMetricsModule.kt @@ -170,28 +170,6 @@ class AppMetricsModule : Module(), UpdatesStateChangeListener { AsyncFunction("clearStoredEntries") Coroutine { -> sessionManager.clearAllData() } - Function("startSession") { - val sessionId = sessionManager.createSessionId() - val timestamp = TimeUtils.getCurrentTimestampInISOFormat() - val sessionMetadata = metadata - - scope.launch { - sessionManager.startSessionWithIdAt( - sessionId = sessionId, - timestamp = timestamp, - metadata = sessionMetadata - ) - } - - return@Function sessionId - } - - Function("stopSession") { sessionId: String -> - scope.launch { - sessionManager.stopSession(sessionId = sessionId) - } - } - AsyncFunction("addCustomMetricToSession") Coroutine { metric: JsMetric -> sessionManager.addMetrics(listOf(metric.toMetric()), sessionId = metric.sessionId) } diff --git a/packages/expo-app-metrics/build/module.web.d.ts b/packages/expo-app-metrics/build/module.web.d.ts index 27eab1950936b8..7f69340b00de2c 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts +++ b/packages/expo-app-metrics/build/module.web.d.ts @@ -11,8 +11,6 @@ declare class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetric getAllSessions(): Promise; simulateCrashReport(): void; triggerCrash(): void; - startSession(metadata?: string): string; - stopSession(sessionId: string): void; getMainSession(): Promise; } declare const _default: typeof ExpoAppMetricsModule; diff --git a/packages/expo-app-metrics/build/module.web.d.ts.map b/packages/expo-app-metrics/build/module.web.d.ts.map index abc89bc923cf30..5f09f5a6cd5bb3 100644 --- a/packages/expo-app-metrics/build/module.web.d.ts.map +++ b/packages/expo-app-metrics/build/module.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEnG,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAGjD,eAAe;IACf,eAAe,CAAC,UAAU,CAAC,EAAE,gBAAgB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAC1C,gBAAgB;IAGhB,kBAAkB;IAClB,cAAc;IAGpB,mBAAmB;IACnB,YAAY;IACZ,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM;IAG9B,WAAW,CAAC,SAAS,EAAE,MAAM;IACvB,cAAc;CAGrB;;AAED,wBAAyE"} \ No newline at end of file +{"version":3,"file":"module.web.d.ts","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEnG,cAAc,SAAS,CAAC;AAExB,cAAM,oBAAqB,SAAQ,YAAa,YAAW,wBAAwB;IACjF,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAGjD,eAAe;IACf,eAAe,CAAC,UAAU,CAAC,EAAE,gBAAgB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAC1C,gBAAgB;IAGhB,kBAAkB;IAClB,cAAc;IAGpB,mBAAmB;IACnB,YAAY;IACN,cAAc;CAGrB;;AAED,wBAAyE"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/module.web.js b/packages/expo-app-metrics/build/module.web.js index e3a64647e81134..d4ed0a8265a9ef 100644 --- a/packages/expo-app-metrics/build/module.web.js +++ b/packages/expo-app-metrics/build/module.web.js @@ -16,10 +16,6 @@ class ExpoAppMetricsModule extends NativeModule { } simulateCrashReport() { } triggerCrash() { } - startSession(metadata) { - return ''; - } - stopSession(sessionId) { } async getMainSession() { return null; } diff --git a/packages/expo-app-metrics/build/module.web.js.map b/packages/expo-app-metrics/build/module.web.js.map index 835d197cc84e78..957831704bc838 100644 --- a/packages/expo-app-metrics/build/module.web.js.map +++ b/packages/expo-app-metrics/build/module.web.js.map @@ -1 +1 @@ -{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CAAC,MAAc;QACrC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,CAAC,UAA6B,IAAG,CAAC;IACvD,QAAQ,CAAC,IAAY,EAAE,OAAyB,IAAG,CAAC;IACpD,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,KAAK,CAAC,cAAc;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,mBAAmB,KAAI,CAAC;IACxB,YAAY,KAAI,CAAC;IACjB,YAAY,CAAC,QAAiB;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,WAAW,CAAC,SAAiB,IAAG,CAAC;IACjC,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType, LogEventOptions, Metric, MetricAttributes } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(metric: Metric): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive(attributes?: MetricAttributes) {}\n logEvent(name: string, options?: LogEventOptions) {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n async getAllSessions() {\n return [];\n }\n simulateCrashReport() {}\n triggerCrash() {}\n startSession(metadata?: string) {\n return '';\n }\n stopSession(sessionId: string) {}\n async getMainSession() {\n return null;\n }\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file +{"version":3,"file":"module.web.js","sourceRoot":"","sources":["../src/module.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIvD,cAAc,SAAS,CAAC;AAExB,MAAM,oBAAqB,SAAQ,YAAY;IAC7C,wBAAwB,CAAC,MAAc;QACrC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,eAAe,KAAI,CAAC;IAC1B,KAAK,CAAC,eAAe,CAAC,UAA6B,IAAG,CAAC;IACvD,QAAQ,CAAC,IAAY,EAAE,OAAyB,IAAG,CAAC;IACpD,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,kBAAkB,KAAI,CAAC;IAC7B,KAAK,CAAC,cAAc;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,mBAAmB,KAAI,CAAC;IACxB,YAAY,KAAI,CAAC;IACjB,KAAK,CAAC,cAAc;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, registerWebModule } from 'expo';\n\nimport type { ExpoAppMetricsModuleType, LogEventOptions, Metric, MetricAttributes } from './types';\n\nexport * from './types';\n\nclass ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleType {\n addCustomMetricToSession(metric: Metric): Promise {\n throw new Error('Method not implemented.');\n }\n async markFirstRender() {}\n async markInteractive(attributes?: MetricAttributes) {}\n logEvent(name: string, options?: LogEventOptions) {}\n async getStoredEntries() {\n return [];\n }\n async clearStoredEntries() {}\n async getAllSessions() {\n return [];\n }\n simulateCrashReport() {}\n triggerCrash() {}\n async getMainSession() {\n return null;\n }\n}\n\nexport default registerWebModule(ExpoAppMetricsModule, 'ExpoAppMetrics');\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.d.ts b/packages/expo-app-metrics/build/types.d.ts index 9fd82b23de3d51..458f3fb813584e 100644 --- a/packages/expo-app-metrics/build/types.d.ts +++ b/packages/expo-app-metrics/build/types.d.ts @@ -277,19 +277,6 @@ export interface ExpoAppMetricsModuleType { * @platform ios */ triggerCrash(kind: CrashKind): void; - /** - * Starts a new app metrics session. Returns the session ID. - * - * @private This API is unstable and may change without notice. - * @platform android - */ - startSession(): string; - /** - * Stops the app metrics session with the given session ID. - * - * @platform android - */ - stopSession(sessionId: string): void; /** * @private This API is unstable and may change without notice. * @platform android diff --git a/packages/expo-app-metrics/build/types.d.ts.map b/packages/expo-app-metrics/build/types.d.ts.map index 9dc6d1589c26ae..40e7d594a641c4 100644 --- a/packages/expo-app-metrics/build/types.d.ts.map +++ b/packages/expo-app-metrics/build/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-app-metrics/build/types.js.map b/packages/expo-app-metrics/build/types.js.map index b4f2ab0325129c..1c9100f3482e96 100644 --- a/packages/expo-app-metrics/build/types.js.map +++ b/packages/expo-app-metrics/build/types.js.map @@ -1 +1 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport type MetricAttributes = {\n /**\n * Name of the route associated with the metric. Some metrics populate this\n * with a sensible default when omitted — for example, the TTI metric falls\n * back to the initial route name detected from the router.\n */\n routeName?: string;\n /**\n * Custom parameters to attach to the metric.\n */\n params?: Record;\n};\n\n/**\n * Severity of a log event, ordered from least to most severe:\n *\n * - `\"trace\"` — Fine-grained tracing, typically only useful while reproducing\n * a specific issue.\n * - `\"debug\"` — Diagnostic detail useful during development; usually filtered\n * out in production.\n * - `\"info\"` — Routine, expected events that record normal app behavior.\n * - `\"warn\"` — Unexpected but recoverable conditions worth investigating.\n * - `\"error\"` — An operation failed; the app continues running but is in a\n * degraded state.\n * - `\"fatal\"` — A severe failure, often immediately followed by app termination.\n */\nexport type LogSeverity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * Value types accepted in a log event's `attributes` map. Strings, numbers,\n * and booleans are stored as typed primitives; arrays and nested maps preserve\n * their structure. Other JS values (functions, `Date`, `undefined`, etc.) are\n * not supported and may be dropped by downstream consumers.\n */\nexport type LogAttributeValue =\n | string\n | number\n | boolean\n | LogAttributeValue[]\n | { [key: string]: LogAttributeValue };\n\n/**\n * A single log event collected during a session.\n */\nexport type LogRecord = {\n /**\n * ISO 8601 timestamp of when the record was created.\n */\n timestamp: string;\n /**\n * Event name.\n */\n name: string;\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n */\n severity: LogSeverity;\n};\n\n/**\n * Optional configuration accepted by `logEvent`. The event name is passed as\n * the first positional argument since it's required and the only field most\n * callers set.\n */\nexport type LogEventOptions = {\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n *\n * @default \"info\"\n */\n severity?: LogSeverity | null;\n};\n\nexport type SessionType = 'main' | 'foreground' | 'screen' | 'custom' | 'unknown';\n\nexport type CrashKind =\n | 'badAccess'\n | 'fatalError'\n | 'divideByZero'\n | 'forceUnwrapNil'\n | 'arrayOutOfBounds'\n | 'objcException'\n | 'stackOverflow';\n\ntype SessionBase = {\n id: string;\n startDate: string;\n endDate?: string | null;\n metrics: Metric[];\n logs: LogRecord[];\n};\n\nexport type MainSession = SessionBase & {\n type: 'main';\n crashReport?: CrashReport | null;\n};\n\nexport type GenericSession = SessionBase & {\n type: Exclude;\n};\n\nexport type Session = MainSession | GenericSession;\n\nexport type CallStackFrame = {\n binaryName?: string | null;\n binaryUUID?: string | null;\n address?: number | null;\n offsetIntoBinaryTextSegment?: number | null;\n sampleCount?: number | null;\n subFrames?: CallStackFrame[] | null;\n /**\n * Resolved symbol from on-device `dladdr` symbolication. Swift and Itanium-ABI C++\n * names are demangled; Objective-C selectors and plain C symbols are returned as-is.\n * `null` when the binary is not loaded in this process or `dladdr` could not resolve it.\n *\n * @platform ios\n */\n symbol?: string | null;\n};\n\nexport type CallStack = {\n threadAttributed?: boolean | null;\n callStackRootFrames?: CallStackFrame[] | null;\n};\n\nexport type CallStackTree = {\n callStacks?: CallStack[] | null;\n};\n\nexport type CrashReport = {\n exceptionType?: number | null;\n exceptionCode?: number | null;\n signal?: number | null;\n terminationReason?: string | null;\n virtualMemoryRegionInfo?: string | null;\n exceptionReason?: {\n composedMessage: string;\n formatString: string;\n arguments: string[];\n exceptionType: string;\n className: string;\n exceptionName: string;\n } | null;\n callStackTree?: CallStackTree | null;\n timestampBegin: string;\n timestampEnd: string;\n ingestedAt: string;\n};\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(attributes?: MetricAttributes): void;\n /**\n * Records a log event against the current main session. The event is\n * persisted locally and dispatched on the next `dispatchEvents()` flush as an\n * OpenTelemetry log record sent to the `/v1/logs` endpoint.\n *\n * Severity defaults to `\"info\"` when not provided.\n *\n * @param name Event name. Maps to the OpenTelemetry `event.name` attribute.\n * @param options Optional body, attributes, and severity overrides.\n */\n logEvent(name: string, options?: LogEventOptions): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n /**\n * Returns all sessions across the current and historical entries,\n * ordered with the current launch first.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n getAllSessions(): Promise;\n\n /**\n * Simulates a crash report, attributing it to the current main session.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n simulateCrashReport(): void;\n\n /**\n * Intentionally crashes the app to produce a real MetricKit diagnostic.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n triggerCrash(kind: CrashKind): void;\n\n /**\n * Starts a new app metrics session. Returns the session ID.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n startSession(): string;\n /**\n * Stops the app metrics session with the given session ID.\n *\n * @platform android\n */\n stopSession(sessionId: string): void;\n /**\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n addCustomMetricToSession(metric: Metric): Promise;\n /**\n * Returns the current main session, including its metrics. Resolves to\n * `null` if the session row hasn't been persisted yet.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n getMainSession(): Promise;\n}\n"]} \ No newline at end of file +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type AppStartupTimes = {\n /**\n * Time from when the user taps the app to the moment the app starts executing the main code.\n * It includes loading native dynamic libraries, executing C++ static constructors\n * and Objective-C `+load` methods defined in classes or categories.\n *\n * @platform iOS\n */\n loadTime?: number;\n /**\n * Full time from process start until the root view of the React Native instance is created,\n * recorded when the app is launched fresh by the user.\n */\n coldLaunchTime?: number;\n /**\n * Launch time recorded when the process was already running in the background\n * before the user launches the app.\n */\n warmLaunchTime?: number;\n /**\n * Duration to evaluate the JavaScript bundle by the runtime.\n */\n bundleLoadTime?: number;\n /**\n * Time until the first React render occurs.\n */\n timeToFirstRender?: number;\n /**\n * Time until the app is interactive (after first render).\n */\n timeToInteractive?: number;\n};\n\nexport type MemoryUsageSnapshot = {\n /**\n * Memory in bytes allocated by the app, including both the physical memory and additional memory that the app might be using,\n * such as memory that has been paged out (swapped) to disk or memory that is shared with other processes.\n *\n * @platform iOS\n */\n allocated?: number;\n /**\n * Physical memory in bytes pages currently in use (resident size).\n */\n physical: number;\n /**\n * The amount of available memory in bytes that app can still allocate.\n */\n available: number;\n /**\n * The amount of memory in bytes currently used by the Java heap.\n *\n * @platform android\n */\n javaHeap?: number;\n};\n\nexport type FrameRateMetrics = {\n /**\n * Total amount of frames rendered.\n */\n renderedFrames: number;\n /**\n * Expected amount of frames rendered if everything renders without any delay.\n */\n expectedFrames: number;\n /**\n * Number of frames which were skipped because the main thread was busy with some work.\n */\n droppedFrames: number;\n /**\n * Total amount of frozen frames. Frozen frame is frame that takes at least 700ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/frozen).\n */\n frozenFrames: number;\n /**\n * Total amount of slow frames. Slow frame is frame that takes at least 17ms to render.\n * It is a term from [Android development](https://developer.android.com/topic/performance/vitals/render).\n */\n slowFrames: number;\n /**\n * Total amount of freeze durations, in seconds. Freeze is an amount of time every frame rendering was delayed by in comparison with the ideal performant frame.\n * For example if expected frame duration was 16ms, but in reality we've rendered this frame in 320ms, we have a freeze with 304ms duration.\n */\n freezeTime: number;\n /**\n * Total duration of the screen session, in seconds. It is counted by summing up all rendered frames duration.\n */\n sessionDuration: number;\n};\n\nexport interface Metric {\n timestamp: string;\n category: string;\n name: string;\n value: number;\n sessionId: string;\n routeName?: string;\n params?: Record;\n}\n\nexport type MetricAttributes = {\n /**\n * Name of the route associated with the metric. Some metrics populate this\n * with a sensible default when omitted — for example, the TTI metric falls\n * back to the initial route name detected from the router.\n */\n routeName?: string;\n /**\n * Custom parameters to attach to the metric.\n */\n params?: Record;\n};\n\n/**\n * Severity of a log event, ordered from least to most severe:\n *\n * - `\"trace\"` — Fine-grained tracing, typically only useful while reproducing\n * a specific issue.\n * - `\"debug\"` — Diagnostic detail useful during development; usually filtered\n * out in production.\n * - `\"info\"` — Routine, expected events that record normal app behavior.\n * - `\"warn\"` — Unexpected but recoverable conditions worth investigating.\n * - `\"error\"` — An operation failed; the app continues running but is in a\n * degraded state.\n * - `\"fatal\"` — A severe failure, often immediately followed by app termination.\n */\nexport type LogSeverity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n\n/**\n * Value types accepted in a log event's `attributes` map. Strings, numbers,\n * and booleans are stored as typed primitives; arrays and nested maps preserve\n * their structure. Other JS values (functions, `Date`, `undefined`, etc.) are\n * not supported and may be dropped by downstream consumers.\n */\nexport type LogAttributeValue =\n | string\n | number\n | boolean\n | LogAttributeValue[]\n | { [key: string]: LogAttributeValue };\n\n/**\n * A single log event collected during a session.\n */\nexport type LogRecord = {\n /**\n * ISO 8601 timestamp of when the record was created.\n */\n timestamp: string;\n /**\n * Event name.\n */\n name: string;\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n */\n severity: LogSeverity;\n};\n\n/**\n * Optional configuration accepted by `logEvent`. The event name is passed as\n * the first positional argument since it's required and the only field most\n * callers set.\n */\nexport type LogEventOptions = {\n /**\n * Optional free-form message describing the event.\n */\n body?: string | null;\n /**\n * Custom attributes attached to the event. Each entry is preserved with its\n * original value type — see `LogAttributeValue` for the supported shapes.\n */\n attributes?: Record | null;\n /**\n * Severity of the event.\n *\n * @default \"info\"\n */\n severity?: LogSeverity | null;\n};\n\nexport type SessionType = 'main' | 'foreground' | 'screen' | 'custom' | 'unknown';\n\nexport type CrashKind =\n | 'badAccess'\n | 'fatalError'\n | 'divideByZero'\n | 'forceUnwrapNil'\n | 'arrayOutOfBounds'\n | 'objcException'\n | 'stackOverflow';\n\ntype SessionBase = {\n id: string;\n startDate: string;\n endDate?: string | null;\n metrics: Metric[];\n logs: LogRecord[];\n};\n\nexport type MainSession = SessionBase & {\n type: 'main';\n crashReport?: CrashReport | null;\n};\n\nexport type GenericSession = SessionBase & {\n type: Exclude;\n};\n\nexport type Session = MainSession | GenericSession;\n\nexport type CallStackFrame = {\n binaryName?: string | null;\n binaryUUID?: string | null;\n address?: number | null;\n offsetIntoBinaryTextSegment?: number | null;\n sampleCount?: number | null;\n subFrames?: CallStackFrame[] | null;\n /**\n * Resolved symbol from on-device `dladdr` symbolication. Swift and Itanium-ABI C++\n * names are demangled; Objective-C selectors and plain C symbols are returned as-is.\n * `null` when the binary is not loaded in this process or `dladdr` could not resolve it.\n *\n * @platform ios\n */\n symbol?: string | null;\n};\n\nexport type CallStack = {\n threadAttributed?: boolean | null;\n callStackRootFrames?: CallStackFrame[] | null;\n};\n\nexport type CallStackTree = {\n callStacks?: CallStack[] | null;\n};\n\nexport type CrashReport = {\n exceptionType?: number | null;\n exceptionCode?: number | null;\n signal?: number | null;\n terminationReason?: string | null;\n virtualMemoryRegionInfo?: string | null;\n exceptionReason?: {\n composedMessage: string;\n formatString: string;\n arguments: string[];\n exceptionType: string;\n className: string;\n exceptionName: string;\n } | null;\n callStackTree?: CallStackTree | null;\n timestampBegin: string;\n timestampEnd: string;\n ingestedAt: string;\n};\n\nexport interface ExpoAppMetricsModuleType {\n markFirstRender(): void;\n markInteractive(attributes?: MetricAttributes): void;\n /**\n * Records a log event against the current main session. The event is\n * persisted locally and dispatched on the next `dispatchEvents()` flush as an\n * OpenTelemetry log record sent to the `/v1/logs` endpoint.\n *\n * Severity defaults to `\"info\"` when not provided.\n *\n * @param name Event name. Maps to the OpenTelemetry `event.name` attribute.\n * @param options Optional body, attributes, and severity overrides.\n */\n logEvent(name: string, options?: LogEventOptions): void;\n getStoredEntries(): Promise;\n clearStoredEntries(): Promise;\n /**\n * Returns all sessions across the current and historical entries,\n * ordered with the current launch first.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n getAllSessions(): Promise;\n\n /**\n * Simulates a crash report, attributing it to the current main session.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n simulateCrashReport(): void;\n\n /**\n * Intentionally crashes the app to produce a real MetricKit diagnostic.\n * Intended for development and debugging only.\n *\n * @private This API is unstable and may change without notice.\n * @platform ios\n */\n triggerCrash(kind: CrashKind): void;\n\n /**\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n addCustomMetricToSession(metric: Metric): Promise;\n /**\n * Returns the current main session, including its metrics. Resolves to\n * `null` if the session row hasn't been persisted yet.\n *\n * @private This API is unstable and may change without notice.\n * @platform android\n */\n getMainSession(): Promise;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-app-metrics/src/module.web.ts b/packages/expo-app-metrics/src/module.web.ts index 6c03faea8e5cdc..4f5fd53ffc8e17 100644 --- a/packages/expo-app-metrics/src/module.web.ts +++ b/packages/expo-app-metrics/src/module.web.ts @@ -20,10 +20,6 @@ class ExpoAppMetricsModule extends NativeModule implements ExpoAppMetricsModuleT } simulateCrashReport() {} triggerCrash() {} - startSession(metadata?: string) { - return ''; - } - stopSession(sessionId: string) {} async getMainSession() { return null; } diff --git a/packages/expo-app-metrics/src/types.ts b/packages/expo-app-metrics/src/types.ts index c8f539301b295e..4aa02663ae5380 100644 --- a/packages/expo-app-metrics/src/types.ts +++ b/packages/expo-app-metrics/src/types.ts @@ -309,19 +309,6 @@ export interface ExpoAppMetricsModuleType { */ triggerCrash(kind: CrashKind): void; - /** - * Starts a new app metrics session. Returns the session ID. - * - * @private This API is unstable and may change without notice. - * @platform android - */ - startSession(): string; - /** - * Stops the app metrics session with the given session ID. - * - * @platform android - */ - stopSession(sessionId: string): void; /** * @private This API is unstable and may change without notice. * @platform android From bad1f627551164148533521a11bf50dec606f044 Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Tue, 12 May 2026 15:43:19 +0200 Subject: [PATCH 09/11] [dev-menu] Fix FAB safe-area bounds (#45647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why There was a bug in calculating the safe area bounds for the FAB. In the first invocation of `rememberFabState`, `SafeBarInsets` were (0,0). Screenshot 2026-05-11 at 15 09 16 This value stayed in `FabState` forever because the `remember` clause lacked change keys, causing the FAB to be positioned above the statusBar: Screenshot 2026-05-12 at 10 52 11 The easier fix would be to make bounds mutable: ```kotlin val state = remember { FabState(initialOffset, fabBounds, prefs) } state.fabBounds = fabBounds ``` However, there is a better solution: slightly changing the architecture to move safe-area responsibility to `MovableFloatingActionButton`. This is achieved with the `safeDrawingPadding` modifier. It works like `WindowInsets.systemBars` but removes the need for manual calculations in `rememberFabState`. This way, the source of truth for bounds is the `BoxWithConstraints` size, not `rememberFabState` logic. # How - Simplified FabState to properties used inside the class; moved the rest of responsibilities to `MovableFloatingActionButton` - Made state bounds mutable so they can update when they change (as seen in the screenshot above). # Test Plan BareExpo ✅ [Screen_recording_20260512_104957.webm](https://github.com/user-attachments/assets/352ed42a-fec9-479c-9216-8c950a7f9fe3) --- packages/expo-dev-menu/CHANGELOG.md | 2 + .../java/expo/modules/devmenu/fab/FabState.kt | 51 +++++-------------- .../java/expo/modules/devmenu/fab/FabUtils.kt | 10 ++++ .../fab/MovableFloatingActionButton.kt | 39 ++++++++++---- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/packages/expo-dev-menu/CHANGELOG.md b/packages/expo-dev-menu/CHANGELOG.md index 9973367a4d0eea..9049187aa77240 100644 --- a/packages/expo-dev-menu/CHANGELOG.md +++ b/packages/expo-dev-menu/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Fix FAB safe-area bounds ([#45647](https://github.com/expo/expo/pull/45647) by [@Wenszel](https://github.com/Wenszel)) + ### 💡 Others ## 56.0.6 — 2026-05-11 diff --git a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabState.kt b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabState.kt index 233262b829c29b..7f7cc1df5f7fe9 100644 --- a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabState.kt +++ b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabState.kt @@ -4,8 +4,6 @@ import android.content.Context import android.content.SharedPreferences import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.VectorConverter -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -15,7 +13,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.core.content.edit import kotlinx.coroutines.delay @@ -25,17 +22,9 @@ private const val FAB_POSITION_X = "fabPositionX" private const val FAB_POSITION_Y = "fabPositionY" private const val FAB_POSITION_UNSET = -1f -data class FabBounds( - val screen: Offset, - val safe: Offset, - val safeMinY: Float, - val drag: Offset, - val halfFab: Offset -) - class FabState( initialOffset: Offset, - val bounds: FabBounds, + var fabAreaBounds: Offset, val prefs: SharedPreferences ) { val animatedOffset = Animatable(initialOffset, Offset.VectorConverter) @@ -51,12 +40,9 @@ class FabState( } fun savePosition(offset: Offset) { - val safeWidth = bounds.safe.x - val safeHeight = bounds.safe.y - bounds.safeMinY - // Store position as 0–1 ratios within the safe area so it survives screen size changes - val normalizedX = if (safeWidth > 0f) offset.x / safeWidth else 0f - val normalizedY = if (safeHeight > 0f) (offset.y - bounds.safeMinY) / safeHeight else 0f + val normalizedX = if (fabAreaBounds.x > 0f) offset.x / fabAreaBounds.x else 0f + val normalizedY = if (fabAreaBounds.y > 0f) offset.y / fabAreaBounds.y else 0f prefs.edit { putFloat(FAB_POSITION_X, normalizedX) @@ -66,40 +52,27 @@ class FabState( } @Composable -fun rememberFabState(screenBounds: Offset, totalFabSizePx: Offset): FabState { - val density = LocalDensity.current - val systemBarInsets = WindowInsets.systemBars - val safeInsetTop = with(density) { systemBarInsets.getTop(this).toFloat() } - val safeInsetBottom = with(density) { systemBarInsets.getBottom(this).toFloat() } - val halfFab = Offset(totalFabSizePx.x / 2f, totalFabSizePx.y / 2f) - - val fabBounds = FabBounds( - screen = screenBounds, - safe = Offset(screenBounds.x, screenBounds.y - safeInsetBottom), - safeMinY = safeInsetTop, - drag = Offset(screenBounds.x + halfFab.x, screenBounds.y + halfFab.y), - halfFab = halfFab - ) - +fun rememberFabState(fabAreaBounds: Offset): FabState { val context = LocalContext.current val prefs = remember { context.getSharedPreferences(FAB_PREFS, Context.MODE_PRIVATE) } - val initialOffset = remember(fabBounds.safe, fabBounds.safeMinY) { + val initialOffset = remember { val savedX = prefs.getFloat(FAB_POSITION_X, FAB_POSITION_UNSET) val savedY = prefs.getFloat(FAB_POSITION_Y, FAB_POSITION_UNSET) - val safeMaxX = maxOf(0f, fabBounds.safe.x) - val safeMaxY = maxOf(fabBounds.safeMinY, fabBounds.safe.y) if (savedX != FAB_POSITION_UNSET && savedY != FAB_POSITION_UNSET) { Offset( - x = (savedX * safeMaxX).coerceIn(0f, safeMaxX), - y = (savedY * (safeMaxY - fabBounds.safeMinY) + fabBounds.safeMinY).coerceIn(fabBounds.safeMinY, safeMaxY) + x = (savedX * fabAreaBounds.x).coerceIn(0f, fabAreaBounds.x), + y = (savedY * fabAreaBounds.y).coerceIn(0f, fabAreaBounds.y) ) } else { - Offset(fabBounds.safe.x, fabBounds.safeMinY) + Offset(fabAreaBounds.x, 0f) } } - val state = remember { FabState(initialOffset, fabBounds, prefs) } + val state = remember { FabState(initialOffset, fabAreaBounds, prefs) } + // We can't simply update the entire state when the bounds change, + // because it would result in resetting the interaction state (isPressed, isDragging). + state.fabAreaBounds = fabAreaBounds LaunchedEffect(state.lastInteractionTime) { state.isIdle = false diff --git a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabUtils.kt b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabUtils.kt index 69ae479d86c239..7cd25c71ec1b4f 100644 --- a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabUtils.kt +++ b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/FabUtils.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.unit.IntOffset import expo.modules.devmenu.fab.ExpoVelocityTracker.PointF import kotlin.math.roundToInt @@ -49,6 +50,15 @@ internal fun Offset.coerceIn(minX: Float = 0f, maxX: Float, minY: Float = 0f, ma ) } +internal fun Offset.coerceIn(rect: Rect): Offset { + return this.coerceIn( + minX = rect.left, + maxX = rect.right, + minY = rect.top, + maxY = rect.bottom + ) +} + @Composable internal fun rememberPrevious(current: T): T? { val previousRef = remember { mutableStateOf(null) } diff --git a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/MovableFloatingActionButton.kt b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/MovableFloatingActionButton.kt index 83fb8ff3db2bc6..c26b78641e0beb 100644 --- a/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/MovableFloatingActionButton.kt +++ b/packages/expo-dev-menu/android/src/debug/java/expo/modules/devmenu/fab/MovableFloatingActionButton.kt @@ -14,12 +14,14 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.platform.LocalDensity @@ -48,7 +50,11 @@ fun MovableFloatingActionButton( margin: Dp = Margin, onPress: () -> Unit = {} ) { - BoxWithConstraints(modifier = modifier.fillMaxSize()) { + BoxWithConstraints( + modifier = modifier + .safeDrawingPadding() + .fillMaxSize() + ) { val totalFabSize = DpSize(fabSize.width + margin * 2, fabSize.height + margin * 2) val totalFabSizePx = with(LocalDensity.current) { Offset(totalFabSize.width.toPx(), totalFabSize.height.toPx()) @@ -58,7 +64,16 @@ fun MovableFloatingActionButton( y = constraints.maxHeight - totalFabSizePx.y ) - val fab = rememberFabState(bounds, totalFabSizePx) + val halfFab = Offset(totalFabSizePx.x / 2f, totalFabSizePx.y / 2f) + val dragBounds = Rect( + left = -halfFab.x, + top = -halfFab.y, + right = bounds.x + halfFab.x, + bottom = bounds.y + halfFab.y + ) + + val fab = rememberFabState(bounds) + val isFabDisplayable = state.showFab && !state.isInPictureInPictureMode && bounds.x >= 0f && @@ -70,7 +85,7 @@ fun MovableFloatingActionButton( LaunchedEffect(state.isOpen) { if (state.isOpen) { fab.restingOffset = fab.animatedOffset.value - val isOnLeftSide = fab.animatedOffset.value.x < fab.bounds.safe.x / 2f + val isOnLeftSide = fab.animatedOffset.value.x < fab.fabAreaBounds.x / 2f val offScreenX = if (isOnLeftSide) -totalFabSizePx.x else constraints.maxWidth.toFloat() fab.animatedOffset.animateTo( targetValue = Offset(offScreenX, fab.animatedOffset.value.y), @@ -97,14 +112,13 @@ fun MovableFloatingActionButton( previousBounds?.let { val oldX = fab.animatedOffset.value.x val oldY = fab.animatedOffset.value.y - val newX = (oldX / previousBounds.x) * fab.bounds.safe.x - val newY = (oldY / previousBounds.y) * fab.bounds.safe.y + val newX = (oldX / previousBounds.x) * fab.fabAreaBounds.x + val newY = (oldY / previousBounds.y) * fab.fabAreaBounds.y val newTarget = calculateTargetPosition( currentPosition = Offset(newX, newY), velocity = ExpoVelocityTracker.PointF(0f, 0f), - bounds = fab.bounds.safe, - totalFabWidth = totalFabSizePx.x, - minY = fab.bounds.safeMinY + bounds = fab.fabAreaBounds, + totalFabWidth = totalFabSizePx.x ) fab.animatedOffset.snapTo(newTarget) @@ -140,7 +154,7 @@ fun MovableFloatingActionButton( drag(pointerId) { change -> dragOffset = (dragOffset + change.positionChange()) - .coerceIn(minX = -fab.bounds.halfFab.x, maxX = fab.bounds.drag.x, minY = -fab.bounds.halfFab.y, maxY = fab.bounds.drag.y) + .coerceIn(dragBounds) dragDistance += change.positionChange().getDistance() velocityTracker.registerPosition(dragOffset.x, dragOffset.y) @@ -187,7 +201,12 @@ private fun CoroutineScope.handleRelease( totalFabSizePx: Offset ) { val velocity = velocityTracker.calculateVelocity() - val newOffset = calculateTargetPosition(fab.animatedOffset.value, velocity, fab.bounds.safe, totalFabSizePx.x, fab.bounds.safeMinY) + val newOffset = calculateTargetPosition( + currentPosition = fab.animatedOffset.value, + velocity = velocity, + bounds = fab.fabAreaBounds, + totalFabWidth = totalFabSizePx.x + ) velocityTracker.clear() launch { From 167437e99e51f7ddafb4390f2c46bca4d4246dff Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Tue, 12 May 2026 16:31:37 +0200 Subject: [PATCH 10/11] [expo] Reexport permission types and hooks from expo-modules-core (#45564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Types and functions related to permissions should be re-exported from the `expo` package as discussed here: https://github.com/expo/expo/pull/44403 # How Re-exported the types and functions from `expo-modules-core` to the `expo` package. # Test Plan BareExpo ✅ --- packages/expo/CHANGELOG.md | 2 ++ packages/expo/build/Expo.d.ts | 2 ++ packages/expo/build/Expo.d.ts.map | 2 +- packages/expo/src/Expo.ts | 9 +++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 53f50b1b7e9b3f..cbdea04fc715f6 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -12,6 +12,8 @@ ### 💡 Others +- Reexport permission hooks and permission types from `expo-modules-core` ([#45564](https://github.com/expo/expo/pull/45564) by [@Wenszel](https://github.com/Wenszel)) + ## 56.0.0-preview.9 — 2026-05-12 _This version does not introduce any user-facing changes._ diff --git a/packages/expo/build/Expo.d.ts b/packages/expo/build/Expo.d.ts index c9570c2f870d84..0a6ccae4ee0c41 100644 --- a/packages/expo/build/Expo.d.ts +++ b/packages/expo/build/Expo.d.ts @@ -12,5 +12,7 @@ EventEmitter as EventEmitterType, NativeModule as NativeModuleType, /** @deprecated Move to `SharedObject` with a type-only import instead */ SharedObject as SharedObjectType, } from 'expo-modules-core/types'; +export { PermissionStatus, type PermissionExpiration, type PermissionResponse, type PermissionHookOptions, } from 'expo-modules-core'; +export { createPermissionHook } from 'expo-modules-core'; export { useEvent, useEventListener } from './hooks/useEvent'; //# sourceMappingURL=Expo.d.ts.map \ No newline at end of file diff --git a/packages/expo/build/Expo.d.ts.map b/packages/expo/build/Expo.d.ts.map index c24ad97236c0b1..0bb823e3f410ad 100644 --- a/packages/expo/build/Expo.d.ts.map +++ b/packages/expo/build/Expo.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Expo.d.ts","sourceRoot":"","sources":["../src/Expo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AAEnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAEL,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EAGZ,mBAAmB,EACnB,2BAA2B,EAC3B,wBAAwB,IAAI,iBAAiB,EAC7C,iBAAiB,EACjB,cAAc,EAGd,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,YAAY;AACV,sEAAsE;AACtE,SAAS,IAAI,aAAa;AAC1B,yEAAyE;AACzE,YAAY,IAAI,gBAAgB;AAChC,yEAAyE;AACzE,YAAY,IAAI,gBAAgB;AAChC,yEAAyE;AACzE,YAAY,IAAI,gBAAgB,GACjC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"} \ No newline at end of file +{"version":3,"file":"Expo.d.ts","sourceRoot":"","sources":["../src/Expo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AAEnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAEL,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,YAAY,EAGZ,mBAAmB,EACnB,2BAA2B,EAC3B,wBAAwB,IAAI,iBAAiB,EAC7C,iBAAiB,EACjB,cAAc,EAGd,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,YAAY;AACV,sEAAsE;AACtE,SAAS,IAAI,aAAa;AAC1B,yEAAyE;AACzE,YAAY,IAAI,gBAAgB;AAChC,yEAAyE;AACzE,YAAY,IAAI,gBAAgB;AAChC,yEAAyE;AACzE,YAAY,IAAI,gBAAgB,GACjC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"} \ No newline at end of file diff --git a/packages/expo/src/Expo.ts b/packages/expo/src/Expo.ts index ddd819acc44b81..723d4f213e5591 100644 --- a/packages/expo/src/Expo.ts +++ b/packages/expo/src/Expo.ts @@ -34,4 +34,13 @@ export type { SharedObject as SharedObjectType, } from 'expo-modules-core/types'; +export { + PermissionStatus, + type PermissionExpiration, + type PermissionResponse, + type PermissionHookOptions, +} from 'expo-modules-core'; + +export { createPermissionHook } from 'expo-modules-core'; + export { useEvent, useEventListener } from './hooks/useEvent'; From e519d0949eb0c70aac46d944e2693b8e31d273b4 Mon Sep 17 00:00:00 2001 From: Wiktor Smaga Date: Tue, 12 May 2026 16:33:38 +0200 Subject: [PATCH 11/11] [expo] Update permission types and hooks imports to expo (#45565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Follow-up to the #45564, updating the imports from expo-modules-core to expo. # How - Updated the imports in packages that use permissions-related types and functions from expo-modules-core. # Test Plan BareExpo ✅ --- packages/expo-audio/CHANGELOG.md | 2 ++ .../expo-audio/build/AudioModule.types.d.ts | 2 +- .../expo-audio/build/AudioModule.types.d.ts.map | 2 +- .../expo-audio/build/AudioModule.types.js.map | 2 +- packages/expo-audio/build/AudioModule.web.d.ts | 2 +- .../expo-audio/build/AudioModule.web.d.ts.map | 2 +- packages/expo-audio/build/AudioModule.web.js | 2 +- .../expo-audio/build/AudioModule.web.js.map | 2 +- packages/expo-audio/build/ExpoAudio.d.ts | 2 +- packages/expo-audio/build/ExpoAudio.d.ts.map | 2 +- packages/expo-audio/build/ExpoAudio.js.map | 2 +- packages/expo-audio/build/ExpoAudio.web.d.ts | 2 +- .../expo-audio/build/ExpoAudio.web.d.ts.map | 2 +- packages/expo-audio/build/ExpoAudio.web.js.map | 2 +- packages/expo-audio/build/index.d.ts | 2 +- packages/expo-audio/build/index.d.ts.map | 2 +- packages/expo-audio/build/index.js | 2 +- packages/expo-audio/build/index.js.map | 2 +- packages/expo-audio/src/AudioModule.types.ts | 2 +- packages/expo-audio/src/AudioModule.web.ts | 3 +-- packages/expo-audio/src/ExpoAudio.ts | 3 +-- packages/expo-audio/src/ExpoAudio.web.ts | 3 +-- packages/expo-audio/src/index.ts | 6 +----- packages/expo-brightness/CHANGELOG.md | 2 ++ packages/expo-brightness/build/Brightness.d.ts | 5 +++-- .../expo-brightness/build/Brightness.d.ts.map | 2 +- packages/expo-brightness/build/Brightness.js | 5 +++-- .../expo-brightness/build/Brightness.js.map | 2 +- .../build/ExpoBrightness.web.d.ts | 2 +- .../build/ExpoBrightness.web.d.ts.map | 2 +- .../expo-brightness/build/ExpoBrightness.web.js | 2 +- .../build/ExpoBrightness.web.js.map | 2 +- packages/expo-brightness/src/Brightness.ts | 10 +++------- .../expo-brightness/src/ExpoBrightness.web.ts | 3 +-- packages/expo-calendar/CHANGELOG.md | 2 ++ packages/expo-calendar/build/Calendar.d.ts | 4 ++-- packages/expo-calendar/build/Calendar.d.ts.map | 2 +- packages/expo-calendar/build/Calendar.js | 3 ++- packages/expo-calendar/build/Calendar.js.map | 2 +- .../expo-calendar/build/ExpoCalendar.web.d.ts | 2 +- .../build/ExpoCalendar.web.d.ts.map | 2 +- .../expo-calendar/build/ExpoCalendar.web.js | 2 +- .../expo-calendar/build/ExpoCalendar.web.js.map | 2 +- .../expo-calendar/build/next/Calendar.d.ts.map | 2 +- packages/expo-calendar/build/next/Calendar.js | 3 ++- .../expo-calendar/build/next/Calendar.js.map | 2 +- .../expo-calendar/build/next/ExpoCalendar.d.ts | 2 +- .../build/next/ExpoCalendar.d.ts.map | 2 +- .../build/next/ExpoCalendar.js.map | 2 +- .../build/next/ExpoCalendar.web.d.ts | 2 +- .../build/next/ExpoCalendar.web.d.ts.map | 2 +- .../build/next/ExpoCalendar.web.js | 2 +- .../build/next/ExpoCalendar.web.js.map | 2 +- .../build/next/ExpoGoCalendarNextStub.d.ts | 2 +- .../build/next/ExpoGoCalendarNextStub.d.ts.map | 2 +- .../build/next/ExpoGoCalendarNextStub.js.map | 2 +- packages/expo-calendar/src/Calendar.ts | 6 +++--- packages/expo-calendar/src/ExpoCalendar.web.ts | 3 +-- packages/expo-calendar/src/next/Calendar.ts | 3 ++- packages/expo-calendar/src/next/ExpoCalendar.ts | 3 +-- .../expo-calendar/src/next/ExpoCalendar.web.ts | 3 +-- .../src/next/ExpoGoCalendarNextStub.ts | 2 +- packages/expo-camera/CHANGELOG.md | 2 ++ packages/expo-camera/build/Camera.types.d.ts | 6 ++++-- .../expo-camera/build/Camera.types.d.ts.map | 2 +- packages/expo-camera/build/Camera.types.js | 4 +++- packages/expo-camera/build/Camera.types.js.map | 2 +- packages/expo-camera/build/index.d.ts | 2 +- packages/expo-camera/build/index.d.ts.map | 2 +- packages/expo-camera/build/index.js | 2 +- packages/expo-camera/build/index.js.map | 2 +- packages/expo-camera/src/Camera.types.ts | 7 ++++--- packages/expo-camera/src/index.ts | 3 +-- packages/expo-cellular/CHANGELOG.md | 2 ++ packages/expo-cellular/build/Cellular.d.ts.map | 2 +- packages/expo-cellular/build/Cellular.js | 3 ++- packages/expo-cellular/build/Cellular.js.map | 2 +- .../expo-cellular/build/Cellular.types.d.ts | 2 +- .../expo-cellular/build/Cellular.types.d.ts.map | 2 +- packages/expo-cellular/build/Cellular.types.js | 2 +- .../expo-cellular/build/Cellular.types.js.map | 2 +- packages/expo-cellular/src/Cellular.ts | 8 ++------ packages/expo-cellular/src/Cellular.types.ts | 2 +- packages/expo-contacts/CHANGELOG.md | 2 ++ packages/expo-contacts/build/Contacts.d.ts | 5 +++-- packages/expo-contacts/build/Contacts.d.ts.map | 2 +- .../expo-contacts/build/ExpoContacts.web.d.ts | 2 +- .../build/ExpoContacts.web.d.ts.map | 2 +- .../build/next/types/Permissions.d.ts | 2 +- .../build/next/types/Permissions.d.ts.map | 2 +- packages/expo-contacts/src/Contacts.ts | 14 +++----------- packages/expo-contacts/src/ExpoContacts.web.ts | 3 +-- .../expo-contacts/src/next/types/Permissions.ts | 2 +- packages/expo-image-picker/CHANGELOG.md | 2 ++ .../build/ExponentImagePicker.web.d.ts | 2 +- .../build/ExponentImagePicker.web.d.ts.map | 2 +- .../build/ExponentImagePicker.web.js | 3 ++- .../build/ExponentImagePicker.web.js.map | 2 +- .../expo-image-picker/build/ImagePicker.d.ts | 3 +-- .../build/ImagePicker.d.ts.map | 2 +- packages/expo-image-picker/build/ImagePicker.js | 3 ++- .../expo-image-picker/build/ImagePicker.js.map | 2 +- .../build/ImagePicker.types.d.ts | 2 +- .../build/ImagePicker.types.d.ts.map | 2 +- .../build/ImagePicker.types.js.map | 2 +- .../src/ExponentImagePicker.web.ts | 4 ++-- packages/expo-image-picker/src/ImagePicker.ts | 15 ++++++--------- .../expo-image-picker/src/ImagePicker.types.ts | 2 +- packages/expo-location/CHANGELOG.md | 2 ++ .../expo-location/build/ExpoLocation.web.d.ts | 2 +- .../build/ExpoLocation.web.d.ts.map | 2 +- .../expo-location/build/ExpoLocation.web.js | 3 ++- .../expo-location/build/ExpoLocation.web.js.map | 2 +- packages/expo-location/build/Location.d.ts | 2 +- packages/expo-location/build/Location.d.ts.map | 2 +- packages/expo-location/build/Location.js | 4 ++-- packages/expo-location/build/Location.js.map | 2 +- .../expo-location/build/Location.types.d.ts | 2 +- .../expo-location/build/Location.types.d.ts.map | 2 +- .../expo-location/build/Location.types.js.map | 2 +- packages/expo-location/build/index.d.ts | 2 +- packages/expo-location/build/index.d.ts.map | 2 +- packages/expo-location/build/index.js | 2 +- packages/expo-location/build/index.js.map | 2 +- packages/expo-location/src/ExpoLocation.web.ts | 4 ++-- packages/expo-location/src/Location.ts | 5 ++--- packages/expo-location/src/Location.types.ts | 2 +- packages/expo-location/src/index.ts | 6 +----- packages/expo-maps/CHANGELOG.md | 2 ++ packages/expo-maps/build/index.js | 2 +- packages/expo-maps/build/index.js.map | 2 +- packages/expo-maps/build/shared.types.d.ts | 2 +- packages/expo-maps/build/shared.types.d.ts.map | 2 +- packages/expo-maps/build/shared.types.js.map | 2 +- packages/expo-maps/src/index.ts | 2 +- packages/expo-maps/src/shared.types.ts | 2 +- packages/expo-media-library/CHANGELOG.md | 2 ++ .../build/ExpoMediaLibrary.web.d.ts | 2 +- .../build/ExpoMediaLibrary.web.d.ts.map | 2 +- .../build/ExpoMediaLibrary.web.js | 2 +- .../build/ExpoMediaLibrary.web.js.map | 2 +- .../expo-media-library/build/MediaLibrary.d.ts | 6 ++++-- .../build/MediaLibrary.d.ts.map | 2 +- .../expo-media-library/build/MediaLibrary.js | 6 ++++-- .../build/MediaLibrary.js.map | 2 +- .../build/next/ExpoMediaLibraryNext.d.ts | 2 +- .../build/next/ExpoMediaLibraryNext.d.ts.map | 2 +- .../build/next/ExpoMediaLibraryNext.js.map | 2 +- .../expo-media-library/build/next/index.d.ts | 3 ++- .../build/next/index.d.ts.map | 2 +- packages/expo-media-library/build/next/index.js | 3 ++- .../expo-media-library/build/next/index.js.map | 2 +- .../src/ExpoMediaLibrary.web.ts | 3 +-- packages/expo-media-library/src/MediaLibrary.ts | 12 ++++-------- .../src/next/ExpoMediaLibraryNext.ts | 2 +- packages/expo-media-library/src/next/index.ts | 8 ++------ packages/expo-notifications/CHANGELOG.md | 2 ++ .../build/NotificationPermissions.types.d.ts | 2 +- .../NotificationPermissions.types.d.ts.map | 2 +- .../build/NotificationPermissions.types.js.map | 2 +- .../NotificationPermissionsModule.d.ts.map | 2 +- .../build/NotificationPermissionsModule.js | 3 ++- .../build/NotificationPermissionsModule.js.map | 2 +- .../build/Notifications.types.d.ts | 3 ++- .../build/Notifications.types.d.ts.map | 2 +- .../build/Notifications.types.js | 3 ++- .../build/Notifications.types.js.map | 2 +- .../src/NotificationPermissions.types.ts | 2 +- .../src/NotificationPermissionsModule.ts | 3 ++- .../src/Notifications.types.ts | 8 ++------ packages/expo-screen-capture/CHANGELOG.md | 2 ++ .../build/ScreenCapture.d.ts | 6 ++++-- .../build/ScreenCapture.d.ts.map | 2 +- .../expo-screen-capture/build/ScreenCapture.js | 6 ++++-- .../build/ScreenCapture.js.map | 2 +- .../expo-screen-capture/src/ScreenCapture.ts | 17 ++++------------- packages/expo-sensors/CHANGELOG.md | 2 ++ packages/expo-sensors/build/DeviceSensor.d.ts | 3 ++- .../expo-sensors/build/DeviceSensor.d.ts.map | 2 +- packages/expo-sensors/build/DeviceSensor.js | 3 ++- packages/expo-sensors/build/DeviceSensor.js.map | 2 +- packages/expo-sensors/build/Pedometer.d.ts | 6 ++++-- packages/expo-sensors/build/Pedometer.d.ts.map | 2 +- packages/expo-sensors/build/Pedometer.js | 6 ++++-- packages/expo-sensors/build/Pedometer.js.map | 2 +- .../build/utils/isSensorEnabledAsync.web.d.ts | 2 +- .../utils/isSensorEnabledAsync.web.d.ts.map | 2 +- .../build/utils/isSensorEnabledAsync.web.js | 3 ++- .../build/utils/isSensorEnabledAsync.web.js.map | 2 +- packages/expo-sensors/src/DeviceSensor.ts | 9 ++------- packages/expo-sensors/src/Pedometer.ts | 16 ++++------------ .../src/utils/isSensorEnabledAsync.web.ts | 4 ++-- .../expo-tracking-transparency/CHANGELOG.md | 2 ++ .../build/TrackingTransparency.d.ts | 2 +- .../build/TrackingTransparency.d.ts.map | 2 +- .../build/TrackingTransparency.js | 3 ++- .../build/TrackingTransparency.js.map | 2 +- .../src/TrackingTransparency.ts | 6 +++--- 198 files changed, 293 insertions(+), 301 deletions(-) diff --git a/packages/expo-audio/CHANGELOG.md b/packages/expo-audio/CHANGELOG.md index 9aa465034bb9dc..ce99b86ffa9de0 100644 --- a/packages/expo-audio/CHANGELOG.md +++ b/packages/expo-audio/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- Updated permission type and permission status imports to be imported from `expo` instead of `expo-modules-core` ([#45565](https://github.com/expo/expo/pull/45565) by [@Wenszel](https://github.com/Wenszel)) + ## 56.0.4 — 2026-05-11 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-audio/build/AudioModule.types.d.ts b/packages/expo-audio/build/AudioModule.types.d.ts index b932c5d42be297..f3ae4ee4044d86 100644 --- a/packages/expo-audio/build/AudioModule.types.d.ts +++ b/packages/expo-audio/build/AudioModule.types.d.ts @@ -1,4 +1,4 @@ -import type { PermissionResponse } from 'expo-modules-core'; +import type { PermissionResponse } from 'expo'; import { NativeModule, SharedObject } from 'expo-modules-core'; import type { AudioMetadata, AudioMode, AudioPlaylistLoopMode, AudioPlaylistStatus, AudioSource, AudioSourceInfo, AudioStatus, PitchCorrectionQuality, RecorderState, RecordingInput, RecordingOptions, RecordingStartOptions, RecordingStatus } from './Audio.types'; import type { AudioLockScreenOptions } from './AudioConstants'; diff --git a/packages/expo-audio/build/AudioModule.types.d.ts.map b/packages/expo-audio/build/AudioModule.types.d.ts.map index 38fa88f11d003e..761680ff0f9d78 100644 --- a/packages/expo-audio/build/AudioModule.types.d.ts.map +++ b/packages/expo-audio/build/AudioModule.types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"AudioModule.types.d.ts","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"AudioModule.types.d.ts","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-audio/build/AudioModule.types.js.map b/packages/expo-audio/build/AudioModule.types.js.map index 6431dbac52666f..33554a408330c5 100644 --- a/packages/expo-audio/build/AudioModule.types.js.map +++ b/packages/expo-audio/build/AudioModule.types.js.map @@ -1 +1 @@ -{"version":3,"file":"AudioModule.types.js","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["import type { PermissionResponse } from 'expo-modules-core';\nimport { NativeModule, SharedObject } from 'expo-modules-core';\n\nimport type {\n AudioMetadata,\n AudioMode,\n AudioPlaylistLoopMode,\n AudioPlaylistStatus,\n AudioSource,\n AudioSourceInfo,\n AudioStatus,\n PitchCorrectionQuality,\n RecorderState,\n RecordingInput,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport type { AudioLockScreenOptions } from './AudioConstants';\nimport type { AudioStream } from './AudioStream.types';\n\n/**\n * @hidden\n */\nexport declare class NativeAudioModule extends NativeModule {\n setIsAudioActiveAsync(active: boolean): Promise;\n setAudioModeAsync(category: Partial): Promise;\n requestRecordingPermissionsAsync(): Promise;\n requestNotificationPermissionsAsync(): Promise;\n getRecordingPermissionsAsync(): Promise;\n preload(source: AudioSource, preferredForwardBufferDuration: number): Promise;\n clearPreloadedSource(source: AudioSource): Promise;\n clearAllPreloadedSources(): Promise;\n getPreloadedSources(): Promise;\n\n readonly AudioPlayer: typeof AudioPlayer;\n readonly AudioRecorder: typeof AudioRecorder;\n readonly AudioPlaylist: typeof AudioPlaylist;\n readonly AudioStream: typeof AudioStream;\n}\n\nexport declare class AudioPlayer extends SharedObject {\n /**\n * Initializes a new audio player instance with the given source.\n * @hidden\n */\n constructor(\n source: AudioSource,\n updateInterval: number,\n keepAudioSessionActive: boolean,\n preferredForwardBufferDuration: number\n );\n\n /**\n * Unique identifier for the player object.\n */\n id: string;\n\n /**\n * Boolean value indicating whether the player is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the player is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the player is currently looping.\n */\n loop: boolean;\n\n /**\n * Boolean value indicating whether the player is currently paused.\n */\n paused: boolean;\n\n /**\n * Boolean value indicating whether the player is finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether audio sampling is supported on the platform.\n */\n isAudioSamplingSupported: boolean;\n\n /**\n * Boolean value indicating whether the player is buffering.\n */\n isBuffering: boolean;\n\n /**\n * The current position through the audio item in seconds.\n */\n currentTime: number;\n\n /**\n * The total duration of the audio in seconds.\n */\n duration: number;\n\n /**\n * The current volume of the audio.\n *\n * **Range:** `0.0` to `1.0`. For example, `0.0` is completely silent (0%), `0.5` is half volume (50%), and `1.0` is full volume (100%).\n *\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Mute the audio\n * player.volume = 0.0;\n *\n * // Set volume to 50%\n * player.volume = 0.5;\n *\n * // Set to full volume\n * player.volume = 1.0;\n * }\n * ```\n */\n volume: number;\n\n /**\n * The current playback rate of the audio. It accepts different values depending on the platform:\n * - **Android**: `0.1` to `2.0`\n * - **iOS**: `0.0` to `2.0`\n * - **Web**: Follows browser implementation\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Normal playback speed\n * player.playbackRate = 1.0;\n *\n * // Slow motion (half speed)\n * player.playbackRate = 0.5;\n *\n * // Fast playback (1.5x speed)\n * player.playbackRate = 1.5;\n *\n * // Maximum speed on mobile\n * player.playbackRate = 2.0;\n * }\n * ```\n */\n playbackRate: number;\n\n /**\n * A boolean describing if we are correcting the pitch for a changed rate.\n */\n shouldCorrectPitch: boolean;\n\n /**\n * The current status of the audio player.\n * @hidden\n */\n currentStatus: AudioStatus;\n\n /**\n * Start playing audio.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current audio source with a new one.\n */\n replace(source: AudioSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n * @param seconds The number of seconds to seek by.\n * @param toleranceMillisBefore The tolerance allowed before the requested seek time, in milliseconds. iOS only.\n * @param toleranceMillisAfter The tolerance allowed after the requested seek time, in milliseconds. iOS only.\n */\n seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise;\n\n /**\n * Sets the current playback rate of the audio.\n *\n * @param rate The playback rate of the audio. See [`playbackRate`](#playbackrate) property for detailed range information.\n * @param pitchCorrectionQuality The quality of the pitch correction.\n */\n setPlaybackRate(rate: number, pitchCorrectionQuality?: PitchCorrectionQuality): void;\n\n /**\n *\n * @hidden\n */\n setAudioSamplingEnabled(enabled: boolean): void;\n\n /**\n * Sets or removes this audio player as the active player for lock screen controls.\n * Only one player can control the lock screen at a time.\n *\n * > **Note:** For lock screen controls to work correctly, [`interruptionMode`](#interruptionmode) must be set to `doNotMix` using [`setAudioModeAsync`](#audiosetaudiomodeasyncmode).\n * > Without this, the OS might not associate lock screen controls with your player.\n *\n * @param active Whether this player should be active for lock screen controls.\n * @param metadata Optional metadata to display on the lock screen (title, artist, album, artwork).\n * @param options Optional configuration to configure the lock screen controls.\n */\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void;\n\n /**\n * Updates the metadata displayed on the lock screen for this player.\n * This method only has an effect if this player is currently active for lock screen controls.\n * @param metadata The metadata to display (title, artist, album, artwork).\n */\n updateLockScreenMetadata(metadata: AudioMetadata): void;\n\n /**\n * Removes this player from lock screen controls if it's currently active.\n * This will clear the lock screen's now playing info.\n */\n clearLockScreenControls(): void;\n\n /**\n * Remove the player from memory to free up resources.\n */\n remove(): void;\n}\n\n/**\n * Represents a single audio sample containing waveform data from all audio channels.\n *\n * Audio samples are provided in real-time when audio sampling is enabled on an `AudioPlayer`.\n * Each sample contains the raw PCM audio data for all channels (mono has 1 channel, stereo has 2).\n * This data can be used for audio visualization, analysis, or processing.\n */\nexport type AudioSample = {\n /** Array of audio channels, each containing PCM frame data. Stereo audio will have 2 channels (left/right). */\n channels: AudioSampleChannel[];\n /** Timestamp of this sample relative to the audio track's timeline, in seconds. */\n timestamp: number;\n};\n\n/**\n * Represents audio data for a single channel (for example, left or right in stereo audio).\n *\n * Contains the raw PCM (Pulse Code Modulation) audio frames for this channel.\n * Frame values are normalized between -1.0 and 1.0, where 0 represents silence.\n */\nexport type AudioSampleChannel = {\n /** Array of PCM audio frame values, each between -1.0 and 1.0. */\n frames: number[];\n};\n\n/**\n * Event types that an `AudioPlayer` can emit.\n *\n * These events allow you to listen for changes in playback state and receive real-time audio data.\n * Use `player.addListener()` to subscribe to these events.\n */\nexport type AudioEvents = {\n /** Fired when the player's status changes (play/pause/seek/load and so on.). */\n playbackStatusUpdate(status: AudioStatus): void;\n /** Fired when audio sampling is enabled and new sample data is available. */\n audioSampleUpdate(data: AudioSample): void;\n};\n\nexport declare class AudioRecorder extends SharedObject {\n /**\n * Initializes a new audio recorder instance with the given source.\n * @hidden\n */\n constructor(options: Partial);\n\n /**\n * Unique identifier for the recorder object.\n */\n id: string;\n\n /**\n * The current length of the recording, in seconds.\n */\n currentTime: number;\n\n /**\n * Boolean value indicating whether the recording is in progress.\n */\n isRecording: boolean;\n\n /**\n * The uri of the recording.\n */\n uri: string | null;\n\n /**\n * Starts the recording.\n * @param options Optional recording configuration options.\n */\n record(options?: RecordingStartOptions): void;\n\n /**\n * Stop the recording.\n */\n stop(): Promise;\n\n /**\n * Pause the recording.\n */\n pause(): void;\n\n /**\n * Returns a list of available recording inputs. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with an array of `RecordingInput` objects.\n */\n getAvailableInputs(): RecordingInput[];\n\n /**\n * Returns the currently-selected recording input. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with a `RecordingInput` object.\n */\n getCurrentInput(): Promise;\n\n /**\n * Sets the current recording input.\n * @param inputUid The uid of a `RecordingInput`.\n * @return A `Promise` that is resolved if successful or rejected if not.\n */\n setInput(inputUid: string): void;\n\n /**\n * Status of the current recording.\n */\n getStatus(): RecorderState;\n\n /**\n * Starts the recording at the given time.\n * @param seconds The time in seconds to start recording at.\n * @deprecated Use `record({ atTime: seconds })` instead.\n */\n startRecordingAtTime(seconds: number): void;\n\n /**\n * Prepares the recording for recording.\n */\n prepareToRecordAsync(options?: Partial): Promise;\n\n /**\n * Stops the recording once the specified time has elapsed.\n * @param seconds The time in seconds to stop recording at.\n * @deprecated Use `record({ forDuration: seconds })` instead.\n */\n recordForDuration(seconds: number): void;\n}\n\n/**\n * Event types that an `AudioRecorder` can emit.\n *\n * These events are used internally by `expo-audio` hooks to provide real-time status updates.\n * Use `useAudioRecorderState()` or the `statusListener` parameter in `useAudioRecorder()` instead of subscribing directly.\n */\nexport type RecordingEvents = {\n /** Fired when the recorder's status changes (start/stop/pause/error, and so on). */\n recordingStatusUpdate: (status: RecordingStatus) => void;\n};\n\n/**\n * Event types that an `AudioPlaylist` can emit.\n *\n * These events allow you to listen for changes in playlist playback state.\n * Use `playlist.addListener()` to subscribe to these events.\n */\nexport type AudioPlaylistEvents = {\n /** Fired when the playlist's status changes (play/pause/seek/load/track change). */\n playlistStatusUpdate(status: AudioPlaylistStatus): void;\n /** Fired when the current track changes (next/previous/skip). */\n trackChanged(data: { previousIndex: number; currentIndex: number }): void;\n};\n\nexport declare class AudioPlaylist extends SharedObject {\n /**\n * Initializes a new audio playlist instance.\n * @hidden\n */\n constructor(sources: AudioSource[], updateInterval: number, loop: AudioPlaylistLoopMode);\n\n /**\n * Unique identifier for the playlist instance.\n */\n id: string;\n\n /**\n * Index of the currently playing track in the playlist.\n */\n readonly currentIndex: number;\n\n /**\n * Total number of tracks in the playlist.\n */\n readonly trackCount: number;\n\n /**\n * The audio sources currently in the playlist.\n */\n readonly sources: AudioSourceInfo[];\n\n /**\n * Boolean value indicating whether the playlist is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the playlist is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the current track has finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether the playlist is buffering.\n */\n isBuffering: boolean;\n\n /**\n * Current playback position in seconds.\n */\n currentTime: number;\n\n /**\n * Duration of the current track in seconds.\n */\n duration: number;\n\n /**\n * Current volume (0.0 to 1.0).\n */\n volume: number;\n\n /**\n * Current playback rate (1.0 = normal speed).\n */\n playbackRate: number;\n\n /**\n * Current loop mode.\n */\n loop: AudioPlaylistLoopMode;\n\n /**\n * The current status of the audio playlist.\n * @hidden\n */\n currentStatus: AudioPlaylistStatus;\n\n /**\n * Start playing the current track in the playlist.\n */\n play(): void;\n\n /**\n * Pause playback.\n */\n pause(): void;\n\n /**\n * Skip to the next track in the playlist.\n * If at the end of the playlist and loop mode is 'all', wraps to the first track.\n * If loop mode is 'none' and at the end, does nothing.\n */\n next(): void;\n\n /**\n * Skip to the previous track in the playlist.\n * If at the beginning of the playlist and loop mode is 'all', wraps to the last track.\n * If loop mode is 'none' and at the beginning, does nothing.\n */\n previous(): void;\n\n /**\n * Skip to a specific track in the playlist by index.\n * @param index The index of the track to skip to.\n */\n skipTo(index: number): void;\n\n /**\n * Seeks the playback to a specific position in seconds.\n * @param seconds The position to seek to.\n */\n seekTo(seconds: number): Promise;\n\n /**\n * Add a track to the end of the playlist.\n * @param source The audio source to add.\n */\n add(source: AudioSource): void;\n\n /**\n * Insert a track at a specific position in the playlist.\n * @param source The audio source to insert.\n * @param index The position to insert at.\n */\n insert(source: AudioSource, index: number): void;\n\n /**\n * Remove a track from the playlist by index.\n * @param index The index of the track to remove.\n */\n remove(index: number): void;\n\n /**\n * Clear all tracks from the playlist.\n */\n clear(): void;\n\n /**\n * Destroy the playlist and free up resources.\n */\n destroy(): void;\n}\n"]} \ No newline at end of file +{"version":3,"file":"AudioModule.types.js","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["import type { PermissionResponse } from 'expo';\nimport { NativeModule, SharedObject } from 'expo-modules-core';\n\nimport type {\n AudioMetadata,\n AudioMode,\n AudioPlaylistLoopMode,\n AudioPlaylistStatus,\n AudioSource,\n AudioSourceInfo,\n AudioStatus,\n PitchCorrectionQuality,\n RecorderState,\n RecordingInput,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport type { AudioLockScreenOptions } from './AudioConstants';\nimport type { AudioStream } from './AudioStream.types';\n\n/**\n * @hidden\n */\nexport declare class NativeAudioModule extends NativeModule {\n setIsAudioActiveAsync(active: boolean): Promise;\n setAudioModeAsync(category: Partial): Promise;\n requestRecordingPermissionsAsync(): Promise;\n requestNotificationPermissionsAsync(): Promise;\n getRecordingPermissionsAsync(): Promise;\n preload(source: AudioSource, preferredForwardBufferDuration: number): Promise;\n clearPreloadedSource(source: AudioSource): Promise;\n clearAllPreloadedSources(): Promise;\n getPreloadedSources(): Promise;\n\n readonly AudioPlayer: typeof AudioPlayer;\n readonly AudioRecorder: typeof AudioRecorder;\n readonly AudioPlaylist: typeof AudioPlaylist;\n readonly AudioStream: typeof AudioStream;\n}\n\nexport declare class AudioPlayer extends SharedObject {\n /**\n * Initializes a new audio player instance with the given source.\n * @hidden\n */\n constructor(\n source: AudioSource,\n updateInterval: number,\n keepAudioSessionActive: boolean,\n preferredForwardBufferDuration: number\n );\n\n /**\n * Unique identifier for the player object.\n */\n id: string;\n\n /**\n * Boolean value indicating whether the player is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the player is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the player is currently looping.\n */\n loop: boolean;\n\n /**\n * Boolean value indicating whether the player is currently paused.\n */\n paused: boolean;\n\n /**\n * Boolean value indicating whether the player is finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether audio sampling is supported on the platform.\n */\n isAudioSamplingSupported: boolean;\n\n /**\n * Boolean value indicating whether the player is buffering.\n */\n isBuffering: boolean;\n\n /**\n * The current position through the audio item in seconds.\n */\n currentTime: number;\n\n /**\n * The total duration of the audio in seconds.\n */\n duration: number;\n\n /**\n * The current volume of the audio.\n *\n * **Range:** `0.0` to `1.0`. For example, `0.0` is completely silent (0%), `0.5` is half volume (50%), and `1.0` is full volume (100%).\n *\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Mute the audio\n * player.volume = 0.0;\n *\n * // Set volume to 50%\n * player.volume = 0.5;\n *\n * // Set to full volume\n * player.volume = 1.0;\n * }\n * ```\n */\n volume: number;\n\n /**\n * The current playback rate of the audio. It accepts different values depending on the platform:\n * - **Android**: `0.1` to `2.0`\n * - **iOS**: `0.0` to `2.0`\n * - **Web**: Follows browser implementation\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Normal playback speed\n * player.playbackRate = 1.0;\n *\n * // Slow motion (half speed)\n * player.playbackRate = 0.5;\n *\n * // Fast playback (1.5x speed)\n * player.playbackRate = 1.5;\n *\n * // Maximum speed on mobile\n * player.playbackRate = 2.0;\n * }\n * ```\n */\n playbackRate: number;\n\n /**\n * A boolean describing if we are correcting the pitch for a changed rate.\n */\n shouldCorrectPitch: boolean;\n\n /**\n * The current status of the audio player.\n * @hidden\n */\n currentStatus: AudioStatus;\n\n /**\n * Start playing audio.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current audio source with a new one.\n */\n replace(source: AudioSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n * @param seconds The number of seconds to seek by.\n * @param toleranceMillisBefore The tolerance allowed before the requested seek time, in milliseconds. iOS only.\n * @param toleranceMillisAfter The tolerance allowed after the requested seek time, in milliseconds. iOS only.\n */\n seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise;\n\n /**\n * Sets the current playback rate of the audio.\n *\n * @param rate The playback rate of the audio. See [`playbackRate`](#playbackrate) property for detailed range information.\n * @param pitchCorrectionQuality The quality of the pitch correction.\n */\n setPlaybackRate(rate: number, pitchCorrectionQuality?: PitchCorrectionQuality): void;\n\n /**\n *\n * @hidden\n */\n setAudioSamplingEnabled(enabled: boolean): void;\n\n /**\n * Sets or removes this audio player as the active player for lock screen controls.\n * Only one player can control the lock screen at a time.\n *\n * > **Note:** For lock screen controls to work correctly, [`interruptionMode`](#interruptionmode) must be set to `doNotMix` using [`setAudioModeAsync`](#audiosetaudiomodeasyncmode).\n * > Without this, the OS might not associate lock screen controls with your player.\n *\n * @param active Whether this player should be active for lock screen controls.\n * @param metadata Optional metadata to display on the lock screen (title, artist, album, artwork).\n * @param options Optional configuration to configure the lock screen controls.\n */\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void;\n\n /**\n * Updates the metadata displayed on the lock screen for this player.\n * This method only has an effect if this player is currently active for lock screen controls.\n * @param metadata The metadata to display (title, artist, album, artwork).\n */\n updateLockScreenMetadata(metadata: AudioMetadata): void;\n\n /**\n * Removes this player from lock screen controls if it's currently active.\n * This will clear the lock screen's now playing info.\n */\n clearLockScreenControls(): void;\n\n /**\n * Remove the player from memory to free up resources.\n */\n remove(): void;\n}\n\n/**\n * Represents a single audio sample containing waveform data from all audio channels.\n *\n * Audio samples are provided in real-time when audio sampling is enabled on an `AudioPlayer`.\n * Each sample contains the raw PCM audio data for all channels (mono has 1 channel, stereo has 2).\n * This data can be used for audio visualization, analysis, or processing.\n */\nexport type AudioSample = {\n /** Array of audio channels, each containing PCM frame data. Stereo audio will have 2 channels (left/right). */\n channels: AudioSampleChannel[];\n /** Timestamp of this sample relative to the audio track's timeline, in seconds. */\n timestamp: number;\n};\n\n/**\n * Represents audio data for a single channel (for example, left or right in stereo audio).\n *\n * Contains the raw PCM (Pulse Code Modulation) audio frames for this channel.\n * Frame values are normalized between -1.0 and 1.0, where 0 represents silence.\n */\nexport type AudioSampleChannel = {\n /** Array of PCM audio frame values, each between -1.0 and 1.0. */\n frames: number[];\n};\n\n/**\n * Event types that an `AudioPlayer` can emit.\n *\n * These events allow you to listen for changes in playback state and receive real-time audio data.\n * Use `player.addListener()` to subscribe to these events.\n */\nexport type AudioEvents = {\n /** Fired when the player's status changes (play/pause/seek/load and so on.). */\n playbackStatusUpdate(status: AudioStatus): void;\n /** Fired when audio sampling is enabled and new sample data is available. */\n audioSampleUpdate(data: AudioSample): void;\n};\n\nexport declare class AudioRecorder extends SharedObject {\n /**\n * Initializes a new audio recorder instance with the given source.\n * @hidden\n */\n constructor(options: Partial);\n\n /**\n * Unique identifier for the recorder object.\n */\n id: string;\n\n /**\n * The current length of the recording, in seconds.\n */\n currentTime: number;\n\n /**\n * Boolean value indicating whether the recording is in progress.\n */\n isRecording: boolean;\n\n /**\n * The uri of the recording.\n */\n uri: string | null;\n\n /**\n * Starts the recording.\n * @param options Optional recording configuration options.\n */\n record(options?: RecordingStartOptions): void;\n\n /**\n * Stop the recording.\n */\n stop(): Promise;\n\n /**\n * Pause the recording.\n */\n pause(): void;\n\n /**\n * Returns a list of available recording inputs. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with an array of `RecordingInput` objects.\n */\n getAvailableInputs(): RecordingInput[];\n\n /**\n * Returns the currently-selected recording input. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with a `RecordingInput` object.\n */\n getCurrentInput(): Promise;\n\n /**\n * Sets the current recording input.\n * @param inputUid The uid of a `RecordingInput`.\n * @return A `Promise` that is resolved if successful or rejected if not.\n */\n setInput(inputUid: string): void;\n\n /**\n * Status of the current recording.\n */\n getStatus(): RecorderState;\n\n /**\n * Starts the recording at the given time.\n * @param seconds The time in seconds to start recording at.\n * @deprecated Use `record({ atTime: seconds })` instead.\n */\n startRecordingAtTime(seconds: number): void;\n\n /**\n * Prepares the recording for recording.\n */\n prepareToRecordAsync(options?: Partial): Promise;\n\n /**\n * Stops the recording once the specified time has elapsed.\n * @param seconds The time in seconds to stop recording at.\n * @deprecated Use `record({ forDuration: seconds })` instead.\n */\n recordForDuration(seconds: number): void;\n}\n\n/**\n * Event types that an `AudioRecorder` can emit.\n *\n * These events are used internally by `expo-audio` hooks to provide real-time status updates.\n * Use `useAudioRecorderState()` or the `statusListener` parameter in `useAudioRecorder()` instead of subscribing directly.\n */\nexport type RecordingEvents = {\n /** Fired when the recorder's status changes (start/stop/pause/error, and so on). */\n recordingStatusUpdate: (status: RecordingStatus) => void;\n};\n\n/**\n * Event types that an `AudioPlaylist` can emit.\n *\n * These events allow you to listen for changes in playlist playback state.\n * Use `playlist.addListener()` to subscribe to these events.\n */\nexport type AudioPlaylistEvents = {\n /** Fired when the playlist's status changes (play/pause/seek/load/track change). */\n playlistStatusUpdate(status: AudioPlaylistStatus): void;\n /** Fired when the current track changes (next/previous/skip). */\n trackChanged(data: { previousIndex: number; currentIndex: number }): void;\n};\n\nexport declare class AudioPlaylist extends SharedObject {\n /**\n * Initializes a new audio playlist instance.\n * @hidden\n */\n constructor(sources: AudioSource[], updateInterval: number, loop: AudioPlaylistLoopMode);\n\n /**\n * Unique identifier for the playlist instance.\n */\n id: string;\n\n /**\n * Index of the currently playing track in the playlist.\n */\n readonly currentIndex: number;\n\n /**\n * Total number of tracks in the playlist.\n */\n readonly trackCount: number;\n\n /**\n * The audio sources currently in the playlist.\n */\n readonly sources: AudioSourceInfo[];\n\n /**\n * Boolean value indicating whether the playlist is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the playlist is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the current track has finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether the playlist is buffering.\n */\n isBuffering: boolean;\n\n /**\n * Current playback position in seconds.\n */\n currentTime: number;\n\n /**\n * Duration of the current track in seconds.\n */\n duration: number;\n\n /**\n * Current volume (0.0 to 1.0).\n */\n volume: number;\n\n /**\n * Current playback rate (1.0 = normal speed).\n */\n playbackRate: number;\n\n /**\n * Current loop mode.\n */\n loop: AudioPlaylistLoopMode;\n\n /**\n * The current status of the audio playlist.\n * @hidden\n */\n currentStatus: AudioPlaylistStatus;\n\n /**\n * Start playing the current track in the playlist.\n */\n play(): void;\n\n /**\n * Pause playback.\n */\n pause(): void;\n\n /**\n * Skip to the next track in the playlist.\n * If at the end of the playlist and loop mode is 'all', wraps to the first track.\n * If loop mode is 'none' and at the end, does nothing.\n */\n next(): void;\n\n /**\n * Skip to the previous track in the playlist.\n * If at the beginning of the playlist and loop mode is 'all', wraps to the last track.\n * If loop mode is 'none' and at the beginning, does nothing.\n */\n previous(): void;\n\n /**\n * Skip to a specific track in the playlist by index.\n * @param index The index of the track to skip to.\n */\n skipTo(index: number): void;\n\n /**\n * Seeks the playback to a specific position in seconds.\n * @param seconds The position to seek to.\n */\n seekTo(seconds: number): Promise;\n\n /**\n * Add a track to the end of the playlist.\n * @param source The audio source to add.\n */\n add(source: AudioSource): void;\n\n /**\n * Insert a track at a specific position in the playlist.\n * @param source The audio source to insert.\n * @param index The position to insert at.\n */\n insert(source: AudioSource, index: number): void;\n\n /**\n * Remove a track from the playlist by index.\n * @param index The index of the track to remove.\n */\n remove(index: number): void;\n\n /**\n * Clear all tracks from the playlist.\n */\n clear(): void;\n\n /**\n * Destroy the playlist and free up resources.\n */\n destroy(): void;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-audio/build/AudioModule.web.d.ts b/packages/expo-audio/build/AudioModule.web.d.ts index 168d4e49a88f2c..0cc7fbc6775d8a 100644 --- a/packages/expo-audio/build/AudioModule.web.d.ts +++ b/packages/expo-audio/build/AudioModule.web.d.ts @@ -1,4 +1,4 @@ -import type { PermissionResponse } from 'expo-modules-core'; +import { type PermissionResponse } from 'expo'; import type { AudioMode, AudioSource } from './Audio.types'; export { AudioPlayerWeb } from './AudioPlayer.web'; export { AudioPlaylistWeb } from './AudioPlaylist.web'; diff --git a/packages/expo-audio/build/AudioModule.web.d.ts.map b/packages/expo-audio/build/AudioModule.web.d.ts.map index a10d93b6930672..a95680c68c461c 100644 --- a/packages/expo-audio/build/AudioModule.web.d.ts.map +++ b/packages/expo-audio/build/AudioModule.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"AudioModule.web.d.ts","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,IAAI,aAAa,SAAO,CAAC;AAuBhC,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,SAAS,iBAAI;AAC3D,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,iBAS1D;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAS9D;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAoBhF;AAED,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkBpF"} \ No newline at end of file +{"version":3,"file":"AudioModule.web.d.ts","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAoB,MAAM,MAAM,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,IAAI,aAAa,SAAO,CAAC;AAuBhC,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,SAAS,iBAAI;AAC3D,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,iBAS1D;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAS9D;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAoBhF;AAED,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkBpF"} \ No newline at end of file diff --git a/packages/expo-audio/build/AudioModule.web.js b/packages/expo-audio/build/AudioModule.web.js index d8252d46f57867..01c49937d85e3a 100644 --- a/packages/expo-audio/build/AudioModule.web.js +++ b/packages/expo-audio/build/AudioModule.web.js @@ -1,4 +1,4 @@ -import { PermissionStatus } from 'expo-modules-core'; +import { PermissionStatus } from 'expo'; import { activePlayers } from './AudioPlayer.web'; import { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web'; export { AudioPlayerWeb } from './AudioPlayer.web'; diff --git a/packages/expo-audio/build/AudioModule.web.js.map b/packages/expo-audio/build/AudioModule.web.js.map index f38dcf9ef399b1..74bb8e8b8c9965 100644 --- a/packages/expo-audio/build/AudioModule.web.js.map +++ b/packages/expo-audio/build/AudioModule.web.js.map @@ -1 +1 @@ -{"version":3,"file":"AudioModule.web.js","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PermissionResponse } from 'expo-modules-core';\nimport { PermissionStatus } from 'expo-modules-core';\n\nimport type { AudioMode, AudioSource } from './Audio.types';\nimport { activePlayers } from './AudioPlayer.web';\nimport { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web';\n\nexport { AudioPlayerWeb } from './AudioPlayer.web';\nexport { AudioPlaylistWeb } from './AudioPlaylist.web';\nexport { AudioRecorderWeb } from './AudioRecorder.web';\n\nexport let isAudioActive = true;\n\nasync function getPermissionWithQueryAsync(\n name: PermissionNameWithAdditionalValues\n): Promise {\n if (!navigator || !navigator.permissions || !navigator.permissions.query) return null;\n\n try {\n const { state } = await navigator.permissions.query({ name });\n switch (state) {\n case 'granted':\n return PermissionStatus.GRANTED;\n case 'denied':\n return PermissionStatus.DENIED;\n default:\n return PermissionStatus.UNDETERMINED;\n }\n } catch {\n // Firefox - TypeError: 'microphone' (value of 'name' member of PermissionDescriptor) is not a valid value for enumeration PermissionName.\n return PermissionStatus.UNDETERMINED;\n }\n}\n\nexport async function setAudioModeAsync(mode: AudioMode) {}\nexport async function setIsAudioActiveAsync(active: boolean) {\n isAudioActive = active;\n if (!active) {\n for (const player of activePlayers) {\n if (player.playing) {\n player.pause();\n }\n }\n }\n}\n\nexport async function preloadAsync(source: AudioSource): Promise {\n const uri = getSourceUri(source);\n if (!uri || preloadCache.has(uri)) return;\n\n const headers =\n source && typeof source === 'object' && !Array.isArray(source) ? source.headers : undefined;\n\n const response = await fetch(uri, headers ? { headers } : undefined);\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n const audio = new Audio(blobUrl);\n audio.preload = 'auto';\n preloadCache.set(uri, { blobUrl, audio });\n}\n\nexport function preload(source: AudioSource): void {\n preloadAsync(source).catch(() => {});\n}\n\nexport function clearPreloadedSource(source: AudioSource): void {\n const uri = getSourceUri(source);\n if (!uri) return;\n\n const cached = preloadCache.get(uri);\n if (cached) {\n URL.revokeObjectURL(cached.blobUrl);\n preloadCache.delete(uri);\n }\n}\n\nexport function clearAllPreloadedSources(): void {\n for (const cached of preloadCache.values()) {\n URL.revokeObjectURL(cached.blobUrl);\n }\n preloadCache.clear();\n}\n\nexport function getPreloadedSources(): string[] {\n return Array.from(preloadCache.keys());\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n const maybeStatus = await getPermissionWithQueryAsync('microphone');\n switch (maybeStatus) {\n case PermissionStatus.GRANTED:\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n case PermissionStatus.DENIED:\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n default:\n return await requestRecordingPermissionsAsync();\n }\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n try {\n const stream = await getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n } catch {\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"AudioModule.web.js","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { type PermissionResponse, PermissionStatus } from 'expo';\n\nimport type { AudioMode, AudioSource } from './Audio.types';\nimport { activePlayers } from './AudioPlayer.web';\nimport { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web';\n\nexport { AudioPlayerWeb } from './AudioPlayer.web';\nexport { AudioPlaylistWeb } from './AudioPlaylist.web';\nexport { AudioRecorderWeb } from './AudioRecorder.web';\n\nexport let isAudioActive = true;\n\nasync function getPermissionWithQueryAsync(\n name: PermissionNameWithAdditionalValues\n): Promise {\n if (!navigator || !navigator.permissions || !navigator.permissions.query) return null;\n\n try {\n const { state } = await navigator.permissions.query({ name });\n switch (state) {\n case 'granted':\n return PermissionStatus.GRANTED;\n case 'denied':\n return PermissionStatus.DENIED;\n default:\n return PermissionStatus.UNDETERMINED;\n }\n } catch {\n // Firefox - TypeError: 'microphone' (value of 'name' member of PermissionDescriptor) is not a valid value for enumeration PermissionName.\n return PermissionStatus.UNDETERMINED;\n }\n}\n\nexport async function setAudioModeAsync(mode: AudioMode) {}\nexport async function setIsAudioActiveAsync(active: boolean) {\n isAudioActive = active;\n if (!active) {\n for (const player of activePlayers) {\n if (player.playing) {\n player.pause();\n }\n }\n }\n}\n\nexport async function preloadAsync(source: AudioSource): Promise {\n const uri = getSourceUri(source);\n if (!uri || preloadCache.has(uri)) return;\n\n const headers =\n source && typeof source === 'object' && !Array.isArray(source) ? source.headers : undefined;\n\n const response = await fetch(uri, headers ? { headers } : undefined);\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n const audio = new Audio(blobUrl);\n audio.preload = 'auto';\n preloadCache.set(uri, { blobUrl, audio });\n}\n\nexport function preload(source: AudioSource): void {\n preloadAsync(source).catch(() => {});\n}\n\nexport function clearPreloadedSource(source: AudioSource): void {\n const uri = getSourceUri(source);\n if (!uri) return;\n\n const cached = preloadCache.get(uri);\n if (cached) {\n URL.revokeObjectURL(cached.blobUrl);\n preloadCache.delete(uri);\n }\n}\n\nexport function clearAllPreloadedSources(): void {\n for (const cached of preloadCache.values()) {\n URL.revokeObjectURL(cached.blobUrl);\n }\n preloadCache.clear();\n}\n\nexport function getPreloadedSources(): string[] {\n return Array.from(preloadCache.keys());\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n const maybeStatus = await getPermissionWithQueryAsync('microphone');\n switch (maybeStatus) {\n case PermissionStatus.GRANTED:\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n case PermissionStatus.DENIED:\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n default:\n return await requestRecordingPermissionsAsync();\n }\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n try {\n const stream = await getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n } catch {\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n }\n}\n"]} \ No newline at end of file diff --git a/packages/expo-audio/build/ExpoAudio.d.ts b/packages/expo-audio/build/ExpoAudio.d.ts index d2f2198aba8d94..0e753738f8e429 100644 --- a/packages/expo-audio/build/ExpoAudio.d.ts +++ b/packages/expo-audio/build/ExpoAudio.d.ts @@ -1,4 +1,4 @@ -import type { PermissionResponse } from 'expo-modules-core'; +import { type PermissionResponse } from 'expo'; import type { AudioMode, AudioPlayerOptions, AudioPlaylistOptions, AudioPlaylistStatus, AudioSource, AudioStatus, PreloadOptions, RecordingOptions, RecordingStatus } from './Audio.types'; import AudioModule from './AudioModule'; import type { AudioPlayer, AudioPlaylist, AudioRecorder, AudioSample } from './AudioModule.types'; diff --git a/packages/expo-audio/build/ExpoAudio.d.ts.map b/packages/expo-audio/build/ExpoAudio.d.ts.map index 78633a9a03d407..850833183cd006 100644 --- a/packages/expo-audio/build/ExpoAudio.d.ts.map +++ b/packages/expo-audio/build/ExpoAudio.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ExpoAudio.d.ts","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ExpoAudio.d.ts","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/expo-audio/build/ExpoAudio.js.map b/packages/expo-audio/build/ExpoAudio.js.map index 39aac77cb32722..22ff33034b1512 100644 --- a/packages/expo-audio/build/ExpoAudio.js.map +++ b/packages/expo-audio/build/ExpoAudio.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpoAudio.js","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEvent } from 'expo';\nimport type { PermissionResponse } from 'expo-modules-core';\nimport { useReleasingSharedObject } from 'expo-modules-core';\nimport { useEffect, useMemo } from 'react';\nimport { Platform } from 'react-native';\n\nimport type {\n AudioMode,\n AudioPlayerOptions,\n AudioPlaylistOptions,\n AudioPlaylistStatus,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n PreloadOptions,\n RecordingOptions,\n RecordingStatus,\n} from './Audio.types';\nimport {\n AUDIO_SAMPLE_UPDATE,\n PLAYBACK_STATUS_UPDATE,\n PLAYLIST_STATUS_UPDATE,\n RECORDING_STATUS_UPDATE,\n} from './AudioEventKeys';\nimport AudioModule from './AudioModule';\nimport type { AudioPlayer, AudioPlaylist, AudioRecorder, AudioSample } from './AudioModule.types';\nimport { createRecordingOptions } from './utils/options';\nimport { resolveSource, resolveSources, resolveSourceWithDownload } from './utils/resolveSource';\n\n// TODO: Temporary solution until we develop a way of overriding prototypes that won't break the lazy loading of the module.\nconst replace = AudioModule.AudioPlayer.prototype.replace;\nAudioModule.AudioPlayer.prototype.replace = function (source: AudioSource) {\n return replace.call(this, resolveSource(source));\n};\n\nconst setPlaybackRate = AudioModule.AudioPlayer.prototype.setPlaybackRate;\nAudioModule.AudioPlayer.prototype.setPlaybackRate = function (\n rate: number,\n pitchCorrectionQuality?: PitchCorrectionQuality\n) {\n if (Platform.OS === 'android') {\n return setPlaybackRate.call(this, rate);\n } else {\n return setPlaybackRate.call(this, rate, pitchCorrectionQuality);\n }\n};\n\n// Audio recording prototypes should not be shimmed on tvOS, where they do not exist\nif (!Platform.isTV || Platform.OS !== 'ios') {\n const prepareToRecordAsync = AudioModule.AudioRecorder.prototype.prepareToRecordAsync;\n AudioModule.AudioRecorder.prototype.prepareToRecordAsync = function (options?: RecordingOptions) {\n const processedOptions = options ? createRecordingOptions(options) : undefined;\n return prepareToRecordAsync.call(this, processedOptions);\n };\n}\n\n/**\n * Creates an `AudioPlayer` instance that automatically releases when the component unmounts.\n *\n * This hook manages the player's lifecycle and ensures it's properly disposed when no longer needed.\n * The player will start loading the audio source immediately upon creation.\n *\n * @param source The audio source to load. Can be a local asset via `require()`, a remote URL, or null for no initial source.\n * @param options Audio player configuration options.\n * @returns An `AudioPlayer` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * function MyComponent() {\n * const player = useAudioPlayer(require('./sound.mp3'));\n *\n * return (\n *