Open notification to existing tab/window

This commit is contained in:
Jeff 2024-10-19 20:30:37 -04:00
parent 37f32cce3a
commit 3a56b9f853
2 changed files with 111 additions and 17 deletions

View File

@ -25,6 +25,7 @@ const createSelf = () =>
clients: { clients: {
claim: jest.fn(() => Promise.resolve()), claim: jest.fn(() => Promise.resolve()),
openWindow: jest.fn(() => Promise.resolve()), openWindow: jest.fn(() => Promise.resolve()),
matchAll: jest.fn(() => Promise.resolve([])),
}, },
}) as unknown as ServiceWorkerGlobalScope }) as unknown as ServiceWorkerGlobalScope
@ -32,12 +33,16 @@ let originalWindow: Window & typeof globalThis
describe("service worker", () => { describe("service worker", () => {
let self: ServiceWorkerGlobalScope let self: ServiceWorkerGlobalScope
let controlledClients: Client[]
let uncontrolledClients: Client[]
beforeEach(() => { beforeEach(() => {
originalWindow = global.window originalWindow = global.window
global.window = undefined as any global.window = undefined as any
indexedDB = new IDBFactory() indexedDB = new IDBFactory()
self = createSelf() self = createSelf()
controlledClients = []
uncontrolledClients = []
fetchMock.mockResponse((req) => { fetchMock.mockResponse((req) => {
if ( if (
@ -94,28 +99,71 @@ describe("service worker", () => {
) )
}) })
test("closes notification on click", async () => { describe("on notification click", () => {
initWorker(self) test("the notification is closed", async () => {
initWorker(self)
const notificationHandler = getHandler("notificationclick") const notificationHandler = getHandler("notificationclick")
const event = createNotificationEvent("tackupnow.com") const event = createNotificationEvent("tackupnow.com")
notificationHandler(event) notificationHandler(event)
await waitUntilCalls(event) await waitUntilCalls(event)
expect(event.notification.close).toHaveBeenCalled() expect(event.notification.close).toHaveBeenCalled()
}) })
test("opens tack up now on click", async () => { test("opens tack up now if no clients match the destination", async () => {
initWorker(self) initWorker(self)
const notificationHandler = getHandler("notificationclick") const notificationHandler = getHandler("notificationclick")
const event = createNotificationEvent("tackupnow.com") const event = createNotificationEvent("tackupnow.com")
notificationHandler(event) notificationHandler(event)
await waitUntilCalls(event) await waitUntilCalls(event)
expect(self.clients.openWindow).toHaveBeenCalledWith("tackupnow.com") expect(self.clients.openWindow).toHaveBeenCalledWith("tackupnow.com")
})
test("focuses first matching client", async () => {
addClient("https://tackupnow.com/place", "window", true)
const matchingClient = addClient(
"https://tackupnow.com/otherplace",
"window",
true
)
addClient("https://tackupnow.com/yetanotherotherplace", "window", true)
initWorker(self)
const notificationHandler = getHandler("notificationclick")
const event = createNotificationEvent("https://tackupnow.com/otherplace")
notificationHandler(event)
await waitUntilCalls(event)
expect(matchingClient.focus).toHaveBeenCalled()
})
test("focuses uncontrolled matching clients", async () => {
addClient("https://derpatious.world", "window", false)
addClient("https://tackupnow.com/1", "window", false)
const matchingClient = addClient(
"https://tackupnow.com/2",
"window",
false
)
addClient("https://tackupnow.com/controlled", "window", true)
initWorker(self)
const notificationHandler = getHandler("notificationclick")
const event = createNotificationEvent("https://tackupnow.com/2")
notificationHandler(event)
await waitUntilCalls(event)
expect(matchingClient.focus).toHaveBeenCalled()
})
}) })
test("claims on activate", async () => { test("claims on activate", async () => {
@ -287,6 +335,31 @@ describe("service worker", () => {
}) })
}) })
function addClient(url: string, type: ClientTypes, controlled: boolean) {
const matchAllMock = self.clients.matchAll as jest.Mock
matchAllMock.mockImplementation(
(args: { type: string; includeUncontrolled?: boolean }) => {
const clientList = args.includeUncontrolled
? [...uncontrolledClients, ...controlledClients]
: controlledClients
return clientList.filter((client) => client.type === args.type)
}
)
const clientList = controlled ? controlledClients : uncontrolledClients
const client: WindowClient = {
url,
type,
focus: jest.fn(() => Promise.resolve(client)) as WindowClient["focus"],
} as WindowClient
clientList.push(client)
return client
}
function getHandler(eventName: string) { function getHandler(eventName: string) {
expect(self.addEventListener).toHaveBeenCalledWith( expect(self.addEventListener).toHaveBeenCalledWith(
eventName, eventName,

View File

@ -17,7 +17,12 @@ export default function start(self: ServiceWorkerGlobalScope) {
}) })
self.addEventListener("push", function (event: PushEvent) { self.addEventListener("push", function (event: PushEvent) {
const { title, body, badge, icon, destination } = event.data?.json() const { title, body, badge, icon, destination } = event.data?.json() ?? {
title: "It was missing yo",
badge: "image/blah",
icon: "image/blah",
destination: "https://tackupnow.com",
}
event.waitUntil( event.waitUntil(
self.registration.showNotification(title, { self.registration.showNotification(title, {
@ -31,9 +36,25 @@ export default function start(self: ServiceWorkerGlobalScope) {
self.addEventListener("notificationclick", function (event) { self.addEventListener("notificationclick", function (event) {
event.notification.close() event.notification.close()
const destination = event.notification.data.destination
event.waitUntil( event.waitUntil(
self.clients.openWindow(event.notification.data.destination) (async () => {
const clients = await self.clients.matchAll({
type: "window",
includeUncontrolled: true,
})
const existingClient = clients.find(
(client) => client.url === event.notification.data.destination
)
if (existingClient === undefined) {
await self.clients.openWindow(destination)
} else {
await existingClient.focus()
}
})()
) )
}) })