Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d7d3c3f
refactor: Move editor loading UI into GutenbergView
jkmassel Feb 7, 2026
156f40b
Don't try to load theme styles if the configuration has them disabled
jkmassel Feb 7, 2026
6c39897
Update AGP to match WPAndroid
jkmassel Feb 9, 2026
bcfa18d
Add debug logging
jkmassel Feb 9, 2026
cad54a1
Add logging and request proxying
jkmassel Feb 10, 2026
12af282
fix: Resolve dev server CORS errors and falsy post id blocking editor…
dcalhoun Feb 10, 2026
9213807
Don’t allow `0` as a post ID
jkmassel Feb 10, 2026
a5e6d08
Use types to build the editor URI
jkmassel Feb 10, 2026
150f89d
Add verbose logging to EditorHTTPClient on both platforms
jkmassel Feb 11, 2026
34bc052
Add WP.com namespace support to Android RESTAPIRepository
jkmassel Feb 11, 2026
1ad303d
Redact sensitive headers in EditorHTTPClient logs on both platforms
jkmassel Feb 11, 2026
d215b2b
Restrict CORS proxy credentials to trusted hosts
jkmassel Feb 11, 2026
2695bf1
Replace HttpURLConnection with OkHttp in CORS proxy
jkmassel Feb 11, 2026
2cf6cc9
Add comment explaining error response body logging
jkmassel Feb 11, 2026
a935623
Clarify that enableNetworkLogging controls editor-to-host logging
jkmassel Feb 11, 2026
a33d421
Remove overly-aggressive themeStyles guard in Android EditorService
jkmassel Feb 11, 2026
2d87bc3
Normalize namespace trailing slash in RESTAPIRepository on both platf…
jkmassel Feb 11, 2026
9129c51
Cancel in-flight view animations in onDetachedFromWindow
jkmassel Feb 11, 2026
a365fca
Remove unused LoadingState enum from GutenbergView
jkmassel Feb 11, 2026
d0b425f
Remove unused ASSET_LOADING_TIMEOUT_MS constant from GutenbergView
jkmassel Feb 11, 2026
0a5cd5d
Add delegate notification for download requests on Android
jkmassel Feb 11, 2026
2a19550
Make URLCollectingMockHTTPClient an actor for thread safety
jkmassel Feb 11, 2026
5e8b852
Clear openMediaLibraryListener and logJsExceptionListener in onDetach…
jkmassel Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,28 @@ interface EditorHTTPClientProtocol {
suspend fun perform(method: EditorHttpMethod, url: String): EditorHTTPClientResponse
}

/**
* The response data from an HTTP request, either in-memory bytes or a downloaded file.
*/
sealed class EditorResponseData {
data class Bytes(val data: ByteArray) : EditorResponseData() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Bytes) return false
return data.contentEquals(other.data)
}
override fun hashCode(): Int = data.contentHashCode()
}
data class File(val file: java.io.File) : EditorResponseData()
}

/**
* A delegate for observing HTTP requests made by the editor.
*
* Implement this interface to inspect or log all network requests.
*/
interface EditorHTTPClientDelegate {
fun didPerformRequest(url: String, method: EditorHttpMethod, response: Response, data: ByteArray)
fun didPerformRequest(url: String, method: EditorHttpMethod, response: Response, data: EditorResponseData)
}

