blob: c6ed999030ab438c1c04662de93c5d79b09fe31f [file] [log] [blame]
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +01001import MWOptionsWatcherClient from '../common/mainWorldOptionsWatcher/Client.js';
2import { OptionCodename } from '../common/options/optionsPrototype.js';
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +01003import {
4 InterceptedResponse,
5 ResponseModifierPort,
6 Result,
7} from './ResponseModifier.port.js';
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +01008
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +01009import { Modifier } from './responseModifiers/types.js';
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +010010
11// Content script target
12export const kCSTarget = 'TWPT-XHRInterceptorOptionsWatcher-CS';
13// Main world (AKA regular web page) target
14export const kMWTarget = 'TWPT-XHRInterceptorOptionsWatcher-MW';
15
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +010016export default class ResponseModifierAdapter implements ResponseModifierPort {
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +010017 private optionsWatcher: MWOptionsWatcherClient;
18
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +010019 constructor(private responseModifiers: Modifier[]) {
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +010020 this.optionsWatcher = new MWOptionsWatcherClient(
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +010021 Array.from(this.watchingFeatures(this.responseModifiers)),
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +010022 kCSTarget,
23 kMWTarget,
24 );
25 }
26
27 private watchingFeatures(modifiers: Modifier[]): Set<OptionCodename> {
28 const union = new Set<OptionCodename>();
29 for (const m of modifiers) {
30 if (!m.featureGated) continue;
31 for (const feature of m.features) union.add(feature);
32 }
33 return union;
34 }
35
36 private async getMatchingModifiers(requestUrl: string) {
37 // First filter modifiers which match the request URL regex.
Adrià Vilanova Martínez9c418ab2024-12-05 15:34:40 +010038 const urlModifiers = this.responseModifiers.filter((modifier) =>
Adrià Vilanova Martínez47c4c812024-12-05 15:34:40 +010039 requestUrl.match(modifier.urlRegex),
40 );
41
42 // Now filter modifiers which require a certain feature to be enabled
43 // (feature-gated modifiers).
44 const featuresAreEnabled = await this.optionsWatcher.areEnabled(
45 Array.from(this.watchingFeatures(urlModifiers)),
46 );
47
48 // #!if !production
49 if (Object.keys(featuresAreEnabled).length > 0) {
50 console.debug(
51 '[XHR Interceptor - Response Modifier] Requested features',
52 featuresAreEnabled,
53 'for request',
54 requestUrl,
55 );
56 }
57 // #!endif
58
59 return urlModifiers.filter((modifier) => {
60 return !modifier.featureGated || modifier.isEnabled(featuresAreEnabled);
61 });
62 }
63
64 async intercept(interception: InterceptedResponse): Promise<Result> {
65 const matchingModifiers = await this.getMatchingModifiers(interception.url);
66
67 // If we didn't find any matching modifiers, return the response right away.
68 if (matchingModifiers.length === 0) return { wasModified: false };
69
70 // Otherwise, apply the modifiers sequentially and set the new response.
71 let json = interception.originalResponse;
72 for (const modifier of matchingModifiers) {
73 json = await modifier.interceptor(json, interception.url);
74 }
75 return {
76 wasModified: true,
77 modifiedResponse: json,
78 };
79 }
80}