refactor progress
This commit is contained in:
39
fluxer_app/src/service_worker/Register.tsx
Normal file
39
fluxer_app/src/service_worker/Register.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Config from '@app/Config';
|
||||
import {Logger} from '@app/lib/Logger';
|
||||
|
||||
const logger = new Logger('ServiceWorkerRegister');
|
||||
|
||||
export async function registerServiceWorker(): Promise<ServiceWorkerRegistration | undefined> {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const versionParam = Config.PUBLIC_BUILD_SHA || Date.now();
|
||||
const swUrl = new URL('/sw.js', window.location.origin);
|
||||
swUrl.searchParams.set('v', String(versionParam));
|
||||
const registration = await navigator.serviceWorker.register(`${swUrl.pathname}${swUrl.search}`);
|
||||
return registration;
|
||||
} catch (error) {
|
||||
logger.error('Registration failed', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
204
fluxer_app/src/service_worker/Worker.tsx
Normal file
204
fluxer_app/src/service_worker/Worker.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope &
|
||||
typeof globalThis & {
|
||||
skipWaiting(): void;
|
||||
__WB_MANIFEST: unknown;
|
||||
console: Console;
|
||||
};
|
||||
|
||||
const logger = {
|
||||
error: (...args: Array<unknown>) => {
|
||||
if (typeof console !== 'undefined') {
|
||||
console.error('[SW]', ...args);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.addEventListener('install', () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event: ExtendableMessageEvent) => {
|
||||
if (event.data?.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
} else if (event.data?.type === 'APP_UPDATE_BADGE') {
|
||||
const rawCount = event.data?.count;
|
||||
let badgeCount: number | null = null;
|
||||
if (typeof rawCount === 'number' && Number.isFinite(rawCount)) {
|
||||
badgeCount = rawCount;
|
||||
} else if (typeof rawCount === 'string' && rawCount.length > 0) {
|
||||
const parsed = Number(rawCount);
|
||||
badgeCount = Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
event.waitUntil(updateAppBadge(badgeCount));
|
||||
}
|
||||
});
|
||||
|
||||
interface PushPayload {
|
||||
title?: string;
|
||||
body?: string;
|
||||
icon?: string;
|
||||
badge?: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const getBadgeCount = (payload: PushPayload): number | null => {
|
||||
const badgeValue = payload.data?.badge_count;
|
||||
if (typeof badgeValue === 'number' && Number.isFinite(badgeValue)) {
|
||||
return badgeValue;
|
||||
}
|
||||
if (typeof badgeValue === 'string' && badgeValue.length > 0) {
|
||||
const parsed = Number(badgeValue);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const updateAppBadge = async (count: number | null): Promise<void> => {
|
||||
if (typeof navigator.setAppBadge !== 'function' && typeof navigator.clearAppBadge !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (count !== null && count > 0) {
|
||||
if (typeof navigator.setAppBadge === 'function') {
|
||||
await navigator.setAppBadge(count);
|
||||
}
|
||||
} else if (typeof navigator.clearAppBadge === 'function') {
|
||||
await navigator.clearAppBadge();
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to update app badge', error);
|
||||
}
|
||||
};
|
||||
|
||||
const resolveTargetUrl = (url?: string): string | null => {
|
||||
if (!url) return null;
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
try {
|
||||
return new URL(url, self.location.origin).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const postMessageToClients = async (message: Record<string, unknown>): Promise<ReadonlyArray<WindowClient>> => {
|
||||
try {
|
||||
const clientList = (await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true,
|
||||
})) as ReadonlyArray<WindowClient>;
|
||||
for (const client of clientList) {
|
||||
client.postMessage(message);
|
||||
}
|
||||
return clientList;
|
||||
} catch (error) {
|
||||
logger.error('Unable to broadcast to clients', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const focusOrOpenClient = async (targetUrl: string, targetUserId?: string): Promise<void> => {
|
||||
const message: Record<string, unknown> = {
|
||||
type: 'NOTIFICATION_CLICK_NAVIGATE',
|
||||
url: targetUrl,
|
||||
};
|
||||
if (targetUserId) {
|
||||
message.targetUserId = targetUserId;
|
||||
}
|
||||
|
||||
const clientList = await postMessageToClients(message);
|
||||
|
||||
const exact = clientList.find((c) => c.url === targetUrl);
|
||||
if (exact) {
|
||||
await exact.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const sameOrigin = clientList.find((c) => {
|
||||
try {
|
||||
return new URL(c.url).origin === self.location.origin;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (sameOrigin) {
|
||||
await sameOrigin.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.clients.openWindow) {
|
||||
await self.clients.openWindow(targetUrl);
|
||||
}
|
||||
};
|
||||
|
||||
self.addEventListener('push', (event: PushEvent) => {
|
||||
const payload: PushPayload = event.data?.json?.() ?? {
|
||||
title: 'Fluxer',
|
||||
};
|
||||
|
||||
const title = payload.title ?? 'Fluxer';
|
||||
const options: NotificationOptions = {
|
||||
body: payload.body ?? undefined,
|
||||
icon: payload.icon ?? undefined,
|
||||
badge: payload.badge ?? undefined,
|
||||
data: payload.data ?? undefined,
|
||||
};
|
||||
|
||||
const badgeCount = getBadgeCount(payload);
|
||||
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
await Promise.all([self.registration.showNotification(title, options), updateAppBadge(badgeCount)]);
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event: NotificationEvent) => {
|
||||
event.notification.close();
|
||||
|
||||
const targetUrl = resolveTargetUrl(event.notification.data?.url as string | undefined);
|
||||
if (!targetUrl) return;
|
||||
|
||||
const targetUserId = event.notification.data?.target_user_id as string | undefined;
|
||||
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
await focusOrOpenClient(targetUrl, targetUserId);
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('pushsubscriptionchange', (event: ExtendableEvent) => {
|
||||
event.waitUntil(
|
||||
postMessageToClients({type: 'PUSH_SUBSCRIPTION_CHANGE'})
|
||||
.then(() => undefined)
|
||||
.catch(() => undefined),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user