aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/platform.js16
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/push.js68
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/settings-page.js41
14 files changed, 224 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index b9466d0..2254a11 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -31,6 +31,7 @@ import {
} from './playlists.js';
import { IndexingDock } from './index-dock.js';
import { SettingsPage } from './settings-page.js';
+import { syncPush, disablePush } from './push.js';
import { ProfilePage } from './profile-page.js';
import { ExplorePage } from './explore-page.js';
import { GroupName } from './group-name.js';
@@ -1042,6 +1043,7 @@ function App() {
.catch(() => {});
refreshNodeKey();
fetchNotifications();
+ syncPush(user).catch(() => {});
}, [user]);
// The node this application ships, set up, started and linked for whoever
@@ -1233,6 +1235,13 @@ function App() {
// Navigating away leaves transfers running; signing out does not. They
// are moving data on tokens that are about to stop being ours.
transfers.reset();
+ // This phone stops being told about the account it is leaving. Its
+ // access token is still good for that request; the refresh token's
+ // revocation below does not touch it.
+ // The stored session, read now: React's copy can be a renewal behind,
+ // and once the session is cleared nothing could renew it.
+ disablePush(user && { ...user, token: (loadAuth() || {}).token || user.token })
+ .catch(() => {});
// Revoked on the hub too, so a copy of the refresh token is worth
// nothing. Read before the next line clears it.
logoutOnHub();
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 4dff605..d8b7b5c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -436,6 +436,15 @@ export default {
'settings.email_verified': 'E-Mail-Adresse erfolgreich geändert.',
'settings.notif_global_disable': 'Disable all notifications',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'Benachrichtigungen auf diesem Telefon',
+ 'settings.push_off': 'Aus. Einschalten, um bei geschlossenem MeshBay über neue Nachrichten und Einladungen informiert zu werden.',
+ 'settings.push_pending': 'Warte auf die Antwort des Verteilers…',
+ 'settings.push_blocked': 'Android blockiert die Benachrichtigungen von MeshBay: Erlauben Sie sie in den Systemeinstellungen.',
+ 'settings.push_ready': 'An. Neue Benachrichtigungen erreichen dieses Telefon über den Verteiler, verschlüsselt, sodass er sie nicht lesen kann.',
+ 'settings.push_poll': 'An. Dieses Telefon sieht etwa alle fünfzehn Minuten nach neuen Benachrichtigungen. Damit sie sofort ankommen, können Sie eine UnifiedPush-Verteiler-App wie ntfy installieren (optional).',
+ 'settings.push_global_off': 'Solange alle Benachrichtigungen ausgeschaltet sind, wird nichts gesendet.',
+ 'push.channel_chat': 'Nachrichten',
+ 'push.channel_other': 'Einladungen und Konto',
'settings.defaults': 'Standardwerte',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 90c043e..a4a8215 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -433,6 +433,15 @@ export default {
'settings.email_verified': 'Email address changed successfully.',
'settings.notif_global_disable': 'Disable all notifications',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'Notifications on this phone',
+ 'settings.push_off': 'Off. Turn on to be told about new messages and invitations while MeshBay is closed.',
+ 'settings.push_pending': 'Waiting for the distributor’s answer…',
+ 'settings.push_blocked': 'Android is blocking MeshBay’s notifications: allow them in the system settings.',
+ 'settings.push_ready': 'On. New notifications reach this phone through the distributor, encrypted so it cannot read them.',
+ 'settings.push_poll': 'On. This phone checks for new notifications about every fifteen minutes. For them to arrive at once, you can install a UnifiedPush distributor app such as ntfy (optional).',
+ 'settings.push_global_off': 'Nothing is sent while every notification is turned off.',
+ 'push.channel_chat': 'Messages',
+ 'push.channel_other': 'Invitations and account',
'settings.defaults': 'Defaults',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 7b46b17..78160d4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -434,6 +434,15 @@ export default {
'settings.email_verified': 'Dirección de correo cambiada con éxito.',
'settings.notif_global_disable': 'Disable all notifications',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'Notificaciones en este teléfono',
+ 'settings.push_off': 'Desactivadas. Actívalas para enterarte de nuevos mensajes e invitaciones con MeshBay cerrado.',
+ 'settings.push_pending': 'Esperando la respuesta del distribuidor…',
+ 'settings.push_blocked': 'Android bloquea las notificaciones de MeshBay: permítelas en los ajustes del sistema.',
+ 'settings.push_ready': 'Activadas. Las nuevas notificaciones llegan a este teléfono a través del distribuidor, cifradas para que no pueda leerlas.',
+ 'settings.push_poll': 'Activadas. Este teléfono busca notificaciones nuevas cada quince minutos aproximadamente. Para recibirlas al instante, puedes instalar una app distribuidora de UnifiedPush como ntfy (opcional).',
+ 'settings.push_global_off': 'No se envía nada mientras todas las notificaciones estén desactivadas.',
+ 'push.channel_chat': 'Mensajes',
+ 'push.channel_other': 'Invitaciones y cuenta',
'settings.defaults': 'Valores predeterminados',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index d3f9200..1fbb6cf 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -435,6 +435,15 @@ export default {
'settings.email_verified': 'Adresse e-mail modifiée avec succès.',
'settings.notif_global_disable': 'Désactiver toutes les notifications',
'settings.notif_global_hint': 'Quand activé, aucune notification n\x27est créée pour aucun groupe.',
+ 'settings.push_label': 'Notifications sur ce téléphone',
+ 'settings.push_off': 'Désactivées. Activez-les pour être prévenu des nouveaux messages et invitations quand MeshBay est fermé.',
+ 'settings.push_pending': 'En attente de la réponse du distributeur…',
+ 'settings.push_blocked': 'Android bloque les notifications de MeshBay : autorisez-les dans les paramètres du système.',
+ 'settings.push_ready': 'Activées. Les nouvelles notifications arrivent sur ce téléphone par le distributeur, chiffrées pour qu’il ne puisse pas les lire.',
+ 'settings.push_poll': 'Activées. Ce téléphone vérifie les nouvelles notifications environ toutes les quinze minutes. Pour les recevoir immédiatement, vous pouvez installer une application de distribution UnifiedPush comme ntfy (facultatif).',
+ 'settings.push_global_off': 'Rien n’est envoyé tant que toutes les notifications sont désactivées.',
+ 'push.channel_chat': 'Messages',
+ 'push.channel_other': 'Invitations et compte',
'settings.defaults': 'Valeurs par défaut',
'settings.default_tab': 'Onglet par défaut',
'settings.default_tab_hint': 'L\'onglet qui s\'ouvre en premier quand vous entrez dans un groupe.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 715738e..250e769 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -435,6 +435,15 @@ export default {
'settings.email_verified': 'Indirizzo e-mail modificato con successo.',
'settings.notif_global_disable': 'Disable all notifications',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'Notifiche su questo telefono',
+ 'settings.push_off': 'Disattivate. Attivale per essere avvisato di nuovi messaggi e inviti quando MeshBay è chiuso.',
+ 'settings.push_pending': 'In attesa della risposta del distributore…',
+ 'settings.push_blocked': 'Android blocca le notifiche di MeshBay: consentile nelle impostazioni di sistema.',
+ 'settings.push_ready': 'Attivate. Le nuove notifiche arrivano su questo telefono tramite il distributore, cifrate in modo che non possa leggerle.',
+ 'settings.push_poll': 'Attivate. Questo telefono controlla le nuove notifiche circa ogni quindici minuti. Per riceverle subito, puoi installare un’app distributore UnifiedPush come ntfy (facoltativo).',
+ 'settings.push_global_off': 'Non viene inviato nulla finché tutte le notifiche sono disattivate.',
+ 'push.channel_chat': 'Messaggi',
+ 'push.channel_other': 'Inviti e account',
'settings.defaults': 'Valori predefiniti',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index 23358c2..ad4ba25 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -432,6 +432,15 @@ export default {
'settings.email_verified': 'メールアドレスが正常に変更されました。',
'settings.notif_global_disable': 'Disable all notifications',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'この端末の通知',
+ 'settings.push_off': 'オフ。オンにすると、MeshBay を閉じていても新着メッセージや招待が通知されます。',
+ 'settings.push_pending': 'ディストリビューターの応答を待っています…',
+ 'settings.push_blocked': 'Android が MeshBay の通知をブロックしています。システム設定で許可してください。',
+ 'settings.push_ready': 'オン。新しい通知はディストリビューター経由でこの端末に届きます。暗号化されているため、ディストリビューターは内容を読めません。',
+ 'settings.push_poll': 'オン。この端末は約15分ごとに新しい通知を確認します。すぐに受け取りたい場合は、ntfy などの UnifiedPush ディストリビューターアプリをインストールできます(任意)。',
+ 'settings.push_global_off': 'すべての通知がオフの間は何も送信されません。',
+ 'push.channel_chat': 'メッセージ',
+ 'push.channel_other': '招待とアカウント',
'settings.defaults': 'デフォルト',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 5e53084..78bada9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -436,6 +436,15 @@ export default {
'settings.email_verified': 'E-mailadres succesvol gewijzigd.',
'settings.notif_global_disable': 'Alle meldingen uitschakelen',
'settings.notif_global_hint': 'When enabled, no notifications are created for any group.',
+ 'settings.push_label': 'Meldingen op deze telefoon',
+ 'settings.push_off': 'Uit. Zet aan om over nieuwe berichten en uitnodigingen te horen terwijl MeshBay gesloten is.',
+ 'settings.push_pending': 'Wachten op het antwoord van de distributor…',
+ 'settings.push_blocked': 'Android blokkeert de meldingen van MeshBay: sta ze toe in de systeeminstellingen.',
+ 'settings.push_ready': 'Aan. Nieuwe meldingen bereiken deze telefoon via de distributor, versleuteld zodat die ze niet kan lezen.',
+ 'settings.push_poll': 'Aan. Deze telefoon kijkt ongeveer elk kwartier of er nieuwe meldingen zijn. Wil je ze meteen ontvangen, installeer dan een UnifiedPush-distributor-app zoals ntfy (optioneel).',
+ 'settings.push_global_off': 'Er wordt niets verzonden zolang alle meldingen uit staan.',
+ 'push.channel_chat': 'Berichten',
+ 'push.channel_other': 'Uitnodigingen en account',
'settings.defaults': 'Standaardwaarden',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 5d6d852..fd26974 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -447,6 +447,15 @@ export default {
'settings.email_verified': 'Adres e-mail został pomyślnie zmieniony.',
'settings.notif_global_disable': 'Wyłącz wszystkie powiadomienia',
'settings.notif_global_hint': 'Po włączeniu nie będą tworzone żadne powiadomienia dla żadnej grupy.',
+ 'settings.push_label': 'Powiadomienia na tym telefonie',
+ 'settings.push_off': 'Wyłączone. Włącz, aby dowiadywać się o nowych wiadomościach i zaproszeniach, gdy MeshBay jest zamknięty.',
+ 'settings.push_pending': 'Oczekiwanie na odpowiedź dystrybutora…',
+ 'settings.push_blocked': 'Android blokuje powiadomienia MeshBay: zezwól na nie w ustawieniach systemu.',
+ 'settings.push_ready': 'Włączone. Nowe powiadomienia docierają na ten telefon przez dystrybutora, zaszyfrowane tak, że nie może ich odczytać.',
+ 'settings.push_poll': 'Włączone. Ten telefon sprawdza nowe powiadomienia mniej więcej co piętnaście minut. Aby otrzymywać je od razu, możesz zainstalować aplikację dystrybutora UnifiedPush, np. ntfy (opcjonalnie).',
+ 'settings.push_global_off': 'Nic nie jest wysyłane, dopóki wszystkie powiadomienia są wyłączone.',
+ 'push.channel_chat': 'Wiadomości',
+ 'push.channel_other': 'Zaproszenia i konto',
'settings.defaults': 'Wartości domyślne',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index 6fe50f6..e91ab10 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -436,6 +436,15 @@ export default {
'settings.email_verified': 'Endereço de e-mail alterado com sucesso.',
'settings.notif_global_disable': 'Desativar todas as notificações',
'settings.notif_global_hint': 'Quando ativado, nenhuma notificação é criada para nenhum grupo.',
+ 'settings.push_label': 'Notificações neste telefone',
+ 'settings.push_off': 'Desativadas. Ative para saber de novas mensagens e convites com o MeshBay fechado.',
+ 'settings.push_pending': 'Aguardando a resposta do distribuidor…',
+ 'settings.push_blocked': 'O Android está bloqueando as notificações do MeshBay: permita-as nas configurações do sistema.',
+ 'settings.push_ready': 'Ativadas. Novas notificações chegam a este telefone pelo distribuidor, criptografadas para que ele não possa lê-las.',
+ 'settings.push_poll': 'Ativadas. Este telefone verifica novas notificações a cada quinze minutos, aproximadamente. Para recebê-las na hora, você pode instalar um app distribuidor UnifiedPush como o ntfy (opcional).',
+ 'settings.push_global_off': 'Nada é enviado enquanto todas as notificações estiverem desativadas.',
+ 'push.channel_chat': 'Mensagens',
+ 'push.channel_other': 'Convites e conta',
'settings.defaults': 'Padrões',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index b3886ff..c030523 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -428,6 +428,15 @@ export default {
'settings.email_verified': '邮箱地址修改成功。',
'settings.notif_global_disable': '关闭所有通知',
'settings.notif_global_hint': '启用后,所有群组都不会创建通知。',
+ 'settings.push_label': '此手机上的通知',
+ 'settings.push_off': '已关闭。开启后,即使 MeshBay 已关闭,也会收到新消息和邀请的通知。',
+ 'settings.push_pending': '正在等待分发应用的响应…',
+ 'settings.push_blocked': 'Android 正在阻止 MeshBay 的通知:请在系统设置中允许。',
+ 'settings.push_ready': '已开启。新通知通过分发应用送达此手机,并经过加密,分发应用无法读取。',
+ 'settings.push_poll': '已开启。此手机大约每十五分钟检查一次新通知。如需即时收到,可以安装 UnifiedPush 分发应用,例如 ntfy(可选)。',
+ 'settings.push_global_off': '所有通知关闭期间不会发送任何内容。',
+ 'push.channel_chat': '消息',
+ 'push.channel_other': '邀请与账户',
'settings.defaults': '默认值',
'settings.default_tab': 'Default tab',
'settings.default_tab_hint': 'Which tab opens first when you enter a group.',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
index e3b3d2d..c70b03a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
@@ -214,6 +214,22 @@ export const playback = {
},
};
+/**
+ * Notifications on this device while the application is closed — fetched, or
+ * pushed through a UnifiedPush distributor when the phone has one. Only the
+ * Android application does this; `available` is false in a browser and on a
+ * desktop, and nothing here is then called. See push.js.
+ */
+export const push = {
+ available: Boolean(bridge && bridge.push),
+ status() { return bridge.push.status(); },
+ enable() { return bridge.push.enable(); },
+ disable() { return bridge.push.disable(); },
+ remember(subscription, account, secret, since) {
+ return bridge.push.remember(subscription, account, secret, since);
+ },
+};
+
/** Pick a directory to add as a group root. Returns { path, name } or null. */
export const rootPicker = {
available: Boolean(bridge && bridge.rootPicker),
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/push.js b/packages/meshbay-hub/src/meshbay_hub/static/push.js
new file mode 100644
index 0000000..a075aa4
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/push.js
@@ -0,0 +1,68 @@
+/**
+ * Notifications on this phone: the page's half.
+ *
+ * Nothing to install: the shell fetches what is new on its own every quarter
+ * of an hour, and when the phone already has a UnifiedPush distributor it is
+ * used too, so they arrive at once. The page gives the hub whatever the shell
+ * ends up with, because the page holds the session. Whether a notification is
+ * wanted — every one turned off, or a group muted — is decided on the hub,
+ * which then creates nothing (docs/MESHBAY_DESIGN.md §11.3).
+ */
+import * as platform from './platform.js';
+import { hubFetch } from './hub-client.js';
+
+// How long turning it on waits for a distributor before registering without
+// one; when the endpoint comes later, the next sync hands it over.
+const WAIT_MS = 10_000;
+const POLL_MS = 500;
+
+/**
+ * Make the hub's row match what this phone has: at every start and sign-in,
+ * and after turning it on. A distributor may hand out a new endpoint, or go
+ * away, at any time, page running or not; this is where the hub hears it.
+ */
+export async function syncPush(user) {
+ if (!platform.push.available || !user) return null;
+ const s = await platform.push.status();
+ if (!s.enabled) return s;
+ const mine = s.subscription && s.account === user.userId;
+ if (mine && s.registered === s.endpoint) return s;
+ const body = s.endpoint
+ ? { endpoint: s.endpoint, p256dh: s.p256dh, auth: s.auth }
+ : {};
+ if (mine) body.id = s.subscription;
+ const r = await hubFetch('/v1/push/subscriptions', {
+ method: 'POST', token: user.token, body,
+ });
+ return platform.push.remember(r.id, user.userId, r.poll_secret, r.now);
+}
+
+/** Turn it on: a moment for a distributor to answer, then register either way. */
+export async function enablePush(user, onProgress) {
+ let s = await platform.push.enable();
+ const until = Date.now() + WAIT_MS;
+ while (s.state === 'pending' && Date.now() < until) {
+ if (onProgress) onProgress(s);
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
+ s = await platform.push.status();
+ }
+ return syncPush(user).then((synced) => synced || s);
+}
+
+/**
+ * Turn it off: the hub forgets this phone first, then the phone stops. The hub
+ * half is the one that can fail; the phone half happens regardless, so a
+ * refused request never leaves it on here.
+ */
+export async function disablePush(user) {
+ if (!platform.push.available) return null;
+ const s = await platform.push.status();
+ if (s.subscription && user && s.account === user.userId) {
+ try {
+ await hubFetch(`/v1/push/subscriptions/${s.subscription}`, {
+ method: 'DELETE', token: user.token,
+ });
+ } catch (e) { /* already gone, or unreachable: the phone half still happens */ }
+ }
+ return platform.push.disable();
+}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js b/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
index 8755556..985b219 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
@@ -5,6 +5,7 @@ import { t, getLocale, setLocale, LOCALES } from './i18n.js';
import * as downloads from './downloads.js';
import * as platform from './platform.js';
import { hubFetch } from './hub-client.js';
+import { syncPush, enablePush, disablePush } from './push.js';
import { APPS } from './apps.js';
import {
PAGE_SIZE_PREF, PAGE_SIZE_DEFAULT, PAGE_SIZE_STEP, PAGE_SIZE_MAX, pageSizeFrom,
@@ -81,6 +82,35 @@ export function SettingsPage({ user, theme, onThemeChange, groups, onPrefsChange
}
}, [globalMute, user.token, onPrefsChange]);
+ // Notifications on this phone (Android only: `platform.push.available`).
+ const [push, setPush] = useState(null);
+ const [pushBusy, setPushBusy] = useState(false);
+ useEffect(() => {
+ if (!platform.push.available) return;
+ syncPush(user).catch(() => platform.push.status()).then(setPush).catch(() => {});
+ }, [user]);
+
+ const togglePush = useCallback(async () => {
+ if (pushBusy) return;
+ setPushBusy(true);
+ try {
+ setPush(push && push.enabled ? await disablePush(user) : await enablePush(user, setPush));
+ } catch (err) {
+ setPush(await platform.push.status().catch(() => null));
+ } finally {
+ setPushBusy(false);
+ }
+ }, [push, pushBusy, user]);
+
+ const pushHint = () => {
+ if (!push) return null;
+ if (globalMute) return t('settings.push_global_off');
+ if (!push.enabled) return t('settings.push_off');
+ if (!push.permitted) return t('settings.push_blocked');
+ if (push.state === 'pending') return t('settings.push_pending');
+ return push.endpoint ? t('settings.push_ready') : t('settings.push_poll');
+ };
+
const toggleMute = useCallback(async (gid) => {
const next = !muted[gid];
setMuted(prev => ({ ...prev, [gid]: next }));
@@ -240,6 +270,17 @@ export function SettingsPage({ user, theme, onThemeChange, groups, onPrefsChange
<span class="toggle-switch-track"><span class="toggle-switch-thumb"></span></span>
</label>
</div>
+ ${push && html`
+ <div class="settings-row">
+ <span class="settings-label">${t('settings.push_label')}</span>
+ <label class="toggle-switch">
+ <input type="checkbox" checked=${!!push.enabled} disabled=${pushBusy}
+ onChange=${togglePush} />
+ <span class="toggle-switch-track"><span class="toggle-switch-thumb"></span></span>
+ </label>
+ </div>
+ <p class="settings-hint">${pushHint()}</p>
+ `}
${!globalMute && html`
<p class="settings-hint" style="margin-bottom:8px">${t('settings.notif_global_hint')}</p>
${groups.map(g => html`