From 232e9a47a28ee632799e65f91d0f06b2f721ca56 Mon Sep 17 00:00:00 2001 From: Gabe Yuan Date: Sun, 20 Aug 2023 23:30:08 +0800 Subject: [PATCH] share rules --- src/apis/index.js | 5 +-- src/config/i18n.js | 8 +++++ src/config/index.js | 4 ++- src/hooks/Alert.js | 60 ++++++++++++++++++++++++++++++++ src/libs/sync.js | 21 +++++++++-- src/libs/utils.js | 17 +++++++-- src/views/Options/Rules.js | 53 ++++++++++++++++++++++++++++ src/views/Options/SyncSetting.js | 8 +++-- src/views/Options/index.js | 23 ++++++------ 9 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 src/hooks/Alert.js 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 ( - - - }> - } /> - } /> - } /> - } /> - - - + + + + }> + } /> + } /> + } /> + } /> + + + + );