/**
Expand Down Expand Up @@ -141,18 +156,32 @@ class EditorHTTPClient(

override suspend fun download(url: String, destination: File): EditorHTTPClientDownloadResponse =
withContext(Dispatchers.IO) {
Log.d(TAG, "DOWNLOAD $url")
Log.d(TAG, " Destination: ${destination.absolutePath}")

val request = Request.Builder()
.url(url)
.addHeader("Authorization", authHeader)
.get()
.build()

val response = client.newCall(request).execute()
Log.d(TAG, " Request headers: ${redactHeaders(request.headers)}")

val response: Response
try {
response = client.newCall(request).execute()
} catch (e: IOException) {
Log.e(TAG, "DOWNLOAD $url – network error: ${e.message}", e)
throw e
}

val statusCode = response.code
val headers = extractHeaders(response)
Log.d(TAG, "DOWNLOAD $url – $statusCode")
Log.d(TAG, " Response headers: ${redactResponseHeaders(response)}")

if (statusCode !in 200..299) {
Log.e(TAG, "HTTP error downloading $url: $statusCode")
Log.e(TAG, "DOWNLOAD $url – HTTP error: $statusCode")
throw EditorHTTPClientError.DownloadFailed(statusCode)
}

Expand All @@ -163,8 +192,14 @@ class EditorHTTPClient(
input.copyTo(output)
}
}
Log.d(TAG, "Downloaded file: file=${destination.absolutePath}, size=${destination.length()} bytes, url=$url")
} ?: throw EditorHTTPClientError.DownloadFailed(statusCode)
Log.d(TAG, "DOWNLOAD $url – complete (${destination.length()} bytes)")
Log.d(TAG, " Saved to: ${destination.absolutePath}")
} ?: run {
Log.e(TAG, "DOWNLOAD $url – empty response body")
throw EditorHTTPClientError.DownloadFailed(statusCode)
}

delegate?.didPerformRequest(url, EditorHttpMethod.GET, response, EditorResponseData.File(destination))

EditorHTTPClientDownloadResponse(
file = destination,
Expand All @@ -175,6 +210,8 @@ class EditorHTTPClient(

override suspend fun perform(method: EditorHttpMethod, url: String): EditorHTTPClientResponse =
withContext(Dispatchers.IO) {
Log.d(TAG, "$method $url")

// OkHttp requires a body for POST, PUT, PATCH methods
// GET, HEAD, OPTIONS, DELETE don't require a body
val requiresBody = method in listOf(
Expand All @@ -190,7 +227,15 @@ class EditorHTTPClient(
.method(method.toString(), requestBody)
.build()

val response = client.newCall(request).execute()
Log.d(TAG, " Request headers: ${redactHeaders(request.headers)}")

val response: Response
try {
response = client.newCall(request).execute()
} catch (e: IOException) {
Log.e(TAG, "$method $url – network error: ${e.message}", e)
throw e
}

// Note: This loads the entire response into memory. This is acceptable because
// this method is only used for WordPress REST API responses (editor settings, post
Expand All @@ -200,14 +245,22 @@ class EditorHTTPClient(
val statusCode = response.code
val headers = extractHeaders(response)

delegate?.didPerformRequest(url, method, response, data)
Log.d(TAG, "$method $url – $statusCode (${data.size} bytes)")
Log.d(TAG, " Response headers: ${redactResponseHeaders(response)}")

delegate?.didPerformRequest(url, method, response, EditorResponseData.Bytes(data))

if (statusCode !in 200..299) {
Log.e(TAG, "HTTP error fetching $url: $statusCode")
Log.e(TAG, "$method $url – HTTP error: $statusCode")
// Log the raw body to aid debugging unexpected error formats.
// This is acceptable because the WordPress REST API should never
// include sensitive information (tokens, credentials) in responses.
Log.e(TAG, " Response body: ${data.toString(Charsets.UTF_8)}")

// Try to parse as WordPress error
val wpError = tryParseWPError(data)
if (wpError != null) {
Log.e(TAG, " WP error – code: ${wpError.code}, message: ${wpError.message}")
throw EditorHTTPClientError.WPErrorResponse(wpError)
}

Expand Down Expand Up @@ -259,5 +312,27 @@ class EditorHTTPClient(
companion object {
private const val TAG = "EditorHTTPClient"
private val gson = Gson()

private val SENSITIVE_HEADERS = setOf("authorization", "cookie", "set-cookie")

/**
* Returns a string representation of the given OkHttp headers with
* sensitive values (Authorization, Cookie) redacted.
*/
internal fun redactHeaders(headers: okhttp3.Headers): String {
return headers.joinToString(", ") { (name, value) ->
if (name.lowercase() in SENSITIVE_HEADERS) "$name: <redacted>" else "$name: $value"
}
}

/**
* Returns a string representation of the given response headers with
* sensitive values (Set-Cookie) redacted.
*/
internal fun redactResponseHeaders(response: Response): String {
return response.headers.joinToString(", ") { (name, value) ->
if (name.lowercase() in SENSITIVE_HEADERS) "$name: <redacted>" else "$name: $value"
}
}
}
}

This file was deleted.

Loading
Loading