background.ts
1 import { chain, ifilter, imap, uniqueEverseen } from 'itertools'; 2 import { nanoid } from 'nanoid'; 3 import { createRouter } from 'radix3'; 4 5 import '@/register'; 6 7 import { EventDispatcher, EventListener } from '@/events'; 8 import { registerChannelsHandlers } from '@/request/channels'; 9 import { registerCommentsHandlers } from '@/request/comments'; 10 import { Context } from '@/request/ctx'; 11 import { registerStoriesHandlers } from '@/request/stories'; 12 import { registerTimelineHandlers } from '@/request/timeline'; 13 import { setLocales, t } from '@/translation/js'; 14 import type {} from '@/types/tsPatch'; 15 import { Logger } from '@/utils/logger'; 16 17 declare global { 18 var ctx: InstanceType<typeof Context>; 19 } 20 21 enum MenuItemId { 22 TAG_LINK_COPY = 'tag-link/copy', 23 TAG_LINK_BLOCK = 'tag-link/block', 24 } 25 26 export default defineBackground(() => { 27 browser.browserAction.onClicked.addListener(() => browser.runtime.openOptionsPage()); 28 29 const logger = Logger.create('bg'); 30 const unloadStylesClassPrefix = `${browser.runtime.id}__unload-styles__`; 31 const unloadStylesClass = `${unloadStylesClassPrefix}${nanoid()}`; 32 33 const ctx = (globalThis.ctx = new Context(logger)); 34 35 browser.menus.create({ 36 id: MenuItemId.TAG_LINK_COPY, 37 contexts: ['link'], 38 targetUrlPatterns: [`${ctx.origin}/tags/*`], 39 title: t('copy-tag'), 40 }); 41 42 browser.menus.create({ 43 id: MenuItemId.TAG_LINK_BLOCK, 44 contexts: ['link'], 45 targetUrlPatterns: [`${ctx.origin}/tags/*`], 46 title: t('block-tag'), 47 }); 48 49 browser.menus.onClicked.addListener((info, tab) => { 50 logger.debug('handling click on menu item', info.menuItemId, { info, tab }); 51 52 switch (info.menuItemId) { 53 case MenuItemId.TAG_LINK_COPY: { 54 info.linkText && 55 navigator.clipboard 56 .writeText(info.linkText) 57 .catch(err => logger.error('failed to copy tag', { info, tab }, err)); 58 break; 59 } 60 61 case MenuItemId.TAG_LINK_BLOCK: { 62 info.linkText && 63 ctx.blockedTags 64 .block(info.linkText) 65 .catch(err => logger.error('failed to block tag', { info, tab }, err)); 66 break; 67 } 68 } 69 }); 70 71 window.addEventListener('languagechange', () => { 72 logger.debug('languagechange', navigator.languages); 73 setLocales(navigator.languages); 74 browser.menus.update(MenuItemId.TAG_LINK_COPY, { title: t('copy-tag') }); 75 browser.menus.update(MenuItemId.TAG_LINK_BLOCK, { title: t('block-tag') }); 76 }); 77 78 const eventListener = new EventListener(logger, (event, sender, sendResponse) => { 79 if (browser.runtime.id !== sender.id) { 80 return; 81 } 82 83 switch (event.type) { 84 case 'GetTabId': 85 return sendResponse(sender.tab?.id); 86 87 case 'GetUnloadStylesClassWithPrefix': 88 return sendResponse([unloadStylesClassPrefix, unloadStylesClass]); 89 90 case 'GetUnloadStylesClass': { 91 (async () => { 92 try { 93 logger.debug('inserting unload styles to tab', sender.tab?.id, 'frame', sender.frameId); 94 await browser.tabs.insertCSS(sender.tab?.id, { 95 code: `.${CSS.escape(unloadStylesClass)} { opacity: 0 !important; }`, 96 frameId: sender.frameId, 97 runAt: 'document_start', 98 }); 99 sendResponse(unloadStylesClass); 100 } catch (err) { 101 logger.error('failed to insert unload styles:', err); 102 } 103 })(); 104 105 return true; 106 } 107 108 default: 109 (async () => { 110 try { 111 const allCoubTabIds = imap( 112 ifilter( 113 await browser.tabs.query({ url: `${ctx.origin}/*` }), 114 tab => tab.discarded !== true && (!tab.url || !prohibitedRouter.lookup(tab.url)), 115 ), 116 tab => tab.id, 117 ); 118 119 for (const tabId of uniqueEverseen(chain([sender.tab?.id], allCoubTabIds))) { 120 if (typeof tabId === 'number') { 121 EventDispatcher.dispatch(`tab ${tabId}`, event, event => 122 browser.tabs.sendMessage(tabId, event), 123 ); 124 } 125 } 126 } catch (err) { 127 logger.error('failed to broadcast message:', err); 128 } 129 })(); 130 } 131 }); 132 133 registerTimelineHandlers(ctx); 134 registerStoriesHandlers(ctx); 135 registerCommentsHandlers(ctx); 136 registerChannelsHandlers(ctx); 137 138 const prohibitedRouter = createRouter({ 139 routes: { 140 [`${ctx.origin}/chat`]: void 0, 141 [`${ctx.origin}/chat/*`]: void 0, 142 [`${ctx.origin}/account/*`]: void 0, 143 [`${ctx.origin}/official/*`]: void 0, 144 [`${ctx.origin}/brand-assets`]: void 0, 145 [`${ctx.origin}/tos`]: void 0, 146 [`${ctx.origin}/privacy`]: void 0, 147 [`${ctx.origin}/rules`]: void 0, 148 [`${ctx.origin}/dmca`]: void 0, 149 }, 150 }); 151 152 browser.runtime.onSuspend.addListener(() => { 153 eventListener[Symbol.dispose](); 154 ctx[Symbol.dispose](); 155 }); 156 });