From 5c49cc0b84109b9cebaecb016f0f87a20e851ec2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 26 May 2022 14:51:59 -0400 Subject: [PATCH 1/5] Convert ServiceWorker to TypeScript --- .../service_worker/{entry.js => entry.ts} | Bin ...fications.js => web_push_notifications.ts} | Bin 7871 -> 9074 bytes tsconfig.json | 1 + webpack/production.js | Bin 3818 -> 3818 bytes 4 files changed, 1 insertion(+) rename app/soapbox/service_worker/{entry.js => entry.ts} (100%) rename app/soapbox/service_worker/{web_push_notifications.js => web_push_notifications.ts} (58%) diff --git a/app/soapbox/service_worker/entry.js b/app/soapbox/service_worker/entry.ts similarity index 100% rename from app/soapbox/service_worker/entry.js rename to app/soapbox/service_worker/entry.ts diff --git a/app/soapbox/service_worker/web_push_notifications.js b/app/soapbox/service_worker/web_push_notifications.ts similarity index 58% rename from app/soapbox/service_worker/web_push_notifications.js rename to app/soapbox/service_worker/web_push_notifications.ts index 5dbd749f4ddf8c76be90279b5ca52fd234ad087e..dee650600984163bd68651978e66d28ad0609a12 100644 GIT binary patch delta 2117 zcmaJ?&u<$=6qaezBum_c;siIS@BlTo!PyN6sbbT_3aLvgq&CQrswxW6cz0}1v%6#M zj_n%9vc!QK64hLw2N3^4EhIn`E^w|8{{bfu2P6>U!hxAtuf2}j>|wR@-kbO1d*6He z!^U4%Kl!{z+&c9cuy!3oM+4ZhEZXoGpdk7zdW^7kUg&O6Mk>TYj8IP+#LpwE5<{#J zNPYMfX}VDHsSBBaqI#L$HDt(O+Q8C1x%cTt&oH3ISUoTeqt$BZHg4j(OsI8>x`yRr z#PDi>{U)*SYK!`Z*k4sXET7bwq1o7SkdMKlUcey0PQ`>$XnspszvIv{a!MAh<8@6V z9>abGSvV@=qN?i(Vn_jjEDHz00i%c5E0_>4pLo^0#@{=Y>O}{D8nJEcy-n?Q%q9|F zcTp9O$%#F7Z1~Q|Gl_q?yD0{pkkxq71 zKKNfP^p`HpjjHi$(_l}oKCJIHCN=y_JC&`wQ?(Iu5Jt>37B+F1W z23uF_#hSOZbA3R#2!oq0VVB3K2N@UJ1RWs8kX3U9rGm_+DrG`$LI>(TZW7!&h;;|L zGU=!vJw+Q4IbPea2P%GCGZ+BK2ujHgXW&Roftr#*8UuwnIdY+ysD4q=y@vSk}*`+%UBHpebwQR2xMb}36AIyF3w{^i+4-kCFEN+MDkD1VxJ<D)21z30Hcg6er`K{-k;GADu&=ZAzbK$?l zGlNPz+=46ON0iO+&bg!ItO#GhzK`vL@s8*C^t0ccr2ge(^%toh*RqyFN_Je#(*m3(hsFd;7BcNYJa?AJ!@=j-X){fA2*BwiTFl~xQAl@Fl4$R_uZ4HWGwYV-2{N&%k*QcHr9LwgJ3@hY-UCKhS1gxNN-J}jDW(%$h^x!kA zn52bIMiaO?Vt0Ow?E3IT;8FA%)NRXcDH`EKQ7xBon1upX1GU)kCD@pka48YN&w4HR+F+UevtB=ztW6PvZHn=d?3!H=`OqtFNaC_7cWnl}Hgw0*!iiz2#GpA?CWcF$9+z-3wwRok zq`pXln?~7MG>Fd1bd`%=JOs<3$M`i=#XI9WseKDV!8`l&7DMC)?IA_HJ+U42Y$Pazx*4@J>6v5%i} z6yL~`Lfwz=WI1V4O(zw?IZ+!XW!zB=%IdrUCCEcMjob2BT$1I>O3S9pI$f!_wgK%n zxJ;upol(KrR~sbsp2r}9bLKZXkp+AeDPu^!0m^@>VNF&#&*VS80HZ5#lW>~B&1ic< zc&Jg^rdp-HLcBV;brcLu9Rq%udOxZfx|a|?PVWVWC)4>B>-zlO%f#K-eaysDcsGtG WMbggq_-P;RB}!uL Date: Thu, 26 May 2022 15:16:03 -0400 Subject: [PATCH 2/5] ServiceWorker: add jsdoc comments --- .../service_worker/web_push_notifications.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/soapbox/service_worker/web_push_notifications.ts b/app/soapbox/service_worker/web_push_notifications.ts index dee6506009..f60c21f544 100644 --- a/app/soapbox/service_worker/web_push_notifications.ts +++ b/app/soapbox/service_worker/web_push_notifications.ts @@ -10,12 +10,15 @@ import type { Status as StatusEntity, } from 'soapbox/types/entities'; +/** Limit before we start grouping device notifications into a single notification. */ const MAX_NOTIFICATIONS = 5; +/** Tag for the grouped notification. */ const GROUP_TAG = 'tag'; // https://www.devextent.com/create-service-worker-typescript/ declare const self: ServiceWorkerGlobalScope; +/** Soapbox notification data from push event. */ interface NotificationData { access_token?: string, preferred_locale: string, @@ -26,11 +29,13 @@ interface NotificationData { count?: number, } +/** ServiceWorker Notification options with extra fields. */ interface ExtendedNotificationOptions extends NotificationOptions { title: string, data: NotificationData, } +/** Partial clone of ServiceWorker Notification with mutability. */ interface ClonedNotification { body?: string, image?: string, @@ -40,15 +45,20 @@ interface ClonedNotification { tag?: string, } +/** Status entitiy from the API (kind of). */ +// HACK interface APIStatus extends Omit { media_attachments: { preview_url: string }[], } +/** Notification entity from the API (kind of). */ +// HACK interface APINotification extends Omit { account: AccountEntity, status?: APIStatus, } +/** Show the actual push notification on the device. */ const notify = (options: ExtendedNotificationOptions): Promise => self.registration.getNotifications().then(notifications => { if (notifications.length >= MAX_NOTIFICATIONS) { // Reached the maximum number of notifications, proceed with grouping @@ -80,6 +90,7 @@ const notify = (options: ExtendedNotificationOptions): Promise => return self.registration.showNotification(options.title, options); }); +/** Perform an API request to the backend. */ const fetchFromApi = (path: string, method: string, accessToken: string): Promise => { 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()); }; +/** Create a mutable object that loosely matches the Notification. */ const cloneNotification = (notification: Notification): ClonedNotification => { const clone: any = {}; let k: string; @@ -112,12 +124,15 @@ const cloneNotification = (notification: Notification): ClonedNotification => { return clone as ClonedNotification; }; +/** Get translated message for the user's locale. */ const formatMessage = (messageId: string, locale: string, values = {}): string => (new IntlMessageFormat(locales[locale][messageId], locale)).format(values) as string; +/** Strip HTML for display in a native notification. */ const htmlToPlainText = (html: string): string => unescape(html.replace(//g, '\n').replace(/<\/p><[^>]*>/g, '\n\n').replace(/<[^>]*>/g, '')); +/** ServiceWorker `push` event callback. */ const handlePush = (event: PushEvent) => { 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) => ({ action: 'expand', icon: `/${require('../../images/web-push/web-push-icon_expand.png')}`, title: formatMessage('status.show_more', preferred_locale), }); +/** Native action to repost status. */ const actionReblog = (preferred_locale: string) => ({ action: 'reblog', icon: `/${require('../../images/web-push/web-push-icon_reblog.png')}`, title: formatMessage('status.reblog', preferred_locale), }); +/** Native action to like status. */ const actionFavourite = (preferred_locale: string) => ({ action: 'favourite', icon: `/${require('../../images/web-push/web-push-icon_favourite.png')}`, title: formatMessage('status.favourite', preferred_locale), }); +/** Get the active tab if possible, or any open tab. */ const findBestClient = (clients: readonly WindowClient[]): WindowClient => { const focusedClient = clients.find(client => client.focused); const visibleClient = clients.find(client => client.visibilityState === 'visible'); @@ -197,6 +216,7 @@ const expandNotification = (notification: Notification) => { 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 newNotification = cloneNotification(notification); @@ -205,6 +225,7 @@ const removeActionFromNotification = (notification: Notification, action: string return self.registration.showNotification(newNotification.title, newNotification); }; +/** Open a URL on the device. */ const openUrl = (url: string) => self.clients.matchAll({ type: 'window' }).then(clientList => { 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 reactToNotificationClick = new Promise((resolve, reject) => { if (event.action) { @@ -238,5 +260,6 @@ const handleNotificationClick = (event: NotificationEvent) => { event.waitUntil(reactToNotificationClick); }; +// ServiceWorker event listeners self.addEventListener('push', handlePush); self.addEventListener('notificationclick', handleNotificationClick); From b8cfb567d1bb48388c342cc303fb5003adfe1027 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 26 May 2022 15:16:54 -0400 Subject: [PATCH 3/5] ServiceWorker: alphabetize type definitions --- app/soapbox/service_worker/web_push_notifications.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/soapbox/service_worker/web_push_notifications.ts b/app/soapbox/service_worker/web_push_notifications.ts index f60c21f544..099bf8f45e 100644 --- a/app/soapbox/service_worker/web_push_notifications.ts +++ b/app/soapbox/service_worker/web_push_notifications.ts @@ -21,28 +21,28 @@ declare const self: ServiceWorkerGlobalScope; /** Soapbox notification data from push event. */ interface NotificationData { access_token?: string, - preferred_locale: string, + count?: number, hiddenBody?: string, hiddenImage?: string, id?: string, + preferred_locale: string, url: string, - count?: number, } /** ServiceWorker Notification options with extra fields. */ interface ExtendedNotificationOptions extends NotificationOptions { - title: string, data: NotificationData, + title: string, } /** Partial clone of ServiceWorker Notification with mutability. */ interface ClonedNotification { - body?: string, - image?: string, actions?: NotificationAction[], + body?: string, data: NotificationData, - title: string, + image?: string, tag?: string, + title: string, } /** Status entitiy from the API (kind of). */ From 5c549a46e5ef45e5c15043f3cd5947c8ea4092a1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 26 May 2022 17:21:38 -0400 Subject: [PATCH 4/5] ServiceWorker: add missing jsdoc comment to expandNotification --- app/soapbox/service_worker/web_push_notifications.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/service_worker/web_push_notifications.ts b/app/soapbox/service_worker/web_push_notifications.ts index 099bf8f45e..9939f88a29 100644 --- a/app/soapbox/service_worker/web_push_notifications.ts +++ b/app/soapbox/service_worker/web_push_notifications.ts @@ -206,6 +206,7 @@ const findBestClient = (clients: readonly WindowClient[]): WindowClient => { return focusedClient || visibleClient || clients[0]; }; +/** Update a notification with CW to display the full status. */ const expandNotification = (notification: Notification) => { const newNotification = cloneNotification(notification); From 4e7256698945997f878d6119398c7a6399de7a98 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 26 May 2022 17:29:44 -0400 Subject: [PATCH 5/5] Jest: fix ServiceWorker filename in collectCoverageFrom --- jest.config.js | Bin 1544 -> 1544 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/jest.config.js b/jest.config.js index c2c762be2e2965ca753ce4f40d4e26c48a32ca2c..8796e5595a3abd6903cec5259107acdc28811b96 100644 GIT binary patch delta 14 VcmeC+>EPM0j)}2k^LnO8W&kC@1ikEPM0j%jiellbI0jN+T`FzGM@086