catalyst-core 0.2.0-beta.3 → 0.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/index.jsx +8 -0
  6. package/dist/logger.js +74 -3
  7. package/dist/native/androidSetup.js +300 -8
  8. package/dist/native/bridge/WebBridge.js +481 -59
  9. package/dist/native/bridge/constants/NativeInterfaces.js +290 -45
  10. package/dist/native/bridge/errors.js +250 -26
  11. package/dist/native/bridge/hooks/index.js +125 -1
  12. package/dist/native/bridge/hooks/useCamera.js +347 -9
  13. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  14. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  15. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  16. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  17. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  18. package/dist/native/bridge/hooks/useIntent.js +95 -1
  19. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  20. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  21. package/dist/native/bridge/hooks/useNotification.js +318 -2
  22. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  23. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  24. package/dist/native/bridge/hooks/utils.js +27 -1
  25. package/dist/native/bridge/hooks.js +16 -1
  26. package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
  27. package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
  28. package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
  29. package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
  30. package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
  31. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
  32. package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
  33. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
  34. package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
  35. package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
  36. package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
  37. package/dist/native/bridge/perf/core/constants.js +103 -20
  38. package/dist/native/bridge/perf/core/measure.js +179 -30
  39. package/dist/native/bridge/perf/core/session.js +89 -23
  40. package/dist/native/bridge/perf/core/store.js +554 -1
  41. package/dist/native/bridge/perf/core/summary.js +174 -1
  42. package/dist/native/bridge/perf/core/traceExport.js +240 -1
  43. package/dist/native/bridge/perf/core/waterfalls.js +146 -1
  44. package/dist/native/bridge/perf/index.js +359 -38
  45. package/dist/native/bridge/safeArea.js +53 -5
  46. package/dist/native/bridge/standardizedHooks.js +595 -82
  47. package/dist/native/bridge/useBaseHook.js +274 -38
  48. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  49. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  50. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  51. package/dist/native/buildAndroid/assets.js +510 -13
  52. package/dist/native/buildAndroid/build.js +296 -19
  53. package/dist/native/buildAndroid/config.js +87 -1
  54. package/dist/native/buildAndroid/index.js +374 -1
  55. package/dist/native/buildAppAndroid.js +34 -2
  56. package/dist/native/buildAppIos.js +62 -2
  57. package/dist/native/buildIos/assets.js +609 -8
  58. package/dist/native/buildIos/build.js +639 -20
  59. package/dist/native/buildIos/config.js +294 -12
  60. package/dist/native/buildIos/index.js +343 -6
  61. package/dist/native/buildIos/plugins.js +343 -16
  62. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  63. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  64. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  65. package/dist/native/internalPluginUtils.js +232 -7
  66. package/dist/native/package.json +3 -0
  67. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  68. package/dist/native/pluginComposerAndroid.js +407 -23
  69. package/dist/native/pluginComposerIos.js +435 -11
  70. package/dist/native/renameAndroidProject.js +1094 -88
  71. package/dist/native/setupEmulatorIos.js +289 -22
  72. package/dist/native/setupServer.js +160 -19
  73. package/dist/native/terminalProgress.js +199 -11
  74. package/dist/native/utils.js +193 -15
  75. package/dist/offline/catalyst-sw.js +145 -1
  76. package/dist/offline/registerServiceWorker.js +33 -1
  77. package/dist/otel.js +375 -46
  78. package/dist/router/ClientRouter.js +14 -2
  79. package/dist/router/ServerRouter.js +9 -1
  80. package/dist/router.js +1 -1
  81. package/dist/scripts/build.js +112 -3
  82. package/dist/scripts/checkVersion.js +3 -1
  83. package/dist/scripts/cleanCache.js +30 -1
  84. package/dist/scripts/generateOfflineManifest.js +116 -1
  85. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  86. package/dist/scripts/plugins.js +309 -1
  87. package/dist/scripts/preServerInit.js +13 -0
  88. package/dist/scripts/scriptUtils.js +54 -3
  89. package/dist/scripts/serve.js +51 -4
  90. package/dist/scripts/start.js +47 -10
  91. package/dist/scripts/validator.js +149 -1
  92. package/dist/sentry.cjs +127 -0
  93. package/dist/sentry.js +125 -5
  94. package/dist/server/expressServer.js +250 -5
  95. package/dist/server/manifestCache.js +36 -0
  96. package/dist/server/renderer/ChunkExtractor.js +144 -0
  97. package/dist/server/renderer/document/Body.jsx +59 -0
  98. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  99. package/dist/server/renderer/document/Head.jsx +60 -0
  100. package/dist/server/renderer/document/index.jsx +2 -0
  101. package/dist/server/renderer/extract.js +149 -31
  102. package/dist/server/renderer/handler.jsx +433 -0
  103. package/dist/server/renderer/index.js +8 -1
  104. package/dist/server/renderer/render.js +47 -0
  105. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  106. package/dist/server/utils/cjsRequire.js +10 -0
  107. package/dist/server/utils/userAgentUtil.js +87 -9
  108. package/dist/server/utils/validator.js +164 -1
  109. package/dist/vite/FastRefresh.jsx +20 -0
  110. package/dist/vite/inject-cache-key-plugin.js +276 -0
  111. package/dist/vite/loadCustomViteConfig.js +22 -0
  112. package/dist/vite/manifest-categorization-plugin.js +455 -0
  113. package/dist/vite/node-loader.mjs +458 -0
  114. package/dist/vite/scssParams.js +21 -0
  115. package/dist/vite/vite.config.client.js +130 -0
  116. package/dist/vite/vite.config.js +422 -0
  117. package/dist/vite/vite.config.server.js +84 -0
  118. package/dist/web-router/components/MetaTag.jsx +34 -0
  119. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  120. package/dist/web-router/components/Split.jsx +200 -0
  121. package/dist/web-router/components/SplitInview.jsx +106 -0
  122. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  123. package/dist/web-router/context.jsx +12 -0
  124. package/dist/web-router/hooks.jsx +72 -0
  125. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  126. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  127. package/dist/web-router/types/context.d.ts +14 -0
  128. package/dist/web-router/types/hooks.d.ts +1 -0
  129. package/dist/web-router/types/index.d.ts +3 -0
  130. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  131. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  132. package/mcp_v2/conversion-tasks.json +35 -17
  133. package/mcp_v2/knowledge-base.json +124 -13
  134. package/mcp_v2/lib/helpers.js +10 -0
  135. package/mcp_v2/mcp.js +4 -4
  136. package/mcp_v2/package.json +1 -0
  137. package/mcp_v2/tools/build.js +116 -20
  138. package/mcp_v2/tools/conversion.js +106 -5
  139. package/mcp_v2/tools/tasks.js +18 -14
  140. package/package.json +35 -68
  141. package/babel.config.js +0 -32
  142. package/dist/index.js +0 -1
  143. package/dist/scripts/devBuild.js +0 -3
  144. package/dist/scripts/devServe.js +0 -3
  145. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  146. package/dist/scripts/registerAliases.js +0 -6
  147. package/dist/server/renderer/document/Body.js +0 -11
  148. package/dist/server/renderer/document/Head.js +0 -9
  149. package/dist/server/renderer/document/index.js +0 -1
  150. package/dist/server/renderer/handler.js +0 -47
  151. package/dist/server/startServer.js +0 -57
  152. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  153. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  154. package/dist/webpack/babel.config.client.js +0 -1
  155. package/dist/webpack/babel.config.ssr.js +0 -1
  156. package/dist/webpack/base.babel.js +0 -7
  157. package/dist/webpack/development.client.babel.js +0 -10
  158. package/dist/webpack/production.client.babel.js +0 -4
  159. package/dist/webpack/production.ssr.babel.js +0 -8
  160. package/dist/webpack/scssParams.js +0 -9
