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.
- package/dist/native/androidProject/app/build.gradle.kts +25 -8
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +239 -75
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +3 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/OfflineCacheService.kt +374 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +136 -26
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +7 -2
- package/dist/native/buildAndroid/assets.js +2 -2
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +39 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineCacheService.swift +683 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineURLSchemeHandler.swift +94 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +19 -6
- package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +15 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +82 -2
- package/dist/native/renameAndroidProject.js +2 -2
- package/dist/offline/catalyst-sw.js +1 -0
- package/dist/offline/registerServiceWorker.js +1 -0
- package/dist/router/ClientRouter.js +1 -1
- package/dist/scripts/build.js +1 -1
- package/dist/scripts/devBuild.js +1 -1
- package/dist/scripts/generateOfflineManifest.js +1 -0
- package/dist/server/expressServer.js +2 -1
- package/dist/server/renderer/document/Body.js +1 -1
- package/dist/server/renderer/handler.js +3 -3
- package/dist/webpack/base.babel.js +1 -1
- package/mcp_v2/knowledge-base.json +13 -0
- 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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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)
|
|
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")
|
|
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;
|
package/dist/scripts/build.js
CHANGED
|
@@ -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();
|
package/dist/scripts/devBuild.js
CHANGED
|
@@ -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`,`
|
|
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)();
|
|
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)",
|