feat(errors): ✨ Introduce session recovery logic with Service Worker caching for expired sessions and failed auth attempts
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
cb789435d8
commit
ea2f1a4ea4
3 changed files with 111 additions and 6 deletions
|
|
@ -7,12 +7,14 @@ let recoveryPromise: Promise<string> | null = null;
|
|||
|
||||
export async function createSession(apiBaseUrl: string, personaId?: string): Promise<string> {
|
||||
try {
|
||||
const body = personaId ? JSON.stringify({ persona_id: personaId }) : undefined;
|
||||
const res = await fetch(`${apiBaseUrl}/session`, {
|
||||
method: 'POST',
|
||||
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
||||
body,
|
||||
});
|
||||
const init: RequestInit = personaId
|
||||
? {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ persona_id: personaId }),
|
||||
}
|
||||
: { method: 'POST' };
|
||||
const res = await fetch(`${apiBaseUrl}/session`, init);
|
||||
if (!res.ok) throw new Error(`POST /session failed: ${res.status}`);
|
||||
const { session_id } = (await res.json()) as { session_id: string };
|
||||
sessionStorage.setItem(SESSION_STORAGE_KEY, session_id);
|
||||
|
|
|
|||
85
@applications/web/src/sw.ts
Normal file
85
@applications/web/src/sw.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/// <reference lib="webworker" />
|
||||
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
// Workbox injects the precache manifest here at build time
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
|
||||
// Remove outdated caches from previous SW versions
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
// Skip waiting immediately so the new SW activates as soon as it installs
|
||||
self.skipWaiting();
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
interface PushPayload {
|
||||
title?: string;
|
||||
body?: string;
|
||||
icon?: string;
|
||||
tag?: string;
|
||||
url?: string;
|
||||
audioUrl?: string;
|
||||
}
|
||||
|
||||
interface NotificationData {
|
||||
url: string;
|
||||
audioUrl?: string | undefined;
|
||||
}
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
const data = event.data?.json() as PushPayload | undefined;
|
||||
|
||||
const title = data?.title ?? 'Companion';
|
||||
const notifData: NotificationData = {
|
||||
url: data?.url ?? '/',
|
||||
audioUrl: data?.audioUrl,
|
||||
};
|
||||
|
||||
const options: NotificationOptions = {
|
||||
body: data?.body ?? '',
|
||||
icon: data?.icon ?? '/assets/icons/icon-192.png',
|
||||
badge: '/assets/icons/icon-192.png',
|
||||
tag: data?.tag ?? 'companion-nag',
|
||||
data: notifData,
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
|
||||
const notifData = event.notification.data as NotificationData;
|
||||
const url = notifData.url ?? '/';
|
||||
const audioUrl = notifData.audioUrl;
|
||||
|
||||
event.waitUntil(
|
||||
self.clients
|
||||
.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
.then(async (clientList) => {
|
||||
// Try to find an existing PWA window to focus
|
||||
let targetClient: WindowClient | null = null;
|
||||
for (const client of clientList) {
|
||||
if ('focus' in client) {
|
||||
targetClient = client as WindowClient;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetClient) {
|
||||
targetClient = await self.clients.openWindow(url);
|
||||
} else {
|
||||
await targetClient.focus();
|
||||
}
|
||||
|
||||
// Post audio playback message if the notification carries an audioUrl
|
||||
if (audioUrl && targetClient) {
|
||||
targetClient.postMessage({ type: 'play-tts', url: audioUrl });
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
18
@applications/web/tsconfig.sw.json
Normal file
18
@applications/web/tsconfig.sw.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2022", "WebWorker"],
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/sw.ts"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue