Compare commits

..

4 Commits

Author SHA1 Message Date
GitHub Actions
19d85dbb2d chore: bump version to 4.11.141 [skip ci] 2026-07-27 14:18:44 +00:00
fbff6b9738 Fix UnifiedPush distributor reset and native Matrix gateway discovery.
All checks were successful
Build / increment-version (push) Successful in 5s
Build / build-android (push) Successful in 4m35s
2026-07-28 00:18:37 +10:00
GitHub Actions
52e0fddcf1 chore: bump version to 4.11.140 [skip ci] 2026-07-25 10:16:53 +00:00
2ed3b0e9c8 Bump cinny: fix avatar upload for JPEG/WebP/GIF without profile color.
All checks were successful
Build / increment-version (push) Successful in 4s
Build / build-android (push) Successful in 6m37s
2026-07-25 20:16:46 +10:00
5 changed files with 102 additions and 2 deletions

View File

@@ -17,6 +17,8 @@ import com.getcapacitor.annotation.CapacitorPlugin
* - `stop()` — clear persisted credentials and unregister UnifiedPush * - `stop()` — clear persisted credentials and unregister UnifiedPush
* - `setAppForeground({ foreground })` — tell the service whether the app UI is visible * - `setAppForeground({ foreground })` — tell the service whether the app UI is visible
* - `getStatus()` — returns current fetch and UnifiedPush state * - `getStatus()` — returns current fetch and UnifiedPush state
* - `requestDistributorSetup()` — clear saved distributor and re-open OS picker
* - `resolveUnifiedPushGateway({ endpoint })` — native Matrix gateway discovery
* - `clearRoomNotifications({ roomId })` — dismiss native tray notifs for a room * - `clearRoomNotifications({ roomId })` — dismiss native tray notifs for a room
* - `setNotificationGroups({ rooms })` — persist space/DM grouping for tray nesting * - `setNotificationGroups({ rooms })` — persist space/DM grouping for tray nesting
* - `showNotification({ title, body, roomId, groupId, groupName, kind, largeIconBase64 })` * - `showNotification({ title, body, roomId, groupId, groupName, kind, largeIconBase64 })`
@@ -131,6 +133,35 @@ class SyncServicePlugin : Plugin() {
call.resolve(result) call.resolve(result)
} }
/**
* Clears the saved UnifiedPush distributor and opens the OS default-distributor
* picker (`unifiedpush://link`), then re-registers on success.
*/
@PluginMethod
fun requestDistributorSetup(call: PluginCall) {
val currentActivity = activity
?: return call.reject("Activity required to select a UnifiedPush distributor")
UnifiedPushManager.requestDistributorSetup(context, currentActivity) { success ->
call.resolve(JSObject().put("success", success))
}
}
/**
* Resolves the Matrix push gateway for a UnifiedPush endpoint using native HTTP
* so discovery is not blocked by missing CORS headers in the WebView.
*/
@PluginMethod
fun resolveUnifiedPushGateway(call: PluginCall) {
val endpoint = call.getString("endpoint")
?: return call.reject("endpoint required")
Thread {
val gateway = UnifiedPushManager.resolveMatrixGateway(endpoint)
bridge?.executeOnMainThread {
call.resolve(JSObject().put("gatewayUrl", gateway))
}
}.start()
}
/** /**
* Cancels native tray notifications for a Matrix room after it has been marked as read. * Cancels native tray notifications for a Matrix room after it has been marked as read.
*/ */

View File

