catalyst-core 0.2.0-beta.1 → 0.2.0-beta.2

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 (29) hide show
  1. package/dist/native/androidProject/app/build.gradle.kts +25 -8
  2. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +239 -75
  3. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +3 -2
  4. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +1 -1
  5. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/OfflineCacheService.kt +374 -0
  6. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +136 -26
  7. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +7 -2
  8. package/dist/native/buildAndroid/assets.js +2 -2
  9. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +39 -4
  10. package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineCacheService.swift +683 -0
  11. package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineURLSchemeHandler.swift +94 -0
  12. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +19 -6
  13. package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +1 -0
  14. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +1 -0
  15. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +15 -1
  16. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +82 -2
  17. package/dist/native/renameAndroidProject.js +2 -2
  18. package/dist/offline/catalyst-sw.js +1 -0
  19. package/dist/offline/registerServiceWorker.js +1 -0
  20. package/dist/router/ClientRouter.js +1 -1
  21. package/dist/scripts/build.js +1 -1
  22. package/dist/scripts/devBuild.js +1 -1
  23. package/dist/scripts/generateOfflineManifest.js +1 -0
  24. package/dist/server/expressServer.js +2 -1
  25. package/dist/server/renderer/document/Body.js +1 -1
  26. package/dist/server/renderer/handler.js +3 -3
  27. package/dist/webpack/base.babel.js +1 -1
  28. package/mcp_v2/knowledge-base.json +13 -0
  29. package/package.json +1 -1
@@ -0,0 +1,94 @@
1
+ import Foundation
2
+ import WebKit
3
+ import os
4
+
5
+ private let offlineSchemeLogger = Logger(
6
+ subsystem: Bundle.main.bundleIdentifier ?? "com.app",
7
+ category: "CatalystOfflineScheme"
8
+ )
9
+
10
+ final class OfflineURLSchemeHandler: NSObject, WKURLSchemeHandler {
11
+ static let shared = OfflineURLSchemeHandler()
12
+
13
+ private override init() {
14
+ super.init()
15
+ }
16
+
17
+ func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
18
+ guard let offlineURL = urlSchemeTask.request.url,
19
+ let originalURL = OfflineCacheService.shared.originalURL(forOfflineURL: offlineURL) else {
20
+ offlineSchemeLogger.info("SCHEME miss reason=invalid-url url=\(urlSchemeTask.request.url?.absoluteString ?? "nil")")
21
+ urlSchemeTask.didFailWithError(URLError(.badURL))
22
+ return
23
+ }
24
+
25
+ Task {
26
+ if let snapshot = OfflineCacheService.shared.snapshotData(for: originalURL) {
27
+ let data = OfflineCacheService.shared.rewriteSameOriginReferencesForOffline(
28
+ snapshot,
29
+ originalURL: originalURL
30
+ ) ?? snapshot
31
+ send(
32
+ data: data,
33
+ mimeType: "text/html",
34
+ encoding: "utf-8",
35
+ url: offlineURL,
36
+ task: urlSchemeTask
37
+ )
38
+ offlineSchemeLogger.info("SCHEME document-hit bytes=\(data.count) original=\(originalURL.absoluteString) offline=\(offlineURL.absoluteString)")
39
+ return
40
+ }
41
+
42
+ var originalRequest = URLRequest(url: originalURL)
43
+ originalRequest.httpMethod = "GET"
44
+ let (cachedData, cacheState, mimeType) = await CacheManager.shared.getCachedResource(for: originalRequest)
45
+ guard let cachedData, cacheState != .expired else {
46
+ offlineSchemeLogger.info("SCHEME asset-miss original=\(originalURL.absoluteString) offline=\(offlineURL.absoluteString)")
47
+ urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
48
+ return
49
+ }
50
+
51
+ let data = OfflineCacheService.shared.rewriteSameOriginReferencesForOffline(
52
+ cachedData,
53
+ originalURL: originalURL
54
+ ) ?? cachedData
55
+ send(
56
+ data: data,
57
+ mimeType: mimeType ?? OfflineCacheService.shared.mimeType(for: originalURL),
58
+ encoding: "utf-8",
59
+ url: offlineURL,
60
+ task: urlSchemeTask
61
+ )
62
+ offlineSchemeLogger.info("SCHEME asset-hit state=\(String(describing: cacheState)) bytes=\(data.count) mime=\(mimeType ?? "unknown") original=\(originalURL.absoluteString)")
63
+ }
64
+ }
65
+
66
+ func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
67
+ offlineSchemeLogger.debug("SCHEME stop url=\(urlSchemeTask.request.url?.absoluteString ?? "nil")")
68
+ }
69
+
70
+ private func send(
71
+ data: Data,
72
+ mimeType: String,
73
+ encoding: String,
74
+ url: URL,
75
+ task: WKURLSchemeTask
76
+ ) {
77
+ let headers = [
78
+ "Content-Type": "\(mimeType); charset=\(encoding)",
79
+ "Access-Control-Allow-Origin": "*",
80
+ "Cache-Control": "no-store"
81
+ ]
82
+ let response = HTTPURLResponse(
83
+ url: url,
84
+ statusCode: 200,
85
+ httpVersion: "HTTP/1.1",
86
+ headerFields: headers
87
+ )!
88
+ task.didReceive(response)
89
+ task.didReceive(data)
90
+ task.didFinish()
91
+ }
92
+ }
93
+
94
+ extension OfflineURLSchemeHandler: @unchecked Sendable {}
@@ -7,16 +7,20 @@ private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.app"
7
7
  final class ResourceURLProtocol: URLProtocol {
8
8
  private var dataTask: URLSessionDataTask?
9
9
  private static let handledKey = "ResourceURLProtocolHandled"
10
+ private static var isRegistered = false
10
11
 
11
12
  // MARK: - Protocol Registration
12
13
  static func register() {
13
- logger.warning("⚠️ ResourceURLProtocol registration is DISABLED to preserve POST bodies")
14
- return
15
-
14
+ guard !isRegistered else { return }
15
+ URLProtocol.registerClass(self)
16
+ isRegistered = true
17
+ logger.info("✅ ResourceURLProtocol registered for GET cache interception")
16
18
  }
17
19
 
18
20
  static func unregister() {
21
+ guard isRegistered else { return }
19
22
  URLProtocol.unregisterClass(self)
23
+ isRegistered = false
20
24
  }
21
25
 
22
26
  // MARK: - URLProtocol
@@ -45,7 +49,15 @@ final class ResourceURLProtocol: URLProtocol {
45
49
  return false
46
50
  }
47
51
 
48
- let shouldCache = CacheManager.shared.shouldCacheURL(url)
52
+ if request.value(forHTTPHeaderField: "X-Catalyst-Offline-Snapshot-Fetch") == "1" ||
53
+ url.path == "/catalyst-offline-manifest.json" ||
54
+ url.path == "/catalyst-sw.js" ||
55
+ url.path == "/offline.html" {
56
+ logger.info("🔍 ResourceURLProtocol.canInit: Skipping internal Catalyst offline request")
57
+ return false
58
+ }
59
+
60
+ let shouldCache = CacheManager.shared.shouldCacheRequest(request)
49
61
  logger.info("🔍 ResourceURLProtocol.canInit: GET request, shouldCache=\(shouldCache)")
50
62
  return shouldCache
51
63
  }
