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,8 @@
1
+ module.exports = {
2
+ presets: [
3
+ ["@babel/preset-env", { targets: { node: "current" } }],
4
+ ["@babel/preset-react", { runtime: "automatic" }],
5
+ ["@babel/preset-typescript", { isTSX: true, allExtensions: true }],
6
+ ],
7
+ compact: false,
8
+ }
package/bin/catalyst.js CHANGED
@@ -1,128 +1,72 @@
1
1
  #!/usr/bin/env node
2
- "use strict"
3
- process.on("unhandledRejection", (err) => {
4
- throw err
5
- })
6
- const { spawnSync } = require("node:child_process")
7
- const args = process.argv.slice(2)
8
2
 
9
- // Array of valid commands
10
- const validCommands = [
11
- "build",
12
- "start",
13
- "serve",
14
- "devBuild",
15
- "devServe",
16
- "plugin",
17
- "plugins",
18
- "buildApp",
19
- "buildApp:ios",
20
- "buildApp:android",
21
- "setupEmulator",
22
- "setupEmulator:ios",
23
- "setupEmulator:android",
24
- ]
3
+ import { spawnSync } from "node:child_process"
4
+ import { dirname, resolve } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ process.on("unhandledRejection", (error) => {
8
+ throw error
9
+ })
25
10
 
26
- // Map of platform-specific commands to their script paths
11
+ const __dirname = dirname(fileURLToPath(import.meta.url))
12
+ const args = process.argv.slice(2)
13
+ const webCommands = new Set(["build", "start", "serve", "serve:inspect", "plugin", "plugins"])
27
14
  const platformScripts = {
28
15
  "setupEmulator:ios": "../dist/native/setupEmulatorIos.js",
29
16
  "setupEmulator:android": "../dist/native/androidSetup.js",
30
17
  "buildApp:ios": "../dist/native/buildAppIos.js",
31
18
  "buildApp:android": "../dist/native/buildAppAndroid.js",
32
19
  }
20
+ const validCommands = new Set([...webCommands, "buildApp", "setupEmulator", ...Object.keys(platformScripts)])
21
+
22
+ const scriptIndex = args.findIndex((arg) => validCommands.has(arg))
23
+ const script = scriptIndex === -1 ? args[0] : args[scriptIndex]
24
+ const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []
25
+ const scriptArgs = args.slice(scriptIndex + 1)
33
26
 
34
- // Helper to check if arg is a platform command
35
- const isPlatformCommand = (arg, prefix) => {
36
- if (!arg.startsWith(`${prefix}:`)) return false
37
- const platform = arg.split(":")[1]
38
- return ["ios", "android"].includes(platform) || platform === undefined
27
+ function handleProcessResult(result) {
28
+ if (result.signal) {
29
+ if (result.signal === "SIGKILL") {
30
+ console.error("The Catalyst process was terminated with SIGKILL.")
31
+ } else if (result.signal === "SIGTERM") {
32
+ console.error("The Catalyst process was terminated with SIGTERM.")
33
+ }
34
+ process.exit(1)
35
+ }
36
+ process.exit(result.status ?? 1)
39
37
  }
40
38
 
41
- // Helper function to run a platform command
42
- const runPlatformCommand = (baseCommand, platform) => {
43
- const command = `${baseCommand}:${platform}`
44
- const result = spawnSync(
39
+ function runScript(relativePath, extraArgs = []) {
40
+ return spawnSync(
45
41
  process.execPath,
46
- nodeArgs.concat(require.resolve(platformScripts[command])).concat(args.slice(scriptIndex + 1)),
47
- { stdio: "inherit" }
42
+ [...nodeArgs, resolve(__dirname, relativePath), ...extraArgs, ...scriptArgs],
43
+ {
44
+ stdio: "inherit",
45
+ }
48
46
  )
49
- return result
50
47
  }
51
48
 
52
- // Helper function to run commands for all platforms
53
- const runAllPlatforms = (baseCommand) => {
54
- const platforms = ["ios", "android"]
55
- for (const platform of platforms) {
56
- const result = runPlatformCommand(baseCommand, platform)
57
- if (result.status !== 0) {
58
- handleProcessResult(result)
59
- return
60
- }
49
+ function runPlatformCommand(command) {
50
+ return runScript(platformScripts[command])
51
+ }
52
+
53
+ if (script === "buildApp" || script === "setupEmulator") {
54
+ for (const platform of ["ios", "android"]) {
55
+ const result = runPlatformCommand(`${script}:${platform}`)
56
+ if (result.status !== 0 || result.signal) handleProcessResult(result)
61
57
  }
62
58
  process.exit(0)
63
59
  }
64
60
 
65
- const scriptIndex = args.findIndex(
66
- (x) =>
67
- x === "build" ||
68
- x === "start" ||
69
- x === "serve" ||
70
- x === "devBuild" ||
71
- x === "devServe" ||
72
- x === "plugin" ||
73
- x === "plugins" ||
74
- isPlatformCommand(x, "buildApp") ||
75
- isPlatformCommand(x, "setupEmulator")
76
- )
77
- const script = scriptIndex === -1 ? args[0] : args[scriptIndex]
78
- const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []
79
- const resolvedScript = script === "plugin" ? "plugins" : script
80
-
81
- if (validCommands.includes(script)) {
82
- // Handle platform-specific or combined commands
83
- if (script === "buildApp" || script === "setupEmulator") {
84
- // Run for all platforms if no specific platform is specified
85
- runAllPlatforms(script)
86
- } else if (script in platformScripts) {
87
- // Run for specific platform
88
- const result = spawnSync(
89
- process.execPath,
90
- nodeArgs.concat(require.resolve(platformScripts[script])).concat(args.slice(scriptIndex + 1)),
91
- { stdio: "inherit" }
92
- )
93
- handleProcessResult(result)
94
- } else {
95
- // Original commands
96
- const result = spawnSync(
97
- process.execPath,
98
- nodeArgs
99
- .concat(require.resolve("../dist/scripts/" + resolvedScript))
100
- .concat(args.slice(scriptIndex + 1)),
101
- { stdio: "inherit" }
102
- )
103
- handleProcessResult(result)
104
- }
105
- } else {
106
- console.log('Unknown script "' + script + '".')
61
+ if (platformScripts[script]) {
62
+ handleProcessResult(runPlatformCommand(script))
107
63
  }
108
64
 
109
- // Helper function to handle process results
110
- function handleProcessResult(result) {
111
- if (result.signal) {
112
- if (result.signal === "SIGKILL") {
113
- console.log(
114
- "The build failed because the process exited too early. " +
115
- "This probably means the system ran out of memory or someone called " +
116
- "`kill -9` on the process."
117
- )
118
- } else if (result.signal === "SIGTERM") {
119
- console.log(
120
- "The build failed because the process exited too early. " +
121
- "Someone might have called `kill` or `killall`, or the system could " +
122
- "be shutting down."
123
- )
124
- }
125
- process.exit(1)
126
- }
127
- process.exit(result.status)
65
+ if (webCommands.has(script)) {
66
+ const scriptName = script === "plugin" ? "plugins" : script === "serve:inspect" ? "serve" : script
67
+ const extraArgs = script === "serve:inspect" ? ["--inspect"] : []
68
+ handleProcessResult(runScript(`../dist/scripts/${scriptName}.js`, extraArgs))
128
69
  }
70
+
71
+ console.error(`Unknown script "${script}".`)
72
+ process.exit(1)
package/changelog.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 1.0.0-beta.1
2
+
3
+ - Replaced the Webpack build and SSR pipeline with Vite and ESM.
4
+ - Added React 19 Suspense-aware routing, split components, and manifest-based asset loading.
5
+ - Added opt-in request, compression, flush, and bot-aware OpenTelemetry spans.
6
+ - Preserved native application builds, internal plugins, Sentry exports, and safe-area SSR hydration.
7
+ - Removed the legacy `devBuild` and `devServe` commands.
8
+
1
9
  # Changelog
2
10
 
3
11
  ## [0.1.0-beta.2] - 2026-05-06
package/dist/caching.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});var _apiCaching=require("@tata1mg/api-caching");Object.keys(_apiCaching).forEach(function(key){if(key==="default"||key==="__esModule")return;if(key in exports&&exports[key]===_apiCaching[key])return;Object.defineProperty(exports,key,{enumerable:true,get:function(){return _apiCaching[key];}});});
1
+ export * from "@tata1mg/api-caching"
package/dist/index.jsx ADDED
@@ -0,0 +1,8 @@
1
+ export * from "react-router-dom"
2
+ export * from "./web-router/components/RouterDataProvider.jsx"
3
+ export * from "./web-router/components/MetaTag.jsx"
4
+ export { split, hydrationReady } from "./web-router/components/Split.jsx"
5
+ export { split as default } from "./web-router/components/Split.jsx"
6
+ export * from "./web-router/hooks.jsx"
7
+ export * from "./web-router/utils/metaDataUtils.jsx"
8
+ export * from "./server/renderer/document/index.jsx"
package/dist/logger.js CHANGED
@@ -1,4 +1,10 @@
1
- "use strict";const pc=require("picocolors");const winston=require("winston");const DailyRotateFile=require("winston-daily-rotate-file");const{createLogger,format,transports}=winston;/**
1
+ import pc from "picocolors"
2
+ import winston from "winston"
3
+ import DailyRotateFile from "winston-daily-rotate-file"
4
+
5
+ const { createLogger, format, transports } = winston
6
+
7
+ /**
2
8
  * @description Logger library with rotational strategy. Creates a logs folder in root.
3
9
  * With debug, error and info log directories with their respective log files.
4
10
  *
@@ -6,5 +12,70 @@
6
12
  * @param config { @enableDebugLogs: Bool // default: true }
7
13
  * @returns loggerInstance
8
14
  *
9
- */const configureLogger=(config={})=>{const{enableDebugLogs=true,enableFileLogging=true,enableConsoleLogging=true}=config;const consoleTransport=new transports.Console({level:"debug"});const fileTransport=(type="info")=>{return new DailyRotateFile({filename:`${process.env.src_path}/logs/${type}/%DATE%.${type}.log`,datePattern:"YYYY-MM-DD",maxFiles:"3d",// Logs will be removed after 2days,
10
- loglevel:type,level:type});};const infoLogger=createLogger({format:format.combine(format.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),format.json()),defaultMeta:{loglevel:"info"}});const debugLogger=createLogger({format:format.combine(format.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),format.json()),defaultMeta:{loglevel:"debug"}});const errorLogger=createLogger({format:format.combine(format.timestamp({format:"YYYY-MM-DD HH:mm:ss"}),format.json()),defaultMeta:{loglevel:"error"}});if(enableConsoleLogging&&JSON.parse(enableConsoleLogging)){infoLogger.add(consoleTransport);debugLogger.add(consoleTransport);errorLogger.add(consoleTransport);}if(enableFileLogging&&JSON.parse(enableFileLogging)){infoLogger.add(fileTransport("info"));debugLogger.add(fileTransport("debug"));errorLogger.add(fileTransport("error"));}const Logger={debug:()=>{},error:msg=>{console.log(pc.red(pc.bold("ERROR: "+JSON.stringify(msg))));errorLogger.error(msg);},info:msg=>{console.log(pc.green(pc.bold("INFO: "+JSON.stringify(msg))));infoLogger.info(msg);}};if(enableDebugLogs&&JSON.parse(enableDebugLogs)){Logger.debug=msg=>{console.log(pc.yellow(pc.bold("DEBUG: "+JSON.stringify(msg))));debugLogger.debug(msg);};}if(global)global.logger=Logger;return Logger;};module.exports={configureLogger};
15
+ */
16
+ const configureLogger = (config = {}) => {
17
+ const { enableDebugLogs = true, enableFileLogging = true, enableConsoleLogging = true } = config
18
+
19
+ const consoleTransport = new transports.Console({ level: "debug" })
20
+
21
+ const fileTransport = (type = "info") => {
22
+ return new DailyRotateFile({
23
+ filename: `${process.env.src_path}/logs/${type}/%DATE%.${type}.log`,
24
+ datePattern: "YYYY-MM-DD",
25
+ maxFiles: "3d", // Logs will be removed after 2days,
26
+ loglevel: type,
27
+ level: type,
28
+ })
29
+ }
30
+
31
+ const infoLogger = createLogger({
32
+ format: format.combine(format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.json()),
33
+ defaultMeta: { loglevel: "info" },
34
+ })
35
+
36
+ const debugLogger = createLogger({
37
+ format: format.combine(format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.json()),
38
+ defaultMeta: { loglevel: "debug" },
39
+ })
40
+
41
+ const errorLogger = createLogger({
42
+ format: format.combine(format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.json()),
43
+ defaultMeta: { loglevel: "error" },
44
+ })
45
+
46
+ if (enableConsoleLogging && JSON.parse(enableConsoleLogging)) {
47
+ infoLogger.add(consoleTransport)
48
+ debugLogger.add(consoleTransport)
49
+ errorLogger.add(consoleTransport)
50
+ }
51
+
52
+ if (enableFileLogging && JSON.parse(enableFileLogging)) {
53
+ infoLogger.add(fileTransport("info"))
54
+ debugLogger.add(fileTransport("debug"))
55
+ errorLogger.add(fileTransport("error"))
56
+ }
57
+
58
+ const Logger = {
59
+ debug: () => {},
60
+ error: (msg) => {
61
+ console.log(pc.red(pc.bold("ERROR: " + JSON.stringify(msg))))
62
+ errorLogger.error(msg)
63
+ },
64
+ info: (msg) => {
65
+ console.log(pc.green(pc.bold("INFO: " + JSON.stringify(msg))))
66
+ infoLogger.info(msg)
67
+ },
68
+ }
69
+
70
+ if (enableDebugLogs && JSON.parse(enableDebugLogs)) {
71
+ Logger.debug = (msg) => {
72
+ console.log(pc.yellow(pc.bold("DEBUG: " + JSON.stringify(msg))))
73
+ debugLogger.debug(msg)
74
+ }
75
+ }
76
+
77
+ if (global) global.logger = Logger
78
+ return Logger
79
+ }
80
+
81
+ export { configureLogger }
@@ -110,6 +110,7 @@ android {
110
110
  applicationIdSuffix = ".debug"
111
111
  versionNameSuffix = "-debug"
112
112
  isMinifyEnabled = false
113
+ enableUnitTestCoverage = true
113
114
  buildConfigField("Boolean", "ALLOW_MIXED_CONTENT", "true")
114
115
  buildConfigField("String", "LOCAL_IP", "\"${getLocalIpAddress()}\"")
115
116
  }
@@ -7,18 +7,26 @@ import android.graphics.Bitmap
7
7
  import android.net.Uri
8
8
  import android.os.Bundle
9
9
  import android.util.Log
10
+ import android.view.GestureDetector
11
+ import android.view.MotionEvent
10
12
  import android.view.View
11
13
  import android.webkit.*
12
14
  import androidx.webkit.ServiceWorkerClientCompat
13
15
  import androidx.webkit.ServiceWorkerControllerCompat
16
+ import androidx.webkit.WebViewCompat
17
+ import androidx.webkit.WebViewFeature
14
18
  import androidx.webkit.WebResourceRequestCompat
15
19
  import android.widget.ProgressBar
16
20
  import androidx.webkit.WebViewAssetLoader
17
21
  import io.yourname.androidproject.WebCacheManager
18
22
  import io.yourname.androidproject.URLWhitelistManager
19
23
  import io.yourname.androidproject.matchesCachePattern
24
+ import io.yourname.androidproject.utils.BridgeUtils
20
25
  import io.yourname.androidproject.utils.CameraUtils
21
26
  import io.yourname.androidproject.utils.NetworkUtils
27
+ import io.yourname.androidproject.utils.PerfEventBuffer
28
+ import io.yourname.androidproject.utils.ProfilerNavigationState
29
+ import org.json.JSONObject
22
30
  import kotlinx.coroutines.*
23
31
  import java.io.FileNotFoundException
24
32
  import java.util.Properties
@@ -45,11 +53,15 @@ class CustomWebView(
45
53
  private var isInitialApiCalled: Boolean = false // Flag to track initial page load
46
54
  private var isInitialPageLoaded: Boolean = false
47
55
  private var buildOptimisation: Boolean = false // Added property for build optimization
56
+ private var profilerEnabled: Boolean = false
57
+ private val profilerNavigationState = ProfilerNavigationState()
48
58
  private lateinit var assetLoader: WebViewAssetLoader
49
59
  private var allowedUrls: List<String> = emptyList()
50
60
  private var accessControlEnabled: Boolean = false
51
61
  private val offlineAssetPath = "offline/offline.html"
52
62
  private val offlineAssetUrl = "file:///android_asset/$offlineAssetPath"
63
+ private var scrollSessionOpen = false
64
+ private lateinit var scrollGestureDetector: GestureDetector
53
65
  private var offlinePageVisible = false
54
66
  private var lastTargetUrl: String? = null
55
67
  private var activeOfflineRouteOrigin: String? = null
@@ -62,8 +74,16 @@ class CustomWebView(
62
74
  private var assetLoadFailures = 0
63
75
 
64
76
  init {
77
+ if (BuildConfig.DEBUG) {
78
+ PerfEventBuffer.add(JSONObject().apply {
79
+ put("type", "boot-webview-constructed")
80
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
81
+ put("thread", Thread.currentThread().name)
82
+ })
83
+ }
65
84
  setupFromProperties()
66
85
  cacheManager = WebCacheManager(context, properties)
86
+ if (profilerEnabled) metricsMonitor.attachWebView(webView)
67
87
  offlineCacheService = OfflineCacheService(context)
68
88
  setupWebView()
69
89
  }
@@ -90,6 +110,7 @@ class CustomWebView(
90
110
 
91
111
  // Parse buildOptimisation property
92
112
  buildOptimisation = properties.getProperty("buildOptimisation", "false").toBoolean()
113
+ profilerEnabled = PerfEventBuffer.isEnabled()
93
114
 
94
115
  // Load allowed URLs from properties
95
116
  allowedUrls = properties.getProperty("accessControl.allowedUrls", "")
@@ -162,12 +183,26 @@ class CustomWebView(
162
183
  }
163
184
  }
164
185
 
186
+ private fun loadTopLevelUrl(url: String) {
187
+ if (BuildConfig.DEBUG) {
188
+ val navigation = profilerNavigationState.begin(url)
189
+ if (navigation.shouldReset) PerfEventBuffer.reset()
190
+ PerfEventBuffer.add(JSONObject().apply {
191
+ put("type", "boot-load-url")
192
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
193
+ put("url", navigation.url)
194
+ put("thread", Thread.currentThread().name)
195
+ })
196
+ }
197
+ loadUrlInternal(url)
198
+ }
199
+
165
200
  fun loadUrl(url: String) {
166
201
  lastTargetUrl = url
167
202
  offlinePageVisible = false
168
203
  visibleOfflineSnapshotUrl = null
169
204
  activeOfflineRouteOrigin = null
170
- loadUrlInternal(url)
205
+ loadTopLevelUrl(url)
171
206
  }
172
207
 
173
208
  fun updateLastTargetUrl(url: String) {
@@ -185,7 +220,7 @@ class CustomWebView(
185
220
  offlinePageVisible = true
186
221
  visibleOfflineSnapshotUrl = null
187
222
  activeOfflineRouteOrigin = null
188
- loadUrlInternal(offlineAssetUrl)
223
+ loadTopLevelUrl(offlineAssetUrl)
189
224
  if (BuildConfig.DEBUG) {
190
225
  Log.d(TAG, "📴 Showing offline page from assets: $offlineAssetPath")
191
226
  }
@@ -228,7 +263,7 @@ class CustomWebView(
228
263
  offlinePageVisible = false
229
264
  visibleOfflineSnapshotUrl = snapshotUrl
230
265
  updateActiveOfflineRoute(url, true)
231
- loadUrlInternal(url)
266
+ loadTopLevelUrl(url)
232
267
  if (BuildConfig.DEBUG) {
233
268
  Log.d(TAG, "📴 Loading cached offline route through request interceptor: $url")
234
269
  }
@@ -304,6 +339,7 @@ class CustomWebView(
304
339
 
305
340
  fun destroy() {
306
341
  job.cancel()
342
+ metricsMonitor.detachWebView()
307
343
  webView.destroy()
308
344
  }
309
345
 
@@ -335,6 +371,13 @@ class CustomWebView(
335
371
  if (BuildConfig.DEBUG) {
336
372
  Log.d(TAG, "🚀 Hardware acceleration enabled - Thread: ${Thread.currentThread().name}")
337
373
  }
374
+ BridgeUtils.emitPerfEvent(webView, JSONObject().apply {
375
+ put("type", "hw-accel-change")
376
+ put("state", "on")
377
+ put("trigger", "cleanup")
378
+ put("thread", Thread.currentThread().name)
379
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
380
+ })
338
381
  }
339
382
  }
340
383
 
@@ -345,6 +388,13 @@ class CustomWebView(
345
388
  if (BuildConfig.DEBUG) {
346
389
  Log.d(TAG, "⚫ Hardware acceleration disabled - Thread: ${Thread.currentThread().name}")
347
390
  }
391
+ BridgeUtils.emitPerfEvent(webView, JSONObject().apply {
392
+ put("type", "hw-accel-change")
393
+ put("state", "off")
394
+ put("trigger", "cache-serve")
395
+ put("thread", Thread.currentThread().name)
396
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
397
+ })
348
398
  }
349
399
  }
350
400
 
@@ -472,7 +522,7 @@ class CustomWebView(
472
522
  }
473
523
  offlinePageVisible = false
474
524
  lastTargetUrl = target
475
- loadUrlInternal(target)
525
+ loadTopLevelUrl(target)
476
526
  } else if (BuildConfig.DEBUG) {
477
527
  Log.w(TAG, "🔄 Retry requested but no target URL is known")
478
528
  }
@@ -718,6 +768,13 @@ class CustomWebView(
718
768
 
719
769
  @SuppressLint("SetJavaScriptEnabled")
720
770
  private fun setupWebView() {
771
+ if (profilerEnabled && WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
772
+ WebViewCompat.addDocumentStartJavaScript(
773
+ webView,
774
+ "window.__CATALYST_PROFILER_ENABLED = true;",
775
+ setOf("*")
776
+ )
777
+ }
721
778
  if (BuildConfig.DEBUG) {
722
779
  Log.d(TAG, "🌐 Setting up WebView on thread: ${Thread.currentThread().name}")
723
780
  }
@@ -1057,14 +1114,53 @@ class CustomWebView(
1057
1114
  Log.d(TAG, "⏳ Page load started for: $url - Hardware Acceleration: $isHardwareAccelerationEnabled")
1058
1115
  Log.d(FLOW_TAG, "PAGE started url=$url online=${NetworkUtils.getCurrentStatus(context).isOnline} offlinePageVisible=$offlinePageVisible visibleSnapshot=${visibleOfflineSnapshotUrl ?: "none"} activeOrigin=${activeOfflineRouteOrigin ?: "none"}")
1059
1116
  }
1117
+
1118
+ if (profilerEnabled) {
1119
+ val nativeNow = android.os.SystemClock.elapsedRealtime()
1120
+ view?.evaluateJavascript(
1121
+ "window.__CATALYST_PROFILER_ENABLED = true; window.__NATIVE_TIME_OFFSET = $nativeNow - performance.now();",
1122
+ null
1123
+ )
1124
+ }
1125
+ if (profilerEnabled && url != null) {
1126
+ val nativeNow = android.os.SystemClock.elapsedRealtime()
1127
+ PerfEventBuffer.add(JSONObject().apply {
1128
+ put("type", "boot-page-started")
1129
+ put("nativeTime", nativeNow)
1130
+ put("url", url)
1131
+ })
1132
+ PerfEventBuffer.add(JSONObject().apply {
1133
+ put("type", "page-load-start")
1134
+ put("nativeTime", nativeNow)
1135
+ put("url", url)
1136
+ })
1137
+ }
1060
1138
  }
1061
1139
 
1062
1140
  override fun onPageFinished(view: WebView?, url: String?) {
1063
1141
  progressBar.visibility = View.GONE
1064
1142
  if(!isInitialPageLoaded){
1065
1143
  isInitialPageLoaded = true
1144
+ metricsMonitor.markAppStartComplete()
1066
1145
  }
1067
1146
  url?.let { metricsMonitor.trackPageLoadEnd(it) }
1147
+
1148
+ val pageFinishedNative = android.os.SystemClock.elapsedRealtime()
1149
+ if (profilerEnabled && url != null) {
1150
+ PerfEventBuffer.add(JSONObject().apply {
1151
+ put("type", "boot-page-finished")
1152
+ put("nativeTime", pageFinishedNative)
1153
+ put("url", url)
1154
+ })
1155
+ val pageStart = view?.tag as? Long
1156
+ PerfEventBuffer.add(JSONObject().apply {
1157
+ put("type", "page-load-end")
1158
+ put("nativeTime", pageFinishedNative)
1159
+ put("url", url)
1160
+ put("durationMs", if (pageStart != null) System.currentTimeMillis() - pageStart else 0L)
1161
+ })
1162
+ if (view != null) PerfEventBuffer.scheduleFlush(view)
1163
+ }
1068
1164
  if (BuildConfig.DEBUG) {
1069
1165
  val startTime = view?.tag as? Long ?: return
1070
1166
  val loadTime = System.currentTimeMillis() - startTime
@@ -1095,6 +1191,15 @@ class CustomWebView(
1095
1191
  if (BuildConfig.DEBUG) {
1096
1192
  Log.e(FLOW_TAG, "PAGE resource-error mainFrame=${request?.isForMainFrame} method=${request?.method} code=${error.errorCode} description=${error.description} url=${request?.url}")
1097
1193
  }
1194
+ if (view != null && request?.isForMainFrame == true) {
1195
+ BridgeUtils.emitPerfEvent(view, JSONObject().apply {
1196
+ put("type", "page-load-error")
1197
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
1198
+ put("url", request.url?.toString() ?: "")
1199
+ put("errorCode", error.errorCode)
1200
+ put("description", error.description?.toString() ?: "")
1201
+ })
1202
+ }
1098
1203
  }
1099
1204
 
1100
1205
  val failedUrl = request?.url?.toString()
@@ -1204,5 +1309,39 @@ class CustomWebView(
1204
1309
 
1205
1310
  // Only enable WebView debugging in debug builds
1206
1311
  WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
1312
+
1313
+ if (profilerEnabled) {
1314
+ scrollGestureDetector = GestureDetector(context,
1315
+ object : GestureDetector.SimpleOnGestureListener() {
1316
+ override fun onScroll(
1317
+ e1: MotionEvent?,
1318
+ e2: MotionEvent,
1319
+ distanceX: Float,
1320
+ distanceY: Float
1321
+ ): Boolean {
1322
+ if (!scrollSessionOpen) {
1323
+ scrollSessionOpen = true
1324
+ BridgeUtils.emitPerfEvent(webView, JSONObject().apply {
1325
+ put("type", "scroll-start")
1326
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
1327
+ })
1328
+ }
1329
+ return false
1330
+ }
1331
+ }
1332
+ )
1333
+ webView.setOnTouchListener { _, event ->
1334
+ scrollGestureDetector.onTouchEvent(event)
1335
+ if (scrollSessionOpen &&
1336
+ (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL)) {
1337
+ scrollSessionOpen = false
1338
+ BridgeUtils.emitPerfEvent(webView, JSONObject().apply {
1339
+ put("type", "scroll-end")
1340
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
1341
+ })
1342
+ }
1343
+ false
1344
+ }
1345
+ }
1207
1346
  }
1208
1347
  }
@@ -274,6 +274,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
274
274
  properties.setProperty("buildType", if (BuildConfig.DEBUG) "debug" else "release")
275
275
  properties.setProperty("buildOptimisation", (!BuildConfig.DEBUG).toString())
276
276
  }
277
+ io.yourname.androidproject.utils.PerfEventBuffer.configure(
278
+ properties.getProperty("profiler.enabled", "false").toBoolean()
279
+ )
277
280
 
278
281
  configureEdgeToEdge()
279
282
 
@@ -286,16 +289,29 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
286
289
  })
287
290
  }
288
291
 
289
- // Initialize MetricsMonitor
292
+ // Initialize MetricsMonitor — recordAppStart() resets the cold-start clock for this
293
+ // Activity lifecycle. The singleton may have been constructed in a previous session
294
+ // (hot restart / Activity recreation), so appStartTime would be stale without this.
290
295
  metricsMonitor = MetricsMonitor.getInstance(this)
296
+ metricsMonitor.recordAppStart()
297
+
298
+ // Boot timing: record activity onCreate as the earliest boot event
299
+ if (BuildConfig.DEBUG) {
300
+ io.yourname.androidproject.utils.PerfEventBuffer.reset()
301
+ io.yourname.androidproject.utils.PerfEventBuffer.add(org.json.JSONObject().apply {
302
+ put("type", "boot-activity-created")
303
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
304
+ put("thread", Thread.currentThread().name)
305
+ })
306
+ }
291
307
 
292
308
  // Setup UI
293
309
  binding = ActivityMainBinding.inflate(layoutInflater)
294
310
  supportActionBar?.hide()
295
311
  setContentView(binding.root)
296
312
 
297
- // Initialize keyboard utility
298
- keyboardUtil = KeyboardUtil(this, binding.webviewContainer)
313
+ // Initialize keyboard utility (pass webView so keyboard events reach WebPerfCollector)
314
+ keyboardUtil = KeyboardUtil(this, binding.webviewContainer, binding.webview)
299
315
  keyboardUtil.initialize()
300
316
 
301
317
  // Enable hardware acceleration for the window
@@ -351,6 +367,12 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
351
367
  onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
352
368
  override fun handleOnBackPressed() {
353
369
  if (customWebView.canGoBack()) {
370
+ if (BuildConfig.DEBUG) {
371
+ BridgeUtils.emitPerfEvent(customWebView.getWebView(), org.json.JSONObject().apply {
372
+ put("type", "navigation-back")
373
+ put("nativeTime", android.os.SystemClock.elapsedRealtime())
374
+ })
375
+ }
354
376
  customWebView.goBack()
355
377
  } else {
356
378
  // Disable this callback and let the system handle back press
@@ -389,9 +411,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
389
411
  customWebView.showOfflineRouteOrOfflinePage(currentUrl)
390
412
  }
391
413
  }
392
-
393
- metricsMonitor.markAppStartComplete()
394
-
395
414
  } catch (e: Exception) {
396
415
  Log.e(TAG, "Failed to load initial URL: ${e.message}")
397
416
  // TODO: Add HTML file workflow - error.html not available yet
@@ -453,6 +472,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
453
472
  }
454
473
 
455
474
  override fun onDestroy() {
475
+ io.yourname.androidproject.utils.PerfEventBuffer.reset()
456
476
  // Log all performance metrics before destroying
457
477
  if (BuildConfig.DEBUG) {
458
478
  Log.d(TAG, "🏁 App shutting down - logging final metrics...")