Skip to content

Commit

Permalink
TW-1563: Add specifications for theming native ads (#178)
Browse files Browse the repository at this point in the history
* TW-1563 Add specifications for theming native ads

* TW-1563 Minor refactoring

* TW-1563 Make ts types match validation schemas and swagger types
  • Loading branch information
keshan3262 authored Dec 2, 2024
1 parent 9a6d786 commit 22c6187
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 82 deletions.
21 changes: 14 additions & 7 deletions src/advertising/external-ads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export interface ExtVersionConstraints {
extVersion: string;
}

interface BrowserConstraints {
enableForMises?: boolean;
enableForNonMises?: boolean;
}

export interface AdPlacesRule extends ExtVersionConstraints {
urlRegexes: string[];
selector: {
Expand All @@ -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;
Expand Down Expand Up @@ -137,18 +142,19 @@ export interface PermanentAdPlacesRule extends ExtVersionConstraints {
stylesOverrides?: AdStylesOverrides[];
shouldHideOriginal?: boolean;
displayWidth?: string;
supportsTheming?: boolean;
fontSampleSelector?: string;
}

export interface AdProvidersByDomainRule extends ExtVersionConstraints {
urlRegexes: string[];
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 {
Expand Down Expand Up @@ -217,18 +223,19 @@ export const hypelabCampaignsBlacklistMethods = setStorageMethodsFactory(HYPELAB
const FALLBACK_VERSION = '0.0.0';

export function filterRules<T extends ExtVersionConstraints>(rules: T[], version: string | undefined): T[];
export function filterRules<T extends ExtVersionConstraints & { enableForMises?: boolean }>(
export function filterRules<T extends ExtVersionConstraints & BrowserConstraints>(
rules: T[],
version: string | undefined,
isMisesBrowser: boolean
): T[];
export function filterRules<T extends ExtVersionConstraints & { enableForMises?: boolean }>(
export function filterRules<T extends ExtVersionConstraints & BrowserConstraints>(
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)
);
}
27 changes: 26 additions & 1 deletion src/routers/slise-ad-rules/ad-places.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../../utils/schemas';

const transformAdPlaces = <T extends ExtVersionConstraints>(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 = <T extends ExtVersionConstraints>(rules: Record<string, T[]>, req: Request) =>
transformValues(rules, value => transformAdPlaces(value, req));

Expand Down Expand Up @@ -306,6 +306,21 @@ const transformAdPlacesDictionary = <T extends ExtVersionConstraints>(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/'
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/routers/slise-ad-rules/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ import {
* enableForMises:
* type: boolean
* default: true
* enableForNonMises:
* type: boolean
* default: true
* AdByProviderSelector:
* oneOf:
* - type: string
Expand Down
150 changes: 76 additions & 74 deletions src/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,90 +162,91 @@ export const adPlacesRulesDictionarySchema: IObjectSchema<Record<string, AdPlace
adPlacesRulesSchema
).required();

const permanentAdPlacesRulesSchema = arraySchema()
.of(
objectSchema()
const permanentAdPlacesRuleSchema: IObjectSchema<PermanentAdPlacesRule> = 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<Record<string, PermanentAdPlacesRule[]>> =
makeDictionarySchema(hostnameSchema, permanentAdPlacesRulesSchema).required();
makeDictionarySchema(
hostnameSchema,
arraySchema().of(permanentAdPlacesRuleSchema.clone().required()).required()
).required();

const adProvidersByDomainRulesSchema = arraySchema()
.of(
Expand All @@ -267,7 +268,8 @@ const adProvidersSelectorsRuleSchema: IObjectSchema<AdProviderSelectorsRule> = 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<AdProviderSelectorsRule[]>(
Expand Down

0 comments on commit 22c6187

Please sign in to comment.