Skip to content

Feat: Add Unifiedpush support#5883

Merged
mahibi merged 47 commits intonextcloud:masterfrom
p1gp1g:unifiedpush
Apr 30, 2026
Merged

Feat: Add Unifiedpush support#5883
mahibi merged 47 commits intonextcloud:masterfrom
p1gp1g:unifiedpush

Conversation

@p1gp1g
Copy link
Copy Markdown
Contributor

@p1gp1g p1gp1g commented Feb 18, 2026

Following web push support in nextcloud/notifications, we can add UnifiedPush support to Talk (and Nextcloud Android) to get push notifications even without the Play Services.

The implementation follows this guide: https://unifiedpush.org/developers/ux/

  • During first startup, the push notification is setup:
    • If the Play Services are available, they are used by default
    • Else, if UnifiedPush is available, it is used
    • If the app uses UnifiedPush and the user has many distributors, but hasn't selected a default yet (again, nearly never the case - the default is a OS wide default), a dialog is shown to introduce the OS screen.
  • Settings:
    • If a distributor is available, you have a new setting that allows to enable/disable UnifiedPush
    • If the user has many distributors (nearly never the case - but that means the user knows what UnifiedPush is!), a 2nd setting is offered, to change the distributor

This feature also gives the possibility to get Push Notifications with the Play Services, without the proxy (Nextcloud servers directly push to Google FCM servers), and without a proprietary library. So this is an accepted way to use FCM for application in F-Droid. => This is in a 2nd PR, already ready.

PS: The description is closed to Nextcloud PR, but the information apply here too

Fix #257

🖼️ Screenshots

🏚️ Before 🏡 After
image | image

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

@p1gp1g
Copy link
Copy Markdown
Contributor Author

p1gp1g commented Feb 18, 2026

I got a conflict with my own previous PR 🙈 -> I'm fixing it

@p1gp1g
Copy link
Copy Markdown
Contributor Author

p1gp1g commented Feb 18, 2026

Oh, and I've seen something strange during the implementation:

userManager.users.blockingGet() returns 2 users even if I'm connected with a single user. I've tested multiple time, from a fresh install and it always happen. The 2 get the same username, but different userId. Are you aware of that? Should I open an issue ? I think the duplication happens during the registration, but I'm not sure at all

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Feb 18, 2026

Thank you so much @p1gp1g i'm really looking forward to have UnifiedPush implemented 👍 👍 👍
We will take a close look as soon as time allows, it might not be this week though.

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Feb 18, 2026

Oh, and I've seen something strange during the implementation:

userManager.users.blockingGet() returns 2 users even if I'm connected with a single user. I've tested multiple time, from a fresh install and it always happen. The 2 get the same username, but different userId. Are you aware of that? Should I open an issue ? I think the duplication happens during the registration, but I'm not sure at all

Thanks for reporting! This sounds like #4499 so no need to create a new issue. So yes, we will have to take a closer look how this can happen during registration.

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Feb 25, 2026

i won't find the time this week. It's on my list though

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Mar 4, 2026

It's not forgotten. Will come back

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Mar 18, 2026

@p1gp1g please excuse the delay, we will have a closer look soon.
Are you willing to fix the merge conflicts?

@p1gp1g
Copy link
Copy Markdown
Contributor Author

p1gp1g commented Mar 18, 2026

Should be fast to fix the conflict, let me know when you start reviewing it so I haven't to rebase many times 👍

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Mar 19, 2026

Should be fast to fix the conflict, let me know when you start reviewing it so I haven't to rebase many times 👍

makes sense. i will let you know! thank you 👍

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 1, 2026

Hi @p1gp1g
due to upcoming vacations in plan to review this PR in CW16.

@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 13, 2026

I'm back. Planning to have a look this week

@p1gp1g p1gp1g force-pushed the unifiedpush branch 2 times, most recently from 793a768 to 1cf7a5e Compare April 13, 2026 13:58
Copy link
Copy Markdown
Collaborator

