catalyst-core-internal 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +3 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +22 -9
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +18 -0
- package/dist/native/buildAppIos.js +1 -1
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +1 -1
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +1 -1
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +7 -7
- package/dist/native/pluginComposerAndroid.js +1 -1
- package/package.json +1 -1
|
@@ -14,15 +14,15 @@ import java.util.Properties
|
|
|
14
14
|
internal data class PluginRequest(
|
|
15
15
|
val pluginId: String,
|
|
16
16
|
val command: String,
|
|
17
|
-
val data:
|
|
17
|
+
val data: JSONObject?,
|
|
18
18
|
val requestId: String?
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
private class PluginBridgeRuntimeError(
|
|
22
|
-
|
|
22
|
+
val publicMessage: String,
|
|
23
23
|
val code: String,
|
|
24
24
|
cause: Throwable? = null
|
|
25
|
-
) : Exception(
|
|
25
|
+
) : Exception(publicMessage, cause)
|
|
26
26
|
|
|
27
27
|
class PluginBridge(
|
|
28
28
|
private val activity: Activity,
|
|
@@ -66,6 +66,19 @@ class PluginBridge(
|
|
|
66
66
|
return rawValue.trim().ifEmpty { null }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
private fun readOptionalObject(body: JSONObject, key: String): JSONObject? {
|
|
70
|
+
if (!body.has(key) || body.isNull(key)) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val rawValue = body.get(key)
|
|
75
|
+
if (rawValue !is JSONObject) {
|
|
76
|
+
throw IllegalArgumentException("$key must be an object when provided")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return rawValue
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
internal fun parseRequest(payload: String?): PluginRequest {
|
|
70
83
|
if (payload.isNullOrBlank()) {
|
|
71
84
|
throw IllegalArgumentException("Payload is required")
|
|
@@ -79,7 +92,7 @@ class PluginBridge(
|
|
|
79
92
|
return PluginRequest(
|
|
80
93
|
pluginId = readRequiredString(body, "pluginId"),
|
|
81
94
|
command = readRequiredString(body, "command"),
|
|
82
|
-
data =
|
|
95
|
+
data = readOptionalObject(body, "data"),
|
|
83
96
|
requestId = readOptionalString(body, "requestId")
|
|
84
97
|
)
|
|
85
98
|
}
|
|
@@ -121,7 +134,7 @@ class PluginBridge(
|
|
|
121
134
|
val plugin = try {
|
|
122
135
|
getPluginForId(request.pluginId)
|
|
123
136
|
} catch (error: PluginBridgeRuntimeError) {
|
|
124
|
-
sendBridgeError(error.
|
|
137
|
+
sendBridgeError(error.publicMessage, error.code, request)
|
|
125
138
|
return
|
|
126
139
|
}
|
|
127
140
|
|
|
@@ -138,10 +151,10 @@ class PluginBridge(
|
|
|
138
151
|
} catch (error: IllegalArgumentException) {
|
|
139
152
|
sendBridgeError(error.message ?: "Invalid payload", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
140
153
|
} catch (error: JSONException) {
|
|
141
|
-
sendBridgeError("Invalid JSON payload
|
|
154
|
+
sendBridgeError("Invalid JSON payload", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
142
155
|
} catch (error: Exception) {
|
|
143
156
|
Log.e(TAG, "Plugin command failed for ${request?.pluginId ?: "<unknown>"}.${request?.command ?: "<unknown>"}", error)
|
|
144
|
-
sendBridgeError("Plugin execution failed
|
|
157
|
+
sendBridgeError("Plugin execution failed", ERROR_CODE_PLUGIN_EXECUTION_FAILED, request)
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
160
|
|
|
@@ -175,7 +188,7 @@ class PluginBridge(
|
|
|
175
188
|
private fun getPluginForId(pluginId: String): CatalystPlugin {
|
|
176
189
|
val className = pluginIdToClassName[pluginId]
|
|
177
190
|
?: throw PluginBridgeRuntimeError(
|
|
178
|
-
"
|
|
191
|
+
"Plugin is not registered",
|
|
179
192
|
ERROR_CODE_PLUGIN_NOT_REGISTERED
|
|
180
193
|
)
|
|
181
194
|
|
|
@@ -187,7 +200,7 @@ class PluginBridge(
|
|
|
187
200
|
} catch (error: Exception) {
|
|
188
201
|
Log.e(TAG, "Failed to instantiate plugin class $className for plugin $pluginId", error)
|
|
189
202
|
throw PluginBridgeRuntimeError(
|
|
190
|
-
"
|
|
203
|
+
"Plugin could not be initialized",
|
|
191
204
|
ERROR_CODE_PLUGIN_INSTANTIATION_FAILED,
|
|
192
205
|
error
|
|
193
206
|
)
|
|
@@ -118,4 +118,22 @@ class PluginBridgeTest {
|
|
|
118
118
|
assertTrue(error.message?.isNotBlank() == true)
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
@Test
|
|
123
|
+
fun `parseRequest rejects non object data`() {
|
|
124
|
+
try {
|
|
125
|
+
PluginBridge.parseRequest(
|
|
126
|
+
"""
|
|
127
|
+
{
|
|
128
|
+
"pluginId": "device-info-plugin",
|
|
129
|
+
"command": "getDeviceInfo",
|
|
130
|
+
"data": "unsafe"
|
|
131
|
+
}
|
|
132
|
+
""".trimIndent()
|
|
133
|
+
)
|
|
134
|
+
fail("Expected non-object data to throw")
|
|
135
|
+
} catch (error: IllegalArgumentException) {
|
|
136
|
+
assertEquals("data must be an object when provided", error.message)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
121
139
|
}
|
|
@@ -60,7 +60,7 @@ projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCo
|
|
|
60
60
|
projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/,)/,`$1\n\t\t\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */,`);// 3. Add to packageProductDependencies array
|
|
61
61
|
projectContent=projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/,`$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);// 4. Add to XCSwiftPackageProductDependency section
|
|
62
62
|
projectContent=projectContent.replace(/(\/\* End XCSwiftPackageProductDependency section \*\/)/,`\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = C99974322E97D56900C25611 /* XCLocalSwiftPackageReference "." */;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n$1`);fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("✅ CatalystNotifications added to Xcode project","success");}else if(!isNotificationsEnabled&&hasNotifications){progress.log("Removing CatalystNotifications from Xcode project","info");// Remove all CatalystNotifications entries
|
|
63
|
-
projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/ = {isa = PBXBuildFile; productRef = [A-F0-9]+ \/\* CatalystNotifications \*\/; };\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = [A-F0-9]+ \/\* XCLocalSwiftPackageReference "." \*\/;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n/g,"");fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("✅ CatalystNotifications removed from Xcode project","success");}else{progress.log("Package dependencies already correct","info");}}catch(error){progress.log(`❌ Failed to update package dependencies: ${error.message}`,"error");throw error;}}async function updateInfoPlist(pluginComposition={}){try{const infoPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info.plist");const infoReleasePlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info-Release.plist");const googleServicesPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,GOOGLE_SERVICES_FILENAME);const googleClientId=WEBVIEW_CONFIG.googleSignIn?.clientId||WEBVIEW_CONFIG.googleSignIn?.webClientId||"";const iosClientId=WEBVIEW_CONFIG.googleSignIn?.iosClientId||"";const googleServicesContent=fs.existsSync(googleServicesPlistPath)?fs.readFileSync(googleServicesPlistPath,"utf8"):null;const reversedClientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>REVERSED_CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const clientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const computeReversed=value=>{if(!value)return"";return value.split(".").reverse().join(".");};const resolvedClientIdForScheme=iosClientId||googleClientId||clientIdFromServices||"";const resolvedReversedClientId=reversedClientIdFromServices||computeReversed(resolvedClientIdForScheme);if(isGoogleSignInEnabled&&!resolvedReversedClientId){progress.fail("config","Google Sign-In enabled but no valid clientId found");process.exit(1);}const plistTargets=[infoPlistPath,infoReleasePlistPath];const pluginUrlSchemes=pluginComposition.urlSchemes||[];const pluginQuerySchemes=pluginComposition.querySchemes||[];const pluginInfoPlist=pluginComposition.infoPlist||{};plistTargets.forEach(plistPath=>{if(!fs.existsSync(plistPath)){return;}restoreManagedFileFromBaseline(plistPath);const plistObject=readPlistObject(plistPath);plistObject.CFBundleDisplayName=iosConfig.appName||"Catalyst Application";mergeIntoTopLevelObject(plistObject,pluginInfoPlist,"ios.infoPlist");if(isGoogleSignInEnabled&&resolvedReversedClientId){mergeUrlSchemes(plistObject,[{name:"googleSignIn",schemes:[resolvedReversedClientId]}]);mergeQuerySchemes(plistObject,["google",resolvedReversedClientId]);}mergeUrlSchemes(plistObject,pluginUrlSchemes);mergeQuerySchemes(plistObject,pluginQuerySchemes);writePlistObject(plistPath,plistObject);});}catch(err){progress.fail("config",err);process.exit(1);}}async function updateEntitlements(pluginComposition={}){try{const entitlementsPath=path.join(PROJECT_DIR,PROJECT_NAME,`${PROJECT_NAME}.entitlements`);if(!fs.existsSync(entitlementsPath)){return;}restoreManagedFileFromBaseline(entitlementsPath);const entitlementsObject=readPlistObject(entitlementsPath);mergeIntoTopLevelObject(entitlementsObject,pluginComposition.entitlements||{},"ios.entitlements");writePlistObject(entitlementsPath,entitlementsObject);}catch(error){progress.fail("config",error);process.exit(1);}}async function removePluginResourcesFromXcodeProject(){try{const projectFilePath=getXcodeProjectFilePath();if(!fs.existsSync(projectFilePath)){return;}let projectContent=fs.readFileSync(projectFilePath,"utf8");const patterns=[/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/ = \{isa = PBXFileReference;[^\n]*path = [^;]*PluginResources\/[^;]+; sourceTree = "<group>"; \};\n/g,/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/ = \{isa = PBXBuildFile;[^\n]*\};\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/,\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/,\n/g];let modified=false;for(const pattern of patterns){
|
|
63
|
+
projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/ = {isa = PBXBuildFile; productRef = [A-F0-9]+ \/\* CatalystNotifications \*\/; };\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = [A-F0-9]+ \/\* XCLocalSwiftPackageReference "." \*\/;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n/g,"");fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("✅ CatalystNotifications removed from Xcode project","success");}else{progress.log("Package dependencies already correct","info");}}catch(error){progress.log(`❌ Failed to update package dependencies: ${error.message}`,"error");throw error;}}async function updateInfoPlist(pluginComposition={}){try{const infoPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info.plist");const infoReleasePlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info-Release.plist");const googleServicesPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,GOOGLE_SERVICES_FILENAME);const googleClientId=WEBVIEW_CONFIG.googleSignIn?.clientId||WEBVIEW_CONFIG.googleSignIn?.webClientId||"";const iosClientId=WEBVIEW_CONFIG.googleSignIn?.iosClientId||"";const googleServicesContent=fs.existsSync(googleServicesPlistPath)?fs.readFileSync(googleServicesPlistPath,"utf8"):null;const reversedClientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>REVERSED_CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const clientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const computeReversed=value=>{if(!value)return"";return value.split(".").reverse().join(".");};const resolvedClientIdForScheme=iosClientId||googleClientId||clientIdFromServices||"";const resolvedReversedClientId=reversedClientIdFromServices||computeReversed(resolvedClientIdForScheme);if(isGoogleSignInEnabled&&!resolvedReversedClientId){progress.fail("config","Google Sign-In enabled but no valid clientId found");process.exit(1);}const plistTargets=[infoPlistPath,infoReleasePlistPath];const pluginUrlSchemes=pluginComposition.urlSchemes||[];const pluginQuerySchemes=pluginComposition.querySchemes||[];const pluginInfoPlist=pluginComposition.infoPlist||{};plistTargets.forEach(plistPath=>{if(!fs.existsSync(plistPath)){return;}restoreManagedFileFromBaseline(plistPath);const plistObject=readPlistObject(plistPath);plistObject.CFBundleDisplayName=iosConfig.appName||"Catalyst Application";mergeIntoTopLevelObject(plistObject,pluginInfoPlist,"ios.infoPlist");if(isGoogleSignInEnabled&&resolvedReversedClientId){mergeUrlSchemes(plistObject,[{name:"googleSignIn",schemes:[resolvedReversedClientId]}]);mergeQuerySchemes(plistObject,["google",resolvedReversedClientId]);}mergeUrlSchemes(plistObject,pluginUrlSchemes);mergeQuerySchemes(plistObject,pluginQuerySchemes);writePlistObject(plistPath,plistObject);});}catch(err){progress.fail("config",err);process.exit(1);}}async function updateEntitlements(pluginComposition={}){try{const entitlementsPath=path.join(PROJECT_DIR,PROJECT_NAME,`${PROJECT_NAME}.entitlements`);if(!fs.existsSync(entitlementsPath)){return;}restoreManagedFileFromBaseline(entitlementsPath);const entitlementsObject=readPlistObject(entitlementsPath);mergeIntoTopLevelObject(entitlementsObject,pluginComposition.entitlements||{},"ios.entitlements");writePlistObject(entitlementsPath,entitlementsObject);}catch(error){progress.fail("config",error);process.exit(1);}}async function removePluginResourcesFromXcodeProject(){try{const projectFilePath=getXcodeProjectFilePath();if(!fs.existsSync(projectFilePath)){return;}let projectContent=fs.readFileSync(projectFilePath,"utf8");const patterns=[/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/ = \{isa = PBXFileReference;[^\n]*path = [^;]*PluginResources\/[^;]+; sourceTree = "<group>"; \};\n/g,/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/ = \{isa = PBXBuildFile;[^\n]*\};\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/,\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/,\n/g];let modified=false;for(const pattern of patterns){const nextContent=projectContent.replace(pattern,"");if(nextContent!==projectContent){projectContent=nextContent;modified=true;}}if(modified){fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("Removed managed plugin resources from Xcode project","info");}}catch(error){throw new Error(`Could not remove plugin resources from Xcode project: ${error.message}`);}}async function addPluginResourcesToXcodeProject(resources){try{if(resources.length===0){return;}const projectFilePath=getXcodeProjectFilePath();if(!fs.existsSync(projectFilePath)){throw new Error(`Xcode project file not found at: ${projectFilePath}`);}let projectContent=fs.readFileSync(projectFilePath,"utf8");for(const resource of resources){const label=resource.bundleRelativePath;const fileRefId=generateProjectObjectId(label,"_ref");const buildFileId=generateProjectObjectId(label,"_build");const pbxprojPath=formatPbxprojPath(label);const fileType=detectPbxprojFileType(label);const fileRefEntry=`\t\t${fileRefId} /* ${label} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; path = ${pbxprojPath}; sourceTree = "<group>"; };`;projectContent=replaceRequired(projectContent,/(\/\* End PBXFileReference section \*\/)/,`${fileRefEntry}\n$1`,"PBXFileReference section not found while registering plugin resources");const buildFileEntry=`\t\t${buildFileId} /* ${label} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRefId} /* ${label} */; };`;projectContent=replaceRequired(projectContent,/(\/\* End PBXBuildFile section \*\/)/,`${buildFileEntry}\n$1`,"PBXBuildFile section not found while registering plugin resources");const groupPattern=/(\/\* iosnativeWebView \*\/ = \{[^}]*children = \([^)]*)/;projectContent=replaceRequired(projectContent,groupPattern,`$1\n\t\t\t\t${fileRefId} /* ${label} */,`,"iosnativeWebView group not found while registering plugin resources");const resourcesPattern=/(\/\* Resources \*\/ = \{[^}]*files = \([^)]*)/;projectContent=replaceRequired(projectContent,resourcesPattern,`$1\n\t\t\t\t${buildFileId} /* ${label} in Resources */,`,"Resources build phase not found while registering plugin resources");}fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log(`Registered ${resources.length} managed plugin resource(s) in Xcode project`,"success");}catch(error){throw new Error(`Could not add plugin resources to Xcode project: ${error.message}`);}}async function syncPluginResources(pluginComposition={}){try{const resources=pluginComposition.resources||[];const pluginResourceDir=path.join(PROJECT_DIR,PROJECT_NAME,PLUGIN_RESOURCE_ROOT);fs.rmSync(pluginResourceDir,{recursive:true,force:true});await removePluginResourcesFromXcodeProject();if(resources.length===0){progress.log("No managed plugin resources to sync","info");return;}for(const resource of resources){const normalizedBundleRelativePath=path.posix.normalize(resource.bundleRelativePath||"");const expectedPrefix=`${PLUGIN_RESOURCE_ROOT}/`;if(!normalizedBundleRelativePath.startsWith(expectedPrefix)||normalizedBundleRelativePath.includes("../")||path.posix.isAbsolute(normalizedBundleRelativePath)){throw new Error(`Invalid managed plugin resource path: ${resource.bundleRelativePath}`);}const targetPath=path.resolve(PROJECT_DIR,PROJECT_NAME,normalizedBundleRelativePath);if(targetPath!==pluginResourceDir&&!targetPath.startsWith(`${pluginResourceDir}${path.sep}`)){throw new Error(`Managed plugin resource escaped bundle directory: ${resource.bundleRelativePath}`);}fs.mkdirSync(path.dirname(targetPath),{recursive:true});fs.copyFileSync(resource.sourcePath,targetPath);}await addPluginResourcesToXcodeProject(resources);progress.log(`Synced ${resources.length} managed plugin resource(s)`,"success");}catch(error){progress.log(`❌ Failed to sync plugin resources: ${error.message}`,"error");throw error;}}// Function to convert JSON value to Swift property
|
|
64
64
|
function generateSwiftProperty(key,value,indent=" "){if(value===null||value===undefined){return`${indent}public static let ${key}: String? = nil`;}// Special handling for cachePattern - always convert to array
|
|
65
65
|
if(key==="cachePattern"){if(typeof value==="string"){// Convert single string to array
|
|
66
66
|
return`${indent}public static let ${key}: [String] = ["${value}"]`;}else if(Array.isArray(value)){const arrayValues=value.map(v=>`"${v}"`).join(", ");return`${indent}public static let ${key}: [String] = [${arrayValues}]`;}}if(typeof value==="string"){return`${indent}public static let ${key} = "${value}"`;}if(typeof value==="number"){return Number.isInteger(value)?`${indent}public static let ${key} = ${value}`:`${indent}public static let ${key} = ${value}`;}if(typeof value==="boolean"){return`${indent}public static let ${key} = ${value}`;}if(Array.isArray(value)){if(value.length===0){return`${indent}public static let ${key}: [String] = []`;}// Determine array type from first element
|
|
@@ -14,7 +14,7 @@ class DeviceInfoPlugin : CatalystPlugin {
|
|
|
14
14
|
private const val CALLBACK_ERROR = "onError"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
override fun handle(command: String, data:
|
|
17
|
+
override fun handle(command: String, data: JSONObject?, bridge: PluginBridgeContext) {
|
|
18
18
|
if (command != COMMAND_GET_DEVICE_INFO) {
|
|
19
19
|
bridge.callback(
|
|
20
20
|
CALLBACK_ERROR,
|
|
@@ -209,16 +209,16 @@ extension PluginBridge: WKScriptMessageHandler {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
final class PluginBridgeContext {
|
|
213
|
-
weak var webView: WKWebView?
|
|
214
|
-
weak var viewController: UIViewController?
|
|
212
|
+
public final class PluginBridgeContext {
|
|
213
|
+
public weak var webView: WKWebView?
|
|
214
|
+
public weak var viewController: UIViewController?
|
|
215
215
|
|
|
216
216
|
private let systemPluginId = "__bridge__"
|
|
217
217
|
private let bridgeErrorEvent = "PLUGIN_BRIDGE_ERROR"
|
|
218
218
|
|
|
219
|
-
let pluginId: String
|
|
220
|
-
let command: String?
|
|
221
|
-
let requestId: String?
|
|
219
|
+
public let pluginId: String
|
|
220
|
+
public let command: String?
|
|
221
|
+
public let requestId: String?
|
|
222
222
|
|
|
223
223
|
init(
|
|
224
224
|
webView: WKWebView?,
|
|
@@ -234,7 +234,7 @@ final class PluginBridgeContext {
|
|
|
234
234
|
self.requestId = requestId
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
func callback(
|
|
237
|
+
public func callback(
|
|
238
238
|
eventName: String,
|
|
239
239
|
data: Any?,
|
|
240
240
|
command: String? = nil
|
|
@@ -6,4 +6,4 @@ object GeneratedPluginIndex {
|
|
|
6
6
|
}
|
|
7
7
|
`;const pluginsDir=path.join(javaRoot,...PLUGINS_PACKAGE_PARTS);ensureDir(pluginsDir);fs.writeFileSync(path.join(pluginsDir,"GeneratedPluginIndex.kt"),indexContent);fs.rmSync(path.join(pluginsDir,"GeneratedPluginMeta.kt"),{force:true});}function escapeRegexLiteral(value){return value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");}function updateAndroidManifestPermissions(manifestPath,selectedPermissions,allKnownPluginPermissions){const uniquePermissions=asUniqueSorted(selectedPermissions);const knownPermissions=asUniqueSorted(allKnownPluginPermissions);let manifest=fs.readFileSync(manifestPath,"utf8");const beginMarker="<!-- CATALYST_PLUGIN_PERMISSIONS_START -->";const endMarker="<!-- CATALYST_PLUGIN_PERMISSIONS_END -->";const markerRegex=/[ \t]*<!-- CATALYST_PLUGIN_PERMISSIONS_START -->[\s\S]*?<!-- CATALYST_PLUGIN_PERMISSIONS_END -->\s*/g;manifest=manifest.replace(markerRegex,"");// Migration cleanup for legacy entries previously written without markers.
|
|
8
8
|
for(const permission of knownPermissions){const escaped=escapeRegexLiteral(permission);const legacyRegex=new RegExp(`^[ \\t]*<uses-permission\\s+android:name="${escaped}"\\s*/>\\s*\\n?`,"gm");manifest=manifest.replace(legacyRegex,"");}if(uniquePermissions.length>0){const permissionLines=uniquePermissions.map(permission=>` <uses-permission android:name="${permission}" />`).join("\n");const managedBlock=` ${beginMarker}\n${permissionLines}\n ${endMarker}\n`;manifest=manifest.replace(/<application\b/,`${managedBlock} <application`);}fs.writeFileSync(manifestPath,manifest);}function findDependenciesBlockRange(gradleText,gradlePath){const headerMatch=gradleText.match(/^dependencies\s*\{/m);if(!headerMatch||headerMatch.index==null){throw new Error(`Could not find dependencies block in ${gradlePath}`);}const openBraceIndex=gradleText.indexOf("{",headerMatch.index);if(openBraceIndex===-1){throw new Error(`Malformed dependencies block in ${gradlePath}`);}let depth=0;for(let index=openBraceIndex;index<gradleText.length;index++){const ch=gradleText[index];if(ch==="{")depth++;if(ch==="}")depth--;if(depth===0){return{openBraceIndex,blockEnd:index};}}throw new Error(`Could not find end of dependencies block in ${gradlePath}`);}function updateGradleDependencies(gradlePath,selectedDependencies,allKnownPluginDependencies){const uniqueDependencies=asUniqueSorted(selectedDependencies);const knownDependencies=asUniqueSorted(allKnownPluginDependencies);let gradle=fs.readFileSync(gradlePath,"utf8");const{openBraceIndex,blockEnd}=findDependenciesBlockRange(gradle,gradlePath);let blockBody=gradle.slice(openBraceIndex+1,blockEnd);const beginMarker="// CATALYST_PLUGIN_DEPENDENCIES_START";const endMarker="// CATALYST_PLUGIN_DEPENDENCIES_END";const markerRegex=/[ \t]*\/\/ CATALYST_PLUGIN_DEPENDENCIES_START[\s\S]*?\/\/ CATALYST_PLUGIN_DEPENDENCIES_END\s*/g;blockBody=blockBody.replace(markerRegex,"");// Migration cleanup for legacy entries previously written without markers.
|
|
9
|
-
for(const dependency of knownDependencies){const escaped=escapeRegexLiteral(dependency);const legacyRegex=new RegExp(`^[ \\t]*implementation\\("${escaped}"\\)\\s*\\n?`,"gm");blockBody=blockBody.replace(legacyRegex,"");}if(uniqueDependencies.length>0){const managedLines=uniqueDependencies.map(dependency=>` implementation("${dependency}")`).join("\n");blockBody=`${blockBody}\n ${beginMarker}\n${managedLines}\n ${endMarker}\n`;}gradle=`${gradle.slice(0,openBraceIndex+1)}${blockBody}${gradle.slice(blockEnd)}`;fs.writeFileSync(gradlePath,gradle);}function updateProguardKeepRules(proguardPath,selectedClassNames,allKnownPluginClassNames){const uniqueClassNames=asUniqueSorted(selectedClassNames);const knownClassNames=asUniqueSorted(allKnownPluginClassNames);let rules=fs.readFileSync(proguardPath,"utf8");const beginMarker="# CATALYST_PLUGIN_KEEP_START";const endMarker="# CATALYST_PLUGIN_KEEP_END";const markerRegex=/[ \t]*# CATALYST_PLUGIN_KEEP_START[\s\S]*?# CATALYST_PLUGIN_KEEP_END\s*/g;rules=rules.replace(markerRegex,"");for(const className of knownClassNames){const escaped=escapeRegexLiteral(className);const legacyRegex=new RegExp(`^[ \\t]*-keep class ${escaped} \\{ \\*; \\}\\s*\\n?`,"gm");rules=rules.replace(legacyRegex,"");}if(uniqueClassNames.length>0){const keepLines=uniqueClassNames.map(className=>`-keep class ${className} { *; }`).join("\n");rules=`${rules.trimEnd()}\n\n${beginMarker}\n${keepLines}\n${endMarker}\n`;}fs.writeFileSync(proguardPath,rules);}function composeAndroidPlugins({corePluginsRoot,androidProjectPath,pluginConfig,log}){const discovered=discoverInternalPlugins(corePluginsRoot,log);validatePlugins(discovered);const enabled=selectPluginsByConfig(discovered,pluginConfig,log);const selected=selectPluginsForPlatform(enabled,"android",log);validateSelectedPluginSources(selected);const javaRoot=path.join(androidProjectPath,"app","src","main","java");const manifestPath=path.join(androidProjectPath,"app","src","main","AndroidManifest.xml");const gradlePath=path.join(androidProjectPath,"app","build.gradle.kts");const proguardPath=path.join(androidProjectPath,"app","proguard-rules.pro");copyAndroidPluginSources(selected,javaRoot,log);copyPluginAssets(selected,androidProjectPath,log);generatePluginRegistryFiles(selected,javaRoot);updateAndroidManifestPermissions(manifestPath,selected.flatMap(plugin=>plugin.android
|
|
9
|
+
for(const dependency of knownDependencies){const escaped=escapeRegexLiteral(dependency);const legacyRegex=new RegExp(`^[ \\t]*implementation\\("${escaped}"\\)\\s*\\n?`,"gm");blockBody=blockBody.replace(legacyRegex,"");}if(uniqueDependencies.length>0){const managedLines=uniqueDependencies.map(dependency=>` implementation("${dependency}")`).join("\n");blockBody=`${blockBody}\n ${beginMarker}\n${managedLines}\n ${endMarker}\n`;}gradle=`${gradle.slice(0,openBraceIndex+1)}${blockBody}${gradle.slice(blockEnd)}`;fs.writeFileSync(gradlePath,gradle);}function updateProguardKeepRules(proguardPath,selectedClassNames,allKnownPluginClassNames){const uniqueClassNames=asUniqueSorted(selectedClassNames);const knownClassNames=asUniqueSorted(allKnownPluginClassNames);let rules=fs.readFileSync(proguardPath,"utf8");const beginMarker="# CATALYST_PLUGIN_KEEP_START";const endMarker="# CATALYST_PLUGIN_KEEP_END";const markerRegex=/[ \t]*# CATALYST_PLUGIN_KEEP_START[\s\S]*?# CATALYST_PLUGIN_KEEP_END\s*/g;rules=rules.replace(markerRegex,"");for(const className of knownClassNames){const escaped=escapeRegexLiteral(className);const legacyRegex=new RegExp(`^[ \\t]*-keep class ${escaped} \\{ \\*; \\}\\s*\\n?`,"gm");rules=rules.replace(legacyRegex,"");}if(uniqueClassNames.length>0){const keepLines=uniqueClassNames.map(className=>`-keep class ${className} { *; }`).join("\n");rules=`${rules.trimEnd()}\n\n${beginMarker}\n${keepLines}\n${endMarker}\n`;}fs.writeFileSync(proguardPath,rules);}function composeAndroidPlugins({corePluginsRoot,androidProjectPath,pluginConfig,log}){const discovered=discoverInternalPlugins(corePluginsRoot,log);validatePlugins(discovered);const enabled=selectPluginsByConfig(discovered,pluginConfig,log);const selected=selectPluginsForPlatform(enabled,"android",log);validateSelectedPluginSources(selected);const javaRoot=path.join(androidProjectPath,"app","src","main","java");const manifestPath=path.join(androidProjectPath,"app","src","main","AndroidManifest.xml");const gradlePath=path.join(androidProjectPath,"app","build.gradle.kts");const proguardPath=path.join(androidProjectPath,"app","proguard-rules.pro");copyAndroidPluginSources(selected,javaRoot,log);copyPluginAssets(selected,androidProjectPath,log);generatePluginRegistryFiles(selected,javaRoot);updateAndroidManifestPermissions(manifestPath,selected.flatMap(plugin=>plugin.android?.permissions||[]),discovered.flatMap(plugin=>plugin.android?.permissions||[]));updateGradleDependencies(gradlePath,selected.flatMap(plugin=>plugin.android?.dependencies||[]),discovered.flatMap(plugin=>plugin.android?.dependencies||[]));updateProguardKeepRules(proguardPath,selected.map(plugin=>plugin.android?.className).filter(Boolean),discovered.map(plugin=>plugin.android?.className).filter(Boolean));log(`Plugin composition complete (${selected.length} enabled plugin(s))`,"success");return{pluginCount:selected.length,commandCount:selected.reduce((total,plugin)=>total+plugin.commands.length,0)};}module.exports={composeAndroidPlugins};
|