diff --git a/apps/example-sender/src/lib/relay.remote.ts b/apps/example-sender/src/lib/relay.remote.ts index 61fb6f1..ac926e6 100644 --- a/apps/example-sender/src/lib/relay.remote.ts +++ b/apps/example-sender/src/lib/relay.remote.ts @@ -6,7 +6,6 @@ import { command, getRequestEvent } from '$app/server'; import { error } from '@sveltejs/kit'; import * as v from 'valibot'; -import { APP_DOMAIN } from '$lib/config'; import { listNotificationsForUser, markAllReadForUser, @@ -60,7 +59,9 @@ export const sendTest = command( recipient: locals.did, title: title?.trim() || 'Hello from the example sender', body: body?.trim() || 'If you can read this, the integration works end to end.', - uri: `https://${APP_DOMAIN}`, + // A cross-site URL so PWA inbox links open in the default browser (an + // atmo.pub subdomain would be treated as same-site and stay in-app on iOS). + uri: 'https://bsky.app', category: category?.trim() || undefined }); return { ok: true, ...result }; diff --git a/apps/web/src/routes/(app)/inbox/+page.svelte b/apps/web/src/routes/(app)/inbox/+page.svelte index 11c2bbe..203cd9f 100644 --- a/apps/web/src/routes/(app)/inbox/+page.svelte +++ b/apps/web/src/routes/(app)/inbox/+page.svelte @@ -1,4 +1,5 @@ Inbox · atmo.pub @@ -75,33 +92,44 @@ {/if} + +{#snippet item(n: PageData['notifications'][number])} + +
+
+ {n.title} + {#if !n.read} + + {/if} +
+ {#if n.body} +

{n.body}

+ {/if} +
+ + {#if n.category}· {n.category}{/if} +
+
+{/snippet} diff --git a/apps/web/src/routes/go/+page.svelte b/apps/web/src/routes/go/+page.svelte new file mode 100644 index 0000000..7c13209 --- /dev/null +++ b/apps/web/src/routes/go/+page.svelte @@ -0,0 +1,56 @@ + + +Open link · atmo.pub + +
+ {#if valid} +
+

Continue to

+

{host}

+
+ + Open in browser + + Back to inbox + {:else} +

That link looks invalid.

+ Back to inbox + {/if} +
diff --git a/apps/web/src/service-worker.ts b/apps/web/src/service-worker.ts index 24c75e3..ec97984 100644 --- a/apps/web/src/service-worker.ts +++ b/apps/web/src/service-worker.ts @@ -68,17 +68,25 @@ sw.addEventListener('push', (event) => { ); }); -// Focus an existing tab (navigating it to the notification's link) or open one. +// A click goes to the PWA (the SW can only open in-scope pages). For an external +// link we open the in-scope `/go` handoff page, which then bounces to the user's +// default browser (a PWA can't render an out-of-scope URL); links to nowhere just +// open the inbox. Routing through `/go` also keeps `client.navigate` same-origin +// (it can't navigate an existing window to a cross-origin URL). sw.addEventListener('notificationclick', (event) => { event.notification.close(); const uri = (event.notification.data as { uri?: string } | null)?.uri; - const target = uri && /^https?:/.test(uri) ? uri : new URL('/inbox', sw.location.origin).href; + const origin = sw.location.origin; + const target = + uri && /^https?:/.test(uri) + ? new URL(`/go?to=${encodeURIComponent(uri)}`, origin).href + : new URL('/inbox', origin).href; event.waitUntil( (async () => { const clients = await sw.clients.matchAll({ type: 'window', includeUncontrolled: true }); for (const client of clients) { await client.focus(); - if (uri) await client.navigate(target); + await client.navigate(target); return; } await sw.clients.openWindow(target);