catalyst-core 0.0.3-canary.7 โ 0.0.3-canary.8
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/changelog.md +39 -25
- package/dist/native/androidProject/.idea/misc.xml +1 -0
- package/dist/native/androidProject/app/build.gradle.kts +44 -26
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +54 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +24 -0
- package/dist/native/androidProject/app/src/main/res/drawable/splashscreen_fallback.png +0 -0
- package/dist/native/bridge/WebBridge.js +5 -5
- package/dist/native/bridge/constants/NativeInterfaces.js +13 -13
- package/dist/native/bridge/errors.js +20 -20
- package/dist/native/bridge/hooks.js +17 -17
- package/dist/native/bridge/standardizedHooks.js +25 -25
- package/dist/native/bridge/useBaseHook.js +12 -12
- package/dist/native/bridge/utils/NativeBridge.js +13 -13
- package/dist/native/buildAppAndroid.js +62 -40
- package/package.json +1 -1
- package/dist/native/androidProject/.idea/deviceManager.xml +0 -13
package/changelog.md
CHANGED
|
@@ -1,44 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-canary.8] - 2025-01-06
|
|
4
|
+
|
|
5
|
+
- **App icon configuration**
|
|
6
|
+
- **Splash screen integration**
|
|
7
|
+
- **Url Whitelisting**
|
|
3
8
|
|
|
4
9
|
## [0.0.3-canary.7] - 2025-01-06
|
|
5
10
|
|
|
6
11
|
### ๐ Features
|
|
7
|
-
- **Native API Integration**: Enhanced native module integration for better performance
|
|
8
|
-
- **Automated IP Resolution**: Intelligent network detection eliminates manual IP configuration
|
|
9
|
-
- **Universal Build System**: Streamlined one-command builds for both Android and iOS platforms
|
|
10
|
-
- **Enhanced CLI Interface**: Improved command-line tools for better developer experience
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Improved
|
|
13
|
+
- **Native API Integration**: Enhanced native module integration for better performance
|
|
14
|
+
- **Automated IP Resolution**: Intelligent network detection eliminates manual IP configuration
|
|
15
|
+
- **Universal Build System**: Streamlined one-command builds for both Android and iOS platforms
|
|
16
|
+
- **Enhanced CLI Interface**: Improved command-line tools for better developer experience
|
|
17
|
+
|
|
18
|
+
### ๐ Bug Fixes
|
|
19
|
+
|
|
20
|
+
- Fixed IP address detection issues on development environments
|
|
21
|
+
- Resolved build configuration conflicts between platforms
|
|
22
|
+
- Improved error handling and recovery mechanisms
|
|
16
23
|
|
|
17
24
|
### ๐ง Improvements
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
25
|
+
|
|
26
|
+
- **Performance**: Optimized native module loading and execution
|
|
27
|
+
- **Developer Experience**: Enhanced CLI with better error messages and debugging info
|
|
28
|
+
- **Configuration**: Dynamic config updates without requiring restarts
|
|
29
|
+
- **Build Process**: Faster and more reliable build pipeline
|
|
22
30
|
|
|
23
31
|
### ๐งช Testing & Validation
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
32
|
+
|
|
33
|
+
- โ
**Android Build**: Verified APK generation, installation, and runtime behavior
|
|
34
|
+
- โ
**iOS Build**: Tested build process and device compatibility
|
|
35
|
+
- โ
**IP Detection**: Validated automatic network resolution across platforms
|
|
36
|
+
- โ
**Native Modules**: Confirmed API integration and performance benchmarks
|
|
37
|
+
- โ
**CLI Operations**: Tested enhanced command-line workflows and error handling
|
|
29
38
|
|
|
30
39
|
### ๐ Breaking Changes
|
|
40
|
+
|
|
31
41
|
**None** - All changes maintain backward compatibility while enhancing existing functionality
|
|
32
42
|
|
|
33
43
|
### ๐ Technical Details
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
44
|
+
|
|
45
|
+
- Improved native API bridge for better cross-platform communication
|
|
46
|
+
- Enhanced error reporting with detailed stack traces and suggestions
|
|
47
|
+
- Optimized build configuration for faster development cycles
|
|
48
|
+
- Added comprehensive logging for debugging and monitoring
|
|
38
49
|
|
|
39
50
|
### ๐ Migration Notes
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
51
|
+
|
|
52
|
+
- No migration steps required
|
|
53
|
+
- Existing projects will automatically benefit from improvements
|
|
54
|
+
- Optional: Update CLI usage to leverage new enhanced features
|
|
55
|
+
|
|
43
56
|
## Version
|
|
44
|
-
|
|
57
|
+
|
|
58
|
+
- Target version: 0.0.3-canary.7
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1
2
|
<project version="4">
|
|
2
3
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
3
4
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" />
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import java.net.NetworkInterface
|
|
2
2
|
import java.io.File
|
|
3
3
|
import java.util.Properties
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
|
|
7
|
+
buildscript {
|
|
8
|
+
dependencies {
|
|
9
|
+
classpath("org.json:json:20231013")
|
|
10
|
+
}
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
val configPath: String? by project.properties
|
|
6
14
|
val keystorePassword: String? by project.properties // Changed from keyStorePassword to keystorePassword
|
|
@@ -133,6 +141,7 @@ dependencies {
|
|
|
133
141
|
androidTestImplementation(libs.androidx.espresso.core)
|
|
134
142
|
implementation("androidx.webkit:webkit:1.12.1")
|
|
135
143
|
implementation("org.json:json:20231013")
|
|
144
|
+
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
136
145
|
|
|
137
146
|
// Ktor Server dependencies for FrameworkServer (~200KB total)
|
|
138
147
|
implementation("io.ktor:ktor-server-core:2.3.7")
|
|
@@ -196,12 +205,15 @@ tasks.register("generateWebViewConfig") {
|
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
val configContent = configJsonFile.readText()
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
|
|
209
|
+
val jsonObject = JSONObject(configContent)
|
|
210
|
+
|
|
211
|
+
if (!jsonObject.has("WEBVIEW_CONFIG")) {
|
|
212
|
+
throw GradleException("WEBVIEW_CONFIG not found in config file")
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
val webviewConfig = jsonObject.getJSONObject("WEBVIEW_CONFIG")
|
|
216
|
+
|
|
205
217
|
val properties = Properties()
|
|
206
218
|
|
|
207
219
|
// Set different IP based on build type
|
|
@@ -215,30 +227,36 @@ tasks.register("generateWebViewConfig") {
|
|
|
215
227
|
properties.setProperty("buildOptimisation", "false")
|
|
216
228
|
}
|
|
217
229
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
fun extractProperties(jsonObj: org.json.JSONObject, prefix: String = "") {
|
|
231
|
+
try {
|
|
232
|
+
val keys = jsonObj.keys()
|
|
233
|
+
|
|
234
|
+
while (keys.hasNext()) {
|
|
235
|
+
val key = keys.next()
|
|
236
|
+
val value = jsonObj.opt(key)
|
|
237
|
+
val fullKey = if (prefix.isEmpty()) key else "$prefix.$key"
|
|
238
|
+
|
|
239
|
+
when (value) {
|
|
240
|
+
is org.json.JSONObject -> {
|
|
241
|
+
extractProperties(value, fullKey)
|
|
242
|
+
}
|
|
243
|
+
is org.json.JSONArray -> {
|
|
244
|
+
val arrayValues = (0 until value.length()).map { i ->
|
|
245
|
+
value.opt(i).toString()
|
|
246
|
+
}.joinToString(",")
|
|
247
|
+
properties.setProperty(fullKey, arrayValues)
|
|
248
|
+
}
|
|
249
|
+
else -> {
|
|
250
|
+
properties.setProperty(fullKey, value.toString())
|
|
251
|
+
}
|
|
252
|
+
}
|
|
225
253
|
}
|
|
254
|
+
} catch (e: Exception) {
|
|
255
|
+
throw RuntimeException("Failed to extract properties from JSON at prefix: $prefix", e)
|
|
226
256
|
}
|
|
227
257
|
}
|
|
228
258
|
|
|
229
|
-
|
|
230
|
-
val androidRegex = """"android"\s*:\s*\{([^}]*)\}""".toRegex()
|
|
231
|
-
val androidMatch = androidRegex.find(webviewConfigContent ?: "")
|
|
232
|
-
val androidContent = androidMatch?.groupValues?.get(1)
|
|
233
|
-
|
|
234
|
-
// Parse android object properties
|
|
235
|
-
val androidPropsRegex = """"([^"]+)"\s*:\s*"([^"]+)"""".toRegex()
|
|
236
|
-
androidContent?.let {
|
|
237
|
-
androidPropsRegex.findAll(it).forEach { matchResult ->
|
|
238
|
-
val (key, value) = matchResult.destructured
|
|
239
|
-
properties.setProperty("android.$key", value)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
259
|
+
extractProperties(webviewConfig)
|
|
242
260
|
|
|
243
261
|
// Set production-specific properties for release builds
|
|
244
262
|
if (gradle.startParameter.taskNames.any { it.contains("Release") || it.contains("release") }) {
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt
CHANGED
|
@@ -2,6 +2,7 @@ package io.yourname.androidproject
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
5
6
|
import android.graphics.Bitmap
|
|
6
7
|
import android.net.Uri
|
|
7
8
|
import android.os.Bundle
|
|
@@ -11,6 +12,8 @@ import android.webkit.*
|
|
|
11
12
|
import android.widget.ProgressBar
|
|
12
13
|
import androidx.webkit.WebViewAssetLoader
|
|
13
14
|
import io.yourname.androidproject.WebCacheManager
|
|
15
|
+
import io.yourname.androidproject.isUrlAllowed
|
|
16
|
+
import io.yourname.androidproject.isExternalDomain
|
|
14
17
|
import kotlinx.coroutines.*
|
|
15
18
|
import java.util.Properties
|
|
16
19
|
|
|
@@ -34,6 +37,7 @@ class CustomWebView(
|
|
|
34
37
|
private var isInitialPageLoaded: Boolean = false
|
|
35
38
|
private var buildOptimisation: Boolean = false // Added property for build optimization
|
|
36
39
|
private lateinit var assetLoader: WebViewAssetLoader
|
|
40
|
+
private var allowedUrls: List<String> = emptyList()
|
|
37
41
|
|
|
38
42
|
// Counters for asset loading statistics
|
|
39
43
|
private var assetLoadAttempts = 0
|
|
@@ -56,6 +60,12 @@ class CustomWebView(
|
|
|
56
60
|
// Parse buildOptimisation property
|
|
57
61
|
buildOptimisation = properties.getProperty("buildOptimisation", "false").toBoolean()
|
|
58
62
|
|
|
63
|
+
// Load allowed URLs from properties
|
|
64
|
+
allowedUrls = properties.getProperty("accessControl.allowedUrls", "")
|
|
65
|
+
.split(",")
|
|
66
|
+
.map { it.trim() }
|
|
67
|
+
.filter { it.isNotEmpty() }
|
|
68
|
+
|
|
59
69
|
// Set initial flags based on buildOptimisation
|
|
60
70
|
if (buildOptimisation) {
|
|
61
71
|
isInitialApiCalled = false
|
|
@@ -71,6 +81,7 @@ class CustomWebView(
|
|
|
71
81
|
Log.d(TAG, "Cache Pattern: $cachePatterns")
|
|
72
82
|
Log.d(TAG, "API Base URL: $apiBaseUrl")
|
|
73
83
|
Log.d(TAG, "Build Optimisation: $buildOptimisation")
|
|
84
|
+
Log.d(TAG, "Allowed URLs: $allowedUrls")
|
|
74
85
|
Log.d(TAG, "Initial API Called: $isInitialApiCalled")
|
|
75
86
|
Log.d(TAG, "Initial Page Loaded: $isInitialPageLoaded")
|
|
76
87
|
}
|
|
@@ -198,6 +209,22 @@ class CustomWebView(
|
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
private fun openInInAppBrowser(url: String) {
|
|
215
|
+
try {
|
|
216
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
217
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
218
|
+
context.startActivity(intent)
|
|
219
|
+
if (BuildConfig.DEBUG) {
|
|
220
|
+
Log.d(TAG, "๐ Opening external URL in in-app browser: $url")
|
|
221
|
+
}
|
|
222
|
+
} catch (e: Exception) {
|
|
223
|
+
Log.e(TAG, "โ Failed to open URL in in-app browser: $url", e)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
201
228
|
private fun isApiCall(url: String): Boolean {
|
|
202
229
|
// Check if URL is an API call based on your API base URL
|
|
203
230
|
return apiBaseUrl.isNotEmpty() && url.startsWith(apiBaseUrl)
|
|
@@ -290,6 +317,25 @@ class CustomWebView(
|
|
|
290
317
|
webView.webViewClient = object : WebViewClient() {
|
|
291
318
|
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
|
292
319
|
request?.url?.let { url ->
|
|
320
|
+
val urlString = url.toString()
|
|
321
|
+
|
|
322
|
+
// Check if URL is an external domain
|
|
323
|
+
if (url.scheme in listOf("http", "https") && isExternalDomain(urlString, allowedUrls)) {
|
|
324
|
+
if (BuildConfig.DEBUG) {
|
|
325
|
+
Log.d(TAG, "๐ External domain detected, opening in in-app browser: $urlString")
|
|
326
|
+
}
|
|
327
|
+
openInInAppBrowser(urlString)
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check if URL is allowed for internal navigation
|
|
332
|
+
if (!isUrlAllowed(urlString, allowedUrls)) {
|
|
333
|
+
if (BuildConfig.DEBUG) {
|
|
334
|
+
Log.w(TAG, "๐ซ URL blocked by access control: $urlString")
|
|
335
|
+
}
|
|
336
|
+
return true
|
|
337
|
+
}
|
|
338
|
+
|
|
293
339
|
// Let WebView handle loading non-API HTTP/HTTPS URLs
|
|
294
340
|
if (url.scheme in listOf("http", "https")) {
|
|
295
341
|
return false
|
|
@@ -307,6 +353,14 @@ class CustomWebView(
|
|
|
307
353
|
Log.d(TAG, "๐ Intercepting request for: $url on thread: ${Thread.currentThread().name}")
|
|
308
354
|
}
|
|
309
355
|
|
|
356
|
+
if (!isUrlAllowed(url, allowedUrls)) {
|
|
357
|
+
if (BuildConfig.DEBUG) {
|
|
358
|
+
Log.w(TAG, "๐ซ Network request blocked by access control: $url")
|
|
359
|
+
}
|
|
360
|
+
// Return an empty response to block the request
|
|
361
|
+
return WebResourceResponse("text/plain", "utf-8", null)
|
|
362
|
+
}
|
|
363
|
+
|
|
310
364
|
// Handle the initial route request - intercept first request regardless of host
|
|
311
365
|
if (!isInitialApiCalled && request.method == "GET") {
|
|
312
366
|
isInitialApiCalled = true
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt
CHANGED
|
@@ -10,6 +10,7 @@ import io.yourname.androidproject.NativeBridge
|
|
|
10
10
|
import kotlinx.coroutines.CoroutineScope
|
|
11
11
|
import kotlinx.coroutines.MainScope
|
|
12
12
|
import kotlinx.coroutines.cancelChildren
|
|
13
|
+
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
13
14
|
|
|
14
15
|
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
20
21
|
private lateinit var properties: Properties
|
|
21
22
|
private var isHardwareAccelerationEnabled = false
|
|
22
23
|
private var currentUrl: String = ""
|
|
24
|
+
private var splashStartTime: Long = 0
|
|
23
25
|
|
|
24
26
|
private fun enableHardwareAcceleration() {
|
|
25
27
|
if (!isHardwareAccelerationEnabled) {
|
|
@@ -45,6 +47,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
50
|
+
val splashScreen = installSplashScreen()
|
|
48
51
|
super.onCreate(savedInstanceState)
|
|
49
52
|
if (BuildConfig.DEBUG) {
|
|
50
53
|
Log.d(TAG, "๐ฑ onCreate started on thread: ${Thread.currentThread().name}")
|
|
@@ -63,6 +66,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
63
66
|
properties.setProperty("buildOptimisation", (!BuildConfig.DEBUG).toString())
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
// Configure splash screen
|
|
70
|
+
splashStartTime = System.currentTimeMillis()
|
|
71
|
+
configureSplashScreen(splashScreen)
|
|
72
|
+
|
|
66
73
|
// Setup UI
|
|
67
74
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
68
75
|
supportActionBar?.hide()
|
|
@@ -159,4 +166,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
159
166
|
customWebView.destroy()
|
|
160
167
|
super.onDestroy()
|
|
161
168
|
}
|
|
169
|
+
|
|
170
|
+
private fun configureSplashScreen(splashScreen: androidx.core.splashscreen.SplashScreen) {
|
|
171
|
+
val durationProperty = properties.getProperty("splashScreen.duration")
|
|
172
|
+
|
|
173
|
+
splashScreen.setKeepOnScreenCondition {
|
|
174
|
+
val webViewLoaded = ::customWebView.isInitialized &&
|
|
175
|
+
customWebView.getWebView().progress >= 100
|
|
176
|
+
|
|
177
|
+
if (durationProperty != null) {
|
|
178
|
+
val duration = durationProperty.toLong()
|
|
179
|
+
val timeElapsed = System.currentTimeMillis() - splashStartTime >= duration
|
|
180
|
+
!timeElapsed
|
|
181
|
+
} else {
|
|
182
|
+
!webViewLoaded
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
162
186
|
}
|
|
Binary file
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _NativeInterfaces=require("./constants/NativeInterfaces.js");class WebBridge{constructor(){this.handlers=new Map();this.initialized=false;this._logInitialization();}/**
|
|
2
2
|
* Log initialization info
|
|
3
|
-
*/_logInitialization(){console.log("๐ WebBridge initialized");console.log("๐ Available callback interfaces:",_NativeInterfaces.ALL_CALLBACKS.length);if(process.env.NODE_ENV===
|
|
3
|
+
*/_logInitialization(){console.log("๐ WebBridge initialized");console.log("๐ Available callback interfaces:",_NativeInterfaces.ALL_CALLBACKS.length);if(process.env.NODE_ENV==="development"){(0,_NativeInterfaces.debugInterfaces)();}}/**
|
|
4
4
|
* Static method to initialize WebBridge on window object
|
|
5
|
-
*/static init=()=>{if(typeof window===
|
|
5
|
+
*/static init=()=>{if(typeof window==="undefined"){console.error("๐ WebBridge cannot be initialized outside the browser!");return null;}if(window.WebBridge){console.warn("๐ WebBridge already initialized!");return window.WebBridge;}const bridge=new WebBridge();window.WebBridge=bridge;bridge.initialized=true;console.log("๐ WebBridge created and attached to window");return bridge;};/**
|
|
6
6
|
* Main callback handler called by native platforms
|
|
7
7
|
* @param {string} interfaceName - The callback interface name
|
|
8
8
|
* @param {any} data - Data from native platform
|
|
9
|
-
*/callback=(interfaceName,data)=>{console.log(`๐ WebBridge callback: ${interfaceName}`,data?{data}:
|
|
9
|
+
*/callback=(interfaceName,data)=>{console.log(`๐ WebBridge callback: ${interfaceName}`,data?{data}:"");// Validate interface
|
|
10
10
|
if(!(0,_NativeInterfaces.isValidCallback)(interfaceName)){console.error(`๐ Invalid callback interface: ${interfaceName}`);console.log("๐ Available callbacks:",_NativeInterfaces.ALL_CALLBACKS);return;}if(!this.handlers.has(interfaceName)){console.warn(`๐ No handler registered for interface: ${interfaceName}`);return;}try{const handler=this.handlers.get(interfaceName);handler(data);}catch(error){console.error(`๐ Error executing callback for ${interfaceName}:`,error);// In development, provide more helpful error info
|
|
11
|
-
if(process.env.NODE_ENV===
|
|
11
|
+
if(process.env.NODE_ENV==="development"){console.error("๐ Handler details:",{interfaceName,data,handlerType:typeof this.handlers.get(interfaceName)});}}};/**
|
|
12
12
|
* Register a callback handler for a native interface
|
|
13
13
|
* @param {string} interfaceName - The callback interface name (from NATIVE_CALLBACKS)
|
|
14
14
|
* @param {function} callback - The callback function to execute
|
|
@@ -39,4 +39,4 @@ if(this.handlers.has(interfaceName)){console.warn(`๐ Interface ${interfaceNam
|
|
|
39
39
|
* Test callback functionality (for debugging)
|
|
40
40
|
* @param {string} interfaceName - The callback interface to test
|
|
41
41
|
* @param {any} testData - Test data to pass
|
|
42
|
-
*/testCallback=(interfaceName,testData=
|
|
42
|
+
*/testCallback=(interfaceName,testData="test")=>{if(process.env.NODE_ENV!=="development"){console.warn("๐ testCallback is only available in development mode");return;}console.log(`๐ Testing callback: ${interfaceName}`);this.callback(interfaceName,testData);};}var _default=exports.default=WebBridge;
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
// Centralized definitions for all native interface communications
|
|
3
3
|
// Command Interfaces: Web โ Native (outgoing commands)
|
|
4
4
|
const NATIVE_COMMANDS=exports.NATIVE_COMMANDS={// Camera commands
|
|
5
|
-
OPEN_CAMERA:
|
|
6
|
-
PICK_FILE:
|
|
7
|
-
REQUEST_HAPTIC_FEEDBACK:
|
|
5
|
+
OPEN_CAMERA:"openCamera",REQUEST_CAMERA_PERMISSION:"requestCameraPermission",// File commands
|
|
6
|
+
PICK_FILE:"pickFile",OPEN_FILE_WITH_INTENT:"openFileWithIntent",// Haptic feedback commands
|
|
7
|
+
REQUEST_HAPTIC_FEEDBACK:"requestHapticFeedback"};// Callback Interfaces: Native โ Web (incoming events/callbacks)
|
|
8
8
|
const NATIVE_CALLBACKS=exports.NATIVE_CALLBACKS={// Camera callbacks
|
|
9
|
-
CAMERA_PERMISSION_STATUS:
|
|
10
|
-
ON_FILE_PICKED:
|
|
11
|
-
ON_INTENT_SUCCESS:
|
|
12
|
-
HAPTIC_FEEDBACK:
|
|
9
|
+
CAMERA_PERMISSION_STATUS:"CAMERA_PERMISSION_STATUS",ON_CAMERA_CAPTURE:"ON_CAMERA_CAPTURE",ON_CAMERA_ERROR:"ON_CAMERA_ERROR",// File picker callbacks
|
|
10
|
+
ON_FILE_PICKED:"ON_FILE_PICKED",ON_FILE_PICK_ERROR:"ON_FILE_PICK_ERROR",ON_FILE_PICK_CANCELLED:"ON_FILE_PICK_CANCELLED",ON_FILE_PICK_STATE_UPDATE:"ON_FILE_PICK_STATE_UPDATE",// Intent callbacks
|
|
11
|
+
ON_INTENT_SUCCESS:"ON_INTENT_SUCCESS",ON_INTENT_ERROR:"ON_INTENT_ERROR",ON_INTENT_CANCELLED:"ON_INTENT_CANCELLED",// Haptic feedback callbacks
|
|
12
|
+
HAPTIC_FEEDBACK:"HAPTIC_FEEDBACK"};// Interface Categories for easier management
|
|
13
13
|
const INTERFACE_CATEGORIES=exports.INTERFACE_CATEGORIES={CAMERA:{commands:[NATIVE_COMMANDS.OPEN_CAMERA,NATIVE_COMMANDS.REQUEST_CAMERA_PERMISSION],callbacks:[NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS,NATIVE_CALLBACKS.ON_CAMERA_CAPTURE,NATIVE_CALLBACKS.ON_CAMERA_ERROR]},FILE:{commands:[NATIVE_COMMANDS.PICK_FILE,NATIVE_COMMANDS.OPEN_FILE_WITH_INTENT],callbacks:[NATIVE_CALLBACKS.ON_FILE_PICKED,NATIVE_CALLBACKS.ON_FILE_PICK_ERROR,NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED,NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE]},INTENT:{commands:[NATIVE_COMMANDS.OPEN_FILE_WITH_INTENT],callbacks:[NATIVE_CALLBACKS.ON_INTENT_SUCCESS,NATIVE_CALLBACKS.ON_INTENT_ERROR,NATIVE_CALLBACKS.ON_INTENT_CANCELLED]},HAPTIC:{commands:[NATIVE_COMMANDS.REQUEST_HAPTIC_FEEDBACK],callbacks:[NATIVE_CALLBACKS.HAPTIC_FEEDBACK]}};// All available interfaces (for validation)
|
|
14
14
|
const ALL_COMMANDS=exports.ALL_COMMANDS=Object.values(NATIVE_COMMANDS);const ALL_CALLBACKS=exports.ALL_CALLBACKS=Object.values(NATIVE_CALLBACKS);const ALL_INTERFACES=exports.ALL_INTERFACES=[...ALL_COMMANDS,...ALL_CALLBACKS];// Permission status constants
|
|
15
|
-
const PERMISSION_STATUS=exports.PERMISSION_STATUS={GRANTED:
|
|
16
|
-
const HAPTIC_FEEDBACK_TYPES=exports.HAPTIC_FEEDBACK_TYPES={LIGHT:
|
|
17
|
-
const FILE_PICKER_STATES=exports.FILE_PICKER_STATES={OPENING:
|
|
18
|
-
const INTENT_STATES=exports.INTENT_STATES={PROCESSING:
|
|
19
|
-
const RESPONSE_STATUS=exports.RESPONSE_STATUS={SUCCESS:
|
|
15
|
+
const PERMISSION_STATUS=exports.PERMISSION_STATUS={GRANTED:"GRANTED",DENIED:"DENIED",NOT_DETERMINED:"NOT_DETERMINED",RESTRICTED:"RESTRICTED"};// Haptic feedback types
|
|
16
|
+
const HAPTIC_FEEDBACK_TYPES=exports.HAPTIC_FEEDBACK_TYPES={LIGHT:"light",MEDIUM:"medium",HEAVY:"heavy",SUCCESS:"success",WARNING:"warning",ERROR:"error"};// File picker state constants
|
|
17
|
+
const FILE_PICKER_STATES=exports.FILE_PICKER_STATES={OPENING:"opening",PROCESSING:"processing",IDLE:null};// Intent processing states
|
|
18
|
+
const INTENT_STATES=exports.INTENT_STATES={PROCESSING:"processing",DOWNLOADING:"downloading",OPENING:"opening",IDLE:null};// Response status constants
|
|
19
|
+
const RESPONSE_STATUS=exports.RESPONSE_STATUS={SUCCESS:"SUCCESS",ERROR:"ERROR",CANCELLED:"CANCELLED"};// Utility functions for interface validation
|
|
20
20
|
const isValidCommand=command=>ALL_COMMANDS.includes(command);exports.isValidCommand=isValidCommand;const isValidCallback=callback=>ALL_CALLBACKS.includes(callback);exports.isValidCallback=isValidCallback;const isValidInterface=interfaceName=>ALL_INTERFACES.includes(interfaceName);// Get interfaces by category
|
|
21
21
|
exports.isValidInterface=isValidInterface;const getInterfacesByCategory=category=>{const categoryData=INTERFACE_CATEGORIES[category.toUpperCase()];if(!categoryData){throw new Error(`Invalid category: ${category}`);}return categoryData;};// Debug helper to list all interfaces
|
|
22
|
-
exports.getInterfacesByCategory=getInterfacesByCategory;const debugInterfaces=()=>{console.group(
|
|
22
|
+
exports.getInterfacesByCategory=getInterfacesByCategory;const debugInterfaces=()=>{console.group("๐ Native Bridge Interfaces");console.log("Commands:",ALL_COMMANDS);console.log("Callbacks:",ALL_CALLBACKS);console.log("Categories:",Object.keys(INTERFACE_CATEGORIES));console.groupEnd();};exports.debugInterfaces=debugInterfaces;var _default=exports.default={NATIVE_COMMANDS,NATIVE_CALLBACKS,INTERFACE_CATEGORIES,ALL_COMMANDS,ALL_CALLBACKS,ALL_INTERFACES,PERMISSION_STATUS,HAPTIC_FEEDBACK_TYPES,FILE_PICKER_STATES,INTENT_STATES,RESPONSE_STATUS,isValidCommand,isValidCallback,isValidInterface,getInterfacesByCategory,debugInterfaces};
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* Standardized Error System for Catalyst Framework
|
|
3
3
|
* Translates native errors into web developer-friendly messages
|
|
4
4
|
*/// Error categories for logical grouping
|
|
5
|
-
const ERROR_CATEGORIES=exports.ERROR_CATEGORIES={PERMISSION:
|
|
5
|
+
const ERROR_CATEGORIES=exports.ERROR_CATEGORIES={PERMISSION:"PERMISSION",NETWORK:"NETWORK",FILE_SYSTEM:"FILE_SYSTEM",USER_ACTION:"USER_ACTION",VALIDATION:"VALIDATION",SYSTEM:"SYSTEM",UNKNOWN:"UNKNOWN"};// Standardized error codes
|
|
6
6
|
const ERROR_CODES=exports.ERROR_CODES={// Permission errors
|
|
7
|
-
PERMISSION_DENIED:
|
|
8
|
-
NETWORK_UNAVAILABLE:
|
|
9
|
-
FILE_NOT_FOUND:
|
|
10
|
-
OPERATION_CANCELLED:
|
|
11
|
-
INVALID_FILE_TYPE:
|
|
12
|
-
BRIDGE_UNAVAILABLE:
|
|
13
|
-
CAMERA_UNAVAILABLE:
|
|
14
|
-
const ERROR_DEFINITIONS=exports.ERROR_DEFINITIONS={[ERROR_CODES.PERMISSION_DENIED]:{category:ERROR_CATEGORIES.PERMISSION,defaultMessage:
|
|
7
|
+
PERMISSION_DENIED:"PERMISSION_DENIED",PERMISSION_REQUIRED:"PERMISSION_REQUIRED",// Network errors
|
|
8
|
+
NETWORK_UNAVAILABLE:"NETWORK_UNAVAILABLE",DOWNLOAD_FAILED:"DOWNLOAD_FAILED",// File system errors
|
|
9
|
+
FILE_NOT_FOUND:"FILE_NOT_FOUND",FILE_TOO_LARGE:"FILE_TOO_LARGE",STORAGE_FULL:"STORAGE_FULL",FILE_CORRUPTED:"FILE_CORRUPTED",// User action errors
|
|
10
|
+
OPERATION_CANCELLED:"OPERATION_CANCELLED",NO_FILE_SELECTED:"NO_FILE_SELECTED",// Validation errors
|
|
11
|
+
INVALID_FILE_TYPE:"INVALID_FILE_TYPE",INVALID_PARAMETERS:"INVALID_PARAMETERS",// System errors
|
|
12
|
+
BRIDGE_UNAVAILABLE:"BRIDGE_UNAVAILABLE",FEATURE_UNSUPPORTED:"FEATURE_UNSUPPORTED",INTERNAL_ERROR:"INTERNAL_ERROR",// Camera specific errors
|
|
13
|
+
CAMERA_UNAVAILABLE:"CAMERA_UNAVAILABLE",CAMERA_IN_USE:"CAMERA_IN_USE"};// Error definitions with user-friendly messages and recovery actions
|
|
14
|
+
const ERROR_DEFINITIONS=exports.ERROR_DEFINITIONS={[ERROR_CODES.PERMISSION_DENIED]:{category:ERROR_CATEGORIES.PERMISSION,defaultMessage:"Permission denied",defaultDetails:"Required permission was not granted by the user",recoverable:true,suggestedAction:"Grant permission in device settings and try again"},[ERROR_CODES.PERMISSION_REQUIRED]:{category:ERROR_CATEGORIES.PERMISSION,defaultMessage:"Permission required",defaultDetails:"This operation requires additional permissions",recoverable:true,suggestedAction:"Grant the required permission to continue"},[ERROR_CODES.NETWORK_UNAVAILABLE]:{category:ERROR_CATEGORIES.NETWORK,defaultMessage:"No internet connection",defaultDetails:"Network connection is required for this operation",recoverable:true,suggestedAction:"Check your internet connection and try again"},[ERROR_CODES.FILE_NOT_FOUND]:{category:ERROR_CATEGORIES.FILE_SYSTEM,defaultMessage:"File not found",defaultDetails:"The requested file could not be located",recoverable:false,suggestedAction:"The file may have been moved or deleted"},[ERROR_CODES.FILE_TOO_LARGE]:{category:ERROR_CATEGORIES.VALIDATION,defaultMessage:"File too large",defaultDetails:"File exceeds the maximum size limit",recoverable:true,suggestedAction:"Choose a smaller file or compress the current file"},[ERROR_CODES.STORAGE_FULL]:{category:ERROR_CATEGORIES.FILE_SYSTEM,defaultMessage:"Storage full",defaultDetails:"Device storage is full and cannot save the file",recoverable:true,suggestedAction:"Free up some storage space and try again"},[ERROR_CODES.OPERATION_CANCELLED]:{category:ERROR_CATEGORIES.USER_ACTION,defaultMessage:"Operation cancelled",defaultDetails:"The operation was cancelled by the user",recoverable:true,suggestedAction:"Try again if you want to complete the operation"},[ERROR_CODES.INVALID_FILE_TYPE]:{category:ERROR_CATEGORIES.VALIDATION,defaultMessage:"Invalid file type",defaultDetails:"The selected file type is not supported",recoverable:true,suggestedAction:"Choose a file with a supported format"},[ERROR_CODES.BRIDGE_UNAVAILABLE]:{category:ERROR_CATEGORIES.SYSTEM,defaultMessage:"Native feature unavailable",defaultDetails:"The native bridge is not available or not initialized",recoverable:false,suggestedAction:"Use web fallback if available"},[ERROR_CODES.FEATURE_UNSUPPORTED]:{category:ERROR_CATEGORIES.SYSTEM,defaultMessage:"Feature not supported",defaultDetails:"This feature is not supported on the current platform",recoverable:false,suggestedAction:"Try using an alternative method or device"},[ERROR_CODES.CAMERA_UNAVAILABLE]:{category:ERROR_CATEGORIES.SYSTEM,defaultMessage:"Camera unavailable",defaultDetails:"Camera hardware is not available or accessible",recoverable:false,suggestedAction:"Check if your device has a camera and try again"},[ERROR_CODES.CAMERA_IN_USE]:{category:ERROR_CATEGORIES.SYSTEM,defaultMessage:"Camera in use",defaultDetails:"Camera is being used by another application",recoverable:true,suggestedAction:"Close other camera apps and try again"},[ERROR_CODES.INTERNAL_ERROR]:{category:ERROR_CATEGORIES.UNKNOWN,defaultMessage:"An unexpected error occurred",defaultDetails:"An internal error occurred while processing the request",recoverable:true,suggestedAction:"Try again or contact support if the problem persists"}};/**
|
|
15
15
|
* Create a standardized error object
|
|
16
16
|
* @param {string} code - Error code from ERROR_CODES
|
|
17
17
|
* @param {string} userMessage - Custom user-friendly message (optional)
|
|
@@ -23,17 +23,17 @@ const ERROR_DEFINITIONS=exports.ERROR_DEFINITIONS={[ERROR_CODES.PERMISSION_DENIE
|
|
|
23
23
|
* @param {any} nativeError - Native error from Android/iOS
|
|
24
24
|
* @returns {Object} Standardized error object
|
|
25
25
|
*/exports.createStandardError=createStandardError;const translateError=nativeError=>{if(!nativeError){return createStandardError(ERROR_CODES.INTERNAL_ERROR,null,nativeError);}const errorString=nativeError.toString().toLowerCase();// Permission errors
|
|
26
|
-
if(errorString.includes(
|
|
27
|
-
if(errorString.includes(
|
|
28
|
-
if(errorString.includes(
|
|
29
|
-
if(errorString.includes(
|
|
30
|
-
if(errorString.includes(
|
|
31
|
-
if(errorString.includes(
|
|
32
|
-
if(typeof nativeError===
|
|
33
|
-
if(nativeError.domain===
|
|
34
|
-
if(nativeError.domain===
|
|
35
|
-
if(nativeError.domain===
|
|
26
|
+
if(errorString.includes("permission")&&errorString.includes("denied")){if(errorString.includes("camera")){return createStandardError(ERROR_CODES.PERMISSION_DENIED,"Camera permission denied",nativeError);}if(errorString.includes("storage")||errorString.includes("external_storage")){return createStandardError(ERROR_CODES.PERMISSION_DENIED,"Storage permission denied",nativeError);}return createStandardError(ERROR_CODES.PERMISSION_DENIED,null,nativeError);}// File system errors
|
|
27
|
+
if(errorString.includes("filenotfound")||errorString.includes("file not found")){return createStandardError(ERROR_CODES.FILE_NOT_FOUND,null,nativeError);}if(errorString.includes("storage")&&errorString.includes("full")){return createStandardError(ERROR_CODES.STORAGE_FULL,null,nativeError);}// Network errors
|
|
28
|
+
if(errorString.includes("network")||errorString.includes("connection")||errorString.includes("internet")){return createStandardError(ERROR_CODES.NETWORK_UNAVAILABLE,null,nativeError);}// User cancellation
|
|
29
|
+
if(errorString.includes("cancel")||errorString.includes("abort")){return createStandardError(ERROR_CODES.OPERATION_CANCELLED,null,nativeError);}// Camera specific errors
|
|
30
|
+
if(errorString.includes("camera")){if(errorString.includes("unavailable")||errorString.includes("not available")){return createStandardError(ERROR_CODES.CAMERA_UNAVAILABLE,null,nativeError);}if(errorString.includes("in use")||errorString.includes("busy")){return createStandardError(ERROR_CODES.CAMERA_IN_USE,null,nativeError);}}// Activity/Intent errors (Android)
|
|
31
|
+
if(errorString.includes("activitynotfound")){return createStandardError(ERROR_CODES.FEATURE_UNSUPPORTED,"No app available to handle this request",nativeError);}// iOS specific error codes
|
|
32
|
+
if(typeof nativeError==="object"&&nativeError.code&&nativeError.domain){// Camera errors (iOS)
|
|
33
|
+
if(nativeError.domain==="AVCaptureSessionErrorDomain"){if(nativeError.code===-11814){return createStandardError(ERROR_CODES.PERMISSION_DENIED,"Camera access denied",nativeError);}}// Network errors (iOS)
|
|
34
|
+
if(nativeError.domain==="NSURLErrorDomain"){if(nativeError.code===-1009){return createStandardError(ERROR_CODES.NETWORK_UNAVAILABLE,null,nativeError);}}// File system errors (iOS)
|
|
35
|
+
if(nativeError.domain==="NSCocoaErrorDomain"){if(nativeError.code===260){return createStandardError(ERROR_CODES.FILE_NOT_FOUND,null,nativeError);}}}// Default to internal error
|
|
36
36
|
return createStandardError(ERROR_CODES.INTERNAL_ERROR,null,nativeError);};/**
|
|
37
37
|
* Check if development mode is enabled
|
|
38
38
|
* @returns {boolean} True if in development mode
|
|
39
|
-
*/exports.translateError=translateError;const isDevelopment=()=>{return typeof process!==
|
|
39
|
+
*/exports.translateError=translateError;const isDevelopment=()=>{return typeof process!=="undefined"&&process.env&&process.env.NODE_ENV==="development";};exports.isDevelopment=isDevelopment;
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* React hook for camera functionality using standardized interface
|
|
3
3
|
* Handles camera permissions, photo capture, and error states
|
|
4
4
|
*/const useCamera=()=>{// Use standardized base hook
|
|
5
|
-
const base=(0,_useBaseHook.useBaseHook)(
|
|
5
|
+
const base=(0,_useBaseHook.useBaseHook)("useCamera");// Camera-specific state
|
|
6
6
|
const[permission,setPermission]=(0,_react.useState)(null);// Server-side rendering safety
|
|
7
7
|
if(typeof window==="undefined"){return{data:null,loading:false,progress:null,error:null,isWeb:true,isNative:false,execute:()=>{},clear:()=>{},clearError:()=>{},permission:null,// Legacy aliases
|
|
8
8
|
photo:null,takePhoto:()=>{},isLoading:false,clearPhoto:()=>{}};}if(!window.WebBridge){throw new Error("WebBridge is not initialized. Call WebBridge.init() first.");}(0,_react.useEffect)(()=>{// Register callback handlers
|
|
9
|
-
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_CAPTURE,data=>{try{const result=typeof data===
|
|
9
|
+
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_CAPTURE,data=>{try{const result=typeof data==="string"?JSON.parse(data):data;console.log("๐ท Camera capture result:",result);// Handle new tri-transport format or legacy format
|
|
10
10
|
const photoData=result.fileSrc?{// New tri-transport format
|
|
11
11
|
fileSrc:result.fileSrc,fileName:result.fileName,size:result.size,mimeType:result.mimeType,transport:result.transport,source:result.source}:{// Legacy format (fallback)
|
|
12
|
-
fileSrc:result.imageUrl,fileName:
|
|
12
|
+
fileSrc:result.imageUrl,fileName:"camera_photo.jpg",size:0,mimeType:"image/jpeg",transport:"LEGACY",source:"camera"};base.setDataAndComplete(photoData);// Update progress with transport info if available
|
|
13
13
|
if(photoData.transport){base.updateProgress({transport:photoData.transport,bytesTotal:photoData.size||null});}console.log("๐ท Photo captured successfully via transport:",photoData.transport);}catch(parseError){console.error("๐ท Error parsing camera capture data:",parseError);base.handleNativeError("Failed to process captured photo");}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS,data=>{setPermission(data);console.log("๐ท Camera permission status:",data);});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_ERROR,data=>{console.error("๐ท Camera error:",data);base.handleNativeError(data);});return()=>{// Cleanup: unregister all handlers
|
|
14
14
|
window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_CAPTURE);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_ERROR);};},[base.setDataAndComplete,base.handleNativeError,base.updateProgress]);const takePhoto=()=>{console.log("๐ท Camera open requested");base.executeOperation(()=>{// Update progress to show capturing state
|
|
15
|
-
base.updateProgress({state:
|
|
15
|
+
base.updateProgress({state:"capturing",phase:"requesting",message:"Opening camera..."});_NativeBridge.default.camera.open();},"camera capture");};// Standardized execute function (new interface)
|
|
16
16
|
const execute=takePhoto;return{// Standardized interface
|
|
17
17
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,execute,clear:base.clear,clearError:base.clearError,// Camera-specific extras
|
|
18
18
|
permission,// Legacy aliases for backward compatibility
|
|
@@ -20,28 +20,28 @@ photo:base.data,takePhoto,isLoading:base.loading,clearPhoto:base.clear};};/**
|
|
|
20
20
|
* React hook for intent handling using standardized interface
|
|
21
21
|
* Manages file opening operations with external apps
|
|
22
22
|
*/exports.useCamera=useCamera;const useIntent=()=>{// Use standardized base hook
|
|
23
|
-
const base=(0,_useBaseHook.useBaseHook)(
|
|
23
|
+
const base=(0,_useBaseHook.useBaseHook)("useIntent");// Server-side rendering safety
|
|
24
24
|
if(typeof window==="undefined"){return{data:null,loading:false,progress:null,error:null,isWeb:true,isNative:false,execute:()=>{},clear:()=>{},clearError:()=>{},// Legacy aliases
|
|
25
25
|
isLoading:false,processingState:null,openFile:()=>{},success:null,reset:()=>{}};}if(!window.WebBridge){throw new Error("WebBridge is not initialized. Call WebBridge.init() first.");}(0,_react.useEffect)(()=>{// Register callback handlers
|
|
26
26
|
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS,data=>{console.log("๐ Intent completed successfully:",data);base.setDataAndComplete({result:data,success:true});});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_ERROR,data=>{console.error("๐ Intent error:",data);base.handleNativeError(data);});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_CANCELLED,data=>{console.log("๐ Intent cancelled:",data);base.setLoading(false);base.resetProgress();// Keep data as is when cancelled
|
|
27
27
|
});return()=>{// Cleanup: unregister all handlers
|
|
28
28
|
window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_ERROR);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_CANCELLED);};},[base.setDataAndComplete,base.handleNativeError,base.setLoading,base.resetProgress]);const openFile=(fileUrl,mimeType=null)=>{if(!fileUrl){base.handleNativeError("File URL is required");return;}console.log("๐ File open with intent requested:",{fileUrl,mimeType});base.executeOperation(()=>{// Update progress to show opening state
|
|
29
|
-
base.updateProgress({state:
|
|
29
|
+
base.updateProgress({state:"opening_file",phase:"processing",message:"Opening file with external app..."});_NativeBridge.default.file.openWithIntent(fileUrl,mimeType);},"intent file open");};// Standardized execute function (new interface)
|
|
30
30
|
const execute=openFile;return{// Standardized interface
|
|
31
31
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,execute,clear:base.clear,clearError:base.clearError,// Legacy aliases for backward compatibility
|
|
32
32
|
isLoading:base.loading,processingState:base.progress?.phase||null,openFile,success:base.data?.success||null,reset:base.clear};};/**
|
|
33
33
|
* React hook for file picker functionality
|
|
34
34
|
* Manages file selection operations using standardized interface
|
|
35
35
|
*/exports.useIntent=useIntent;const useFilePicker=()=>{// Use standardized base hook
|
|
36
|
-
const base=(0,_useBaseHook.useBaseHook)(
|
|
36
|
+
const base=(0,_useBaseHook.useBaseHook)("useFilePicker");// Server-side rendering safety
|
|
37
37
|
if(typeof window==="undefined"){return{data:null,loading:false,progress:null,error:null,isWeb:true,isNative:false,execute:()=>{},clear:()=>{},clearError:()=>{},// Legacy aliases for backward compatibility
|
|
38
38
|
selectedFile:null,pickFile:()=>{},isLoading:false,processingState:null,clearFile:()=>{}};}if(!window.WebBridge){throw new Error("WebBridge is not initialized. Call WebBridge.init() first.");}(0,_react.useEffect)(()=>{// Register callback handlers
|
|
39
|
-
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED,data=>{try{const fileData=typeof data===
|
|
39
|
+
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED,data=>{try{const fileData=typeof data==="string"?JSON.parse(data):data;console.log("๐ File picked:",fileData);base.setDataAndComplete(fileData);// Update progress with transport info if available
|
|
40
40
|
if(fileData.transport){base.updateProgress({transport:fileData.transport,bytesTotal:fileData.size||null});}}catch(parseError){console.error("๐ Error parsing file data:",parseError);base.handleNativeError("Error processing selected file");}});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_ERROR,data=>{console.error("๐ File pick error:",data);base.handleNativeError(data);});window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED,data=>{console.log("๐ File pick cancelled:",data);base.setLoading(false);base.resetProgress();// Keep data as is when cancelled
|
|
41
41
|
});// File picker state updates
|
|
42
|
-
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE,data=>{try{const stateData=typeof data===
|
|
43
|
-
const progressState=stateData.state===
|
|
44
|
-
window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_ERROR);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE);};},[base.setDataAndComplete,base.handleNativeError,base.setLoading,base.resetProgress,base.updateProgress]);const pickFile=(mimeType=null)=>{const finalMimeType=mimeType||"*/*";console.log("๐ Picking file with MIME type:",finalMimeType);base.executeOperation(()=>{_NativeBridge.default.file.pick(finalMimeType);},
|
|
42
|
+
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE,data=>{try{const stateData=typeof data==="string"?JSON.parse(data):data;console.log("๐ File picker state:",stateData.state);// Map file picker states to standardized progress states
|
|
43
|
+
const progressState=stateData.state==="opening"?"opening":stateData.state==="processing"?"processing":"starting";base.updateProgress({state:progressState,phase:stateData.state,message:`File picker: ${stateData.state}...`});if(stateData.state){base.setLoading(true);}}catch(parseError){console.error("๐ Error parsing state data:",parseError);}});return()=>{// Cleanup: unregister all handlers
|
|
44
|
+
window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_ERROR);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE);};},[base.setDataAndComplete,base.handleNativeError,base.setLoading,base.resetProgress,base.updateProgress]);const pickFile=(mimeType=null)=>{const finalMimeType=mimeType||"*/*";console.log("๐ Picking file with MIME type:",finalMimeType);base.executeOperation(()=>{_NativeBridge.default.file.pick(finalMimeType);},"file pick");};// Standardized execute function (new interface)
|
|
45
45
|
const execute=pickFile;return{// Standardized interface
|
|
46
46
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,execute,clear:base.clear,clearError:base.clearError,// Legacy aliases for backward compatibility
|
|
47
47
|
selectedFile:base.data,pickFile,isLoading:base.loading,processingState:base.progress?.phase||null,clearFile:base.clear};};/**
|
|
@@ -59,13 +59,13 @@ const handlePermissionStatus=data=>{window.WebBridge.unregister(_NativeInterface
|
|
|
59
59
|
const handleHapticResponse=data=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.HAPTIC_FEEDBACK);if(data===_NativeInterfaces.RESPONSE_STATUS.SUCCESS){resolve(data);}else{reject(new Error(`Haptic feedback failed: ${data}`));}};window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.HAPTIC_FEEDBACK,handleHapticResponse);_NativeBridge.default.haptic.feedback(feedbackType);console.log("๐ณ Haptic feedback requested:",feedbackType);}catch(error){reject(error);}});};/**
|
|
60
60
|
* React hook for haptic feedback
|
|
61
61
|
* Provides a function to trigger haptic feedback
|
|
62
|
-
*/exports.requestHapticFeedback=requestHapticFeedback;const useHapticFeedback=()=>{const base=(0,_useBaseHook.useBaseHook)(
|
|
63
|
-
const HAPTIC_TYPES={LIGHT:
|
|
64
|
-
const userAgent=typeof navigator!==
|
|
65
|
-
const executeHaptic=(type=
|
|
66
|
-
const handleHapticTrigger=async(type,options={})=>{try{base.setLoading(true);base.updateProgress({state:
|
|
62
|
+
*/exports.requestHapticFeedback=requestHapticFeedback;const useHapticFeedback=()=>{const base=(0,_useBaseHook.useBaseHook)("useHapticFeedback");const[capabilities,setCapabilities]=(0,_react.useState)({isSupported:false,availableTypes:["light","medium","heavy"],platform:"unknown"});// Haptic types
|
|
63
|
+
const HAPTIC_TYPES={LIGHT:"light",MEDIUM:"medium",HEAVY:"heavy",SUCCESS:"success",WARNING:"warning",ERROR:"error",SELECTION:"selection",IMPACT:"impact"};(0,_react.useEffect)(()=>{// Initialize capabilities
|
|
64
|
+
const userAgent=typeof navigator!=="undefined"?navigator.userAgent.toLowerCase():"";let platform="unknown";let isSupported=false;let availableTypes=[];if(base.isNative){isSupported=true;if(userAgent.includes("android")){platform="android";availableTypes=[HAPTIC_TYPES.LIGHT,HAPTIC_TYPES.MEDIUM,HAPTIC_TYPES.HEAVY];}else if(userAgent.includes("iphone")||userAgent.includes("ipad")){platform="ios";availableTypes=Object.values(HAPTIC_TYPES);}}else{platform="web";isSupported=typeof navigator!=="undefined"&&"vibrate"in navigator;if(isSupported){availableTypes=[HAPTIC_TYPES.LIGHT,HAPTIC_TYPES.MEDIUM,HAPTIC_TYPES.HEAVY];}}setCapabilities({isSupported,availableTypes,platform});},[base.isNative]);// Main execute function for haptic superhook
|
|
65
|
+
const executeHaptic=(type="light",options={})=>{if(!capabilities.isSupported){const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.FEATURE_UNSUPPORTED,"Haptic feedback not supported",null,"Device does not support haptic feedback");base.handleNativeError(error);return;}return handleHapticTrigger(type,options);};// Haptic trigger handler
|
|
66
|
+
const handleHapticTrigger=async(type,options={})=>{try{base.setLoading(true);base.updateProgress({state:"active",phase:"triggering",message:`Triggering ${type} haptic feedback...`});let success=false;if(base.isNative){// Use existing native implementation
|
|
67
67
|
success=await requestHapticFeedback(type);}else{// Web fallback
|
|
68
|
-
success=handleWebHaptic(type,options);}const hapticData={lastType:type,lastOptions:options,timestamp:new Date().toISOString(),success:success,capabilities:capabilities,lastOperation:
|
|
68
|
+
success=handleWebHaptic(type,options);}const hapticData={lastType:type,lastOptions:options,timestamp:new Date().toISOString(),success:success,capabilities:capabilities,lastOperation:"trigger",operationSuccess:success};base.setDataAndComplete(hapticData);return success;}catch(error){console.error("๐ณ Haptic feedback failed:",error);base.handleNativeError(error);return false;}};// Web haptic fallback
|
|
69
69
|
const handleWebHaptic=(type,options={})=>{if(!navigator.vibrate){return false;}const vibrationPatterns={[HAPTIC_TYPES.LIGHT]:[50],[HAPTIC_TYPES.MEDIUM]:[100],[HAPTIC_TYPES.HEAVY]:[200],[HAPTIC_TYPES.SUCCESS]:[100,50,100],[HAPTIC_TYPES.WARNING]:[200,100,200],[HAPTIC_TYPES.ERROR]:[300,100,300,100,300],[HAPTIC_TYPES.SELECTION]:[25],[HAPTIC_TYPES.IMPACT]:[150]};const pattern=vibrationPatterns[type]||[100];navigator.vibrate(pattern);return true;};// Return standardized superhook interface
|
|
70
70
|
return{// Standard interface (from useBaseHook)
|
|
71
71
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,clear:base.clear,clearError:base.clearError,// Main execute function
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
* Standardized Camera Superhook
|
|
3
3
|
* Comprehensive camera functionality with integrated permission management
|
|
4
4
|
* Follows standardized interface with execute as primary function
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @param {Object} options - Configuration options
|
|
7
7
|
* @returns {Object} Standardized camera superhook interface
|
|
8
|
-
*/const useCamera=(options={})=>{const base=(0,_useBaseHook.useBaseHook)(
|
|
9
|
-
const CAMERA_OPERATIONS={TAKE_PHOTO:
|
|
10
|
-
const CAMERA_PHASES={REQUESTING_PERMISSION:
|
|
8
|
+
*/const useCamera=(options={})=>{const base=(0,_useBaseHook.useBaseHook)("useCamera",options);const[permission,setPermission]=(0,_react.useState)({status:"not_determined",canRequest:true,lastChecked:null});// Camera operations
|
|
9
|
+
const CAMERA_OPERATIONS={TAKE_PHOTO:"takePhoto",REQUEST_PERMISSION:"requestPermission",CHECK_PERMISSION:"checkPermission"};// Camera-specific progress phases
|
|
10
|
+
const CAMERA_PHASES={REQUESTING_PERMISSION:"requesting_permission",CHECKING_PERMISSION:"checking_permission",OPENING_CAMERA:"opening_camera",CAPTURING:"capturing",PROCESSING:"processing",COMPLETE:"complete"};(0,_react.useEffect)(()=>{// Server-side rendering safety
|
|
11
11
|
if(typeof window==="undefined")return;if(!window.WebBridge){console.warn("WebBridge not initialized. Camera functionality unavailable.");return;}// Camera capture success handler
|
|
12
|
-
const handleCameraCapture=data=>{try{const result=typeof data===
|
|
12
|
+
const handleCameraCapture=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;base.updateProgress({phase:CAMERA_PHASES.PROCESSING,message:"Processing captured photo..."});// Handle new tri-transport format or legacy format
|
|
13
13
|
const photoData=result.fileSrc?{// New tri-transport format
|
|
14
14
|
fileSrc:result.fileSrc,fileName:result.fileName,size:result.size,mimeType:result.mimeType,transport:result.transport,source:result.source,filePath:result.filePath}:{// Legacy format (fallback)
|
|
15
|
-
fileSrc:result.imageUrl,fileName:result.fileName||
|
|
15
|
+
fileSrc:result.imageUrl,fileName:result.fileName||"camera_photo.jpg",size:result.size||0,mimeType:result.mimeType||"image/jpeg",transport:"LEGACY",source:"camera",filePath:result.filePath||null};// Create comprehensive data object with photo and permission info
|
|
16
16
|
const cameraData={...photoData,permission:permission,lastOperation:CAMERA_OPERATIONS.TAKE_PHOTO,operationSuccess:true,timestamp:new Date().toISOString()};base.setDataAndComplete(cameraData);console.log("๐ท Photo captured successfully via transport:",photoData.transport);}catch(parseError){console.error("๐ท Error parsing camera capture data:",parseError);const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INTERNAL_ERROR,"Failed to process captured photo",parseError);base.handleNativeError(error);}};// Camera permission status handler
|
|
17
|
-
const handlePermissionStatus=data=>{const permissionStatus=typeof data===
|
|
18
|
-
const permissionData={permission:newPermission,lastOperation:CAMERA_OPERATIONS.REQUEST_PERMISSION,operationSuccess:permissionStatus===
|
|
17
|
+
const handlePermissionStatus=data=>{const permissionStatus=typeof data==="string"?data:data.status||data;const newPermission={status:permissionStatus.toLowerCase(),canRequest:permissionStatus!=="permanently_denied",lastChecked:new Date().toISOString()};setPermission(newPermission);console.log("๐ท Camera permission status:",newPermission);// Update data with permission info
|
|
18
|
+
const permissionData={permission:newPermission,lastOperation:CAMERA_OPERATIONS.REQUEST_PERMISSION,operationSuccess:permissionStatus==="GRANTED",timestamp:new Date().toISOString()};if(permissionStatus==="GRANTED"){base.updateProgress({phase:CAMERA_PHASES.OPENING_CAMERA,message:"Camera permission granted, ready to capture..."});base.setDataAndComplete(permissionData);}else if(permissionStatus==="DENIED"){const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.PERMISSION_DENIED,"Camera permission denied",null,"Camera permission is required to take photos");base.handleNativeError(error);}};// Camera error handler
|
|
19
19
|
const handleCameraError=data=>{console.error("๐ท Camera error:",data);base.handleNativeError(data);};// Register event listeners
|
|
20
20
|
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_CAPTURE,handleCameraCapture);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS,handlePermissionStatus);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_ERROR,handleCameraError);// Cleanup function
|
|
21
21
|
return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_CAPTURE);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_CAMERA_ERROR);};},[base]);// Main execute function for camera superhook
|
|
22
|
-
const executeCamera=(operation,...args)=>{if(base.isWeb){console.warn(`Camera ${operation} requires web fallback implementation (isWeb: true)`);return;}switch(operation){case CAMERA_OPERATIONS.TAKE_PHOTO:return handleTakePhoto(args[0]||{});case CAMERA_OPERATIONS.REQUEST_PERMISSION:return handleRequestPermission();case CAMERA_OPERATIONS.CHECK_PERMISSION:return handleCheckPermission();default:const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INVALID_PARAMETERS,`Unknown camera operation: ${operation}`,null,`Valid operations: ${Object.values(CAMERA_OPERATIONS).join(
|
|
23
|
-
const handleTakePhoto=(cameraOptions={})=>{base.executeOperation(()=>{base.updateProgress({phase:CAMERA_PHASES.REQUESTING_PERMISSION,message:
|
|
24
|
-
_NativeBridge.default.camera.checkPermission();}console.log("๐ท Camera permission requested");},
|
|
22
|
+
const executeCamera=(operation,...args)=>{if(base.isWeb){console.warn(`Camera ${operation} requires web fallback implementation (isWeb: true)`);return;}switch(operation){case CAMERA_OPERATIONS.TAKE_PHOTO:return handleTakePhoto(args[0]||{});case CAMERA_OPERATIONS.REQUEST_PERMISSION:return handleRequestPermission();case CAMERA_OPERATIONS.CHECK_PERMISSION:return handleCheckPermission();default:const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INVALID_PARAMETERS,`Unknown camera operation: ${operation}`,null,`Valid operations: ${Object.values(CAMERA_OPERATIONS).join(", ")}`);base.handleNativeError(error);}};// Camera operation handlers
|
|
23
|
+
const handleTakePhoto=(cameraOptions={})=>{base.executeOperation(()=>{base.updateProgress({phase:CAMERA_PHASES.REQUESTING_PERMISSION,message:"Requesting camera permission..."});_NativeBridge.default.camera.open(cameraOptions);console.log("๐ท Camera open requested with options:",cameraOptions);},"photo capture");};const handleRequestPermission=()=>{base.executeOperation(()=>{base.updateProgress({phase:CAMERA_PHASES.REQUESTING_PERMISSION,message:"Requesting camera permission..."});if(_NativeBridge.default.camera.requestPermission){_NativeBridge.default.camera.requestPermission();}else{// Fallback method
|
|
24
|
+
_NativeBridge.default.camera.checkPermission();}console.log("๐ท Camera permission requested");},"permission request");};const handleCheckPermission=()=>{base.executeOperation(()=>{base.updateProgress({phase:CAMERA_PHASES.CHECKING_PERMISSION,message:"Checking camera permission..."});_NativeBridge.default.camera.checkPermission();console.log("๐ท Camera permission check requested");},"permission check");};// Return standardized superhook interface
|
|
25
25
|
return{// Standard interface (from useBaseHook)
|
|
26
26
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,clear:base.clear,clearError:base.clearError,// Main execute function
|
|
27
27
|
execute:executeCamera,// Semantic aliases for execute
|
|
@@ -30,20 +30,20 @@ permission:permission,// Legacy compatibility
|
|
|
30
30
|
photo:base.data,clearPhoto:base.clear};};/**
|
|
31
31
|
* Standardized File Picker Hook
|
|
32
32
|
* Provides consistent interface with error standardization and environment detection
|
|
33
|
-
*
|
|
33
|
+
*
|
|
34
34
|
* @param {Object} options - Configuration options
|
|
35
35
|
* @returns {Object} Standardized file picker interface
|
|
36
|
-
*/exports.useCamera=useCamera;const useFilePicker=(options={})=>{const base=(0,_useBaseHook.useBaseHook)(
|
|
37
|
-
const FILE_PICKER_PHASES={OPENING:
|
|
36
|
+
*/exports.useCamera=useCamera;const useFilePicker=(options={})=>{const base=(0,_useBaseHook.useBaseHook)("useFilePicker",options);// File picker specific progress phases
|
|
37
|
+
const FILE_PICKER_PHASES={OPENING:"opening",PROCESSING:"processing",ROUTING:"routing",COMPLETE:"complete"};(0,_react.useEffect)(()=>{// Server-side rendering safety
|
|
38
38
|
if(typeof window==="undefined")return;if(!window.WebBridge){console.warn("WebBridge not initialized. File picker functionality unavailable.");return;}// File picked success handler
|
|
39
|
-
const handleFilePicked=data=>{try{const result=typeof data===
|
|
39
|
+
const handleFilePicked=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;console.log("๐ File picked result:",result);base.updateProgress({phase:FILE_PICKER_PHASES.PROCESSING,message:"Processing selected file..."});// Handle tri-transport format
|
|
40
40
|
const fileData={fileSrc:result.fileSrc,fileName:result.fileName,size:result.size,mimeType:result.mimeType,transport:result.transport,filePath:result.filePath};base.setDataAndComplete(fileData);console.log("๐ File processed successfully via transport:",fileData.transport);}catch(parseError){console.error("๐ Error parsing file picker data:",parseError);const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INTERNAL_ERROR,"Failed to process selected file",parseError);base.handleNativeError(error);}};// File pick error handler
|
|
41
41
|
const handleFilePickError=data=>{console.error("๐ File pick error:",data);base.handleNativeError(data);};// File pick cancelled handler
|
|
42
42
|
const handleFilePickCancelled=data=>{console.log("๐ File pick cancelled:",data);const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.OPERATION_CANCELLED,"File selection was cancelled",data);base.handleNativeError(error);};// File picker state updates
|
|
43
|
-
const handleFilePickStateUpdate=data=>{try{const stateData=typeof data===
|
|
43
|
+
const handleFilePickStateUpdate=data=>{try{const stateData=typeof data==="string"?JSON.parse(data):data;console.log("๐ File picker state:",stateData.state);let phase=FILE_PICKER_PHASES.PROCESSING;let message="Processing...";switch(stateData.state){case"opening":phase=FILE_PICKER_PHASES.OPENING;message="Opening file picker...";break;case"processing":phase=FILE_PICKER_PHASES.PROCESSING;message="Processing selected file...";break;case"routing":phase=FILE_PICKER_PHASES.ROUTING;message="Determining transport method...";break;default:message=stateData.state;}base.updateProgress({phase,message});}catch(parseError){console.error("๐ Error parsing file picker state:",parseError);}};// Register event listeners
|
|
44
44
|
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED,handleFilePicked);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_ERROR,handleFilePickError);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED,handleFilePickCancelled);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE,handleFilePickStateUpdate);// Cleanup function
|
|
45
45
|
return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICKED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_ERROR);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED);window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE);};},[base]);// Pick file function
|
|
46
|
-
const pickFile=(mimeType=null)=>{base.executeOperation(()=>{const finalMimeType=mimeType||"*/*";base.updateProgress({phase:FILE_PICKER_PHASES.OPENING,message:`Opening file picker${mimeType?` for ${mimeType}`:
|
|
46
|
+
const pickFile=(mimeType=null)=>{base.executeOperation(()=>{const finalMimeType=mimeType||"*/*";base.updateProgress({phase:FILE_PICKER_PHASES.OPENING,message:`Opening file picker${mimeType?` for ${mimeType}`:""}...`});_NativeBridge.default.file.pick(finalMimeType);console.log("๐ File picker opened with MIME type:",finalMimeType);},"file selection");};// Return standardized interface
|
|
47
47
|
return{// Standard interface
|
|
48
48
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,// Actions
|
|
49
49
|
execute:pickFile,// Standard execute function
|
|
@@ -56,21 +56,21 @@ clearFile:base.clear// Legacy alias
|
|
|
56
56
|
};};/**
|
|
57
57
|
* Standardized Intent Hook
|
|
58
58
|
* Provides consistent interface with error standardization and environment detection
|
|
59
|
-
*
|
|
59
|
+
*
|
|
60
60
|
* @param {Object} options - Configuration options
|
|
61
61
|
* @returns {Object} Standardized intent interface
|
|
62
|
-
*/exports.useFilePicker=useFilePicker;const useIntent=(options={})=>{const base=(0,_useBaseHook.useBaseHook)(
|
|
63
|
-
const INTENT_PHASES={PREPARING:
|
|
62
|
+
*/exports.useFilePicker=useFilePicker;const useIntent=(options={})=>{const base=(0,_useBaseHook.useBaseHook)("useIntent",options);// Intent specific progress phases
|
|
63
|
+
const INTENT_PHASES={PREPARING:"preparing",DOWNLOADING:"downloading",OPENING:"opening",COMPLETE:"complete"};(0,_react.useEffect)(()=>{// Server-side rendering safety
|
|
64
64
|
if(typeof window==="undefined")return;if(!window.WebBridge){console.warn("WebBridge not initialized. Intent functionality unavailable.");return;}// Intent success handler
|
|
65
|
-
const handleIntentSuccess=data=>{try{const result=typeof data===
|
|
65
|
+
const handleIntentSuccess=data=>{try{const result=typeof data==="string"?JSON.parse(data):data;console.log("๐ Intent success result:",result);const intentData={url:result.url,mimeType:result.mimeType,success:result.success||true,message:result.message||"File opened successfully"};base.setDataAndComplete(intentData);console.log("๐ Intent completed successfully");}catch(parseError){console.error("๐ Error parsing intent result:",parseError);const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INTERNAL_ERROR,"Failed to process intent result",parseError);base.handleNativeError(error);}};// Intent error handler
|
|
66
66
|
const handleIntentError=data=>{console.error("๐ Intent error:",data);base.handleNativeError(data);};// Intent state updates (if available)
|
|
67
|
-
const handleIntentStateUpdate=data=>{try{const stateData=typeof data===
|
|
68
|
-
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS||
|
|
67
|
+
const handleIntentStateUpdate=data=>{try{const stateData=typeof data==="string"?JSON.parse(data):data;console.log("๐ Intent state:",stateData.state);let phase=INTENT_PHASES.PREPARING;let message="Processing...";switch(stateData.state){case"preparing":phase=INTENT_PHASES.PREPARING;message="Preparing to open file...";break;case"downloading":phase=INTENT_PHASES.DOWNLOADING;message="Downloading file...";break;case"opening":phase=INTENT_PHASES.OPENING;message="Opening with external app...";break;default:message=stateData.state;}base.updateProgress({phase,message,percentage:stateData.percentage||null});}catch(parseError){console.error("๐ Error parsing intent state:",parseError);}};// Register event listeners (some callbacks might not exist in legacy intent)
|
|
68
|
+
window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS||"ON_INTENT_SUCCESS",handleIntentSuccess);window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_ERROR||"ON_INTENT_ERROR",handleIntentError);// Optional state updates (might not be implemented in current intent system)
|
|
69
69
|
if(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_STATE_UPDATE){window.WebBridge.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_STATE_UPDATE,handleIntentStateUpdate);}// Cleanup function
|
|
70
|
-
return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS||
|
|
70
|
+
return()=>{window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_SUCCESS||"ON_INTENT_SUCCESS");window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_ERROR||"ON_INTENT_ERROR");if(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_STATE_UPDATE){window.WebBridge.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_INTENT_STATE_UPDATE);}};},[base]);// Open file function
|
|
71
71
|
const openFile=(url,mimeType=null)=>{if(!url){const error=(0,_errors.createStandardError)(_errors.ERROR_CODES.INVALID_PARAMETERS,"URL is required for opening files",null,"Please provide a valid URL to open");base.handleNativeError(error);return;}base.executeOperation(()=>{const finalMimeType=mimeType||"*/*";base.updateProgress({phase:INTENT_PHASES.PREPARING,message:`Preparing to open ${url}...`});// Check if intent system has specific methods
|
|
72
72
|
if(_NativeBridge.default.intent&&_NativeBridge.default.intent.openFile){_NativeBridge.default.intent.openFile(url,finalMimeType);}else{// Fallback to generic method if available
|
|
73
|
-
_NativeBridge.default.openFile(url,finalMimeType);}console.log("๐ Intent to open file:",url,"with MIME type:",finalMimeType);},
|
|
73
|
+
_NativeBridge.default.openFile(url,finalMimeType);}console.log("๐ Intent to open file:",url,"with MIME type:",finalMimeType);},"file opening");};// Return standardized interface
|
|
74
74
|
return{// Standard interface
|
|
75
75
|
data:base.data,loading:base.loading,progress:base.progress,error:base.error,isWeb:base.isWeb,isNative:base.isNative,// Actions
|
|
76
76
|
execute:openFile,// Standard execute function
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.useEnvironmentInfo=exports.useBaseHook=exports.PROGRESS_STATES=void 0;var _react=require("react");var _NativeBridge=_interopRequireDefault(require("./utils/NativeBridge.js"));var _errors=require("./errors");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}/**
|
|
2
2
|
* Base hook utility that provides standardized interface for all Catalyst hooks
|
|
3
3
|
* Handles environment detection, error standardization, and common state management
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* @param {string} hookName - Name of the hook for debugging purposes
|
|
6
6
|
* @param {Object} options - Configuration options
|
|
7
7
|
* @returns {Object} Base hook interface with common functionality
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
const isNative=(0,_react.useCallback)(()=>{// Server-side rendering safety
|
|
10
10
|
if(typeof window==="undefined")return false;// Check for WebBridge and verify it's functional
|
|
11
11
|
return!!(window.WebBridge&&_NativeBridge.default.isAvailable());},[]);const isWeb=(0,_react.useCallback)(()=>{return!isNative();},[isNative]);// Common state management
|
|
12
|
-
const[data,setData]=(0,_react.useState)(null);const[loading,setLoading]=(0,_react.useState)(false);const[error,setError]=(0,_react.useState)(null);const[progress,setProgress]=(0,_react.useState)({state:
|
|
12
|
+
const[data,setData]=(0,_react.useState)(null);const[loading,setLoading]=(0,_react.useState)(false);const[error,setError]=(0,_react.useState)(null);const[progress,setProgress]=(0,_react.useState)({state:"idle",// 'idle' | 'opening' | 'processing' | 'routing' | 'complete' | 'error'
|
|
13
13
|
percentage:null,// 0-100 or null
|
|
14
14
|
message:null,// Human readable message or null
|
|
15
15
|
phase:null,// Operation specific phase or null
|
|
@@ -17,11 +17,11 @@ transport:null,// Transport method being used
|
|
|
17
17
|
bytesLoaded:null,// Bytes processed
|
|
18
18
|
bytesTotal:null// Total bytes
|
|
19
19
|
});// Progress management utilities
|
|
20
|
-
const updateProgress=(0,_react.useCallback)(updates=>{setProgress(prev=>({...prev,...updates}));},[]);const resetProgress=(0,_react.useCallback)(()=>{setProgress({state:
|
|
20
|
+
const updateProgress=(0,_react.useCallback)(updates=>{setProgress(prev=>({...prev,...updates}));},[]);const resetProgress=(0,_react.useCallback)(()=>{setProgress({state:"idle",percentage:null,message:null,phase:null,transport:null,bytesLoaded:null,bytesTotal:null});},[]);const startProgress=(0,_react.useCallback)((phase=null,message=null)=>{updateProgress({state:"active",phase,message,percentage:null});},[updateProgress]);const completeProgress=(0,_react.useCallback)(()=>{updateProgress({state:"complete",percentage:100});},[updateProgress]);const errorProgress=(0,_react.useCallback)(()=>{updateProgress({state:"error"});},[updateProgress]);// Error handling utilities
|
|
21
21
|
const handleNativeError=(0,_react.useCallback)(nativeError=>{const standardError=(0,_errors.translateError)(nativeError);setError(standardError);setLoading(false);errorProgress();// Development logging
|
|
22
|
-
if((0,_errors.isDevelopment)()){console.group(`๐จ ${hookName} Error`);console.log(
|
|
22
|
+
if((0,_errors.isDevelopment)()){console.group(`๐จ ${hookName} Error`);console.log("Standard Error:",standardError);console.log("Native Error:",nativeError);console.groupEnd();}return standardError;},[hookName,errorProgress]);const clearError=(0,_react.useCallback)(()=>{setError(null);if(progress.state==="error"){resetProgress();}},[progress.state,resetProgress]);// Data management utilities
|
|
23
23
|
const setDataAndComplete=(0,_react.useCallback)(newData=>{setData(newData);setLoading(false);completeProgress();setError(null);if((0,_errors.isDevelopment)()){console.log(`โ
${hookName} Success:`,newData);}},[hookName,completeProgress]);const clear=(0,_react.useCallback)(()=>{setData(null);setError(null);resetProgress();if((0,_errors.isDevelopment)()){console.log(`๐๏ธ ${hookName} Cleared`);}},[hookName,resetProgress]);// Operation wrapper that handles common patterns
|
|
24
|
-
const executeOperation=(0,_react.useCallback)((operationCallback,operationName=
|
|
24
|
+
const executeOperation=(0,_react.useCallback)((operationCallback,operationName="operation")=>{try{if(isWeb()){console.warn(`${hookName} requires web fallback implementation (isWeb: true)`);return;}if(!_NativeBridge.default.isAvailable()){throw new Error("Native bridge not available");}setLoading(true);setError(null);startProgress("starting",`Starting ${operationName}...`);if((0,_errors.isDevelopment)()){console.log(`๐ ${hookName} ${operationName} started`);}// Execute the actual operation
|
|
25
25
|
operationCallback();}catch(err){handleNativeError(err);console.error(`โ ${hookName} ${operationName} failed:`,err);}},[hookName,isWeb,startProgress,handleNativeError]);// Environment flags (computed values, not functions)
|
|
26
26
|
const environmentFlags={isWeb:isWeb(),isNative:isNative()};// Return standardized interface
|
|
27
27
|
return{// Data state
|
|
@@ -32,13 +32,13 @@ error,// Environment detection
|
|
|
32
32
|
clear,clearError,// Internal utilities for specific hooks
|
|
33
33
|
setData,setLoading,setError,setProgress,updateProgress,resetProgress,startProgress,completeProgress,errorProgress,setDataAndComplete,handleNativeError,executeOperation};};/**
|
|
34
34
|
* Hook for development debugging and testing
|
|
35
|
-
* @param {string} hookName
|
|
35
|
+
* @param {string} hookName
|
|
36
36
|
* @returns {Object} Environment information and utilities
|
|
37
|
-
*/exports.useBaseHook=useBaseHook;const useEnvironmentInfo=hookName=>{const isNativeAvailable=()=>{if(typeof window==="undefined")return false;return!!(window.WebBridge&&_NativeBridge.default.isAvailable());};const getEnvironmentDetails=()=>{const details={hasWindow:typeof window!=="undefined",hasWebBridge:typeof window!=="undefined"&&!!window.WebBridge,nativeBridgeAvailable:false,userAgent:typeof navigator!=="undefined"?navigator.userAgent:
|
|
37
|
+
*/exports.useBaseHook=useBaseHook;const useEnvironmentInfo=hookName=>{const isNativeAvailable=()=>{if(typeof window==="undefined")return false;return!!(window.WebBridge&&_NativeBridge.default.isAvailable());};const getEnvironmentDetails=()=>{const details={hasWindow:typeof window!=="undefined",hasWebBridge:typeof window!=="undefined"&&!!window.WebBridge,nativeBridgeAvailable:false,userAgent:typeof navigator!=="undefined"?navigator.userAgent:"SSR",timestamp:new Date().toISOString()};if(details.hasWebBridge){details.nativeBridgeAvailable=_NativeBridge.default.isAvailable();}return details;};if((0,_errors.isDevelopment)()){console.log(`๐ ${hookName} Environment:`,getEnvironmentDetails());}return{isNative:isNativeAvailable(),isWeb:!isNativeAvailable(),environmentDetails:getEnvironmentDetails()};};/**
|
|
38
38
|
* Progress state constants for consistency across hooks
|
|
39
39
|
*/exports.useEnvironmentInfo=useEnvironmentInfo;const PROGRESS_STATES=exports.PROGRESS_STATES={// Common states
|
|
40
|
-
IDLE:
|
|
41
|
-
OPENING:
|
|
42
|
-
REQUESTING:
|
|
43
|
-
DOWNLOADING:
|
|
44
|
-
CONNECTING:
|
|
40
|
+
IDLE:"idle",STARTING:"starting",COMPLETE:"complete",ERROR:"error",// File Picker states
|
|
41
|
+
OPENING:"opening",PROCESSING:"processing",ROUTING:"routing",// Camera states
|
|
42
|
+
REQUESTING:"requesting",CAPTURING:"capturing",// Intent states
|
|
43
|
+
DOWNLOADING:"downloading",OPENING_FILE:"opening_file",// Server states
|
|
44
|
+
CONNECTING:"connecting",UPLOADING:"uploading",SERVING:"serving"};
|
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
* NativeBridge Utility
|
|
3
3
|
* Handles communication from Web to Native platforms
|
|
4
4
|
* Supports both Android (window.NativeBridge) and iOS (webkit.messageHandlers)
|
|
5
|
-
*/class NativeBridgeUtil{constructor(){this.isAndroid=this._detectAndroid();this.isIOS=this._detectIOS();this.isNativeEnvironment=this.isAndroid||this.isIOS;if(typeof window!==
|
|
5
|
+
*/class NativeBridgeUtil{constructor(){this.isAndroid=this._detectAndroid();this.isIOS=this._detectIOS();this.isNativeEnvironment=this.isAndroid||this.isIOS;if(typeof window!=="undefined"){this._logEnvironmentInfo();}}/**
|
|
6
6
|
* Detect if running on Android WebView
|
|
7
|
-
*/_detectAndroid(){if(typeof window===
|
|
7
|
+
*/_detectAndroid(){if(typeof window==="undefined")return false;return!!(window.NativeBridge&&typeof window.NativeBridge==="object");}/**
|
|
8
8
|
* Detect if running on iOS WebView
|
|
9
|
-
*/_detectIOS(){if(typeof window===
|
|
9
|
+
*/_detectIOS(){if(typeof window==="undefined")return false;return!!window.webkit?.messageHandlers?.NativeBridge;}/**
|
|
10
10
|
* Log environment detection info for debugging
|
|
11
|
-
*/_logEnvironmentInfo(){console.log(
|
|
11
|
+
*/_logEnvironmentInfo(){console.log("๐ NativeBridge Environment Detection:",{isAndroid:this.isAndroid,isIOS:this.isIOS,isNativeEnvironment:this.isNativeEnvironment,hasAndroidBridge:!!window.NativeBridge,hasIOSBridge:!!window.webkit?.messageHandlers?.NativeBridge});}/**
|
|
12
12
|
* Validate command before execution
|
|
13
|
-
*/_validateCommand(command){if(!command){throw new Error(
|
|
13
|
+
*/_validateCommand(command){if(!command){throw new Error("Command is required");}if(!(0,_NativeInterfaces.isValidCommand)(command)){throw new Error(`Invalid command: ${command}. Available commands: ${Object.values(_NativeInterfaces.NATIVE_COMMANDS).join(", ")}`);}if(!this.isNativeEnvironment){throw new Error("Native bridge not available. Ensure you are running in a native WebView environment.");}}/**
|
|
14
14
|
* Execute command on Android platform
|
|
15
|
-
*/_executeAndroidCommand(command,data){try{if(typeof window.NativeBridge[command]===
|
|
15
|
+
*/_executeAndroidCommand(command,data){try{if(typeof window.NativeBridge[command]==="function"){console.log(`๐ Calling Android method '${command}'`,data?{data}:"(no data)");// Always pass data parameter to match Kotlin method signatures
|
|
16
16
|
window.NativeBridge[command](data);return true;}else{throw new Error(`Android bridge method '${command}' not found`);}}catch(error){console.error(`Error executing Android command '${command}':`,error);throw error;}}/**
|
|
17
17
|
* Execute command on iOS platform
|
|
18
18
|
*/_executeIOSCommand(command,data){try{// For iOS, we always send a message object
|
|
19
19
|
const message={command:command,data:data// This can be null/undefined, iOS bridge should handle it
|
|
20
|
-
};console.log(`๐ Calling iOS method '${command}'`,data?{data}:
|
|
20
|
+
};console.log(`๐ Calling iOS method '${command}'`,data?{data}:"(no data)");window.webkit.messageHandlers.NativeBridge.postMessage(message);return true;}catch(error){console.error(`Error executing iOS command '${command}':`,error);throw error;}}/**
|
|
21
21
|
* Main method to call native commands
|
|
22
22
|
* @param {string} command - The command to execute (from NATIVE_COMMANDS)
|
|
23
23
|
* @param {any} data - Optional data to pass to the native side
|
|
24
24
|
* @returns {boolean} - Success status
|
|
25
25
|
*/call(command,data=null){try{// Validate environment and command
|
|
26
|
-
this._validateCommand(command);console.log(`๐ Executing native command: ${command}`,data?{data}:
|
|
26
|
+
this._validateCommand(command);console.log(`๐ Executing native command: ${command}`,data?{data}:"");// Execute on appropriate platform
|
|
27
27
|
if(this.isAndroid){return this._executeAndroidCommand(command,data);}else if(this.isIOS){return this._executeIOSCommand(command,data);}return false;}catch(error){console.error(`๐ Failed to execute command '${command}':`,error);// In development, we might want to show user-friendly errors
|
|
28
|
-
if(process.env.NODE_ENV===
|
|
28
|
+
if(process.env.NODE_ENV==="development"){console.warn(`๐ Development mode: Native command '${command}' failed. This is expected when running in browser.`);}throw error;}}/**
|
|
29
29
|
* Camera-specific methods for easier usage
|
|
30
30
|
*/camera={/**
|
|
31
31
|
* Open camera for photo capture
|
|
@@ -36,7 +36,7 @@ if(process.env.NODE_ENV==='development'){console.warn(`๐ Development mode: Na
|
|
|
36
36
|
*/file={/**
|
|
37
37
|
* Open file picker
|
|
38
38
|
* @param {string} mimeType - MIME type filter (e.g., 'image/*', 'application/pdf')
|
|
39
|
-
*/pick:(mimeType=
|
|
39
|
+
*/pick:(mimeType="*/*")=>this.call(_NativeInterfaces.NATIVE_COMMANDS.PICK_FILE,mimeType),/**
|
|
40
40
|
* Open file with external app
|
|
41
41
|
* @param {string} fileUrl - URL of the file to open
|
|
42
42
|
* @param {string} mimeType - Optional MIME type
|
|
@@ -45,11 +45,11 @@ if(process.env.NODE_ENV==='development'){console.warn(`๐ Development mode: Na
|
|
|
45
45
|
*/haptic={/**
|
|
46
46
|
* Request haptic feedback
|
|
47
47
|
* @param {string} feedbackType - Type of haptic feedback
|
|
48
|
-
*/feedback:(feedbackType=
|
|
48
|
+
*/feedback:(feedbackType="light")=>this.call(_NativeInterfaces.NATIVE_COMMANDS.REQUEST_HAPTIC_FEEDBACK,feedbackType)};/**
|
|
49
49
|
* Get environment info
|
|
50
|
-
*/getEnvironmentInfo(){return{isAndroid:this.isAndroid,isIOS:this.isIOS,isNativeEnvironment:this.isNativeEnvironment,platform:this.isAndroid?
|
|
50
|
+
*/getEnvironmentInfo(){return{isAndroid:this.isAndroid,isIOS:this.isIOS,isNativeEnvironment:this.isNativeEnvironment,platform:this.isAndroid?"android":this.isIOS?"ios":"web"};}/**
|
|
51
51
|
* Check if native bridge is available
|
|
52
52
|
*/isAvailable(){return this.isNativeEnvironment;}/**
|
|
53
53
|
* Debug method to test native bridge connectivity
|
|
54
|
-
*/testConnection(){console.group(
|
|
54
|
+
*/testConnection(){console.group("๐ NativeBridge Connection Test");console.log("Environment:",this.getEnvironmentInfo());console.log("Available commands:",Object.values(_NativeInterfaces.NATIVE_COMMANDS));if(this.isAndroid&&window.NativeBridge){console.log("Android bridge methods:",Object.getOwnPropertyNames(window.NativeBridge));}if(this.isIOS&&window.webkit?.messageHandlers?.NativeBridge){console.log("iOS webkit bridge available");}console.groupEnd();}}// Export singleton instance
|
|
55
55
|
exports.NativeBridgeUtil=NativeBridgeUtil;const nativeBridge=new NativeBridgeUtil();var _default=exports.default=nativeBridge;// Also export the class for advanced usage
|
|
@@ -1,54 +1,76 @@
|
|
|
1
1
|
"use strict";var _renameAndroidProject=require("./renameAndroidProject.js");var _setupServer=require("./setupServer.js");var _child_process=require("child_process");var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _utils=require("./utils.js");var _TerminalProgress=_interopRequireDefault(require("./TerminalProgress.js"));// Import the AAB builder
|
|
2
|
-
function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const configPath=`${process.env.PWD}/config/config.json`;const pwd=`${process.cwd()}/node_modules/catalyst-core/dist/native`;const ANDROID_PACKAGE="io.yourname.androidproject";// Default values for AAB building
|
|
2
|
+
function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const configPath=`${process.env.PWD}/config/config.json`;const publicPath=`${process.env.PWD}/public`;const pwd=`${process.cwd()}/node_modules/catalyst-core/dist/native`;const ANDROID_PACKAGE="io.yourname.androidproject";// Default values for AAB building
|
|
3
3
|
const DEFAULT_PROJECT_PATH=`${pwd}/androidProject`;const DEFAULT_DEPLOYMENT_PATH="./deployment";const DEFAULT_OLD_PROJECT_NAME="androidProject";// Use actual project name in catalyst
|
|
4
|
-
const DEFAULT_OVERWRITE_EXISTING=true;const steps={config:
|
|
5
|
-
const buildType=WEBVIEW_CONFIG.android.buildType||
|
|
6
|
-
const buildType=androidConfig.buildType||
|
|
7
|
-
if(!deviceId.startsWith(
|
|
4
|
+
const DEFAULT_OVERWRITE_EXISTING=true;const steps={config:"Initialize Configuration",tools:"Validate Android Tools",emulator:"Check and Start Emulator",copyAssets:"Copy Build Assets",build:"Build and Install Application",aab:"Build Signed AAB"};const progressConfig={titlePaddingTop:2,titlePaddingBottom:1,stepPaddingLeft:4,stepSpacing:1,errorPaddingLeft:6,bottomMargin:2};const progress=new _TerminalProgress.default(steps,"Catalyst Android Build",progressConfig);async function initializeConfig(){const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG}=config;if(!WEBVIEW_CONFIG||Object.keys(WEBVIEW_CONFIG).length===0){throw new Error("WebView Config missing in "+configPath);}if(!WEBVIEW_CONFIG.android){throw new Error("Android config missing in WebView Config");}// Log build type information
|
|
5
|
+
const buildType=WEBVIEW_CONFIG.android.buildType||"debug";progress.log(`Build Type: ${buildType}`,"info");if(buildType==="release"){progress.log("Release build detected - AAB will be generated","info");}return{WEBVIEW_CONFIG};}function validateAndroidTools(androidConfig){const ANDROID_SDK=androidConfig.sdkPath;const ADB_PATH=`${ANDROID_SDK}/platform-tools/adb`;const EMULATOR_PATH=`${ANDROID_SDK}/emulator/emulator`;progress.log("Validating Android tools...","info");if(!ANDROID_SDK){throw new Error("Android SDK path is not configured");}if(!_fs.default.existsSync(ANDROID_SDK)){throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);}if(!_fs.default.existsSync(ADB_PATH)){throw new Error(`ADB not found at: ${ADB_PATH}`);}try{;(0,_utils.runCommand)(`${ADB_PATH} version`);progress.log("ADB validation successful","success");}catch(error){throw new Error(`ADB is not working properly: ${error.message}`);}// Skip emulator validation for release builds
|
|
6
|
+
const buildType=androidConfig.buildType||"debug";if(buildType!=="release"){if(!_fs.default.existsSync(EMULATOR_PATH)){throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);}try{;(0,_utils.runCommand)(`${EMULATOR_PATH} -version`);progress.log("Emulator validation successful","success");}catch(error){throw new Error(`Emulator is not working properly: ${error.message}`);}try{const avdOutput=(0,_utils.runCommand)(`${EMULATOR_PATH} -list-avds`);if(!avdOutput.includes(androidConfig.emulatorName)){throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);}progress.log(`Emulator "${androidConfig.emulatorName}" exists`,"success");}catch(error){throw new Error(`Error checking emulator AVD: ${error.message}`);}}else{progress.log("Skipping emulator validation for release build","info");}progress.log("Android tools validation completed successfully!","success");return{ANDROID_SDK,ADB_PATH,EMULATOR_PATH};}async function detectPhysicalDevice(ADB_PATH){try{progress.log("Detecting physical devices...","info");const devices=(0,_utils.runCommand)(`${ADB_PATH} devices -l`);const lines=devices.split("\n").filter(line=>line.trim()&&!line.includes("List of devices"));for(const line of lines){const parts=line.trim().split(/\s+/);if(parts.length>=2&&parts[1]==="device"){const deviceId=parts[0];// Check if it's a physical device (not emulator)
|
|
7
|
+
if(!deviceId.startsWith("emulator-")){try{// Double-check it's not an emulator using device properties
|
|
8
8
|
const qemuCheck=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell getprop ro.kernel.qemu`);if(!qemuCheck.trim()){// Empty means physical device
|
|
9
|
-
progress.log(`Physical device detected: ${deviceId}`,
|
|
10
|
-
let deviceModel=
|
|
9
|
+
progress.log(`Physical device detected: ${deviceId}`,"success");// Get device model/name for better logging
|
|
10
|
+
let deviceModel="Unknown Device";try{const model=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell getprop ro.product.model`);deviceModel=model.trim()||deviceModel;}catch(e){// Ignore model detection errors
|
|
11
11
|
}return{id:deviceId,model:deviceModel};}}catch(error){// If we can't check properties, skip this device
|
|
12
|
-
continue;}}}}progress.log(
|
|
13
|
-
const connectionTest=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell echo "test"`);if(!connectionTest.includes(
|
|
14
|
-
try{const developerOptions=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell settings get global development_settings_enabled`);if(developerOptions.trim()!==
|
|
15
|
-
try{const usbDebugging=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell settings get global adb_enabled`);if(usbDebugging.trim()!==
|
|
16
|
-
try{(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell pm list packages -3 | head -1`);}catch(error){progress.log(
|
|
17
|
-
const sourcePath=`${process.env.PWD}/build/`;const destPath=`${pwd}/androidProject/app/src/main/assets/build
|
|
18
|
-
(0,_utils.runCommand)(`mkdir -p ${destPath}`)
|
|
19
|
-
(0,_utils.runCommand)(`rm -rf ${destPath}/*`);// Files to exclude from copying
|
|
20
|
-
const excludePatterns=[
|
|
21
|
-
for(const pattern of excludePatterns){const checkCommand=`find ${destPath} -name "${pattern}" | wc -l`;const count=parseInt((0,_utils.runCommand)(checkCommand).trim(),10);if(count>0){progress.log(`Warning: Found ${count} instances of excluded file ${pattern}`,
|
|
12
|
+
continue;}}}}progress.log("No physical devices detected","info");return null;}catch(error){progress.log("Error detecting physical devices: "+error.message,"error");return null;}}async function testPhysicalDeviceInstallation(ADB_PATH,deviceId){try{progress.log(`Testing installation capability on device ${deviceId}...`,"info");// Test 1: Basic ADB connection
|
|
13
|
+
const connectionTest=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell echo "test"`);if(!connectionTest.includes("test")){progress.log("Device connection test failed","error");return false;}// Test 2: Check if developer options are enabled
|
|
14
|
+
try{const developerOptions=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell settings get global development_settings_enabled`);if(developerOptions.trim()!=="1"){progress.log("Developer options not enabled on device","warning");return false;}}catch(error){progress.log("Cannot verify developer options status","warning");}// Test 3: Check USB debugging status
|
|
15
|
+
try{const usbDebugging=(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell settings get global adb_enabled`);if(usbDebugging.trim()!=="1"){progress.log("USB debugging not enabled on device","warning");return false;}}catch(error){progress.log("Cannot verify USB debugging status","warning");}// Test 4: Check if we can access package manager
|
|
16
|
+
try{;(0,_utils.runCommand)(`${ADB_PATH} -s ${deviceId} shell pm list packages -3 | head -1`);}catch(error){progress.log("Cannot access package manager on device","error");return false;}progress.log(`Device ${deviceId} is ready for installation`,"success");return true;}catch(error){progress.log(`Installation capability test failed: ${error.message}`,"error");return false;}}async function checkEmulator(ADB_PATH){try{const devices=(0,_utils.runCommand)(`${ADB_PATH} devices`);return devices.includes("emulator");}catch(error){progress.log("Error checking emulator status: "+error.message,"error");return false;}}async function handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig){progress.log("Setting up emulator...","info");const emulatorRunning=await checkEmulator(ADB_PATH);if(!emulatorRunning){progress.log("No emulator running, attempting to start one...","info");await startEmulator(EMULATOR_PATH,androidConfig);await new Promise(resolve=>setTimeout(resolve,5000));}else{progress.log("Emulator already running","success");}return{type:"emulator",name:androidConfig.emulatorName};}async function startEmulator(EMULATOR_PATH,androidConfig){progress.log(`Starting emulator: ${androidConfig.emulatorName}...`,"info");return new Promise((resolve,reject)=>{;(0,_child_process.exec)(`${EMULATOR_PATH} -avd ${androidConfig.emulatorName} -read-only > /dev/null &`,(error,stdout,stderr)=>{if(error){progress.log("Error starting emulator: "+error.message,"error");reject(error);}else{progress.log("Emulator started successfully","success");resolve();}});});}async function copyBuildAssets(androidConfig,buildOptimisation=false){if(!buildOptimisation)return;progress.log("Copying build assets to Android project...","info");try{// Define source and destination paths
|
|
17
|
+
const sourcePath=`${process.env.PWD}/build/`;const destPath=`${pwd}/androidProject/app/src/main/assets/build/`// Create destination directory if it doesn't exist
|
|
18
|
+
;(0,_utils.runCommand)(`mkdir -p ${destPath}`)// Clear existing destination to avoid conflicts
|
|
19
|
+
;(0,_utils.runCommand)(`rm -rf ${destPath}/*`);// Files to exclude from copying
|
|
20
|
+
const excludePatterns=["route-manifest.json.gz","route-manifest.json.br"];if(buildOptimisation){progress.log("Running with build optimization...","info");const excludeParams=excludePatterns.map(pattern=>`--exclude="${pattern}"`).join(" ");const rsyncCommand=`rsync -av ${excludeParams} ${sourcePath} ${destPath}`;progress.log("Executing rsync command with exclusions...","info");(0,_utils.runCommand)(rsyncCommand);// Verify excluded files don't exist in destination
|
|
21
|
+
for(const pattern of excludePatterns){const checkCommand=`find ${destPath} -name "${pattern}" | wc -l`;const count=parseInt((0,_utils.runCommand)(checkCommand).trim(),10);if(count>0){progress.log(`Warning: Found ${count} instances of excluded file ${pattern}`,"warning");(0,_utils.runCommand)(`find ${destPath} -name "${pattern}" -delete`);}}progress.log("Build assets copied with optimization (excluded route-manifest JSON files)","success");}else{progress.log("Running without build optimization...","info");const exclusions=excludePatterns.map(pattern=>`-not -name "${pattern}"`).join(" ");const copyCommand=`find ${sourcePath} -type f ${exclusions} -exec cp --parents {} ${destPath} \\;`;progress.log(`Executing copy command with exclusions...`,"info");(0,_utils.runCommand)(copyCommand);progress.log("Build assets copied successfully!","success");}}catch(error){throw new Error("Error copying build assets: "+error.message);}}async function buildApp(ADB_PATH,androidConfig,buildOptimisation,buildType="debug",targetDevice=null){progress.log("Building and installing app...","info");try{// Build command without monkey launch
|
|
22
22
|
let buildCommand=`cd ${pwd}/androidProject && ./gradlew generateWebViewConfig -PconfigPath=${configPath} -PbuildOptimisation=${buildOptimisation} && ./gradlew clean installDebug --quiet --console=rich`;// Add device-specific install target if physical device
|
|
23
|
-
if(targetDevice&&targetDevice.type===
|
|
24
|
-
try{progress.log(
|
|
25
|
-
|
|
23
|
+
if(targetDevice&&targetDevice.type==="physical"){buildCommand=buildCommand.replace("installDebug",`installDebug -Pandroid.injected.target.device=${targetDevice.id}`);}await(0,_utils.runInteractiveCommand)("sh",["-c",buildCommand],{"BUILD SUCCESSFUL":""});progress.log("App build and installation completed successfully!","success");}catch(error){throw new Error("Error building/installing app: "+error.message);}}async function launchApp(ADB_PATH,buildType="debug",targetDevice=null){if(!targetDevice){progress.log("No target device specified, skipping launch","warning");return;}if(targetDevice.type==="physical"){progress.log(`App installed on physical device: ${targetDevice.model}`,"success");progress.log("Manual launch required - check your device to open the app","info");return;}// Launch on emulator
|
|
24
|
+
try{progress.log("Launching app on emulator...","info");const packageName=`${ANDROID_PACKAGE}${buildType==="debug"?".debug":""}`;const launchCommand=`${ADB_PATH} shell monkey -p ${packageName} 1`;await(0,_utils.runInteractiveCommand)("sh",["-c",launchCommand],{});progress.log("App launched successfully on emulator!","success");}catch(error){progress.log(`Warning: Could not auto-launch app: ${error.message}`,"warning");progress.log("App was installed successfully, but auto-launch failed","info");}}async function copySplashscreenAssets(){try{const sourcePath=`${process.env.PWD}/build/`;const destPath=`${pwd}/androidProject/app/src/main/res`;const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG={}}=config;const themesFile=`${destPath}/values/themes.xml`;const themesNightFile=`${destPath}/values-night/themes.xml`;let themesContent=_fs.default.readFileSync(themesFile,"utf8");let themesNightContent=_fs.default.readFileSync(themesNightFile,"utf8");const colorsFile=`${destPath}/values/colors.xml`;let colorsContent=_fs.default.readFileSync(colorsFile,"utf8");const splashscreenProperties={icon:'<item name="android:windowSplashScreenAnimatedIcon">@drawable/splashscreen</item>',backgroundColor:'<item name="android:windowSplashScreenBackground">@color/splashscreen_background</item>'};const hasSplashscreenConfig=!!WEBVIEW_CONFIG.splashScreen;// Always remove splashscreen properties and colors first (regardless of config)
|
|
25
|
+
themesContent=themesContent.replace(splashscreenProperties.backgroundColor,"");themesContent=themesContent.replace(splashscreenProperties.icon,"");themesNightContent=themesNightContent.replace(splashscreenProperties.backgroundColor,"");themesNightContent=themesNightContent.replace(splashscreenProperties.icon,"");// Also remove splashscreen color from colors.xml
|
|
26
|
+
const existingColorLine=colorsContent.split("\n").find(line=>line.includes('name="splashscreen_background"'));if(existingColorLine){colorsContent=colorsContent.replace(existingColorLine,"");}if(!hasSplashscreenConfig){progress.log("Removed splashscreen properties from themes.xml and colors.xml","info");_fs.default.writeFileSync(colorsFile,colorsContent);_fs.default.writeFileSync(themesFile,themesContent);_fs.default.writeFileSync(themesNightFile,themesNightContent);return;}const styleEndTag="</style>";const insertPosition=themesContent.lastIndexOf(styleEndTag);if(insertPosition!==-1){const beforeStyle=themesContent.substring(0,insertPosition).trimEnd();const afterStyle=themesContent.substring(insertPosition);const splashscreenProps=Object.values(splashscreenProperties).map(prop=>`\t\t${prop}`).join("\n");// Insert properties with proper spacing
|
|
27
|
+
themesContent=beforeStyle+"\n"+splashscreenProps+"\n\t"+afterStyle;progress.log("Added splashscreen properties to themes.xml","info");}const nightInsertPosition=themesNightContent.lastIndexOf(styleEndTag);if(nightInsertPosition!==-1){const beforeNightStyle=themesNightContent.substring(0,nightInsertPosition).trimEnd();const afterNightStyle=themesNightContent.substring(nightInsertPosition);const splashscreenProps=Object.values(splashscreenProperties).map(prop=>`\t\t${prop}`).join("\n");// Insert properties with proper spacing
|
|
28
|
+
themesNightContent=beforeNightStyle+"\n"+splashscreenProps+"\n\t"+afterNightStyle;progress.log("Added splashscreen properties to themes-night.xml","info");}_fs.default.writeFileSync(themesFile,themesContent);_fs.default.writeFileSync(themesNightFile,themesNightContent);// Check for splashscreen image file in public directory
|
|
29
|
+
const imageFormats=["png","jpg","jpeg","gif","bmp","svg","webp"];let splashscreenImageFound=false;// Ensure public directory exists
|
|
30
|
+
if(!_fs.default.existsSync(publicPath)){progress.log(`Warning: Public directory not found at ${publicPath}`,"warning");return;}// Create drawable directory if it doesn't exist
|
|
31
|
+
const drawableDir=`${destPath}/drawable`;if(!_fs.default.existsSync(drawableDir)){_fs.default.mkdirSync(drawableDir,{recursive:true});}// Remove existing splashscreen assets to avoid conflicts
|
|
32
|
+
for(const format of imageFormats){const existingSplashscreenPath=`${destPath}/drawable/splashscreen.${format}`;if(_fs.default.existsSync(existingSplashscreenPath)){_fs.default.unlinkSync(existingSplashscreenPath);progress.log(`Removed existing splashscreen.${format}`,"info");}}for(const format of imageFormats){const splashscreenImagePath=`${publicPath}/splashscreen.${format}`;if(_fs.default.existsSync(splashscreenImagePath)){// Copy splashscreen image to drawable directory
|
|
33
|
+
const destImagePath=`${destPath}/drawable/splashscreen.${format}`;_fs.default.copyFileSync(splashscreenImagePath,destImagePath);progress.log(`Splashscreen image copied: splashscreen.${format}`,"success");splashscreenImageFound=true;break;}}if(!splashscreenImageFound){// Use the default Android launcher icon
|
|
34
|
+
const launcherIconPath=`${destPath}/drawable/splashscreen_fallback.png`;const destinationPath=`${destPath}/drawable/splashscreen.png`;if(_fs.default.existsSync(launcherIconPath)){_fs.default.copyFileSync(launcherIconPath,destinationPath);progress.log("Using default Android launcher icon as splashscreen","info");}}const backgroundColor=WEBVIEW_CONFIG.splashScreen?.backgroundColor||"#FFFFFF";// Ensure color starts with # and convert to Android format
|
|
35
|
+
const androidColor=backgroundColor.startsWith("#")?`#FF${backgroundColor.slice(1).toUpperCase()}`:`#FF${backgroundColor.toUpperCase()}`;const newColorEntry=`\t<color name="splashscreen_background">${androidColor}</color>`;colorsContent=colorsContent.replace("</resources>",`${newColorEntry}\n</resources>`);_fs.default.writeFileSync(colorsFile,colorsContent);if(!WEBVIEW_CONFIG.splashScreen?.backgroundColor){progress.log("Using fallback background color: #FFFFFF","info");}progress.log("Splashscreen assets copied successfully!","success");}catch(error){progress.log(`Warning: Error copying splashscreen assets: ${error.message}`,"warning");}}async function copyIconAssets(){try{const destPath=`${pwd}/androidProject/app/src/main/res`;const manifestPath=`${pwd}/androidProject/app/src/main/AndroidManifest.xml`;// Check for icon image file in public directory
|
|
36
|
+
const imageFormats=["png","jpg","jpeg","gif","bmp","svg","webp"];let iconImageFound=false;// Ensure public directory exists
|
|
37
|
+
if(!_fs.default.existsSync(publicPath)){progress.log(`Warning: Public directory not found at ${publicPath}`,"warning");return;}// Create drawable directory if it doesn't exist
|
|
38
|
+
const drawableDir=`${destPath}/drawable`;if(!_fs.default.existsSync(drawableDir)){_fs.default.mkdirSync(drawableDir,{recursive:true});}// Remove existing custom icon assets to avoid conflicts
|
|
39
|
+
for(const format of imageFormats){const existingIconPath=`${destPath}/drawable/icon.${format}`;if(_fs.default.existsSync(existingIconPath)){_fs.default.unlinkSync(existingIconPath);progress.log(`Removed existing icon.${format}`,"info");}}// Look for icon in public directory
|
|
40
|
+
for(const format of imageFormats){const iconImagePath=`${publicPath}/icon.${format}`;if(_fs.default.existsSync(iconImagePath)){// Copy icon image to drawable directory
|
|
41
|
+
const destImagePath=`${destPath}/drawable/icon.${format}`;_fs.default.copyFileSync(iconImagePath,destImagePath);progress.log(`App icon copied: icon.${format} -> icon.${format}`,"success");iconImageFound=true;break;}}// If no custom icon found, just log and return (keep default icon)
|
|
42
|
+
if(!iconImageFound){progress.log("No custom app icon found (icon.png/jpg/jpeg), using default","info");if(_fs.default.existsSync(manifestPath)){let manifestContent=_fs.default.readFileSync(manifestPath,"utf8");// Only revert if manifest currently references custom icon
|
|
43
|
+
if(manifestContent.includes('android:icon="@drawable/icon"')){manifestContent=manifestContent.replace(/android:icon="@drawable\/icon"/,'android:icon="@mipmap/ic_launcher"');manifestContent=manifestContent.replace(/android:roundIcon="@drawable\/icon"/,'android:roundIcon="@mipmap/ic_launcher_round"');_fs.default.writeFileSync(manifestPath,manifestContent);progress.log("Reverted AndroidManifest.xml to use default launcher icon","info");}}return;}// Update AndroidManifest.xml if it exists
|
|
44
|
+
if(_fs.default.existsSync(manifestPath)){let manifestContent=_fs.default.readFileSync(manifestPath,"utf8");// Look for the application tag and update the icon reference
|
|
45
|
+
const applicationTagRegex=/<application([^>]*android:icon="[^"]*"[^>]*>)/;const iconAttributeRegex=/android:icon="[^"]*"/;const roundIconAttributeRegex=/android:roundIcon="[^"]*"/;if(applicationTagRegex.test(manifestContent)){// Replace existing icon attribute
|
|
46
|
+
manifestContent=manifestContent.replace(iconAttributeRegex,'android:icon="@drawable/icon"');manifestContent=manifestContent.replace(roundIconAttributeRegex,'android:roundIcon="@drawable/icon"');progress.log("Updated app icon reference in AndroidManifest.xml","success");}_fs.default.writeFileSync(manifestPath,manifestContent);}progress.log("App icon assets processed successfully!","success");}catch(error){progress.log(`Warning: Error copying app icon assets: ${error.message}`,"warning");}}// Legacy function for backward compatibility
|
|
47
|
+
async function installApp(ADB_PATH,androidConfig,buildOptimisation,buildType="debug"){await buildApp(ADB_PATH,androidConfig,buildOptimisation,buildType);await launchApp(ADB_PATH,buildType,{type:"emulator"});}async function createAABConfig(androidConfig){// Create AAB configuration based on WebView config with defaults applied
|
|
26
48
|
const aabConfig={// Apply defaults here in buildAppAndroid.js
|
|
27
49
|
projectPath:androidConfig.projectPath||DEFAULT_PROJECT_PATH,deploymentPath:DEFAULT_DEPLOYMENT_PATH,oldProjectName:DEFAULT_OLD_PROJECT_NAME,overwriteExisting:androidConfig.overwriteExisting!==undefined?androidConfig.overwriteExisting:DEFAULT_OVERWRITE_EXISTING,// Required field - use configured value or derive from other config
|
|
28
|
-
newProjectName:androidConfig.newProjectName||androidConfig.appName||androidConfig.packageName?.split(
|
|
50
|
+
newProjectName:androidConfig.newProjectName||androidConfig.appName||androidConfig.packageName?.split(".").pop()||"catalystapp",// AAB specific settings
|
|
29
51
|
createSignedAAB:true,outputPath:androidConfig.outputPath||`${process.env.PWD}/build-output`};// Add keystore configuration if available
|
|
30
52
|
if(androidConfig.keystoreConfig){aabConfig.keystoreConfig=androidConfig.keystoreConfig;}else if(androidConfig.keystore){// Map old keystore format to new format
|
|
31
53
|
aabConfig.keystoreConfig={keyAlias:androidConfig.keystore.alias||"release",storePassword:androidConfig.keystore.storePassword,keyPassword:androidConfig.keystore.keyPassword,validityYears:25,organizationInfo:{companyName:androidConfig.keystore.organizationName||"YourCompany",city:androidConfig.keystore.city||"YourCity",state:androidConfig.keystore.state||"YourState",countryCode:androidConfig.keystore.countryCode||"US"}};}// Log the configuration being used
|
|
32
|
-
progress.log(`AAB Configuration:`,
|
|
54
|
+
progress.log(`AAB Configuration:`,"info");progress.log(` Project Path: ${aabConfig.projectPath} ${aabConfig.projectPath===DEFAULT_PROJECT_PATH?"(default)":"(configured)"}`,"info");progress.log(` Deployment Path: ${aabConfig.deploymentPath} ${aabConfig.deploymentPath===DEFAULT_DEPLOYMENT_PATH?"(default)":"(configured)"}`,"info");progress.log(` Old Project Name: ${aabConfig.oldProjectName} ${aabConfig.oldProjectName===DEFAULT_OLD_PROJECT_NAME?"(default)":"(configured)"}`,"info");progress.log(` New Project Name: ${aabConfig.newProjectName}`,"info");progress.log(` Overwrite Existing: ${aabConfig.overwriteExisting} ${aabConfig.overwriteExisting===DEFAULT_OVERWRITE_EXISTING?"(default)":"(configured)"}`,"info");progress.log(` Output Path: ${aabConfig.outputPath}`,"info");return aabConfig;}async function buildSignedAAB(androidConfig){progress.log("Building signed AAB for release...","info");try{// Create AAB configuration directly without temporary file
|
|
33
55
|
const aabConfig=await createAABConfig(androidConfig);// Call the AAB builder directly with config object
|
|
34
|
-
await(0,_renameAndroidProject.buildAndroidAAB)(aabConfig);progress.log(
|
|
56
|
+
await(0,_renameAndroidProject.buildAndroidAAB)(aabConfig);progress.log("Signed AAB build completed successfully!","success");}catch(error){throw new Error("Error building signed AAB: "+error.message);}}async function buildAndroidApp(){// Initialize androidConfig outside try block to ensure it's available in catch
|
|
35
57
|
let androidConfig=null;try{await(0,_setupServer.setupServer)(configPath);// Initialize configuration
|
|
36
|
-
progress.start(
|
|
37
|
-
progress.start(
|
|
38
|
-
let targetDevice=null;if(buildType!==
|
|
39
|
-
const physicalDevice=await detectPhysicalDevice(ADB_PATH);if(physicalDevice){progress.log(`Found physical device: ${physicalDevice.model} (${physicalDevice.id})`,
|
|
58
|
+
progress.start("config");const{WEBVIEW_CONFIG}=await initializeConfig();androidConfig=WEBVIEW_CONFIG.android;const buildType=androidConfig.buildType||"debug";const buildOptimisation=!!androidConfig.buildOptimisation||false;progress.complete("config");// Validate tools and get paths
|
|
59
|
+
progress.start("tools");const{ANDROID_SDK,ADB_PATH,EMULATOR_PATH}=await validateAndroidTools(androidConfig);progress.complete("tools");// Device detection and setup (skip for release builds)
|
|
60
|
+
let targetDevice=null;if(buildType!=="release"){progress.start("emulator");// Step 1: Check for physical device first
|
|
61
|
+
const physicalDevice=await detectPhysicalDevice(ADB_PATH);if(physicalDevice){progress.log(`Found physical device: ${physicalDevice.model} (${physicalDevice.id})`,"success");// Test if we can install on physical device
|
|
40
62
|
const canInstallOnPhysical=await testPhysicalDeviceInstallation(ADB_PATH,physicalDevice.id);if(canInstallOnPhysical){// Use physical device - skip emulator entirely
|
|
41
|
-
targetDevice={type:
|
|
42
|
-
progress.log(
|
|
43
|
-
targetDevice=await handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig);}progress.complete(
|
|
44
|
-
progress.start(
|
|
45
|
-
if(buildType===
|
|
46
|
-
progress.start(
|
|
47
|
-
progress.start(
|
|
48
|
-
const summaryItems=[
|
|
49
|
-
const buildType=androidConfig?.buildType||
|
|
50
|
-
if(buildType!==
|
|
51
|
-
if(error.message.includes(
|
|
52
|
-
if(androidConfig){troubleshootingItems.push({text:`Build Type: ${buildType}`,indent:1,prefix:
|
|
53
|
-
if(targetDevice){troubleshootingItems.push({text:`Target Device: ${targetDevice.type===
|
|
63
|
+
targetDevice={type:"physical",id:physicalDevice.id,model:physicalDevice.model};progress.log(`Using physical device: ${physicalDevice.model}`,"success");}else{// Physical device failed, fallback to emulator
|
|
64
|
+
progress.log("Physical device installation test failed, falling back to emulator","warning");targetDevice=await handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig);}}else{// No physical device, use emulator (current behavior)
|
|
65
|
+
targetDevice=await handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig);}progress.complete("emulator");}else{progress.log("Skipping device setup for release build","info");}// Copy build assets
|
|
66
|
+
progress.start("copyAssets");await copyBuildAssets(androidConfig,buildOptimisation);await copySplashscreenAssets();await copyIconAssets();progress.log(`Build optimization: ${buildOptimisation?"Enabled":"Disabled"}`,"info");progress.complete("copyAssets");// Build based on type
|
|
67
|
+
if(buildType==="release"){// Build signed AAB for release
|
|
68
|
+
progress.start("aab");await buildSignedAAB(androidConfig);progress.complete("aab");}else{// Install debug app for development
|
|
69
|
+
progress.start("build");await buildApp(ADB_PATH,androidConfig,buildOptimisation,buildType,targetDevice);await launchApp(ADB_PATH,buildType,targetDevice);progress.complete("build");}// Print build summary
|
|
70
|
+
const summaryItems=["Build completed successfully:",{text:`Build Type: ${buildType}`,indent:1,prefix:"โโ ",color:"gray"},{text:`SDK Path: ${androidConfig.sdkPath}`,indent:1,prefix:"โโ ",color:"gray"},{text:`Build Optimization: ${buildOptimisation?"Enabled":"Disabled"}`,indent:1,prefix:"โโ ",color:"gray"}];if(buildType==="release"){summaryItems.push({text:`Output: Signed AAB generated in build-output/`,indent:1,prefix:"โโ ",color:"green"});}else{if(targetDevice&&targetDevice.type==="physical"){summaryItems.push({text:`Target Device: ${targetDevice.model} (Physical)`,indent:1,prefix:"โโ ",color:"green"});}else{summaryItems.push({text:`Target Device: ${androidConfig.emulatorName} (Emulator)`,indent:1,prefix:"โโ ",color:"gray"});}}progress.printTreeContent("Build Summary",summaryItems);process.exit(0);}catch(error){if(progress.currentStep){progress.fail(progress.currentStep.id,error.message);const troubleshootingItems=["Build failed. Please try the following steps:",{text:"Check if Android SDK is properly configured",indent:1,prefix:"โโ ",color:"yellow"},{text:"Verify build assets exist in the source directory",indent:1,prefix:"โโ ",color:"yellow"}];// Use androidConfig safely with null checks
|
|
71
|
+
const buildType=androidConfig?.buildType||"debug";// Device-specific troubleshooting
|
|
72
|
+
if(buildType!=="release"){// Check if error is related to device issues
|
|
73
|
+
if(error.message.includes("physical device")||error.message.includes("installation test")){troubleshootingItems.push("\nPhysical Device Issues:",{text:"Enable Developer Options on your device",indent:1,prefix:"โโ ",color:"yellow"},{text:"Enable USB Debugging in Developer Options",indent:1,prefix:"โโ ",color:"yellow"},{text:'Accept the "Allow USB Debugging" prompt on your device',indent:1,prefix:"โโ ",color:"yellow"},{text:"Try disconnecting and reconnecting your device",indent:1,prefix:"โโ ",color:"yellow"},{text:'Check if "adb devices" shows your device as authorized',indent:1,prefix:"โโ ",color:"yellow"});}if(buildType==="release"){troubleshootingItems.push({text:"Verify keystore configuration for release builds",indent:1,prefix:"โโ ",color:"yellow"},{text:"Check that keystore passwords are properly set",indent:1,prefix:"โโ ",color:"yellow"});}else{troubleshootingItems.push({text:"Verify that the emulator exists and is working",indent:1,prefix:"โโ ",color:"yellow"},{text:"If using physical device, ensure it stays connected",indent:1,prefix:"โโ ",color:"yellow"});}}else{troubleshootingItems.push({text:"Verify keystore configuration for release builds",indent:1,prefix:"โโ ",color:"yellow"},{text:"Check that keystore passwords are properly set",indent:1,prefix:"โโ ",color:"yellow"});}troubleshootingItems.push({text:'Run "npm run setupEmulator:android" to reconfigure Android settings',indent:1,prefix:"โโ ",color:"yellow"},"\nVerify Configuration:");// Add configuration details only if androidConfig is available
|
|
74
|
+
if(androidConfig){troubleshootingItems.push({text:`Build Type: ${buildType}`,indent:1,prefix:"โโ ",color:"gray"},{text:`Android SDK Path: ${androidConfig.sdkPath||"Not configured"}`,indent:1,prefix:"โโ ",color:"gray"});if(buildType!=="release"){// Show device information if available
|
|
75
|
+
if(targetDevice){troubleshootingItems.push({text:`Target Device: ${targetDevice.type==="physical"?`${targetDevice.model} (Physical)`:`${androidConfig.emulatorName} (Emulator)`}`,indent:1,prefix:"โโ ",color:"gray"});}else{troubleshootingItems.push({text:`Selected Emulator: ${androidConfig.emulatorName||"Not configured"}`,indent:1,prefix:"โโ ",color:"gray"});}}else{troubleshootingItems.push({text:`Output Path: ${androidConfig.outputPath||"build-output/"}`,indent:1,prefix:"โโ ",color:"gray"});}}else{troubleshootingItems.push({text:"Configuration could not be loaded",indent:1,prefix:"โโ ",color:"red"},{text:"Check if config/config.json exists and has valid Android configuration",indent:1,prefix:"โโ ",color:"red"});}progress.printTreeContent("Troubleshooting Guide",troubleshootingItems);}process.exit(1);}}// Execute the main build process
|
|
54
76
|
buildAndroidApp();
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="DeviceTable">
|
|
4
|
-
<option name="columnSorters">
|
|
5
|
-
<list>
|
|
6
|
-
<ColumnSorterState>
|
|
7
|
-
<option name="column" value="Name" />
|
|
8
|
-
<option name="order" value="ASCENDING" />
|
|
9
|
-
</ColumnSorterState>
|
|
10
|
-
</list>
|
|
11
|
-
</option>
|
|
12
|
-
</component>
|
|
13
|
-
</project>
|