@@ -115,15 +127,16 @@ final class ResourceURLProtocol: URLProtocol {
115
127
  // If not in cache or expired, load from network using URLSession
116
128
  logger.info("🌐 Fetching from network: \(url.absoluteString)")
117
129
 
118
- let config = URLSessionConfiguration.default
130
+ let config = URLSessionConfiguration.ephemeral
119
131
  config.requestCachePolicy = .reloadIgnoringLocalCacheData
132
+ config.protocolClasses = []
120
133
  let session = URLSession(configuration: config)
121
134
 
122
135
  let (data, response) = try await session.data(for: mutableRequest as URLRequest)
123
136
 
124
137
  if let httpResponse = response as? HTTPURLResponse {
125
138
  // Cache the response if it's valid
126
- if CacheManager.shared.isCacheableResponse(httpResponse) {
139
+ if CacheManager.shared.isCacheableResponse(httpResponse, for: request) {
127
140
  CacheManager.shared.storeCachedResponse(httpResponse, data: data, for: request)
128
141
  }
129
142
 
@@ -49,6 +49,7 @@ public struct SafeAreaInsets: Codable, Equatable, CustomStringConvertible {
49
49
  "X-Safe-Area-Right": String(Int(right)),
50
50
  "X-Safe-Area-Bottom": String(Int(bottom)),
51
51
  "X-Safe-Area-Left": String(Int(left)),
52
+ "X-Catalyst-Native-WebView": "1",
52
53
  // Prevent caching of SSR response so updated headers are always used
53
54
  "Cache-Control": "no-cache, no-store, must-revalidate",
54
55
  "Pragma": "no-cache"
@@ -508,6 +508,7 @@ class BridgeCommandHandler {
508
508
  store.removeData(ofTypes: dataTypes, modifiedSince: .distantPast) { [weak self] in
509
509
  guard let self else { return }
510
510
  CacheManager.shared.clearCache()
511
+ OfflineCacheService.shared.clearAll()
511
512
  commandLogger.debug("clearWebData complete")
512
513
  DispatchQueue.main.async {
513
514
  self.delegate?.sendJSONCallback(eventName: "ON_WEB_DATA_CLEARED", data: [
@@ -38,6 +38,14 @@ public struct WebView: UIViewRepresentable, Equatable {
38
38
  logWithTimestamp("🔨 makeUIView() started")
39
39
 
40
40
  let configuration = WKWebViewConfiguration()
41
+ configuration.setURLSchemeHandler(
42
+ OfflineURLSchemeHandler.shared,
43
+ forURLScheme: OfflineCacheService.offlineHTTPScheme
44
+ )
45
+ configuration.setURLSchemeHandler(
46
+ OfflineURLSchemeHandler.shared,
47
+ forURLScheme: OfflineCacheService.offlineHTTPSScheme
48
+ )
41
49
 
42
50
  // Hook kept for legacy behavior; currently a no-op on iOS 15+.
43
51
  WebKitConfig.applySharedProcessPoolIfNeeded(to: configuration)
@@ -98,7 +106,13 @@ public struct WebView: UIViewRepresentable, Equatable {
98
106
  logWithTimestamp("✅ webView.load() returned (took \(String(format: "%.2f", loadTime))ms)")
99
107
  } else {
100
108
  logWithTimestamp("📴 Device offline on launch, showing offline page")
101
- _ = navigationDelegate.showOfflinePage(in: webView)
109
+ if OfflineCacheService.shared.loadSnapshot(in: webView, for: url) {
110
+ viewModel.setLoading(false, fromCache: true)
111
+ } else if navigationDelegate.showOfflinePage(in: webView) {
112
+ viewModel.setLoading(false, fromCache: true)
113
+ } else {
114
+ viewModel.setLoading(false, fromCache: false)
115
+ }
102
116
  }
103
117
  }
104
118
 
@@ -9,6 +9,7 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
9
9
  private let offlineFileName = "offline.html"
10
10
  private let offlineSubdirectory = "offline"
11
11
  private var offlinePageVisible = false
12
+ private var offlineSnapshotVisibleURL: String?
12
13
  private var lastTargetURL: URL?
13
14
  private let initialURL: URL?
14
15
  private weak var cameraManager: NativeCameraManager?
@@ -46,6 +47,7 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
46
47
  // Only inject on main frame to match Android behavior (not on subresources)
47
48
  let isMainFrame = navigationAction.targetFrame?.isMainFrame ?? false
48
49
  let isHttpScheme = ["http", "https"].contains(url.scheme?.lowercased() ?? "")
50
+ let isCatalystOfflineURL = OfflineCacheService.shared.originalURL(forOfflineURL: url) != nil
49
51
 
50
52
  if isMainFrame && isHttpScheme && httpMethod == "GET" {
51
53
  let safeAreaHeaders = viewModel.getSafeAreaHeaders()
@@ -86,7 +88,7 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
86
88
  return
87
89
  }
88
90
 
89
- if URLWhitelistManager.shared.isAccessControlEnabled {
91
+ if URLWhitelistManager.shared.isAccessControlEnabled && !isCatalystOfflineURL {
90
92
 
91
93
  // Check if URL is an external domain
92
94
  let isExternal = URLWhitelistManager.shared.isExternalDomain(url)
@@ -107,8 +109,10 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
107
109
  }
108
110
 
109
111
  logger.info("✅ URL passed whitelist checks, allowing navigation: \(url.absoluteString)")
110
- } else {
112
+ } else if !isCatalystOfflineURL {
111
113
  logger.info("⚠️ Access control disabled, allowing all navigation: \(url.absoluteString)")
114
+ } else {
115
+ logger.info("📴 Catalyst offline URL allowed: \(url.absoluteString)")
112
116
  }
113
117
 
114
118
  if ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
@@ -120,6 +124,36 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
120
124
  let isCacheableMethod = httpMethod == "GET"
121
125
  logger.info("🔍 Cache check - Method: \(httpMethod), isCacheable: \(isCacheableMethod)")
122
126
 
127
+ if isMainFrame && isHttpScheme && isCacheableMethod {
128
+ if NetworkMonitor.shared.currentStatus.isOnline {
129
+ let isOfflineRoute = await OfflineCacheService.shared.prepareActiveOfflineRoute(
130
+ for: navigationAction.request,
131
+ webView: webView
132
+ )
133
+ offlineSnapshotVisibleURL = nil
134
+ if !isOfflineRoute {
135
+ logger.info("⏭️ Main-frame route is not currently offline eligible: \(url.absoluteString)")
136
+ }
137
+ OfflineCacheService.shared.storeRouteSnapshot(
138
+ for: navigationAction.request,
139
+ webView: webView
140
+ )
141
+ } else {
142
+ let loadedSnapshot = await MainActor.run {
143
+ loadCachedSnapshotIfNeeded(in: webView, for: url)
144
+ }
145
+
146
+ if loadedSnapshot {
147
+ logger.info("📴 Serving cached offline route snapshot: \(url.absoluteString)")
148
+ decisionHandler(.cancel)
149
+ return
150
+ }
151
+ OfflineCacheService.shared.clearActiveOfflineRoute()
152
+ }
153
+ } else if isMainFrame && isHttpScheme {
154
+ OfflineCacheService.shared.clearActiveOfflineRoute()
155
+ }
156
+
123
157
  if isCacheableMethod && CacheManager.shared.shouldCacheURL(url) {
124
158
  logger.info("🎯 URL matches cache pattern: \(url.absoluteString)")
125
159
 
@@ -259,6 +293,18 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
259
293
  logger.error("Navigation failed: \(error.localizedDescription)")
260
294
 
261
295
  guard shouldShowOfflinePage(for: error), let webView else { return }
296
+ if let targetURL = lastTargetURL ?? webView.url,
297
+ isOfflineSnapshotVisible(for: targetURL) {
298
+ logger.info("📴 Ignoring repeated error for visible offline route snapshot")
299
+ return
300
+ }
301
+
302
+ if let targetURL = lastTargetURL ?? webView.url,
303
+ loadCachedSnapshotIfNeeded(in: webView, for: targetURL) {
304
+ logger.info("📴 Showing cached offline route snapshot")
305
+ return
306
+ }
307
+
262
308
  if showOfflinePage(in: webView) {
263
309
  logger.info("📴 Showing offline fallback page")
264
310
  } else {
@@ -320,6 +366,8 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
320
366
  }
321
367
 
322
368
  offlinePageVisible = false
369
+ offlineSnapshotVisibleURL = nil
370
+ OfflineCacheService.shared.clearActiveOfflineRoute()
323
371
  logWithTimestamp("🔄 Retry requested, online. Reloading: \(targetURL.absoluteString)")
324
372
  webView.load(URLRequest(url: targetURL))
325
373
  }
@@ -351,8 +399,11 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
351
399
  }
352
400
 
353
401
  offlinePageVisible = true
402
+ offlineSnapshotVisibleURL = nil
403
+ OfflineCacheService.shared.clearActiveOfflineRoute()
354
404
  let readAccessURL = offlineURL.deletingLastPathComponent()
355
405
  webView.loadFileURL(offlineURL, allowingReadAccessTo: readAccessURL)
406
+ viewModel.setLoading(false, fromCache: true)
356
407
  return true
357
408
  }
358
409
 
@@ -396,4 +447,33 @@ class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
396
447
 
397
448
  return true
398
449
  }
450
+
451
+ private func loadCachedSnapshotIfNeeded(in webView: WKWebView, for url: URL) -> Bool {
452
+ let key = snapshotKey(for: url)
453
+ if offlineSnapshotVisibleURL == key {
454
+ return true
455
+ }
456
+
457
+ if OfflineCacheService.shared.loadSnapshot(in: webView, for: url) {
458
+ offlinePageVisible = false
459
+ offlineSnapshotVisibleURL = key
460
+ viewModel.setLoading(false, fromCache: true)
461
+ return true
462
+ }
463
+
464
+ offlineSnapshotVisibleURL = nil
465
+ return false
466
+ }
467
+
468
+ private func isOfflineSnapshotVisible(for url: URL) -> Bool {
469
+ offlineSnapshotVisibleURL == snapshotKey(for: url)
470
+ }
471
+
472
+ private func snapshotKey(for url: URL) -> String {
473
+ guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
474
+ return url.absoluteString
475
+ }
476
+ components.fragment = nil
477
+ return components.url?.absoluteString ?? url.absoluteString
478
+ }
399
479
  }
@@ -56,9 +56,9 @@ const keystorePath=_path.default.join(tempDeploymentPath,"app","keystore.jks");i
56
56
  await cleanupTempDeployment(tempDeploymentPath);return{...aabResult,finalAabPath,finalAabName,outputDir};}catch(error){throw new Error(`Failed to finalize AAB: ${error.message}`);}}async function cleanupTempDeployment(tempDeploymentPath){if(!tempDeploymentPath||!_fs.default.existsSync(tempDeploymentPath)){return;}try{progress.log("Cleaning up temporary deployment files...","info");(0,_utils.runCommand)(`rm -rf "${tempDeploymentPath}"`);progress.log("Temporary files cleaned up successfully","success");}catch(error){progress.log(`Warning: Could not cleanup temporary directory: ${error.message}`,"warning");}}async function updateFileContents(projectPaths,androidConfig){const{newProjectPath}=projectPaths;const{oldProjectName,newProjectName,packageName}=androidConfig;progress.log("Updating file contents and names...","info");try{// STEP 1: Update packageName in build.gradle.kts and source files
57
57
  if(packageName){const OLD_PACKAGE="io.yourname.androidproject";progress.log(`Replacing package name: ${OLD_PACKAGE} → ${packageName}`,"info");// Update build.gradle.kts
58
58
  const buildGradlePath=_path.default.join(newProjectPath,"app","build.gradle.kts");if(_fs.default.existsSync(buildGradlePath)){let content=_fs.default.readFileSync(buildGradlePath,"utf8");content=content.replace(/namespace = "io\.yourname\.androidproject"/g,`namespace = "${packageName}"`);content=content.replace(/applicationId = "io\.yourname\.androidproject"/g,`applicationId = "${packageName}"`);_fs.default.writeFileSync(buildGradlePath,content,"utf8");progress.log(`✅ Updated build.gradle.kts with package: ${packageName}`,"success");}// Update source directory structure for all source sets
59
- const sourceSets=["main","test","noFcm","withFcm"];const newPackageParts=packageName.split(".");let totalUpdatedFiles=0;for(const sourceSet of sourceSets){const oldPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java","io","yourname","androidproject");if(_fs.default.existsSync(oldPackagePath)){const newPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java",...newPackageParts);// Create new package directory
59
+ const sourceSets=["main","test","noFcm","withFcm"];const newPackageParts=packageName.split(".");let totalUpdatedFiles=0;for(const sourceSet of sourceSets){const oldPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java","io","yourname","androidproject");if(_fs.default.existsSync(oldPackagePath)){const newPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java",...newPackageParts);const packagePathChanged=_path.default.resolve(oldPackagePath)!==_path.default.resolve(newPackagePath);if(packagePathChanged){// Create new package directory
60
60
  const newPackageDir=_path.default.dirname(newPackagePath);_fs.default.mkdirSync(newPackageDir,{recursive:true})// Move source files
61
- ;(0,_utils.runCommand)(`mv "${oldPackagePath}" "${newPackagePath}"`);progress.log(`✅ Moved ${sourceSet} source files to: ${newPackagePath}`,"success");// Update package declarations in source files
61
+ ;(0,_utils.runCommand)(`mv "${oldPackagePath}" "${newPackagePath}"`);progress.log(`✅ Moved ${sourceSet} source files to: ${newPackagePath}`,"success");}else{progress.log(`Package path unchanged for ${sourceSet}; skipping source directory move`,"info");}// Update package declarations in source files
62
62
  const findCommand=`find "${newPackagePath}" -type f \\( -name "*.kt" -o -name "*.java" \\)`;const sourceFiles=(0,_utils.runCommand)(findCommand).trim().split("\n").filter(f=>f.trim());let updatedCount=0;for(const file of sourceFiles){let content=_fs.default.readFileSync(file,"utf8");if(content.includes(OLD_PACKAGE)){content=content.replace(new RegExp(OLD_PACKAGE,"g"),packageName);_fs.default.writeFileSync(file,content,"utf8");updatedCount++;}}totalUpdatedFiles+=updatedCount;progress.log(`✅ Updated package declarations in ${updatedCount} ${sourceSet} files`,"success");// Clean up old empty directories
63
63
  try{;(0,_utils.runCommand)(`find "${_path.default.join(newProjectPath,"app","src",sourceSet,"java")}" -type d -empty -delete`);}catch(err){// Ignore cleanup errors
64
64
  }}}if(totalUpdatedFiles>0){progress.log(`✅ Total package declarations updated: ${totalUpdatedFiles} files`,"success");}// Update AndroidManifest.xml
@@ -0,0 +1 @@
1
+ "use strict";/* eslint-disable no-restricted-globals */const MANIFEST_URL="/catalyst-offline-manifest.json";const OFFLINE_URL="/offline.html";const CACHE_PREFIX="catalyst-offline";const MANIFEST_CACHE=`${CACHE_PREFIX}-manifest`;self.addEventListener("install",event=>{event.waitUntil((async()=>{await loadManifest(true);await cacheOfflineFallback();await self.skipWaiting();})());});self.addEventListener("activate",event=>{event.waitUntil((async()=>{await cleanupOldCaches();await self.clients.claim();})());});self.addEventListener("fetch",event=>{const{request}=event;if(request.method!=="GET")return;if(request.mode==="navigate"||request.destination==="document"){event.respondWith(handleNavigation(request));return;}if(["script","style","font","image"].includes(request.destination)){event.respondWith(handleAsset(request));}});async function loadManifest(preferNetwork){const cache=await caches.open(MANIFEST_CACHE);if(preferNetwork){try{const response=await fetch(MANIFEST_URL,{cache:"no-store"});if(response.ok){await cache.put(MANIFEST_URL,response.clone());const manifest=await response.json();cleanupOldCaches(manifest);return manifest;}}catch(_){}}const cached=await cache.match(MANIFEST_URL);if(cached)return cached.json();return{buildId:"unknown",routes:[]};}async function cacheOfflineFallback(){try{const cache=await caches.open(`${CACHE_PREFIX}-fallback`);const response=await fetch(OFFLINE_URL,{cache:"no-store"});if(response.ok)await cache.put(OFFLINE_URL,response);}catch(_){}}function routeCacheName(manifest){return`${CACHE_PREFIX}-routes-${manifest.buildId}`;}function assetCacheName(manifest){return`${CACHE_PREFIX}-assets-${manifest.buildId}`;}async function cleanupOldCaches(manifest){manifest=manifest||(await loadManifest(false));if(!manifest.buildId||manifest.buildId==="unknown")return;const keep=new Set([MANIFEST_CACHE,`${CACHE_PREFIX}-fallback`,routeCacheName(manifest),assetCacheName(manifest)]);const cacheNames=await caches.keys();await Promise.all(cacheNames.filter(name=>name.startsWith(CACHE_PREFIX)&&!keep.has(name)).map(name=>caches.delete(name)));}function isOfflineRoute(url,manifest){const{pathname}=new URL(url);return(manifest.routes||[]).some(route=>{try{return new RegExp(route.regex).test(pathname);}catch(_){return false;}});}function canStore(response){return!!(response&&(response.ok||response.type==="opaque"));}async function handleNavigation(request){const manifest=await loadManifest(true);if(!isOfflineRoute(request.url,manifest))return fetch(request);const cache=await caches.open(routeCacheName(manifest));try{const response=await fetch(request);const contentType=response.headers.get("content-type")||"";if(canStore(response)&&contentType.includes("text/html")){await cache.put(request.url,response.clone());}return response;}catch(_){const cached=await cache.match(request.url);if(cached)return cached;const offlineResponse=await caches.match(OFFLINE_URL);if(offlineResponse)return offlineResponse;return fetch(OFFLINE_URL);}}async function handleAsset(request){const manifest=await loadManifest(false);const cache=await caches.open(assetCacheName(manifest));const cached=await cache.match(request);if(cached)return cached;const response=await fetch(request);if(canStore(response))await cache.put(request,response.clone());return response;}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.registerCatalystServiceWorker=registerCatalystServiceWorker;let registered=false;function isNativeWebView(){if(typeof window==="undefined")return false;return!!(window.NativeBridge||window.webkit?.messageHandlers?.NativeBridge);}function registerCatalystServiceWorker(){if(registered)return;if(typeof window==="undefined"||typeof navigator==="undefined")return;if(process.env.NODE_ENV!=="production")return;if(isNativeWebView())return;if(!("serviceWorker"in navigator))return;registered=true;const register=()=>{navigator.serviceWorker.register("/catalyst-sw.js",{scope:"/",updateViaCache:"none"}).catch(()=>{registered=false;});};if(document.readyState==="complete"){register();}else{window.addEventListener("load",register,{once:true});}}
@@ -1,3 +1,3 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _router=require("@tata1mg/router");const{preparedRoutes}=require(`${process.env.src_path}/src/js/routes/utils.js`);/**
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _router=require("@tata1mg/router");var _registerServiceWorker=require("../offline/registerServiceWorker.js");const{preparedRoutes}=require(`${process.env.src_path}/src/js/routes/utils.js`);(0,_registerServiceWorker.registerCatalystServiceWorker)();/**
2
2
  * @returns returns browsers routers (client routes)
3
3
  */const clientRouter=({store,routerInitialState})=>(0,_router.createBrowserRouter)(preparedRoutes({store,routerInitialState}));var _default=exports.default=clientRouter;
@@ -1,3 +1,3 @@
1
1
  "use strict";const path=require("path");const{green,cyan,yellow}=require("picocolors");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);const{arrayToObject,printBundleInformation,runBuildCommands}=require("./scriptUtils.js");/**
2
2
  * @description - creates a production build of the application.
3
- */function build(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --quiet`];console.log("Creating an optimized production build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:false,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run serve"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}build();
3
+ */function build(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} node ./dist/scripts/generateOfflineManifest.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --quiet`];console.log("Creating an optimized production build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:false,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run serve"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}build();
@@ -1,3 +1,3 @@
1
1
  "use strict";const path=require("path");const{green,cyan,yellow}=require("picocolors");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);const{arrayToObject,printBundleInformation,runBuildCommands}=require("./scriptUtils.js");/**
2
2
  * @description - creates a production build of the application.
3
- */function devBuild(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,` cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --quiet`];console.log("Creating an optimized local build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run devServe"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}devBuild();
3
+ */function devBuild(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} node ./dist/scripts/generateOfflineManifest.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --quiet`];console.log("Creating an optimized local build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run devServe"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}devBuild();
@@ -0,0 +1 @@
1
+ "use strict";const fs=require("fs");const path=require("path");function toRoutePath(basePath,routePath,isIndex){if(isIndex||routePath===undefined||routePath===null||routePath===""){return basePath||"/";}const nextPath=String(routePath).startsWith("/")?routePath:`${basePath}/${routePath}`;return`/${nextPath}`.replace(/\/+/g,"/").replace(/\/$/,"")||"/";}function routeRegex(pattern,exact=false){if(pattern==="*"||pattern==="/*")return"^/.*$";if(pattern==="/")return exact?"^/$":"^/.*$";const parts=pattern.split("/").filter(Boolean);const regex=parts.map(part=>{if(part==="*")return".*";if(part.startsWith(":"))return"[^/]+";return part.replace(/[|\\{}()[\]^$+?.]/g,"\\$&");}).join("/");return exact?`^/${regex}$`:`^/${regex}(?:/.*)?$`;}function collectOfflineRoutes(routes,basePath="",parentOffline=false,result=[]){for(const route of Array.isArray(routes)?routes:[]){if(!route||typeof route!=="object")continue;const offline=Object.prototype.hasOwnProperty.call(route,"offline")?route.offline===true:parentOffline;const isIndexRoute=route.index===true||route.path==="";const pattern=toRoutePath(basePath,route.path,isIndexRoute);const regex=routeRegex(pattern,isIndexRoute);if(offline&&!result.some(item=>item.pattern===pattern&&item.regex===regex)){result.push({pattern,regex});}collectOfflineRoutes(route.children,pattern,offline,result);}return result;}function loadAppRoutes(){require("./loadScriptsBeforeServerStarts.js");const emptyModule=()=>null;[".css",".scss",".sass",".less",".png",".jpg",".jpeg",".gif",".svg",".webp"].forEach(ext=>{require.extensions[ext]=emptyModule;});require("@babel/register")({babelrc:false,extensions:[".js",".jsx",".ts",".tsx"],ignore:[/node_modules/],presets:[["@babel/preset-env",{targets:{node:"current"}}],["@babel/preset-react",{runtime:"automatic"}],["@babel/preset-typescript",{isTSX:true,allExtensions:true}]],plugins:["@loadable/babel-plugin"]});const{getRoutes}=require(path.join(process.env.src_path,"src/js/routes/utils.js"));return typeof getRoutes==="function"?getRoutes():[];}function copyIfExists(source,destination){if(fs.existsSync(source))fs.copyFileSync(source,destination);}function main(){const appConfig=require(path.join(process.env.src_path,"config/config.json"));const publicDir=path.join(process.env.src_path,process.env.BUILD_OUTPUT_PATH||appConfig.BUILD_OUTPUT_PATH,"public");const routes=collectOfflineRoutes(loadAppRoutes());fs.mkdirSync(publicDir,{recursive:true});fs.writeFileSync(path.join(publicDir,"catalyst-offline-manifest.json"),`${JSON.stringify({schemaVersion:1,buildId:Date.now().toString(36),routes},null,2)}\n`);copyIfExists(path.join(process.env.src_path,"public/offline.html"),path.join(publicDir,"offline.html"));fs.copyFileSync(path.join(__dirname,"../offline/catalyst-sw.js"),path.join(publicDir,"catalyst-sw.js"));console.log(`Catalyst offline manifest generated: ${routes.length} route(s)`);}main();
@@ -1,4 +1,5 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _path=_interopRequireDefault(require("path"));var _express=_interopRequireDefault(require("express"));var _bodyParser=_interopRequireDefault(require("body-parser"));var _compression=_interopRequireDefault(require("compression"));var _cookieParser=_interopRequireDefault(require("cookie-parser"));var _expressStaticGzip=_interopRequireDefault(require("express-static-gzip"));var _renderer=_interopRequireDefault(require("@catalyst/server/renderer"));var _server=require("@catalyst/template/server/server.js");var _validator=require("@catalyst/server/utils/validator");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const env=process.env.NODE_ENV||"development";const app=(0,_express.default)();app.use(_bodyParser.default.json({limit:"100kb"}));// Handles "bytestream array" content types sent as raw application/* bodies
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _path=_interopRequireDefault(require("path"));var _fs=_interopRequireDefault(require("fs"));var _express=_interopRequireDefault(require("express"));var _bodyParser=_interopRequireDefault(require("body-parser"));var _compression=_interopRequireDefault(require("compression"));var _cookieParser=_interopRequireDefault(require("cookie-parser"));var _expressStaticGzip=_interopRequireDefault(require("express-static-gzip"));var _renderer=_interopRequireDefault(require("@catalyst/server/renderer"));var _server=require("@catalyst/template/server/server.js");var _validator=require("@catalyst/server/utils/validator");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const env=process.env.NODE_ENV||"development";const app=(0,_express.default)();const sendBuiltPublicFile=(res,fileName,headers={})=>{const publicFilePath=_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`,fileName);if(!_fs.default.existsSync(publicFilePath)){res.status(404).end();return;}res.set(headers);res.sendFile(publicFilePath);};app.get("/catalyst-sw.js",(_req,res)=>{sendBuiltPublicFile(res,"catalyst-sw.js",{"Content-Type":"application/javascript; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate","Service-Worker-Allowed":"/"});});app.get("/catalyst-offline-manifest.json",(_req,res)=>{sendBuiltPublicFile(res,"catalyst-offline-manifest.json",{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate"});});app.get("/offline.html",(_req,res)=>{sendBuiltPublicFile(res,"offline.html",{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-cache, no-store, must-revalidate"});});// This middleware is being used to extract the body of the request
2
+ app.use(_bodyParser.default.json({limit:"100kb"}));// Handles "bytestream array" content types sent as raw application/* bodies
2
3
  app.use(_bodyParser.default.raw({type:"application/*",limit:"100kb"}));app.use((0,_cookieParser.default)());if((0,_validator.validateMiddleware)(_server.addMiddlewares))(0,_server.addMiddlewares)(app);app.use((0,_compression.default)());// Serve pre-compressed static assets (brotli preferred) from the build output directory
