catalyst-core 0.2.0-beta.2 → 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 (184) 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/androidProject/app/build.gradle.kts +1 -0
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +143 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +26 -6
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +143 -17
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +6 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +26 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +56 -1
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +48 -8
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfDeliveryState.kt +36 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfEventBuffer.kt +289 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/ProfilerNavigationState.kt +13 -0
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ProfilerLifecycleTest.kt +51 -0
  19. package/dist/native/androidSetup.js +300 -8
  20. package/dist/native/bridge/WebBridge.js +481 -59
  21. package/dist/native/bridge/constants/NativeInterfaces.js +290 -44
  22. package/dist/native/bridge/errors.js +250 -26
  23. package/dist/native/bridge/hooks/index.js +125 -1
  24. package/dist/native/bridge/hooks/useCamera.js +347 -9
  25. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  26. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  27. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  28. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  29. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  30. package/dist/native/bridge/hooks/useIntent.js +95 -1
  31. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  32. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  33. package/dist/native/bridge/hooks/useNotification.js +318 -2
  34. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  35. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  36. package/dist/native/bridge/hooks/utils.js +27 -1
  37. package/dist/native/bridge/hooks.js +16 -1
  38. package/dist/native/bridge/perf/collectors/ApiCollector.js +85 -0
  39. package/dist/native/bridge/perf/collectors/CacheCollector.js +215 -0
  40. package/dist/native/bridge/perf/collectors/HookCollector.js +165 -0
  41. package/dist/native/bridge/perf/collectors/InsightsCollector.js +505 -0
  42. package/dist/native/bridge/perf/collectors/InteractionCollector.js +273 -0
  43. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +124 -0
  44. package/dist/native/bridge/perf/collectors/NavigationCollector.js +294 -0
  45. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +183 -0
  46. package/dist/native/bridge/perf/collectors/RenderCollector.js +284 -0
  47. package/dist/native/bridge/perf/collectors/ScrollCollector.js +111 -0
  48. package/dist/native/bridge/perf/collectors/ThreadCollector.js +111 -0
  49. package/dist/native/bridge/perf/core/constants.js +126 -0
  50. package/dist/native/bridge/perf/core/measure.js +189 -0
  51. package/dist/native/bridge/perf/core/session.js +100 -0
  52. package/dist/native/bridge/perf/core/store.js +554 -0
  53. package/dist/native/bridge/perf/core/summary.js +174 -0
  54. package/dist/native/bridge/perf/core/traceExport.js +240 -0
  55. package/dist/native/bridge/perf/core/waterfalls.js +146 -0
  56. package/dist/native/bridge/perf/index.js +402 -0
  57. package/dist/native/bridge/safeArea.js +53 -5
  58. package/dist/native/bridge/standardizedHooks.js +595 -82
  59. package/dist/native/bridge/useBaseHook.js +274 -38
  60. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  61. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  62. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  63. package/dist/native/buildAndroid/assets.js +510 -13
  64. package/dist/native/buildAndroid/build.js +296 -19
  65. package/dist/native/buildAndroid/config.js +87 -1
  66. package/dist/native/buildAndroid/index.js +374 -1
  67. package/dist/native/buildAppAndroid.js +34 -2
  68. package/dist/native/buildAppIos.js +62 -2
  69. package/dist/native/buildIos/assets.js +609 -8
  70. package/dist/native/buildIos/build.js +639 -20
  71. package/dist/native/buildIos/config.js +294 -12
  72. package/dist/native/buildIos/index.js +343 -6
  73. package/dist/native/buildIos/plugins.js +343 -16
  74. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  75. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  76. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  77. package/dist/native/internalPluginUtils.js +232 -7
  78. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +19 -0
  79. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +4 -0
  80. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +5 -1
  81. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +10 -0
  82. package/dist/native/iosnativeWebView/Sources/Core/Utils/DisplayLinkPerfMonitor.swift +89 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +373 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +35 -1
  85. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +24 -11
  86. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +9 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +126 -0
  88. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +75 -1
  89. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +13 -0
  90. package/dist/native/package.json +3 -0
  91. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  92. package/dist/native/pluginComposerAndroid.js +407 -23
  93. package/dist/native/pluginComposerIos.js +435 -11
  94. package/dist/native/renameAndroidProject.js +1094 -88
  95. package/dist/native/setupEmulatorIos.js +289 -22
  96. package/dist/native/setupServer.js +160 -19
  97. package/dist/native/terminalProgress.js +199 -11
  98. package/dist/native/utils.js +193 -15
  99. package/dist/offline/catalyst-sw.js +145 -1
  100. package/dist/offline/registerServiceWorker.js +33 -1
  101. package/dist/otel.js +375 -46
  102. package/dist/router/ClientRouter.js +14 -2
  103. package/dist/router/ServerRouter.js +9 -1
  104. package/dist/router.js +1 -1
  105. package/dist/scripts/build.js +112 -3
  106. package/dist/scripts/checkVersion.js +3 -1
  107. package/dist/scripts/cleanCache.js +30 -1
  108. package/dist/scripts/generateOfflineManifest.js +116 -1
  109. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  110. package/dist/scripts/plugins.js +309 -1
  111. package/dist/scripts/preServerInit.js +13 -0
  112. package/dist/scripts/scriptUtils.js +54 -3
  113. package/dist/scripts/serve.js +51 -4
  114. package/dist/scripts/start.js +47 -10
  115. package/dist/scripts/validator.js +149 -1
  116. package/dist/sentry.cjs +127 -0
  117. package/dist/sentry.js +125 -5
  118. package/dist/server/expressServer.js +250 -5
  119. package/dist/server/manifestCache.js +36 -0
  120. package/dist/server/renderer/ChunkExtractor.js +144 -0
  121. package/dist/server/renderer/document/Body.jsx +59 -0
  122. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  123. package/dist/server/renderer/document/Head.jsx +60 -0
  124. package/dist/server/renderer/document/index.jsx +2 -0
  125. package/dist/server/renderer/extract.js +149 -31
  126. package/dist/server/renderer/handler.jsx +433 -0
  127. package/dist/server/renderer/index.js +8 -1
  128. package/dist/server/renderer/render.js +47 -0
  129. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  130. package/dist/server/utils/cjsRequire.js +10 -0
  131. package/dist/server/utils/userAgentUtil.js +87 -9
  132. package/dist/server/utils/validator.js +164 -1
  133. package/dist/vite/FastRefresh.jsx +20 -0
  134. package/dist/vite/inject-cache-key-plugin.js +276 -0
  135. package/dist/vite/loadCustomViteConfig.js +22 -0
  136. package/dist/vite/manifest-categorization-plugin.js +455 -0
  137. package/dist/vite/node-loader.mjs +458 -0
  138. package/dist/vite/scssParams.js +21 -0
  139. package/dist/vite/vite.config.client.js +130 -0
  140. package/dist/vite/vite.config.js +422 -0
  141. package/dist/vite/vite.config.server.js +84 -0
  142. package/dist/web-router/components/MetaTag.jsx +34 -0
  143. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  144. package/dist/web-router/components/Split.jsx +200 -0
  145. package/dist/web-router/components/SplitInview.jsx +106 -0
  146. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  147. package/dist/web-router/context.jsx +12 -0
  148. package/dist/web-router/hooks.jsx +72 -0
  149. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  150. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  151. package/dist/web-router/types/context.d.ts +14 -0
  152. package/dist/web-router/types/hooks.d.ts +1 -0
  153. package/dist/web-router/types/index.d.ts +3 -0
  154. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  155. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  156. package/mcp_v2/conversion-tasks.json +35 -17
  157. package/mcp_v2/knowledge-base.json +124 -13
  158. package/mcp_v2/lib/helpers.js +10 -0
  159. package/mcp_v2/mcp.js +4 -4
  160. package/mcp_v2/package.json +1 -0
  161. package/mcp_v2/tools/build.js +116 -20
  162. package/mcp_v2/tools/conversion.js +106 -5
  163. package/mcp_v2/tools/tasks.js +18 -14
  164. package/package.json +35 -68
  165. package/babel.config.js +0 -32
  166. package/dist/index.js +0 -1
  167. package/dist/scripts/devBuild.js +0 -3
  168. package/dist/scripts/devServe.js +0 -3
  169. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  170. package/dist/scripts/registerAliases.js +0 -6
  171. package/dist/server/renderer/document/Body.js +0 -11
  172. package/dist/server/renderer/document/Head.js +0 -9
  173. package/dist/server/renderer/document/index.js +0 -1
  174. package/dist/server/renderer/handler.js +0 -47
  175. package/dist/server/startServer.js +0 -57
  176. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  177. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  178. package/dist/webpack/babel.config.client.js +0 -1
  179. package/dist/webpack/babel.config.ssr.js +0 -1
  180. package/dist/webpack/base.babel.js +0 -7
  181. package/dist/webpack/development.client.babel.js +0 -10
  182. package/dist/webpack/production.client.babel.js +0 -4
  183. package/dist/webpack/production.ssr.babel.js +0 -8
  184. package/dist/webpack/scssParams.js +0 -9
@@ -0,0 +1,554 @@
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;