aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/features/messages.js17
-rw-r--r--src/features/nwc.js44
-rw-r--r--src/i18n.js3
3 files changed, 55 insertions, 9 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index e1f3ea3..b12efa7 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -1278,6 +1278,10 @@ export function messagesFeature(ctx) {
const { publicKey } = await (await fetch(`${NOTIFIER}/vapid`)).json();
const wantKey = b64ToBytes(publicKey);
let sub = await reg.pushManager.getSubscription();
+ // see nwc.js: a Chromium without Google Play Services (GrapheneOS, and
+ // any de-Googled Android) has no push service at all, and says so in a
+ // way nobody can act on. Remember it so the offer can explain itself.
+ const noPushService = (e) => /push service|Registration failed|AbortError|NotSupportedError/i.test((e && (e.message || e.name)) || '');
// A subscription made against a DIFFERENT VAPID key can never receive a
// push — the notifier's sends fail with a permanent 403 and it keeps
// reusing the dead sub, so the device goes silent forever (this is what
@@ -1291,8 +1295,14 @@ export function messagesFeature(ctx) {
};
if (sub && !keyMatches(sub)) { try { await sub.unsubscribe(); } catch {} sub = null; }
if (!sub) {
- sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: wantKey });
+ try {
+ sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: wantKey });
+ } catch (e) {
+ if (noPushService(e)) { const s2 = st(); s2.noPushService = true; save(s2); render(); }
+ return false;
+ }
}
+ { const s2 = st(); if (s2.noPushService) { delete s2.noPushService; save(s2); } }
const r = await fetch(`${NOTIFIER}/register`, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ subscription: sub.toJSON(), notify: pushWatch() }),
@@ -3969,6 +3979,11 @@ export function messagesFeature(ctx) {
class: 'btn-ghost btn-sm',
onClick: () => { const s = st(); s.pushDismissed = true; save(s); render(); },
}, t('msgDismiss')))));
+ // A browser with no push service at all: the offer above is beside the
+ // point (permission may even be granted), so say what's actually wrong
+ // wherever the user got to.
+ if (st().noPushService && !st().push)
+ kids.push(h('div', { class: 'notice info small' }, t('nwcNoPushService')));
// ---- the feed, above the conversations: it's the thing you read, they're
// the things you answer
diff --git a/src/features/nwc.js b/src/features/nwc.js
index 45bbbf5..077b53e 100644
--- a/src/features/nwc.js
+++ b/src/features/nwc.js
@@ -584,18 +584,39 @@ export function nwcFeature(ctx) {
return Uint8Array.from(raw, (c) => c.charCodeAt(0));
};
+ // Waking a closed app needs a push service, and on Android that means the
+ // browser's: Chrome (and every Chromium, including GrapheneOS's Vanadium)
+ // routes web push through Firebase, which isn't there on a phone without
+ // Google Play Services. The browser says so as "Registration failed - push
+ // service error", which tells the user nothing. Translate it, and name the
+ // thing that does work.
+ const isNoPushService = (e) => {
+ const m = (e && (e.message || e.name)) || '';
+ return /push service|Registration failed|AbortError|NotSupportedError|permission denied/i.test(m);
+ };
+
async function enableBackground() {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
- throw new Error('this browser cannot receive push');
+ throw new Error(t('nwcNoPushHere'));
}
const perm = await Notification.requestPermission();
- if (perm !== 'granted') throw new Error('notifications were not allowed');
+ if (perm !== 'granted') throw new Error(t('nwcNoPermission'));
const reg = await navigator.serviceWorker.ready;
const { publicKey } = await (await fetch(`${NOTIFIER}/vapid`)).json();
- const sub = await reg.pushManager.subscribe({
- userVisibleOnly: true,
- applicationServerKey: b64ToBytes(publicKey),
- });
+ let sub;
+ try {
+ sub = await reg.pushManager.subscribe({
+ userVisibleOnly: true,
+ applicationServerKey: b64ToBytes(publicKey),
+ });
+ } catch (e) {
+ if (isNoPushService(e)) {
+ const st2 = load(); st2.pushUnavailable = true; save(st2);
+ throw new Error(t('nwcNoPushService'));
+ }
+ throw e;
+ }
+ { const st2 = load(); if (st2.pushUnavailable) { delete st2.pushUnavailable; save(st2); } }
const pks1 = conns().map((c) => c.servicePk);
if (load().offer) pks1.push(load().offer.pk);
// Arming can precede the first connection (creation requires it) — the
@@ -807,7 +828,11 @@ export function nwcFeature(ctx) {
: h('button', { class: 'btn-sm', onClick: () => { ui.nwcShow = c.id; render(); } }, t('nwcShow')));
}),
list.length
- ? h('div', { class: 'row between', style: 'border-top:1px solid var(--border,rgba(128,128,128,.2));padding-top:8px' },
+ ? h('div', { class: 'col', style: 'gap:6px;border-top:1px solid var(--border,rgba(128,128,128,.2));padding-top:8px' },
+ load().pushUnavailable && !load().background
+ ? h('div', { class: 'small faint' }, t('nwcNoPushService'))
+ : null,
+ h('div', { class: 'row between' },
h('span', { class: 'small' }, t('nwcBackground')),
h('button', { class: 'btn-sm', onClick: async () => {
try {
@@ -817,6 +842,9 @@ export function nwcFeature(ctx) {
const s = load(); s.backgroundOff = true; save(s);
toast(t('nwcBackgroundOff'));
} else {
+ // an explicit tap always retries: a phone that grew a push
+ // service (or a different browser) deserves another go
+ const s0 = load(); delete s0.pushUnavailable; save(s0);
await enableBackground();
const s = load(); delete s.backgroundOff; save(s);
await reconcileBg();
@@ -824,7 +852,7 @@ export function nwcFeature(ctx) {
}
} catch (e) { toast(e.message); }
render();
- } }, load().background ? t('nwcOn') : t('nwcOff')))
+ } }, load().background ? t('nwcOn') : t('nwcOff'))))
: null,
st
? h('div', { class: 'col', style: 'gap:6px;border-top:1px solid var(--border,rgba(128,128,128,.2));padding-top:8px' },
diff --git a/src/i18n.js b/src/i18n.js
index 4121df1..38836d2 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -852,6 +852,9 @@ const STR = {
nwcTabWarning: 'Works while Coinos is open — turn on background answering below to keep working when it isn’t.',
nwcAdd: 'Add a connection',
nwcBackground: 'Answer while closed',
+ nwcNoPushHere: 'This browser can\u2019t receive push messages.',
+ nwcNoPermission: 'Notifications weren\u2019t allowed.',
+ nwcNoPushService: 'This browser has no push service, so it can\u2019t be woken while closed. On Android that\u2019s usually a phone without Google Play Services \u2014 Firefox brings its own push and works there. Zaps are still answered whenever coinos is open.',
nwcOn: 'On',
nwcOff: 'Off',
nwcBackgroundOn: 'Requests will be answered in the background',