blob: c6ed999030ab438c1c04662de93c5d79b09fe31f [file] [log] [blame]
import MWOptionsWatcherClient from '../common/mainWorldOptionsWatcher/Client.js';
import { OptionCodename } from '../common/options/optionsPrototype.js';
import {
InterceptedResponse,
ResponseModifierPort,
Result,
} from './ResponseModifier.port.js';
import { Modifier } from './responseModifiers/types.js';
// Content script target
export const kCSTarget = 'TWPT-XHRInterceptorOptionsWatcher-CS';
// Main world (AKA regular web page) target
export const kMWTarget = 'TWPT-XHRInterceptorOptionsWatcher-MW';
export default class ResponseModifierAdapter implements ResponseModifierPort {
private optionsWatcher: MWOptionsWatcherClient;
constructor(private responseModifiers: Modifier[]) {
this.optionsWatcher = new MWOptionsWatcherClient(
Array.from(this.watchingFeatures(this.responseModifiers)),
kCSTarget,
kMWTarget,
);
}
private watchingFeatures(modifiers: Modifier[]): Set<OptionCodename> {
const union = new Set<OptionCodename>();
for (const m of modifiers) {
if (!m.featureGated) continue;
for (const feature of m.features) union.add(feature);
}
return union;
}
private async getMatchingModifiers(requestUrl: string) {
// First filter modifiers which match the request URL regex.
const urlModifiers = this.responseModifiers.filter((modifier) =>
requestUrl.match(modifier.urlRegex),
);
// Now filter modifiers which require a certain feature to be enabled
// (feature-gated modifiers).
const featuresAreEnabled = await this.optionsWatcher.areEnabled(
Array.from(this.watchingFeatures(urlModifiers)),
);
// #!if !production
if (Object.keys(featuresAreEnabled).length > 0) {
console.debug(
'[XHR Interceptor - Response Modifier] Requested features',
featuresAreEnabled,
'for request',
requestUrl,
);
}
// #!endif
return urlModifiers.filter((modifier) => {
return !modifier.featureGated || modifier.isEnabled(featuresAreEnabled);
});
}
async intercept(interception: InterceptedResponse): Promise<Result> {
const matchingModifiers = await this.getMatchingModifiers(interception.url);
// If we didn't find any matching modifiers, return the response right away.
if (matchingModifiers.length === 0) return { wasModified: false };
// Otherwise, apply the modifiers sequentially and set the new response.
let json = interception.originalResponse;
for (const modifier of matchingModifiers) {
json = await modifier.interceptor(json, interception.url);
}
return {
wasModified: true,
modifiedResponse: json,
};
}
}