|
1 | | -const CAMBRIDGE_ORIGIN = 'https://dictionary.cambridge.org' |
2 | | -const CAMBRIDGE_PARTITION_KEY_SITE = 'https://cambridge.org' |
3 | | -const CAMBRIDGE_URLS = ['https://dictionary.cambridge.org/*'] |
4 | | -const CAMBRIDGE_RULE_ID = 32002 |
5 | | -const CAMBRIDGE_CLEARANCE_COOKIE = 'cf_clearance' |
6 | | -const CAMBRIDGE_CLEARANCE_COOKIE_FILTERS = [ |
7 | | - { |
8 | | - url: CAMBRIDGE_ORIGIN, |
9 | | - name: CAMBRIDGE_CLEARANCE_COOKIE |
10 | | - }, |
11 | | - { |
12 | | - url: CAMBRIDGE_ORIGIN, |
13 | | - name: CAMBRIDGE_CLEARANCE_COOKIE, |
14 | | - partitionKey: { |
15 | | - topLevelSite: CAMBRIDGE_PARTITION_KEY_SITE |
16 | | - } |
17 | | - } |
18 | | -] |
19 | | - |
20 | | -type ChromeDeclarativeNetRequest = { |
21 | | - updateSessionRules: (options: { |
22 | | - addRules?: Array<{ |
23 | | - id: number |
24 | | - priority: number |
25 | | - action: { |
26 | | - type: 'modifyHeaders' |
27 | | - requestHeaders: Array<ChromeModifyHeaderInfo> |
28 | | - } |
29 | | - condition: { |
30 | | - regexFilter: string |
31 | | - resourceTypes: string[] |
32 | | - } |
33 | | - }> |
34 | | - removeRuleIds?: number[] |
35 | | - }) => Promise<void> |
36 | | -} |
37 | | - |
38 | | -type BrowserWebRequest = typeof browser.webRequest |
39 | | - |
40 | | -type ChromeModifyHeaderInfo = { |
41 | | - header: string |
42 | | - operation: 'append' | 'set' |
43 | | - value: string |
44 | | -} |
45 | | - |
46 | | -let ensureNetworkCompatibilityPromise: Promise<void> | null = null |
47 | | - |
48 | | -export function ensureNetworkCompatibility() { |
49 | | - if (!ensureNetworkCompatibilityPromise) { |
50 | | - ensureNetworkCompatibilityPromise = doEnsureCambridgeNetworkCompatibility().catch( |
51 | | - error => { |
52 | | - ensureNetworkCompatibilityPromise = null |
53 | | - throw error |
54 | | - } |
55 | | - ) |
56 | | - } |
57 | | - |
58 | | - return ensureNetworkCompatibilityPromise |
59 | | -} |
60 | | - |
61 | | -async function doEnsureCambridgeNetworkCompatibility() { |
62 | | - const dnr = getChromeDeclarativeNetRequest() |
63 | | - if (dnr) { |
64 | | - await installMv3HeaderRule(dnr, await getCambridgeClearanceCookie()) |
65 | | - return |
66 | | - } |
67 | | - |
68 | | - const webRequest = getBrowserWebRequest() |
69 | | - if (webRequest && webRequest.onBeforeSendHeaders) { |
70 | | - installMv2HeaderListener(webRequest, await getCambridgeClearanceCookie()) |
71 | | - return |
72 | | - } |
73 | | - |
74 | | - if (isManifestV3()) { |
75 | | - throw new Error( |
76 | | - 'declarativeNetRequest is unavailable in the current MV3 context.' |
77 | | - ) |
78 | | - } |
79 | | - |
80 | | - throw new Error('webRequest.onBeforeSendHeaders is unavailable.') |
81 | | -} |
82 | | - |
83 | | -function isManifestV3() { |
84 | | - const manifest = browser.runtime.getManifest && browser.runtime.getManifest() |
85 | | - return !!(manifest && manifest.manifest_version === 3) |
86 | | -} |
87 | | - |
88 | | -function installMv2HeaderListener( |
89 | | - webRequest: BrowserWebRequest, |
90 | | - clearanceCookie: string |
91 | | -) { |
92 | | - const extraInfoSpec = ['blocking', 'requestHeaders'] |
93 | | - const onBeforeSendHeadersOptions = (webRequest as any) |
94 | | - .OnBeforeSendHeadersOptions |
95 | | - |
96 | | - if ( |
97 | | - onBeforeSendHeadersOptions && |
98 | | - Object.prototype.hasOwnProperty.call( |
99 | | - onBeforeSendHeadersOptions, |
100 | | - 'EXTRA_HEADERS' |
101 | | - ) |
102 | | - ) { |
103 | | - extraInfoSpec.push('extraHeaders') |
104 | | - } |
105 | | - |
106 | | - webRequest.onBeforeSendHeaders.addListener( |
107 | | - details => { |
108 | | - if (details && details.requestHeaders) { |
109 | | - setRequestHeader(details.requestHeaders, 'Referer', CAMBRIDGE_ORIGIN) |
110 | | - if (clearanceCookie) { |
111 | | - appendCookieHeader(details.requestHeaders, clearanceCookie) |
112 | | - } |
113 | | - } |
114 | | - return { requestHeaders: details.requestHeaders } |
115 | | - }, |
116 | | - { urls: CAMBRIDGE_URLS }, |
117 | | - /** WebExt type is missing Chrome support */ |
118 | | - extraInfoSpec as any |
119 | | - ) |
120 | | -} |
| 1 | +import { createCookieHeaderNetworkCompatibility } from '../network-compat' |
121 | 2 |
|
122 | | -async function installMv3HeaderRule( |
123 | | - dnr: ChromeDeclarativeNetRequest, |
124 | | - clearanceCookie: string |
125 | | -) { |
126 | | - const requestHeaders: ChromeModifyHeaderInfo[] = [ |
127 | | - { |
128 | | - header: 'referer', |
129 | | - operation: 'set', |
130 | | - value: CAMBRIDGE_ORIGIN |
131 | | - } |
132 | | - ] |
133 | | - |
134 | | - if (clearanceCookie) { |
135 | | - requestHeaders.push({ |
136 | | - header: 'cookie', |
137 | | - operation: 'append', |
138 | | - value: clearanceCookie |
139 | | - }) |
140 | | - } |
141 | | - |
142 | | - await dnr.updateSessionRules({ |
143 | | - removeRuleIds: [CAMBRIDGE_RULE_ID], |
144 | | - addRules: [ |
145 | | - { |
146 | | - id: CAMBRIDGE_RULE_ID, |
147 | | - priority: 1, |
148 | | - action: { |
149 | | - type: 'modifyHeaders', |
150 | | - requestHeaders |
151 | | - }, |
152 | | - condition: { |
153 | | - regexFilter: '^https://dictionary\\.cambridge\\.org/.*', |
154 | | - resourceTypes: ['xmlhttprequest', 'media'] |
155 | | - } |
156 | | - } |
157 | | - ] |
158 | | - }) |
159 | | -} |
160 | | - |
161 | | -async function getCambridgeClearanceCookie() { |
162 | | - const cookiesApi = getBrowserCookies() |
163 | | - if (!cookiesApi || !cookiesApi.get) { |
164 | | - return '' |
165 | | - } |
166 | | - |
167 | | - for (const filter of CAMBRIDGE_CLEARANCE_COOKIE_FILTERS) { |
168 | | - const cookie = await cookiesApi.get(filter as any) |
169 | | - if (cookie && cookie.value) { |
170 | | - return `${CAMBRIDGE_CLEARANCE_COOKIE}=${cookie.value}` |
171 | | - } |
172 | | - } |
173 | | - |
174 | | - if (cookiesApi.getAll) { |
175 | | - for (const partitionKey of [ |
176 | | - undefined, |
177 | | - { |
178 | | - topLevelSite: CAMBRIDGE_PARTITION_KEY_SITE |
179 | | - } |
180 | | - ]) { |
181 | | - const cookies = await cookiesApi.getAll({ |
182 | | - domain: 'dictionary.cambridge.org', |
183 | | - name: CAMBRIDGE_CLEARANCE_COOKIE, |
184 | | - partitionKey |
185 | | - } as any) |
186 | | - const clearanceCookie = cookies && cookies.find(cookie => cookie.value) |
187 | | - if (clearanceCookie) { |
188 | | - return `${CAMBRIDGE_CLEARANCE_COOKIE}=${clearanceCookie.value}` |
189 | | - } |
190 | | - } |
191 | | - } |
192 | | - |
193 | | - return '' |
194 | | -} |
195 | | - |
196 | | -function setRequestHeader( |
197 | | - requestHeaders: Array<{ name: string; value?: string }>, |
198 | | - name: string, |
199 | | - value: string |
200 | | -) { |
201 | | - const target = name.toLowerCase() |
202 | | - for (var i = 0; i < requestHeaders.length; ++i) { |
203 | | - if (requestHeaders[i].name.toLowerCase() === target) { |
204 | | - requestHeaders[i].value = value |
205 | | - return |
206 | | - } |
207 | | - } |
208 | | - |
209 | | - requestHeaders.push({ name, value }) |
210 | | -} |
211 | | - |
212 | | -function appendCookieHeader( |
213 | | - requestHeaders: Array<{ name: string; value?: string }>, |
214 | | - cookie: string |
215 | | -) { |
216 | | - const cookieHeader = getRequestHeader(requestHeaders, 'Cookie') |
217 | | - if (cookieHeader) { |
218 | | - if ( |
219 | | - !new RegExp(`(?:^|;\\s*)${CAMBRIDGE_CLEARANCE_COOKIE}=`).test( |
220 | | - cookieHeader.value || '' |
221 | | - ) |
222 | | - ) { |
223 | | - cookieHeader.value = cookieHeader.value |
224 | | - ? `${cookieHeader.value}; ${cookie}` |
225 | | - : cookie |
226 | | - } |
227 | | - return |
228 | | - } |
229 | | - |
230 | | - requestHeaders.push({ name: 'Cookie', value: cookie }) |
231 | | -} |
232 | | - |
233 | | -function getRequestHeader( |
234 | | - requestHeaders: Array<{ name: string; value?: string }>, |
235 | | - name: string |
236 | | -) { |
237 | | - const target = name.toLowerCase() |
238 | | - return requestHeaders.find(header => header.name.toLowerCase() === target) |
239 | | -} |
240 | | - |
241 | | -function getChromeDeclarativeNetRequest(): |
242 | | - | ChromeDeclarativeNetRequest |
243 | | - | undefined { |
244 | | - const chromeApi = (self as any).chrome |
245 | | - return chromeApi && chromeApi.declarativeNetRequest |
246 | | - ? chromeApi.declarativeNetRequest |
247 | | - : undefined |
248 | | -} |
249 | | - |
250 | | -function getBrowserWebRequest(): BrowserWebRequest | undefined { |
251 | | - const browserApi = browser as |
252 | | - | typeof browser |
253 | | - | { webRequest?: BrowserWebRequest } |
254 | | - return browserApi && browserApi.webRequest ? browserApi.webRequest : undefined |
255 | | -} |
256 | | - |
257 | | -function getBrowserCookies(): typeof browser.cookies | undefined { |
258 | | - const browserApi = browser as |
259 | | - | typeof browser |
260 | | - | { cookies?: typeof browser.cookies } |
261 | | - return browserApi && browserApi.cookies ? browserApi.cookies : undefined |
262 | | -} |
| 3 | +export const ensureNetworkCompatibility = createCookieHeaderNetworkCompatibility( |
| 4 | + { |
| 5 | + origin: 'https://dictionary.cambridge.org', |
| 6 | + cookieDomain: 'dictionary.cambridge.org', |
| 7 | + topLevelSite: 'https://cambridge.org', |
| 8 | + urls: ['https://dictionary.cambridge.org/*'], |
| 9 | + ruleId: 32002, |
| 10 | + ruleRegexFilter: '^https://dictionary\\.cambridge\\.org/.*', |
| 11 | + fallbackCookieNames: ['cf_clearance'] |
| 12 | + } |
| 13 | +) |
0 commit comments