diff --git a/package.json b/package.json index 3940d402..94d9c2c9 100644 --- a/package.json +++ b/package.json @@ -1,49 +1,49 @@ { "name": "vocechat-web", - "version": "0.6.2", + "version": "0.6.8", "homepage": "https://voce.chat", "dependencies": { "@metamask/onboarding": "^1.0.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@radix-ui/react-popover": "^1.0.7", - "@react-oauth/google": "^0.11.1", + "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^1.9.7", "@svgr/webpack": "^8.1.0", "@tippyjs/react": "^4.2.6", "@toast-ui/editor": "^3.2.2", "@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0", "@toast-ui/react-editor": "^3.2.3", - "@udecode/plate-break": "^24.5.2", - "@udecode/plate-combobox": "^24.5.2", - "@udecode/plate-common": "^24.5.2", - "@udecode/plate-emoji": "^24.5.2", - "@udecode/plate-floating": "^24.5.2", - "@udecode/plate-font": "^24.5.2", - "@udecode/plate-mention": "^24.5.2", - "@udecode/plate-node-id": "^24.5.2", - "@udecode/plate-paragraph": "^24.5.2", - "@udecode/plate-trailing-block": "^24.5.2", + "@udecode/plate-break": "^25.0.1", + "@udecode/plate-combobox": "^25.0.1", + "@udecode/plate-common": "^25.0.1", + "@udecode/plate-emoji": "^25.0.1", + "@udecode/plate-floating": "^25.0.1", + "@udecode/plate-font": "^25.0.1", + "@udecode/plate-mention": "^25.0.1", + "@udecode/plate-node-id": "^25.0.1", + "@udecode/plate-paragraph": "^25.0.1", + "@udecode/plate-trailing-block": "^25.0.1", "@uiball/loaders": "^1.3.0", - "agora-rtc-sdk-ng": "^4.19.2", - "broadcast-channel": "^5.5.1", + "agora-rtc-sdk-ng": "^4.19.3", + "broadcast-channel": "^6.0.0", "browserslist": "^4.22.1", "clsx": "^2.0.0", "copy-image-clipboard": "^2.1.2", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "dayjs": "^1.11.10", - "eslint": "^8.52.0", + "eslint": "^8.54.0", "firebase": "^10.5.2", - "framer-motion": "^10.16.4", + "framer-motion": "^10.16.5", "fs-extra": "^11.1.1", "heic2any": "^0.0.4", "html-webpack-plugin": "^5.5.3", - "i18next": "^23.6.0", - "i18next-browser-languagedetector": "^7.1.0", - "i18next-http-backend": "^2.3.0", - "linkify-plugin-mention": "^4.1.1", - "linkify-react": "^4.1.1", - "linkifyjs": "^4.1.1", + "i18next": "^23.7.6", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.1", + "linkify-plugin-mention": "^4.1.2", + "linkify-react": "^4.1.2", + "linkifyjs": "^4.1.2", "localforage": "^1.10.0", "localforage-setitems": "^1.4.0", "lodash": "^4.17.21", @@ -58,10 +58,10 @@ "react-error-boundary": "^4.0.11", "react-helmet": "^6.1.0", "react-hot-toast": "^2.4.1", - "react-i18next": "^13.3.1", + "react-i18next": "^13.5.0", "react-redux": "^8.1.3", "react-refresh": "0.14.0", - "react-router-dom": "^6.17.0", + "react-router-dom": "^6.19.0", "react-syntax-highlighter": "^15.5.0", "react-textarea-autosize": "^8.5.3", "react-use-wizard": "^2.2.4", @@ -71,14 +71,14 @@ "slate": "^0.100.0", "slate-history": "^0.100.0", "slate-hyperscript": "^0.100.0", - "slate-react": "0.100.0", + "slate-react": "0.101.0", "source-map-loader": "^4.0.1", "style-loader": "^3.3.3", - "tailwind-merge": "1.14.0", + "tailwind-merge": "2.0.0", "terser-webpack-plugin": "^5.3.9", "tippy.js": "^6.3.7", "typescript": "^5.2.2", - "wavesurfer.js": "^7.4.2", + "wavesurfer.js": "^7.4.5", "webpack": "^5.89.0", "webpack-dev-server": "^4.15.1", "webpack-manifest-plugin": "^5.0.0", @@ -117,14 +117,14 @@ "@babel/core": "^7.23.0", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@ianvs/prettier-plugin-sort-imports": "^4.1.1", - "@types/masonry-layout": "^4.2.6", - "@types/node": "^20.8.9", - "@types/react": "18.2.33", - "@types/react-dom": "^18.2.14", - "@types/react-helmet": "^6.1.8", - "@types/react-syntax-highlighter": "^15.5.9", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", + "@types/masonry-layout": "^4.2.7", + "@types/node": "^20.9.2", + "@types/react": "18.2.37", + "@types/react-dom": "^18.2.15", + "@types/react-helmet": "^6.1.9", + "@types/react-syntax-highlighter": "^15.5.10", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", "@welldone-software/why-did-you-render": "^7.0.1", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", @@ -135,14 +135,14 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "gh-pages": "^6.0.0", + "gh-pages": "^6.1.0", "md5-file": "^5.0.0", "patch-package": "^8.0.0", "postcss": "^8.4.31", "postcss-loader": "^7.3.3", "postinstall-postinstall": "^2.1.0", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "tailwindcss": "^3.3.5", - "webpack-bundle-analyzer": "^4.9.1" + "webpack-bundle-analyzer": "^4.10.1" } } diff --git a/public/locales/en/setting.json b/public/locales/en/setting.json index a202a566..ae5bef30 100644 --- a/public/locales/en/setting.json +++ b/public/locales/en/setting.json @@ -249,6 +249,7 @@ "custom_style_tip": "if you want customize the widget position more precisely, use the widget element ID in your HTML or CSS file, like this", "param_host": "Assign the user chatting with visitor(User ID)", "param_auto_reg": "true: Auto register a new user if not exist, false: need visitor to input email to register", + "param_login_token": "The login token generated by yourself's account, for more details, please refer to: https://doc.voce.chat/zh-cn/login-with-other-account.", "param_theme_color": "The theme color of widget", "param_close_width": "The width while widget closed", "param_close_height": "The height while widget closed", diff --git a/public/locales/zh/setting.json b/public/locales/zh/setting.json index 929850b3..1c7b1905 100644 --- a/public/locales/zh/setting.json +++ b/public/locales/zh/setting.json @@ -242,6 +242,7 @@ "custom_style_tip": "如果你想更精确控制挂件的样式,可以借助 widget 元素的 ID,自行写 CSS 样式,加到自己的 html 或者 css 文件中,如下所示:", "param_host": "指定和谁聊天 (用户 ID)", "param_auto_reg": "true:访客无需输入邮箱,直接注册;false:访客需要输入邮箱注册", + "param_login_token": "自有账号生成的登录 token,详见:https://doc.voce.chat/zh-cn/login-with-other-account", "param_theme_color": "挂件的主题色", "param_close_width": "挂件关闭态的宽度", "param_close_height": "挂件关闭态的高度", diff --git a/public/widget.js b/public/widget.js index 5ef6cbf0..28830a52 100644 --- a/public/widget.js +++ b/public/widget.js @@ -3,6 +3,7 @@ const { hostId = 1, autoReg = "false", + loginToken = "", closeWidth = 48, closeHeight = 48, openWidth = 380, @@ -27,7 +28,7 @@ Object.assign(wrapper.style, styles); wrapper.src = `${ new URL(_src).origin - }/widget.html?host=${hostId}&autoReg=${autoReg}&themeColor=${encodeURIComponent( + }/widget.html?host=${hostId}&autoReg=${autoReg}&token=${loginToken}&themeColor=${encodeURIComponent( themeColor )}&from=${encodeURIComponent(location.hostname)}&welcome=${encodeURIComponent(welcome)}`; wrapper.width = closeWidth; diff --git a/src/app/services/auth.ts b/src/app/services/auth.ts index ed453bd1..4538f52b 100644 --- a/src/app/services/auth.ts +++ b/src/app/services/auth.ts @@ -15,7 +15,7 @@ import baseQuery from "./base.query"; const getDeviceId = () => { let d = localStorage.getItem(KEY_DEVICE_ID); - let dt = localStorage.getItem(KEY_DEVICE_TOKEN) || ""; + const dt = localStorage.getItem(KEY_DEVICE_TOKEN) || ""; if (!d) { d = `web:${nanoid()}`; localStorage.setItem(KEY_DEVICE_ID, d); @@ -53,7 +53,7 @@ export const authApi = createApi({ if (data) { dispatch(setAuthData(data)); } - // 从localstorage 去掉 magic token + // 从 localstorage 去掉 magic token localStorage.removeItem(KEY_LOCAL_MAGIC_TOKEN); } catch { console.log("login error"); @@ -67,7 +67,7 @@ export const authApi = createApi({ const { data } = await queryFulfilled; if (data) { dispatch(setAuthData(data)); - // 从localstorage 去掉 magic token + // 从 localstorage 去掉 magic token localStorage.removeItem(KEY_LOCAL_MAGIC_TOKEN); } } catch { @@ -86,7 +86,7 @@ export const authApi = createApi({ } }) }), - // 更新token + // 更新 token renew: builder.mutation({ query: (data) => ({ url: "/token/renew", @@ -116,7 +116,7 @@ export const authApi = createApi({ } }) }), - // 获取openid + // 获取 openid getOpenid: builder.mutation<{ url: string }, { issuer: string; redirect_uri: string }>({ query: (data) => ({ url: "/token/openid/authorize", diff --git a/src/routes/setting/Widget.tsx b/src/routes/setting/Widget.tsx index e572d6b9..65a6b675 100644 --- a/src/routes/setting/Widget.tsx +++ b/src/routes/setting/Widget.tsx @@ -54,7 +54,7 @@ export default function Widget() { {`\n`} @@ -95,6 +95,11 @@ export default function Widget() { paramDefault: "false", remarks: t("param_auto_reg") }, + { + paramKey: "login-token", + paramDefault: "", + remarks: t("param_login_token") + }, { paramKey: "theme-color", paramDefault: "#1fe1f9", diff --git a/src/widget/Popup/Login/index.tsx b/src/widget/Popup/Login/index.tsx index ec2a0b39..caa72f11 100644 --- a/src/widget/Popup/Login/index.tsx +++ b/src/widget/Popup/Login/index.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import clsx from "clsx"; -import { useRegisterMutation } from "../../../app/services/auth"; +import { useLoginMutation, useRegisterMutation } from "../../../app/services/auth"; import { useGetLoginConfigQuery } from "../../../app/services/server"; import { setAuthData } from "../../../app/slices/auth.data"; import Divider from "../../../components/Divider"; @@ -23,10 +23,11 @@ const randomText = () => (Math.random() + 1).toString(36).substring(7); const Login = () => { const { t } = useTranslation("widget"); const dispatch = useDispatch(); - const { color, fgColor, from, autoReg } = useWidget(); + const { color, fgColor, from, autoReg, token } = useWidget(); const { clientId } = useGoogleAuthConfig(); const { config: githubAuthConfig } = useGithubAuthConfig(); const [register, { isLoading, isSuccess, data, error }] = useRegisterMutation(); + const [loginByToken, { isLoading: isLogging }] = useLoginMutation(); // const [login]= useLoginMutation(); const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery(); const registerUser = (name: string, auto?: boolean) => { @@ -55,10 +56,12 @@ const Login = () => { // const content = new FormData(form).get("prompt") as string; }; useEffect(() => { - if (autoReg) { + if (autoReg && !token) { registerUser(`w-${randomText()}`, true); + } else if (token) { + loginByToken({ key: token, type: "thirdparty" }); } - }, [autoReg]); + }, [autoReg, token]); useEffect(() => { if (isSuccess && data) { @@ -84,7 +87,7 @@ const Login = () => { } }, [error]); if (!loginConfigSuccess) return null; - if (autoReg) return ; + if (autoReg || isLogging) return ; const { github: enableGithubLogin, google: enableGoogleLogin } = loginConfig; const googleLogin = enableGoogleLogin && clientId; const hasSocialLogins = enableGithubLogin || googleLogin; diff --git a/src/widget/WidgetContext.tsx b/src/widget/WidgetContext.tsx index 1bfe627e..a747bab6 100644 --- a/src/widget/WidgetContext.tsx +++ b/src/widget/WidgetContext.tsx @@ -8,11 +8,13 @@ import { shallowEqual } from "react-redux"; const query = new URLSearchParams(location.search); const welcome = decodeURIComponent(query.get("welcome") || ""); const autoReg = decodeURIComponent(query.get("autoReg") || "false") == "true"; +const token = decodeURIComponent(query.get("token") || ""); const color = decodeURIComponent(query.get("themeColor") || "#1fe1f9"); const from = decodeURIComponent(query.get("from") || "widget.link"); const fgColor = getContrastColor(color); const embed = isInIframe(); const WidgetContext = createContext({ + token, autoReg, color, fgColor, @@ -36,6 +38,7 @@ function WidgetProvider({ children }: { children: ReactNode }) { return (