|
| 1 | +/** |
| 2 | + * Ghostery Browser Extension |
| 3 | + * https://www.ghostery.com/ |
| 4 | + * |
| 5 | + * Copyright 2017-present Ghostery GmbH. All rights reserved. |
| 6 | + * |
| 7 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 8 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 9 | + * file, You can obtain one at http://mozilla.org/MPL/2.0 |
| 10 | + */ |
| 11 | + |
| 12 | +import { html, store } from 'hybrids'; |
| 13 | +import { detectFilterType } from '@cliqz/adblocker'; |
| 14 | +import convert from '../dnr-converter.js'; |
| 15 | +import CustomFiltersInput from '../store/custom-filters-input.js'; |
| 16 | + |
| 17 | +async function onSave(host) { |
| 18 | + const { networkFilters, cosmeticFilters } = host.filters; |
| 19 | + const output = { |
| 20 | + networkFilters, |
| 21 | + cosmeticFilters, |
| 22 | + dnrRules: [], |
| 23 | + }; |
| 24 | + const dnrErrors = []; |
| 25 | + |
| 26 | + const results = await Promise.allSettled( |
| 27 | + networkFilters.map((filter) => convert(filter)), |
| 28 | + ); |
| 29 | + |
| 30 | + for (const result of results) { |
| 31 | + dnrErrors.push(...result.value.errors); |
| 32 | + output.dnrRules.push(...result.value.rules); |
| 33 | + } |
| 34 | + |
| 35 | + host.dnrErrors = dnrErrors; |
| 36 | + |
| 37 | + if (!dnrErrors.length) { |
| 38 | + store.submit(host.input); |
| 39 | + host.output = output; |
| 40 | + } |
| 41 | +} |
| 42 | + |
| 43 | +export default { |
| 44 | + input: store(CustomFiltersInput, { draft: true }), |
| 45 | + output: { |
| 46 | + get: async () => { |
| 47 | + const { |
| 48 | + networkFilters = [], |
| 49 | + cosmeticFilters = [], |
| 50 | + dnrRules = [], |
| 51 | + } = (await chrome.storage.local.get(['custom-filters-output']))[ |
| 52 | + 'custom-filters-output' |
| 53 | + ] || {}; |
| 54 | + |
| 55 | + return { |
| 56 | + networkFilters, |
| 57 | + cosmeticFilters, |
| 58 | + dnrRules, |
| 59 | + }; |
| 60 | + }, |
| 61 | + set: async ( |
| 62 | + _, |
| 63 | + { networkFilters = [], cosmeticFilters = [], dnrRules = [] } = {}, |
| 64 | + lastValue, |
| 65 | + ) => { |
| 66 | + if (lastValue) { |
| 67 | + await chrome.storage.local.set({ |
| 68 | + 'custom-filters-output': { |
| 69 | + networkFilters, |
| 70 | + cosmeticFilters, |
| 71 | + dnrRules, |
| 72 | + }, |
| 73 | + }); |
| 74 | + } |
| 75 | + return { |
| 76 | + networkFilters, |
| 77 | + cosmeticFilters, |
| 78 | + dnrRules, |
| 79 | + }; |
| 80 | + }, |
| 81 | + }, |
| 82 | + dnrErrors: { set: (_, values = []) => values }, |
| 83 | + filters: (host) => { |
| 84 | + const networkFilters = []; |
| 85 | + const cosmeticFilters = []; |
| 86 | + const errors = []; |
| 87 | + |
| 88 | + let filters = []; |
| 89 | + |
| 90 | + if (store.ready(host.input)) { |
| 91 | + filters = host.input.text |
| 92 | + .split('\n') |
| 93 | + .map((f) => f.trim()) |
| 94 | + .filter(Boolean); |
| 95 | + } |
| 96 | + |
| 97 | + for (const filter of filters) { |
| 98 | + const filterType = detectFilterType(filter); |
| 99 | + switch (filterType) { |
| 100 | + case 1: // NETWORK |
| 101 | + networkFilters.push(filter); |
| 102 | + break; |
| 103 | + case 2: // COSMETIC |
| 104 | + cosmeticFilters.push(filter); |
| 105 | + break; |
| 106 | + default: |
| 107 | + errors.push(`Filter not supported: '${filter}'`); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + return { |
| 112 | + networkFilters, |
| 113 | + cosmeticFilters, |
| 114 | + errors, |
| 115 | + }; |
| 116 | + }, |
| 117 | + content: ({ input, filters, output, dnrErrors }) => html` |
| 118 | + <template layout="column gap:3"> |
| 119 | + ${store.ready(input) && |
| 120 | + html` |
| 121 | + <textarea rows="10" oninput="${html.set(input, 'text')}"> |
| 122 | +${input.text}</textarea |
| 123 | + > |
| 124 | + `} |
| 125 | + <div layout="row gap items:center"> |
| 126 | + <ui-button |
| 127 | + size="small" |
| 128 | + type=${filters.errors.length > 0 ? 'outline-error' : 'outline'} |
| 129 | + disabled=${filters.errors.length > 0 || dnrErrors.length > 0} |
| 130 | + onclick="${onSave}" |
| 131 | + layout="shrink:0" |
| 132 | + > |
| 133 | + <a>Update</a> |
| 134 | + </ui-button> |
| 135 | + </div> |
| 136 | + <section layout="gap items:center"> |
| 137 | + <h4>Errors</h4> |
| 138 | + <ul> |
| 139 | + ${[...filters.errors, ...dnrErrors].map( |
| 140 | + (error) => |
| 141 | + html`<li> |
| 142 | + <ui-text color="danger-500">${error}</ui-text> |
| 143 | + </li>`, |
| 144 | + )} |
| 145 | + </ul> |
| 146 | + <h4>Filters</h4> |
| 147 | + <div>Network filters: ${filters.networkFilters.length}</div> |
| 148 | + <div>Cosmetic filters: ${filters.cosmeticFilters.length}</div> |
| 149 | + <div>Filter errors: ${filters.errors.length}</div> |
| 150 | + ${html.resolve( |
| 151 | + output.then( |
| 152 | + ({ networkFilters, cosmeticFilters, dnrRules }) => |
| 153 | + html` |
| 154 | + <h4>Output</h4> |
| 155 | + <div>Network filters: ${networkFilters.length}</div> |
| 156 | + <div>Cosmetic filters: ${cosmeticFilters.length}</div> |
| 157 | + <div>DNR rules: ${dnrRules.length}</div> |
| 158 | + <ul> |
| 159 | + ${dnrRules.map( |
| 160 | + (rule) => html`<li>${JSON.stringify(rule, null, 2)}</li>`, |
| 161 | + )} |
| 162 | + </ul> |
| 163 | + `, |
| 164 | + ), |
| 165 | + )} |
| 166 | + </section> |
| 167 | + </template> |
| 168 | + `, |
| 169 | +}; |
0 commit comments