Fix password reset landing on the feed; show who is going on feed cards
This commit is contained in:
parent
42249b6046
commit
e34648b5af
4 changed files with 1267 additions and 1 deletions
1034
_expo/static/js/web/entry-9f9c184d0d0f667c1a13ee2c5fb624d0.js
Normal file
1034
_expo/static/js/web/entry-9f9c184d0d0f667c1a13ee2c5fb624d0.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -32,6 +32,6 @@
|
|||
</noscript>
|
||||
<!-- The root element for your Expo app. -->
|
||||
<div id="root"></div>
|
||||
<script src="/_expo/static/js/web/entry-bdf450540d0a1dff60e59848513ca861.js" defer></script>
|
||||
<script src="/_expo/static/js/web/entry-9f9c184d0d0f667c1a13ee2c5fb624d0.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
164
sw.js
Normal file
164
sw.js
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/* Pull Up service worker.
|
||||
*
|
||||
* HOW THIS FILE GETS SHIPPED, because the next person will look for a build
|
||||
* step and there isn't one:
|
||||
*
|
||||
* `app/public/` is Expo's public folder. `npx expo export --platform web`
|
||||
* copies it verbatim into `dist/` (@expo/cli export/exportApp.js calls
|
||||
* copyPublicFolderAsync before it writes anything else), and `npx expo start
|
||||
* --web` serves it from the dev server through ServeStaticMiddleware. So this
|
||||
* file lands at `dist/sw.js` and is served at `/sw.js` on both.
|
||||
*
|
||||
* That path matters. A service worker's default scope is the directory it was
|
||||
* served from, so a worker at `/sw.js` controls `/` and a worker tucked under
|
||||
* `/_expo/` or `/assets/` would control only that subtree and would never see
|
||||
* a push aimed at the app. Nothing here needs `Service-Worker-Allowed`
|
||||
* precisely because it sits at the root already.
|
||||
*
|
||||
* It is NOT bundled by Metro. It never imports from `app/lib/*` and it cannot:
|
||||
* a service worker is a separate JS realm with no DOM, no React, and no
|
||||
* module graph in common with the app. Everything below is plain ES2020 that
|
||||
* runs as-is.
|
||||
*
|
||||
* WHAT IT DOES, AND DELIBERATELY DOES NOT DO
|
||||
*
|
||||
* It does not cache anything. This is not an offline app -- it is a live feed of
|
||||
* who is playing in the next two hours, and a stale cached bundle is a worse
|
||||
* outcome than a spinner. A push receiver is the entire job.
|
||||
*/
|
||||
|
||||
/* Take over as soon as a new copy is installed. Without these two, a returning
|
||||
* user keeps whatever worker their tab already had until every tab is closed,
|
||||
* which for a site people leave open is effectively never. */
|
||||
self.addEventListener('install', () => self.skipWaiting());
|
||||
self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
|
||||
|
||||
/* ── where a notification goes when you tap it ───────────────────────────────
|
||||
*
|
||||
* The app's own resolver is `linkFor` in app/lib/notifications.ts and that is
|
||||
* the source of truth. This worker cannot import it (see above), so the click
|
||||
* handler below prefers to hand the notification back to an open tab and let
|
||||
* the real `linkFor` decide.
|
||||
*
|
||||
* `data.url` is the cold-start fallback, computed server-side by
|
||||
* ops/reminder-mailer.js. Two copies of one rule is a real cost, and the
|
||||
* alternative -- opening `/` and making somebody find the thing themselves --
|
||||
* is a worse one. ops/reminder-mailer.js has a self-check that asserts its copy
|
||||
* still agrees with linkFor's cases; run `node ops/reminder-mailer.js --selftest`.
|
||||
*/
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
event.waitUntil(handlePush(event));
|
||||
});
|
||||
|
||||
async function handlePush(event) {
|
||||
let payload = {};
|
||||
try {
|
||||
payload = event.data ? event.data.json() : {};
|
||||
} catch {
|
||||
// A push with no payload, or one we cannot read, still means "something
|
||||
// happened". Saying so vaguely beats saying nothing: the browser will show
|
||||
// its own "This site has been updated in the background" otherwise, which
|
||||
// is worse than anything we could write.
|
||||
payload = {};
|
||||
}
|
||||
|
||||
const title = payload.title || 'Pull Up';
|
||||
const body = payload.body || '';
|
||||
|
||||
await self.registration.showNotification(title, {
|
||||
body,
|
||||
// The maskable icon already in the export. No emoji anywhere in this app.
|
||||
icon: '/assets/icon.png',
|
||||
badge: '/assets/icon.png',
|
||||
/* Collapse by conversation. Five chat messages from one game should be one
|
||||
* line on the lock screen, not five -- `renotify` still buzzes for each so
|
||||
* nothing is silently swallowed. A notification with no session falls back
|
||||
* to its own id so unrelated things never overwrite each other. */
|
||||
tag: payload.tag || payload.id || 'pullup',
|
||||
renotify: true,
|
||||
/* False, so a notification that arrives while you are staring at the tab
|
||||
* still appears. The click handler is what avoids the silly case of
|
||||
* re-navigating a tab that is already on the right screen. */
|
||||
silent: false,
|
||||
timestamp: payload.at ? Date.parse(payload.at) || Date.now() : Date.now(),
|
||||
data: {
|
||||
id: payload.id || null,
|
||||
kind: payload.kind || null,
|
||||
payload: payload.data || null,
|
||||
url: payload.url || '/',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
event.waitUntil(openFor(event.notification.data || {}));
|
||||
});
|
||||
|
||||
async function openFor(data) {
|
||||
const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
||||
|
||||
/* An open tab is the good case: it has the router, the session and the real
|
||||
* `linkFor`, so it can navigate without a reload. Focus it and tell it what
|
||||
* was tapped; app/lib/webPush.ts is listening. */
|
||||
for (const client of all) {
|
||||
if (client.url && new URL(client.url).origin === self.location.origin) {
|
||||
client.postMessage({
|
||||
type: 'pullup-notification-click',
|
||||
id: data.id,
|
||||
kind: data.kind,
|
||||
data: data.payload,
|
||||
url: data.url,
|
||||
});
|
||||
if ('focus' in client) {
|
||||
try {
|
||||
await client.focus();
|
||||
return;
|
||||
} catch {
|
||||
// Some browsers refuse focus() outside a user gesture they recognise.
|
||||
// Fall through and open a window instead of failing silently.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const url = typeof data.url === 'string' && data.url.startsWith('/') ? data.url : '/';
|
||||
if (self.clients.openWindow) await self.clients.openWindow(url);
|
||||
}
|
||||
|
||||
/* The browser rotates a subscription occasionally, and when it does the old
|
||||
* endpoint stops working. There is no session in here to re-save the new one
|
||||
* with, so this worker does the half it can -- get a fresh subscription using
|
||||
* the same application server key -- and tells any open tab, which has the
|
||||
* session and can call save_push_subscription.
|
||||
*
|
||||
* If no tab is open the new subscription is simply lost, and that is fine:
|
||||
* app/lib/webPush.ts re-reads and re-saves the live subscription on every app
|
||||
* start, so the next visit repairs it. Support for this event is patchy enough
|
||||
* that it could not be relied on either way.
|
||||
*/
|
||||
self.addEventListener('pushsubscriptionchange', (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const old = event.oldSubscription || (await self.registration.pushManager.getSubscription());
|
||||
const key = old && old.options ? old.options.applicationServerKey : null;
|
||||
if (!key) return;
|
||||
|
||||
let fresh;
|
||||
try {
|
||||
fresh = await self.registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: key,
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
||||
for (const client of all) {
|
||||
client.postMessage({ type: 'pullup-subscription-changed', subscription: fresh.toJSON() });
|
||||
}
|
||||
})()
|
||||
);
|
||||
});
|
||||
Loading…
Reference in a new issue