Skip to content

Commit 27a77fd

Browse files
authored
Add types for Service Worker Controller (#673)
1 parent aaee15d commit 27a77fd

File tree

5 files changed

+280
-145
lines changed

5 files changed

+280
-145
lines changed

packages/messaging/src/controllers/sw-controller.ts

Lines changed: 78 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,67 @@
1414
* limitations under the License.
1515
*/
1616

17+
import './sw-types';
18+
1719
import { FirebaseApp } from '@firebase/app-types';
1820

1921
import { ERROR_CODES } from '../models/errors';
2022
import { DEFAULT_PUBLIC_VAPID_KEY } from '../models/fcm-details';
21-
import * as WorkerPageMessage from '../models/worker-page-message';
23+
import {
24+
InternalMessage,
25+
MessageParameter,
26+
MessageType
27+
} from '../models/worker-page-message';
2228
import { ControllerInterface } from './controller-interface';
2329

30+
// Let TS know that this is a service worker
31+
declare const self: ServiceWorkerGlobalScope;
32+
2433
const FCM_MSG = 'FCM_MSG';
2534

26-
export type BgMessageHandler = (input: any) => Promise<any>;
35+
export type BgMessageHandler = (input: Payload) => Promise<void>;
36+
37+
export interface NotificationDetails extends NotificationOptions {
38+
title: string;
39+
click_action?: string;
40+
}
41+
42+
export interface Payload {
43+
notification?: NotificationDetails;
44+
}
2745

2846
export class SWController extends ControllerInterface {
2947
private bgMessageHandler_: BgMessageHandler | null = null;
3048

3149
constructor(app: FirebaseApp) {
3250
super(app);
3351

34-
self.addEventListener(
35-
'push',
36-
(e: any) => {
37-
this.onPush(e);
38-
},
39-
false
40-
);
41-
self.addEventListener(
42-
'pushsubscriptionchange',
43-
(e: any) => {
44-
this.onSubChange(e);
45-
},
46-
false
47-
);
48-
self.addEventListener(
49-
'notificationclick',
50-
(e: any) => {
51-
this.onNotificationClick(e);
52-
},
53-
false
54-
);
52+
self.addEventListener('push', e => {
53+
this.onPush(e);
54+
});
55+
self.addEventListener('pushsubscriptionchange', e => {
56+
this.onSubChange(e);
57+
});
58+
self.addEventListener('notificationclick', e => {
59+
this.onNotificationClick(e);
60+
});
5561
}
5662

5763
// Visible for testing
5864
// TODO: Make private
59-
onPush(event: any): void {
65+
onPush(event: PushEvent): void {
6066
event.waitUntil(this.onPush_(event));
6167
}
6268

6369
// Visible for testing
6470
// TODO: Make private
65-
onSubChange(event: any): void {
71+
onSubChange(event: PushSubscriptionChangeEvent): void {
6672
event.waitUntil(this.onSubChange_(event));
6773
}
6874

6975
// Visible for testing
7076
// TODO: Make private
71-
onNotificationClick(event: any): void {
77+
onNotificationClick(event: NotificationEvent): void {
7278
event.waitUntil(this.onNotificationClick_(event));
7379
}
7480

@@ -84,8 +90,12 @@ export class SWController extends ControllerInterface {
8490
* If there is no notification data in the payload then no notification will be
8591
* shown.
8692
*/
87-
private async onPush_(event: any): Promise<void> {
88-
let msgPayload: any;
93+
private async onPush_(event: PushEvent): Promise<void> {
94+
if (!event.data) {
95+
return;
96+
}
97+
98+
let msgPayload: Payload;
8999
try {
90100
msgPayload = event.data.json();
91101
} catch (err) {
@@ -105,15 +115,18 @@ export class SWController extends ControllerInterface {
105115

106116
const notificationDetails = this.getNotificationData_(msgPayload);
107117
if (notificationDetails) {
108-
const notificationTitle = (notificationDetails as any).title || '';
118+
const notificationTitle = notificationDetails.title || '';
109119
const reg = await this.getSWRegistration_();
110120
return reg.showNotification(notificationTitle, notificationDetails);
111121
} else if (this.bgMessageHandler_) {
112-
return this.bgMessageHandler_(msgPayload);
122+
await this.bgMessageHandler_(msgPayload);
123+
return;
113124
}
114125
}
115126

116-
private async onSubChange_(event: any): Promise<void> {
127+
private async onSubChange_(
128+
event: PushSubscriptionChangeEvent
129+
): Promise<void> {
117130
let registration: ServiceWorkerRegistration;
118131
try {
119132
registration = await this.getSWRegistration_();
@@ -140,12 +153,12 @@ export class SWController extends ControllerInterface {
140153
}
141154

142155
// Attempt to delete the token if we know it's bad
143-
await this.deleteToken(tokenDetails['fcmToken']);
156+
await this.deleteToken(tokenDetails.fcmToken);
144157
throw err;
145158
}
146159
}
147160

148-
private async onNotificationClick_(event: any): Promise<void> {
161+
private async onNotificationClick_(event: NotificationEvent): Promise<void> {
149162
if (
150163
!event.notification ||
151164
!event.notification.data ||
@@ -160,13 +173,13 @@ export class SWController extends ControllerInterface {
160173

161174
event.notification.close();
162175

163-
const msgPayload = event.notification.data[FCM_MSG];
164-
if (!msgPayload['notification']) {
176+
const msgPayload: Payload = event.notification.data[FCM_MSG];
177+
if (!msgPayload.notification) {
165178
// Nothing to do.
166179
return;
167180
}
168181

169-
const clickAction = msgPayload['notification']['click_action'];
182+
const clickAction = msgPayload.notification.click_action;
170183
if (!clickAction) {
171184
// Nothing to do.
172185
return;
@@ -175,7 +188,7 @@ export class SWController extends ControllerInterface {
175188
let windowClient = await this.getWindowClient_(clickAction);
176189
if (!windowClient) {
177190
// Unable to find window client so need to open one.
178-
windowClient = await (self as any).clients.openWindow(clickAction);
191+
windowClient = await self.clients.openWindow(clickAction);
179192
} else {
180193
windowClient = await windowClient.focus();
181194
}
@@ -186,10 +199,10 @@ export class SWController extends ControllerInterface {
186199
}
187200

188201
// Delete notification data from payload before sending to the page.
189-
delete msgPayload['notification'];
202+
delete msgPayload.notification;
190203

191-
const internalMsg = WorkerPageMessage.createNewMsg(
192-
WorkerPageMessage.TYPES_OF_MSG.NOTIFICATION_CLICKED,
204+
const internalMsg = createNewMsg(
205+
MessageType.NOTIFICATION_CLICKED,
193206
msgPayload
194207
);
195208

@@ -200,7 +213,7 @@ export class SWController extends ControllerInterface {
200213

201214
// Visible for testing
202215
// TODO: Make private
203-
getNotificationData_(msgPayload: any): NotificationOptions | undefined {
216+
getNotificationData_(msgPayload: Payload): NotificationDetails | undefined {
204217
if (!msgPayload) {
205218
return;
206219
}
@@ -250,16 +263,16 @@ export class SWController extends ControllerInterface {
250263
*/
251264
// Visible for testing
252265
// TODO: Make private
253-
async getWindowClient_(url: string): Promise<any> {
266+
async getWindowClient_(url: string): Promise<WindowClient | null> {
254267
// Use URL to normalize the URL when comparing to windowClients.
255268
// This at least handles whether to include trailing slashes or not
256-
const parsedURL = new URL(url, (self as any).location).href;
269+
const parsedURL = new URL(url, self.location.href).href;
257270

258271
const clientList = await getClientList();
259272

260-
let suitableClient = null;
273+
let suitableClient: WindowClient | null = null;
261274
for (let i = 0; i < clientList.length; i++) {
262-
const parsedClientUrl = new URL(clientList[i].url, (self as any).location)
275+
const parsedClientUrl = new URL(clientList[i].url, self.location.href)
263276
.href;
264277
if (parsedClientUrl === parsedURL) {
265278
suitableClient = clientList[i];
@@ -279,7 +292,10 @@ export class SWController extends ControllerInterface {
279292
*/
280293
// Visible for testing
281294
// TODO: Make private
282-
async attemptToMessageClient_(client: any, message: any): Promise<void> {
295+
async attemptToMessageClient_(
296+
client: WindowClient,
297+
message: InternalMessage
298+
): Promise<void> {
283299
// NOTE: This returns a promise in case this API is abstracted later on to
284300
// do additional work
285301
if (!client) {
@@ -299,7 +315,7 @@ export class SWController extends ControllerInterface {
299315
const clientList = await getClientList();
300316

301317
return clientList.some(
302-
(client: any) => client.visibilityState === 'visible'
318+
(client: WindowClient) => client.visibilityState === 'visible'
303319
);
304320
}
305321

@@ -311,16 +327,13 @@ export class SWController extends ControllerInterface {
311327
*/
312328
// Visible for testing
313329
// TODO: Make private
314-
async sendMessageToWindowClients_(msgPayload: any): Promise<void> {
330+
async sendMessageToWindowClients_(msgPayload: Payload): Promise<void> {
315331
const clientList = await getClientList();
316332

317-
const internalMsg = WorkerPageMessage.createNewMsg(
318-
WorkerPageMessage.TYPES_OF_MSG.PUSH_MSG_RECEIVED,
319-
msgPayload
320-
);
333+
const internalMsg = createNewMsg(MessageType.PUSH_MSG_RECEIVED, msgPayload);
321334

322335
await Promise.all(
323-
clientList.map((client: any) =>
336+
clientList.map(client =>
324337
this.attemptToMessageClient_(client, internalMsg)
325338
)
326339
);
@@ -331,7 +344,7 @@ export class SWController extends ControllerInterface {
331344
* @return he service worker registration to be used for the push service.
332345
*/
333346
async getSWRegistration_(): Promise<ServiceWorkerRegistration> {
334-
return (self as any).registration;
347+
return self.registration;
335348
}
336349

337350
/**
@@ -355,9 +368,17 @@ export class SWController extends ControllerInterface {
355368
}
356369
}
357370

358-
function getClientList(): Promise<any[]> {
359-
return (self as any).clients.matchAll({
371+
function getClientList(): Promise<WindowClient[]> {
372+
return self.clients.matchAll({
360373
type: 'window',
361374
includeUncontrolled: true
362-
});
375+
// TS doesn't know that "type: 'window'" means it'll return WindowClient[]
376+
}) as Promise<WindowClient[]>;
377+
}
378+
379+
function createNewMsg(msgType: MessageType, msgData: Payload): InternalMessage {
380+
return {
381+
[MessageParameter.TYPE_OF_MSG]: msgType,
382+
[MessageParameter.DATA]: msgData
383+
};
363384
}

0 commit comments

Comments
 (0)