diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7a55a50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true \ No newline at end of file diff --git a/package.json b/package.json index 5917b62..7bc6d2e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ }, "manifest": { "permissions": [ - "declarativeNetRequest" + "declarativeNetRequest", + "tabs" ], "host_permissions": [ "" diff --git a/src/background/messages/hello.ts b/src/background/messages/hello.ts index ecff028..296f1ac 100644 --- a/src/background/messages/hello.ts +++ b/src/background/messages/hello.ts @@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; -import { validateDomainWhiteList } from '~utils/storage'; +import { assertDomainWhitelist } from '~utils/storage'; type Response = BaseResponse<{ version: string; @@ -10,7 +10,7 @@ type Response = BaseResponse<{ const handler: PlasmoMessaging.MessageHandler = async (req, res) => { try { - await validateDomainWhiteList(req.body.requestDomain); + await assertDomainWhitelist(req.body.requestDomain); const version = chrome.runtime.getManifest().version; diff --git a/src/background/messages/makeRequest.ts b/src/background/messages/makeRequest.ts index 42534a9..463aa51 100644 --- a/src/background/messages/makeRequest.ts +++ b/src/background/messages/makeRequest.ts @@ -3,7 +3,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; import { makeFullUrl } from '~utils/fetcher'; -import { validateDomainWhiteList } from '~utils/storage'; +import { assertDomainWhitelist } from '~utils/storage'; export interface Request extends BaseRequest { baseUrl?: string; @@ -26,7 +26,7 @@ type Response = BaseResponse<{ const handler: PlasmoMessaging.MessageHandler> = async (req, res) => { try { - await validateDomainWhiteList(req.body.requestDomain); + await assertDomainWhitelist(req.body.requestDomain); const response = await fetch(makeFullUrl(req.body.url, req.body), { method: req.body.method, diff --git a/src/background/messages/prepareStream.ts b/src/background/messages/prepareStream.ts index 3d6dc1d..f19978f 100644 --- a/src/background/messages/prepareStream.ts +++ b/src/background/messages/prepareStream.ts @@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging'; import type { BaseRequest } from '~types/request'; import type { BaseResponse } from '~types/response'; -import { validateDomainWhiteList } from '~utils/storage'; +import { assertDomainWhitelist } from '~utils/storage'; interface Request extends BaseRequest { ruleId: number; @@ -23,7 +23,7 @@ const mapHeadersToDeclarativeNetRequestHeaders = ( const handler: PlasmoMessaging.MessageHandler = async (req, res) => { try { - await validateDomainWhiteList(req.body.requestDomain); + await assertDomainWhitelist(req.body.requestDomain); await chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: [req.body.ruleId], diff --git a/src/components/Frame.tsx b/src/components/Frame.tsx new file mode 100644 index 0000000..0733f80 --- /dev/null +++ b/src/components/Frame.tsx @@ -0,0 +1,9 @@ +import type { ReactNode } from 'react'; + +export interface FrameProps { + children?: ReactNode; +} + +export function Frame(props: FrameProps) { + return
{props.children}
; +} diff --git a/src/components/ToggleButton.tsx b/src/components/ToggleButton.tsx new file mode 100644 index 0000000..b9a3344 --- /dev/null +++ b/src/components/ToggleButton.tsx @@ -0,0 +1,12 @@ +export interface ToggleButtonProps { + onClick?: () => void; + active?: boolean; +} + +export function ToggleButton(props: ToggleButtonProps) { + return ( + + ); +} diff --git a/src/hooks/useDomain.ts b/src/hooks/useDomain.ts new file mode 100644 index 0000000..69e4ca7 --- /dev/null +++ b/src/hooks/useDomain.ts @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; + +import { makeUrlIntoDomain } from '~utils/domains'; + +function queryCurrentDomain(cb: (domain: string | null) => void) { + chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + const url = tabs[0]?.url; + if (!url) cb(null); + else cb(url); + }); +} + +export function useDomain(): null | string { + const [domain, setDomain] = useState(null); + + useEffect(() => { + queryCurrentDomain(setDomain); + function listen() { + queryCurrentDomain(setDomain); + } + chrome.tabs.onActivated.addListener(listen); + chrome.tabs.onUpdated.addListener(listen); + return () => { + chrome.tabs.onActivated.removeListener(listen); + chrome.tabs.onUpdated.removeListener(listen); + }; + }, []); + + return makeUrlIntoDomain(domain); +} diff --git a/src/hooks/useDomainWhitelist.ts b/src/hooks/useDomainWhitelist.ts new file mode 100644 index 0000000..00adc44 --- /dev/null +++ b/src/hooks/useDomainWhitelist.ts @@ -0,0 +1,38 @@ +import { useCallback } from 'react'; + +import { useDomainStorage } from '~utils/storage'; + +export function useDomainWhitelist() { + const [domainWhitelist, setDomainWhitelist] = useDomainStorage(); + + const removeDomain = useCallback((domain: string | null) => { + if (!domain) return; + setDomainWhitelist((s) => [...s.filter((v) => v !== domain)]); + }, []); + + const addDomain = useCallback((domain: string | null) => { + if (!domain) return; + setDomainWhitelist((s) => [...s.filter((v) => v !== domain), domain]); + }, []); + + return { + removeDomain, + addDomain, + domainWhitelist, + }; +} + +export function useToggleWhitelistDomain(domain: string) { + const { domainWhitelist, addDomain, removeDomain } = useDomainWhitelist(); + const isWhitelisted = domainWhitelist.includes(domain); + + const toggle = useCallback(() => { + if (isWhitelisted) removeDomain(domain); + else addDomain(domain); + }, [isWhitelisted, domain, addDomain, removeDomain]); + + return { + toggle, + isWhitelisted, + }; +} diff --git a/src/hooks/useVersion.ts b/src/hooks/useVersion.ts new file mode 100644 index 0000000..63a960b --- /dev/null +++ b/src/hooks/useVersion.ts @@ -0,0 +1,4 @@ +export function useVersion(ops?: { prefixed?: boolean }) { + const prefix = ops?.prefixed ? 'v' : ''; + return `${prefix}${chrome.runtime.getManifest().version}`; +} diff --git a/src/popup.tsx b/src/popup.tsx index 2fda79d..4c01d3e 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -1,61 +1,20 @@ -import { useStorage } from '@plasmohq/storage/hook'; -import { useState } from 'react'; - -import { DEFAULT_DOMAIN_WHITELIST } from '~utils/storage'; +import { Frame } from '~components/Frame'; +import { ToggleButton } from '~components/ToggleButton'; +import { useDomain } from '~hooks/useDomain'; +import { useToggleWhitelistDomain } from '~hooks/useDomainWhitelist'; +import { useVersion } from '~hooks/useVersion'; function IndexPopup() { - const [domainInput, setDomainInput] = useState(''); - const [domainWhiteist, setDomainWhitelist] = useStorage( - 'domainWhitelist', - (v) => v ?? DEFAULT_DOMAIN_WHITELIST, - ); - - const [error, setError] = useState(null); - - const handleDomainSubmit = () => { - try { - const origin = new URL(domainInput).origin; - setDomainWhitelist([...domainWhiteist, origin]); - setDomainInput(''); - } catch (e) { - setError('Invalid domain'); - } - }; + const domain = useDomain(); + const { isWhitelisted, toggle } = useToggleWhitelistDomain(domain); + const version = useVersion({ prefixed: true }); return ( -
-
-

movie-web

- -

v{chrome.runtime.getManifest().version}

-
- -

Domains

- -
-
- setDomainInput(e.target.value)} /> - -
- {error && {error}} - - - {domainWhiteist.map((domain) => ( - - - - - ))} - -
{domain} - -
-
-
+ + + {!domain ?

Cant use extension on this page

: null} +

{version} - movie-web

+ ); } diff --git a/src/utils/domains.ts b/src/utils/domains.ts new file mode 100644 index 0000000..fc1e344 --- /dev/null +++ b/src/utils/domains.ts @@ -0,0 +1,9 @@ +export function makeUrlIntoDomain(url: string): string | null { + try { + const u = new URL(url); + if (!['http:', 'https:'].includes(u.protocol)) return null; + return u.host.toLowerCase(); + } catch { + return null; + } +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts index a173abd..7b52595 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,15 +1,24 @@ import { Storage } from '@plasmohq/storage'; +import { useStorage } from '@plasmohq/storage/hook'; + +import { makeUrlIntoDomain } from '~utils/domains'; export const DEFAULT_DOMAIN_WHITELIST = ['https://movie-web.app', 'http://localhost:5173']; export const storage = new Storage(); -export const domainIsInWhitelist = async (domain: string) => { +const domainIsInWhitelist = async (domain: string) => { const whitelist = await storage.get('domainWhitelist'); return whitelist?.some((d) => d.includes(domain)) ?? false; }; -export const validateDomainWhiteList = async (domain: string) => { +export function useDomainStorage() { + return useStorage('domainWhitelist', (v) => v ?? DEFAULT_DOMAIN_WHITELIST); +} + +export const assertDomainWhitelist = async (url: string) => { + const domain = makeUrlIntoDomain(url); + if (!domain) throw new Error('Domain is from a normal tab'); const isWhiteListed = await domainIsInWhitelist(domain); if (!isWhiteListed) throw new Error('Domain is not whitelisted'); };