3
4
  if(env==="production"){app.use(process.env.PUBLIC_STATIC_ASSET_PATH,(0,_expressStaticGzip.default)(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`),{enableBrotli:true,orderPreference:["br","gzip","deflate"]}));}else{app.use(process.env.PUBLIC_STATIC_ASSET_PATH,_express.default.static(_path.default.join(process.env.src_path,`./${process.env.BUILD_OUTPUT_PATH}/public`)));}// Catch-all: every non-asset request is handled by the SSR renderer
4
5
  app.use("*",_renderer.default);var _default=exports.default=app;
@@ -5,7 +5,7 @@
5
5
  * @param {object} firstFoldCss - style elements extracted for initial page load
6
6
  * @param {object} fetcherData - contains data from executing serverFetcher function
7
7
  * @param {object} children - contains any child elements defined within the component
8
- */const DEFAULT_SAFE_AREA={top:0,right:0,bottom:0,left:0};function Body({jsx="",initialState={},fetcherData={},children,safeArea=DEFAULT_SAFE_AREA}){return/*#__PURE__*/(0,_jsxRuntime.jsxs)("body",{children:[/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable-next-line risxss/catch-potential-xss-react */dangerouslySetInnerHTML:{__html:`window.__SAFE_AREA_INITIAL__ = ${JSON.stringify(safeArea)}`}}),jsx,/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable */dangerouslySetInnerHTML:{__html:`
8
+ */const DEFAULT_SAFE_AREA={top:0,right:0,bottom:0,left:0};function Body({jsx="",initialState={},fetcherData={},children,safeArea=DEFAULT_SAFE_AREA,nativeWebView=false}){return/*#__PURE__*/(0,_jsxRuntime.jsxs)("body",{children:[/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable-next-line risxss/catch-potential-xss-react */dangerouslySetInnerHTML:{__html:`window.__SAFE_AREA_INITIAL__ = ${JSON.stringify(safeArea)}; window.__CATALYST_NATIVE_WEBVIEW__ = ${nativeWebView?"true":"false"}`}}),jsx,/*#__PURE__*/(0,_jsxRuntime.jsx)("script",{/* eslint-disable */dangerouslySetInnerHTML:{__html:`
9
9
  window.__INITIAL_STATE__ = ${JSON.stringify(initialState)}
10
10
  window.__ROUTER_INITIAL_DATA__ = ${JSON.stringify(fetcherData)}
11
11
  `}}),children]});}
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _react=_interopRequireDefault(require("react"));var _extract=_interopRequireWildcard(require("./extract"));var _otel=require("../../otel");var _reactRedux=require("react-redux");var _document=require("./document");var _server=require("react-router-dom/server");var _ServerRouter=_interopRequireDefault(require("@catalyst/router/ServerRouter.js"));var _index=_interopRequireDefault(require("@catalyst/template/src/js/containers/App/index.js"));var _server2=require("@loadable/server");var _server3=require("react-dom/server");var _userAgentUtil=require("@catalyst/server/utils/userAgentUtil");var _router=require("@tata1mg/router");var _validator=require("@catalyst/server/utils/validator");var _document2=_interopRequireDefault(require("@catalyst/template/server/document.js"));var _utils=require("@catalyst/template/src/js/routes/utils.js");var _index2=require("@catalyst/template/server/index.js");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap(),n=new WeakMap();return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f);}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f;})(e,t);}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const storePath=_path.default.resolve(`${process.env.src_path}/src/js/store/index.js`);let createStore;if(_fs.default.existsSync(storePath)){try{const{default:configureStore}=require(`${process.env.src_path}/src/js/store/index.js`);createStore=configureStore;}catch(error){createStore=()=>{return{getState:()=>{}};};}}else{createStore=()=>{return{getState:()=>{}};};}const isProduction=process.env.NODE_ENV==="production";const DEFAULT_SAFE_AREA_INSETS={top:0,right:0,bottom:0,left:0};const parseSafeAreaFromHeaders=req=>{const readEdge=header=>{const raw=req.get(header)??req.headers[header.toLowerCase()];const value=Number(raw);return Number.isFinite(value)&&value>=0?value:null;};const top=readEdge("X-Safe-Area-Top");const right=readEdge("X-Safe-Area-Right");const bottom=readEdge("X-Safe-Area-Bottom");const left=readEdge("X-Safe-Area-Left");const hasAny=[top,right,bottom,left].some(value=>value!==null);if(!hasAny){return null;}return{top:top??0,right:right??0,bottom:bottom??0,left:left??0};};// Cache webStats path - computed once at module load
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _react=_interopRequireDefault(require("react"));var _extract=_interopRequireWildcard(require("./extract"));var _otel=require("../../otel");var _reactRedux=require("react-redux");var _document=require("./document");var _server=require("react-router-dom/server");var _ServerRouter=_interopRequireDefault(require("@catalyst/router/ServerRouter.js"));var _index=_interopRequireDefault(require("@catalyst/template/src/js/containers/App/index.js"));var _server2=require("@loadable/server");var _server3=require("react-dom/server");var _userAgentUtil=require("@catalyst/server/utils/userAgentUtil");var _router=require("@tata1mg/router");var _validator=require("@catalyst/server/utils/validator");var _document2=_interopRequireDefault(require("@catalyst/template/server/document.js"));var _utils=require("@catalyst/template/src/js/routes/utils.js");var _index2=require("@catalyst/template/server/index.js");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap(),n=new WeakMap();return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f);}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f;})(e,t);}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const storePath=_path.default.resolve(`${process.env.src_path}/src/js/store/index.js`);let createStore;if(_fs.default.existsSync(storePath)){try{const{default:configureStore}=require(`${process.env.src_path}/src/js/store/index.js`);createStore=configureStore;}catch(error){createStore=()=>{return{getState:()=>{}};};}}else{createStore=()=>{return{getState:()=>{}};};}const isProduction=process.env.NODE_ENV==="production";const DEFAULT_SAFE_AREA_INSETS={top:0,right:0,bottom:0,left:0};const parseSafeAreaFromHeaders=req=>{const readEdge=header=>{const raw=req.get(header)??req.headers[header.toLowerCase()];const value=Number(raw);return Number.isFinite(value)&&value>=0?value:null;};const top=readEdge("X-Safe-Area-Top");const right=readEdge("X-Safe-Area-Right");const bottom=readEdge("X-Safe-Area-Bottom");const left=readEdge("X-Safe-Area-Left");const hasAny=[top,right,bottom,left].some(value=>value!==null);if(!hasAny){return null;}return{top:top??0,right:right??0,bottom:bottom??0,left:left??0};};const isNativeWebViewRequest=req=>{const raw=req.get("X-Catalyst-Native-WebView")??req.headers["x-catalyst-native-webview"];return raw==="1"||raw==="true";};// Cache webStats path - computed once at module load
2
2
  const webStatsPath=isProduction?_path.default.join(process.env.src_path,`${process.env.BUILD_OUTPUT_PATH}/public/loadable-stats.json`):_path.default.join(__dirname,"../../..",`loadable-stats.json`);// Cache for parsed stats (production only)
