Signatur
Beschreibung
Das Clients-Interface gibt es nur im Service-Worker-Kontext. Man erreicht es über self.clients in einem ServiceWorkerGlobalScope. Damit findet, steuert und öffnet ein Service Worker die Clients seines Gültigkeitsbereichs (Scope), also Browser-Tabs, dedizierte Worker und Shared Worker.
Die wichtigsten Methoden:
clients.get(id): liefert denClientzu einer ID, z. B. zuevent.clientIdaus einemfetch-Event.clients.matchAll(options): liefert alle passenden Clients, optional gefiltert nach Typ oder auch unkontrollierte Clients.clients.openWindow(url): öffnet ein neues Fenster oder einen neuen Tab. Das ist z. B. nach einem Klick auf eine Benachrichtigung nötig.clients.claim(): macht den aktiven Service Worker sofort zum Controller aller Clients im Scope, ohne dass die Seite neu geladen werden muss.
Typische Anwendungsfälle sind Push-Benachrichtigungen („Tab fokussieren, falls schon offen, sonst neu öffnen“), Nachrichten an alle offenen Tabs (z. B. „Neue Version verfügbar“) und das sofortige Übernehmen der Kontrolle nach der Aktivierung. Alle Methoden arbeiten asynchron und geben ein Promise zurück.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $id Pflicht | string | Nur bei get(id): die eindeutige ID des gesuchten Clients, z. B. event.clientId oder event.resultingClientId. |
|
| $options | { includeUncontrolled?: boolean, type?: 'window' | 'worker' | 'sharedworker' | 'all' } | { includeUncontrolled: false, type: 'window' } | Nur bei matchAll(options): includeUncontrolled nimmt auch Clients auf, die nicht von diesem Service Worker kontrolliert werden. type filtert nach Art des Clients. |
| $url Pflicht | string | Nur bei openWindow(url): die zu öffnende URL. Sie wird relativ zum Service-Worker-Skript aufgelöst. |
Rückgabewert
get() liefert einen Client oder undefined. matchAll() liefert ein Array von Client-Objekten. openWindow() liefert einen WindowClient oder null, wenn die URL einen anderen Ursprung hat. claim() liefert ein Promise<void>.Beispiele
Kontrolle sofort übernehmen mit claim()
// sw.js
self.addEventListener('install', (event) => {
self.skipWaiting(); // Wartephase überspringen
});
self.addEventListener('activate', (event) => {
// Alle offenen Seiten im Scope sofort kontrollieren
event.waitUntil(self.clients.claim());
});
Nachricht an alle offenen Tabs senden
// sw.js
const broadcast = async (message) => {
const allClients = await self.clients.matchAll({
type: 'window',
includeUncontrolled: true
});
for (const client of allClients) {
client.postMessage(message);
}
return allClients.length;
};
self.addEventListener('activate', (event) => {
event.waitUntil(
broadcast({ type: 'SW_UPDATED', version: '2.1.0' })
.then((n) => console.log(`${n} Tab(s) benachrichtigt`))
);
});
// Auf der Seite (main.js):
// navigator.serviceWorker.addEventListener('message', (e) => console.log(e.data));
Klick auf Benachrichtigung: Tab fokussieren oder öffnen
// sw.js
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const targetUrl = new URL('/posteingang', self.location.origin).href;
event.waitUntil((async () => {
const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
const existing = windows.find((c) => c.url === targetUrl);
if (existing) {
return existing.focus();
}
return self.clients.openWindow(targetUrl);
})());
});
Absender eines fetch-Requests ermitteln mit get()
// sw.js
self.addEventListener('fetch', (event) => {
if (!event.clientId) return; // z. B. Navigationsanfragen
event.waitUntil((async () => {
const client = await self.clients.get(event.clientId);
if (client) {
console.log(`Anfrage ${event.request.url} von ${client.type}-Client ${client.url}`);
}
})());
});
// Wichtig · Fallstricke
- Nur im Service Worker:
self.clientsgibt es ausschließlich imServiceWorkerGlobalScope. Auf normalen Seiten oder in dedizierten Workern ist es nicht vorhanden. openWindow()braucht eine Nutzeraktion: Browser erlauben den Aufruf nur im Zusammenhang mit einer Nutzerinteraktion, typischerweise in einemnotificationclick-Handler. Sonst wird das Promise mit einemInvalidAccessErrorabgelehnt. Bei einer URL mit fremdem Ursprung liefert esnull.matchAll()findet standardmäßig nur kontrollierte Fenster: Direkt nach der ersten Installation werden offene Tabs noch nicht kontrolliert. DannincludeUncontrolled: truesetzen oderclients.claim()aufrufen.claim()mit Bedacht einsetzen: Seiten, die ohne Service Worker geladen wurden, bekommen plötzlich einen Controller. Ab dann gehen ihre Anfragen durch den neuen Service Worker, undcontrollerchangewird ausgelöst.- Aufrufe in Event-Handlern immer in
event.waitUntil()einschließen. Sonst beendet der Browser den Service Worker womöglich vor dem Abschluss. - Kompatibilität: Wird in allen modernen Browsern (Chrome, Edge, Firefox, Safari 11.1+) unterstützt. Voraussetzung ist ein sicherer Kontext (HTTPS oder
localhost). Die Reihenfolge der Ergebnisse vonmatchAll()(zuletzt fokussierte zuerst) ist nicht in allen Browsern gleich umgesetzt.