ServiceWorker: add jsdoc comments

environments/review-sw-typescr-g2a40b/deployments/85
Alex Gleason 2 years ago
parent 5c49cc0b84
commit d111c4c2d2
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7

@ -10,12 +10,15 @@ import type {
Status as StatusEntity, Status as StatusEntity,
} from 'soapbox/types/entities'; } from 'soapbox/types/entities';
/** Limit before we start grouping device notifications into a single notification. */
const MAX_NOTIFICATIONS = 5; const MAX_NOTIFICATIONS = 5;
/** Tag for the grouped notification. */
const GROUP_TAG = 'tag'; const GROUP_TAG = 'tag';
// https://www.devextent.com/create-service-worker-typescript/ // https://www.devextent.com/create-service-worker-typescript/
declare const self: ServiceWorkerGlobalScope; declare const self: ServiceWorkerGlobalScope;
/** Soapbox notification data from push event. */
interface NotificationData { interface NotificationData {
access_token?: string, access_token?: string,
preferred_locale: string, preferred_locale: string,
@ -26,11 +29,13 @@ interface NotificationData {
count?: number, count?: number,
} }
/** ServiceWorker Notification options with extra fields. */
interface ExtendedNotificationOptions extends NotificationOptions { interface ExtendedNotificationOptions extends NotificationOptions {
title: string, title: string,
data: NotificationData, data: NotificationData,
} }
/** Partial clone of ServiceWorker Notification with mutability. */
interface ClonedNotification { interface ClonedNotification {
body?: string, body?: string,
image?: string, image?: string,
@ -40,15 +45,20 @@ interface ClonedNotification {
tag?: string, tag?: string,
} }
/** Status entitiy from the API (kind of). */
// HACK
interface APIStatus extends Omit<StatusEntity, 'media_attachments'> { interface APIStatus extends Omit<StatusEntity, 'media_attachments'> {
media_attachments: { preview_url: string }[], media_attachments: { preview_url: string }[],
} }
/** Notification entity from the API (kind of). */
// HACK
interface APINotification extends Omit<NotificationEntity, 'account' | 'status'> { interface APINotification extends Omit<NotificationEntity, 'account' | 'status'> {
account: AccountEntity, account: AccountEntity,
status?: APIStatus, status?: APIStatus,
} }
/** Show the actual push notification on the device. */
const notify = (options: ExtendedNotificationOptions): Promise<void> => const notify = (options: ExtendedNotificationOptions): Promise<void> =>
self.registration.getNotifications().then(notifications => { self.registration.getNotifications().then(notifications => {
if (notifications.length >= MAX_NOTIFICATIONS) { // Reached the maximum number of notifications, proceed with grouping if (notifications.length >= MAX_NOTIFICATIONS) { // Reached the maximum number of notifications, proceed with grouping
@ -80,6 +90,7 @@ const notify = (options: ExtendedNotificationOptions): Promise<void> =>
return self.registration.showNotification(options.title, options); return self.registration.showNotification(options.title, options);
}); });
/** Perform an API request to the backend. */
const fetchFromApi = (path: string, method: string, accessToken: string): Promise<APINotification> => { const fetchFromApi = (path: string, method: string, accessToken: string): Promise<APINotification> => {
const url = (new URL(path, self.location.href)).href; const url = (new URL(path, self.location.href)).href;
@ -100,6 +111,7 @@ const fetchFromApi = (path: string, method: string, accessToken: string): Promis
}).then(res => res.json()); }).then(res => res.json());
}; };
/** Create a mutable object that loosely matches the Notification. */
const cloneNotification = (notification: Notification): ClonedNotification => { const cloneNotification = (notification: Notification): ClonedNotification => {
const clone: any = {}; const clone: any = {};
let k: string; let k: string;
@ -112,12 +124,15 @@ const cloneNotification = (notification: Notification): ClonedNotification => {
return clone as ClonedNotification; return clone as ClonedNotification;
}; };
/** Get translated message for the user's locale. */
const formatMessage = (messageId: string, locale: string, values = {}): string => const formatMessage = (messageId: string, locale: string, values = {}): string =>
(new IntlMessageFormat(locales[locale][messageId], locale)).format(values) as string; (new IntlMessageFormat(locales[locale][messageId], locale)).format(values) as string;
/** Strip HTML for display in a native notification. */
const htmlToPlainText = (html: string): string => const htmlToPlainText = (html: string): string =>
unescape(html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><[^>]*>/g, '\n\n').replace(/<[^>]*>/g, '')); unescape(html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><[^>]*>/g, '\n\n').replace(/<[^>]*>/g, ''));
/** ServiceWorker `push` event callback. */
const handlePush = (event: PushEvent) => { const handlePush = (event: PushEvent) => {
const { access_token, notification_id, preferred_locale, title, body, icon } = event.data?.json(); const { access_token, notification_id, preferred_locale, title, body, icon } = event.data?.json();
@ -162,24 +177,28 @@ const handlePush = (event: PushEvent) => {
); );
}; };
/** Native action to open a status on the device. */
const actionExpand = (preferred_locale: string) => ({ const actionExpand = (preferred_locale: string) => ({
action: 'expand', action: 'expand',
icon: `/${require('../../images/web-push/web-push-icon_expand.png')}`, icon: `/${require('../../images/web-push/web-push-icon_expand.png')}`,
title: formatMessage('status.show_more', preferred_locale), title: formatMessage('status.show_more', preferred_locale),
}); });
/** Native action to repost status. */
const actionReblog = (preferred_locale: string) => ({ const actionReblog = (preferred_locale: string) => ({
action: 'reblog', action: 'reblog',
icon: `/${require('../../images/web-push/web-push-icon_reblog.png')}`, icon: `/${require('../../images/web-push/web-push-icon_reblog.png')}`,
title: formatMessage('status.reblog', preferred_locale), title: formatMessage('status.reblog', preferred_locale),
}); });
/** Native action to like status. */
const actionFavourite = (preferred_locale: string) => ({ const actionFavourite = (preferred_locale: string) => ({
action: 'favourite', action: 'favourite',
icon: `/${require('../../images/web-push/web-push-icon_favourite.png')}`, icon: `/${require('../../images/web-push/web-push-icon_favourite.png')}`,
title: formatMessage('status.favourite', preferred_locale), title: formatMessage('status.favourite', preferred_locale),
}); });
/** Get the active tab if possible, or any open tab. */
const findBestClient = (clients: readonly WindowClient[]): WindowClient => { const findBestClient = (clients: readonly WindowClient[]): WindowClient => {
const focusedClient = clients.find(client => client.focused); const focusedClient = clients.find(client => client.focused);
const visibleClient = clients.find(client => client.visibilityState === 'visible'); const visibleClient = clients.find(client => client.visibilityState === 'visible');
@ -197,6 +216,7 @@ const expandNotification = (notification: Notification) => {
return self.registration.showNotification(newNotification.title, newNotification); return self.registration.showNotification(newNotification.title, newNotification);
}; };
/** Update the native notification, but delete the action (because it was performed). */
const removeActionFromNotification = (notification: Notification, action: string) => { const removeActionFromNotification = (notification: Notification, action: string) => {
const newNotification = cloneNotification(notification); const newNotification = cloneNotification(notification);
@ -205,6 +225,7 @@ const removeActionFromNotification = (notification: Notification, action: string
return self.registration.showNotification(newNotification.title, newNotification); return self.registration.showNotification(newNotification.title, newNotification);
}; };
/** Open a URL on the device. */
const openUrl = (url: string) => const openUrl = (url: string) =>
self.clients.matchAll({ type: 'window' }).then(clientList => { self.clients.matchAll({ type: 'window' }).then(clientList => {
if (clientList.length === 0) { if (clientList.length === 0) {
@ -215,6 +236,7 @@ const openUrl = (url: string) =>
} }
}); });
/** Callback when a native notification is clicked/touched on the device. */
const handleNotificationClick = (event: NotificationEvent) => { const handleNotificationClick = (event: NotificationEvent) => {
const reactToNotificationClick = new Promise((resolve, reject) => { const reactToNotificationClick = new Promise((resolve, reject) => {
if (event.action) { if (event.action) {
@ -238,5 +260,6 @@ const handleNotificationClick = (event: NotificationEvent) => {
event.waitUntil(reactToNotificationClick); event.waitUntil(reactToNotificationClick);
}; };
// ServiceWorker event listeners
self.addEventListener('push', handlePush); self.addEventListener('push', handlePush);
self.addEventListener('notificationclick', handleNotificationClick); self.addEventListener('notificationclick', handleNotificationClick);

Loading…
Cancel
Save