feat(sync): 通过 base64 复制粘贴配置 (#311)

motivation: 同浏览器多个 profile 简化配置流程,通过同一个同步配置即可实现
This commit is contained in:
@刘羡鱼
2025-09-16 08:44:07 +08:00
committed by GitHub
parent 7dc847fca2
commit fa87d87011
2 changed files with 73 additions and 1 deletions

View File

@@ -33,6 +33,7 @@ export const DEFAULT_CSPLIST = ["https://github.com"]; // 禁用CSP名单
// 同步设置
export const OPT_SYNCTYPE_WORKER = "KISS-Worker";
export const OPT_SYNCTYPE_WEBDAV = "WebDAV";
export const OPT_SYNCTOKEN_PERFIX = "kt_";
export const OPT_SYNCTYPE_ALL = [OPT_SYNCTYPE_WORKER, OPT_SYNCTYPE_WEBDAV];
export const DEFAULT_SYNC = {
syncType: OPT_SYNCTYPE_WORKER, // 同步方式

View File

@@ -7,18 +7,22 @@ import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link";
import MenuItem from "@mui/material/MenuItem";
import LoadingButton from "@mui/lab/LoadingButton";
import Button from "@mui/material/Button";
import {
URL_KISS_WORKER,
OPT_SYNCTYPE_ALL,
OPT_SYNCTYPE_WORKER,
OPT_SYNCTYPE_WEBDAV,
OPT_SYNCTOKEN_PERFIX,
} from "../../config";
import { useState } from "react";
import { syncSettingAndRules } from "../../libs/sync";
import { useAlert } from "../../hooks/Alert";
import SyncIcon from "@mui/icons-material/Sync";
import { useSetting } from "../../hooks/Setting";
import { kissLog } from "../../libs/log";
import SyncIcon from "@mui/icons-material/Sync";
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
export default function SyncSetting() {
const i18n = useI18n();
@@ -50,6 +54,57 @@ export default function SyncSetting() {
}
};
const handleGenerateShareString = async () => {
try {
const base64Config = btoa(JSON.stringify({
syncType: syncType,
syncUrl: syncUrl,
syncUser: syncUser,
syncKey: syncKey,
}));
const shareString = `${OPT_SYNCTOKEN_PERFIX}${base64Config}`;
await navigator.clipboard.writeText(shareString);
console.debug("Share string copied to clipboard", shareString);
} catch (error) {
console.error("Failed to copy share string to clipboard", error);
}
};
const handleImportFromClipboard = async () => {
try {
const text = await navigator.clipboard.readText();
console.debug('read_clipboard', text)
if (text.startsWith(OPT_SYNCTOKEN_PERFIX)) {
const base64Config = text.slice(OPT_SYNCTOKEN_PERFIX.length);
const jsonString = atob(base64Config);
const updatedConfig = JSON.parse(jsonString);
if (!OPT_SYNCTYPE_ALL.includes(updatedConfig.syncType)) {
console.error('error syncType', updatedConfig.syncType)
return;
}
if (
updatedConfig.syncUrl
) {
updateSync({
syncType: updatedConfig.syncType,
syncUrl: updatedConfig.syncUrl,
syncUser: updatedConfig.syncUser,
syncKey: updatedConfig.syncKey,
});
} else {
console.error("Invalid config structure");
}
} else {
console.error("Invalid share string", text);
}
} catch (error) {
console.error("Failed to read from clipboard or parse JSON", error);
}
};
if (!sync) {
return;
}
@@ -133,6 +188,22 @@ export default function SyncSetting() {
>
{i18n("sync_now")}
</LoadingButton>
<Button
size="small"
variant="contained"
onClick={handleGenerateShareString}
startIcon={<ContentCopyIcon />}
>
{i18n("copy", "copy")}
</Button>
<Button
onClick={handleImportFromClipboard}
size="small"
variant="contained"
startIcon={<ContentPasteIcon />}
>
{i18n("import", "import")}
</Button>
</Stack>
</Stack>
</Box>