Functionality and state for popout

This commit is contained in:
mrjvs
2024-01-10 19:27:20 +01:00
parent 3a8144ee67
commit 5a3268fd29
13 changed files with 141 additions and 63 deletions

7
.editorconfig Normal file
View File

@@ -0,0 +1,7 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true

View File

@@ -40,7 +40,8 @@
},
"manifest": {
"permissions": [
"declarativeNetRequest"
"declarativeNetRequest",
"tabs"
],
"host_permissions": [
"<all_urls>"

View File

@@ -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<BaseRequest, Response> = async (req, res) => {
try {
await validateDomainWhiteList(req.body.requestDomain);
await assertDomainWhitelist(req.body.requestDomain);
const version = chrome.runtime.getManifest().version;

View File

@@ -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<T> = BaseResponse<{
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = 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,

View File

@@ -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<Request, BaseResponse> = async (req, res) => {
try {
await validateDomainWhiteList(req.body.requestDomain);
await assertDomainWhitelist(req.body.requestDomain);
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [req.body.ruleId],

9
src/components/Frame.tsx Normal file
View File

@@ -0,0 +1,9 @@
import type { ReactNode } from 'react';
export interface FrameProps {
children?: ReactNode;
}
export function Frame(props: FrameProps) {
return <div style={{ width: 300, height: 300 }}>{props.children}</div>;
}

View File

@@ -0,0 +1,12 @@
export interface ToggleButtonProps {
onClick?: () => void;
active?: boolean;
}
export function ToggleButton(props: ToggleButtonProps) {
return (
<button type="button" onClick={props.onClick}>
{props.active ? 'ON' : 'OFF'}
</button>
);
}

30
src/hooks/useDomain.ts Normal file
View File

@@ -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<string | null>(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);
}

View File

@@ -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,
};
}

4
src/hooks/useVersion.ts Normal file
View File

@@ -0,0 +1,4 @@
export function useVersion(ops?: { prefixed?: boolean }) {
const prefix = ops?.prefixed ? 'v' : '';
return `${prefix}${chrome.runtime.getManifest().version}`;
}

View File

@@ -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<string[]>(
'domainWhitelist',
(v) => v ?? DEFAULT_DOMAIN_WHITELIST,
);
const [error, setError] = useState<string | null>(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 (
<div style={{ width: 300 }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<h1 style={{ flexGrow: 1 }}>movie-web</h1>
<h3>v{chrome.runtime.getManifest().version}</h3>
</div>
<h2 style={{ marginTop: 0 }}>Domains</h2>
<div>
<div>
<input type="text" value={domainInput} onChange={(e) => setDomainInput(e.target.value)} />
<button type="button" onClick={handleDomainSubmit}>
Save
</button>
</div>
{error && <span style={{ fontWeight: 'bold' }}>{error}</span>}
<table>
<tbody>
{domainWhiteist.map((domain) => (
<tr key={domain}>
<td>{domain}</td>
<td>
<button type="button" onClick={() => setDomainWhitelist(domainWhiteist.filter((d) => d !== domain))}>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<Frame>
<ToggleButton active={isWhitelisted} onClick={toggle} />
{!domain ? <p>Cant use extension on this page</p> : null}
<h3>{version} - movie-web</h3>
</Frame>
);
}

9
src/utils/domains.ts Normal file
View File

@@ -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;
}
}

View File

@@ -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<string[]>('domainWhitelist');
return whitelist?.some((d) => d.includes(domain)) ?? false;
};
export const validateDomainWhiteList = async (domain: string) => {
export function useDomainStorage() {
return useStorage<string[]>('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');
};