Skip to content

Commit

Permalink
Initial implementation to warn users of potential dangers from third …
Browse files Browse the repository at this point in the history
…party repositories and plugins
  • Loading branch information
RodoMa92 committed Mar 13, 2024
1 parent 4a7e9a5 commit 056f921
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 16 deletions.
9 changes: 9 additions & 0 deletions backend/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,15 @@
"updating": "Updating"
}
},
"WarnThirdParty":{
"title_zip": "Third party plugin install",
"title_repo": "Third party store selection",
"button_processing_one": "Please wait {{timer}} second",
"button_processing_many": "Please wait {{timer}} seconds",
"button_idle": "I accept the risks",
"desc_zip": "This plugin that has been requested to be installed is taken outside our official store; we haven't audited it, so it will contain unvetted code: it might be doing exactly what it would be supposed to do or it might have additional features built in that will try to steal your data and take over your Steam account. Decky devs are not responsible for any problem arising from installing this plugin.",
"desc_repo": "This third party store that has been requested to be used is not our official store; we haven't audited it, so it will contain plugins with unvetted code from us: you should check with whoever is maintaining this store and decide for yourself if you trust them or not. Decky devs are not responsible for any problem arising from installing plugins from this third party store."
},
"Testing": {
"download": "Download"
}
Expand Down
82 changes: 82 additions & 0 deletions frontend/src/components/modals/WarnThirdParty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ConfirmModal } from 'decky-frontend-lib';
import { FC, useEffect, useState } from 'react';
import { FaExclamationTriangle } from 'react-icons/fa';

import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper';

interface WarnThirdPartyProps {
seconds?: number;
type: WarnThirdPartyType;
onOK(): void;
onCancel(): void;
closeModal?(): void;
}

export enum WarnThirdPartyType {
REPO = 0,
ZIP = 1,
}

const WarnThirdParty: FC<WarnThirdPartyProps> = ({ seconds = 5, type, onOK, onCancel, closeModal }) => {
const [waitTimer, setWaitTimer] = useState(seconds);

useEffect(() => {
// exit early when we reach 0
if (waitTimer <= 0) return;

// save intervalId to clear the interval when the
// component re-renders
const intervalId = setInterval(() => {
setWaitTimer(waitTimer - 1);
}, 1000);

// clear interval on re-render to avoid memory leaks
return () => clearInterval(intervalId);
// add waitTimer as a dependency to re-rerun the effect
// when we update it
}, [waitTimer]);

return (
<ConfirmModal
bOKDisabled={waitTimer > 0}
closeModal={closeModal}
onOK={async () => {
await onOK();
}}
onCancel={async () => {
await onCancel();
}}
strTitle={
<div>
<FaExclamationTriangle />
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="title" warn_type={type} />
</div>
}
strOKButtonText={
waitTimer > 0 ? (
<div>
<TranslationHelper
trans_class={TranslationClass.WARN_THIRD_PARTY}
trans_text="button_processing"
i18n_args={{
timer: waitTimer,
}}
/>
</div>
) : (
<div>
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="button_idle" />
</div>
)
}
>
<span style={{ color: 'red' }}>
<div>
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="desc" warn_type={type} />
</div>
</span>
</ConfirmModal>
);
};

export default WarnThirdParty;
17 changes: 16 additions & 1 deletion frontend/src/components/settings/pages/developer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Navigation,
TextField,
Toggle,
showModal,
} from 'decky-frontend-lib';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -18,6 +19,7 @@ import { installFromURL } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import { getSetting } from '../../../../utils/settings';
import { FileSelectionType } from '../../../modals/filepicker';
import WarnThirdParty, { WarnThirdPartyType } from '../../../modals/WarnThirdParty';
import RemoteDebuggingSettings from '../general/RemoteDebugging';

const logger = new Logger('DeveloperIndex');
Expand Down Expand Up @@ -77,7 +79,20 @@ export default function DeveloperSettings() {
}
icon={<FaLink style={{ display: 'block' }} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
<DialogButton
disabled={pluginURL.length == 0}
onClick={() =>
showModal(
<WarnThirdParty
type={WarnThirdPartyType.ZIP}
onOK={() => {
installFromURL(pluginURL);
}}
onCancel={() => {}}
/>,
)
}
>
{t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton>
</Field>
Expand Down
38 changes: 23 additions & 15 deletions frontend/src/components/settings/pages/general/StoreSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Dropdown, Field, TextField } from 'decky-frontend-lib';
import { Dropdown, Field, TextField, showModal } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import { FaShapes } from 'react-icons/fa';

import Logger from '../../../../logger';
import { Store } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import WarnThirdParty, { WarnThirdPartyType } from '../../../modals/WarnThirdParty';

const logger = new Logger('StoreSelect');

Expand Down Expand Up @@ -38,20 +39,27 @@ const StoreSelect: FunctionComponent<{}> = () => {
}}
/>
</Field>
{selectedStore == Store.Custom && (
<Field
label={t('StoreSelect.custom_store.label')}
indentLevel={1}
description={
<TextField
label={t('StoreSelect.custom_store.url_label')}
value={selectedStoreURL || undefined}
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
/>
}
icon={<FaShapes style={{ display: 'block' }} />}
></Field>
)}
{selectedStore == Store.Custom &&
showModal(
<WarnThirdParty
type={WarnThirdPartyType.REPO}
onOK={() => {}}
onCancel={() => setSelectedStore(Store.Default)}
/>,
) && (
<Field
label={t('StoreSelect.custom_store.label')}
indentLevel={1}
description={
<TextField
label={t('StoreSelect.custom_store.url_label')}
value={selectedStoreURL || undefined}
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
/>
}
icon={<FaShapes style={{ display: 'block' }} />}
></Field>
)}
</>
);
};
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/utils/TranslationHelper.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { FC } from 'react';
import { Translation } from 'react-i18next';

import { WarnThirdPartyType } from '../components/modals/WarnThirdParty';
import Logger from '../logger';
import { InstallType } from '../plugin';

export enum TranslationClass {
PLUGIN_LOADER = 'PluginLoader',
PLUGIN_INSTALL_MODAL = 'PluginInstallModal',
DEVELOPER = 'Developer',
WARN_THIRD_PARTY = 'WarnThirdParty',
}

interface TranslationHelperProps {
trans_class: TranslationClass;
trans_text: string;
i18n_args?: {};
install_type?: number;
warn_type?: WarnThirdPartyType;
}

const logger = new Logger('TranslationHelper');
Expand All @@ -24,6 +27,7 @@ const TranslationHelper: FC<TranslationHelperProps> = ({
trans_text,
i18n_args = null,
install_type = 0,
warn_type = WarnThirdPartyType.REPO,
}) => {
return (
<Translation>
Expand Down Expand Up @@ -52,6 +56,25 @@ const TranslationHelper: FC<TranslationHelperProps> = ({
return i18n_args
? t(TranslationClass.DEVELOPER + '.' + trans_text, i18n_args)
: t(TranslationClass.DEVELOPER + '.' + trans_text);
//Handle different messages in different class cases
case TranslationClass.WARN_THIRD_PARTY:
//Needed only for title and description
if (!trans_text.startsWith('button')) {
switch (warn_type) {
case WarnThirdPartyType.REPO:
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_repo', i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_repo');
case WarnThirdPartyType.ZIP:
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_zip', i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_zip');
}
} else {
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text, i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text);
}
default:
logger.error('We should never fall in the default case!');
return '';
Expand Down

0 comments on commit 056f921

Please sign in to comment.