pl-fe: support configuring redirect services, i think it works but idk
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -55,7 +55,7 @@
|
||||
"@lexical/selection": "^0.29.0",
|
||||
"@lexical/utils": "^0.29.0",
|
||||
"@mkljczk/react-hotkeys": "^1.3.0",
|
||||
"@mkljczk/url-purify": "^0.0.2",
|
||||
"@mkljczk/url-purify": "^0.0.3",
|
||||
"@reach/combobox": "^0.18.0",
|
||||
"@reach/rect": "^0.18.0",
|
||||
"@reach/tabs": "^0.18.0",
|
||||
@ -136,6 +136,7 @@
|
||||
"sass": "^1.86.3",
|
||||
"stringz": "^2.1.0",
|
||||
"tiny-queue": "^0.2.1",
|
||||
"use-mutative": "^1.2.1",
|
||||
"util": "^0.12.5",
|
||||
"valibot": "^1.0.0-beta.12",
|
||||
"zustand": "^5.0.3",
|
||||
|
||||
@ -50,6 +50,8 @@ interface IParsedContent {
|
||||
emojis?: Array<CustomEmoji>;
|
||||
/** Whether to call a function to remove tracking parameters from URLs. */
|
||||
cleanUrls?: boolean;
|
||||
/** Whether to call a function to redirect URLs to popular websites to privacy-respecting proxy services. */
|
||||
redirectUrls?: boolean;
|
||||
/** Whether to display link target domain when it's not part of the text. */
|
||||
displayTargetHost?: boolean;
|
||||
greentext?: boolean;
|
||||
@ -100,6 +102,7 @@ function parseContent({
|
||||
hasQuote,
|
||||
emojis,
|
||||
cleanUrls = false,
|
||||
redirectUrls = false,
|
||||
displayTargetHost = true,
|
||||
greentext = false,
|
||||
speakAsCat = false,
|
||||
@ -172,7 +175,7 @@ function parseContent({
|
||||
|
||||
if (cleanUrls) {
|
||||
try {
|
||||
href = Purify.clearUrl(href);
|
||||
href = Purify.clearUrl(href, cleanUrls, redirectUrls);
|
||||
} catch (_) {
|
||||
//
|
||||
}
|
||||
@ -275,9 +278,11 @@ function parseContent({
|
||||
const ParsedContent: React.FC<IParsedContent> = React.memo((props) => {
|
||||
const { urlPrivacy } = useSettings();
|
||||
|
||||
if (props.cleanUrls === undefined) {
|
||||
props = { ...props, cleanUrls: urlPrivacy.clearLinksInContent, displayTargetHost: urlPrivacy.displayTargetHost };
|
||||
}
|
||||
props = { ...props };
|
||||
|
||||
if (props.cleanUrls === undefined) props.cleanUrls = urlPrivacy.clearLinksInContent;
|
||||
if (props.redirectUrls === undefined) props.redirectUrls = urlPrivacy.redirectLinksMode !== 'off';
|
||||
if (props.displayTargetHost === undefined) props.displayTargetHost = urlPrivacy.displayTargetHost;
|
||||
|
||||
return parseContent(props, false);
|
||||
}, (prevProps, nextProps) => prevProps.html === nextProps.html);
|
||||
|
||||
@ -147,6 +147,7 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
||||
hasQuote: !!status.quote_id,
|
||||
emojis: status.emojis,
|
||||
cleanUrls: urlPrivacy.clearLinksInContent,
|
||||
redirectUrls: urlPrivacy.redirectLinksMode !== 'off',
|
||||
displayTargetHost: urlPrivacy.displayTargetHost,
|
||||
greentext,
|
||||
speakAsCat: status.account.speak_as_cat,
|
||||
|
||||
@ -41,7 +41,7 @@ const StatePlugin: React.FC<IStatePlugin> = ({ composeId, isWysiwyg }) => {
|
||||
|
||||
editorState.read(() => {
|
||||
const compareUrl = (url: string) => {
|
||||
const cleanUrl = Purify.clearUrl(url);
|
||||
const cleanUrl = Purify.clearUrl(url, true, false);
|
||||
return {
|
||||
originalUrl: url,
|
||||
cleanUrl,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { mappings } from '@mkljczk/url-purify';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, FormattedList, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useMutative } from 'use-mutative';
|
||||
|
||||
import { changeSetting } from 'pl-fe/actions/settings';
|
||||
import List, { ListItem } from 'pl-fe/components/list';
|
||||
@ -12,16 +14,27 @@ import FormGroup from 'pl-fe/components/ui/form-group';
|
||||
import Input from 'pl-fe/components/ui/input';
|
||||
import Toggle from 'pl-fe/components/ui/toggle';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { KVStoreRedirectServicesItem } from 'pl-fe/utils/url-purify';
|
||||
|
||||
import { SelectDropdown } from '../forms';
|
||||
|
||||
const messages = defineMessages({
|
||||
urlPrivacy: { id: 'settings.url_privacy', defaultMessage: 'URL privacy' },
|
||||
rulesUrlPlaceholder: { id: 'url_privacy.rules_url.placeholder', defaultMessage: 'Rules URL' },
|
||||
hashUrlPlaceholder: { id: 'url_privacy.hash_url.placeholder', defaultMessage: 'Hash URL' },
|
||||
redirectLinksModeOff: { id: 'url_privacy.redirect_links_mode.off', defaultMessage: 'Disabled' },
|
||||
redirectLinksModeAuto: { id: 'url_privacy.redirect_links_mode.auto', defaultMessage: 'From URL' },
|
||||
redirectLinksModeManual: { id: 'url_privacy.redirect_links_mode.manual', defaultMessage: 'Specify manually' },
|
||||
redirectServicesUrlPlaceholder: { id: 'url_privacy.redirect_services_url.placeholder', defaultMessage: 'Rules URL' },
|
||||
redirectServicePlaceholder: { id: 'url_privacy.redirect_services_url.placeholder', defaultMessage: 'eg. https://proxy.example.org' },
|
||||
});
|
||||
|
||||
const UrlPrivacy = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const me = useAppSelector((state) => state.me);
|
||||
const intl = useIntl();
|
||||
|
||||
const { urlPrivacy } = useSettings();
|
||||
@ -29,25 +42,62 @@ const UrlPrivacy = () => {
|
||||
const [displayTargetHost, setDisplayTargetHost] = useState(urlPrivacy.displayTargetHost);
|
||||
const [clearLinksInCompose, setClearLinksInCompose] = useState(urlPrivacy.clearLinksInCompose);
|
||||
const [clearLinksInContent, setClearLinksInContent] = useState(urlPrivacy.clearLinksInContent);
|
||||
// const [allowReferralMarketing, setAllowReferralMarketing] = useState(urlPrivacy.allowReferralMarketing);
|
||||
const [hashUrl, setHashUrl] = useState(urlPrivacy.hashUrl);
|
||||
const [rulesUrl, setRulesUrl] = useState(urlPrivacy.rulesUrl);
|
||||
const [redirectLinksMode, setRedirectLinksMode] = useState(urlPrivacy.redirectLinksMode);
|
||||
const [redirectServicesUrl, setRedirectServicesUrl] = useState(urlPrivacy.redirectServicesUrl);
|
||||
const [redirectServices, setRedirectServices] = useMutative(urlPrivacy.redirectServices);
|
||||
|
||||
const onSubmit = () => {
|
||||
dispatch(changeSetting(['urlPrivacy'], {
|
||||
const value = {
|
||||
...urlPrivacy,
|
||||
displayTargetHost,
|
||||
clearLinksInCompose,
|
||||
clearLinksInContent,
|
||||
// allowReferralMarketing,
|
||||
hashUrl,
|
||||
rulesUrl,
|
||||
}, {
|
||||
redirectLinksMode,
|
||||
redirectServicesUrl,
|
||||
redirectServices,
|
||||
};
|
||||
|
||||
switch (redirectLinksMode) {
|
||||
case 'off':
|
||||
value.redirectServicesUrl = '';
|
||||
value.redirectServices = {};
|
||||
break;
|
||||
case 'manual':
|
||||
value.redirectServicesUrl = '';
|
||||
break;
|
||||
case 'auto':
|
||||
value.redirectServices = {};
|
||||
break;
|
||||
}
|
||||
|
||||
dispatch(changeSetting(['urlPrivacy'], value, {
|
||||
save: true,
|
||||
showAlert: true,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleChangeRedirectLinksMode = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (redirectLinksMode === 'auto' && event.target.value === 'manual') {
|
||||
KVStore.getItem<KVStoreRedirectServicesItem>(`url-purify-redirect-services:${me}`).then((services) => {
|
||||
if (!services?.redirectServices) return;
|
||||
|
||||
setRedirectServices(
|
||||
Object.fromEntries(
|
||||
mappings.map(({ name, targets }) => ([
|
||||
name,
|
||||
services.redirectServices.find((service) => targets.includes(service.type) && service.instances.length)?.instances[0].split('|')[0] || '',
|
||||
])),
|
||||
),
|
||||
);
|
||||
}).catch(() => { });
|
||||
}
|
||||
return setRedirectLinksMode(event.target.value as 'off');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}, [dispatch]);
|
||||
|
||||
@ -74,10 +124,6 @@ const UrlPrivacy = () => {
|
||||
<ListItem label={<FormattedMessage id='url_privacy.clear_links_in_content' defaultMessage='Remove tracking parameters from displayed posts' />}>
|
||||
<Toggle checked={clearLinksInContent} onChange={({ target }) => setClearLinksInContent(target.checked)} />
|
||||
</ListItem>
|
||||
|
||||
{/* <ListItem label={<FormattedMessage id='url_privacy.allow_referral_marketing' defaultMessage='Make exception for referral marketing parameters' />}>
|
||||
<Toggle checked={allowReferralMarketing} onChange={({ target }) => setAllowReferralMarketing(target.checked)} disabled={!clearLinksInCompose && !clearLinksInContent} />
|
||||
</ListItem> */}
|
||||
</List>
|
||||
|
||||
<FormGroup
|
||||
@ -104,6 +150,55 @@ const UrlPrivacy = () => {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<List>
|
||||
<ListItem label={<FormattedMessage id='url_privacy.redirect_links_mode' defaultMessage='Redirect links to popular websites to privacy-respecting proxy services' />}>
|
||||
<SelectDropdown
|
||||
className='max-w-fit'
|
||||
items={{
|
||||
off: intl.formatMessage(messages.redirectLinksModeOff),
|
||||
auto: intl.formatMessage(messages.redirectLinksModeAuto),
|
||||
manual: intl.formatMessage(messages.redirectLinksModeManual),
|
||||
}}
|
||||
defaultValue={redirectLinksMode}
|
||||
onChange={handleChangeRedirectLinksMode}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
{redirectLinksMode === 'auto' && (
|
||||
<FormGroup
|
||||
labelText={<FormattedMessage id='url_privacy.redirect_services_url.label' defaultMessage='Redirect services URLs database address' />}
|
||||
hintText={<FormattedMessage id='url_privacy.redirect_services_url.hint' defaultMessage='URLs database in Farside-compatible format, eg. {url}' values={{ url: 'https://raw.githubusercontent.com/benbusby/farside/refs/heads/main/services.json' }} />}
|
||||
>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.redirectServicesUrlPlaceholder)}
|
||||
value={redirectServicesUrl}
|
||||
onChange={({ target }) => setRedirectServicesUrl(target.value)}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
{redirectLinksMode === 'manual' && (
|
||||
mappings.map((service) => (
|
||||
<FormGroup
|
||||
key={service.name}
|
||||
labelText={<FormattedMessage id='url_privacy.redirect_services.name' defaultMessage='{name}' values={{ name: service.name }} />}
|
||||
hintText={<FormattedMessage id='url_privacy.redirect_services.patterns' defaultMessage='Matches: {pattern}, eg. {services}, leave empty for no redirect' values={{ pattern: service.urlPattern, services: <FormattedList value={service.targets} /> }} />}
|
||||
>
|
||||
<Input
|
||||
outerClassName='grow'
|
||||
type='text'
|
||||
value={redirectServices[service.name]}
|
||||
onChange={(e) => setRedirectServices((services) => {
|
||||
services[service.name] = e.target.value;
|
||||
})}
|
||||
placeholder={intl.formatMessage(messages.redirectServicePlaceholder)}
|
||||
/>
|
||||
</FormGroup>
|
||||
))
|
||||
)}
|
||||
|
||||
<FormActions>
|
||||
<Button type='submit'>
|
||||
<FormattedMessage id='url_privacy.save' defaultMessage='Save' />
|
||||
|
||||
@ -1674,6 +1674,17 @@
|
||||
"url_privacy.hash_url.hint": "SHA256 hash of rules database, used to avoid unnecessary fetches, eg. {url}",
|
||||
"url_privacy.hash_url.label": "URL cleaning rules hash address (optional)",
|
||||
"url_privacy.hash_url.placeholder": "Hash URL",
|
||||
"url_privacy.redirect_links_mode": "Redirect links to popular websites to privacy-respecting proxy services",
|
||||
"url_privacy.redirect_links_mode.auto": "From URL",
|
||||
"url_privacy.redirect_links_mode.manual": "Specify manually",
|
||||
"url_privacy.redirect_links_mode.off": "Disabled",
|
||||
"url_privacy.redirect_services.name": "{name}",
|
||||
"url_privacy.redirect_services.patterns": "Matches: {pattern}, eg. {services}, leave empty for no redirect",
|
||||
"url_privacy.redirect_services_update.fail": "Failed to update redirect services URL",
|
||||
"url_privacy.redirect_services_update.success": "Successfully updated redirect services",
|
||||
"url_privacy.redirect_services_url.hint": "URLs database in Farside-compatible format, eg. {url}",
|
||||
"url_privacy.redirect_services_url.label": "Redirect services URLs database address",
|
||||
"url_privacy.redirect_services_url.placeholder": "eg. https://proxy.example.org",
|
||||
"url_privacy.rules_url.hint": "Rules database in ClearURLs-compatible format, eg. {url}",
|
||||
"url_privacy.rules_url.label": "URL cleaning rules database address",
|
||||
"url_privacy.rules_url.placeholder": "Rules URL",
|
||||
|
||||
@ -44,6 +44,9 @@ const settingsSchema = v.object({
|
||||
rulesUrl: v.fallback(v.string(), ''),
|
||||
hashUrl: v.fallback(v.string(), ''),
|
||||
displayTargetHost: v.fallback(v.boolean(), true),
|
||||
redirectLinksMode: v.fallback(v.picklist(['off', 'auto', 'manual']), 'off'),
|
||||
redirectServicesUrl: v.fallback(v.string(), ''),
|
||||
redirectServices: v.fallback(v.record(v.string(), v.string()), {}),
|
||||
}),
|
||||
|
||||
theme: v.fallback(v.optional(v.object({
|
||||
|
||||
@ -4,8 +4,9 @@ import { create } from 'zustand';
|
||||
import { mutative } from 'zustand-mutative';
|
||||
|
||||
import { settingsSchema, type Settings } from 'pl-fe/schemas/pl-fe/settings';
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import toast from 'pl-fe/toast';
|
||||
import { updateRulesFromUrl } from 'pl-fe/utils/url-purify';
|
||||
import { KVStoreRedirectServicesItem, resetRules, setManualRedirectServices, updateRedirectServicesFromUrl, updateRulesFromUrl } from 'pl-fe/utils/url-purify';
|
||||
|
||||
import type { Emoji } from 'pl-fe/features/emoji';
|
||||
import type { store } from 'pl-fe/store';
|
||||
@ -15,8 +16,10 @@ let lazyStore: typeof store;
|
||||
import('pl-fe/store').then(({ store }) => lazyStore = store).catch(() => {});
|
||||
|
||||
const messages = defineMessages({
|
||||
updateSuccess: { id: 'url_privacy.update.success', defaultMessage: 'Successfully updated rules database' },
|
||||
updateFail: { id: 'url_privacy.update.fail', defaultMessage: 'Failed to update rules database URL' },
|
||||
rulesUpdateSuccess: { id: 'url_privacy.update.success', defaultMessage: 'Successfully updated rules database' },
|
||||
rulesUpdateFail: { id: 'url_privacy.update.fail', defaultMessage: 'Failed to update rules database URL' },
|
||||
redirectServicesUpdateSuccess: { id: 'url_privacy.redirect_services_update.success', defaultMessage: 'Successfully updated redirect services' },
|
||||
redirectServicesUpdateFail: { id: 'url_privacy.redirect_services_update.fail', defaultMessage: 'Failed to update redirect services URL' },
|
||||
});
|
||||
|
||||
const settingsSchemaPartial = v.partial(settingsSchema);
|
||||
@ -50,14 +53,35 @@ const changeSetting = (object: APIEntity, path: string[], value: any, root?: Set
|
||||
|
||||
const mergeSettings = (state: State, updating = false) => {
|
||||
const mergedSettings = { ...state.defaultSettings, ...state.userSettings };
|
||||
if (updating && mergedSettings.urlPrivacy.rulesUrl && state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl) {
|
||||
if (updating) {
|
||||
const me = lazyStore?.getState().me;
|
||||
if (me) {
|
||||
if (mergedSettings.urlPrivacy.rulesUrl && state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl) {
|
||||
updateRulesFromUrl(me, mergedSettings.urlPrivacy.rulesUrl, mergedSettings.urlPrivacy.hashUrl).then(() => {
|
||||
toast.success(messages.updateSuccess);
|
||||
toast.success(messages.rulesUpdateSuccess);
|
||||
}).catch(() => {
|
||||
toast.error(messages.updateFail);
|
||||
toast.error(messages.rulesUpdateFail);
|
||||
});
|
||||
} else if (!mergedSettings.urlPrivacy.rulesUrl && state.settings.urlPrivacy.rulesUrl !== mergedSettings.urlPrivacy.rulesUrl) {
|
||||
resetRules(me).then(() => {
|
||||
toast.success(messages.rulesUpdateSuccess);
|
||||
}).catch(() => {
|
||||
toast.error(messages.rulesUpdateFail);
|
||||
});
|
||||
}
|
||||
if (mergedSettings.urlPrivacy.redirectLinksMode === 'auto' && mergedSettings.urlPrivacy.redirectServicesUrl && state.settings.urlPrivacy.redirectServicesUrl !== mergedSettings.urlPrivacy.redirectServicesUrl) {
|
||||
updateRedirectServicesFromUrl(me, mergedSettings.urlPrivacy.redirectServicesUrl).then(() => {
|
||||
toast.success(messages.redirectServicesUpdateSuccess);
|
||||
}).catch(() => {
|
||||
toast.error(messages.redirectServicesUpdateFail);
|
||||
});
|
||||
} else if (mergedSettings.urlPrivacy.redirectLinksMode === 'manual') {
|
||||
setManualRedirectServices(me, mergedSettings.urlPrivacy.redirectServices).then(() => {
|
||||
toast.success(messages.redirectServicesUpdateSuccess);
|
||||
}).catch(() => {
|
||||
toast.error(messages.redirectServicesUpdateFail);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
state.settings = mergedSettings;
|
||||
@ -80,6 +104,45 @@ const useSettingsStore = create<State>()(mutative((set) => ({
|
||||
if (typeof settings !== 'object') return;
|
||||
|
||||
state.userSettings = v.parse(settingsSchemaPartial, settings);
|
||||
|
||||
const me = lazyStore?.getState().me;
|
||||
if (me) {
|
||||
KVStore.getItem<string>('url-purify-rules:last').then((value) => {
|
||||
if (value !== me) {
|
||||
if (state.userSettings.urlPrivacy?.rulesUrl) {
|
||||
updateRulesFromUrl(me, state.userSettings.urlPrivacy.rulesUrl, state.userSettings.urlPrivacy.hashUrl).then(() => {
|
||||
toast.success(messages.rulesUpdateSuccess);
|
||||
}).catch(() => {
|
||||
toast.error(messages.rulesUpdateFail);
|
||||
});
|
||||
} else {
|
||||
resetRules(me);
|
||||
}
|
||||
switch (state.userSettings.urlPrivacy?.redirectLinksMode) {
|
||||
case 'auto':
|
||||
updateRedirectServicesFromUrl(me, state.userSettings.urlPrivacy?.redirectServicesUrl);
|
||||
break;
|
||||
case 'manual':
|
||||
setManualRedirectServices(me, state.userSettings.urlPrivacy.redirectServices);
|
||||
break;
|
||||
default:
|
||||
setManualRedirectServices(me, {});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
KVStore.getItem<KVStoreRedirectServicesItem>(`url-purify-redirect-services:${me}`).then((services) => {
|
||||
if (state.userSettings.urlPrivacy?.redirectLinksMode === 'auto') {
|
||||
if (services?.redirectServicesUrl !== state.userSettings.urlPrivacy?.redirectServicesUrl) {
|
||||
updateRedirectServicesFromUrl(me, state.userSettings.urlPrivacy?.redirectServicesUrl);
|
||||
}
|
||||
} else {
|
||||
setManualRedirectServices(me, state.userSettings.urlPrivacy?.redirectServices || {});
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
mergeSettings(state);
|
||||
}),
|
||||
|
||||
|
||||
@ -12,19 +12,25 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
// I hope I got this relicensing stuff right xD
|
||||
import { URLPurify, type SerializedRules } from '@mkljczk/url-purify';
|
||||
import { mappings, URLPurify, type SerializedRules, type SerializedServices } from '@mkljczk/url-purify';
|
||||
|
||||
import KVStore from 'pl-fe/storage/kv-store';
|
||||
import { Me } from 'pl-fe/types/pl-fe';
|
||||
|
||||
interface KVStoreItem {
|
||||
hashUrl: string;
|
||||
rulesUrl: string;
|
||||
hash: string;
|
||||
interface KVStoreRulesItem {
|
||||
hashUrl?: string;
|
||||
rulesUrl?: string;
|
||||
hash?: string;
|
||||
rules: SerializedRules;
|
||||
fetchedAt: number;
|
||||
}
|
||||
|
||||
interface KVStoreRedirectServicesItem {
|
||||
redirectServicesUrl?: string;
|
||||
redirectServices: SerializedServices;
|
||||
fetchedAt: number;
|
||||
}
|
||||
|
||||
const sha256 = async (message: string) =>
|
||||
Array.from(new Uint8Array(
|
||||
await window.crypto.subtle.digest('SHA-256', (new TextEncoder()).encode(message)),
|
||||
@ -106,10 +112,17 @@ const DEFAULT_RULESET: SerializedRules = {
|
||||
|
||||
const Purify = new URLPurify({
|
||||
rulesFromMemory: DEFAULT_RULESET,
|
||||
instancePickMode: 'random',
|
||||
});
|
||||
|
||||
const updateRulesFromUrl = async (user: Me, rulesUrl: string, hashUrl: string, oldHash?: string) => {
|
||||
if (oldHash) {
|
||||
const resetRules = async (user: Me) => {
|
||||
Purify.setRules(DEFAULT_RULESET);
|
||||
await KVStore.setItem('url-purify-rules:last', user);
|
||||
return KVStore.removeItem(`url-purify-rules:${user}`);
|
||||
};
|
||||
|
||||
const updateRulesFromUrl = async (user: Me, rulesUrl: string, hashUrl?: string, oldHash?: string) => {
|
||||
if (oldHash && hashUrl) {
|
||||
const newHash = await fetch(hashUrl).then((response) => response.text());
|
||||
if (newHash === oldHash) return;
|
||||
}
|
||||
@ -121,7 +134,7 @@ const updateRulesFromUrl = async (user: Me, rulesUrl: string, hashUrl: string, o
|
||||
Purify.setRules(parsedRules, hash);
|
||||
|
||||
await KVStore.setItem('url-purify-rules:last', user);
|
||||
return KVStore.setItem<KVStoreItem>(`url-purify-rules:${user}`, {
|
||||
return KVStore.setItem<KVStoreRulesItem>(`url-purify-rules:${user}`, {
|
||||
hashUrl,
|
||||
rulesUrl,
|
||||
hash,
|
||||
@ -130,25 +143,82 @@ const updateRulesFromUrl = async (user: Me, rulesUrl: string, hashUrl: string, o
|
||||
});
|
||||
};
|
||||
|
||||
const updateRedirectServicesFromUrl = async (user: Me, redirectServicesUrl: string) => {
|
||||
const redirectServices = await fetch(redirectServicesUrl).then((response) => response.text());
|
||||
|
||||
const parsedRedirectServices = JSON.parse(redirectServices);
|
||||
Purify.setRedirectServices(parsedRedirectServices);
|
||||
|
||||
await KVStore.setItem('url-purify-redirect-services:last', user);
|
||||
return KVStore.setItem<KVStoreRedirectServicesItem>(`url-purify-redirect-services:${user}`, {
|
||||
redirectServicesUrl,
|
||||
redirectServices: parsedRedirectServices,
|
||||
fetchedAt: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
const setManualRedirectServices = async (user: Me, redirectServices: Record<string, string>) => {
|
||||
const parsedRedirectServices: SerializedServices = Object.entries(redirectServices).filter(([_, instance]) => instance.trim()).map((service) => ({
|
||||
fallback: service[1],
|
||||
instances: [service[1]],
|
||||
test_url: '',
|
||||
type: mappings.find((mapping) => mapping.name === service[0])?.targets[0] || 'unknown',
|
||||
}));
|
||||
Purify.setRedirectServices(parsedRedirectServices);
|
||||
|
||||
await KVStore.setItem('url-purify-redirect-services:last', user);
|
||||
return KVStore.setItem<KVStoreRedirectServicesItem>(`url-purify-redirect-services:${user}`, {
|
||||
redirectServices: parsedRedirectServices,
|
||||
fetchedAt: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
const getRulesForUser = (user: Me) => {
|
||||
if (!user || typeof user !== 'string') return;
|
||||
KVStore.getItem<KVStoreItem>(`url-purify-rules:${user}`, (rules) => {
|
||||
if (!rules) return;
|
||||
KVStore.getItem<KVStoreRulesItem>(`url-purify-rules:${user}`).then((rules) => {
|
||||
if (!rules?.rulesUrl) return;
|
||||
Purify.setRules(rules.rules, rules.hash);
|
||||
|
||||
if (rules.fetchedAt + 1000 * 60 * 60 * 24 < Date.now()) {
|
||||
updateRulesFromUrl(user, rules.rulesUrl, rules.hashUrl, rules.hash);
|
||||
}
|
||||
});
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const getRulesFromMemory = () => {
|
||||
KVStore.getItem('url-purify-rules:last', (url: string) => {
|
||||
KVStore.getItem<string>('url-purify-rules:last').then((url) => {
|
||||
if (!url) return;
|
||||
getRulesForUser(url);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
getRulesFromMemory();
|
||||
const getRedirectServicesForUser = (user: Me) => {
|
||||
if (!user || typeof user !== 'string') return;
|
||||
KVStore.getItem<KVStoreRedirectServicesItem>(`url-purify-redirect-services:${user}`).then((services) => {
|
||||
if (!services) return;
|
||||
Purify.setRedirectServices(services.redirectServices);
|
||||
|
||||
export { Purify as default, getRulesForUser, updateRulesFromUrl };
|
||||
if (services.redirectServicesUrl && services.fetchedAt + 1000 * 60 * 60 * 24 < Date.now()) {
|
||||
updateRedirectServicesFromUrl(user, services.redirectServicesUrl);
|
||||
}
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const getRedirectServicesFromMemory = () => {
|
||||
KVStore.getItem<string>('url-purify-redirect-services:last').then((url) => {
|
||||
if (!url) return;
|
||||
getRedirectServicesForUser(url);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
getRulesFromMemory();
|
||||
getRedirectServicesFromMemory();
|
||||
|
||||
export {
|
||||
Purify as default,
|
||||
resetRules,
|
||||
setManualRedirectServices,
|
||||
updateRedirectServicesFromUrl,
|
||||
updateRulesFromUrl,
|
||||
type KVStoreRedirectServicesItem,
|
||||
};
|
||||
|
||||
@ -1775,10 +1775,10 @@
|
||||
lodash "^4.17.21"
|
||||
mousetrap "^1.6.5"
|
||||
|
||||
"@mkljczk/url-purify@^0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@mkljczk/url-purify/-/url-purify-0.0.2.tgz#f6aed0f750ab49b8cf9d3e8169b2b358c1d4519f"
|
||||
integrity sha512-OH5bb84cf7rpwPxE8y0JzkdPMCysC1ZO5wEZZf/cxBGa5io6XKV1f6WHZwU/CYUaBFDYana3ctU4FZdCNH6slg==
|
||||
"@mkljczk/url-purify@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@mkljczk/url-purify/-/url-purify-0.0.3.tgz#e5121927617b007d2f91f6e08c73552a4f3c06dc"
|
||||
integrity sha512-3O4QO/nH9yV/GKim+yKvQF1cKWN0dBAsxC5Ve50d1PaUYQFSd4y733eRGt+zRcPZrvgyAkyZZ5Bx7dAHWX+bBQ==
|
||||
|
||||
"@napi-rs/wasm-runtime@^0.2.7":
|
||||
version "0.2.7"
|
||||
@ -8851,6 +8851,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-mutative@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/use-mutative/-/use-mutative-1.2.1.tgz#ee86017899f48027bf87b9d42c6c3a237997a536"
|
||||
integrity sha512-cXvnAWmCxjcxYNitIwLJJEmjLISQTIfj0T88xfk1xNM0knmnOfEQPeU4+CDWBAcK0IXP7ey8fKeGc0seh+5OBA==
|
||||
|
||||
use-sync-external-store@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
|
||||
|
||||
Reference in New Issue
Block a user