@mahibi mahibi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works like a charm, amazing job @p1gp1g 👍 💯

For now i mostly "just tested" and left some minor comments.
I will dive deeper into the code on monday

Comment thread app/src/main/res/values/strings.xml Outdated
Comment thread app/src/main/java/com/nextcloud/talk/models/json/push/Vapid.kt
@github-project-automation github-project-automation Bot moved this to 🏗️ In progress in 💬 Talk team Apr 17, 2026
Copy link
Copy Markdown
Collaborator

@mahibi mahibi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 💯
Thank you for the clear explanations in the code, that pretty much answered the questions i had 👍

I only added minor todos but it should be ready to merge then.

Please

rebase on latest master

and run ./gradlew ktlintFormat

which should already solve some warnings from

https://app.codacy.com/gh/nextcloud/talk-android/pull-requests/5883/issues

feel free to solve further warnings from codacy.

Looking forward to merge it!

Comment thread app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.kt Outdated
Comment thread app/build.gradle.kts
@mahibi mahibi self-assigned this Apr 22, 2026
@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 22, 2026

assigned myself just to keep an overview

@alauzon
Copy link
Copy Markdown

alauzon commented Apr 25, 2026

Strong support for this PR — sovereign self-hosting use case

I'm writing as a self-hoster operating multiple Nextcloud instances for community organizations in Quebec, Canada (4 production instances at the moment, plus more planned over the next year as I onboard new clients).

Why this PR matters for our use case:

Our entire infrastructure is built around the principle of technological sovereignty — no Google, no Apple, no proprietary cloud dependencies. Today, we have a hard wall: any client who wants Talk push notifications has to go through Firebase Cloud Messaging via the official Play Store APK. For organizations that explicitly chose Nextcloud to escape Big Tech surveillance, having to install a Google-tracked APK to receive notifications is a contradiction we can't ship to clients.

The current generic flavor (which we use) silently lacks notifications when Talk is closed/backgrounded — documented in the build notes but a hard blocker for production use.

What this PR enables for the broader ecosystem:

  • Self-hosters can pair Talk Android with their own UnifiedPush distributor (ntfy, gotify) — full data path under their control
  • Organizations subject to data sovereignty regulations (Quebec Bill 25, EU GDPR, healthcare contexts) get a compliant notification path
  • Anti-Google users (Linux phone users, /e/OS, GrapheneOS, LineageOS) finally have a working Talk experience

Concrete impact for me:

I'm currently fighting two flavor decisions per client onboarding: (1) gplay with notifications but with Google dependency, or (2) generic with full sovereignty but no real-time notifications. UnifiedPush eliminates this trade-off entirely — that's why it matters.

I want to thank @p1gp1g for the persistent work on this (44 commits since February!) and @mahibi for picking it up in review. If there's anything I can do to help — testing on my self-hosted setup with multiple HPB-enabled instances, providing logs from real usage scenarios, or anything else — please let me know.

Looking forward to seeing this merged. 🙏

p1gp1g added 16 commits April 30, 2026 16:01
…en UP is enabled

Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
…ltiple distrbutor on first run

Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
Signed-off-by: sim <git@sgougeon.fr>
@p1gp1g
Copy link
Copy Markdown
Contributor Author

p1gp1g commented Apr 30, 2026

I've rebased and force-push the branch, I'll do #5884 when this branch is merged then

@alauzon
Copy link
Copy Markdown

alauzon commented Apr 30, 2026

End-to-end test report: NC 34 beta 2 + ntfy + /e/OS Teracube — ✅ working

Following up on my previous comment. Now have a full end-to-end validation on a real-world self-hosted setup. Posting findings that may help other testers and the maintainers.

