blob: c2b3a346a38a856ad1c2f9a3ef8b565ba7a29269 [file] [log] [blame]
import MWOptionsWatcherClient from '../common/mainWorldOptionsWatcher/Client.js';
import { OptionCodename } from '../common/options/optionsPrototype.js';
import { ProtobufObject } from '../common/protojs.types.js';
import createMessageRemoveParentRef from './responseModifiers/createMessageRemoveParentRef';
import flattenThread from './responseModifiers/flattenThread';
import loadMoreThread from './responseModifiers/loadMoreThread';
import { Modifier } from './responseModifiers/types';
export const responseModifiers = [
loadMoreThread,
flattenThread,
createMessageRemoveParentRef,
] as Modifier[];
// 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 ResponseModifier {
private optionsWatcher: MWOptionsWatcherClient;
constructor() {
this.optionsWatcher = new MWOptionsWatcherClient(
Array.from(this.watchingFeatures(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 = 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,
};
}
}
/**
* Represents an intercepted response.
*/
export interface InterceptedResponse {
/**
* URL of the original request.
*/
url: string;
/**
* Object with the response as intercepted without any modification.
*/
originalResponse: ProtobufObject;
}
export type Result =
| { wasModified: false }
| {
wasModified: true;
modifiedResponse: ProtobufObject;
};