diff --git a/package.json b/package.json index c5ce48f..fd947d1 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@types/express": "^4.17.17", "@types/express-jwt": "^7.4.2", "@types/express-unless": "^2.0.1", + "@types/lodash": "^4.17.0", "@types/memoizee": "^0.4.5", "@types/node": "^18.14.6", "@types/pino": "^6.3.8", diff --git a/src/advertising/external-ads.ts b/src/advertising/external-ads.ts index a1bc5a2..a735339 100644 --- a/src/advertising/external-ads.ts +++ b/src/advertising/external-ads.ts @@ -84,7 +84,7 @@ interface AdStylesOverrides { style: Record; } -interface ExtVersionConstraints { +export interface ExtVersionConstraints { extVersion: string; } @@ -132,18 +132,24 @@ export interface AdProvidersByDomainRule extends ExtVersionConstraints { export interface AdProviderSelectorsRule extends ExtVersionConstraints { selectors: string[]; + parentDepth?: number; } export interface AdProviderForAllSitesRule extends ExtVersionConstraints { providers: string[]; } +export interface ReplaceAdsUrlsBlacklistEntry extends ExtVersionConstraints { + regexes: string[]; +} + const AD_PLACES_RULES_KEY = 'ad_places_rules'; const AD_PROVIDERS_BY_SITES_KEY = 'ad_providers_by_sites'; const AD_PROVIDERS_ALL_SITES_KEY = 'ad_providers_all_sites'; const AD_PROVIDERS_LIST_KEY = 'ad_providers_list'; const PERMANENT_AD_PLACES_RULES_KEY = 'permanent_ad_places_rules'; const PERMANENT_NATIVE_AD_PLACES_RULES_KEY = 'permanent_native_ad_places_rules'; +const REPLACE_ADS_URLS_BLACKLIST_KEY = 'replace_ads_urls_blacklist'; export const adPlacesRulesMethods = objectStorageMethodsFactory(AD_PLACES_RULES_KEY, []); @@ -164,6 +170,11 @@ export const permanentNativeAdPlacesMethods = objectStorageMethodsFactory( + REPLACE_ADS_URLS_BLACKLIST_KEY, + [] +); + export const getAdProvidersForAllSites = async () => redisClient.smembers(AD_PROVIDERS_ALL_SITES_KEY); export const addAdProvidersForAllSites = async (providers: string[]) => diff --git a/src/getDAppsStats.ts b/src/getDAppsStats.ts index cd38b16..9b7115e 100644 --- a/src/getDAppsStats.ts +++ b/src/getDAppsStats.ts @@ -1,11 +1,11 @@ -import { dappList } from './utils/dapp-list-constants'; +import { DAPPS_LIST, IOS_DAPPS_LIST } from './utils/dapp-list-constants'; import logger from './utils/logger'; -const getDAppsStats = async () => { +const getDAppsStats = (forIOs: boolean) => { logger.info('Getting dApps list...'); return { - dApps: dappList + dApps: forIOs ? IOS_DAPPS_LIST : DAPPS_LIST }; }; diff --git a/src/index.ts b/src/index.ts index 9938eb9..80d4f01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,8 +66,6 @@ app.use(pinoHttp(PINO_LOGGER)); app.use(cors()); app.use(bodyParser.json()); -const dAppsProvider = new SingleQueryDataProvider(15 * 60 * 1000, getDAppsStats); - const androidApp = firebaseAdmin.initializeApp( { projectId: 'templewallet-fa3b3' @@ -162,7 +160,13 @@ app.post('/api/notifications', basicAuth, async (req, res) => { } }); -app.get('/api/dapps', makeProviderDataRequestHandler(dAppsProvider)); +app.get('/api/dapps', (req, res) => { + const platform = req.query.platform; + + const data = getDAppsStats(platform === 'ios'); + + res.status(200).header('Cache-Control', 'public, max-age=300').send(data); +}); app.get('/api/abtest', (_, res) => { const data = getABData(); diff --git a/src/routers/slise-ad-rules/ad-places.ts b/src/routers/slise-ad-rules/ad-places.ts index 22cd924..169fd29 100644 --- a/src/routers/slise-ad-rules/ad-places.ts +++ b/src/routers/slise-ad-rules/ad-places.ts @@ -1,18 +1,27 @@ -import { Router } from 'express'; +import { Request, Router } from 'express'; import { filterByVersion, permanentNativeAdPlacesMethods, permanentAdPlacesMethods, - adPlacesRulesMethods + adPlacesRulesMethods, + PermanentAdPlacesRule, + AdPlacesRule, + ExtVersionConstraints } from '../../advertising/external-ads'; import { addObjectStorageMethodsToRouter } from '../../utils/express-helpers'; +import { transformValues } from '../../utils/helpers'; import { hostnamesListSchema, permanentAdPlacesRulesDictionarySchema, adPlacesRulesDictionarySchema } from '../../utils/schemas'; +const transformAdPlaces = (value: T[], req: Request) => + filterByVersion(value, req.query.extVersion as string | undefined); +const transformAdPlacesDictionary = (rules: Record, req: Request) => + transformValues(rules, value => transformAdPlaces(value, req)); + /** * @swagger * tags: @@ -68,13 +77,14 @@ import { * type: string * ExtVersionConstraints: * type: object + * required: + * - extVersion * properties: * extVersion: * type: string * description: > * A range of versions where the rule is applicable. If not specified, the rule is applicable * for all versions. See the [ranges format](https://www.npmjs.com/package/semver#ranges) - * default: '*' * AdPlacesRule: * allOf: * - $ref: '#/components/schemas/ExtVersionConstraints' @@ -420,14 +430,15 @@ export const adPlacesRulesRouter = Router(); * '500': * $ref: '#/components/responses/ErrorResponse' */ -addObjectStorageMethodsToRouter(adPlacesRulesRouter, { +addObjectStorageMethodsToRouter(adPlacesRulesRouter, { path: '/permanent-native', methods: permanentNativeAdPlacesMethods, keyName: 'domain', objectValidationSchema: permanentAdPlacesRulesDictionarySchema, keysArrayValidationSchema: hostnamesListSchema, successfulRemovalMessage: entriesCount => `${entriesCount} entries have been removed`, - transformGotValueFn: (value, req) => filterByVersion(value, req.query.extVersion as string | undefined) + valueTransformFn: transformAdPlaces, + objectTransformFn: transformAdPlacesDictionary }); /** @@ -569,14 +580,15 @@ addObjectStorageMethodsToRouter(adPlacesRulesRouter, { * '500': * $ref: '#/components/responses/ErrorResponse' */ -addObjectStorageMethodsToRouter(adPlacesRulesRouter, { +addObjectStorageMethodsToRouter(adPlacesRulesRouter, { path: '/permanent', methods: permanentAdPlacesMethods, keyName: 'domain', objectValidationSchema: permanentAdPlacesRulesDictionarySchema, keysArrayValidationSchema: hostnamesListSchema, successfulRemovalMessage: entriesCount => `${entriesCount} entries have been removed`, - transformGotValueFn: (value, req) => filterByVersion(value, req.query.extVersion as string | undefined) + valueTransformFn: transformAdPlaces, + objectTransformFn: transformAdPlacesDictionary }); /** @@ -718,12 +730,13 @@ addObjectStorageMethodsToRouter(adPlacesRulesRouter, { * '500': * $ref: '#/components/responses/ErrorResponse' */ -addObjectStorageMethodsToRouter(adPlacesRulesRouter, { +addObjectStorageMethodsToRouter(adPlacesRulesRouter, { path: '/', methods: adPlacesRulesMethods, keyName: 'domain', objectValidationSchema: adPlacesRulesDictionarySchema, keysArrayValidationSchema: hostnamesListSchema, successfulRemovalMessage: entriesCount => `${entriesCount} entries have been removed`, - transformGotValueFn: (value, req) => filterByVersion(value, req.query.extVersion as string | undefined) + valueTransformFn: transformAdPlaces, + objectTransformFn: transformAdPlacesDictionary }); diff --git a/src/routers/slise-ad-rules/index.ts b/src/routers/slise-ad-rules/index.ts index a56c849..ea84c98 100644 --- a/src/routers/slise-ad-rules/index.ts +++ b/src/routers/slise-ad-rules/index.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import { adPlacesRulesRouter } from './ad-places'; import { adProvidersRouter } from './providers'; +import { replaceUrlsBlacklistRouter } from './replace-urls-blacklist'; /** * @swagger @@ -41,3 +42,4 @@ export const adRulesRouter = Router(); adRulesRouter.use('/ad-places', adPlacesRulesRouter); adRulesRouter.use('/providers', adProvidersRouter); +adRulesRouter.use('/replace-urls-blacklist', replaceUrlsBlacklistRouter); diff --git a/src/routers/slise-ad-rules/providers.ts b/src/routers/slise-ad-rules/providers.ts index 81be4e4..e4f443a 100644 --- a/src/routers/slise-ad-rules/providers.ts +++ b/src/routers/slise-ad-rules/providers.ts @@ -1,4 +1,5 @@ -import { Router } from 'express'; +import { Request, Router } from 'express'; +import { identity } from 'lodash'; import { addAdProvidersForAllSites, @@ -7,12 +8,14 @@ import { adProvidersMethods, adProvidersByDomainRulesMethods, AdProviderSelectorsRule, - filterByVersion + filterByVersion, + AdProvidersByDomainRule } from '../../advertising/external-ads'; import { basicAuth } from '../../middlewares/basic-auth.middleware'; import { addObjectStorageMethodsToRouter, withBodyValidation, withExceptionHandler } from '../../utils/express-helpers'; +import { isDefined, transformValues } from '../../utils/helpers'; import { - adTypesListSchema, + nonEmptyStringsListSchema, hostnamesListSchema, adProvidersByDomainsRulesDictionarySchema, adProvidersDictionarySchema @@ -69,17 +72,36 @@ import { * type: array * items: * type: string + * parentDepth: + * type: integer + * minimum: 0 + * default: 0 + * AdByProviderSelector: + * oneOf: + * - type: string + * - type: object + * required: + * - selector + * - parentDepth + * properties: + * selector: + * type: string + * parentDepth: + * type: integer * AdProvidersDictionary: * type: object * additionalProperties: * type: array * items: - * type: string + * $ref: '#/components/schemas/AdByProviderSelector' * example: * google: * - '#Ads_google_bottom_wide' * - '.GoogleAdInfo' * - 'a[href^="https://googleads.g.doubleclick.net/pcs/click"]' + * persona: + * - selector: "a.persona-product" + * parentDepth: 1 * AdProvidersInputsDictionary: * type: object * additionalProperties: @@ -178,7 +200,7 @@ adProvidersRouter .post( basicAuth, withExceptionHandler( - withBodyValidation(adTypesListSchema, async (req, res) => { + withBodyValidation(nonEmptyStringsListSchema, async (req, res) => { const providersAddedCount = await addAdProvidersForAllSites(req.body); res.status(200).send({ message: `${providersAddedCount} providers have been added` }); @@ -188,7 +210,7 @@ adProvidersRouter .delete( basicAuth, withExceptionHandler( - withBodyValidation(adTypesListSchema, async (req, res) => { + withBodyValidation(nonEmptyStringsListSchema, async (req, res) => { const providersRemovedCount = await removeAdProvidersForAllSites(req.body); res.status(200).send({ message: `${providersRemovedCount} providers have been removed` }); @@ -287,13 +309,15 @@ adProvidersRouter * '500': * $ref: '#/components/responses/ErrorResponse' */ -addObjectStorageMethodsToRouter(adProvidersRouter, { +addObjectStorageMethodsToRouter(adProvidersRouter, { path: '/by-sites', methods: adProvidersByDomainRulesMethods, keyName: 'domain', objectValidationSchema: adProvidersByDomainsRulesDictionarySchema, keysArrayValidationSchema: hostnamesListSchema, - successfulRemovalMessage: entriesCount => `${entriesCount} entries have been removed` + successfulRemovalMessage: entriesCount => `${entriesCount} entries have been removed`, + valueTransformFn: identity, + objectTransformFn: identity }); /** @@ -361,11 +385,13 @@ addObjectStorageMethodsToRouter(adProvidersRouter, { * schema: * type: array * items: - * type: string + * $ref: '#/components/schemas/AdByProviderSelector' * example: * - '#Ads_google_bottom_wide' * - '.GoogleAdInfo' * - 'a[href^="https://googleads.g.doubleclick.net/pcs/click"]' + * - selector: "a.persona-product" + * parentDepth: 1 * '500': * $ref: '#/components/responses/ErrorResponse' * /api/slise-ad-rules/providers: @@ -437,19 +463,30 @@ addObjectStorageMethodsToRouter(adProvidersRouter, { * '500': * $ref: '#/components/responses/ErrorResponse' */ -addObjectStorageMethodsToRouter(adProvidersRouter, { +const transformAdProviderSelectorsRules = (rules: AdProviderSelectorsRule[], req: Request) => + Array.from( + new Set( + filterByVersion(rules, req.query.extVersion as string | undefined) + .map(({ selectors, parentDepth }) => + isDefined(parentDepth) && parentDepth > 0 ? { selector: selectors.join(', '), parentDepth } : selectors + ) + .flat() + ) + ); + +type AdByProviderSelector = string | { selector: string; parentDepth: number }; + +addObjectStorageMethodsToRouter< + AdProviderSelectorsRule[], + Record, + AdByProviderSelector[] +>(adProvidersRouter, { path: '/', methods: adProvidersMethods, keyName: 'providerId', objectValidationSchema: adProvidersDictionarySchema, - keysArrayValidationSchema: adTypesListSchema, + keysArrayValidationSchema: nonEmptyStringsListSchema, successfulRemovalMessage: entriesCount => `${entriesCount} providers have been removed`, - transformGotValueFn: (rules, req) => - Array.from( - new Set( - filterByVersion(rules, req.query.extVersion as string | undefined) - .map(({ selectors }) => selectors) - .flat() - ) - ) + valueTransformFn: transformAdProviderSelectorsRules, + objectTransformFn: (rules, req) => transformValues(rules, value => transformAdProviderSelectorsRules(value, req)) }); diff --git a/src/routers/slise-ad-rules/replace-urls-blacklist.ts b/src/routers/slise-ad-rules/replace-urls-blacklist.ts new file mode 100644 index 0000000..35482d7 --- /dev/null +++ b/src/routers/slise-ad-rules/replace-urls-blacklist.ts @@ -0,0 +1,192 @@ +import { Router } from 'express'; + +import { + filterByVersion, + ReplaceAdsUrlsBlacklistEntry, + replaceAdsUrlsBlacklistMethods +} from '../../advertising/external-ads'; +import { addObjectStorageMethodsToRouter } from '../../utils/express-helpers'; +import { nonEmptyStringsListSchema, replaceUrlsBlacklistDictionarySchema } from '../../utils/schemas'; + +export const replaceUrlsBlacklistRouter = Router(); + +/** + * @swagger + * tags: + * name: URLs blacklist for replacing ads + * components: + * schemas: + * ReplaceAdsUrlsBlacklistEntry: + * allOf: + * - $ref: '#/components/schemas/ExtVersionConstraints' + * - type: object + * required: + * - regexes + * properties: + * regexes: + * type: array + * items: + * type: string + * format: regex + * ReplaceAdsUrlsBlacklistDictionary: + * type: object + * additionalProperties: + * type: array + * items: + * $ref: '#/components/schemas/ReplaceAdsUrlsBlacklistEntry' + * example: + * 'arbitrary-key': + * - extVersion: '>=1.21.1' + * regexes: + * - "^https://[^/?#]*google\\.com" + * - "^https://[^/?#]*youtube\\.com" + * /api/slise-ad-rules/replace-urls-blacklist/raw/all: + * get: + * summary: Get all blacklist entries + * tags: + * - URLs blacklist for replacing ads + * responses: + * '200': + * description: A dictionary of all blacklist entries + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ReplaceAdsUrlsBlacklistDictionary' + * '500': + * $ref: '#/components/responses/ErrorResponse' + * /api/slise-ad-rules/replace-urls-blacklist/{id}/raw: + * get: + * summary: Get blacklist entries by array ID + * tags: + * - URLs blacklist for replacing ads + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * description: Array ID + * responses: + * '200': + * description: An array of blacklist entries + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/ReplaceAdsUrlsBlacklistEntry' + * '500': + * $ref: '#/components/responses/ErrorResponse' + * /api/slise-ad-rules/replace-urls-blacklist/{id}: + * get: + * summary: Get blacklist entries by array ID filtered by extension version + * tags: + * - URLs blacklist for replacing ads + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: string + * description: Array ID + * - name: extVersion + * in: query + * schema: + * type: string + * default: '0.0.0' + * description: Extension version + * responses: + * '200': + * description: An array of blacklist entries + * content: + * application/json: + * schema: + * type: array + * items: + * type: string + * '500': + * $ref: '#/components/responses/ErrorResponse' + * /api/slise-ad-rules/replace-urls-blacklist: + * get: + * summary: Get all blacklist entries filtered by extension version + * tags: + * - URLs blacklist for replacing ads + * parameters: + * - name: extVersion + * in: query + * schema: + * type: string + * default: '0.0.0' + * description: Extension version + * responses: + * '200': + * description: A dictionary of blacklist entries + * content: + * application/json: + * schema: + * type: array + * items: + * type: string + * '500': + * $ref: '#/components/responses/ErrorResponse' + * post: + * summary: Add blacklist entries + * tags: + * - URLs blacklist for replacing ads + * security: + * - basicAuth: [] + * requestBody: + * description: A dictionary of blacklist entries + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ReplaceAdsUrlsBlacklistDictionary' + * responses: + * '200': + * $ref: '#/components/responses/SuccessResponse' + * '400': + * $ref: '#/components/responses/ErrorResponse' + * '401': + * $ref: '#/components/responses/UnauthorizedError' + * '500': + * $ref: '#/components/responses/ErrorResponse' + * delete: + * summary: Remove blacklist entries by arrays IDs + * tags: + * - URLs blacklist for replacing ads + * security: + * - basicAuth: [] + * requestBody: + * description: An array of arrays IDs + * content: + * application/json: + * schema: + * type: array + * items: + * type: string + * responses: + * '200': + * $ref: '#/components/responses/SuccessResponse' + * '400': + * $ref: '#/components/responses/ErrorResponse' + * '401': + * $ref: '#/components/responses/UnauthorizedError' + * '500': + * $ref: '#/components/responses/ErrorResponse' + */ +addObjectStorageMethodsToRouter(replaceUrlsBlacklistRouter, { + path: '/', + methods: replaceAdsUrlsBlacklistMethods, + keyName: 'id', + objectValidationSchema: replaceUrlsBlacklistDictionarySchema, + keysArrayValidationSchema: nonEmptyStringsListSchema, + successfulRemovalMessage: entriesCount => `${entriesCount} blacklist entries have been removed`, + objectTransformFn: (value, req) => + filterByVersion(Object.values(value).flat(), req.query.extVersion as string | undefined) + .map(({ regexes }) => regexes) + .flat(), + valueTransformFn: (value, req) => + filterByVersion(value, req.query.extVersion as string | undefined) + .map(({ regexes }) => regexes) + .flat() +}); diff --git a/src/utils/dapp-list-constants.ts b/src/utils/dapp-list-constants.ts index 752588b..90e17bb 100644 --- a/src/utils/dapp-list-constants.ts +++ b/src/utils/dapp-list-constants.ts @@ -1,4 +1,4 @@ -interface DappList { +interface DappListItem { name: string; dappUrl: string; type: DappType; @@ -16,7 +16,7 @@ enum DappType { Other = 'Other' } -export const dappList: DappList[] = [ +export const DAPPS_LIST: DappListItem[] = [ { name: 'QuipuSwap', dappUrl: 'https://quipuswap.com', @@ -154,3 +154,38 @@ export const dappList: DappList[] = [ categories: [DappType.DeFi] } ]; + +export const IOS_DAPPS_LIST: DappListItem[] = [ + { + name: 'Tezos Projects', + dappUrl: 'https://ecosystem.tezos.com', + logo: 'https://temple-wallet-stage-bucket.nyc3.cdn.digitaloceanspaces.com/dapps/tezos.png', + slug: 'ecosystem.tezos.com', + type: DappType.Other, + categories: [] + }, + { + name: 'Mad.Fish', + dappUrl: 'https://mad.fish/products', + logo: 'https://temple-wallet-stage-bucket.nyc3.cdn.digitaloceanspaces.com/dapps/madfish.png', + slug: 'mad.fish/products', + type: DappType.Other, + categories: [] + }, + { + name: 'Temple Wallet', + dappUrl: 'https://templewallet.com/download?platform=extension', + logo: 'https://temple-wallet-stage-bucket.nyc3.cdn.digitaloceanspaces.com/dapps/temple.png', + slug: 'download-tw-extension', + type: DappType.Other, + categories: [] + }, + { + name: 'TZKT', + dappUrl: 'https://tzkt.io/dapps', + logo: 'https://temple-wallet-stage-bucket.nyc3.cdn.digitaloceanspaces.com/dapps/tzkt.png', + slug: 'tzkt.io/dapps', + type: DappType.Other, + categories: [] + } +]; diff --git a/src/utils/express-helpers.ts b/src/utils/express-helpers.ts index b4b5f55..a1e58f9 100644 --- a/src/utils/express-helpers.ts +++ b/src/utils/express-helpers.ts @@ -44,19 +44,24 @@ export const withExceptionHandler = } }; -interface ObjectStorageMethodsEntrypointsConfig { +interface ObjectStorageMethodsEntrypointsConfig { path: string; - methods: ObjectStorageMethods; + methods: ObjectStorageMethods; keyName: string; - objectValidationSchema: IObjectSchema>; + objectValidationSchema: IObjectSchema>; keysArrayValidationSchema: IArraySchema; successfulRemovalMessage: (removedEntriesCount: number) => string; - transformGotValueFn?: (value: U, req: Request) => V; + objectTransformFn: (value: Record, req: Request) => ObjectResponse; + valueTransformFn: (value: StoredValue, req: Request) => ValueResponse; } -export const addObjectStorageMethodsToRouter = ( +export const addObjectStorageMethodsToRouter = < + StoredValue, + ObjectResponse = Record, + ValueResponse = StoredValue +>( router: Router, - config: ObjectStorageMethodsEntrypointsConfig + config: ObjectStorageMethodsEntrypointsConfig ) => { const { path, @@ -65,7 +70,8 @@ export const addObjectStorageMethodsToRouter = ( objectValidationSchema, keysArrayValidationSchema, successfulRemovalMessage, - transformGotValueFn = value => value as unknown as V + objectTransformFn, + valueTransformFn } = config; router.get( @@ -95,7 +101,7 @@ export const addObjectStorageMethodsToRouter = ( const value = await methods.getByKey(key); - res.status(200).send(transformGotValueFn(value, req)); + res.status(200).send(valueTransformFn(value, req)); }) ); @@ -105,12 +111,7 @@ export const addObjectStorageMethodsToRouter = ( withExceptionHandler(async (req, res) => { const values = await methods.getAllValues(); - res - .status(200) - .header('Cache-Control', 'public, max-age=300') - .send( - Object.fromEntries(Object.entries(values).map(([key, value]) => [key, transformGotValueFn(value, req)])) - ); + res.status(200).header('Cache-Control', 'public, max-age=300').send(objectTransformFn(values, req)); }) ) .post( diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 6e2f2e7..eba0a0e 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -55,3 +55,6 @@ export function safeCheck(check: () => boolean, def = false) { return def; } } + +export const transformValues = (input: Record, transformFn: (value: T) => U): Record => + Object.fromEntries(Object.entries(input).map(([key, value]) => [key, transformFn(value)])); diff --git a/src/utils/schemas.ts b/src/utils/schemas.ts index 7533c51..d624f45 100644 --- a/src/utils/schemas.ts +++ b/src/utils/schemas.ts @@ -17,7 +17,8 @@ import { AdProvidersByDomainRule, StylePropName, stylePropsNames, - AdProviderSelectorsRule + AdProviderSelectorsRule, + ReplaceAdsUrlsBlacklistEntry } from '../advertising/external-ads'; import { isValidSelectorsGroup } from '../utils/selectors.min.js'; import { isDefined } from './helpers'; @@ -86,14 +87,14 @@ export const hostnamesListSchema: IArraySchema = arraySch .of(hostnameSchema.clone().required()) .required(); -const adTypeSchema = stringSchema().min(1); +const nonEmptyStringSchema = stringSchema().min(1); // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const adTypesListSchema: IArraySchema = arraySchema() - .of(adTypeSchema.clone().required()) +export const nonEmptyStringsListSchema: IArraySchema = arraySchema() + .of(nonEmptyStringSchema.clone().required()) .min(1) .required() - .typeError('Must be a non-empty string'); + .typeError('Must be a non-empty array'); const styleSchema: IObjectSchema> = makeDictionarySchema( stringSchema() @@ -205,11 +206,23 @@ export const adProvidersByDomainsRulesDictionarySchema: IObjectSchema> = makeDictionarySchema( - adTypeSchema.clone().required(), + nonEmptyStringSchema.clone().required(), arraySchema().of(adProvidersSelectorsRuleSchema.clone().required()).required() ).required(); + +const replaceUrlsBlacklistEntrySchema = objectSchema().shape({ + extVersion: versionRangeSchema.clone().required(), + regexes: regexStringListSchema.clone().required() +}); + +export const replaceUrlsBlacklistDictionarySchema: IObjectSchema> = + makeDictionarySchema( + nonEmptyStringSchema.clone().required(), + arraySchema().of(replaceUrlsBlacklistEntrySchema.clone().required()).required() + ).required(); diff --git a/yarn.lock b/yarn.lock index 44c2709..028fa57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -810,6 +810,11 @@ dependencies: "@types/node" "*" +"@types/lodash@^4.17.0": + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" + integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"