Setup

  • Server: Self-hosted Nextcloud 34.0.0 beta 2 (LXC, Debian 12, PHP 8.3)
  • Apps server-side:
    • notifications 7.0.0-dev.1 (shipped with NC 34 beta 2)
    • spreed master (24.0.0-dev.3) — installed manually via tarball + composer install + npm ci && npm run build (Node 20 with --engine-strict=false; master declares Node 24 but Node 20 builds with non-blocking warnings)
  • Push backend: self-hosted ntfy on push.serveursdupeuple.net, deployed 2026-04-26 specifically for this UnifiedPush use case
  • Client: Teracube 2e on /e/OS (Android 12, no Google Play Services, microG present)
  • Talk Android: APK from this PR's branch (after p1gp1g's recent rebase, version 24.0.0 Alpha 03, generic flavor)
  • UnifiedPush distributor: io.heckel.ntfy v1.24.0, configured with default server https://push.serveursdupeuple.net

🔍 Three findings worth documenting

Finding 1 — webpush_enabled is opt-in and not documented

The webpush capability is exposed only when two app config flags are set to true on the server:

occ config:app:set notifications webpush_enabled --value=true
occ config:app:set notifications webpush_browsers_enabled --value=true

Without these, /cloud/capabilities returns notifications.push without webpush and webpush-browsers, and the Android client (this PR) silently doesn't try to register for webpush. No user-facing error, just no push notifications.

This caused us a wasted afternoon on 2026-04-26 when we first tried this on NC 33 (concluding wrongly "NC 33 lacks the code"). Looking at Capabilities.php master:

if ($this->appConfig->getAppValueBool('webpush_enabled')) {
    $capabilities['notifications']['push'][] = 'webpush';
    if ($this->appConfig->getAppValueBool('webpush_browsers_enabled')) {
        $capabilities['notifications']['push'][] = 'webpush-browsers';
    }
}

→ It's opt-in by design. But:

