Skip to content

Commit

Permalink
feat: 支持同步 storages
Browse files Browse the repository at this point in the history
  • Loading branch information
oustn committed Nov 19, 2024
1 parent c30c546 commit 0607bfd
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 62 deletions.
33 changes: 30 additions & 3 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Runtime } from '@src/core';
import { onStorageChanged, resolveConfig } from '@src/common';
import { onStorageChanged, RESOLVE_STORAGE, resolveConfig } from '@src/common';
import { Target } from '@src/types';

declare const contentScripts: Array<{ js: Array<string>, css: Array<string> }>;
Expand Down Expand Up @@ -47,7 +47,7 @@ function generateContentScriptOptions(hosts: Array<string>): chrome.scripting.Re
...content,
id: `content-script-${index + 1}`,
runAt: 'document_end',
matches: hosts.map(host => `${host}/modeling/*`),
matches: hosts.map(host => `${host}/*`),
}));
}

Expand All @@ -58,11 +58,38 @@ async function registerContentScripts(rules: Record<string, Target[]>) {
return;
}
const hosts = Object.keys(rules);
const targets = Object.values(rules).flat().filter(d => d.activated).map(d => d.host)
if (!hosts.length) return;
await chrome.scripting.registerContentScripts(generateContentScriptOptions(hosts));
await chrome.scripting.registerContentScripts(generateContentScriptOptions([...hosts, ...targets]));
console.log('registerContentScripts');
}

onStorageChanged(registerContentScripts);

resolveConfig().then(config => registerContentScripts(config.rules))

async function resolveStorage(host: string, site: string) {
return new self.Promise((resolve) => {
chrome.tabs.query({ url: `${host}/*` }, async (tabs) => {
if (tabs.length > 0) {
console.log('try to resolve tab for storage', host)
const storage = chrome.tabs.sendMessage(tabs[0].id!, { type: RESOLVE_STORAGE, target: site });
storage.then(d => resolve(d)).catch(e => {
console.log('resolve storage error', e)
resolve(null)
})
} else {
resolve(null)
}
});
})
}

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.type === RESOLVE_STORAGE) {
const { source } = message.data || {}
if (!source) return
resolveStorage(source, message.site).then(data => sendResponse(data))
return true
}
});
3 changes: 2 additions & 1 deletion src/common/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const CONFIG_CHANGE_EVENT = 'configChanged';
export const OPEN_EVENT = 'openEvent'
export const SPY_ID = 'cookie-sharer-spy'
export const RESPONSE_MESSAGE = 'RESPONSE_MESSAGE';

export const SYNC_STORAGE = 'SYNC_STORAGE'
export const RESOLVE_STORAGE = 'RESOLVE_STORAGE'
68 changes: 67 additions & 1 deletion src/contents/dev/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { CONFIG_CHANGE_EVENT, onStorageChanged, SPY_ID, URL_CHANGE_MESSAGE } from '@src/common';
import {
CONFIG_CHANGE_EVENT,
onStorageChanged,
RESOLVE_STORAGE,
resolveConfig,
SPY_ID, SYNC_STORAGE,
URL_CHANGE_MESSAGE,
} from '@src/common';
import { process } from './actions';

import './index.scss';
Expand Down Expand Up @@ -45,3 +52,62 @@ function launch(path: string, query: string) {
console.log('🚀');
process(path, query);
}

async function resolveStorage() {
return new self.Promise((resolve) => {
const fn = (event: MessageEvent<{
type: string,
payload: { local: Record<string, unknown>, session: Record<string, unknown> }
}>) => {
window.removeEventListener('message', fn)
// We only accept messages from ourselves
if (event.source !== window) {
return;
}

if (event?.data?.type !== RESOLVE_STORAGE) {
return;
}

const data = event.data.payload || {};
resolve(data)
}

window.addEventListener('message', fn)
const event = new Event(RESOLVE_STORAGE);
spy.dispatchEvent(event);
})
}

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.type === RESOLVE_STORAGE) {
console.log('Resolve storage message for', message.target)
resolveStorage().then(data => sendResponse(data))
return true
}
});

