fix data sync

This commit is contained in:
Gabe Yuan
2023-07-31 15:08:51 +08:00
parent 2aef159d9d
commit fed0f6849a
18 changed files with 237 additions and 145 deletions

View File

@@ -33,6 +33,7 @@ If you also like a little more simplicity, welcome to pick it up.
- [ ] DeepL
- [ ] Upload to app Store
- [x] Open source
- [x] Data Synchronization Function
### Guide
@@ -42,3 +43,7 @@ cd kiss-translator
yarn
yarn dist
```
### Data Sync
Goto: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)

View File

@@ -33,6 +33,7 @@
- [ ] DeepL
- [ ] 上架应用市场
- [x] 开放源代码
- [x] 数据同步功能
### 指引
@@ -42,3 +43,7 @@ cd kiss-translator
yarn
yarn dist
```
### 数据同步
移步: [https://github.com/fishjar/kiss-worker](https://github.com/fishjar/kiss-worker)

View File

@@ -1,79 +0,0 @@
import { fetchPolyfill } from "../libs/fetch";
import {
KV_HEADER_KEY,
KV_RULES_KEY,
KV_SETTING_KEY,
STOKEY_RULES,
STOKEY_SETTING,
STOKEY_RULES_UPDATE_AT,
} from "../config";
import { getSetting, getRules } from "../libs";
import storage from "../libs/storage";
/**
* 同步数据
* @param {*} param0
* @returns
*/
const apiSyncData = async ({ key, value, updateAt }) => {
const { syncUrl, syncKey } = await getSetting();
if (!syncUrl || !syncKey) {
console.log("data sync should set the api and key");
return;
}
return fetchPolyfill(syncUrl, {
headers: {
"Content-type": "application/json",
[KV_HEADER_KEY]: syncKey,
},
method: "POST",
body: JSON.stringify({ key, value, updateAt }),
});
};
/**
* 同步rules
* @param {*} value
* @param {*} updateAt
*/
export const apiSyncRules = async (value, updateAt) => {
const res = await apiSyncData({
key: KV_RULES_KEY,
value,
updateAt,
});
console.log("res", res);
if (res && res.updateAt > updateAt) {
await storage.setObj(STOKEY_RULES, res.value);
await storage.setObj(STOKEY_RULES_UPDATE_AT, res.updateAt);
}
};
/**
* 同步setting
* @param {*} value
* @param {*} updateAt
*/
export const apiSyncSetting = async (value, updateAt) => {
const res = await apiSyncData({
key: KV_SETTING_KEY,
value,
updateAt,
});
console.log("res", res);
if (res && res.updateAt > updateAt) {
await storage.setObj(STOKEY_SETTING, res.value);
}
};
/**
* 同步全部数据
*/
export const apiSyncAll = async () => {
const setting = await getSetting();
const rules = await getRules();
const settingUpdateAt = setting.updateAt;
const rulesUpdateAt = (await storage.getObj(STOKEY_RULES_UPDATE_AT)) || 1;
await apiSyncSetting(setting, settingUpdateAt);
await apiSyncRules(rules, rulesUpdateAt);
};

View File

@@ -8,9 +8,27 @@ import {
OPT_LANGS_SPECIAL,
PROMPT_PLACE_FROM,
PROMPT_PLACE_TO,
KV_HEADER_KEY,
} from "../config";
import { getSetting, detectLang } from "../libs";
/**
* 同步数据
* @param {*} url
* @param {*} key
* @param {*} data
* @returns
*/
export const apiSyncData = async (url, key, data) =>
fetchPolyfill(url, {
headers: {
"Content-type": "application/json",
[KV_HEADER_KEY]: key,
},
method: "POST",
body: JSON.stringify(data),
});
/**
* 谷歌翻译
* @param {*} text

View File

@@ -4,14 +4,16 @@ import {
MSG_FETCH_LIMIT,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_SYNC,
CACHE_NAME,
} from "./config";
import { fetchData, setFetchLimit } from "./libs/fetch";
import storage from "./libs/storage";
import { getSetting } from "./libs";
import { apiSyncAll } from "./apis/data";
import { syncAll } from "./libs/sync";
/**
* 插件安装
@@ -20,6 +22,7 @@ browser.runtime.onInstalled.addListener(() => {
console.log("onInstalled");
storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
storage.trySetObj(STOKEY_RULES, DEFAULT_RULES);
storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
});
/**
@@ -29,11 +32,7 @@ browser.runtime.onStartup.addListener(async () => {
console.log("onStartup");
// 同步数据
try {
await apiSyncAll();
} catch (err) {
console.log("[sync all]", err);
}
await syncAll();
// 清除缓存
const { clearCache } = await getSetting();

View File

@@ -22,6 +22,10 @@ export const I18N = {
zh: `规则设置`,
en: `Rules Setting`,
},
sync_setting: {
zh: `同步设置`,
en: `Sync Setting`,
},
about: {
zh: `关于`,
en: `About`,
@@ -94,9 +98,13 @@ export const I18N = {
zh: `添加`,
en: `Add`,
},
advanced_warn: {
zh: `如不明白,谨慎修改!不同的浏览器,选择器规则不一定通用。`,
en: `If you don't understand, modify it carefully! Different browsers, the selector rules are not necessarily universal.`,
sync_warn: {
zh: `数据同步功能只是按修改时间简单覆盖,谨慎使用!`,
en: `The data synchronization function is simply overwritten according to the modification time, use it with caution!`,
},
about_sync_api: {
zh: `查看关于数据同步接口部署`,
en: `View About Data Synchronization Interface Deployment`,
},
style_none: {
zh: ``,
@@ -139,8 +147,8 @@ export const I18N = {
en: `Multiple URLs can be separated by English commas ","`,
},
selector_helper: {
zh: `遵循CSS选择器规则但不同浏览器可能支持不同,有些不同的写法。`,
en: `Follow the CSS selector rules, but different browsers may support different, and some have different ways of writing.`,
zh: `遵循CSS选择器规则但不同浏览器有些不同的写法。`,
en: `Follow the CSS selector rules, but different browsers have some different ways of writing.`,
},
translate_switch: {
zh: `开启翻译`,

View File

@@ -8,7 +8,7 @@ export const APP_LCNAME = APP_NAME.toLowerCase();
export const STOKEY_MSAUTH = `${APP_NAME}_msauth`;
export const STOKEY_SETTING = `${APP_NAME}_setting`;
export const STOKEY_RULES = `${APP_NAME}_rules`;
export const STOKEY_RULES_UPDATE_AT = `${APP_NAME}_rules_update_at`;
export const STOKEY_SYNC = `${APP_NAME}_sync`;
export const KV_HEADER_KEY = "X-KISS-PSK";
export const KV_RULES_KEY = "KT_RULES";
@@ -26,6 +26,7 @@ export const THEME_LIGHT = "light";
export const THEME_DARK = "dark";
export const URL_APP_HOMEPAGE = "https://github.com/fishjar/kiss-translator";
export const URL_KISS_WORKER = "https://github.com/fishjar/kiss-worker";
export const URL_RAW_PREFIX =
"https://raw.githubusercontent.com/fishjar/kiss-translator/master";
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
@@ -135,9 +136,6 @@ export const DEFAULT_SETTING = {
openaiKey: "",
openaiModel: "gpt-4",
openaiPrompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`,
syncUrl: "", // 数据同步接口
syncKey: "", // 数据同步密钥
updateAt: 1, // 更新时间
};
export const DEFAULT_RULES = [
@@ -151,3 +149,12 @@ export const DEFAULT_RULES = [
export const TRANS_MIN_LENGTH = 5; // 最短翻译长度
export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度
export const DEFAULT_SYNC = {
syncUrl: "", // 数据同步接口
syncKey: "", // 数据同步密钥
settingUpdateAt: 0,
settingSyncAt: 0,
rulesUpdateAt: 0,
rulesSyncAt: 0,
};

View File

@@ -4,12 +4,12 @@ import {
OPT_STYLE_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
STOKEY_RULES_UPDATE_AT,
} from "../config";
import storage from "../libs/storage";
import { useStorages } from "./Storage";
import { matchValue } from "../libs/utils";
import { apiSyncRules } from "../apis/data";
import { syncRules } from "../libs/sync";
import { useSync } from "./Sync";
/**
* 匹配规则增删改查 hook
@@ -18,13 +18,14 @@ import { apiSyncRules } from "../apis/data";
export function useRules() {
const storages = useStorages();
const list = storages?.[STOKEY_RULES] || [];
const sync = useSync();
const update = async (rules) => {
const now = Date.now();
const updateAt = sync.opt?.rulesUpdateAt ? Date.now() : 0;
await storage.setObj(STOKEY_RULES, rules);
await storage.setObj(STOKEY_RULES_UPDATE_AT, now);
await sync.update({ rulesUpdateAt: updateAt });
try {
await apiSyncRules(rules, now);
await syncRules();
} catch (err) {
console.log("[sync rules]", err);
}

View File

@@ -1,7 +1,7 @@
import { useCallback } from "react";
import { STOKEY_SETTING } from "../config";
import storage from "../libs/storage";
import { useStorages } from "./Storage";
import { useSync } from "./Sync";
/**
* 设置hook
@@ -17,7 +17,10 @@ export function useSetting() {
* @returns
*/
export function useSettingUpdate() {
return useCallback(async (obj) => {
await storage.putObj(STOKEY_SETTING, { ...obj, updateAt: Date.now() });
}, []);
const sync = useSync();
return async (obj) => {
const updateAt = sync.opt?.settingUpdateAt ? Date.now() : 0;
await storage.putObj(STOKEY_SETTING, obj);
await sync.update({ settingUpdateAt: updateAt });
};
}

View File

@@ -4,9 +4,10 @@ import {
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_MSAUTH,
STOKEY_SYNC,
DEFAULT_SETTING,
DEFAULT_RULES,
STOKEY_RULES_UPDATE_AT,
DEFAULT_SYNC,
} from "../config";
import storage from "../libs/storage";
@@ -17,7 +18,7 @@ export const defaultStorage = {
[STOKEY_MSAUTH]: null,
[STOKEY_SETTING]: DEFAULT_SETTING,
[STOKEY_RULES]: DEFAULT_RULES,
[STOKEY_RULES_UPDATE_AT]: 1,
[STOKEY_SYNC]: DEFAULT_SYNC,
};
const StoragesContext = createContext(null);

20
src/hooks/Sync.js Normal file
View File

@@ -0,0 +1,20 @@
import { useCallback } from "react";
import { STOKEY_SYNC } from "../config";
import storage from "../libs/storage";
import { useStorages } from "./Storage";
/**
* sync hook
* @returns
*/
export function useSync() {
const storages = useStorages();
const opt = storages?.[STOKEY_SYNC];
const update = useCallback(async (obj) => {
await storage.putObj(STOKEY_SYNC, obj);
}, []);
return {
opt,
update,
};
}

74
src/libs/sync.js Normal file
View File

@@ -0,0 +1,74 @@
import {
STOKEY_SYNC,
DEFAULT_SYNC,
KV_SETTING_KEY,
KV_RULES_KEY,
STOKEY_SETTING,
STOKEY_RULES,
} from "../config";
import storage from "../libs/storage";
import { getSetting, getRules } from ".";
import { apiSyncData } from "../apis";
const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
export const syncSetting = async () => {
const { syncUrl, syncKey, settingUpdateAt } = await loadOpt();
if (!syncUrl || !syncKey) {
return;
}
const setting = await getSetting();
const res = await apiSyncData(syncUrl, syncKey, {
key: KV_SETTING_KEY,
value: setting,
updateAt: settingUpdateAt,
});
if (res && res.updateAt > settingUpdateAt) {
await storage.putObj(STOKEY_SYNC, {
settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_SETTING, res.value);
} else {
await storage.putObj(STOKEY_SYNC, {
settingSyncAt: res.updateAt,
});
}
};
export const syncRules = async () => {
const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt();
if (!syncUrl || !syncKey) {
return;
}
const rules = await getRules();
const res = await apiSyncData(syncUrl, syncKey, {
key: KV_RULES_KEY,
value: rules,
updateAt: rulesUpdateAt,
});
if (res && res.updateAt > rulesUpdateAt) {
await storage.putObj(STOKEY_SYNC, {
rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_RULES, res.value);
} else {
await storage.putObj(STOKEY_SYNC, {
rulesSyncAt: res.updateAt,
});
}
};
export const syncAll = async () => {
try {
await syncSetting();
await syncRules();
} catch (err) {
console.log("[sync all]", err);
}
};

View File

@@ -6,6 +6,7 @@ import Box from "@mui/material/Box";
import Navigator from "./Navigator";
import Header from "./Header";
import { useTheme } from "@mui/material/styles";
import { syncAll } from "../../libs/sync";
export default function Layout() {
const navWidth = 256;
@@ -20,6 +21,7 @@ export default function Layout() {
useEffect(() => {
setOpen(false);
syncAll();
}, [location]);
return (

View File

@@ -9,6 +9,7 @@ import SettingsIcon from "@mui/icons-material/Settings";
import InfoIcon from "@mui/icons-material/Info";
import DesignServicesIcon from "@mui/icons-material/DesignServices";
import { useI18n } from "../../hooks/I18n";
import SyncIcon from "@mui/icons-material/Sync";
function LinkItem({ label, url, icon }) {
const match = useMatch(url);
@@ -35,6 +36,12 @@ export default function Navigator(props) {
url: "/rules",
icon: <DesignServicesIcon />,
},
{
id: "sync",
label: i18n("sync_setting"),
url: "/sync",
icon: <SyncIcon />,
},
{ id: "about", label: i18n("about"), url: "/about", icon: <InfoIcon /> },
];
return (

View File

@@ -10,7 +10,6 @@ import {
OPT_STYLE_ALL,
} from "../../config";
import { useState, useRef } from "react";
import Alert from "@mui/material/Alert";
import { useI18n } from "../../hooks/I18n";
import Typography from "@mui/material/Typography";
import Accordion from "@mui/material/Accordion";
@@ -354,8 +353,6 @@ export default function Rules() {
return (
<Box>
<Stack spacing={3}>
<Alert severity="warning">{i18n("advanced_warn")}</Alert>
<Stack direction="row" spacing={2}>
<Button
size="small"

View File

@@ -9,22 +9,12 @@ import { useSetting, useSettingUpdate } from "../../hooks/Setting";
import { limitNumber } from "../../libs/utils";
import { useI18n } from "../../hooks/I18n";
import { UI_LANGS } from "../../config";
import { apiSyncAll } from "../../apis/data";
import { useCallback } from "react";
export default function Settings() {
const i18n = useI18n();
const setting = useSetting();
const updateSetting = useSettingUpdate();
const handleSyncBlur = useCallback(async () => {
try {
await apiSyncAll();
} catch (err) {
console.log("sync data", err);
}
}, []);
if (!setting) {
return;
}
@@ -38,8 +28,6 @@ export default function Settings() {
openaiModel,
openaiPrompt,
clearCache,
syncUrl,
syncKey,
} = setting;
return (
@@ -148,31 +136,6 @@ export default function Settings() {
minRows={2}
maxRows={10}
/>
<TextField
size="small"
label={i18n("data_sync_url")}
defaultValue={syncUrl}
onChange={(e) => {
updateSetting({
syncUrl: e.target.value,
});
}}
onBlur={handleSyncBlur}
/>
<TextField
size="small"
type="password"
label={i18n("data_sync_key")}
defaultValue={syncKey}
onChange={(e) => {
updateSetting({
syncKey: e.target.value,
});
}}
onBlur={handleSyncBlur}
/>
</Stack>
</Box>
);

View File

@@ -0,0 +1,59 @@
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import { useI18n } from "../../hooks/I18n";
import { useSync } from "../../hooks/Sync";
import { syncAll } from "../../libs/sync";
import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link";
import { URL_KISS_WORKER } from "../../config";
export default function SyncSetting() {
const i18n = useI18n();
const sync = useSync();
if (!sync.opt) {
return;
}
const { syncUrl, syncKey } = sync.opt;
const handleSyncBlur = () => {
syncAll();
};
return (
<Box>
<Stack spacing={3}>
<Alert severity="warning">{i18n("sync_warn")}</Alert>
<TextField
size="small"
label={i18n("data_sync_url")}
defaultValue={syncUrl}
onChange={(e) => {
sync.update({
syncUrl: e.target.value,
});
}}
onBlur={handleSyncBlur}
helperText={
<Link href={URL_KISS_WORKER}>{i18n("about_sync_api")}</Link>
}
/>
<TextField
size="small"
type="password"
label={i18n("data_sync_key")}
defaultValue={syncKey}
onChange={(e) => {
sync.update({
syncKey: e.target.value,
});
}}
onBlur={handleSyncBlur}
/>
</Stack>
</Box>
);
}

View File

@@ -3,6 +3,7 @@ import About from "./About";
import Rules from "./Rules";
import Setting from "./Setting";
import Layout from "./Layout";
import SyncSetting from "./SyncSetting";
export default function Options() {
return (
@@ -10,6 +11,7 @@ export default function Options() {
<Route path="/" element={<Layout />}>
<Route index element={<Setting />} />
<Route path="rules" element={<Rules />} />
<Route path="sync" element={<SyncSetting />} />
<Route path="about" element={<About />} />
</Route>
</Routes>