Suggestion: at minimum document this prerequisite (a ## Prerequisites block in this PR's description would suffice). Better: what's the design intent — could webpush_enabled default to true for NC 34 stable? The migration runs anyway; defaulting to true would make this work out-of-the-box for everyone instead of silently failing.

Even better UX-wise: the Android client could surface a one-time admin-actionable hint when notifications.push arrives without webpush, e.g. "Web push not enabled on this server. Ask your admin to run occ config:app:set notifications webpush_enabled --value=true."

Finding 2 — foundation.e.ntfy 1.17.8-3 (preinstalled on /e/OS) is too old

/e/OS ships with foundation.e.ntfy 1.17.8-3 as the preinstalled UnifiedPush distributor. With only that distributor installed, Talk shows:

"Les services Google Play ne sont pas disponibles. Les notifications ne sont pas prises en charge."
("Google Play Services unavailable. Notifications not supported.")

The PackageManager confirms foundation.e.ntfy/io.heckel.ntfy.up.BroadcastReceiver is registered for org.unifiedpush.android.distributor.REGISTER, but Talk's distributor detection (or the UnifiedPush SDK it uses) seems to reject it — possibly an API-version mismatch.

Workaround: install io.heckel.ntfy v1.24.0 alongside (both distributors coexist). Then Talk shows two distributors in logs and successfully registers via the newer one:

UnifiedPush: Found distributor with package name foundation.e.ntfy
UnifiedPush: Found distributor with package name io.heckel.ntfy

Worth flagging this to the Murena team to update their foundation.e.ntfy package — but also worth ensuring this PR's distributor selection is robust against version mismatches in distributor implementations.

Finding 3 — spreed master required (no v23.x for NC 34)

spreed v23.x targets NC 33 (max-version="33" in its info.xml). For NC 34, you need spreed master (24.0.0-dev.3) which has no release tarball yet, so manual install:

cd /tmp
curl -sL https://github.com/nextcloud/spreed/archive/refs/heads/master.tar.gz -o spreed.tar.gz
tar xzf spreed.tar.gz && mv spreed-main /var/www/nextcloud/apps/spreed
cd /var/www/nextcloud/apps/spreed
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --no-interaction
npm ci --engine-strict=false  # Node 20+, master declares Node 24
npm run build --engine-strict=false
chown -R www-data:www-data .
sudo -u www-data php occ app:enable spreed --force

Until master gets a beta release tag, this is the only path forward for testing on NC 34.

✅ End-to-end validation — confirmed working

Database snapshot post-test (oc_notifications_webpush):

id uid endpoint app_types
1 admin https://fcm.googleapis.com/fcm/send/... all (Brave webpush W3C, FCM transport)
2 Test https://fcm.googleapis.com/fcm/send/... all (same)
3 admin https://push.serveursdupeuple.net/upU9ZGMjcl1ZYx?up=1 talk ⭐ UnifiedPush via self-hosted ntfy

Talk Android logs at registration time (logcat, abridged):

UnifiedPush: Found distributor with package name foundation.e.ntfy
UnifiedPush: Found distributor with package name io.heckel.ntfy
PushRegistrationWorker: PushRegistrationWorker called via UnifiedPushService#onActivationToken (webPushActivationWork)
PushRegistrationWorker: Activating web push for admin
--> POST https://NC.example.com/ocs/v2.php/apps/notifications/api/v2/webpush/activate
<-- 202 Accepted (138ms)
PushRegistrationWorker: Web push registration for admin activated
NtfyUpDistributor: Sending MESSAGE to com.nextcloud.talk2 (token=...): 2922 bytes

Real-world test — both directions work:

Direction A: desktop → Teracube

  1. Browser (Brave on desktop, logged as Test user) starts a Talk audio call to admin
  2. Teracube — Talk app fully closed (swiped from recents) — receives the push notification → phone rings
  3. Pick up on Teracube → audio call connects, bidirectional voice fine

Direction B: Teracube → desktop
4. Same scenario reversed: Talk Android (Teracube, generic flavor APK from this PR) initiates the call to Test (desktop browser)
5. Browser receives the call, picks up, audio works

Push routed entirely through push.serveursdupeuple.net (our self-hosted ntfy), no Google FCM in the path — exactly what self-hosters in sovereignty/compliance contexts have been waiting for. Note: WebRTC media path used the public stun.nextcloud.com:443 (no TURN configured on our side yet) and connected fine in both directions; our setup obviously can't speak to all NAT traversal scenarios but for the basic 1:1 call from Wi-Fi-connected devices it just works.

Why this matters for our use case

We operate Nextcloud for Quebec community organizations whose explicit mandate is sovereignty from Big Tech (Bill 25 compliance, anti-surveillance principles, /e/OS-friendly). Today we ship Talk gplay flavor → clients must install a Google-tracked APK. With this PR + the documentation fixes above, we can ship the generic flavor with full real-time push via our own ntfy backend. This is a real differentiator for self-hosters with sovereignty constraints.

Thanks for the persistent work since February and for picking up the review. Looking forward to seeing this merged.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 30, 2026

Thank you for testing @alauzon 👍
Please create separate github issues for any findings that require action.
As this is not yet officially released, documentation might indeed be lacking for now.

@mahibi mahibi merged commit f198a54 into nextcloud:master Apr 30, 2026
13 of 17 checks passed
@github-project-automation github-project-automation Bot moved this from 🏗️ In progress to ☑️ Done in 💬 Talk team Apr 30, 2026
@mahibi
Copy link
Copy Markdown
Collaborator

mahibi commented Apr 30, 2026

Thank you @p1gp1g
Amazing job 👍 👍 👍

@mahibi mahibi added this to the 24.0.0 milestone Apr 30, 2026
@alauzon
Copy link
Copy Markdown

alauzon commented Apr 30, 2026

🎉 Update — Scénario "patch master apps on NC 33" also works

Following up on my previous comment where I validated the full UnifiedPush chain end-to-end on NC 34 beta 2.

Today I tested whether the same approach works on existing NC 33.0.2 deployments without waiting for NC 34 stable (June 9, 2026).

Test environment

  • LXC 235 = exact zfs send | zfs recv clone of our prod cloud.entretablees.ca (NC 33.0.2)
  • Hostname: 34.et.serveursdupeuple.net
  • notifications 7.0.0 → master (7.0.0-dev.1)
  • spreed stock → master (24.0.0-dev.3)
  • Forced max-version="33" in both info.xml (master declares 34)
  • integrity.check.disabled=true (required, signed apps mismatch)
  • Both webpush_enabled=true and webpush_browsers_enabled=true set via occ

Result

"notifications": {
  "push": ["devices", "object-data", "delete", "webpush", "webpush-browsers"]
}
"spreed": { "version": "24.0.0-dev.3" }

✅ Capability webpush exposed on NC 33.0.2 with master apps. Same Teracube /e/OS + ntfy v1.24 + Talk Android PR #5883 build that worked on NC 34 beta 2 yesterday.

Why this matters

I had concluded on April 26 that NC 33 was incompatible with the UnifiedPush chain because the stock 33.x notifications app didn't ship the webpush code. That conclusion was wrong — the master branch can be backported to a 33.x install with only:

  1. composer install --no-dev on both apps
  2. npm ci && npm run build on spreed (Node 20, --engine-strict=false)
  3. sed max-version from 34 → 33 in info.xml
  4. Disable integrity check, app:enable --force
  5. Set the opt-in flag (still the trap from the previous comment)

So three viable paths now exist for NC operators wanting UnifiedPush before 34 stable:

Scenario Effort Risk When
A — wait for NC 34 stable None Near-zero June 9, 2026
B — patch master apps on NC 33 ~25 min + tests Moderate (unmanaged apps until 34 upgrade) Now
C — stay on FCM None Doctrinal incompatibility for sovereignty-focused deployments n/a

Open questions for the maintainers

These would be useful to resolve before this PR merges and operators start patching prod instances:

  • Stability/sysadmin: Are there known regressions in master notifications 7.0.0-dev.1 + spreed 24.0.0-dev.3 that would make Scenario B unsafe for prod? Anything in the migration path I should warn about?
  • WebRTC: Audio call worked end-to-end on NC 34 beta 2 (Brave desktop ↔ Talk Android Teracube without TURN configured). Is there a recommended STUN/TURN config for federated NC 33 deployments using this chain?
  • Test strategy: Is there a regression test suite I can run on a patched NC 33 to confirm no breakage in non-Talk paths (Files share provider was the gotcha last time — oc_talk_attendees table missing)?
  • Documentation: Would you accept a doc PR pointing operators to the webpush_enabled opt-in flag? Right now the only path to discover it is reading Capabilities.php source. This is the single biggest blocker I encountered, on both NC 33 and NC 34 beta 2.

Happy to provide ZFS snapshots, exact occ outputs, capability JSON dumps, or test on additional NC 33 minor versions if it helps the merge.


Context: Serveurs du Peuple is a Quebec-based Nextcloud hosting collective with an explicit "anti-FCM" doctrine for mobile push (sovereignty + Five Eyes avoidance). UnifiedPush via self-hosted ntfy is our target architecture for all Android users. This PR is the missing piece — thank you @p1gp1g for the work.

@AndyScherzinger
Copy link
Copy Markdown
Member

AndyScherzinger commented Apr 30, 2026

@p1gp1g what are the things you'd be looking for feedback? As for backport ingredients to 33, we'd reject that. We typically don't backports features except in very rare cases and we wouldn't consider this one. Also but not limited to the fact that it is uncommon for Nextcloud. So most of operating parties expect that there is no (larger) behavioral change when upgrading maintenance releases within the same major release version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews enhancement New feature or request feedback-requested

Projects

Status: ☑️ Done

Development

Successfully merging this pull request may close these issues.

Support notifications for f-droid version

4 participants