mirror of
https://github.com/movie-web/extension.git
synced 2025-09-13 10:23:24 +00:00
Functionality and state for popout
This commit is contained in:
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
@@ -40,7 +40,8 @@
|
|||||||
},
|
},
|
||||||
"manifest": {
|
"manifest": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"declarativeNetRequest"
|
"declarativeNetRequest",
|
||||||
|
"tabs"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
|
@@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging';
|
|||||||
|
|
||||||
import type { BaseRequest } from '~types/request';
|
import type { BaseRequest } from '~types/request';
|
||||||
import type { BaseResponse } from '~types/response';
|
import type { BaseResponse } from '~types/response';
|
||||||
import { validateDomainWhiteList } from '~utils/storage';
|
import { assertDomainWhitelist } from '~utils/storage';
|
||||||
|
|
||||||
type Response = BaseResponse<{
|
type Response = BaseResponse<{
|
||||||
version: string;
|
version: string;
|
||||||
@@ -10,7 +10,7 @@ type Response = BaseResponse<{
|
|||||||
|
|
||||||
const handler: PlasmoMessaging.MessageHandler<BaseRequest, Response> = async (req, res) => {
|
const handler: PlasmoMessaging.MessageHandler<BaseRequest, Response> = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await validateDomainWhiteList(req.body.requestDomain);
|
await assertDomainWhitelist(req.body.requestDomain);
|
||||||
|
|
||||||
const version = chrome.runtime.getManifest().version;
|
const version = chrome.runtime.getManifest().version;
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging';
|
|||||||
import type { BaseRequest } from '~types/request';
|
import type { BaseRequest } from '~types/request';
|
||||||
import type { BaseResponse } from '~types/response';
|
import type { BaseResponse } from '~types/response';
|
||||||
import { makeFullUrl } from '~utils/fetcher';
|
import { makeFullUrl } from '~utils/fetcher';
|
||||||
import { validateDomainWhiteList } from '~utils/storage';
|
import { assertDomainWhitelist } from '~utils/storage';
|
||||||
|
|
||||||
export interface Request extends BaseRequest {
|
export interface Request extends BaseRequest {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
@@ -26,7 +26,7 @@ type Response<T> = BaseResponse<{
|
|||||||
|
|
||||||
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (req, res) => {
|
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await validateDomainWhiteList(req.body.requestDomain);
|
await assertDomainWhitelist(req.body.requestDomain);
|
||||||
|
|
||||||
const response = await fetch(makeFullUrl(req.body.url, req.body), {
|
const response = await fetch(makeFullUrl(req.body.url, req.body), {
|
||||||
method: req.body.method,
|
method: req.body.method,
|
||||||
|
@@ -2,7 +2,7 @@ import type { PlasmoMessaging } from '@plasmohq/messaging';
|
|||||||
|
|
||||||
import type { BaseRequest } from '~types/request';
|
import type { BaseRequest } from '~types/request';
|
||||||
import type { BaseResponse } from '~types/response';
|
import type { BaseResponse } from '~types/response';
|
||||||
import { validateDomainWhiteList } from '~utils/storage';
|
import { assertDomainWhitelist } from '~utils/storage';
|
||||||
|
|
||||||
interface Request extends BaseRequest {
|
interface Request extends BaseRequest {
|
||||||
ruleId: number;
|
ruleId: number;
|
||||||
@@ -23,7 +23,7 @@ const mapHeadersToDeclarativeNetRequestHeaders = (
|
|||||||
|
|
||||||
const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => {
|
const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await validateDomainWhiteList(req.body.requestDomain);
|
await assertDomainWhitelist(req.body.requestDomain);
|
||||||
|
|
||||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
await chrome.declarativeNetRequest.updateDynamicRules({
|
||||||
removeRuleIds: [req.body.ruleId],
|
removeRuleIds: [req.body.ruleId],
|
||||||
|
9
src/components/Frame.tsx
Normal file
9
src/components/Frame.tsx
Normal 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>;
|
||||||
|
}
|
12
src/components/ToggleButton.tsx
Normal file
12
src/components/ToggleButton.tsx
Normal 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
30
src/hooks/useDomain.ts
Normal 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);
|
||||||
|
}
|
38
src/hooks/useDomainWhitelist.ts
Normal file
38
src/hooks/useDomainWhitelist.ts
Normal 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
4
src/hooks/useVersion.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export function useVersion(ops?: { prefixed?: boolean }) {
|
||||||
|
const prefix = ops?.prefixed ? 'v' : '';
|
||||||
|
return `${prefix}${chrome.runtime.getManifest().version}`;
|
||||||
|
}
|
@@ -1,61 +1,20 @@
|
|||||||
import { useStorage } from '@plasmohq/storage/hook';
|
import { Frame } from '~components/Frame';
|
||||||
import { useState } from 'react';
|
import { ToggleButton } from '~components/ToggleButton';
|
||||||
|
import { useDomain } from '~hooks/useDomain';
|
||||||
import { DEFAULT_DOMAIN_WHITELIST } from '~utils/storage';
|
import { useToggleWhitelistDomain } from '~hooks/useDomainWhitelist';
|
||||||
|
import { useVersion } from '~hooks/useVersion';
|
||||||
|
|
||||||
function IndexPopup() {
|
function IndexPopup() {
|
||||||
const [domainInput, setDomainInput] = useState('');
|
const domain = useDomain();
|
||||||
const [domainWhiteist, setDomainWhitelist] = useStorage<string[]>(
|
const { isWhitelisted, toggle } = useToggleWhitelistDomain(domain);
|
||||||
'domainWhitelist',
|
const version = useVersion({ prefixed: true });
|
||||||
(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');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: 300 }}>
|
<Frame>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<ToggleButton active={isWhitelisted} onClick={toggle} />
|
||||||
<h1 style={{ flexGrow: 1 }}>movie-web</h1>
|
{!domain ? <p>Cant use extension on this page</p> : null}
|
||||||
|
<h3>{version} - movie-web</h3>
|
||||||
<h3>v{chrome.runtime.getManifest().version}</h3>
|
</Frame>
|
||||||
</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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
src/utils/domains.ts
Normal file
9
src/utils/domains.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,24 @@
|
|||||||
import { Storage } from '@plasmohq/storage';
|
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 DEFAULT_DOMAIN_WHITELIST = ['https://movie-web.app', 'http://localhost:5173'];
|
||||||
|
|
||||||
export const storage = new Storage();
|
export const storage = new Storage();
|
||||||
|
|
||||||
export const domainIsInWhitelist = async (domain: string) => {
|
const domainIsInWhitelist = async (domain: string) => {
|
||||||
const whitelist = await storage.get<string[]>('domainWhitelist');
|
const whitelist = await storage.get<string[]>('domainWhitelist');
|
||||||
return whitelist?.some((d) => d.includes(domain)) ?? false;
|
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);
|
const isWhiteListed = await domainIsInWhitelist(domain);
|
||||||
if (!isWhiteListed) throw new Error('Domain is not whitelisted');
|
if (!isWhiteListed) throw new Error('Domain is not whitelisted');
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user