(async () => {
const host = window.location.origin;
const config = await resolveConfig()
if (host in config.rules) {
return
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const source = Object.entries(config.rules).find(([_, value]) => {
return value.find(u => u.host === host && u.activated)
})?.[0]
if (!source) return
console.log(`Current host: ${host}, source: ${source}`, `Try to resolve ${source} storages`)
const storages = chrome.runtime.sendMessage({ type: RESOLVE_STORAGE, site: host, data: { source } });
storages.then(data => {
if (data) {
const event = new CustomEvent(SYNC_STORAGE, {
detail: data
});
spy.dispatchEvent(event);
}
}).catch((e) => {
console.log(`Resolve storage failed: ${e.message}`)
})
})();
41 changes: 37 additions & 4 deletions src/contents/inject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { CONFIG_CHANGE_EVENT, OPEN_EVENT, RESPONSE_MESSAGE, SPY_ID, URL_CHANGE_MESSAGE } from '@src/common';
import {
CONFIG_CHANGE_EVENT,
OPEN_EVENT,
RESPONSE_MESSAGE,
SPY_ID,
URL_CHANGE_MESSAGE,
SYNC_STORAGE, RESOLVE_STORAGE,
} from '@src/common';
import { MyXMLHttpRequest } from './xhr';

interface Payload {
Expand Down Expand Up @@ -93,13 +100,39 @@ if (spy) {

spy.addEventListener(OPEN_EVENT, (e: Event) => {
const { url, options } = (e as unknown as CustomEvent)?.detail ?? {};
let target = url
if (!url) return
let target = url;
if (!url) return;
if (options && options.withHash) {
target = `${url}${window.location.hash}`
target = `${url}${window.location.hash}`;
}
window.open(target, '_blank');
});

spy.addEventListener(RESOLVE_STORAGE, () => {
window.postMessage({
type: RESOLVE_STORAGE,
payload: {
local: Object.fromEntries(Object.entries(localStorage)),
session: Object.fromEntries(Object.entries(sessionStorage)),
},
}, '*');
});

spy.addEventListener(SYNC_STORAGE, (e: Event) => {
console.log(`Sync storage from spy`);
const { local, session } = (e as unknown as CustomEvent)?.detail ?? {};
if (local) {
Object.keys(local).forEach((key) => {
localStorage.setItem(key, local[key]);
});
}

if (session) {
Object.keys(session).forEach((key) => {
sessionStorage.setItem(key, session[key]);
});
}
});
}

MyXMLHttpRequest.use({
Expand Down
6 changes: 4 additions & 2 deletions src/contents/xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class MyXMLHttpRequest extends originalXhr {
key && headers.append(key, value);
});

const response = new Response(this.response, {
const response = new Response([101, 204, 205, 304].includes(this.status) ? null : this.response, {
status: this.status,
statusText: this.statusText,
headers: headers,
Expand Down Expand Up @@ -77,4 +77,6 @@ export class MyXMLHttpRequest extends originalXhr {
}
}

window.XMLHttpRequest = MyXMLHttpRequest.bind(window).bind(window)
if (window.location.pathname.includes('/modeling')) {
window.XMLHttpRequest = MyXMLHttpRequest.bind(window).bind(window)
}
104 changes: 53 additions & 51 deletions src/pages/popup/List.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import Container from '@mui/material/Container'
import Typography from '@mui/material/Typography'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import ListItemText from '@mui/material/ListItemText'
import ListSubheader from '@mui/material/ListSubheader'
import ListItemIcon from '@mui/material/ListItemIcon'
import IconButton from '@mui/material/IconButton'
import DeleteIcon from '@mui/icons-material/Delete'
import Switch from '@mui/material/Switch'
import Container from '@mui/material/Container';
import Typography from '@mui/material/Typography';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import ListSubheader from '@mui/material/ListSubheader';
import ListItemIcon from '@mui/material/ListItemIcon';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import Switch from '@mui/material/Switch';
import empty from '@src/assets/empty.svg';
import {resolveUrls} from "@src/common";
import { resolveUrls } from '@src/common';

import Cookie from './Cookie.tsx';
import type {Target} from "@src/types";
import type { Target } from '@src/types';

interface RuleListProps {
host: string
targets: Target[]
sources: Target[]
removeRule: (target: string) => void
isSource: boolean
isTarget: boolean
handleSuccess: () => void
handleActive: (target: string) => void
host: string;
targets: Target[];
sources: Target[];
removeRule: (target: string) => void;
isSource: boolean;
isTarget: boolean;
handleSuccess: () => void;
handleActive: (target: string) => void;
}

export default function RuleList({
Expand All @@ -32,7 +32,7 @@ export default function RuleList({
sources,
isSource,
handleSuccess,
handleActive
handleActive,
}: RuleListProps) {
if (!isTarget && !targets.length) {
return (
Expand All @@ -47,19 +47,19 @@ export default function RuleList({
alignItems: 'center',
justifyContent: 'center',
}}>
<img className="Empty" src={empty} alt="empty"/>
<Typography variant="caption" display="block" sx={{mt: 2}}>请添加规则</Typography>
<img className="Empty" src={empty} alt="empty" />
<Typography variant="caption" display="block" sx={{ mt: 2 }}>请添加规则</Typography>
</Container>
)
);
}

const header = isTarget ? 'Cookie 来自以下网站:' : '共享的网站列表:'
const header = isTarget ? 'Cookie 来自以下网站:' : '共享的网站列表:';

const handleNewTab = async (url: string) => {
chrome.tabs.create({url}).catch(e => {
console.log(e)
})
}
chrome.tabs.create({ url }).catch(e => {
console.log(e);
});
};

return (
<Container
Expand All @@ -71,30 +71,32 @@ export default function RuleList({
}}
>
<List
sx={{width: '100%', bgcolor: 'background.paper'}}
sx={{ width: '100%', bgcolor: 'background.paper' }}
subheader={<ListSubheader>{header}</ListSubheader>}
>
{(isTarget ? sources : targets).map(item => {
const target = item.host
const active = item.activated
const target = item.host;
const active = item.activated;

return (
<ListItem
key={target}
secondaryAction={[
(<IconButton
key="delete"
edge="end"
aria-label="delete"
onClick={() => {
removeRule(target)
removeRule(target);
}}
>
<DeleteIcon/>
<DeleteIcon />
</IconButton>),
isTarget && <Cookie
host={target}
handleSuccess={handleSuccess}
/>
key="show-cookie"
host={target}
handleSuccess={handleSuccess}
/>,
]}
sx={{
pl: 0,
Expand All @@ -103,39 +105,39 @@ export default function RuleList({
>
{
<ListItemIcon>
<Switch
disabled={!isTarget}
edge="end"
onChange={() => handleActive(target)}
checked={active}
inputProps={{
'aria-labelledby': 'active-rule',
}}
/>
<Switch
disabled={!isTarget}
edge="end"
onChange={() => handleActive(target)}
checked={active}
inputProps={{
'aria-labelledby': 'active-rule',
}}
/>
</ListItemIcon>
}
<ListItemText
primary={target}
title={target}
secondary={isSource ? resolveUrls(target)?.map(d => (
<span
style={{display: 'block'}}
style={{ display: 'block' }}
key={d}>{d}</span>)) : ''
}
sx={{
cursor: 'pointer',
pr: isTarget ? '24px' : 0,
span: {
overflow: 'hidden',
textOverflow: 'ellipsis'
}
textOverflow: 'ellipsis',
},
}}
onClick={() => handleNewTab(target)}
/>
</ListItem>
)
);
})}
</List>
</Container>
)
);
}

0 comments on commit 0607bfd

Please sign in to comment.