fix: preserve webbricks params for startup ui requests

This commit is contained in:
yumoqing 2026-05-19 00:04:43 +08:00
parent 87ebf5a407
commit 39727f87f9
2 changed files with 26 additions and 16 deletions

View File

@ -87,8 +87,8 @@ class BricksHttp(private val context: BricksContext? = null) {
authToken: String = "" authToken: String = ""
): JsonObject { ): JsonObject {
val requestParams = params.withBackendContextIfNeeded(url) val requestParams = params.withBackendContextIfNeeded(url)
val response = client.post(url) { val requestUrl = url.withQueryParameters(requestParams)
appendQueryParameters(requestParams) val response = client.post(requestUrl) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(body) setBody(body)
if (authToken.isNotEmpty()) { if (authToken.isNotEmpty()) {
@ -96,7 +96,7 @@ class BricksHttp(private val context: BricksContext? = null) {
} }
} }
val text = response.bodyAsText() val text = response.bodyAsText()
response.throwIfHttpError(text, url) response.throwIfHttpError(text, requestUrl)
return parseJsonObjectOrError(text) return parseJsonObjectOrError(text)
} }
@ -109,11 +109,11 @@ class BricksHttp(private val context: BricksContext? = null) {
authToken: String = "" authToken: String = ""
): String { ): String {
val requestParams = emptyMap<String, String>().withBackendContextIfNeeded(url) val requestParams = emptyMap<String, String>().withBackendContextIfNeeded(url)
val requestUrl = url.withQueryParameters(requestParams)
val formBody = form.entries.joinToString("&") { (k, v) -> val formBody = form.entries.joinToString("&") { (k, v) ->
"${k.encodeURLParameter()}=${v.encodeURLParameter()}" "${k.encodeURLParameter()}=${v.encodeURLParameter()}"
} }
val response = client.post(url) { val response = client.post(requestUrl) {
appendQueryParameters(requestParams)
contentType(ContentType.Application.FormUrlEncoded) contentType(ContentType.Application.FormUrlEncoded)
setBody(formBody) setBody(formBody)
if (authToken.isNotEmpty()) { if (authToken.isNotEmpty()) {
@ -121,7 +121,7 @@ class BricksHttp(private val context: BricksContext? = null) {
} }
} }
val text = response.bodyAsText() val text = response.bodyAsText()
response.throwIfHttpError(text, url) response.throwIfHttpError(text, requestUrl)
return text return text
} }
@ -134,14 +134,14 @@ class BricksHttp(private val context: BricksContext? = null) {
authToken: String = "" authToken: String = ""
): String { ): String {
val requestParams = params.withBackendContextIfNeeded(url) val requestParams = params.withBackendContextIfNeeded(url)
val response = client.get(url) { val requestUrl = url.withQueryParameters(requestParams)
appendQueryParameters(requestParams) val response = client.get(requestUrl) {
if (authToken.isNotEmpty()) { if (authToken.isNotEmpty()) {
header(HttpHeaders.Authorization, "Bearer $authToken") header(HttpHeaders.Authorization, "Bearer $authToken")
} }
} }
val text = response.bodyAsText() val text = response.bodyAsText()
response.throwIfHttpError(text, url) response.throwIfHttpError(text, requestUrl)
return text return text
} }
@ -175,13 +175,23 @@ class BricksHttp(private val context: BricksContext? = null) {
private fun Map<String, String>.withBackendContextIfNeeded(url: String): Map<String, String> = private fun Map<String, String>.withBackendContextIfNeeded(url: String): Map<String, String> =
if (url.isWebBricksBackendResource()) withWebBricksRequestContext(this, requestContext) else this if (url.isWebBricksBackendResource()) withWebBricksRequestContext(this, requestContext) else this
private fun HttpRequestBuilder.appendQueryParameters(params: Map<String, String>) { private fun String.withQueryParameters(params: Map<String, String>): String {
url { if (params.isEmpty()) return this
params.forEach { (k, v) -> val fragmentIndex = indexOf('#')
parameters.remove(k) val baseAndQuery = if (fragmentIndex >= 0) substring(0, fragmentIndex) else this
parameters.append(k, v) val fragment = if (fragmentIndex >= 0) substring(fragmentIndex) else ""
} val path = baseAndQuery.substringBefore('?')
val existingQuery = baseAndQuery.substringAfter('?', missingDelimiterValue = "")
val encodedOverrideKeys = params.keys.map { it.encodeURLParameter() }.toSet()
val preservedQuery = existingQuery
.split('&')
.filter { it.isNotBlank() }
.filter { entry -> entry.substringBefore('=').substringBefore('&') !in encodedOverrideKeys }
val appendedQuery = params.entries.map { (key, value) ->
"${key.encodeURLParameter()}=${value.encodeURLParameter()}"
} }
val query = (preservedQuery + appendedQuery).joinToString("&")
return if (query.isBlank()) "$path$fragment" else "$path?$query$fragment"
} }
private suspend fun HttpResponse.throwIfHttpError(body: String, requestUrl: String) { private suspend fun HttpResponse.throwIfHttpError(body: String, requestUrl: String) {

View File

@ -57,7 +57,7 @@ The packaged macOS app also accepts the URL as its first argument:
open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui open build/compose/binaries/main/app/sageclient.app --args https://ai.atvoe.com/center.ui
``` ```
If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and loads the path/query part as the initial Bricks UI. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. If the argument is an absolute `http://` or `https://` URL, `sageclient` uses the URL origin as `baseUrl` and loads the path/query part as the initial Bricks UI. If the argument is a relative path such as `/center.ui`, it uses `sage.baseUrl` and loads that path. The actual HTTP request is still issued through `BricksHttp`, so startup URLs ending in `.ui` / `.dspy` are requested with `_webbricks_=1`, `_width`, `_height`, `_is_mobile` and `_lang`; they must not be fetched as raw HTML/template pages.
Optional properties: Optional properties: