Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19d85dbb2d | ||
| fbff6b9738 | |||
|
|
52e0fddcf1 | ||
| 2ed3b0e9c8 |
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
2
cinny
Submodule cinny updated: b52926f7d8...0de2fd942f
@@ -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';
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user