3
3
  let cachedWebStats=null;const getWebStats=()=>{if(!isProduction){// In development, stats can change; always read fresh.
4
4
  return JSON.parse(_fs.default.readFileSync(webStatsPath,"utf-8"));}if(!cachedWebStats){cachedWebStats=JSON.parse(_fs.default.readFileSync(webStatsPath,"utf-8"));logger.info("ChunkExtractor stats cached in memory");}return cachedWebStats;};// Cache for ChunkExtractor instances per route (production only)
@@ -16,10 +16,10 @@ if(!res.locals.webExtractor){res.locals.webExtractor=getOrCreateExtractorForRout
16
16
  // chunks this route needs. The output is discarded — the real render happens later
17
17
  // in renderMarkUp via renderToPipeableStream.
18
18
  renderToStringWithObservability(res.locals.webExtractor,store,context,req,fetcherData);}const wc=route.component;matches.push({route,match,serverSideFunction:wc&&wc.serverSideFunction||(()=>Promise.resolve())});}if(!match&&route.children){const nested=_getMatchRoutes(route.children,req,res,store,context,fetcherData,`${basePath}/${path}`);if(nested.length){matches=matches.concat(nested);}}return matches;},[]);};const getMatchRoutes=(0,_otel.withSyncObservability)(SSR_SERVICE,_getMatchRoutes,"getMatchRoutes");// Preloads chunks required for rendering document
