mirror of
https://github.com/p-stream/extension.git
synced 2026-04-14 13:10:34 +00:00
fix: lint check
This commit is contained in:
parent
b8a678d46c
commit
4772efdd83
11 changed files with 240 additions and 290 deletions
|
|
@ -38,16 +38,21 @@ Safari requires manual permission setup:
|
|||
### 3. Common Safari Issues & Fixes
|
||||
|
||||
#### Issue: "Invalid call to runtime.connect()"
|
||||
|
||||
**Fix**: This occurs when the background script isn't ready. The extension now includes:
|
||||
|
||||
- Delayed messaging setup for Safari
|
||||
- Retry logic for failed connections
|
||||
- Better error handling
|
||||
|
||||
#### Issue: WebSocket Connection Blocked
|
||||
|
||||
**Fix**: Use `pnpm dev:safari` which disables hot-reload WebSockets that Safari blocks.
|
||||
|
||||
#### Issue: Permissions Not Working
|
||||
**Fix**:
|
||||
|
||||
**Fix**:
|
||||
|
||||
- Ensure both `host_permissions` and `optional_host_permissions` are in manifest
|
||||
- Guide users through Safari's manual permission setup
|
||||
- Check permissions through Safari Preferences, not runtime API
|
||||
|
|
@ -71,6 +76,7 @@ Safari requires manual permission setup:
|
|||
### 6. Production Deployment
|
||||
|
||||
For Safari App Store distribution:
|
||||
|
||||
1. You'll need to create a native macOS app wrapper
|
||||
2. Use Xcode to create the Safari extension project
|
||||
3. Follow Apple's Safari extension guidelines
|
||||
|
|
|
|||
|
|
@ -1,56 +1,47 @@
|
|||
import '@plasmohq/messaging/background'
|
||||
import '@plasmohq/messaging/background';
|
||||
import { getBrowserAPI, isChrome, isFirefox, isSafari } from '~utils/extension';
|
||||
|
||||
import { getBrowserAPI, isChrome, isFirefox, isSafari } from '~utils/extension'
|
||||
const browserAPI = getBrowserAPI();
|
||||
|
||||
// Initialize Plasmo messaging system
|
||||
// This ensures that the messaging handlers are properly set up
|
||||
console.log('Background script loaded')
|
||||
|
||||
const browserAPI = getBrowserAPI()
|
||||
|
||||
// Safari has different startup behavior, so we need to handle it differently
|
||||
if (isSafari()) {
|
||||
// Safari doesn't need the reload behavior that Chrome/Firefox require
|
||||
console.log('Running on Safari')
|
||||
console.log('Running on Safari');
|
||||
|
||||
// Ensure messaging is ready for Safari
|
||||
browserAPI.runtime.onInstalled.addListener(() => {
|
||||
console.log('Safari extension installed/updated')
|
||||
console.log('Safari extension installed/updated');
|
||||
// Give Safari time to initialize messaging
|
||||
setTimeout(() => {
|
||||
console.log('Safari messaging should be ready')
|
||||
}, 1000)
|
||||
})
|
||||
console.log('Safari messaging should be ready');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Handle Safari runtime errors more gracefully
|
||||
browserAPI.runtime.onConnect.addListener(port => {
|
||||
console.log('Safari port connected:', port.name)
|
||||
browserAPI.runtime.onConnect.addListener((port) => {
|
||||
console.log('Safari port connected:', port.name);
|
||||
port.onDisconnect.addListener(() => {
|
||||
if (browserAPI.runtime.lastError) {
|
||||
console.log(
|
||||
'Safari port disconnected with error:',
|
||||
browserAPI.runtime.lastError.message
|
||||
)
|
||||
console.log('Safari port disconnected with error:', browserAPI.runtime.lastError.message);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
} else if (isChrome()) {
|
||||
// Both brave and chrome for some reason need this extension reload,
|
||||
// If this isn't done, they will never load properly and will fail updateDynamicRules()
|
||||
chrome.runtime.onStartup.addListener(() => {
|
||||
chrome.runtime.reload()
|
||||
})
|
||||
chrome.runtime.reload();
|
||||
});
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
console.log('Chrome extension installed/updated')
|
||||
})
|
||||
console.log('Chrome extension installed/updated');
|
||||
});
|
||||
} else if (isFirefox()) {
|
||||
// Firefox behavior
|
||||
browser.runtime.onStartup.addListener(() => {
|
||||
browser.runtime.reload()
|
||||
})
|
||||
browser.runtime.reload();
|
||||
});
|
||||
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
console.log('Firefox extension installed/updated')
|
||||
})
|
||||
console.log('Firefox extension installed/updated');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +1,60 @@
|
|||
import type { PlasmoMessaging } from '@plasmohq/messaging'
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import type { BaseRequest } from '~types/request'
|
||||
import type { BaseResponse } from '~types/response'
|
||||
import {
|
||||
removeDynamicRules,
|
||||
setDynamicRules
|
||||
} from '~utils/declarativeNetRequest'
|
||||
import { getBrowserAPI, isFirefox, isSafari } from '~utils/extension'
|
||||
import { makeFullUrl } from '~utils/fetcher'
|
||||
import { assertDomainWhitelist, canAccessCookies } from '~utils/storage'
|
||||
import type { BaseRequest } from '~types/request';
|
||||
import type { BaseResponse } from '~types/response';
|
||||
import { removeDynamicRules, setDynamicRules } from '~utils/declarativeNetRequest';
|
||||
import { isFirefox } from '~utils/extension';
|
||||
import { makeFullUrl } from '~utils/fetcher';
|
||||
import { assertDomainWhitelist, canAccessCookies } from '~utils/storage';
|
||||
|
||||
const MAKE_REQUEST_DYNAMIC_RULE = 23498
|
||||
const MAKE_REQUEST_DYNAMIC_RULE = 23498;
|
||||
|
||||
export interface Request extends BaseRequest {
|
||||
baseUrl?: string
|
||||
headers?: Record<string, string>
|
||||
method?: string
|
||||
query?: Record<string, string>
|
||||
readHeaders?: Record<string, string>
|
||||
url: string
|
||||
body?: any
|
||||
bodyType?: 'string' | 'FormData' | 'URLSearchParams' | 'object'
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
method?: string;
|
||||
query?: Record<string, string>;
|
||||
readHeaders?: Record<string, string>;
|
||||
url: string;
|
||||
body?: any;
|
||||
bodyType?: 'string' | 'FormData' | 'URLSearchParams' | 'object';
|
||||
}
|
||||
|
||||
type Response<T> = BaseResponse<{
|
||||
response: {
|
||||
statusCode: number
|
||||
headers: Record<string, string>
|
||||
finalUrl: string
|
||||
body: T
|
||||
}
|
||||
}>
|
||||
statusCode: number;
|
||||
headers: Record<string, string>;
|
||||
finalUrl: string;
|
||||
body: T;
|
||||
};
|
||||
}>;
|
||||
|
||||
const mapBodyToFetchBody = (
|
||||
body: Request['body'],
|
||||
bodyType: Request['bodyType']
|
||||
): BodyInit => {
|
||||
const mapBodyToFetchBody = (body: Request['body'], bodyType: Request['bodyType']): BodyInit => {
|
||||
if (bodyType === 'FormData') {
|
||||
const formData = new FormData()
|
||||
const formData = new FormData();
|
||||
body.forEach(([key, value]: [any, any]) => {
|
||||
formData.append(key, value.toString())
|
||||
})
|
||||
formData.append(key, value.toString());
|
||||
});
|
||||
}
|
||||
if (bodyType === 'URLSearchParams') {
|
||||
return new URLSearchParams(body)
|
||||
return new URLSearchParams(body);
|
||||
}
|
||||
if (bodyType === 'object') {
|
||||
return JSON.stringify(body)
|
||||
return JSON.stringify(body);
|
||||
}
|
||||
if (bodyType === 'string') {
|
||||
return body
|
||||
return body;
|
||||
}
|
||||
return body
|
||||
}
|
||||
return body;
|
||||
};
|
||||
|
||||
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (
|
||||
req,
|
||||
res
|
||||
) => {
|
||||
const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (req, res) => {
|
||||
try {
|
||||
if (!req.sender?.tab?.url)
|
||||
throw new Error('No tab URL found in the request.')
|
||||
if (!req.body) throw new Error('No request body found in the request.')
|
||||
if (!req.sender?.tab?.url) throw new Error('No tab URL found in the request.');
|
||||
if (!req.body) throw new Error('No request body found in the request.');
|
||||
|
||||
const url = makeFullUrl(req.body.url, req.body)
|
||||
await assertDomainWhitelist(req.sender.tab.url)
|
||||
const url = makeFullUrl(req.body.url, req.body);
|
||||
await assertDomainWhitelist(req.sender.tab.url);
|
||||
|
||||
await setDynamicRules({
|
||||
ruleId: MAKE_REQUEST_DYNAMIC_RULE,
|
||||
|
|
@ -73,28 +63,26 @@ const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (
|
|||
// set Access-Control-Allow-Credentials if the reqested host has access to cookies
|
||||
responseHeaders: {
|
||||
...(canAccessCookies(new URL(url).hostname) && {
|
||||
'Access-Control-Allow-Credentials': 'true'
|
||||
})
|
||||
}
|
||||
})
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: req.body.method,
|
||||
headers: req.body.headers,
|
||||
body: mapBodyToFetchBody(req.body.body, req.body.bodyType)
|
||||
})
|
||||
await removeDynamicRules([MAKE_REQUEST_DYNAMIC_RULE])
|
||||
const contentType = response.headers.get('content-type')
|
||||
const body = contentType?.includes('application/json')
|
||||
? await response.json()
|
||||
: await response.text()
|
||||
body: mapBodyToFetchBody(req.body.body, req.body.bodyType),
|
||||
});
|
||||
await removeDynamicRules([MAKE_REQUEST_DYNAMIC_RULE]);
|
||||
const contentType = response.headers.get('content-type');
|
||||
const body = contentType?.includes('application/json') ? await response.json() : await response.text();
|
||||
|
||||
const cookies = await (chrome || browser).cookies.getAll({
|
||||
url: response.url,
|
||||
...(isFirefox() && {
|
||||
firstPartyDomain: new URL(response.url).hostname
|
||||
})
|
||||
})
|
||||
firstPartyDomain: new URL(response.url).hostname,
|
||||
}),
|
||||
});
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
|
|
@ -104,22 +92,20 @@ const handler: PlasmoMessaging.MessageHandler<Request, Response<any>> = async (
|
|||
...Object.fromEntries(response.headers.entries()),
|
||||
// include cookies if allowed for the reqested host
|
||||
...(canAccessCookies(new URL(url).hostname) && {
|
||||
'Set-Cookie': cookies
|
||||
.map(cookie => `${cookie.name}=${cookie.value}`)
|
||||
.join(', ')
|
||||
})
|
||||
'Set-Cookie': cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join(', '),
|
||||
}),
|
||||
},
|
||||
body,
|
||||
finalUrl: response.url
|
||||
}
|
||||
})
|
||||
finalUrl: response.url,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('failed request', err)
|
||||
console.error('failed request', err);
|
||||
res.send({
|
||||
success: false,
|
||||
error: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default handler
|
||||
export default handler;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { PlasmoMessaging } from '@plasmohq/messaging'
|
||||
import type { PlasmoMessaging } from '@plasmohq/messaging';
|
||||
|
||||
import type { BaseRequest } from '~types/request'
|
||||
import type { BaseResponse } from '~types/response'
|
||||
import { getBrowserAPI } from '~utils/extension'
|
||||
import type { BaseRequest } from '~types/request';
|
||||
import type { BaseResponse } from '~types/response';
|
||||
import { getBrowserAPI } from '~utils/extension';
|
||||
|
||||
type Request = BaseRequest & {
|
||||
page: string;
|
||||
|
|
@ -14,15 +14,13 @@ const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (re
|
|||
if (!req.sender?.tab?.id) throw new Error('No tab ID found in the request.');
|
||||
if (!req.body) throw new Error('No body found in the request.');
|
||||
|
||||
const searchParams = new URLSearchParams()
|
||||
searchParams.set('redirectUrl', req.body.redirectUrl)
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('redirectUrl', req.body.redirectUrl);
|
||||
|
||||
const browserAPI = getBrowserAPI()
|
||||
const url = browserAPI.runtime.getURL(
|
||||
`/tabs/${req.body.page}.html?${searchParams.toString()}`
|
||||
)
|
||||
const browserAPI = getBrowserAPI();
|
||||
const url = browserAPI.runtime.getURL(`/tabs/${req.body.page}.html?${searchParams.toString()}`);
|
||||
await (browserAPI.tabs as any).update(req.sender.tab.id, {
|
||||
url
|
||||
url,
|
||||
});
|
||||
|
||||
res.send({
|
||||
|
|
@ -34,6 +32,6 @@ const handler: PlasmoMessaging.MessageHandler<Request, BaseResponse> = async (re
|
|||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useCallback } from 'react';
|
|||
|
||||
import { Button } from '~components/Button';
|
||||
import { Icon } from '~components/Icon';
|
||||
import { isSafari } from '~utils/extension';
|
||||
import { usePermission } from '~hooks/usePermission';
|
||||
import { isSafari } from '~utils/extension';
|
||||
|
||||
import '../tabs/PermissionRequest.css';
|
||||
|
||||
|
|
@ -40,59 +40,47 @@ export default function SafariPermissionGuide() {
|
|||
return (
|
||||
<div className="container permission-request">
|
||||
<div className="inner-container">
|
||||
<h1 className="color-white">
|
||||
Safari Setup Required
|
||||
</h1>
|
||||
<h1 className="color-white">Safari Setup Required</h1>
|
||||
<p className="text-color paragraph">
|
||||
Safari extensions require manual setup. Please follow these steps to enable the P-Stream extension:
|
||||
</p>
|
||||
|
||||
<div className="card-list" style={{ marginTop: '2.5rem' }}>
|
||||
<Card
|
||||
icon={<Icon name="shield" />}
|
||||
purple
|
||||
>
|
||||
<Card icon={<Icon name="shield" />} purple>
|
||||
<div>
|
||||
<h3 className="card-title">Step 1: Enable Extension</h3>
|
||||
<p className="card-description">
|
||||
Open Safari → Preferences → Extensions, then enable "P-Stream extension"
|
||||
Open Safari → Preferences → Extensions, then enable "P-Stream extension"
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={<Icon name="network" />}
|
||||
>
|
||||
<Card icon={<Icon name="network" />}>
|
||||
<div>
|
||||
<h3 className="card-title">Step 2: Grant Website Access</h3>
|
||||
<p className="card-description">
|
||||
Go to Safari → Preferences → Websites → P-Stream extension → Set to "Allow on all websites" or add specific sites
|
||||
Go to Safari → Preferences → Websites → P-Stream extension → Set to "Allow on all websites" or
|
||||
add specific sites
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
icon={<Icon name="power" />}
|
||||
>
|
||||
<Card icon={<Icon name="power" />}>
|
||||
<div>
|
||||
<h3 className="card-title">Step 3: Reload Pages</h3>
|
||||
<p className="card-description">
|
||||
Reload any streaming website pages where you want to use the extension
|
||||
</p>
|
||||
<p className="card-description">Reload any streaming website pages where you want to use the extension</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="button-spacing">
|
||||
<Button onClick={handleSafariSetup}>
|
||||
Test Extension Setup
|
||||
</Button>
|
||||
<Button onClick={handleSafariSetup}>Test Extension Setup</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '1.5rem' }}>
|
||||
<p className="text-color paragraph" style={{ fontSize: '0.9rem' }}>
|
||||
<strong>Note:</strong> Safari handles extension permissions differently than Chrome.
|
||||
These steps are required for the extension to work properly with streaming websites.
|
||||
<strong>Note:</strong> Safari handles extension permissions differently than Chrome. These steps are
|
||||
required for the extension to work properly with streaming websites.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,49 +1,48 @@
|
|||
import { relayMessage } from '@plasmohq/messaging'
|
||||
import type { PlasmoCSConfig } from 'plasmo'
|
||||
import { relayMessage } from '@plasmohq/messaging';
|
||||
import type { PlasmoCSConfig } from 'plasmo';
|
||||
|
||||
export const config: PlasmoCSConfig = {
|
||||
matches: ['<all_urls>']
|
||||
}
|
||||
matches: ['<all_urls>'],
|
||||
};
|
||||
|
||||
// Safari requires a delay before setting up messaging
|
||||
const isSafari = () => {
|
||||
try {
|
||||
return (
|
||||
chrome.runtime.getURL('').startsWith('safari-web-extension://') ||
|
||||
(typeof browser !== 'undefined' &&
|
||||
browser.runtime.getURL('').startsWith('safari-web-extension://'))
|
||||
)
|
||||
(typeof browser !== 'undefined' && browser.runtime.getURL('').startsWith('safari-web-extension://'))
|
||||
);
|
||||
} catch {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setupMessaging = () => {
|
||||
try {
|
||||
relayMessage({
|
||||
name: 'hello'
|
||||
})
|
||||
name: 'hello',
|
||||
});
|
||||
|
||||
relayMessage({
|
||||
name: 'makeRequest'
|
||||
})
|
||||
name: 'makeRequest',
|
||||
});
|
||||
|
||||
relayMessage({
|
||||
name: 'prepareStream'
|
||||
})
|
||||
name: 'prepareStream',
|
||||
});
|
||||
|
||||
relayMessage({
|
||||
name: 'openPage'
|
||||
})
|
||||
name: 'openPage',
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Failed to setup messaging, retrying...', error)
|
||||
setTimeout(setupMessaging, 1000)
|
||||
console.log('Failed to setup messaging, retrying...', error);
|
||||
setTimeout(setupMessaging, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Safari needs a delay to ensure background script is ready
|
||||
if (isSafari()) {
|
||||
setTimeout(setupMessaging, 500)
|
||||
setTimeout(setupMessaging, 500);
|
||||
} else {
|
||||
setupMessaging()
|
||||
setupMessaging();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,7 @@ export function useToggleWhitelistDomain(domain: string | null) {
|
|||
const { domainWhitelist, addDomain, removeDomain } = useDomainWhitelist();
|
||||
const isWhitelisted = domainWhitelist.includes(domain ?? '');
|
||||
const { grantPermission } = usePermission();
|
||||
const iconPath = (chrome || browser).runtime.getURL(
|
||||
isWhitelisted ? 'assets/active.png' : 'assets/inactive.png',
|
||||
);
|
||||
const iconPath = (chrome || browser).runtime.getURL(isWhitelisted ? 'assets/active.png' : 'assets/inactive.png');
|
||||
|
||||
(chrome || browser).action.setIcon({
|
||||
path: iconPath,
|
||||
|
|
|
|||
|
|
@ -1,81 +1,83 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getBrowserAPI, isSafari } from '~utils/extension'
|
||||
import { useDomainWhitelist } from './useDomainWhitelist'
|
||||
import { getBrowserAPI, isSafari } from '~utils/extension';
|
||||
|
||||
export async function hasPermission () {
|
||||
const browserAPI = getBrowserAPI()
|
||||
import { useDomainWhitelist } from './useDomainWhitelist';
|
||||
|
||||
export async function hasPermission() {
|
||||
const browserAPI = getBrowserAPI();
|
||||
|
||||
if (isSafari()) {
|
||||
try {
|
||||
const tabs = await (browserAPI.tabs as any).query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
})
|
||||
return tabs && tabs.length > 0
|
||||
currentWindow: true,
|
||||
});
|
||||
return tabs && tabs.length > 0;
|
||||
} catch (error) {
|
||||
console.log('Safari permission check failed:', error)
|
||||
return false
|
||||
console.log('Safari permission check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await (browserAPI.permissions as any).contains({
|
||||
origins: ['<all_urls>']
|
||||
})
|
||||
origins: ['<all_urls>'],
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Permission check failed:', error)
|
||||
return false
|
||||
console.log('Permission check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function usePermission () {
|
||||
const { addDomain } = useDomainWhitelist()
|
||||
const [permission, setPermission] = useState(false)
|
||||
export function usePermission() {
|
||||
const { addDomain } = useDomainWhitelist();
|
||||
const [permission, setPermission] = useState(false);
|
||||
|
||||
const grantPermission = useCallback(
|
||||
async (domain?: string) => {
|
||||
const browserAPI = getBrowserAPI()
|
||||
const browserAPI = getBrowserAPI();
|
||||
|
||||
if (isSafari()) {
|
||||
const hasPerms = await hasPermission()
|
||||
const hasPerms = await hasPermission();
|
||||
if (!hasPerms) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert(
|
||||
'To use this extension, please:\n\n' +
|
||||
'1. Open Safari Preferences\n' +
|
||||
'2. Go to the Websites tab\n' +
|
||||
'3. Find "P-Stream extension" in the left sidebar\n' +
|
||||
'4. Set it to "Allow" for the websites you want to use\n\n' +
|
||||
'You may also need to enable the extension in Safari > Preferences > Extensions'
|
||||
)
|
||||
return false
|
||||
'You may also need to enable the extension in Safari > Preferences > Extensions',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
setPermission(true)
|
||||
if (domain) addDomain(domain)
|
||||
return true
|
||||
setPermission(true);
|
||||
if (domain) addDomain(domain);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const granted = await (browserAPI.permissions as any).request({
|
||||
origins: ['<all_urls>']
|
||||
})
|
||||
setPermission(granted)
|
||||
if (granted && domain) addDomain(domain)
|
||||
return granted
|
||||
origins: ['<all_urls>'],
|
||||
});
|
||||
setPermission(granted);
|
||||
if (granted && domain) addDomain(domain);
|
||||
return granted;
|
||||
} catch (error) {
|
||||
console.log('Permission request failed:', error)
|
||||
return false
|
||||
console.log('Permission request failed:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[addDomain]
|
||||
)
|
||||
[addDomain],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
hasPermission().then(has => setPermission(has))
|
||||
}, [])
|
||||
hasPermission().then((has) => setPermission(has));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
hasPermission: permission,
|
||||
grantPermission
|
||||
}
|
||||
grantPermission,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
import { getBrowserAPI, isChrome, isSafari } from './extension'
|
||||
import { modifiableResponseHeaders } from './storage'
|
||||
import { getBrowserAPI, isChrome, isSafari } from './extension';
|
||||
|
||||
interface DynamicRule {
|
||||
ruleId: number
|
||||
targetDomains?: [string, ...string[]]
|
||||
targetRegex?: string
|
||||
requestHeaders?: Record<string, string>
|
||||
responseHeaders?: Record<string, string>
|
||||
ruleId: number;
|
||||
targetDomains?: [string, ...string[]];
|
||||
targetRegex?: string;
|
||||
requestHeaders?: Record<string, string>;
|
||||
responseHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
const mapHeadersToDeclarativeNetRequestHeaders = (
|
||||
headers: Record<string, string>,
|
||||
op: string
|
||||
op: string,
|
||||
): { header: string; operation: any; value: string }[] => {
|
||||
return Object.entries(headers).map(([name, value]) => ({
|
||||
header: name,
|
||||
operation: op,
|
||||
value
|
||||
}))
|
||||
}
|
||||
value,
|
||||
}));
|
||||
};
|
||||
|
||||
export const setDynamicRules = async (body: DynamicRule) => {
|
||||
const browserAPI = getBrowserAPI()
|
||||
const browserAPI = getBrowserAPI();
|
||||
|
||||
if (isChrome() || isSafari()) {
|
||||
await (browserAPI.declarativeNetRequest as any).updateDynamicRules({
|
||||
|
|
@ -31,44 +30,37 @@ export const setDynamicRules = async (body: DynamicRule) => {
|
|||
id: body.ruleId,
|
||||
condition: {
|
||||
...(body.targetDomains && { requestDomains: body.targetDomains }),
|
||||
...(body.targetRegex && { regexFilter: body.targetRegex })
|
||||
...(body.targetRegex && { regexFilter: body.targetRegex }),
|
||||
},
|
||||
action: {
|
||||
type: 'modifyHeaders',
|
||||
...(body.requestHeaders &&
|
||||
Object.keys(body.requestHeaders).length > 0
|
||||
...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0
|
||||
? {
|
||||
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(
|
||||
body.requestHeaders,
|
||||
'set'
|
||||
)
|
||||
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(body.requestHeaders, 'set'),
|
||||
}
|
||||
: {}),
|
||||
responseHeaders: [
|
||||
{
|
||||
header: 'Access-Control-Allow-Origin',
|
||||
operation: 'set',
|
||||
value: '*'
|
||||
value: '*',
|
||||
},
|
||||
{
|
||||
header: 'Access-Control-Allow-Methods',
|
||||
operation: 'set',
|
||||
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS'
|
||||
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
|
||||
},
|
||||
{
|
||||
header: 'Access-Control-Allow-Headers',
|
||||
operation: 'set',
|
||||
value: '*'
|
||||
value: '*',
|
||||
},
|
||||
...mapHeadersToDeclarativeNetRequestHeaders(
|
||||
body.responseHeaders ?? {},
|
||||
'set'
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
...mapHeadersToDeclarativeNetRequestHeaders(body.responseHeaders ?? {}, 'set'),
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
// Firefox
|
||||
await (browserAPI.declarativeNetRequest as any).updateDynamicRules({
|
||||
|
|
@ -78,57 +70,50 @@ export const setDynamicRules = async (body: DynamicRule) => {
|
|||
id: body.ruleId,
|
||||
condition: {
|
||||
...(body.targetDomains && { requestDomains: body.targetDomains }),
|
||||
...(body.targetRegex && { regexFilter: body.targetRegex })
|
||||
...(body.targetRegex && { regexFilter: body.targetRegex }),
|
||||
},
|
||||
action: {
|
||||
type: 'modifyHeaders',
|
||||
...(body.requestHeaders &&
|
||||
Object.keys(body.requestHeaders).length > 0
|
||||
...(body.requestHeaders && Object.keys(body.requestHeaders).length > 0
|
||||
? {
|
||||
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(
|
||||
body.requestHeaders,
|
||||
'set'
|
||||
)
|
||||
requestHeaders: mapHeadersToDeclarativeNetRequestHeaders(body.requestHeaders, 'set'),
|
||||
}
|
||||
: {}),
|
||||
responseHeaders: [
|
||||
{
|
||||
header: 'Access-Control-Allow-Origin',
|
||||
operation: 'set',
|
||||
value: '*'
|
||||
value: '*',
|
||||
},
|
||||
{
|
||||
header: 'Access-Control-Allow-Methods',
|
||||
operation: 'set',
|
||||
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS'
|
||||
value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
|
||||
},
|
||||
{
|
||||
header: 'Access-Control-Allow-Headers',
|
||||
operation: 'set',
|
||||
value: '*'
|
||||
value: '*',
|
||||
},
|
||||
...mapHeadersToDeclarativeNetRequestHeaders(
|
||||
body.responseHeaders ?? {},
|
||||
'set'
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
...mapHeadersToDeclarativeNetRequestHeaders(body.responseHeaders ?? {}, 'set'),
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (browserAPI.runtime.lastError?.message) {
|
||||
throw new Error(browserAPI.runtime.lastError.message)
|
||||
throw new Error(browserAPI.runtime.lastError.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const removeDynamicRules = async (ruleIds: number[]) => {
|
||||
const browserAPI = getBrowserAPI()
|
||||
const browserAPI = getBrowserAPI();
|
||||
await (browserAPI.declarativeNetRequest as any).updateDynamicRules({
|
||||
removeRuleIds: ruleIds
|
||||
})
|
||||
removeRuleIds: ruleIds,
|
||||
});
|
||||
if (browserAPI.runtime.lastError?.message) {
|
||||
throw new Error(browserAPI.runtime.lastError?.message ?? 'Unknown error')
|
||||
throw new Error(browserAPI.runtime.lastError?.message ?? 'Unknown error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
export const isChrome = () => {
|
||||
return chrome.runtime.getURL('').startsWith('chrome-extension://')
|
||||
}
|
||||
return chrome.runtime.getURL('').startsWith('chrome-extension://');
|
||||
};
|
||||
|
||||
export const isFirefox = () => {
|
||||
try {
|
||||
return browser.runtime.getURL('').startsWith('moz-extension://')
|
||||
return browser.runtime.getURL('').startsWith('moz-extension://');
|
||||
} catch {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const isSafari = () => {
|
||||
try {
|
||||
return (
|
||||
chrome.runtime.getURL('').startsWith('safari-web-extension://') ||
|
||||
browser.runtime.getURL('').startsWith('safari-web-extension://')
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getBrowserAPI = () => {
|
||||
if (isSafari()) {
|
||||
return typeof browser !== 'undefined' ? browser : chrome
|
||||
return typeof browser !== 'undefined' ? browser : chrome;
|
||||
}
|
||||
if (isFirefox()) {
|
||||
return browser
|
||||
return browser;
|
||||
}
|
||||
return chrome
|
||||
}
|
||||
return chrome;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,26 +1,23 @@
|
|||
import { getBrowserAPI, isChrome, isSafari } from './extension'
|
||||
import { getBrowserAPI } from './extension';
|
||||
|
||||
export function queryCurrentDomain (cb: (domain: string | null) => void) {
|
||||
export function queryCurrentDomain(cb: (domain: string | null) => void) {
|
||||
const handle = (tabUrl: string | undefined) => {
|
||||
if (!tabUrl) cb(null)
|
||||
else cb(tabUrl)
|
||||
}
|
||||
const ops = { active: true, currentWindow: true } as const
|
||||
const browserAPI = getBrowserAPI()
|
||||
|
||||
;(browserAPI.tabs as any)
|
||||
.query(ops)
|
||||
.then((tabs: any[]) => handle(tabs[0]?.url))
|
||||
if (!tabUrl) cb(null);
|
||||
else cb(tabUrl);
|
||||
};
|
||||
const ops = { active: true, currentWindow: true } as const;
|
||||
const browserAPI = getBrowserAPI();
|
||||
(browserAPI.tabs as any).query(ops).then((tabs: any[]) => handle(tabs[0]?.url));
|
||||
}
|
||||
|
||||
export function listenToTabChanges (cb: () => void) {
|
||||
const browserAPI = getBrowserAPI()
|
||||
;(browserAPI.tabs as any).onActivated.addListener(cb)
|
||||
;(browserAPI.tabs as any).onUpdated.addListener(cb)
|
||||
export function listenToTabChanges(cb: () => void) {
|
||||
const browserAPI = getBrowserAPI();
|
||||
(browserAPI.tabs as any).onActivated.addListener(cb);
|
||||
(browserAPI.tabs as any).onUpdated.addListener(cb);
|
||||
}
|
||||
|
||||
export function stopListenToTabChanges (cb: () => void) {
|
||||
const browserAPI = getBrowserAPI()
|
||||
;(browserAPI.tabs as any).onActivated.removeListener(cb)
|
||||
;(browserAPI.tabs as any).onUpdated.removeListener(cb)
|
||||
export function stopListenToTabChanges(cb: () => void) {
|
||||
const browserAPI = getBrowserAPI();
|
||||
(browserAPI.tabs as any).onActivated.removeListener(cb);
|
||||
(browserAPI.tabs as any).onUpdated.removeListener(cb);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue