Skip to content

Commit

Permalink
wip: 🔕 temporary commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam committed Apr 28, 2024
1 parent eded5a0 commit a3bf804
Show file tree
Hide file tree
Showing 26 changed files with 287 additions and 222 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/deploy-website-index.yml
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions .github/workflows/deploy-website-sandbox.yml
Original file line number Diff line number Diff line change
@@ -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
29 changes: 0 additions & 29 deletions .github/workflows/site.yml

This file was deleted.

40 changes: 25 additions & 15 deletions src/entrypoints/background/api/filters.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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<Array<Rule> | void> {
): Promise<readonly [Array<Rule>, 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
Expand Down
2 changes: 1 addition & 1 deletion src/entrypoints/background/api/index.ts
Original file line number Diff line number Diff line change
@@ -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'
64 changes: 64 additions & 0 deletions src/entrypoints/background/hooks/content-script-bridge.ts
Original file line number Diff line number Diff line change
@@ -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<string>; exceptDomains?: ReadonlyArray<string> }
): Promise<ContentScriptPayload> => {
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<void> => {
await chrome.storage.local.remove([__UNIQUE_PAYLOAD_KEY_NAME__])

if (chrome.runtime.lastError) {
throw new Error(chrome.runtime.lastError.message)
}
}
83 changes: 21 additions & 62 deletions src/entrypoints/background/hooks/http-requests.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<string> | false => {
try {
return btoa(JSON.stringify(data)).replace(/=/g, '-')
} catch (_) {
// do nothing
}

return false
}

/**
* Enables the request headers modification.
*
Expand Down Expand Up @@ -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) {
Expand All @@ -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<Rule> = [
{
Expand Down Expand Up @@ -169,56 +152,32 @@ 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 },
],
},
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({
Expand Down
1 change: 1 addition & 0 deletions src/entrypoints/background/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { setRequestHeaders, unsetRequestHeaders } from './http-requests'
export { setBridgeData, unsetBridgeData } from './content-script-bridge'
Loading

0 comments on commit a3bf804

Please sign in to comment.