diff --git a/src/apis/index.js b/src/apis/index.js
index 86165c5..847d80a 100644
--- a/src/apis/index.js
+++ b/src/apis/index.js
@@ -8,9 +8,10 @@ import {
OPT_LANGS_SPECIAL,
PROMPT_PLACE_FROM,
PROMPT_PLACE_TO,
- KV_HEADER_KEY,
+ KV_SALT_SYNC,
} from "../config";
import { getSetting, detectLang } from "../libs";
+import { sha256 } from "../libs/utils";
/**
* 同步数据
@@ -25,7 +26,7 @@ export const apiSyncData = async (url, key, data) =>
{
headers: {
"Content-type": "application/json",
- [KV_HEADER_KEY]: key,
+ Authorization: `Bearer ${await sha256(key, KV_SALT_SYNC)}`,
},
method: "POST",
body: JSON.stringify(data),
diff --git a/src/config/i18n.js b/src/config/i18n.js
index 0aa01d4..9fcedd4 100644
--- a/src/config/i18n.js
+++ b/src/config/i18n.js
@@ -248,4 +248,12 @@ export const I18N = {
zh: `数据同步密钥`,
en: `Data Sync Key`,
},
+ error_got_some_wrong: {
+ zh: "抱歉,出错了!",
+ en: "Sorry, something went wrong!",
+ },
+ error_sync_setting: {
+ zh: "您的同步设置未填写,无法在线分享。",
+ en: "Your sync settings are missing and cannot be shared online.",
+ },
};
diff --git a/src/config/index.js b/src/config/index.js
index 99367db..4287bea 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -25,9 +25,11 @@ export const CLIENT_FIREFOX = "firefox";
export const CLIENT_USERSCRIPT = "userscript";
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
-export const KV_HEADER_KEY = "X-KISS-PSK";
export const KV_RULES_KEY = "KT_RULES";
+export const KV_RULES_SHARE_KEY = "KT_RULES_SHARE";
export const KV_SETTING_KEY = "KT_SETTING";
+export const KV_SALT_SYNC = "KISS-Translator-SYNC";
+export const KV_SALT_SHARE = "KISS-Translator-SHARE";
export const CACHE_NAME = `${APP_NAME}_cache`;
diff --git a/src/hooks/Alert.js b/src/hooks/Alert.js
new file mode 100644
index 0000000..56567cc
--- /dev/null
+++ b/src/hooks/Alert.js
@@ -0,0 +1,60 @@
+import { createContext, useContext, useState, forwardRef } from "react";
+import Snackbar from "@mui/material/Snackbar";
+import MuiAlert from "@mui/material/Alert";
+
+const Alert = forwardRef(function Alert(props, ref) {
+ return ;
+});
+
+const AlertContext = createContext(null);
+
+/**
+ * 左下角提示,注入context后,方便全局调用
+ * @param {*} param0
+ * @returns
+ */
+export function AlertProvider({ children }) {
+ const vertical = "top";
+ const horizontal = "center";
+ const [open, setOpen] = useState(false);
+ const [severity, setSeverity] = useState("info");
+ const [message, setMessage] = useState("");
+
+ const error = (msg) => showAlert(msg, "error");
+ const warning = (msg) => showAlert(msg, "warning");
+ const info = (msg) => showAlert(msg, "info");
+ const success = (msg) => showAlert(msg, "success");
+
+ const showAlert = (msg, type) => {
+ setOpen(true);
+ setMessage(msg);
+ setSeverity(type);
+ };
+
+ const handleClose = (_, reason) => {
+ if (reason === "clickaway") {
+ return;
+ }
+ setOpen(false);
+ };
+
+ return (
+
+ {children}
+
+
+ {message}
+
+
+
+ );
+}
+
+export function useAlert() {
+ return useContext(AlertContext);
+}
diff --git a/src/libs/sync.js b/src/libs/sync.js
index 4c7a848..f2136d9 100644
--- a/src/libs/sync.js
+++ b/src/libs/sync.js
@@ -3,18 +3,22 @@ import {
DEFAULT_SYNC,
KV_SETTING_KEY,
KV_RULES_KEY,
+ KV_RULES_SHARE_KEY,
STOKEY_SETTING,
STOKEY_RULES,
+ KV_SALT_SHARE,
} from "../config";
import storage from "../libs/storage";
import { getSetting, getRules } from ".";
import { apiSyncData } from "../apis";
+import { sha256 } from "./utils";
-const loadOpt = async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
+export const loadSyncOpt = async () =>
+ (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC;
export const syncSetting = async () => {
try {
- const { syncUrl, syncKey, settingUpdateAt } = await loadOpt();
+ const { syncUrl, syncKey, settingUpdateAt } = await loadSyncOpt();
if (!syncUrl || !syncKey) {
return;
}
@@ -44,7 +48,7 @@ export const syncSetting = async () => {
export const syncRules = async () => {
try {
- const { syncUrl, syncKey, rulesUpdateAt } = await loadOpt();
+ const { syncUrl, syncKey, rulesUpdateAt } = await loadSyncOpt();
if (!syncUrl || !syncKey) {
return;
}
@@ -72,6 +76,17 @@ export const syncRules = async () => {
}
};
+export const syncShareRules = async ({ rules, syncUrl, syncKey }) => {
+ await apiSyncData(syncUrl, syncKey, {
+ key: KV_RULES_SHARE_KEY,
+ value: rules,
+ updateAt: Date.now(),
+ });
+ const psk = await sha256(syncKey, KV_SALT_SHARE);
+ const shareUrl = `${syncUrl}?psk=${psk}`;
+ return shareUrl;
+};
+
export const syncAll = async () => {
await syncSetting();
await syncRules();
diff --git a/src/libs/utils.js b/src/libs/utils.js
index 6f80d93..5b654c8 100644
--- a/src/libs/utils.js
+++ b/src/libs/utils.js
@@ -91,10 +91,23 @@ export const isMatch = (s, p) => {
/**
* 类型检查
- * @param {*} o
- * @returns
+ * @param {*} o
+ * @returns
*/
export const type = (o) => {
const s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
+
+/**
+ * sha256
+ * @param {*} text
+ * @returns
+ */
+export const sha256 = async (text, salt) => {
+ const data = new TextEncoder().encode(text + salt);
+ const digest = await crypto.subtle.digest({ name: "SHA-256" }, data);
+ return [...new Uint8Array(digest)]
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("");
+};
diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js
index 9980573..13e065f 100644
--- a/src/views/Options/Rules.js
+++ b/src/views/Options/Rules.js
@@ -32,9 +32,12 @@ import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup";
import DeleteIcon from "@mui/icons-material/Delete";
import IconButton from "@mui/material/IconButton";
+import ShareIcon from "@mui/icons-material/Share";
import SyncIcon from "@mui/icons-material/Sync";
import { useSubrules } from "../../hooks/Rules";
import { rulesCache, tryLoadRules } from "../../libs/rules";
+import { useAlert } from "../../hooks/Alert";
+import { loadSyncOpt, syncShareRules } from "../../libs/sync";
function RuleFields({ rule, rules, setShow }) {
const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" };
@@ -393,12 +396,56 @@ function UploadButton({ onChange, text }) {
);
}
+function ShareButton({ rules, injectRules, selectedSub }) {
+ const alert = useAlert();
+ const i18n = useI18n();
+ const handleClick = async () => {
+ try {
+ const { syncUrl, syncKey } = await loadSyncOpt();
+ if (!syncUrl || !syncKey) {
+ alert.warning(i18n("error_sync_setting"));
+ return;
+ }
+
+ const shareRules = [...rules.list];
+ if (injectRules) {
+ const subRules = await tryLoadRules(selectedSub?.url);
+ shareRules.splice(-1, 0, ...subRules);
+ }
+
+ const url = await syncShareRules({
+ rules: shareRules,
+ syncUrl,
+ syncKey,
+ });
+
+ window.open(url, "_blank");
+ } catch (err) {
+ alert.warning(i18n("error_got_some_wrong"));
+ console.log("[share rules]", err);
+ }
+ };
+
+ return (
+ }
+ >
+ {"分享"}
+
+ );
+}
+
function UserRules() {
const i18n = useI18n();
const rules = useRules();
const [showAdd, setShowAdd] = useState(false);
const setting = useSetting();
const updateSetting = useSettingUpdate();
+ const subrules = useSubrules();
+ const selectedSub = subrules.list.find((item) => item.selected);
const injectRules = !!setting?.injectRules;
@@ -451,6 +498,12 @@ function UserRules() {
text={i18n("export")}
/>
+
+
- debounce((e) => {
+ debounce(async (e) => {
e.preventDefault();
const { name, value } = e.target;
- sync.update({
+ await sync.update({
[name]: value,
});
- }, 500),
+ await syncAll();
+ }, 1000),
[sync]
);
diff --git a/src/views/Options/index.js b/src/views/Options/index.js
index 32064d5..9b3440b 100644
--- a/src/views/Options/index.js
+++ b/src/views/Options/index.js
@@ -11,6 +11,7 @@ import { isGm } from "../../libs/browser";
import { sleep } from "../../libs/utils";
import CircularProgress from "@mui/material/CircularProgress";
import { syncAll } from "../../libs/sync";
+import { AlertProvider } from "../../hooks/Alert";
export default function Options() {
const [error, setError] = useState(false);
@@ -69,16 +70,18 @@ export default function Options() {
return (
-
-
- }>
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
+
+
);