catalyst-core 0.2.0-beta.3 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +290 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +125 -1
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
- package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
- package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
- package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
- package/dist/native/bridge/perf/core/constants.js +103 -20
- package/dist/native/bridge/perf/core/measure.js +179 -30
- package/dist/native/bridge/perf/core/session.js +89 -23
- package/dist/native/bridge/perf/core/store.js +554 -1
- package/dist/native/bridge/perf/core/summary.js +174 -1
- package/dist/native/bridge/perf/core/traceExport.js +240 -1
- package/dist/native/bridge/perf/core/waterfalls.js +146 -1
- package/dist/native/bridge/perf/index.js +359 -38
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +374 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +250 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +433 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +422 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +124 -13
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +4 -4
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +116 -20
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -1,8 +1,157 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
const {
|
|
7
|
+
execSync
|
|
8
|
+
} = require("child_process");
|
|
9
|
+
const PLUGIN_RESOURCE_ROOT = "PluginResources";
|
|
10
|
+
|
|
11
|
+
// ─── Pure helpers ─────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function packageRequirementKey(dependency) {
|
|
14
|
+
return `${dependency.requirement.type}:${dependency.requirement.version}`;
|
|
15
|
+
}
|
|
16
|
+
function mergePackageDependency(map, dependency, sourceLabel) {
|
|
17
|
+
const existing = map.get(dependency.url);
|
|
18
|
+
if (!existing) {
|
|
19
|
+
map.set(dependency.url, {
|
|
20
|
+
url: dependency.url,
|
|
21
|
+
package: dependency.package,
|
|
22
|
+
requirement: dependency.requirement,
|
|
23
|
+
products: [...new Set(dependency.products)]
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (packageRequirementKey(existing) !== packageRequirementKey(dependency)) {
|
|
28
|
+
throw new Error(`iOS package dependency conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.requirement.type}:${existing.requirement.version}' vs '${dependency.requirement.type}:${dependency.requirement.version}'`);
|
|
29
|
+
}
|
|
30
|
+
if (existing.package !== dependency.package) {
|
|
31
|
+
throw new Error(`iOS package identity conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.package}' vs '${dependency.package}'`);
|
|
32
|
+
}
|
|
33
|
+
existing.products = [...new Set([...existing.products, ...dependency.products])].sort();
|
|
34
|
+
}
|
|
35
|
+
function formatSwiftPackageRequirement(dependency) {
|
|
36
|
+
if (dependency.requirement.type === "from") return `from: "${dependency.requirement.version}"`;
|
|
37
|
+
if (dependency.requirement.type === "exact") return `exact: "${dependency.requirement.version}"`;
|
|
38
|
+
throw new Error(`Unsupported iOS package requirement type: ${dependency.requirement.type}`);
|
|
39
|
+
}
|
|
40
|
+
function formatSwiftPackageEntries(dependencies) {
|
|
41
|
+
return dependencies.map(d => ` .package(url: "${d.url}", ${formatSwiftPackageRequirement(d)})`);
|
|
42
|
+
}
|
|
43
|
+
function formatSwiftProductEntries(dependencies) {
|
|
44
|
+
return dependencies.flatMap(d => d.products.map(p => ` .product(name: "${p}", package: "${d.package}")`));
|
|
45
|
+
}
|
|
46
|
+
function formatPbxprojPath(value) {
|
|
47
|
+
return /[^A-Za-z0-9_./-]/.test(value) ? `"${value}"` : value;
|
|
48
|
+
}
|
|
49
|
+
function detectPbxprojFileType(filePath) {
|
|
50
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
51
|
+
const fileTypes = {
|
|
52
|
+
".json": "text.json",
|
|
53
|
+
".plist": "text.plist.xml",
|
|
54
|
+
".txt": "text",
|
|
55
|
+
".html": "text.html",
|
|
56
|
+
".js": "sourcecode.javascript",
|
|
57
|
+
".css": "text.css",
|
|
58
|
+
".png": "image.png",
|
|
59
|
+
".jpg": "image.jpeg",
|
|
60
|
+
".jpeg": "image.jpeg",
|
|
61
|
+
".gif": "image.gif",
|
|
62
|
+
".svg": "image.svg",
|
|
63
|
+
".mp3": "audio.mp3",
|
|
64
|
+
".wav": "audio.wav",
|
|
65
|
+
".m4a": "audio.m4a",
|
|
66
|
+
".caf": "audio.caf",
|
|
67
|
+
".pdf": "image.pdf"
|
|
68
|
+
};
|
|
69
|
+
return fileTypes[extension] || "file";
|
|
70
|
+
}
|
|
71
|
+
function generateProjectObjectId(identifier, suffix = "") {
|
|
72
|
+
return crypto.createHash("md5").update(`${identifier}:${suffix}`).digest("hex").substring(0, 24).toUpperCase();
|
|
73
|
+
}
|
|
74
|
+
function replaceRequired(content, pattern, replacement, errorMessage) {
|
|
75
|
+
if (!pattern.test(content)) throw new Error(errorMessage);
|
|
76
|
+
return content.replace(pattern, replacement);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Module factory ───────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
module.exports = function createPluginsPhase(ctx) {
|
|
82
|
+
const {
|
|
83
|
+
WEBVIEW_CONFIG,
|
|
84
|
+
PROJECT_DIR,
|
|
85
|
+
PROJECT_NAME,
|
|
86
|
+
SCHEME_NAME,
|
|
87
|
+
isGoogleSignInEnabled,
|
|
88
|
+
progress,
|
|
89
|
+
getXcodeProjectFilePath
|
|
90
|
+
} = ctx;
|
|
91
|
+
async function generatePackageSwift(pluginDependencies = []) {
|
|
92
|
+
try {
|
|
93
|
+
const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
|
|
94
|
+
const baseCoreDependencies = [{
|
|
95
|
+
url: "https://github.com/kylef/JSONSchema.swift",
|
|
96
|
+
package: "JSONSchema.swift",
|
|
97
|
+
requirement: {
|
|
98
|
+
type: "from",
|
|
99
|
+
version: "0.6.0"
|
|
100
|
+
},
|
|
101
|
+
products: ["JSONSchema"]
|
|
102
|
+
}, {
|
|
103
|
+
url: "https://github.com/google/GoogleSignIn-iOS",
|
|
104
|
+
package: "GoogleSignIn-iOS",
|
|
105
|
+
requirement: {
|
|
106
|
+
type: "from",
|
|
107
|
+
version: "7.0.0"
|
|
108
|
+
},
|
|
109
|
+
products: ["GoogleSignIn"]
|
|
110
|
+
}];
|
|
111
|
+
const notificationsDependencies = isNotificationsEnabled ? [{
|
|
112
|
+
url: "https://github.com/firebase/firebase-ios-sdk",
|
|
113
|
+
package: "firebase-ios-sdk",
|
|
114
|
+
requirement: {
|
|
115
|
+
type: "from",
|
|
116
|
+
version: "12.3.0"
|
|
117
|
+
},
|
|
118
|
+
products: ["FirebaseCore", "FirebaseMessaging"]
|
|
119
|
+
}] : [];
|
|
120
|
+
const coreDependencyMap = new Map();
|
|
121
|
+
baseCoreDependencies.forEach(d => mergePackageDependency(coreDependencyMap, d, "base core"));
|
|
122
|
+
pluginDependencies.forEach(d => mergePackageDependency(coreDependencyMap, d, "plugin manifests"));
|
|
123
|
+
const coreDependencies = [...coreDependencyMap.values()].sort((l, r) => l.url.localeCompare(r.url));
|
|
124
|
+
const packageDependencyMap = new Map();
|
|
125
|
+
coreDependencies.forEach(d => mergePackageDependency(packageDependencyMap, d, "core target"));
|
|
126
|
+
notificationsDependencies.forEach(d => mergePackageDependency(packageDependencyMap, d, "notifications target"));
|
|
127
|
+
const packageDependencies = [...packageDependencyMap.values()].sort((l, r) => l.url.localeCompare(r.url));
|
|
128
|
+
progress.log(`🔧 Generating Package.swift (notifications: ${isNotificationsEnabled})`, "info");
|
|
129
|
+
const configHash = crypto.createHash("md5").update(JSON.stringify({
|
|
130
|
+
notifications: isNotificationsEnabled,
|
|
131
|
+
googleSignIn: isGoogleSignInEnabled,
|
|
132
|
+
pluginDependencies: coreDependencies
|
|
133
|
+
})).digest("hex");
|
|
134
|
+
const hashFilePath = path.join(PROJECT_DIR, ".package-config-hash"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
135
|
+
const targetPath = path.join(PROJECT_DIR, "Package.swift"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
136
|
+
let shouldUpdate = true;
|
|
137
|
+
try {
|
|
138
|
+
const previousHash = fs.readFileSync(hashFilePath, "utf8");
|
|
139
|
+
if (previousHash === configHash) {
|
|
140
|
+
shouldUpdate = false;
|
|
141
|
+
progress.log("Package.swift already up to date", "info");
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
/* hash file absent — proceed with update */
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
fs.accessSync(targetPath);
|
|
148
|
+
} catch {
|
|
149
|
+
shouldUpdate = true;
|
|
150
|
+
progress.log("Package.swift missing, will generate it now", "info");
|
|
151
|
+
}
|
|
152
|
+
if (shouldUpdate) {
|
|
153
|
+
progress.log("Generating Package.swift dynamically", "info");
|
|
154
|
+
let packageContent = `// swift-tools-version: 5.9
|
|
6
155
|
// Auto-generated Package.swift - DO NOT EDIT MANUALLY
|
|
7
156
|
// Generated based on config: notifications.enabled = ${isNotificationsEnabled}
|
|
8
157
|
import PackageDescription
|
|
@@ -11,7 +160,11 @@ let package = Package(
|
|
|
11
160
|
name: "iosnativeWebView",
|
|
12
161
|
platforms: [.iOS(.v17)],
|
|
13
162
|
products: [
|
|
14
|
-
.library(name: "CatalystCore", targets: ["CatalystCore"])`;
|
|
163
|
+
.library(name: "CatalystCore", targets: ["CatalystCore"])`;
|
|
164
|
+
if (isNotificationsEnabled) {
|
|
165
|
+
packageContent += `,\n .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;
|
|
166
|
+
}
|
|
167
|
+
packageContent += `
|
|
15
168
|
],
|
|
16
169
|
dependencies: [
|
|
17
170
|
${formatSwiftPackageEntries(packageDependencies).join(",\n")}
|
|
@@ -25,7 +178,9 @@ ${formatSwiftPackageEntries(packageDependencies).join(",\n")}
|
|
|
25
178
|
${formatSwiftProductEntries(coreDependencies).join(",\n")}
|
|
26
179
|
],
|
|
27
180
|
path: "Sources/Core"
|
|
28
|
-
)`;
|
|
181
|
+
)`;
|
|
182
|
+
if (isNotificationsEnabled) {
|
|
183
|
+
packageContent += `,
|
|
29
184
|
// Notifications functionality (optional, includes Firebase)
|
|
30
185
|
.target(
|
|
31
186
|
name: "CatalystNotifications",
|
|
@@ -34,12 +189,184 @@ ${formatSwiftProductEntries(coreDependencies).join(",\n")}
|
|
|
34
189
|
${formatSwiftProductEntries(notificationsDependencies).join(",\n")}
|
|
35
190
|
],
|
|
36
191
|
path: "Sources/CatalystNotifications"
|
|
37
|
-
)`;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
progress.log(
|
|
44
|
-
|
|
45
|
-
|
|
192
|
+
)`;
|
|
193
|
+
}
|
|
194
|
+
packageContent += `\n ]\n)\n`;
|
|
195
|
+
fs.writeFileSync(targetPath, packageContent, "utf8");
|
|
196
|
+
progress.log(`Generated Package.swift with ${isNotificationsEnabled ? "notifications enabled" : "notifications disabled"}`, "success");
|
|
197
|
+
fs.writeFileSync(hashFilePath, configHash);
|
|
198
|
+
progress.log("Resolving package dependencies...", "info");
|
|
199
|
+
try {
|
|
200
|
+
execSync(`cd "${PROJECT_DIR}" && rm -rf .build`, {
|
|
201
|
+
stdio: "ignore"
|
|
202
|
+
}); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
203
|
+
try {
|
|
204
|
+
fs.rmSync(path.join(PROJECT_DIR, "Package.resolved"), {
|
|
205
|
+
force: true
|
|
206
|
+
}); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - PROJECT_DIR is the fixed generated iOS project directory.
|
|
207
|
+
} catch {
|
|
208
|
+
progress.log("");
|
|
209
|
+
}
|
|
210
|
+
execSync(`cd "${PROJECT_DIR}" && rm -rf .swiftpm`, {
|
|
211
|
+
stdio: "ignore"
|
|
212
|
+
}); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
213
|
+
const projectPath = path.join(PROJECT_DIR, `${PROJECT_NAME}.xcodeproj`); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
214
|
+
execSync(`cd "${PROJECT_DIR}" && xcodebuild -resolvePackageDependencies -project "${projectPath}" -scheme "${SCHEME_NAME}"`,
|
|
215
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - PROJECT_DIR, projectPath, and SCHEME_NAME are generated from trusted iOS project config.
|
|
216
|
+
{
|
|
217
|
+
stdio: "inherit"
|
|
218
|
+
});
|
|
219
|
+
progress.log("Package dependencies resolved successfully", "success");
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (isNotificationsEnabled) {
|
|
222
|
+
progress.log(`❌ CRITICAL: Package resolution failed. Firebase dependencies required for notifications could not be resolved.`, "error");
|
|
223
|
+
throw new Error(`Package resolution failed: ${error.message}. This is required when notifications are enabled.`);
|
|
224
|
+
} else {
|
|
225
|
+
progress.log(`Warning: Package resolution may have failed: ${error.message}`, "warning");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
progress.log("✅ Package.swift ready", "success");
|
|
230
|
+
} catch (error) {
|
|
231
|
+
progress.log(`❌ Failed to generate Package.swift: ${error.message}`, "error");
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function updateXcodeProjectPackageDependencies() {
|
|
236
|
+
try {
|
|
237
|
+
const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
|
|
238
|
+
const projectFilePath = path.join(PROJECT_DIR, `${PROJECT_NAME}.xcodeproj`, "project.pbxproj"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
239
|
+
|
|
240
|
+
progress.log(`🔧 Updating Xcode package dependencies (notifications: ${isNotificationsEnabled})`, "info");
|
|
241
|
+
let projectContent;
|
|
242
|
+
try {
|
|
243
|
+
projectContent = fs.readFileSync(projectFilePath, "utf8");
|
|
244
|
+
} catch {
|
|
245
|
+
throw new Error(`Xcode project file not found at: ${projectFilePath}`);
|
|
246
|
+
}
|
|
247
|
+
const NOTIF_BUILD_FILE_ID = "C99974352E97D56900C25611";
|
|
248
|
+
const NOTIF_PRODUCT_ID = "C99974362E97D56900C25611";
|
|
249
|
+
const hasNotifications = projectContent.includes("/* CatalystNotifications */");
|
|
250
|
+
if (isNotificationsEnabled && !hasNotifications) {
|
|
251
|
+
progress.log("Adding CatalystNotifications to Xcode project", "info");
|
|
252
|
+
projectContent = projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/ = {isa = PBXBuildFile; productRef = C99974332E97D56900C25611 \/\* CatalystCore \*\/; };)/, `$1\n\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = ${NOTIF_PRODUCT_ID} /* CatalystNotifications */; };`);
|
|
253
|
+
projectContent = projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/,)/, `$1\n\t\t\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */,`);
|
|
254
|
+
projectContent = projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/, `$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);
|
|
255
|
+
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`);
|
|
256
|
+
fs.writeFileSync(projectFilePath, projectContent, "utf8");
|
|
257
|
+
progress.log("✅ CatalystNotifications added to Xcode project", "success");
|
|
258
|
+
} else if (!isNotificationsEnabled && hasNotifications) {
|
|
259
|
+
progress.log("Removing CatalystNotifications from Xcode project", "info");
|
|
260
|
+
projectContent = projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/ = {isa = PBXBuildFile; productRef = [A-F0-9]+ \/\* CatalystNotifications \*\/; };\n/g, "");
|
|
261
|
+
projectContent = projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/,\n/g, "");
|
|
262
|
+
projectContent = projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/,\n/g, "");
|
|
263
|
+
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, "");
|
|
264
|
+
fs.writeFileSync(projectFilePath, projectContent, "utf8");
|
|
265
|
+
progress.log("✅ CatalystNotifications removed from Xcode project", "success");
|
|
266
|
+
} else {
|
|
267
|
+
progress.log("Package dependencies already correct", "info");
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
progress.log(`❌ Failed to update package dependencies: ${error.message}`, "error");
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function removePluginResourcesFromXcodeProject() {
|
|
275
|
+
try {
|
|
276
|
+
const projectFilePath = getXcodeProjectFilePath();
|
|
277
|
+
let projectContent;
|
|
278
|
+
try {
|
|
279
|
+
projectContent = fs.readFileSync(projectFilePath, "utf8");
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (err.code === "ENOENT") return;
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
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];
|
|
285
|
+
let modified = false;
|
|
286
|
+
for (const pattern of patterns) {
|
|
287
|
+
const nextContent = projectContent.replace(pattern, "");
|
|
288
|
+
if (nextContent !== projectContent) {
|
|
289
|
+
projectContent = nextContent;
|
|
290
|
+
modified = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (modified) {
|
|
294
|
+
fs.writeFileSync(projectFilePath, projectContent, "utf8");
|
|
295
|
+
progress.log("Removed managed plugin resources from Xcode project", "info");
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
throw new Error(`Could not remove plugin resources from Xcode project: ${error.message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function addPluginResourcesToXcodeProject(resources) {
|
|
302
|
+
try {
|
|
303
|
+
if (resources.length === 0) return;
|
|
304
|
+
const projectFilePath = getXcodeProjectFilePath();
|
|
305
|
+
let projectContent;
|
|
306
|
+
try {
|
|
307
|
+
projectContent = fs.readFileSync(projectFilePath, "utf8");
|
|
308
|
+
} catch (err) {
|
|
309
|
+
if (err.code === "ENOENT") throw new Error(`Xcode project file not found at: ${projectFilePath}`);
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
for (const resource of resources) {
|
|
313
|
+
const label = resource.bundleRelativePath;
|
|
314
|
+
const fileRefId = generateProjectObjectId(label, "_ref");
|
|
315
|
+
const buildFileId = generateProjectObjectId(label, "_build");
|
|
316
|
+
const pbxprojPath = formatPbxprojPath(label);
|
|
317
|
+
const fileType = detectPbxprojFileType(label);
|
|
318
|
+
const fileRefEntry = `\t\t${fileRefId} /* ${label} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; path = ${pbxprojPath}; sourceTree = "<group>"; };`;
|
|
319
|
+
projectContent = replaceRequired(projectContent, /(\/\* End PBXFileReference section \*\/)/, `${fileRefEntry}\n$1`, "PBXFileReference section not found while registering plugin resources");
|
|
320
|
+
const buildFileEntry = `\t\t${buildFileId} /* ${label} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRefId} /* ${label} */; };`;
|
|
321
|
+
projectContent = replaceRequired(projectContent, /(\/\* End PBXBuildFile section \*\/)/, `${buildFileEntry}\n$1`, "PBXBuildFile section not found while registering plugin resources");
|
|
322
|
+
projectContent = replaceRequired(projectContent, /(\/\* iosnativeWebView \*\/ = \{[^}]*children = \([^)]*)/, `$1\n\t\t\t\t${fileRefId} /* ${label} */,`, "iosnativeWebView group not found while registering plugin resources");
|
|
323
|
+
projectContent = replaceRequired(projectContent, /(\/\* Resources \*\/ = \{[^}]*files = \([^)]*)/, `$1\n\t\t\t\t${buildFileId} /* ${label} in Resources */,`, "Resources build phase not found while registering plugin resources");
|
|
324
|
+
}
|
|
325
|
+
fs.writeFileSync(projectFilePath, projectContent, "utf8");
|
|
326
|
+
progress.log(`Registered ${resources.length} managed plugin resource(s) in Xcode project`, "success");
|
|
327
|
+
} catch (error) {
|
|
328
|
+
throw new Error(`Could not add plugin resources to Xcode project: ${error.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function syncPluginResources(pluginComposition = {}) {
|
|
332
|
+
try {
|
|
333
|
+
const resources = pluginComposition.resources || [];
|
|
334
|
+
const pluginResourceDir = path.join(PROJECT_DIR, PROJECT_NAME, PLUGIN_RESOURCE_ROOT); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
335
|
+
fs.rmSync(pluginResourceDir, {
|
|
336
|
+
recursive: true,
|
|
337
|
+
force: true
|
|
338
|
+
});
|
|
339
|
+
await removePluginResourcesFromXcodeProject();
|
|
340
|
+
if (resources.length === 0) {
|
|
341
|
+
progress.log("No managed plugin resources to sync", "info");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
for (const resource of resources) {
|
|
345
|
+
const normalizedBundleRelativePath = path.posix.normalize(resource.bundleRelativePath || "");
|
|
346
|
+
const expectedPrefix = `${PLUGIN_RESOURCE_ROOT}/`;
|
|
347
|
+
if (!normalizedBundleRelativePath.startsWith(expectedPrefix) || normalizedBundleRelativePath.includes("../") || path.posix.isAbsolute(normalizedBundleRelativePath)) {
|
|
348
|
+
throw new Error(`Invalid managed plugin resource path: ${resource.bundleRelativePath}`);
|
|
349
|
+
}
|
|
350
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The normalized path is validated above and containment is enforced immediately below.
|
|
351
|
+
const targetPath = path.resolve(PROJECT_DIR, PROJECT_NAME, normalizedBundleRelativePath);
|
|
352
|
+
if (targetPath !== pluginResourceDir && !targetPath.startsWith(`${pluginResourceDir}${path.sep}`)) {
|
|
353
|
+
throw new Error(`Managed plugin resource escaped bundle directory: ${resource.bundleRelativePath}`);
|
|
354
|
+
}
|
|
355
|
+
fs.mkdirSync(path.dirname(targetPath), {
|
|
356
|
+
recursive: true
|
|
357
|
+
});
|
|
358
|
+
fs.copyFileSync(resource.sourcePath, targetPath);
|
|
359
|
+
}
|
|
360
|
+
await addPluginResourcesToXcodeProject(resources);
|
|
361
|
+
progress.log(`Synced ${resources.length} managed plugin resource(s)`, "success");
|
|
362
|
+
} catch (error) {
|
|
363
|
+
progress.log(`❌ Failed to sync plugin resources: ${error.message}`, "error");
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
generatePackageSwift,
|
|
369
|
+
updateXcodeProjectPackageDependencies,
|
|
370
|
+
syncPluginResources
|
|
371
|
+
};
|
|
372
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package io.yourname.androidproject.plugins.internal.deviceinfo
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import io.yourname.androidproject.plugins.CatalystPlugin
|
|
5
|
+
import io.yourname.androidproject.plugins.PluginBridgeContext
|
|
6
|
+
import io.yourname.androidproject.utils.DeviceInfoUtils
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
|
|
9
|
+
class DeviceInfoPlugin : CatalystPlugin {
|
|
10
|
+
companion object {
|
|
11
|
+
private const val TAG = "DeviceInfoPlugin"
|
|
12
|
+
private const val COMMAND_GET_DEVICE_INFO = "getDeviceInfo"
|
|
13
|
+
private const val CALLBACK_SUCCESS = "onSuccess"
|
|
14
|
+
private const val CALLBACK_ERROR = "onError"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun handle(command: String, data: JSONObject?, bridge: PluginBridgeContext) {
|
|
18
|
+
if (command != COMMAND_GET_DEVICE_INFO) {
|
|
19
|
+
bridge.callback(
|
|
20
|
+
CALLBACK_ERROR,
|
|
21
|
+
JSONObject().apply {
|
|
22
|
+
put("message", "Unsupported command: $command")
|
|
23
|
+
put("code", "UNSUPPORTED_COMMAND")
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
val deviceInfo = DeviceInfoUtils.getDeviceInfo(bridge.context, bridge.properties)
|
|
31
|
+
bridge.callback(CALLBACK_SUCCESS, deviceInfo)
|
|
32
|
+
} catch (error: Exception) {
|
|
33
|
+
Log.e(TAG, "Failed to resolve device info", error)
|
|
34
|
+
bridge.callback(
|
|
35
|
+
CALLBACK_ERROR,
|
|
36
|
+
JSONObject().apply {
|
|
37
|
+
put("message", error.message ?: "Failed to get device info")
|
|
38
|
+
put("code", "DEVICE_INFO_ERROR")
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
final class DeviceInfoPlugin: CatalystPlugin {
|
|
4
|
+
private let commandGetDeviceInfo = "getDeviceInfo"
|
|
5
|
+
private let successCallback = "onSuccess"
|
|
6
|
+
private let errorCallback = "onError"
|
|
7
|
+
|
|
8
|
+
func handle(command: String, data: Any?, bridge: PluginBridgeContext) {
|
|
9
|
+
guard command == commandGetDeviceInfo else {
|
|
10
|
+
bridge.callback(eventName: errorCallback, data: [
|
|
11
|
+
"message": "Unsupported command: \(command)",
|
|
12
|
+
"code": "UNSUPPORTED_COMMAND",
|
|
13
|
+
])
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let deviceInfo = DeviceInfoUtils.getDeviceInfo()
|
|
18
|
+
if let error = deviceInfo["error"] as? String {
|
|
19
|
+
bridge.callback(eventName: errorCallback, data: [
|
|
20
|
+
"message": error,
|
|
21
|
+
"code": "DEVICE_INFO_ERROR",
|
|
22
|
+
])
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bridge.callback(eventName: successCallback, data: deviceInfo)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "io.catalyst.device_info",
|
|
3
|
+
"configKey": "deviceInfo",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"displayName": "Device Info",
|
|
6
|
+
"description": "Provides device metadata, screen metrics, app info, and platform-specific diagnostics.",
|
|
7
|
+
"category": "device",
|
|
8
|
+
"platforms": ["android", "ios"],
|
|
9
|
+
"commands": ["getDeviceInfo"],
|
|
10
|
+
"android": {
|
|
11
|
+
"className": "io.yourname.androidproject.plugins.internal.deviceinfo.DeviceInfoPlugin",
|
|
12
|
+
"permissions": [],
|
|
13
|
+
"dependencies": []
|
|
14
|
+
},
|
|
15
|
+
"ios": {
|
|
16
|
+
"className": "DeviceInfoPlugin",
|
|
17
|
+
"dependencies": []
|
|
18
|
+
}
|
|
19
|
+
}
|