19
- const getComponent=(store,context,req,fetcherData)=>{return/*#__PURE__*/(0,_jsxRuntime.jsx)("div",{id:"app",children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_reactRedux.Provider,{store:store,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_server.StaticRouter,{context:context,location:req.originalUrl,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_ServerRouter.default,{store:store,intialData:fetcherData})})})});};const tracedResWriteFirstFoldCss=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldCss");const tracedResWriteFirstFoldJS=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldJS");const tracedResEnd=(0,_otel.withSyncObservability)(SSR_SERVICE,res=>res.end(),"res.end");const renderMarkUp=async(errorCode,req,res,metaTags,fetcherData,store,matches,context,webExtractor)=>{const deviceDetails=(0,_userAgentUtil.getUserAgentDetails)(req.headers["user-agent"]||"");const isBot=deviceDetails.googleBot||deviceDetails.aiBot?true:false;const safeArea=parseSafeAreaFromHeaders(req)||{...DEFAULT_SAFE_AREA_INSETS};/* eslint-disable no-undef */const previousSafeArea=globalThis.__SAFE_AREA_INITIAL__;globalThis.__SAFE_AREA_INITIAL__=safeArea;/* eslint-enable no-undef */let state=store.getState();// collectChunks wraps the component tree so the extractor can track which loadable
19
+ const getComponent=(store,context,req,fetcherData)=>{return/*#__PURE__*/(0,_jsxRuntime.jsx)("div",{id:"app",children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_reactRedux.Provider,{store:store,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_server.StaticRouter,{context:context,location:req.originalUrl,children:/*#__PURE__*/(0,_jsxRuntime.jsx)(_ServerRouter.default,{store:store,intialData:fetcherData})})})});};const tracedResWriteFirstFoldCss=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldCss");const tracedResWriteFirstFoldJS=(0,_otel.withSyncObservability)(SSR_SERVICE,(res,chunk)=>res.write(chunk),"res.write.firstFoldJS");const tracedResEnd=(0,_otel.withSyncObservability)(SSR_SERVICE,res=>res.end(),"res.end");const renderMarkUp=async(errorCode,req,res,metaTags,fetcherData,store,matches,context,webExtractor)=>{const deviceDetails=(0,_userAgentUtil.getUserAgentDetails)(req.headers["user-agent"]||"");const isBot=deviceDetails.googleBot||deviceDetails.aiBot?true:false;const safeArea=parseSafeAreaFromHeaders(req)||{...DEFAULT_SAFE_AREA_INSETS};const nativeWebView=isNativeWebViewRequest(req);/* eslint-disable no-undef */const previousSafeArea=globalThis.__SAFE_AREA_INITIAL__;const previousNativeWebView=globalThis.__CATALYST_NATIVE_WEBVIEW__;globalThis.__SAFE_AREA_INITIAL__=safeArea;globalThis.__CATALYST_NATIVE_WEBVIEW__=nativeWebView;/* eslint-enable no-undef */let state=store.getState();// collectChunks wraps the component tree so the extractor can track which loadable
20
20
  // chunks are actually rendered, building the final script/link tag lists.