@@ -4,9 +4,12 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
import org.json.JSONObject
import org.unifiedpush.android.connector.FailedReason import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.INSTANCE_DEFAULT import org.unifiedpush.android.connector.INSTANCE_DEFAULT
import org.unifiedpush.android.connector.UnifiedPush import org.unifiedpush.android.connector.UnifiedPush
import java.net.HttpURLConnection
import java.net.URL
/** Coordinates UnifiedPush registration state and bridges events back to JS. */ /** Coordinates UnifiedPush registration state and bridges events back to JS. */
object UnifiedPushManager { object UnifiedPushManager {
@@ -18,6 +21,7 @@ object UnifiedPushManager {
private const val EVENT_UNREGISTERED = "unifiedPushUnregistered" private const val EVENT_UNREGISTERED = "unifiedPushUnregistered"
private const val EVENT_REGISTRATION_FAILED = "unifiedPushRegistrationFailed" private const val EVENT_REGISTRATION_FAILED = "unifiedPushRegistrationFailed"
private const val DEFAULT_MESSAGE = "Paarrot notifications" private const val DEFAULT_MESSAGE = "Paarrot notifications"
const val DEFAULT_MATRIX_GATEWAY = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
@Volatile @Volatile
private var plugin: SyncServicePlugin? = null private var plugin: SyncServicePlugin? = null
@@ -53,6 +57,60 @@ object UnifiedPushManager {
} }
} }
/**
* Clears any saved distributor and re-opens the OS/default distributor picker
* (`unifiedpush://link`), then registers again on success.
*/
fun requestDistributorSetup(context: Context, activity: Activity, onDone: (Boolean) -> Unit) {
runCatching { UnifiedPush.removeDistributor(context) }
.onFailure { Log.w(TAG, "removeDistributor failed: ${it.message}") }
clearEndpoint(context)
UnifiedPush.tryUseDefaultDistributor(activity) { success ->
if (success) {
requestRegistration(context)
} else {
dispatchRegistrationFailed(FailedReason.ACTION_REQUIRED.name, INSTANCE_DEFAULT)
}
onDone(success)
}
}
/**
* Discovers the Matrix push gateway for a UnifiedPush endpoint via native HTTP
* (WebView `fetch` fails — ntfy responses omit CORS headers).
*/
fun resolveMatrixGateway(endpoint: String): String {
return try {
val endpointUrl = URL(endpoint)
val discoveryUrl = URL(
endpointUrl.protocol,
endpointUrl.host,
endpointUrl.port,
"/_matrix/push/v1/notify",
)
val conn = discoveryUrl.openConnection() as HttpURLConnection
try {
conn.requestMethod = "GET"
conn.connectTimeout = 4_000
conn.readTimeout = 6_000
conn.setRequestProperty("Accept", "application/json")
if (conn.responseCode != 200) return DEFAULT_MATRIX_GATEWAY
val body = JSONObject(conn.inputStream.bufferedReader().readText())
val gateway =
body.optString("gateway").takeIf { it.isNotBlank() }
?: body.optJSONObject("unifiedpush")?.optString("gateway")
if (gateway == "matrix") discoveryUrl.toString() else DEFAULT_MATRIX_GATEWAY
} finally {
conn.disconnect()
}
} catch (e: Exception) {
Log.w(TAG, "Matrix gateway discovery failed: ${e.message}")
DEFAULT_MATRIX_GATEWAY
}
}
fun unregister(context: Context) { fun unregister(context: Context) {
runCatching { runCatching {
UnifiedPush.unregister(context, INSTANCE_DEFAULT) UnifiedPush.unregister(context, INSTANCE_DEFAULT)

2
cinny

Submodule cinny updated: b52926f7d8...0de2fd942f

View File

@@ -52,6 +52,8 @@ interface MatrixBackgroundSyncPlugin {
setAppForeground(options: { foreground: boolean }): Promise<void>; setAppForeground(options: { foreground: boolean }): Promise<void>;
/** Returns fetch state and current UnifiedPush registration details. */ /** Returns fetch state and current UnifiedPush registration details. */
getStatus(): Promise<UnifiedPushStatus>; getStatus(): Promise<UnifiedPushStatus>;
/** Native Matrix gateway discovery for a UnifiedPush endpoint (avoids WebView CORS). */
resolveUnifiedPushGateway(options: { endpoint: string }): Promise<{ gatewayUrl: string }>;
/** Cancel native tray notifications posted for a Matrix room. */ /** Cancel native tray notifications posted for a Matrix room. */
clearRoomNotifications(options: { roomId: string }): Promise<ClearRoomNotificationsResult>; clearRoomNotifications(options: { roomId: string }): Promise<ClearRoomNotificationsResult>;
/** /**
@@ -170,6 +172,15 @@ const normalizeDistributors = (raw: UnifiedPushStatus['distributors']): string[]
}; };
const resolveUnifiedPushGateway = async (endpoint: string): Promise<string> => { const resolveUnifiedPushGateway = async (endpoint: string): Promise<string> => {
// Prefer native HTTP — ntfy/Matrix gateway responses omit CORS headers, so
// WebView fetch fails with TypeError: Failed to fetch and wrongly falls back.
try {
const result = await MatrixBackgroundSync.resolveUnifiedPushGateway({ endpoint });
if (result?.gatewayUrl) return result.gatewayUrl;
} catch (err) {
console.warn('[BackgroundSync] Native UnifiedPush gateway discovery failed:', err);
}
try { try {
const discoveryUrl = new URL(endpoint); const discoveryUrl = new URL(endpoint);
discoveryUrl.pathname = '/_matrix/push/v1/notify'; discoveryUrl.pathname = '/_matrix/push/v1/notify';

View File

@@ -1,6 +1,6 @@
{ {
"name": "paarrot", "name": "paarrot",
"version": "4.11.139", "version": "4.11.141",
"description": "Paarrot - A Matrix client based on Cinny", "description": "Paarrot - A Matrix client based on Cinny",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"