diff --git a/.github/workflows/deploy-website-index.yml b/.github/workflows/deploy-website-index.yml new file mode 100644 index 00000000..2cba2c90 --- /dev/null +++ b/.github/workflows/deploy-website-index.yml @@ -0,0 +1,27 @@ +name: 🚀 Deploy the website index + +on: + workflow_dispatch: {} + push: + branches: [rewrite] #[master, main] # TODO: change this + tags-ignore: ['**'] + paths: [website/index/**, .github/workflows/deploy-website-index.yml] + +concurrency: + group: ${{ github.ref }}-website-index + cancel-in-progress: true + +jobs: + publish: + name: 🚀 Publish the site + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cloudflare/wrangler-action@v3 + env: {PROJECT_NAME: random-user-agent-index, DIST_DIR: ./website/index} + with: + apiToken: ${{ secrets.CLOUDFLARE_PAGES_DEPLOY_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + environment: main # aka CF "production" environment + command: pages deploy ${{ env.DIST_DIR }} --project-name=${{ env.PROJECT_NAME }} --commit-dirty=true diff --git a/.github/workflows/deploy-website-sandbox.yml b/.github/workflows/deploy-website-sandbox.yml new file mode 100644 index 00000000..80ca1f7c --- /dev/null +++ b/.github/workflows/deploy-website-sandbox.yml @@ -0,0 +1,27 @@ +name: 🚀 Deploy the sandbox website + +on: + workflow_dispatch: {} + push: + branches: [rewrite] #[master, main] # TODO: change this + tags-ignore: ['**'] + paths: [website/sandbox/**, .github/workflows/deploy-website-sandbox.yml] + +concurrency: + group: ${{ github.ref }}-sandbox-website + cancel-in-progress: true + +jobs: + publish: + name: 🚀 Publish the site + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cloudflare/wrangler-action@v3 + env: {PROJECT_NAME: random-user-agent-ua-test-sandbox, DIST_DIR: ./website/sandbox} + with: + apiToken: ${{ secrets.CLOUDFLARE_PAGES_DEPLOY_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + environment: main # aka CF "production" environment + command: pages deploy ${{ env.DIST_DIR }} --project-name=${{ env.PROJECT_NAME }} --commit-dirty=true diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml deleted file mode 100644 index cd82765b..00000000 --- a/.github/workflows/site.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: site - -on: - workflow_dispatch: {} - push: - branches: [rewrite] #master, main] # TODO: uncomment - tags-ignore: ['**'] - paths: [site/**, .github/workflows/site.yml] - -concurrency: - group: ${{ github.ref }}-gh - cancel-in-progress: true - -jobs: - publish: - name: Publish the site - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: cloudflare/pages-action@1 - with: - apiToken: ${{ secrets.CLOUDFLARE_PAGES_DEPLOY_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - projectName: random-user-agent-index - directory: ./site - branch: main # aka CF "production" environment - gitHubToken: ${{ secrets.GITHUB_TOKEN }} - wranglerVersion: '3' # enable Wrangler v3 diff --git a/src/entrypoints/background/api/filters.ts b/src/entrypoints/background/api/filters.ts index 7b82dda9..ee78e9b3 100644 --- a/src/entrypoints/background/api/filters.ts +++ b/src/entrypoints/background/api/filters.ts @@ -1,32 +1,42 @@ import Rule = chrome.declarativeNetRequest.Rule -import type { ReadonlySettingsState, ReadonlyUserAgentState } from '~/shared/types' -import { setRequestHeaders, unsetRequestHeaders } from '../hooks' +import type { ContentScriptPayload, ReadonlySettingsState, ReadonlyUserAgentState } from '~/shared/types' +import { setBridgeData, setRequestHeaders, unsetRequestHeaders } from '../hooks' /** Returns true if the extension is applicable for the given domain name. */ export async function isApplicableForDomain(settings: ReadonlySettingsState, domain: string): Promise { + const isInList = settings.blacklist.domains.some((item): boolean => item === domain || domain.endsWith(`.${item}`)) + switch (settings.blacklist.mode) { case 'blacklist': - return !settings.blacklist.domains.includes(domain) + return !isInList case 'whitelist': - return settings.blacklist.domains.includes(domain) + return isInList } } -/** Reloads the request headers based on the current settings. */ -export async function reloadRequestHeaders( +/** Reloads the request headers and the bridge based on the current settings and user-agent. */ +export async function reloadRequestHeadersAndBridge( settings: ReadonlySettingsState, current: ReadonlyUserAgentState | undefined -): Promise | void> { +): Promise, ContentScriptPayload | undefined] | void> { if (settings.enabled && current) { - // if the extension is disabled or current user-agent is not set, we do not need to update the - // browser request headers - return await setRequestHeaders( - current, - settings.blacklist.mode === 'blacklist' - ? { exceptDomains: settings.blacklist.domains } - : { applyToDomains: settings.blacklist.domains } - ) + return await Promise.all([ + // if the extension is disabled or current user-agent is not set, we do not need to update the + // browser request headers + setRequestHeaders( + current, + settings.blacklist.mode === 'blacklist' + ? { exceptDomains: settings.blacklist.domains } + : { applyToDomains: settings.blacklist.domains } + ), + settings.jsProtection.enabled + ? setBridgeData(current, { + applyToDomains: settings.blacklist.mode === 'blacklist' ? undefined : settings.blacklist.domains, + exceptDomains: settings.blacklist.mode === 'blacklist' ? settings.blacklist.domains : undefined, + }) + : undefined, + ]) } // otherwise, we need to unset the request headers diff --git a/src/entrypoints/background/api/index.ts b/src/entrypoints/background/api/index.ts index 7a4242ea..5205c90e 100644 --- a/src/entrypoints/background/api/index.ts +++ b/src/entrypoints/background/api/index.ts @@ -1,3 +1,3 @@ -export { isApplicableForDomain, reloadRequestHeaders } from './filters' +export { isApplicableForDomain, reloadRequestHeadersAndBridge } from './filters' export { default as renewUserAgent } from './renew-user-agent' export { default as updateRemoteUserAgentList } from './update-remote-useragent-list' diff --git a/src/entrypoints/background/hooks/content-script-bridge.ts b/src/entrypoints/background/hooks/content-script-bridge.ts new file mode 100644 index 00000000..edcac014 --- /dev/null +++ b/src/entrypoints/background/hooks/content-script-bridge.ts @@ -0,0 +1,64 @@ +import type { ContentScriptPayload, ReadonlyUserAgentState } from '~/shared/types' +import { browserBrands, isMobile, platform } from '~/shared/client-hint' + +/** + * Sets the bridge data to the local storage, which will be used by the content script. In fact, it enables the + * javascript protection. + */ +export const setBridgeData = async ( + ua: ReadonlyUserAgentState, + filter?: { applyToDomains?: ReadonlyArray; exceptDomains?: ReadonlyArray } +): Promise => { + const payload: ContentScriptPayload = { + current: ua, + brands: { + major: (() => { + switch (ua.browser) { + case 'chrome': + return browserBrands('chrome', ua.version.browser.major) + case 'opera': + return browserBrands('opera', ua.version.browser.major, ua.version.underHood?.major || 0) + case 'edge': + return browserBrands('edge', ua.version.browser.major, ua.version.underHood?.major || 0) + } + + return [] + })(), + full: (() => { + switch (ua.browser) { + case 'chrome': + return browserBrands('chrome', ua.version.browser.full) + case 'opera': + return browserBrands('opera', ua.version.browser.full, ua.version.underHood?.full || '') + case 'edge': + return browserBrands('edge', ua.version.browser.full, ua.version.underHood?.full || '') + } + + return [] + })(), + }, + platform: platform(ua.os), + isMobile: isMobile(ua.os), + filtering: { + applyToDomains: filter?.applyToDomains || [], + exceptDomains: filter?.exceptDomains || [], + }, + } + + await chrome.storage.local.set({ [__UNIQUE_PAYLOAD_KEY_NAME__]: payload }) + + if (chrome.runtime.lastError) { + throw new Error(chrome.runtime.lastError.message) + } + + return payload +} + +/** Unsets the bridge data from the local storage. In fact, it disables the javascript protection. */ +export const unsetBridgeData = async (): Promise => { + await chrome.storage.local.remove([__UNIQUE_PAYLOAD_KEY_NAME__]) + + if (chrome.runtime.lastError) { + throw new Error(chrome.runtime.lastError.message) + } +} diff --git a/src/entrypoints/background/hooks/http-requests.ts b/src/entrypoints/background/hooks/http-requests.ts index 69fa5d7e..ad74bf60 100644 --- a/src/entrypoints/background/hooks/http-requests.ts +++ b/src/entrypoints/background/hooks/http-requests.ts @@ -1,32 +1,33 @@ import { browserBrands, isMobile, platform } from '~/shared/client-hint' import { canonizeDomain } from '~/shared' -import type { ContentScriptPayload, ReadonlyUserAgentState } from '~/shared/types' +import type { ReadonlyUserAgentState } from '~/shared/types' import ResourceType = chrome.declarativeNetRequest.ResourceType import Rule = chrome.declarativeNetRequest.Rule // copy-paste of chrome.declarativeNetRequest.RuleActionType type (FireFox v124 does not have it) +// https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest#type-RuleActionType enum RuleActionType { - BLOCK = 'block', - REDIRECT = 'redirect', - ALLOW = 'allow', - UPGRADE_SCHEME = 'upgradeScheme', - MODIFY_HEADERS = 'modifyHeaders', - ALLOW_ALL_REQUESTS = 'allowAllRequests', + BLOCK = 'block', // Block the network request + REDIRECT = 'redirect', // Redirect the network request + ALLOW = 'allow', // Allow the network request. The request won't be intercepted if there is an allow rule which matches it + UPGRADE_SCHEME = 'upgradeScheme', // Upgrade the network request url's scheme to https if the request is http or ftp + MODIFY_HEADERS = 'modifyHeaders', // Modify request/response headers from the network request + ALLOW_ALL_REQUESTS = 'allowAllRequests', // Allow all requests within a frame hierarchy, including the frame request itself } // copy-paste of chrome.declarativeNetRequest.HeaderOperation type (FireFox v124 does not have it) +// https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest#type-HeaderOperation enum HeaderOperation { - APPEND = 'append', - SET = 'set', - REMOVE = 'remove', + APPEND = 'append', // Adds a new entry for the specified header. This operation is not supported for request headers + SET = 'set', // Sets a new value for the specified header, removing any existing headers with the same name + REMOVE = 'remove', // Removes all entries for the specified header } // Note: the rule IDs must be unique, and do not change them after the extension is published. // The rule IDs are used to remove the existing rules before adding new ones. -const RuleIDs: { readonly [_ in 'ReplaceUserAgent' | 'ReplaceClientHints' | 'SendPayloadUsingCookies']: number } = { +const RuleIDs: { readonly [_ in 'ReplaceUserAgent' | 'ReplaceClientHints']: number } = { ReplaceUserAgent: 1, ReplaceClientHints: 2, - SendPayloadUsingCookies: 3, } enum HeaderNames { @@ -37,27 +38,10 @@ enum HeaderNames { CLIENT_HINT_BRAND_FULL = 'Sec-CH-UA-Full-Version-List', // https://mzl.la/3C3x5TT CLIENT_HINT_PLATFORM = 'Sec-CH-UA-Platform', // https://mzl.la/3EbrbTj CLIENT_HINT_MOBILE = 'Sec-CH-UA-Mobile', // https://mzl.la/3SYTA3f - SET_COOKIE = 'Set-Cookie', // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie } const allResourceTypes = Object.values(ResourceType) -/** - * Serialize is a special function that takes any data and converts it to a string, which can be safely stored in a - * cookie or URL. - * - * NOTE: Keep this function implementation in sync with the content script. - */ -const serialize = (data: ContentScriptPayload): Readonly | false => { - try { - return btoa(JSON.stringify(data)).replace(/=/g, '-') - } catch (_) { - // do nothing - } - - return false -} - /** * Enables the request headers modification. * @@ -124,7 +108,8 @@ export async function setRequestHeaders( return [] })() - const brandsWithMajorString: string = brandsWithMajor.map((b) => `"${b.brand}";v="${b.version}"`).join(', ') + .map((b) => `"${b.brand}";v="${b.version}"`) + .join(', ') const brandsWithFull = (() => { switch (ua.browser) { @@ -138,10 +123,8 @@ export async function setRequestHeaders( return [] })() - const brandsWithFullString: string = brandsWithFull.map((b) => `"${b.brand}";v="${b.version}"`).join(', ') - - const setPlatform = platform(ua.os) - const setMobile = isMobile(ua.os) + .map((b) => `"${b.brand}";v="${b.version}"`) + .join(', ') const rules: Array = [ { @@ -169,25 +152,25 @@ export async function setRequestHeaders( ? { operation: HeaderOperation.SET, header: HeaderNames.CLIENT_HINT_BRAND_MAJOR, - value: brandsWithMajorString, + value: brandsWithMajor, } : { operation: HeaderOperation.REMOVE, header: HeaderNames.CLIENT_HINT_BRAND_MAJOR }, brandsWithFull ? { operation: HeaderOperation.SET, header: HeaderNames.CLIENT_HINT_BRAND_FULL, - value: brandsWithFullString, + value: brandsWithFull, } : { operation: HeaderOperation.REMOVE, header: HeaderNames.CLIENT_HINT_BRAND_FULL }, { operation: HeaderOperation.SET, header: HeaderNames.CLIENT_HINT_PLATFORM, - value: `"${setPlatform}"`, + value: `"${platform(ua.os)}"`, }, { operation: HeaderOperation.SET, header: HeaderNames.CLIENT_HINT_MOBILE, - value: setMobile ? '?1' : '?0', + value: isMobile(ua.os) ? '?1' : '?0', }, { operation: HeaderOperation.REMOVE, header: HeaderNames.CLIENT_HINT_FULL_VERSION }, { operation: HeaderOperation.REMOVE, header: HeaderNames.CLIENT_HINT_PLATFORM_VERSION }, @@ -195,30 +178,6 @@ export async function setRequestHeaders( }, condition, }, - { - // is used to send the payload using cookies to the content script in the fastest way - id: RuleIDs.SendPayloadUsingCookies, - priority: 3, - action: { - type: RuleActionType.MODIFY_HEADERS, - responseHeaders: [ - { - operation: HeaderOperation.SET, - header: HeaderNames.SET_COOKIE, - value: `${__UNIQUE_RUA_COOKIE_NAME__}=${serialize({ - current: ua, - brands: { - major: brandsWithMajor, - full: brandsWithFull, - }, - platform: setPlatform, - isMobile: setMobile, - })}; SameSite=Strict; Max-Age=10; Path=/`, - }, - ], - }, - condition, - }, ] await chrome.declarativeNetRequest.updateDynamicRules({ diff --git a/src/entrypoints/background/hooks/index.ts b/src/entrypoints/background/hooks/index.ts index 5bcd3e8f..c390f172 100644 --- a/src/entrypoints/background/hooks/index.ts +++ b/src/entrypoints/background/hooks/index.ts @@ -1 +1,2 @@ export { setRequestHeaders, unsetRequestHeaders } from './http-requests' +export { setBridgeData, unsetBridgeData } from './content-script-bridge' diff --git a/src/entrypoints/background/index.ts b/src/entrypoints/background/index.ts index d420678e..81fa495a 100644 --- a/src/entrypoints/background/index.ts +++ b/src/entrypoints/background/index.ts @@ -1,8 +1,8 @@ import { checkPermissions, detectBrowser, watchPermissionsChange } from '~/shared' import { type HandlersMap, listen as listenRuntime } from '~/shared/messaging' import { newErrorEvent, newExtensionLoadedEvent } from '~/shared/stats' -import { isApplicableForDomain, reloadRequestHeaders, renewUserAgent, updateRemoteUserAgentList } from './api' -import { unsetRequestHeaders } from './hooks' +import { isApplicableForDomain, reloadRequestHeadersAndBridge, renewUserAgent, updateRemoteUserAgentList } from './api' +import { unsetBridgeData, unsetRequestHeaders } from './hooks' import { registerHotkeys } from './hotkeys' import { CurrentUserAgent, RemoteUserAgentList, Settings, StorageArea, UserID } from './persistent' import { type Collector as StatsCollector, GaCollector } from './stats' @@ -90,9 +90,9 @@ let stats: StatsCollector | undefined = undefined // update the extension title with the current user-agent information await setExtensionTitle(c) - // reload the request headers with the new user-agent information - const newRules = await reloadRequestHeaders(await settings.get(), await currentUserAgent.get()) - debug('the new request header rules have been ' + (newRules ? 'set' : 'unset'), ...[newRules]) + // reload the request headers (and bridge data) with the new user-agent information + const reloaded = await reloadRequestHeadersAndBridge(await settings.get(), await currentUserAgent.get()) + debug('the request header rules and bridge data have been ' + (reloaded ? 'set' : 'unset'), ...[reloaded]) }) settings.onChange(async (s) => { @@ -137,14 +137,16 @@ let stats: StatsCollector | undefined = undefined await remoteListUpdateTimer.stop() } - // 🚀 update the browser request headers with the current user-agent information - const newRules = await reloadRequestHeaders(s, await currentUserAgent.get()) - debug('the new request header rules have been ' + (newRules ? 'set' : 'unset'), ...[newRules]) + // 🚀 update the browser request headers (and bridge data) with the current user-agent information + const reloaded = await reloadRequestHeadersAndBridge(await settings.get(), await currentUserAgent.get()) + debug('the request header rules and bridge data have been ' + (reloaded ? 'set' : 'unset'), ...[reloaded]) } else { // 🌚 otherwise, if the extension is disabled, we need to disable everything await Promise.allSettled([ // disable headers replacement unsetRequestHeaders(), + // disable the javascript protection + unsetBridgeData(), // disable the user-agent renewal timer userAgentRenewTimer.stop(), // disable the remote user-agents list update timer diff --git a/src/entrypoints/content/content.ts b/src/entrypoints/content/content.ts index e16ac9e0..64a12225 100644 --- a/src/entrypoints/content/content.ts +++ b/src/entrypoints/content/content.ts @@ -1,59 +1,6 @@ // ⚠ DO NOT IMPORT ANYTHING EXCEPT TYPES HERE DUE THE `import()` ERRORS ⚠ import type { ContentScriptPayload } from '~/shared/types' -/** - * Cookie store API isn't supported by all browsers yet. - * - * @link https://developer.mozilla.org/en-US/docs/Web/API/CookieStore#browser_compatibility - */ -declare global { - interface Window { - cookieStore: { delete: (name: string) => Promise } | undefined - } -} - -/** The cookie name with the serialized data, sent by the background script */ -const cookieName = __UNIQUE_RUA_COOKIE_NAME__ - -/** Returns the value of the cookie value with serialized data. */ -const getCookieValue = (): string | undefined => - document.cookie - .split(';') - .map((cookie) => cookie.trim()) - .filter((cookie) => cookie.startsWith(cookieName)) - .map((cookie) => cookie.split('=')) - .filter((cookie) => cookie.length >= 2)?.[0]?.[1] - -/** - * Deserializes the cookie value and returns the data. - * - * NOTE: Keep this function implementation in sync with the background script. - */ -const deserializeCookie = (serialized: string | undefined): ContentScriptPayload | undefined => { - if (!serialized) { - return - } - - try { - return JSON.parse(atob(serialized.replace(/-/g, '='))) - } catch (_) { - // do nothing - } -} - -/** Deletes the cookie with the serialized data. */ -const deleteCookie = (): void => { - document.cookie = `${cookieName}=; Path=/; SameSite=Strict; Max-Age=0; Expires=${new Date().toUTCString()}` - - if (window.cookieStore) { - try { - window.cookieStore.delete(cookieName).catch(void 0) - } catch (_) { - // do nothing - } - } -} - /** Injects and executes the script with the payload. The ID of the script tag is the same as the filename. */ const injectAndExecuteScript = (payload: ContentScriptPayload): void => { const script = document.createElement('script') @@ -67,20 +14,54 @@ const injectAndExecuteScript = (payload: ContentScriptPayload): void => { parent.appendChild(script) } +// TODO: need to check the logic of this function +const isApplicableToDomain = (currentDomain: string, p: ContentScriptPayload): boolean => { + const applyToDomains = p.filtering?.applyToDomains || [] + const exceptDomains = p.filtering?.exceptDomains || [] + + const isInExceptList = exceptDomains.some( + (except): boolean => except === currentDomain || currentDomain.endsWith(`.${except}`) + ) + + // if the `applyTo` array is NOT empty, the script should be applied ONLY to the domains in the array (including + // subdomains), except the ones in the `exceptDomains` array (including subdomains too) + if (applyToDomains.length) { + const isInApplyToList = applyToDomains.some( + (apply): boolean => apply === currentDomain || currentDomain.endsWith(`.${apply}`) + ) + + return isInApplyToList && !isInExceptList + } + + // otherwise, the script should be applied to all domains, except the ones in the `exceptDomains` array (including + // subdomains) + return !isInExceptList +} + /** Run the script */ try { - ;(() => { - try { - const payload = deserializeCookie(getCookieValue()) - if (!payload) { - return // invalid payload = no fun too :( - } + const key = __UNIQUE_PAYLOAD_KEY_NAME__ - injectAndExecuteScript(payload) - } finally { - deleteCookie() + chrome.storage.local.get([key], (items) => { + if (chrome.runtime.lastError) { + throw new Error(chrome.runtime.lastError.message) } - })() + + if (!(key in items)) { + return // no payload = javascript protection is disabled + } + + const payload = items[key] as ContentScriptPayload + const currentDomain = window.location.hostname + + console.log('currentDomain', currentDomain, isApplicableToDomain(currentDomain, payload), payload) + + if (!isApplicableToDomain(currentDomain, payload)) { + return // the script is not applicable to the current domain + } + + injectAndExecuteScript(payload) + }) } catch (err) { console.debug(`%c🕊 RUA: An error occurred in the content script`, 'font-weight:bold', err) } diff --git a/src/entrypoints/content/inject.ts b/src/entrypoints/content/inject.ts index cb2bf6fe..b583c42e 100644 --- a/src/entrypoints/content/inject.ts +++ b/src/entrypoints/content/inject.ts @@ -100,6 +100,7 @@ try { } // patch the current navigator object + console.debug(`%c👻 RUA: Patching the current navigator object`, 'font-weight:bold') patchNavigator(navigator) // handler for patching navigator object for the iframes @@ -114,6 +115,7 @@ try { }, { once: true, passive: true } ) + console.debug(`%c👻 RUA: Patching the navigator objects of the iframes`, 'font-weight:bold') // watch for the new iframes and patch their navigator objects new MutationObserver((mutations): void => { @@ -129,6 +131,7 @@ try { }) }) }).observe(document, { childList: true, subtree: true }) + console.debug(`%c👻 RUA: Watching for the new iframes and patching their navigator objects`, 'font-weight:bold') })() } catch (err) { console.debug(`%c👻 RUA: An error occurred in the injected script`, 'font-weight:bold', err) diff --git a/src/entrypoints/onboard/components/container/component.tsx b/src/entrypoints/onboard/components/container/component.tsx index dda84b32..b40115a1 100644 --- a/src/entrypoints/onboard/components/container/component.tsx +++ b/src/entrypoints/onboard/components/container/component.tsx @@ -19,9 +19,6 @@ export default function Container({ children = null }: { children?: React.ReactN
  • {i18n('read_and_modify_data') + ' '}({i18n('read_and_modify_data_reason')})
  • -
  • - {i18n('send_custom_cookies') + ' '}({i18n('send_custom_cookies_reason')}) -
  • {children}
    diff --git a/src/i18n/locales.ts b/src/i18n/locales.ts index 1c6deac9..7549f682 100644 --- a/src/i18n/locales.ts +++ b/src/i18n/locales.ts @@ -84,8 +84,6 @@ export const locales: Partial> = { read_and_modify_data: 'Read and modify all your data on the websites you visit', read_and_modify_data_reason: 'to inject the necessary scripts into the pages to prevent real user-agent and other data leaks', - send_custom_cookies: 'Send custom cookies to the loaded pages', - send_custom_cookies_reason: 'these will be removed immediately after the page is loaded', grant_permission_button: 'Grant permissions', }, @@ -173,8 +171,6 @@ export const locales: Partial> = { read_and_modify_data: 'Lesen und Ändern aller Ihrer Daten auf den von Ihnen besuchten Websites', read_and_modify_data_reason: 'um die erforderlichen Skripte in die Seiten einzufügen, um echte Benutzeragenten und andere Datenlecks zu verhindern', - send_custom_cookies: 'Senden von benutzerdefinierten Cookies an die geladenen Seiten', - send_custom_cookies_reason: 'diese werden sofort nach dem Laden der Seite entfernt', grant_permission_button: 'Berechtigungen erteilen', }, @@ -263,8 +259,6 @@ export const locales: Partial> = { read_and_modify_data: 'Leer y modificar todos tus datos en los sitios web que visitas', read_and_modify_data_reason: 'para inyectar los scripts necesarios en las páginas y evitar la filtración del agente de usuario real y otros datos', - send_custom_cookies: 'Enviar cookies personalizadas a las páginas cargadas', - send_custom_cookies_reason: 'éstas se eliminarán inmediatamente después de que la página se cargue', grant_permission_button: 'Conceder permisos', }, @@ -354,8 +348,6 @@ export const locales: Partial> = { read_and_modify_data: 'Lire et modifier toutes vos données sur les sites Web que vous visitez', read_and_modify_data_reason: "pour injecter les scripts nécessaires dans les pages afin de prévenir les fuites de données réelles sur l'utilisateur et autres", - send_custom_cookies: 'Envoyer des cookies personnalisés aux pages chargées', - send_custom_cookies_reason: 'ceux-ci seront supprimés immédiatement après le chargement de la page', grant_permission_button: 'Accorder les autorisations', }, @@ -442,8 +434,6 @@ export const locales: Partial> = { read_and_modify_data: 'Baca dan ubah semua data Anda pada situs web yang Anda kunjungi', read_and_modify_data_reason: 'untuk menyuntikkan skrip yang diperlukan ke halaman untuk mencegah identitas pengguna asli dan kebocoran data lainnya', - send_custom_cookies: 'Kirimkan cookie kustom ke halaman yang dimuat', - send_custom_cookies_reason: 'cookie ini akan dihapus segera setelah halaman dimuat', grant_permission_button: 'Berikan izin', }, @@ -524,8 +514,6 @@ export const locales: Partial> = { read_and_modify_data: '訪れるウェブサイトのすべてのデータを読み取り、変更する', read_and_modify_data_reason: '必要なスクリプトをページに注入し、実際のユーザーエージェントや他のデータ漏洩を防ぐため', - send_custom_cookies: '読み込まれたページにカスタムのクッキーを送信する', - send_custom_cookies_reason: 'これらはページの読み込み後すぐに削除されます', grant_permission_button: '許可を付与する', }, @@ -613,8 +601,6 @@ export const locales: Partial> = { 'Odczytywanie i modyfikowanie wszystkich danych na odwiedzanych przez Ciebie stronach internetowych', read_and_modify_data_reason: 'aby wstrzykiwać niezbędne skrypty na strony w celu zapobiegania realnemu przeciekom danych, takim jak prawdziwy identyfikator użytkownika (user-agent) i inne', - send_custom_cookies: 'Wysyłanie niestandardowych plików cookie do wczytanych stron', - send_custom_cookies_reason: 'zostaną one usunięte natychmiast po załadowaniu strony', grant_permission_button: 'Udziel uprawnień', }, @@ -702,8 +688,6 @@ export const locales: Partial> = { read_and_modify_data: 'Leia e modifique todos os seus dados nos sites que você visita', read_and_modify_data_reason: 'para injetar os scripts necessários nas páginas para evitar o vazamento de dados reais do usuário e outros', - send_custom_cookies: 'Envie cookies personalizados para as páginas carregadas', - send_custom_cookies_reason: 'eles serão removidos imediatamente após a página ser carregada', grant_permission_button: 'Conceder permissões', }, @@ -791,8 +775,6 @@ export const locales: Partial> = { read_and_modify_data: 'Чтение и изменение всех ваших данных на посещаемых вами веб-сайтах', read_and_modify_data_reason: 'для вставки необходимых скриптов на страницы для предотвращения реального user-agent и других утечек данных', - send_custom_cookies: 'Отправка пользовательских файлов cookie на загруженные страницы', - send_custom_cookies_reason: 'они будут удалены сразу после загрузки страницы', grant_permission_button: 'Предоставить разрешения', }, @@ -880,8 +862,6 @@ export const locales: Partial> = { read_and_modify_data: 'Ziyaret ettiğiniz web sitelerindeki tüm verilerinizi okuyun ve değiştirin', read_and_modify_data_reason: 'gerçek kullanıcı ajanı ve diğer veri sızıntılarını önlemek için gereken betikleri sayfalara enjekte etmek için', - send_custom_cookies: 'Yüklenen sayfalara özel çerezler gönder', - send_custom_cookies_reason: 'bu sayfa yüklendikten hemen sonra kaldırılacaktır', grant_permission_button: 'İzinleri ver', }, @@ -967,8 +947,6 @@ export const locales: Partial> = { read_and_modify_data: 'Читати та змінювати всі ваші дані на відвідуваних вами веб-сайтах', read_and_modify_data_reason: 'для впровадження необхідних скриптів на сторінках для запобігання реального використання користувача та інших витоків даних', - send_custom_cookies: 'Надсилати спеціальні файли cookie на завантажені сторінки', - send_custom_cookies_reason: 'вони будуть видалені негайно після завантаження сторінки', grant_permission_button: 'Надати дозволи', }, @@ -1056,8 +1034,6 @@ export const locales: Partial> = { read_and_modify_data: 'Đọc và chỉnh sửa tất cả dữ liệu của bạn trên các trang web bạn truy cập', read_and_modify_data_reason: 'để chèn các kịch bản cần thiết vào các trang để ngăn chặn thông tin người dùng thật và rò rỉ dữ liệu khác', - send_custom_cookies: 'Gửi cookie tùy chỉnh đến các trang đã tải', - send_custom_cookies_reason: 'những cookie này sẽ được loại bỏ ngay sau khi trang được tải', grant_permission_button: 'Cấp quyền', }, @@ -1136,8 +1112,6 @@ export const locales: Partial> = { why_we_need_permissions: '为了使扩展程序正常运行,需要以下权限', read_and_modify_data: '读取并修改您在访问的网站上的所有数据', read_and_modify_data_reason: '以注入必要的脚本到页面中,防止真实用户代理和其他数据泄漏', - send_custom_cookies: '发送自定义Cookie到加载的页面', - send_custom_cookies_reason: '这些Cookie将在页面加载后立即删除', grant_permission_button: '授予权限', }, } diff --git a/src/i18n/types.d.ts b/src/i18n/types.d.ts index c3743552..946231ea 100644 --- a/src/i18n/types.d.ts +++ b/src/i18n/types.d.ts @@ -179,10 +179,6 @@ export type Localization = { read_and_modify_data: T /** to inject the necessary scripts into the pages (note: in lower case) */ read_and_modify_data_reason: T - /** Send custom cookies to the loaded pages */ - send_custom_cookies: T - /** these will be removed immediately after the page is loaded (note: in lower case) */ - send_custom_cookies_reason: T /** Grant permissions */ grant_permission_button: T } diff --git a/src/shared/types/content-script-payload.d.ts b/src/shared/types/content-script-payload.d.ts index 58fdfc07..ac249e75 100644 --- a/src/shared/types/content-script-payload.d.ts +++ b/src/shared/types/content-script-payload.d.ts @@ -10,4 +10,8 @@ export type ContentScriptPayload // if empty, apply to all domains except the `exceptDomains` + exceptDomains: Array + } }> diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 1200441b..32d44c8e 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -1,9 +1,7 @@ /** - * Holds the name of the cookie that will be used to pass the data from backend to content script. - * * @description declared in `vite.config.ts` */ -declare const __UNIQUE_RUA_COOKIE_NAME__: Readonly +declare const __UNIQUE_PAYLOAD_KEY_NAME__: Readonly declare const __UNIQUE_INJECT_FILENAME__: Readonly /** diff --git a/vite.config.ts b/vite.config.ts index e92021cc..9ee6dd87 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,10 +26,6 @@ const srcDir = resolve(__dirname, 'src') const entrypointDir = join(srcDir, 'entrypoints') const staticDir = resolve(__dirname, 'static') -const uniqueCookieName: string = randomstring.generate({ - length: Math.floor(Math.random() * 12 + 5), - charset: 'alphabetic', -}) const uniqueInjectFileName: string = randomstring.generate({ length: 8, charset: 'alphabetic' }) enum ProjectURLs { @@ -84,6 +80,8 @@ const renameInjectFilePlugin: PluginOption = { const splitChromeAndFirefoxPlugin: PluginOption = { name: 'split-chrome-and-firefox', writeBundle: { + sequential: true, // https://rollupjs.org/plugin-development/#build-hooks + order: 'pre', handler() { // remove "./dist/firefox" directory rmSync(distFireFoxDir, { recursive: true, force: true }) @@ -93,6 +91,10 @@ const splitChromeAndFirefoxPlugin: PluginOption = { readdirSync(from, { withFileTypes: true }) .sort() .forEach((file) => { + if (file.name === 'manifest.json') { + return // skip the manifest.json file + } + const fromPath = join(from, file.name) const toPath = join(to, file.name) const stat = statSync(fromPath) @@ -116,7 +118,7 @@ const splitChromeAndFirefoxPlugin: PluginOption = { const copyAndModifyManifestPlugin: PluginOption = { name: 'copy-and-modify-manifest', writeBundle: { - sequential: true, // https://rollupjs.org/plugin-development/#build-hooks + sequential: true, handler() { const content: Partial & { version: string }> = { ...manifestJson, @@ -167,7 +169,7 @@ const zipDistPlugin = (): PluginOption => { config = cfg }, writeBundle: { - sequential: true, // https://rollupjs.org/plugin-development/#build-hooks + sequential: true, async handler() { if (config.command !== 'build' || process.argv.includes('--watch')) { return // do nothing in dev/watch mode @@ -202,7 +204,8 @@ export default defineConfig({ plugins: [ react(), [createLocalesPlugin, copyStaticContentAsIsPlugin, renameInjectFilePlugin], - [splitChromeAndFirefoxPlugin, copyAndModifyManifestPlugin, zipDistPlugin()], + splitChromeAndFirefoxPlugin, + [copyAndModifyManifestPlugin, zipDistPlugin()], ], resolve: { alias: { @@ -210,7 +213,7 @@ export default defineConfig({ }, }, define: { - __UNIQUE_RUA_COOKIE_NAME__: JSON.stringify(uniqueCookieName), + __UNIQUE_PAYLOAD_KEY_NAME__: JSON.stringify('rua_payload'), __UNIQUE_INJECT_FILENAME__: JSON.stringify(`${uniqueInjectFileName}.js`), __BUGREPORT_URL__: JSON.stringify(ProjectURLs.BUGREPORT), diff --git a/site/apple-touch-icon.png b/website/index/apple-touch-icon.png similarity index 100% rename from site/apple-touch-icon.png rename to website/index/apple-touch-icon.png diff --git a/site/browserconfig.xml b/website/index/browserconfig.xml similarity index 100% rename from site/browserconfig.xml rename to website/index/browserconfig.xml diff --git a/site/favicon-16x16.png b/website/index/favicon-16x16.png similarity index 100% rename from site/favicon-16x16.png rename to website/index/favicon-16x16.png diff --git a/site/favicon-32x32.png b/website/index/favicon-32x32.png similarity index 100% rename from site/favicon-32x32.png rename to website/index/favicon-32x32.png diff --git a/site/favicon.ico b/website/index/favicon.ico similarity index 100% rename from site/favicon.ico rename to website/index/favicon.ico diff --git a/site/index.html b/website/index/index.html similarity index 100% rename from site/index.html rename to website/index/index.html diff --git a/site/mstile-150x150.png b/website/index/mstile-150x150.png similarity index 100% rename from site/mstile-150x150.png rename to website/index/mstile-150x150.png diff --git a/site/site.webmanifest b/website/index/site.webmanifest similarity index 100% rename from site/site.webmanifest rename to website/index/site.webmanifest diff --git a/website/sandbox/index.html b/website/sandbox/index.html new file mode 100644 index 00000000..47f25f68 --- /dev/null +++ b/website/sandbox/index.html @@ -0,0 +1,48 @@ + + + + + User-agent test + + + +
    +

    User-agent test

    +

    +
    + + +