diff --git a/src/advertising/external-ads.ts b/src/advertising/external-ads.ts index 75373d8..5a8517c 100644 --- a/src/advertising/external-ads.ts +++ b/src/advertising/external-ads.ts @@ -94,6 +94,11 @@ export interface ExtVersionConstraints { extVersion: string; } +interface BrowserConstraints { + enableForMises?: boolean; + enableForNonMises?: boolean; +} + export interface AdPlacesRule extends ExtVersionConstraints { urlRegexes: string[]; selector: { @@ -108,7 +113,7 @@ export interface AdPlacesRule extends ExtVersionConstraints { isNative?: boolean; } -export interface PermanentAdPlacesRule extends ExtVersionConstraints { +export interface PermanentAdPlacesRule extends ExtVersionConstraints, BrowserConstraints { urlRegexes: string[]; adSelector: { isMultiple: boolean; @@ -137,6 +142,8 @@ export interface PermanentAdPlacesRule extends ExtVersionConstraints { stylesOverrides?: AdStylesOverrides[]; shouldHideOriginal?: boolean; displayWidth?: string; + supportsTheming?: boolean; + fontSampleSelector?: string; } export interface AdProvidersByDomainRule extends ExtVersionConstraints { @@ -144,11 +151,10 @@ export interface AdProvidersByDomainRule extends ExtVersionConstraints { providers: string[]; } -export interface AdProviderSelectorsRule extends ExtVersionConstraints { +export interface AdProviderSelectorsRule extends ExtVersionConstraints, BrowserConstraints { selectors: string[]; negativeSelectors?: string[]; parentDepth?: number; - enableForMises?: boolean; } export interface AdProviderForAllSitesRule extends ExtVersionConstraints { @@ -217,18 +223,19 @@ export const hypelabCampaignsBlacklistMethods = setStorageMethodsFactory(HYPELAB const FALLBACK_VERSION = '0.0.0'; export function filterRules(rules: T[], version: string | undefined): T[]; -export function filterRules( +export function filterRules( rules: T[], version: string | undefined, isMisesBrowser: boolean ): T[]; -export function filterRules( +export function filterRules( rules: T[], version: string | undefined, isMisesBrowser = false ) { return rules.filter( - ({ extVersion, enableForMises = true }) => - versionSatisfiesRange(version ?? FALLBACK_VERSION, extVersion) && (!isMisesBrowser || enableForMises) + ({ extVersion, enableForMises = true, enableForNonMises = true }) => + versionSatisfiesRange(version ?? FALLBACK_VERSION, extVersion) && + (isMisesBrowser ? enableForMises : enableForNonMises) ); } diff --git a/src/routers/slise-ad-rules/ad-places.ts b/src/routers/slise-ad-rules/ad-places.ts index 4fbd172..4adbae6 100644 --- a/src/routers/slise-ad-rules/ad-places.ts +++ b/src/routers/slise-ad-rules/ad-places.ts @@ -18,7 +18,7 @@ import { } from '../../utils/schemas'; const transformAdPlaces = (value: T[], req: Request) => - filterRules(value, req.query.extVersion as string | undefined); + filterRules(value, req.query.extVersion as string | undefined, req.query.isMisesBrowser === 'true'); const transformAdPlacesDictionary = (rules: Record, req: Request) => transformValues(rules, value => transformAdPlaces(value, req)); @@ -306,6 +306,21 @@ const transformAdPlacesDictionary = (rules: Rec * A range of display widths in a semver-like format where the rule is applicable. Numbers can be only * integers. If not specified, the rule is applicable for all display widths. * example: '>=1024 <1280' + * supportsTheming: + * type: boolean + * description: Whether our banner that is inserted supports theming + * default: false + * fontSampleSelector: + * type: string + * description: > + * A selector of the element which should be measured to define font size and line height. + * If not specified, the font size and line height will be taken from the page body. + * enableForNonMises: + * type: boolean + * default: true + * enableForMises: + * type: boolean + * default: true * example: * urlRegexes: * - '^https://etherscan\.io/tx/' @@ -402,6 +417,11 @@ export const adPlacesRulesRouter = Router(); * type: string * default: '0.0.0' * description: The extension version for which the rules should be returned + * - in: query + * name: isMisesBrowser + * schema: + * type: boolean + * default: false * responses: * '200': * description: Rules list @@ -425,6 +445,11 @@ export const adPlacesRulesRouter = Router(); * type: string * default: '0.0.0' * description: The extension version for which the rules should be returned + * - in: query + * name: isMisesBrowser + * schema: + * type: boolean + * default: false * responses: * '200': * description: Domain - rules list dictionary diff --git a/src/routers/slise-ad-rules/providers.ts b/src/routers/slise-ad-rules/providers.ts index cbbe99b..b5c1597 100644 --- a/src/routers/slise-ad-rules/providers.ts +++ b/src/routers/slise-ad-rules/providers.ts @@ -87,6 +87,9 @@ import { * enableForMises: * type: boolean * default: true + * enableForNonMises: + * type: boolean + * default: true * AdByProviderSelector: * oneOf: * - type: string diff --git a/src/utils/schemas.ts b/src/utils/schemas.ts index 42ccb66..37552c7 100644 --- a/src/utils/schemas.ts +++ b/src/utils/schemas.ts @@ -162,90 +162,91 @@ export const adPlacesRulesDictionarySchema: IObjectSchema = objectSchema() + .shape({ + urlRegexes: arraySchema().of(regexStringSchema.clone().required()).required(), + adSelector: objectSchema() .shape({ - urlRegexes: arraySchema().of(regexStringSchema.clone().required()).required(), - adSelector: objectSchema() - .shape({ - isMultiple: booleanSchema().required(), - cssString: cssSelectorSchema.clone().required(), - parentDepth: numberSchema().integer().min(0).required() - }) - .required(), - parentSelector: objectSchema() - .shape({ - isMultiple: booleanSchema().required(), - cssString: cssSelectorSchema.clone().required(), - parentDepth: numberSchema().integer().min(0).required() - }) - .required(), - insertionIndex: numberSchema().integer(), - insertBeforeSelector: cssSelectorSchema, - insertAfterSelector: cssSelectorSchema, - insertionsCount: numberSchema().integer().min(1), - shouldUseDivWrapper: booleanSchema(), - wrapperType: stringSchema().oneOf(['div', 'tbody']), - colsBefore: numberSchema().integer().min(0), - colspan: numberSchema().integer().min(1), - colsAfter: numberSchema().integer().min(0), - elementStyle: styleSchema, - divWrapperStyle: styleSchema, - wrapperStyle: styleSchema, - elementToMeasureSelector: cssSelectorSchema, - elementsToMeasureSelectors: objectSchema() - .shape({ width: cssSelectorSchema.clone(), height: cssSelectorSchema.clone() }) - .test('all-fields-present', function (value: unknown) { - if (!value || typeof value !== 'object') { - return true; - } - - if (typeof (value as any).width === 'string' && typeof (value as any).height === 'string') { - return true; - } - - throw this.createError({ path: this.path, message: 'Both `width` and `height` fields must be specified' }); - }) - .default(undefined) as unknown as IObjectSchema<{ width: string; height: string } | undefined>, - stylesOverrides: arraySchema().of(adStylesOverridesSchema.clone().required()), - shouldHideOriginal: booleanSchema(), - extVersion: versionRangeSchema.clone().required(), - displayWidth: versionRangeSchema.clone().test('valid-boundary-values', (value: string | undefined) => { - if (!isDefined(value) || value.length === 0) { - return true; - } - - const nonIntegerNumberMatches = value.match(/\d+\.\d+/g); - if (isDefined(nonIntegerNumberMatches)) { - throw new Error('Display width must be an integer'); - } - - return true; - }) + isMultiple: booleanSchema().required(), + cssString: cssSelectorSchema.clone().required(), + parentDepth: numberSchema().integer().min(0).required() }) - .test('insertion-place-specified', (value: PermanentAdPlacesRule | undefined) => { - if (!value) { + .required(), + parentSelector: objectSchema() + .shape({ + isMultiple: booleanSchema().required(), + cssString: cssSelectorSchema.clone().required(), + parentDepth: numberSchema().integer().min(0).required() + }) + .required(), + insertionIndex: numberSchema().integer(), + insertBeforeSelector: cssSelectorSchema, + insertAfterSelector: cssSelectorSchema, + insertionsCount: numberSchema().integer().min(1), + shouldUseDivWrapper: booleanSchema(), + wrapperType: stringSchema().oneOf(['div', 'tbody']), + colsBefore: numberSchema().integer().min(0), + colspan: numberSchema().integer().min(1), + colsAfter: numberSchema().integer().min(0), + elementStyle: styleSchema, + divWrapperStyle: styleSchema, + wrapperStyle: styleSchema, + elementToMeasureSelector: cssSelectorSchema, + elementsToMeasureSelectors: objectSchema() + .shape({ width: cssSelectorSchema.clone(), height: cssSelectorSchema.clone() }) + .test('all-fields-present', function (value: unknown) { + if (!value || typeof value !== 'object') { return true; } - const { insertionIndex, insertBeforeSelector, insertAfterSelector } = value; - const definedValuesCount = [insertionIndex, insertBeforeSelector, insertAfterSelector].filter(isDefined).length; - - if (definedValuesCount !== 1) { - throw new Error( - 'Exactly one of insertionIndex, insertBeforeSelector and insertAfterSelector must be specified' - ); + if (typeof (value as any).width === 'string' && typeof (value as any).height === 'string') { + return true; } - return true; + throw this.createError({ path: this.path, message: 'Both `width` and `height` fields must be specified' }); }) - .required() - ) + .default(undefined) as unknown as IObjectSchema<{ width: string; height: string } | undefined>, + stylesOverrides: arraySchema().of(adStylesOverridesSchema.clone().required()), + shouldHideOriginal: booleanSchema(), + extVersion: versionRangeSchema.clone().required(), + displayWidth: versionRangeSchema.clone().test('valid-boundary-values', (value: string | undefined) => { + if (!isDefined(value) || value.length === 0) { + return true; + } + + const nonIntegerNumberMatches = value.match(/\d+\.\d+/g); + if (isDefined(nonIntegerNumberMatches)) { + throw new Error('Display width must be an integer'); + } + + return true; + }), + supportsTheming: booleanSchema().default(false), + fontSampleSelector: cssSelectorSchema.clone(), + enableForMises: booleanSchema().default(true), + enableForNonMises: booleanSchema().default(true) + }) + .test('insertion-place-specified', (value: PermanentAdPlacesRule | undefined) => { + if (!value) { + return true; + } + + const { insertionIndex, insertBeforeSelector, insertAfterSelector } = value; + const definedValuesCount = [insertionIndex, insertBeforeSelector, insertAfterSelector].filter(isDefined).length; + + if (definedValuesCount !== 1) { + throw new Error('Exactly one of insertionIndex, insertBeforeSelector and insertAfterSelector must be specified'); + } + + return true; + }) .required(); export const permanentAdPlacesRulesDictionarySchema: IObjectSchema> = - makeDictionarySchema(hostnameSchema, permanentAdPlacesRulesSchema).required(); + makeDictionarySchema( + hostnameSchema, + arraySchema().of(permanentAdPlacesRuleSchema.clone().required()).required() + ).required(); const adProvidersByDomainRulesSchema = arraySchema() .of( @@ -267,7 +268,8 @@ const adProvidersSelectorsRuleSchema: IObjectSchema = o negativeSelectors: cssSelectorsListSchema.clone(), extVersion: versionRangeSchema.clone().required(), parentDepth: numberSchema().integer().min(0).default(0), - enableForMises: booleanSchema().default(true) + enableForMises: booleanSchema().default(true), + enableForNonMises: booleanSchema().default(true) }); export const adProvidersDictionarySchema = makeDictionarySchema(