catalyst-core 0.2.0-beta.3 → 0.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/globals.d.ts +7 -0
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidProject/app/build.gradle.kts +45 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
- package/dist/native/androidProject/settings.gradle.kts +10 -0
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +132 -1
- package/dist/native/bridge/hooks/useAI.js +94 -0
- 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 +406 -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 +296 -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 +450 -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 +498 -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 +176 -17
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +40 -8
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +161 -37
- 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
|
@@ -7,15 +7,21 @@ import io.ktor.http.*
|
|
|
7
7
|
import io.ktor.server.application.*
|
|
8
8
|
import io.ktor.server.engine.*
|
|
9
9
|
import io.ktor.server.netty.*
|
|
10
|
+
import io.ktor.server.request.*
|
|
10
11
|
import io.ktor.server.response.*
|
|
11
12
|
import io.ktor.server.routing.*
|
|
13
|
+
import io.ktor.http.ContentType
|
|
14
|
+
import io.ktor.server.response.respondTextWriter
|
|
15
|
+
import io.ktor.server.sse.*
|
|
12
16
|
import kotlinx.coroutines.*
|
|
17
|
+
import kotlinx.coroutines.flow.Flow
|
|
18
|
+
import org.json.JSONObject
|
|
13
19
|
import java.io.File
|
|
14
20
|
import java.io.IOException
|
|
15
21
|
import java.net.InetSocketAddress
|
|
16
22
|
import java.nio.channels.ServerSocketChannel
|
|
17
23
|
import java.security.SecureRandom
|
|
18
|
-
import java.util
|
|
24
|
+
import java.util.UUID
|
|
19
25
|
import java.util.concurrent.ConcurrentHashMap
|
|
20
26
|
|
|
21
27
|
/**
|
|
@@ -36,6 +42,7 @@ object FrameworkServerUtils {
|
|
|
36
42
|
private const val FRAMEWORK_PORT_RANGE_START = 3000
|
|
37
43
|
private const val FRAMEWORK_PORT_RANGE_END = 3099
|
|
38
44
|
private const val SESSION_TIMEOUT_MS = 30 * 60 * 1000L // 30 minutes
|
|
45
|
+
private const val AI_GENERATE_TIMEOUT_MS = 120 * 1000L // 2 minutes
|
|
39
46
|
|
|
40
47
|
// Server state
|
|
41
48
|
private var server: EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>? = null
|
|
@@ -49,7 +56,14 @@ object FrameworkServerUtils {
|
|
|
49
56
|
// File management
|
|
50
57
|
private val servedFiles = ConcurrentHashMap<String, ServedFile>()
|
|
51
58
|
private var cacheDirectory: File? = null
|
|
52
|
-
|
|
59
|
+
|
|
60
|
+
// Native AI — supplier set by NativeBridge before POST /ai/stream is called
|
|
61
|
+
// Returns (conversationId, tokenFlow) so the route can emit conversationId as an SSE event
|
|
62
|
+
private var nativeAiSupplier: (suspend (prompt: String, genConfig: JSONObject, conversationId: String?) -> Pair<String, Flow<String>>)? = null
|
|
63
|
+
|
|
64
|
+
// System prompt injected into every native AI request (built from attachmentComponents at initAI time)
|
|
65
|
+
private var nativeSystemPrompt: String = ""
|
|
66
|
+
|
|
53
67
|
// Coroutine scope for server operations
|
|
54
68
|
private val serverScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
55
69
|
|
|
@@ -60,7 +74,7 @@ object FrameworkServerUtils {
|
|
|
60
74
|
val fileId: String,
|
|
61
75
|
val createdAt: Long = System.currentTimeMillis()
|
|
62
76
|
)
|
|
63
|
-
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
79
|
* Initialize and start the framework server
|
|
66
80
|
* @param context Android context for file operations
|
|
@@ -223,7 +237,21 @@ object FrameworkServerUtils {
|
|
|
223
237
|
* Get current session ID
|
|
224
238
|
*/
|
|
225
239
|
fun getSessionId(): String = sessionId
|
|
226
|
-
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Register the native AI supplier. Called by NativeBridge once LiteRT-LM is ready.
|
|
243
|
+
* The supplier receives the prompt, gen-config, and optional incomingConversationId,
|
|
244
|
+
* and returns (activeConversationId, tokenFlow) so stateful sessions can be chained.
|
|
245
|
+
*/
|
|
246
|
+
fun setNativeAiSupplier(supplier: suspend (prompt: String, genConfig: JSONObject, conversationId: String?) -> Pair<String, Flow<String>>) {
|
|
247
|
+
nativeAiSupplier = supplier
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fun setNativeSystemPrompt(systemPrompt: String) {
|
|
251
|
+
nativeSystemPrompt = systemPrompt
|
|
252
|
+
Log.d(TAG, "System prompt set (${systemPrompt.length} chars)")
|
|
253
|
+
}
|
|
254
|
+
|
|
227
255
|
// Private implementation methods
|
|
228
256
|
|
|
229
257
|
private fun initializeCacheDirectory(context: Context) {
|
|
@@ -291,7 +319,145 @@ object FrameworkServerUtils {
|
|
|
291
319
|
|
|
292
320
|
private fun startKtorServer() {
|
|
293
321
|
server = embeddedServer(Netty, port = serverPort) {
|
|
322
|
+
install(SSE)
|
|
294
323
|
routing {
|
|
324
|
+
// Native AI streaming endpoint — POST body: { "prompt": "...", "genConfig": {...} }
|
|
325
|
+
// Writes SSE manually over chunked response (Ktor SSE plugin only supports GET).
|
|
326
|
+
post("/framework-$sessionId/ai/stream") {
|
|
327
|
+
call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
|
|
328
|
+
call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
|
|
329
|
+
call.response.header(HttpHeaders.ContentType, "text/event-stream")
|
|
330
|
+
call.response.header(HttpHeaders.CacheControl, "no-cache")
|
|
331
|
+
call.response.header("X-Accel-Buffering", "no")
|
|
332
|
+
|
|
333
|
+
val body = try {
|
|
334
|
+
JSONObject(call.receiveText())
|
|
335
|
+
} catch (e: Exception) {
|
|
336
|
+
JSONObject()
|
|
337
|
+
}
|
|
338
|
+
val userPrompt = body.optString("prompt", "")
|
|
339
|
+
val genConfig = body.optJSONObject("genConfig") ?: JSONObject()
|
|
340
|
+
val incomingConvId = body.optString("conversationId", "").takeIf { it.isNotBlank() }
|
|
341
|
+
if (nativeSystemPrompt.isNotBlank()) Log.d(TAG, "Injecting system prompt (${nativeSystemPrompt.length} chars)")
|
|
342
|
+
val prompt = if (nativeSystemPrompt.isNotBlank()) "$nativeSystemPrompt\n\n$userPrompt" else userPrompt
|
|
343
|
+
|
|
344
|
+
call.respondTextWriter(contentType = ContentType.Text.EventStream) {
|
|
345
|
+
val supplier = nativeAiSupplier
|
|
346
|
+
if (supplier == null) {
|
|
347
|
+
write("data: {\"error\":\"Native AI not initialised — call initAI() first\"}\n\n")
|
|
348
|
+
flush()
|
|
349
|
+
} else if (prompt.isBlank()) {
|
|
350
|
+
write("data: {\"error\":\"prompt is empty\"}\n\n")
|
|
351
|
+
flush()
|
|
352
|
+
} else {
|
|
353
|
+
try {
|
|
354
|
+
val (activeConvId, tokenFlow) = supplier(prompt, genConfig, incomingConvId)
|
|
355
|
+
write("data: {\"conversationId\":${JSONObject.quote(activeConvId)}}\n\n")
|
|
356
|
+
flush()
|
|
357
|
+
tokenFlow.collect { token ->
|
|
358
|
+
write("data: {\"token\":${JSONObject.quote(token)}}\n\n")
|
|
359
|
+
flush()
|
|
360
|
+
}
|
|
361
|
+
write("data: {\"done\":true}\n\n")
|
|
362
|
+
flush()
|
|
363
|
+
} catch (e: java.io.IOException) {
|
|
364
|
+
// Client disconnected — channel closed, nothing to write
|
|
365
|
+
Log.d(TAG, "Native AI stream: client disconnected (${e.message})")
|
|
366
|
+
} catch (e: Exception) {
|
|
367
|
+
Log.e(TAG, "Native AI stream error", e)
|
|
368
|
+
try {
|
|
369
|
+
write("data: {\"error\":${JSONObject.quote(e.message ?: "stream error")}}\n\n")
|
|
370
|
+
flush()
|
|
371
|
+
} catch (_: java.io.IOException) {
|
|
372
|
+
// Channel already closed, ignore
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// OPTIONS preflight for ai/stream
|
|
380
|
+
options("/framework-$sessionId/ai/stream") {
|
|
381
|
+
call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
|
|
382
|
+
call.response.header(HttpHeaders.AccessControlAllowMethods, "POST, OPTIONS")
|
|
383
|
+
call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
|
|
384
|
+
call.response.header(HttpHeaders.AccessControlMaxAge, "86400")
|
|
385
|
+
call.respond(HttpStatusCode.OK)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Native AI non-streaming endpoint — POST body: { "prompt": "...", "genConfig": {...} }
|
|
389
|
+
// Drains nativeAiSupplier's tokenFlow fully, then returns a single JSON response.
|
|
390
|
+
post("/framework-$sessionId/ai/generate") {
|
|
391
|
+
call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
|
|
392
|
+
call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
|
|
393
|
+
|
|
394
|
+
val body = try {
|
|
395
|
+
JSONObject(call.receiveText())
|
|
396
|
+
} catch (e: Exception) {
|
|
397
|
+
JSONObject()
|
|
398
|
+
}
|
|
399
|
+
val userPrompt = body.optString("prompt", "")
|
|
400
|
+
val genConfig = body.optJSONObject("genConfig") ?: JSONObject()
|
|
401
|
+
val incomingConvId = body.optString("conversationId", "").takeIf { it.isNotBlank() }
|
|
402
|
+
if (nativeSystemPrompt.isNotBlank()) Log.d(TAG, "Injecting system prompt (${nativeSystemPrompt.length} chars)")
|
|
403
|
+
val prompt = if (nativeSystemPrompt.isNotBlank()) "$nativeSystemPrompt\n\n$userPrompt" else userPrompt
|
|
404
|
+
|
|
405
|
+
val supplier = nativeAiSupplier
|
|
406
|
+
if (supplier == null) {
|
|
407
|
+
call.respondText(
|
|
408
|
+
JSONObject().put("error", "Native AI not initialised — call initAI() first").toString(),
|
|
409
|
+
ContentType.Application.Json,
|
|
410
|
+
HttpStatusCode.ServiceUnavailable
|
|
411
|
+
)
|
|
412
|
+
return@post
|
|
413
|
+
}
|
|
414
|
+
if (prompt.isBlank()) {
|
|
415
|
+
call.respondText(
|
|
416
|
+
JSONObject().put("error", "prompt is empty").toString(),
|
|
417
|
+
ContentType.Application.Json,
|
|
418
|
+
HttpStatusCode.BadRequest
|
|
419
|
+
)
|
|
420
|
+
return@post
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
val (activeConvId, tokenFlow) = supplier(prompt, genConfig, incomingConvId)
|
|
425
|
+
val output = StringBuilder()
|
|
426
|
+
var tokenCount = 0
|
|
427
|
+
withTimeout(AI_GENERATE_TIMEOUT_MS) {
|
|
428
|
+
tokenFlow.collect { token -> output.append(token); tokenCount++ }
|
|
429
|
+
}
|
|
430
|
+
val responseJson = JSONObject()
|
|
431
|
+
.put("output", output.toString())
|
|
432
|
+
.put("conversationId", activeConvId)
|
|
433
|
+
.put("tokenCount", tokenCount)
|
|
434
|
+
call.respondText(responseJson.toString(), ContentType.Application.Json, HttpStatusCode.OK)
|
|
435
|
+
} catch (e: TimeoutCancellationException) {
|
|
436
|
+
Log.e(TAG, "Native AI generate timed out after ${AI_GENERATE_TIMEOUT_MS}ms", e)
|
|
437
|
+
call.respondText(
|
|
438
|
+
JSONObject().put("error", "generate timed out after ${AI_GENERATE_TIMEOUT_MS / 1000}s").toString(),
|
|
439
|
+
ContentType.Application.Json,
|
|
440
|
+
HttpStatusCode.GatewayTimeout
|
|
441
|
+
)
|
|
442
|
+
} catch (e: Exception) {
|
|
443
|
+
Log.e(TAG, "Native AI generate error", e)
|
|
444
|
+
call.respondText(
|
|
445
|
+
JSONObject().put("error", (e.message ?: "generate error")).toString(),
|
|
446
|
+
ContentType.Application.Json,
|
|
447
|
+
HttpStatusCode.InternalServerError
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// OPTIONS preflight for ai/generate
|
|
453
|
+
options("/framework-$sessionId/ai/generate") {
|
|
454
|
+
call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
|
|
455
|
+
call.response.header(HttpHeaders.AccessControlAllowMethods, "POST, OPTIONS")
|
|
456
|
+
call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
|
|
457
|
+
call.response.header(HttpHeaders.AccessControlMaxAge, "86400")
|
|
458
|
+
call.respond(HttpStatusCode.OK)
|
|
459
|
+
}
|
|
460
|
+
|
|
295
461
|
get("/framework-$sessionId/file-{fileId}") {
|
|
296
462
|
val fileId = call.parameters["fileId"]
|
|
297
463
|
|
|
@@ -385,22 +551,16 @@ object FrameworkServerUtils {
|
|
|
385
551
|
val expiredFiles = servedFiles.filter { (_, servedFile) ->
|
|
386
552
|
currentTime - servedFile.createdAt > SESSION_TIMEOUT_MS
|
|
387
553
|
}
|
|
388
|
-
|
|
389
554
|
if (expiredFiles.isNotEmpty()) {
|
|
390
555
|
Log.d(TAG, "Cleaning up ${expiredFiles.size} expired files")
|
|
391
|
-
|
|
392
|
-
expiredFiles.forEach { (fileId, _) ->
|
|
393
|
-
removeServedFile(fileId)
|
|
394
|
-
}
|
|
556
|
+
expiredFiles.forEach { (fileId, _) -> removeServedFile(fileId) }
|
|
395
557
|
}
|
|
558
|
+
|
|
396
559
|
}
|
|
397
560
|
|
|
398
561
|
private fun cleanupAllFiles() {
|
|
399
562
|
Log.d(TAG, "Cleaning up all served files (${servedFiles.size} files)")
|
|
400
|
-
|
|
401
|
-
servedFiles.keys.toList().forEach { fileId ->
|
|
402
|
-
removeServedFile(fileId)
|
|
403
|
-
}
|
|
563
|
+
servedFiles.keys.toList().forEach { fileId -> removeServedFile(fileId) }
|
|
404
564
|
|
|
405
565
|
// Clean cache directory
|
|
406
566
|
cacheDirectory?.listFiles()?.forEach { file ->
|
|
@@ -33,3 +33,13 @@ dependencyResolutionManagement {
|
|
|
33
33
|
|
|
34
34
|
rootProject.name = "Catalyst Application"
|
|
35
35
|
include(":app")
|
|
36
|
+
|
|
37
|
+
// @catalyst/cloud-ai — include when the package is installed.
|
|
38
|
+
// The npm package ships an /android library module for native AI; wire it here so Gradle picks it up.
|
|
39
|
+
// After running: npm install @catalyst/cloud-ai && npm run sync-packages -- --packages cloud-ai
|
|
40
|
+
// Also set ai.enabled=true in your app config to enable useLegacyPackaging for GPU dlopen().
|
|
41
|
+
val cloudAiDir = File(rootDir, "../node_modules/@catalyst/cloud-ai/android")
|
|
42
|
+
if (cloudAiDir.exists()) {
|
|
43
|
+
include(":catalyst-cloud-ai")
|
|
44
|
+
project(":catalyst-cloud-ai").projectDir = cloudAiDir
|
|
45
|
+
}
|
|
@@ -1,9 +1,301 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
4
|
+
var _path = _interopRequireDefault(require("path"));
|
|
5
|
+
var _child_process = require("child_process");
|
|
6
|
+
var _utils = require("./utils.js");
|
|
7
|
+
var _TerminalProgress = _interopRequireDefault(require("./TerminalProgress.js"));
|
|
8
|
+
var _setupServer = require("./setupServer.js");
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
const catalystCorePath = _path.default.dirname(require.resolve("catalyst-core/package.json"));
|
|
11
|
+
const pwd = _path.default.join(catalystCorePath, "dist/native");
|
|
12
|
+
const configPath = `${process.env.PWD}/config/config.json`;
|
|
13
|
+
const steps = {
|
|
14
|
+
java: "Check Java Environment",
|
|
15
|
+
config: "Initialize Configuration",
|
|
16
|
+
androidTools: "Validate Android Tools",
|
|
17
|
+
emulator: "Configure Android Emulator",
|
|
18
|
+
properties: "Update Local Properties",
|
|
19
|
+
saveConfig: "Save Configuration",
|
|
20
|
+
setupServer: "Setup Server"
|
|
21
|
+
};
|
|
22
|
+
const progressPaddingConfig = {
|
|
23
|
+
titlePaddingTop: 2,
|
|
24
|
+
titlePaddingBottom: 1,
|
|
25
|
+
stepPaddingLeft: 4,
|
|
26
|
+
stepSpacing: 1,
|
|
27
|
+
errorPaddingLeft: 6,
|
|
28
|
+
bottomMargin: 2
|
|
29
|
+
};
|
|
30
|
+
const progress = new _TerminalProgress.default(steps, "Catalyst Universal Android Setup", progressPaddingConfig);
|
|
31
|
+
async function checkJavaInstallation() {
|
|
32
|
+
try {
|
|
33
|
+
(0, _utils.runCommand)("java -version");
|
|
34
|
+
(0, _utils.runCommand)("javac -version");
|
|
35
|
+
progress.log("Java is installed and configured", "success");
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
progress.log("Java installation not found or not properly configured", "error");
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function installJava() {
|
|
43
|
+
progress.log("Installing Java (Zulu JDK 17)...");
|
|
44
|
+
try {
|
|
45
|
+
try {
|
|
46
|
+
(0, _utils.runCommand)("brew --version");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error("Homebrew is required but not installed. Please install Homebrew first.");
|
|
49
|
+
}
|
|
50
|
+
const javaHome = "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home";
|
|
51
|
+
|
|
52
|
+
// Try installing Java
|
|
53
|
+
try {
|
|
54
|
+
await (0, _utils.runCommand)("brew install --cask zulu@17");
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.message.includes("already installed")) {
|
|
57
|
+
progress.log("Attempting to reinstall Java...", "info");
|
|
58
|
+
try {
|
|
59
|
+
await (0, _utils.runCommand)("brew reinstall --cask zulu@17");
|
|
60
|
+
} catch (reinstallError) {
|
|
61
|
+
throw new Error(`Failed to reinstall Java: ${reinstallError.message}. Please run 'brew reinstall --cask zulu@17' manually.`);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Verify Java installation
|
|
69
|
+
if (!_fs.default.existsSync(javaHome)) {
|
|
70
|
+
throw new Error(`Java installation not found at ${javaHome}. Please verify the installation manually.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Set up environment variables
|
|
74
|
+
const homeDir = process.env.HOME;
|
|
75
|
+
const shellProfile = `${homeDir}/.zshrc`;
|
|
76
|
+
const exportCommand = `\n# Java Configuration\nexport JAVA_HOME=${javaHome}\nexport PATH=$JAVA_HOME/bin:$PATH\n`;
|
|
77
|
+
try {
|
|
78
|
+
_fs.default.appendFileSync(shellProfile, exportCommand);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Failed to update ${shellProfile}: ${error.message}. Please add the following lines manually:\n${exportCommand}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Set for current process
|
|
84
|
+
process.env.JAVA_HOME = javaHome;
|
|
85
|
+
process.env.PATH = `${javaHome}/bin:${process.env.PATH}`;
|
|
86
|
+
|
|
87
|
+
// Verify Java configuration
|
|
88
|
+
try {
|
|
89
|
+
const javaVersion = (0, _utils.runCommand)("java -version");
|
|
90
|
+
progress.log("Java installed and configured successfully", "success");
|
|
91
|
+
progress.log(`Java Version: ${javaVersion}`, "info");
|
|
92
|
+
progress.log(`IMPORTANT: Please run the following commands to complete setup:`, "warning");
|
|
93
|
+
progress.log(`1. source ${shellProfile}`, "info");
|
|
94
|
+
progress.log(`2. echo $JAVA_HOME`, "info");
|
|
95
|
+
return true;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Java installation verified but environment not configured. Please run:\n1. source ${shellProfile}\n2. echo $JAVA_HOME`);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
progress.log(error.message, "error");
|
|
101
|
+
progress.log("Manual Setup Instructions:", "info");
|
|
102
|
+
progress.log("1. Run: brew reinstall --cask zulu@17", "info");
|
|
103
|
+
progress.log("2. Add to ~/.zshrc:", "info");
|
|
104
|
+
progress.log(" export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home", "info");
|
|
105
|
+
progress.log(" export PATH=$JAVA_HOME/bin:$PATH", "info");
|
|
106
|
+
progress.log("3. Run: source ~/.zshrc", "info");
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function validateJavaEnvironment() {
|
|
111
|
+
progress.start("java");
|
|
112
|
+
const hasJava = await checkJavaInstallation();
|
|
113
|
+
if (!hasJava) {
|
|
114
|
+
progress.log("Java installation required. Starting installation process...");
|
|
115
|
+
const javaInstalled = await installJava();
|
|
116
|
+
if (!javaInstalled) {
|
|
117
|
+
progress.fail("java", "Failed to set up Java environment");
|
|
118
|
+
throw new Error("Failed to set up Java environment");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
progress.complete("java");
|
|
122
|
+
}
|
|
123
|
+
async function initializeConfig() {
|
|
124
|
+
const configFile = _fs.default.readFileSync(configPath, "utf8");
|
|
125
|
+
const config = JSON.parse(configFile);
|
|
126
|
+
const {
|
|
127
|
+
WEBVIEW_CONFIG
|
|
128
|
+
} = config;
|
|
129
|
+
if (!WEBVIEW_CONFIG || Object.keys(WEBVIEW_CONFIG).length === 0) {
|
|
130
|
+
progress.log("WebView Config missing in " + configPath, "error");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (!WEBVIEW_CONFIG.android) {
|
|
134
|
+
WEBVIEW_CONFIG.android = {};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
WEBVIEW_CONFIG
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function saveConfig(newConfig) {
|
|
141
|
+
try {
|
|
142
|
+
const existingConfigFile = _fs.default.readFileSync(configPath, "utf8");
|
|
143
|
+
const existingConfig = JSON.parse(existingConfigFile);
|
|
144
|
+
const updatedConfig = {
|
|
145
|
+
...existingConfig,
|
|
146
|
+
WEBVIEW_CONFIG: {
|
|
147
|
+
...existingConfig.WEBVIEW_CONFIG,
|
|
148
|
+
android: newConfig.WEBVIEW_CONFIG.android
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
_fs.default.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2));
|
|
152
|
+
progress.log("Configuration saved successfully", "success");
|
|
153
|
+
} catch (error) {
|
|
154
|
+
progress.log("Failed to save configuration: " + error.message, "error");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function validateAndroidTools(androidConfig) {
|
|
159
|
+
const ANDROID_SDK = androidConfig.sdkPath;
|
|
160
|
+
const EMULATOR_PATH = `${ANDROID_SDK}/emulator/emulator`;
|
|
161
|
+
const ADB_PATH = `${ANDROID_SDK}/platform-tools/adb`;
|
|
162
|
+
if (!ANDROID_SDK) {
|
|
163
|
+
throw new Error("Android SDK path is not configured");
|
|
164
|
+
}
|
|
165
|
+
if (!_fs.default.existsSync(ANDROID_SDK)) {
|
|
166
|
+
throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);
|
|
167
|
+
}
|
|
168
|
+
if (!_fs.default.existsSync(ADB_PATH)) {
|
|
169
|
+
throw new Error(`ADB not found at: ${ADB_PATH}`);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
(0, _utils.runCommand)(`${ADB_PATH} version`);
|
|
173
|
+
progress.log("ADB is valid", "success");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(`ADB is not working properly: ${error.message}`);
|
|
176
|
+
}
|
|
177
|
+
if (!_fs.default.existsSync(EMULATOR_PATH)) {
|
|
178
|
+
throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
(0, _utils.runCommand)(`${EMULATOR_PATH} -version`);
|
|
182
|
+
progress.log("Emulator is valid", "success");
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw new Error(`Emulator is not working properly: ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const avdOutput = (0, _utils.runCommand)(`${EMULATOR_PATH} -list-avds`);
|
|
188
|
+
if (!avdOutput.includes(androidConfig.emulatorName)) {
|
|
189
|
+
throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);
|
|
190
|
+
}
|
|
191
|
+
progress.log(`Emulator "${androidConfig.emulatorName}" exists`, "success");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new Error(`Error checking emulator AVD: ${error.message}`);
|
|
194
|
+
}
|
|
195
|
+
progress.log("Android tools validation completed successfully!", "success");
|
|
196
|
+
return {
|
|
197
|
+
EMULATOR_PATH,
|
|
198
|
+
ADB_PATH
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function checkEmulator(ADB_PATH) {
|
|
202
|
+
try {
|
|
203
|
+
const devices = (0, _utils.runCommand)(`${ADB_PATH} devices`);
|
|
204
|
+
return devices.includes("emulator");
|
|
205
|
+
} catch (error) {
|
|
206
|
+
progress.log(`Error checking emulator: ${error}`, "error");
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function startEmulator(EMULATOR_PATH, androidConfig) {
|
|
211
|
+
progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);
|
|
212
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - EMULATOR_PATH is derived from SDK configuration and emulatorName is local native setup config.
|
|
213
|
+
// eslint-disable-next-line security/detect-child-process
|
|
214
|
+
(0, _child_process.exec)(`${EMULATOR_PATH} -avd ${androidConfig.emulatorName} -read-only > /dev/null &`, error => {
|
|
215
|
+
if (error) {
|
|
216
|
+
progress.log(`Error starting emulator: ${error}`, "error");
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function updateLocalProperties(sdkPath) {
|
|
221
|
+
progress.start("properties");
|
|
222
|
+
try {
|
|
223
|
+
(0, _utils.runCommand)(`cd ${pwd}/androidProject && ./gradlew updateSdkPath -PsdkPath="${sdkPath}"`);
|
|
224
|
+
progress.log("Updated local.properties successfully", "success");
|
|
225
|
+
progress.complete("properties");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
progress.fail("properties", `Failed to update local.properties: ${error.message}`);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function setupAndroidEnvironment() {
|
|
232
|
+
try {
|
|
233
|
+
await validateJavaEnvironment();
|
|
234
|
+
progress.start("config");
|
|
235
|
+
await initializeConfig();
|
|
236
|
+
const config = await (0, _utils.validateAndCompleteConfig)("android", configPath);
|
|
237
|
+
progress.complete("config");
|
|
238
|
+
progress.start("androidTools");
|
|
239
|
+
const {
|
|
240
|
+
EMULATOR_PATH,
|
|
241
|
+
ADB_PATH
|
|
242
|
+
} = validateAndroidTools(config.android);
|
|
243
|
+
progress.complete("androidTools");
|
|
244
|
+
progress.start("emulator");
|
|
245
|
+
const emulatorRunning = await checkEmulator(ADB_PATH);
|
|
246
|
+
if (!emulatorRunning) {
|
|
247
|
+
progress.log("No emulator running, attempting to start one...");
|
|
248
|
+
await startEmulator(EMULATOR_PATH, config.android);
|
|
249
|
+
} else {
|
|
250
|
+
progress.log("Emulator already running, proceeding with installation...", "success");
|
|
251
|
+
}
|
|
252
|
+
progress.complete("emulator");
|
|
253
|
+
if (config.android?.sdkPath) {
|
|
254
|
+
await updateLocalProperties(config.android.sdkPath);
|
|
255
|
+
}
|
|
256
|
+
progress.start("saveConfig");
|
|
257
|
+
await saveConfig({
|
|
258
|
+
WEBVIEW_CONFIG: config
|
|
259
|
+
});
|
|
260
|
+
progress.complete("saveConfig");
|
|
261
|
+
progress.start("setupServer");
|
|
262
|
+
await (0, _setupServer.setupServer)(configPath);
|
|
263
|
+
progress.complete("setupServer");
|
|
264
|
+
progress.printTreeContent("Configuration Explanation", ["WEBVIEW_CONFIG: Main configuration object for the WebView setup", {
|
|
265
|
+
text: "port: Port number for the WebView server",
|
|
266
|
+
indent: 1,
|
|
267
|
+
prefix: "├─ ",
|
|
268
|
+
color: "gray"
|
|
269
|
+
}, {
|
|
270
|
+
text: "android: Android-specific configuration",
|
|
271
|
+
indent: 1,
|
|
272
|
+
prefix: "└─ ",
|
|
273
|
+
color: "gray"
|
|
274
|
+
}, {
|
|
275
|
+
text: "buildType: Build type (debug/release)",
|
|
276
|
+
indent: 2,
|
|
277
|
+
prefix: "├─ ",
|
|
278
|
+
color: "gray"
|
|
279
|
+
}, {
|
|
280
|
+
text: "sdkPath: Android SDK path",
|
|
281
|
+
indent: 2,
|
|
282
|
+
prefix: "├─ ",
|
|
283
|
+
color: "gray"
|
|
284
|
+
}, {
|
|
285
|
+
text: "emulatorName: Selected Android emulator name",
|
|
286
|
+
indent: 2,
|
|
287
|
+
prefix: "└─ ",
|
|
288
|
+
color: "gray"
|
|
289
|
+
}]);
|
|
290
|
+
progress.printTreeContent("Final Configuration", [JSON.stringify(config, null, 2)]);
|
|
291
|
+
process.exit(0);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (progress.currentStep) {
|
|
294
|
+
progress.fail(progress.currentStep.id, error.message);
|
|
295
|
+
}
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Execute the main setup
|
|
9
301
|
setupAndroidEnvironment();
|