mirror of
https://github.com/quantum5/totp.git
synced 2025-04-24 05:31:59 -04:00
Use URL-based state management
This commit is contained in:
parent
51df02bf3f
commit
230326ab9c
|
@ -53,7 +53,7 @@
|
|||
</ul>
|
||||
<p class="card-text">What this tool <strong>can't</strong> do:</p>
|
||||
<ul class="card-text">
|
||||
<li>Store your TOTP secrets;</li>
|
||||
<li>Store your TOTP secrets (you can try bookmarking this page with the secret in the URL, but it's not secure);</li>
|
||||
<li>Act as your general purpose authenticator app; and</li>
|
||||
<li>Scan TOTP QR codes.</li>
|
||||
</ul>
|
||||
|
|
59
src/App.tsx
59
src/App.tsx
|
@ -3,16 +3,22 @@ import {NumberInput, TextInput} from './Input';
|
|||
import OTPOutput, {HashAlgorithm} from './OTPOutput';
|
||||
import Select from './Select';
|
||||
import Collapsible from './Collapsible';
|
||||
import ActionLink from './ActionLink.tsx';
|
||||
import ActionLink from './ActionLink';
|
||||
import {type State, defaults, serializeState, deserializeState} from './state';
|
||||
|
||||
function parseState() {
|
||||
if (window.location.hash.startsWith('#!')) {
|
||||
return deserializeState(window.location.hash.slice(2));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [advanced, setAdvanced] = useState(false);
|
||||
|
||||
const [secret, setSecret] = React.useState('');
|
||||
const [step, setStep] = React.useState(30);
|
||||
const [state, setState] = React.useState(() => parseState() || defaults);
|
||||
const {secret, step, digits, algorithm} = state;
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const [digits, setDigits] = React.useState(6);
|
||||
const [algorithm, setAlgorithm] = React.useState<HashAlgorithm>('sha1');
|
||||
|
||||
const validStep = step > 0;
|
||||
const validDigits = digits > 0 && digits <= 10;
|
||||
|
@ -41,12 +47,47 @@ function App() {
|
|||
setAdvanced(false);
|
||||
}, []);
|
||||
|
||||
const onReset = React.useCallback(() => {
|
||||
setStep(30);
|
||||
setDigits(6);
|
||||
setAlgorithm('sha1');
|
||||
const setSecret = React.useCallback(
|
||||
(secret: string) => setState((state) => ({...state, secret})),
|
||||
[],
|
||||
);
|
||||
|
||||
const setStep = React.useCallback(
|
||||
(step: number) => setState((state) => ({...state, step})),
|
||||
[],
|
||||
);
|
||||
|
||||
const setDigits = React.useCallback(
|
||||
(digits: number) => setState((state) => ({...state, digits})),
|
||||
[],
|
||||
);
|
||||
|
||||
const setAlgorithm = React.useCallback(
|
||||
(algorithm: string) => setState((state) => ({...state, algorithm})),
|
||||
[],
|
||||
);
|
||||
|
||||
const onReset = React.useCallback(
|
||||
() => setState((state) => ({...state, step: 30, digits: 6, algorithm: 'sha1'})),
|
||||
[],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const value = serializeState(state);
|
||||
console.log(value);
|
||||
history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`));
|
||||
}, [state]);
|
||||
|
||||
const onHashChange = React.useCallback(() => {
|
||||
const state = parseState();
|
||||
state && setState(state);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('hashchange', onHashChange);
|
||||
return () => window.removeEventListener('hashchange', onHashChange);
|
||||
}, [onHashChange]);
|
||||
|
||||
return (
|
||||
<div className="totp-app">
|
||||
<div className="totp-settings">
|
||||
|
|
|
@ -4,7 +4,7 @@ import {createDigest} from '@otplib/plugin-crypto-js';
|
|||
import classNames from 'classnames';
|
||||
import CopyButton from './CopyButton.tsx';
|
||||
|
||||
const ALGORITHMS = {
|
||||
export const ALGORITHMS = {
|
||||
sha1: HashAlgorithms.SHA1,
|
||||
sha256: HashAlgorithms.SHA256,
|
||||
sha512: HashAlgorithms.SHA512,
|
||||
|
|
35
src/state.tsx
Normal file
35
src/state.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {ALGORITHMS, HashAlgorithm} from './OTPOutput';
|
||||
|
||||
export type State = {
|
||||
secret: string;
|
||||
step: number;
|
||||
digits: number;
|
||||
algorithm: HashAlgorithm;
|
||||
};
|
||||
|
||||
export const defaults: State = {
|
||||
secret: '',
|
||||
step: 30,
|
||||
digits: 6,
|
||||
algorithm: 'sha1',
|
||||
} as const;
|
||||
|
||||
export function serializeState(state: State): string {
|
||||
const values = ['secret', 'step', 'digits', 'algorithm'].map(
|
||||
(key) => state[key] !== defaults[key] ? encodeURIComponent(state[key]) : '',
|
||||
);
|
||||
while (values[values.length - 1] === '') {
|
||||
values.pop();
|
||||
}
|
||||
return values.join('/');
|
||||
}
|
||||
|
||||
export function deserializeState(data: string): State {
|
||||
const values = data.split('/').map(decodeURIComponent);
|
||||
return {
|
||||
secret: values[0] || defaults.secret,
|
||||
step: +values[1] || defaults.step,
|
||||
digits: +values[2] || defaults.digits,
|
||||
algorithm: ALGORITHMS[values[3]] !== undefined ? values[3] : defaults.algorithm,
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue