diff --git a/src/OTPOutput.tsx b/src/OTPOutput.tsx
index 1727af9..1739433 100644
--- a/src/OTPOutput.tsx
+++ b/src/OTPOutput.tsx
@@ -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,
diff --git a/src/state.tsx b/src/state.tsx
new file mode 100644
index 0000000..3cd8e2e
--- /dev/null
+++ b/src/state.tsx
@@ -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,
+ };
+}