21
21
  const jsx=webExtractor.collectChunks(getComponent(store,context,req,fetcherData));const{IS_DEV_COMMAND,WEBPACK_DEV_SERVER_HOSTNAME,WEBPACK_DEV_SERVER_PORT}=process.env;let publicAssetPath=`${process.env.PUBLIC_STATIC_ASSET_URL}${process.env.PUBLIC_STATIC_ASSET_PATH}`;// serves assets from localhost on running devBuild and devServe command
22
- if(IS_DEV_COMMAND==="true"){publicAssetPath=`http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/assets/`;}const finalProps={req,res,lang:"en",pageCss:res.locals.pageCss,preloadJSLinks:res.locals.preloadJSLinks,metaTags,isBot,publicAssetPath,jsx,initialState:state,fetcherData,safeArea};let CompleteDocument=()=>{if((0,_validator.validateCustomDocument)(_document2.default)){return(0,_document2.default)(finalProps);}else{return/*#__PURE__*/(0,_jsxRuntime.jsxs)("html",{lang:finalProps.lang,children:[/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Head,{isBot:finalProps.isBot,pageCss:finalProps.pageCss,metaTags:finalProps.metaTags,preloadJSLinks:finalProps.preloadJSLinks,publicAssetPath:finalProps.publicAssetPath}),/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Body,{jsx:finalProps.jsx,fetcherData:finalProps.fetcherData,initialState:finalProps.initialState,safeArea:finalProps.safeArea})]});}};const cleanupGlobalThis=()=>{/* eslint-disable no-undef */if(previousSafeArea===undefined){delete globalThis.__SAFE_AREA_INITIAL__;}else{globalThis.__SAFE_AREA_INITIAL__=previousSafeArea;}/* eslint-enable no-undef */};try{let status=errorCode||(matches.length&&matches[0].match.path==="*"?404:200);res.set({"content-type":"text/html; charset=utf-8"});res.status(status);return new Promise((resolve,reject)=>{const{pipe}=(0,_server3.renderToPipeableStream)(/*#__PURE__*/(0,_jsxRuntime.jsx)(CompleteDocument,{}),{onShellReady(){// Start streaming the HTML shell (everything above Suspense boundaries)
22
+ if(IS_DEV_COMMAND==="true"){publicAssetPath=`http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/assets/`;}const finalProps={req,res,lang:"en",pageCss:res.locals.pageCss,preloadJSLinks:res.locals.preloadJSLinks,metaTags,isBot,publicAssetPath,jsx,initialState:state,fetcherData,safeArea,nativeWebView};let CompleteDocument=()=>{if((0,_validator.validateCustomDocument)(_document2.default)){return(0,_document2.default)(finalProps);}else{return/*#__PURE__*/(0,_jsxRuntime.jsxs)("html",{lang:finalProps.lang,children:[/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Head,{isBot:finalProps.isBot,pageCss:finalProps.pageCss,metaTags:finalProps.metaTags,preloadJSLinks:finalProps.preloadJSLinks,publicAssetPath:finalProps.publicAssetPath}),/*#__PURE__*/(0,_jsxRuntime.jsx)(_document.Body,{jsx:finalProps.jsx,fetcherData:finalProps.fetcherData,initialState:finalProps.initialState,safeArea:finalProps.safeArea,nativeWebView:finalProps.nativeWebView})]});}};const cleanupGlobalThis=()=>{/* eslint-disable no-undef */if(previousSafeArea===undefined){delete globalThis.__SAFE_AREA_INITIAL__;}else{globalThis.__SAFE_AREA_INITIAL__=previousSafeArea;}if(previousNativeWebView===undefined){delete globalThis.__CATALYST_NATIVE_WEBVIEW__;}else{globalThis.__CATALYST_NATIVE_WEBVIEW__=previousNativeWebView;}/* eslint-enable no-undef */};try{let status=errorCode||(matches.length&&matches[0].match.path==="*"?404:200);res.set({"content-type":"text/html; charset=utf-8"});res.status(status);return new Promise((resolve,reject)=>{const{pipe}=(0,_server3.renderToPipeableStream)(/*#__PURE__*/(0,_jsxRuntime.jsx)(CompleteDocument,{}),{onShellReady(){// Start streaming the HTML shell (everything above Suspense boundaries)
23
23
  // as soon as it's ready to minimise TTFB.
24
24
  res.setHeader("content-type","text/html");pipe(res);},onAllReady(){// All Suspense boundaries have resolved. Append inline CSS and script
25
25
  // tags at the end of the stream so the browser can start executing JS.
@@ -1,5 +1,5 @@
1
1
  "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=exports.basePlugins=void 0;var _path=_interopRequireDefault(require("path"));var _webpack=_interopRequireDefault(require("webpack"));var _miniCssExtractPlugin=_interopRequireDefault(require("mini-css-extract-plugin"));var _webpackBundleAnalyzer=require("webpack-bundle-analyzer");var _config=_interopRequireDefault(require("@catalyst/root/config.json"));var _package=require("@catalyst/template/package.json");var _scssParams=require("@catalyst/webpack/scssParams");var _babelConfig=_interopRequireDefault(require("@catalyst/webpack/babel.config.ssr"));var _babelConfig2=_interopRequireDefault(require("@catalyst/webpack/babel.config.client"));var _loadEnvironmentVariables=_interopRequireDefault(require("@catalyst/scripts/loadEnvironmentVariables"));var _config2=require("@catalyst/template/config/config.json");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}(0,_loadEnvironmentVariables.default)();const isDev=process.env.NODE_ENV==="development";const isSSR=!!process.env.SSR||false;const basePlugins=exports.basePlugins=[// Expose only the whitelisted env vars to the client bundle
2
- new _webpack.default.DefinePlugin({"process.env":([..._config2.CLIENT_ENV_VARIABLES,"BUILD_OUTPUT_PATH","PUBLIC_STATIC_ASSET_PATH","PUBLIC_STATIC_ASSET_URL","src_path","PWD","SENTRY_CONFIG"]||[]).reduce((clientEnvMap,env)=>{clientEnvMap[env]=JSON.stringify(process.env[env]);return clientEnvMap;},{})}),_config2.ANALYZE_BUNDLE&&new _webpackBundleAnalyzer.BundleAnalyzerPlugin({generateStatsFile:_config2.ANALYZE_BUNDLE}),new _webpack.default.IgnorePlugin({resourceRegExp:/^\.\/locale$/,contextRegExp:/moment$/})];const{IS_DEV_COMMAND,PUBLIC_STATIC_ASSET_URL,PUBLIC_STATIC_ASSET_PATH,WEBPACK_DEV_SERVER_HOSTNAME,WEBPACK_DEV_SERVER_PORT,NODE_SERVER_HOSTNAME,NODE_SERVER_PORT}=process.env;let publicPath=isDev?`http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/`:`${PUBLIC_STATIC_ASSET_URL}${PUBLIC_STATIC_ASSET_PATH}`;// serves assets from local on running devBuild and devServe command
2
+ new _webpack.default.DefinePlugin({"process.env":([..._config2.CLIENT_ENV_VARIABLES,"BUILD_OUTPUT_PATH","NODE_ENV","PUBLIC_STATIC_ASSET_PATH","PUBLIC_STATIC_ASSET_URL","src_path","PWD","SENTRY_CONFIG"]||[]).reduce((clientEnvMap,env)=>{clientEnvMap[env]=JSON.stringify(process.env[env]);return clientEnvMap;},{})}),_config2.ANALYZE_BUNDLE&&new _webpackBundleAnalyzer.BundleAnalyzerPlugin({generateStatsFile:_config2.ANALYZE_BUNDLE}),new _webpack.default.IgnorePlugin({resourceRegExp:/^\.\/locale$/,contextRegExp:/moment$/})];const{IS_DEV_COMMAND,PUBLIC_STATIC_ASSET_URL,PUBLIC_STATIC_ASSET_PATH,WEBPACK_DEV_SERVER_HOSTNAME,WEBPACK_DEV_SERVER_PORT,NODE_SERVER_HOSTNAME,NODE_SERVER_PORT}=process.env;let publicPath=isDev?`http://${WEBPACK_DEV_SERVER_HOSTNAME}:${WEBPACK_DEV_SERVER_PORT}/`:`${PUBLIC_STATIC_ASSET_URL}${PUBLIC_STATIC_ASSET_PATH}`;// serves assets from local on running devBuild and devServe command
3
3
  if(IS_DEV_COMMAND==="true"&&!isDev){publicPath=`http://${NODE_SERVER_HOSTNAME}:${NODE_SERVER_PORT}/assets/`;}var _default=exports.default={context:_path.default.resolve(process.env.src_path),mode:isDev?"development":"production",// Use filesystem cache to reduce memory pressure and improve rebuild performance
4
4
  cache:isDev?{type:"filesystem",buildDependencies:{config:[__filename]},cacheDirectory:_path.default.join(process.env.src_path,"node_modules/catalyst-core/.cache/webpack")}:false,entry:{app:[_path.default.resolve(process.env.src_path,"./client/index.js")]},output:{path:_path.default.join(process.env.src_path,`${process.env.BUILD_OUTPUT_PATH}/public`),filename:process.env.NODE_ENV==="development"?"[name].bundle.js":"[name].[contenthash].js",chunkFilename:process.env.NODE_ENV==="development"?"[name].bundle.js":"[name].[contenthash].js",publicPath:publicPath},resolve:{fallback:{url:require.resolve("url")},extensions:[".js",".jsx",".scss",".ts",".tsx"],alias:Object.keys(_package._moduleAliases||{}).reduce((moduleEnvMap,alias)=>{// nosemgrep
5
5
  moduleEnvMap[alias]=_path.default.join(process.env.src_path,..._package._moduleAliases[alias].split("/"));return moduleEnvMap;},{})},plugins:basePlugins,module:{rules:[{test:/\.jsx$|\.js$/,exclude:_path.default.resolve(process.env.src_path,"./node_modules"),use:{loader:"babel-loader",options:isSSR?_babelConfig.default:_babelConfig2.default}},{test:/\.tsx$|\.ts$/,exclude:_path.default.resolve(process.env.src_path,"./node_modules"),use:{loader:"babel-loader",options:isSSR?_babelConfig.default:_babelConfig2.default}},{// CSS Modules for component-scoped styles; excludes node_modules and global base styles
@@ -700,6 +700,19 @@
700
700
  "source": "extracted:changelog",
701
701
  "tags": ["offline", "fallback", "native", "canary.19", "public"]
702
702
  },
703
+ {
704
+ "section": "native_offline",
705
+ "title": "Route Offline Snapshots - Online Recovery Behavior",
706
+ "content": "Route offline snapshots are network-first while online. Cached route HTML is served only when eligible route navigation is offline or the navigation fetch fails. When connectivity returns, retry, reload, or a new navigation uses the network again and can refresh the snapshot. If the bundled offline fallback page is already visible, Catalyst does not auto-reload it on reconnect; the user must retry/reload/navigate. Static assets may still come from cache while fresh or revalidating. API responses and non-GET requests are not cached by route offline snapshots.",
707
+ "layer": "Runtime",
708
+ "source": "static",
709
+ "tags": ["offline", "route-snapshots", "network-first", "native", "service-worker"],
710
+ "github_files": [
711
+ "packages/catalyst-core/src/offline/catalyst-sw.js",
712
+ "packages/catalyst-core/src/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt",
713
+ "packages/catalyst-core/src/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift"
714
+ ]
715
+ },
703
716
  {
704
717
  "section": "framework_identity",
705
718
  "title": "Mono-Repository Support (canary.2)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catalyst-core",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0-beta.2",
4
4
  "main": "dist/index.js",
5
5
  "description": "Web framework that provides great performance out of the box",
6
6
  "bin": {