@@ -1 +1,554 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.PerfStore=void 0;var _waterfalls=require("./waterfalls.js");var _summary=require("./summary.js");var _traceExport=require("./traceExport.js");const MAX_RECORDS=5000;const CACHE_STATUS_BY_TYPE={"cache-hit-memory":"memory-hit","cache-hit-disk":"disk-hit","cache-miss-fetch":"miss","network-fetch-complete":"fetch"};const BOOT_LABEL_BY_TYPE={"boot-activity-created":"Activity created","boot-webview-constructed":"WebView constructed","boot-load-url":"loadUrl called","boot-page-started":"Page started","boot-page-finished":"Page finished"};function clone(value){return JSON.parse(JSON.stringify(value));}function round(value){return Number.isFinite(value)?Math.round(value):value;}function shortUrl(url){if(!url)return"";try{const parsed=new URL(url,window.location?.href);return parsed.pathname||parsed.host;}catch{return String(url).slice(0,80);}}function isFiniteNumber(value){return Number.isFinite(value);}class PerfStore{constructor(nativeToWeb){this._nativeToWeb=nativeToWeb??(time=>time);this._seq=0;this._pendingPageLoads=new Map();this._pendingHwAccel=null;this._pendingInteractions=new Map();this._data=this._createData();}expose(){if(typeof window==="undefined")return;window.__catalystPerfStore=this._data;const api={version:this._data.version,store:()=>this.snapshot(),events:()=>clone(this._data.events),requests:()=>clone(this._data.requests),sessions:()=>clone(this._data.sessions),metrics:()=>clone(this._data.metrics),insights:()=>clone(this._data.insights),waterfalls:()=>(0,_waterfalls.listWaterfalls)(),waterfall:(type="all")=>(0,_waterfalls.buildWaterfall)(this.snapshot(),type),summary:()=>(0,_summary.buildSummary)(this.snapshot()),trace:()=>JSON.stringify((0,_traceExport.buildChromeTrace)(this.snapshot())),downloadTrace:filename=>(0,_traceExport.downloadChromeTrace)(this.snapshot(),filename),export:()=>this.snapshot(),clear:()=>this.clear()};window.CatalystPerf=api;}snapshot(){return clone(this._data);}clear(){this._seq=0;this._data.createdAt=new Date().toISOString();this._data.events.length=0;this._data.requests.length=0;this._data.sessions.length=0;this._data.metrics.length=0;this._data.insights.length=0;this._pendingPageLoads.clear();this._pendingInteractions.clear();this._pendingHwAccel=null;}recordNativeEvent(event,source="native"){if(!event?.type)return;const time=this._nativeEventTime(event);if(event.type==="memory-snapshot"){this._recordNativeDerivedData(event,time);return;}this._add("events",{id:this._id("event"),type:event.type,kind:this._eventKind(event.type),label:this._nativeEventLabel(event),source,startTime:time,endTime:time+1,durationMs:event.durationMs??1,detail:{...event}});this._recordNativeDerivedData(event,time);}recordNavigation(data){if(!data)return;if(data.label==="LCP"||data.label==="FCP"){this._add("metrics",{id:this._id("metric"),kind:"paint",name:data.label,label:data.label,startTime:data.startTime??0,endTime:data.endTime??data.durationMs??0,durationMs:data.durationMs,value:data.durationMs,unit:"ms",detail:{...data}});return;}this._add("sessions",{id:this._id("session"),kind:"route",label:data.label??"Route transition",startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,detail:{...data}});}recordInteraction(data){if(!data)return;if(data.phase==="start"){this._pendingInteractions.set(data.sessionId,data);this._add("events",{id:this._id("event"),type:"interaction-start",kind:"interaction",label:data.target?`Tap ${data.target}`:"Tap",source:"web",startTime:data.startTime,endTime:data.startTime+1,durationMs:1,detail:{...data}});return;}if(data.phase==="end"){const start=this._pendingInteractions.get(data.sessionId);this._pendingInteractions.delete(data.sessionId);const startTime=start?.startTime??data.startTime;const endTime=data.endTime??startTime;this._add("sessions",{id:this._id("session"),kind:"interaction",sessionId:data.sessionId,label:data.target?`Tap ${data.target}`:"Interaction",startTime,endTime,durationMs:data.durationMs??round(endTime-startTime),target:data.target??start?.target??null,detail:{...start,...data}});}}recordNetwork(data){if(!data)return;this._add("requests",{id:this._id("request"),kind:"network",label:`${data.method??"GET"} ${shortUrl(data.url)}`,url:data.url,method:data.method,status:data.status,startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,sessionId:data.sessionId??null,interactionId:data.sessionId??null,detail:{...data}});}recordNativeApi(data){if(!data)return;this._add("requests",{id:this._id("request"),kind:"native-api",label:`Native execution: ${data.method??"API"}`,method:data.method,startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,sessionId:data.sessionId??null,interactionId:data.sessionId??null,detail:{...data}});}recordRender(data){if(!data||data.metric==="memory")return;this._add("metrics",{id:this._id("metric"),kind:data.metric,name:data.metric,label:this._renderLabel(data),startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,value:data.value??data.minFps??data.durationMs??null,unit:this._renderUnit(data.metric),context:data.sessionId??null,detail:{...data}});}recordHook(data){if(!data)return;this._add("sessions",{id:this._id("session"),kind:"hook",label:data.label??`Hook ${data.hookType??"work"}`,startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,detail:{...data}});}recordScroll(data){if(!data)return;this._add("sessions",{id:this._id("session"),kind:"scroll",label:data.janky?"Scroll jank":"Scroll",startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,detail:{...data}});}recordKeyboard(data){if(!data)return;if(data.metric){this.recordRender(data);return;}this._add("sessions",{id:this._id("session"),kind:"keyboard",label:"Keyboard",startTime:data.startTime,endTime:data.endTime,durationMs:data.durationMs,detail:{...data}});}recordInsight(data){if(!data)return;this._add("insights",{id:this._id("insight"),kind:"insight",severity:data.severity,rule:data.rule,label:data.title??data.rule,title:data.title??data.rule,category:data.category??"general",message:data.message,fix:data.fix,startTime:data.startTime,endTime:data.endTime??(data.startTime??0)+1,durationMs:data.durationMs??1,interactionId:data.interactionId??null,callId:data.callId??null,requestId:data.requestId??null,memoryId:data.memoryId??null,detail:{...data}});}_recordNativeDerivedData(event,time){const{type}=event;if(type==="cold-start"){this._add("metrics",{id:this._id("metric"),kind:"startup",name:"cold-start",label:`Cold start ${round(event.durationMs)}ms`,startTime:Math.max(0,time-(event.durationMs??0)),endTime:time,durationMs:event.durationMs,value:event.durationMs,unit:"ms",detail:{...event}});return;}if(type?.startsWith("boot-"))return;if(type==="page-load-start"){this._pendingPageLoads.set(event.url,{startTime:time,event});return;}if(type==="page-load-end"||type==="page-load-error"){const pending=this._pendingPageLoads.get(event.url);this._pendingPageLoads.delete(event.url);const durationMs=event.durationMs??null;const startTime=pending?.startTime??(durationMs!=null?Math.max(0,time-durationMs):time);this._add("sessions",{id:this._id("session"),kind:"page-load",label:event.url?`Page ${shortUrl(event.url)}`:"Page load",startTime,endTime:time,durationMs:durationMs??round(time-startTime),url:event.url,detail:{...(pending?.event??{}),...event}});return;}if(CACHE_STATUS_BY_TYPE[type]){this._recordCacheRequest(event,time);return;}if(type==="cache-summary"){this._add("metrics",{id:this._id("metric"),kind:"cache-summary",name:"cache-summary",label:`Cache ${event.hitRatePct??0}% hit`,startTime:time,endTime:time+1,durationMs:1,value:event.hitRatePct,unit:"%",detail:{...event}});return;}if(type==="memory-snapshot"){this._add("metrics",{id:this._id("metric"),kind:"memory",name:"memory",label:`Memory ${round(event.totalMb)}MB total`,startTime:time,endTime:time+1,durationMs:1,value:event.totalMb,unit:"MB",detail:{...event}});this._updateMemorySummary(event,time);return;}if(type==="hw-accel-change"){this._recordHwAccel(event,time);}}_recordCacheRequest(event,time){const durationMs=event.durationMs??0;const startTime=isFiniteNumber(event.nativeStartMs)?this._nativeToWeb(event.nativeStartMs):Math.max(0,time-durationMs);const endTime=startTime+durationMs;const cacheStatus=CACHE_STATUS_BY_TYPE[event.type];const label=`${cacheStatus} ${shortUrl(event.url)}`;this._add("requests",{id:this._id("request"),kind:"cache",label,url:event.url,status:event.statusCode??null,startTime,endTime,durationMs,source:event.type.includes("memory")?"memory":event.type.includes("disk")?"disk":event.type==="network-fetch-complete"?"network":"cache",cacheStatus,resourceType:event.resourceType??"other",detail:{...event}});}_recordHwAccel(event,time){if(event.state==="off"){this._pendingHwAccel={startTime:time,event};return;}if(event.state!=="on"||!this._pendingHwAccel)return;const start=this._pendingHwAccel;this._pendingHwAccel=null;this._add("sessions",{id:this._id("session"),kind:"hw-accel",label:"Software render window",startTime:start.startTime,endTime:time,durationMs:round(time-start.startTime),detail:{...start.event,restored:event}});}_updateMemorySummary(event,time){let summary=this._data.metrics.find(metric=>metric.kind==="memory-summary");if(!summary){summary=this._add("metrics",{id:"metric-memory-summary",kind:"memory-summary",name:"memory-summary",label:"Memory summary",startTime:time,endTime:time,durationMs:1,value:event.totalMb,unit:"MB",detail:{sampleCount:0,firstTime:time,lastTime:time,latest:{},peak:{},min:{},max:{}}});}const detail=summary.detail;const latest={jvmMb:event.jvmMb,webviewMb:event.webviewMb,otherMb:event.otherMb,totalMb:event.totalMb,peakMb:event.peakMb,coldStartMs:event.coldStartMs??null,thread:event.thread??null};const fields=["jvmMb","webviewMb","otherMb","totalMb","peakMb"];detail.sampleCount+=1;detail.lastTime=time;detail.latest=latest;for(const field of fields){const value=latest[field];if(!Number.isFinite(value))continue;detail.min[field]=Number.isFinite(detail.min[field])?Math.min(detail.min[field],value):value;detail.max[field]=Number.isFinite(detail.max[field])?Math.max(detail.max[field],value):value;detail.peak[field]=detail.max[field];}summary.label=`Memory ${round(event.totalMb)}MB total`;summary.startTime=detail.firstTime;summary.endTime=time;summary.durationMs=Math.max(1,round(time-detail.firstTime));summary.value=event.totalMb;}_nativeEventTime(event){if(isFiniteNumber(event.nativeTime))return this._nativeToWeb(event.nativeTime);if(isFiniteNumber(event.nativeEndMs))return this._nativeToWeb(event.nativeEndMs);if(isFiniteNumber(event.endNativeTime))return this._nativeToWeb(event.endNativeTime);if(isFiniteNumber(event.nativeStartMs))return this._nativeToWeb(event.nativeStartMs);if(isFiniteNumber(event.startNativeTime))return this._nativeToWeb(event.startNativeTime);return performance.now();}_nativeEventLabel(event){if(BOOT_LABEL_BY_TYPE[event.type])return BOOT_LABEL_BY_TYPE[event.type];if(event.type==="page-load-start")return`Page started ${shortUrl(event.url)}`;if(event.type==="page-load-end")return`Page finished ${shortUrl(event.url)}`;if(event.type==="page-load-error")return`Page error ${shortUrl(event.url)}`;if(CACHE_STATUS_BY_TYPE[event.type])return`${CACHE_STATUS_BY_TYPE[event.type]} ${shortUrl(event.url)}`;return event.type;}_eventKind(type){if(type?.startsWith("boot-")||type==="cold-start")return"startup";if(type?.startsWith("page-load"))return"page-load";if(type?.startsWith("cache-")||type==="network-fetch-complete")return"cache";if(type?.startsWith("keyboard"))return"keyboard";if(type?.startsWith("scroll"))return"scroll";if(type==="fps-drop-episode"||type==="long-task")return"render";return"event";}_renderLabel(data){if(data.metric==="fps-drop")return`FPS drop min ${data.minFps?.toFixed?.(1)??"?"}`;if(data.metric==="long-task")return`Long task ${round(data.durationMs)}ms`;if(data.metric==="loaf")return`LoAF ${round(data.durationMs)}ms`;if(data.metric==="layout-shift")return`Layout shift ${data.value?.toFixed?.(3)??"?"}`;return data.metric;}_renderUnit(metric){if(metric==="fps-drop")return"fps";if(metric==="layout-shift")return"score";return"ms";}_add(bucket,record){this._data[bucket].push(record);if(this._data[bucket].length>MAX_RECORDS){this._data[bucket].shift();}return record;}_id(prefix){this._seq+=1;return`${prefix}-${this._seq}`;}_createData(){return{version:1,createdAt:new Date().toISOString(),events:[],requests:[],sessions:[],metrics:[],insights:[]};}}exports.PerfStore=PerfStore;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PerfStore = void 0;
7
+ var _waterfalls = require("./waterfalls.js");
8
+ var _summary = require("./summary.js");
9
+ var _traceExport = require("./traceExport.js");
10
+ const MAX_RECORDS = 5000;
11
+ const CACHE_STATUS_BY_TYPE = {
12
+ "cache-hit-memory": "memory-hit",
13
+ "cache-hit-disk": "disk-hit",
14
+ "cache-miss-fetch": "miss",
15
+ "network-fetch-complete": "fetch"
16
+ };
17
+ const BOOT_LABEL_BY_TYPE = {
18
+ "boot-activity-created": "Activity created",
19
+ "boot-webview-constructed": "WebView constructed",
20
+ "boot-load-url": "loadUrl called",
21
+ "boot-page-started": "Page started",
22
+ "boot-page-finished": "Page finished"
23
+ };
24
+ function clone(value) {
25
+ return JSON.parse(JSON.stringify(value));
26
+ }
27
+ function round(value) {
28
+ return Number.isFinite(value) ? Math.round(value) : value;
29
+ }
30
+ function shortUrl(url) {
31
+ if (!url) return "";
32
+ try {
33
+ const parsed = new URL(url, window.location?.href);
34
+ return parsed.pathname || parsed.host;
35
+ } catch {
36
+ return String(url).slice(0, 80);
37
+ }
38
+ }
39
+ function isFiniteNumber(value) {
40
+ return Number.isFinite(value);
41
+ }
42
+ class PerfStore {
43
+ constructor(nativeToWeb) {
44
+ this._nativeToWeb = nativeToWeb ?? (time => time);
45
+ this._seq = 0;
46
+ this._pendingPageLoads = new Map();
47
+ this._pendingHwAccel = null;
48
+ this._pendingInteractions = new Map();
49
+ this._data = this._createData();
50
+ }
51
+ expose() {
52
+ if (typeof window === "undefined") return;
53
+ window.__catalystPerfStore = this._data;
54
+ const api = {
55
+ version: this._data.version,
56
+ store: () => this.snapshot(),
57
+ events: () => clone(this._data.events),
58
+ requests: () => clone(this._data.requests),
59
+ sessions: () => clone(this._data.sessions),
60
+ metrics: () => clone(this._data.metrics),
61
+ insights: () => clone(this._data.insights),
62
+ waterfalls: () => (0, _waterfalls.listWaterfalls)(),
63
+ waterfall: (type = "all") => (0, _waterfalls.buildWaterfall)(this.snapshot(), type),
64
+ summary: () => (0, _summary.buildSummary)(this.snapshot()),
65
+ trace: () => JSON.stringify((0, _traceExport.buildChromeTrace)(this.snapshot())),
66
+ downloadTrace: filename => (0, _traceExport.downloadChromeTrace)(this.snapshot(), filename),
67
+ export: () => this.snapshot(),
68
+ clear: () => this.clear()
69
+ };
70
+ window.CatalystPerf = api;
71
+ }
72
+ snapshot() {
73
+ return clone(this._data);
74
+ }
75
+ clear() {
76
+ this._seq = 0;
77
+ this._data.createdAt = new Date().toISOString();
78
+ this._data.events.length = 0;
79
+ this._data.requests.length = 0;
80
+ this._data.sessions.length = 0;
81
+ this._data.metrics.length = 0;
82
+ this._data.insights.length = 0;
83
+ this._pendingPageLoads.clear();
84
+ this._pendingInteractions.clear();
85
+ this._pendingHwAccel = null;
86
+ }
87
+ recordNativeEvent(event, source = "native") {
88
+ if (!event?.type) return;
89
+ const time = this._nativeEventTime(event);
90
+ if (event.type === "memory-snapshot") {
91
+ this._recordNativeDerivedData(event, time);
92
+ return;
93
+ }
94
+ this._add("events", {
95
+ id: this._id("event"),
96
+ type: event.type,
97
+ kind: this._eventKind(event.type),
98
+ label: this._nativeEventLabel(event),
99
+ source,
100
+ startTime: time,
101
+ endTime: time + 1,
102
+ durationMs: event.durationMs ?? 1,
103
+ detail: {
104
+ ...event
105
+ }
106
+ });
107
+ this._recordNativeDerivedData(event, time);
108
+ }
109
+ recordNavigation(data) {
110
+ if (!data) return;
111
+ if (data.label === "LCP" || data.label === "FCP") {
112
+ this._add("metrics", {
113
+ id: this._id("metric"),
114
+ kind: "paint",
115
+ name: data.label,
116
+ label: data.label,
117
+ startTime: data.startTime ?? 0,
118
+ endTime: data.endTime ?? data.durationMs ?? 0,
119
+ durationMs: data.durationMs,
120
+ value: data.durationMs,
121
+ unit: "ms",
122
+ detail: {
123
+ ...data
124
+ }
125
+ });
126
+ return;
127
+ }
128
+ this._add("sessions", {
129
+ id: this._id("session"),
130
+ kind: "route",
131
+ label: data.label ?? "Route transition",
132
+ startTime: data.startTime,
133
+ endTime: data.endTime,
134
+ durationMs: data.durationMs,
135
+ detail: {
136
+ ...data
137
+ }
138
+ });
139
+ }
140
+ recordInteraction(data) {
141
+ if (!data) return;
142
+ if (data.phase === "start") {
143
+ this._pendingInteractions.set(data.sessionId, data);
144
+ this._add("events", {
145
+ id: this._id("event"),
146
+ type: "interaction-start",
147
+ kind: "interaction",
148
+ label: data.target ? `Tap ${data.target}` : "Tap",
149
+ source: "web",
150
+ startTime: data.startTime,
151
+ endTime: data.startTime + 1,
152
+ durationMs: 1,
153
+ detail: {
154
+ ...data
155
+ }
156
+ });
157
+ return;
158
+ }
159
+ if (data.phase === "end") {
160
+ const start = this._pendingInteractions.get(data.sessionId);
161
+ this._pendingInteractions.delete(data.sessionId);
162
+ const startTime = start?.startTime ?? data.startTime;
163
+ const endTime = data.endTime ?? startTime;
164
+ this._add("sessions", {
165
+ id: this._id("session"),
166
+ kind: "interaction",
167
+ sessionId: data.sessionId,
168
+ label: data.target ? `Tap ${data.target}` : "Interaction",
169
+ startTime,
170
+ endTime,
171
+ durationMs: data.durationMs ?? round(endTime - startTime),
172
+ target: data.target ?? start?.target ?? null,
173
+ detail: {
174
+ ...start,
175
+ ...data
176
+ }
177
+ });
178
+ }
179
+ }
180
+ recordNetwork(data) {
181
+ if (!data) return;
182
+ this._add("requests", {
183
+ id: this._id("request"),
184
+ kind: "network",
185
+ label: `${data.method ?? "GET"} ${shortUrl(data.url)}`,
186
+ url: data.url,
187
+ method: data.method,
188
+ status: data.status,
189
+ startTime: data.startTime,
190
+ endTime: data.endTime,
191
+ durationMs: data.durationMs,
192
+ sessionId: data.sessionId ?? null,
193
+ interactionId: data.sessionId ?? null,
194
+ detail: {
195
+ ...data
196
+ }
197
+ });
198
+ }
199
+ recordNativeApi(data) {
200
+ if (!data) return;
201
+ this._add("requests", {
202
+ id: this._id("request"),
203
+ kind: "native-api",
204
+ label: `Native execution: ${data.method ?? "API"}`,
205
+ method: data.method,
206
+ startTime: data.startTime,
207
+ endTime: data.endTime,
208
+ durationMs: data.durationMs,
209
+ sessionId: data.sessionId ?? null,
210
+ interactionId: data.sessionId ?? null,
211
+ detail: {
212
+ ...data
213
+ }
214
+ });
215
+ }
216
+ recordRender(data) {
217
+ if (!data || data.metric === "memory") return;
218
+ this._add("metrics", {
219
+ id: this._id("metric"),
220
+ kind: data.metric,
221
+ name: data.metric,
222
+ label: this._renderLabel(data),
223
+ startTime: data.startTime,
224
+ endTime: data.endTime,
225
+ durationMs: data.durationMs,
226
+ value: data.value ?? data.minFps ?? data.durationMs ?? null,
227
+ unit: this._renderUnit(data.metric),
228
+ context: data.sessionId ?? null,
229
+ detail: {
230
+ ...data
231
+ }
232
+ });
233
+ }
234
+ recordHook(data) {
235
+ if (!data) return;
236
+ this._add("sessions", {
237
+ id: this._id("session"),
238
+ kind: "hook",
239
+ label: data.label ?? `Hook ${data.hookType ?? "work"}`,
240
+ startTime: data.startTime,
241
+ endTime: data.endTime,
242
+ durationMs: data.durationMs,
243
+ detail: {
244
+ ...data
245
+ }
246
+ });
247
+ }
248
+ recordScroll(data) {
249
+ if (!data) return;
250
+ this._add("sessions", {
251
+ id: this._id("session"),
252
+ kind: "scroll",
253
+ label: data.janky ? "Scroll jank" : "Scroll",
254
+ startTime: data.startTime,
255
+ endTime: data.endTime,
256
+ durationMs: data.durationMs,
257
+ detail: {
258
+ ...data
259
+ }
260
+ });
261
+ }
262
+ recordKeyboard(data) {
263
+ if (!data) return;
264
+ if (data.metric) {
265
+ this.recordRender(data);
266
+ return;
267
+ }
268
+ this._add("sessions", {
269
+ id: this._id("session"),
270
+ kind: "keyboard",
271
+ label: "Keyboard",
272
+ startTime: data.startTime,
273
+ endTime: data.endTime,
274
+ durationMs: data.durationMs,
275
+ detail: {
276
+ ...data
277
+ }
278
+ });
279
+ }
280
+ recordInsight(data) {
281
+ if (!data) return;
282
+ this._add("insights", {
283
+ id: this._id("insight"),
284
+ kind: "insight",
285
+ severity: data.severity,
286
+ rule: data.rule,
287
+ label: data.title ?? data.rule,
288
+ title: data.title ?? data.rule,
289
+ category: data.category ?? "general",
290
+ message: data.message,
291
+ fix: data.fix,
292
+ startTime: data.startTime,
293
+ endTime: data.endTime ?? (data.startTime ?? 0) + 1,
294
+ durationMs: data.durationMs ?? 1,
295
+ interactionId: data.interactionId ?? null,
296
+ callId: data.callId ?? null,
297
+ requestId: data.requestId ?? null,
298
+ memoryId: data.memoryId ?? null,
299
+ detail: {
300
+ ...data
301
+ }
302
+ });
303
+ }
304
+ _recordNativeDerivedData(event, time) {
305
+ const {
306
+ type
307
+ } = event;
308
+ if (type === "cold-start") {
309
+ this._add("metrics", {
310
+ id: this._id("metric"),
311
+ kind: "startup",
312
+ name: "cold-start",
313
+ label: `Cold start ${round(event.durationMs)}ms`,
314
+ startTime: Math.max(0, time - (event.durationMs ?? 0)),
315
+ endTime: time,
316
+ durationMs: event.durationMs,
317
+ value: event.durationMs,
318
+ unit: "ms",
319
+ detail: {
320
+ ...event
321
+ }
322
+ });
323
+ return;
324
+ }
325
+ if (type?.startsWith("boot-")) return;
326
+ if (type === "page-load-start") {
327
+ this._pendingPageLoads.set(event.url, {
328
+ startTime: time,
329
+ event
330
+ });
331
+ return;
332
+ }
333
+ if (type === "page-load-end" || type === "page-load-error") {
334
+ const pending = this._pendingPageLoads.get(event.url);
335
+ this._pendingPageLoads.delete(event.url);
336
+ const durationMs = event.durationMs ?? null;
337
+ const startTime = pending?.startTime ?? (durationMs != null ? Math.max(0, time - durationMs) : time);
338
+ this._add("sessions", {
339
+ id: this._id("session"),
340
+ kind: "page-load",
341
+ label: event.url ? `Page ${shortUrl(event.url)}` : "Page load",
342
+ startTime,
343
+ endTime: time,
344
+ durationMs: durationMs ?? round(time - startTime),
345
+ url: event.url,
346
+ detail: {
347
+ ...(pending?.event ?? {}),
348
+ ...event
349
+ }
350
+ });
351
+ return;
352
+ }
353
+ if (CACHE_STATUS_BY_TYPE[type]) {
354
+ this._recordCacheRequest(event, time);
355
+ return;
356
+ }
357
+ if (type === "cache-summary") {
358
+ this._add("metrics", {
359
+ id: this._id("metric"),
360
+ kind: "cache-summary",
361
+ name: "cache-summary",
362
+ label: `Cache ${event.hitRatePct ?? 0}% hit`,
363
+ startTime: time,
364
+ endTime: time + 1,
365
+ durationMs: 1,
366
+ value: event.hitRatePct,
367
+ unit: "%",
368
+ detail: {
369
+ ...event
370
+ }
371
+ });
372
+ return;
373
+ }
374
+ if (type === "memory-snapshot") {
375
+ this._add("metrics", {
376
+ id: this._id("metric"),
377
+ kind: "memory",
378
+ name: "memory",
379
+ label: `Memory ${round(event.totalMb)}MB total`,
380
+ startTime: time,
381
+ endTime: time + 1,
382
+ durationMs: 1,
383
+ value: event.totalMb,
384
+ unit: "MB",
385
+ detail: {
386
+ ...event
387
+ }
388
+ });
389
+ this._updateMemorySummary(event, time);
390
+ return;
391
+ }
392
+ if (type === "hw-accel-change") {
393
+ this._recordHwAccel(event, time);
394
+ }
395
+ }
396
+ _recordCacheRequest(event, time) {
397
+ const durationMs = event.durationMs ?? 0;
398
+ const startTime = isFiniteNumber(event.nativeStartMs) ? this._nativeToWeb(event.nativeStartMs) : Math.max(0, time - durationMs);
399
+ const endTime = startTime + durationMs;
400
+ const cacheStatus = CACHE_STATUS_BY_TYPE[event.type];
401
+ const label = `${cacheStatus} ${shortUrl(event.url)}`;
402
+ this._add("requests", {
403
+ id: this._id("request"),
404
+ kind: "cache",
405
+ label,
406
+ url: event.url,
407
+ status: event.statusCode ?? null,
408
+ startTime,
409
+ endTime,
410
+ durationMs,
411
+ source: event.type.includes("memory") ? "memory" : event.type.includes("disk") ? "disk" : event.type === "network-fetch-complete" ? "network" : "cache",
412
+ cacheStatus,
413
+ resourceType: event.resourceType ?? "other",
414
+ detail: {
415
+ ...event
416
+ }
417
+ });
418
+ }
419
+ _recordHwAccel(event, time) {
420
+ if (event.state === "off") {
421
+ this._pendingHwAccel = {
422
+ startTime: time,
423
+ event
424
+ };
425
+ return;
426
+ }
427
+ if (event.state !== "on" || !this._pendingHwAccel) return;
428
+ const start = this._pendingHwAccel;
429
+ this._pendingHwAccel = null;
430
+ this._add("sessions", {
431
+ id: this._id("session"),
432
+ kind: "hw-accel",
433
+ label: "Software render window",
434
+ startTime: start.startTime,
435
+ endTime: time,
436
+ durationMs: round(time - start.startTime),
437
+ detail: {
438
+ ...start.event,
439
+ restored: event
440
+ }
441
+ });
442
+ }
443
+ _updateMemorySummary(event, time) {
444
+ let summary = this._data.metrics.find(metric => metric.kind === "memory-summary");
445
+ if (!summary) {
446
+ summary = this._add("metrics", {
447
+ id: "metric-memory-summary",
448
+ kind: "memory-summary",
449
+ name: "memory-summary",
450
+ label: "Memory summary",
451
+ startTime: time,
452
+ endTime: time,
453
+ durationMs: 1,
454
+ value: event.totalMb,
455
+ unit: "MB",
456
+ detail: {
457
+ sampleCount: 0,
458
+ firstTime: time,
459
+ lastTime: time,
460
+ latest: {},
461
+ peak: {},
462
+ min: {},
463
+ max: {}
464
+ }
465
+ });
466
+ }
467
+ const detail = summary.detail;
468
+ const latest = {
469
+ jvmMb: event.jvmMb,
470
+ webviewMb: event.webviewMb,
471
+ otherMb: event.otherMb,
472
+ totalMb: event.totalMb,
473
+ peakMb: event.peakMb,
474
+ coldStartMs: event.coldStartMs ?? null,
475
+ thread: event.thread ?? null
476
+ };
477
+ const fields = ["jvmMb", "webviewMb", "otherMb", "totalMb", "peakMb"];
478
+ detail.sampleCount += 1;
479
+ detail.lastTime = time;
480
+ detail.latest = latest;
481
+ for (const field of fields) {
482
+ const value = latest[field];
483
+ if (!Number.isFinite(value)) continue;
484
+ detail.min[field] = Number.isFinite(detail.min[field]) ? Math.min(detail.min[field], value) : value;
485
+ detail.max[field] = Number.isFinite(detail.max[field]) ? Math.max(detail.max[field], value) : value;
486
+ detail.peak[field] = detail.max[field];
487
+ }
488
+ summary.label = `Memory ${round(event.totalMb)}MB total`;
489
+ summary.startTime = detail.firstTime;
490
+ summary.endTime = time;
491
+ summary.durationMs = Math.max(1, round(time - detail.firstTime));
492
+ summary.value = event.totalMb;
493
+ }
494
+ _nativeEventTime(event) {
495
+ if (isFiniteNumber(event.nativeTime)) return this._nativeToWeb(event.nativeTime);
496
+ if (isFiniteNumber(event.nativeEndMs)) return this._nativeToWeb(event.nativeEndMs);
497
+ if (isFiniteNumber(event.endNativeTime)) return this._nativeToWeb(event.endNativeTime);
498
+ if (isFiniteNumber(event.nativeStartMs)) return this._nativeToWeb(event.nativeStartMs);
499
+ if (isFiniteNumber(event.startNativeTime)) return this._nativeToWeb(event.startNativeTime);
500
+ return performance.now();
501
+ }
502
+ _nativeEventLabel(event) {
503
+ if (BOOT_LABEL_BY_TYPE[event.type]) return BOOT_LABEL_BY_TYPE[event.type];
504
+ if (event.type === "page-load-start") return `Page started ${shortUrl(event.url)}`;
505
+ if (event.type === "page-load-end") return `Page finished ${shortUrl(event.url)}`;
506
+ if (event.type === "page-load-error") return `Page error ${shortUrl(event.url)}`;
507
+ if (CACHE_STATUS_BY_TYPE[event.type]) return `${CACHE_STATUS_BY_TYPE[event.type]} ${shortUrl(event.url)}`;
508
+ return event.type;
509
+ }
510
+ _eventKind(type) {
511
+ if (type?.startsWith("boot-") || type === "cold-start") return "startup";
512
+ if (type?.startsWith("page-load")) return "page-load";
513
+ if (type?.startsWith("cache-") || type === "network-fetch-complete") return "cache";
514
+ if (type?.startsWith("keyboard")) return "keyboard";
515
+ if (type?.startsWith("scroll")) return "scroll";
516
+ if (type === "fps-drop-episode" || type === "long-task") return "render";
517
+ return "event";
518
+ }
519
+ _renderLabel(data) {
520
+ if (data.metric === "fps-drop") return `FPS drop min ${data.minFps?.toFixed?.(1) ?? "?"}`;
521
+ if (data.metric === "long-task") return `Long task ${round(data.durationMs)}ms`;
522
+ if (data.metric === "loaf") return `LoAF ${round(data.durationMs)}ms`;
523
+ if (data.metric === "layout-shift") return `Layout shift ${data.value?.toFixed?.(3) ?? "?"}`;
524
+ return data.metric;
525
+ }
526
+ _renderUnit(metric) {
527
+ if (metric === "fps-drop") return "fps";
528
+ if (metric === "layout-shift") return "score";
529
+ return "ms";
530
+ }
531
+ _add(bucket, record) {
532
+ this._data[bucket].push(record);
533
+ if (this._data[bucket].length > MAX_RECORDS) {
534
+ this._data[bucket].shift();
535
+ }
536
+ return record;
537
+ }
538
+ _id(prefix) {
539
+ this._seq += 1;
540
+ return `${prefix}-${this._seq}`;
541
+ }
542
+ _createData() {
543
+ return {
544
+ version: 1,
545
+ createdAt: new Date().toISOString(),
546
+ events: [],
547
+ requests: [],
548
+ sessions: [],
549
+ metrics: [],
550
+ insights: []
551
+ };
552
+ }
553
+ }
554
+ exports.PerfStore = PerfStore;