diff --git a/client/shared/event/index.ts b/client/shared/event/index.ts index daa1be99..16afa4e3 100644 --- a/client/shared/event/index.ts +++ b/client/shared/event/index.ts @@ -2,11 +2,17 @@ import { useEffect } from 'react'; import { useUpdateRef } from '../hooks/useUpdateRef'; import type { ChatMessage, SendMessagePayload } from '../model/message'; import { EventEmitter } from 'eventemitter-strict'; +import type { UserBaseInfo } from '../model/user'; /** * 共享事件类型 */ export interface SharedEventMap { + /** + * 登录成功 + */ + loginSuccess: (userInfo: UserBaseInfo) => void; + /** * 修改配色方案 */ diff --git a/client/shared/i18n/langs/en-US/translation.json b/client/shared/i18n/langs/en-US/translation.json index 8e20ba33..ecae8005 100644 --- a/client/shared/i18n/langs/en-US/translation.json +++ b/client/shared/i18n/langs/en-US/translation.json @@ -274,6 +274,7 @@ "kea977d95": "The following users are offline", "kec46a57f": "Add members", "kecb51e2c": "Old password", + "kecbb0e45": "System", "kecbd7449": "Delete", "ked2baf28": "Loading...", "ked5385d5": "Create Panel", diff --git a/client/shared/i18n/langs/zh-CN/translation.json b/client/shared/i18n/langs/zh-CN/translation.json index 4d72dd2f..7adbe60d 100644 --- a/client/shared/i18n/langs/zh-CN/translation.json +++ b/client/shared/i18n/langs/zh-CN/translation.json @@ -274,6 +274,7 @@ "kea977d95": "以下用户已离线", "kec46a57f": "添加成员", "kecb51e2c": "旧密码", + "kecbb0e45": "系统", "kecbd7449": "删除", "ked2baf28": "加载中...", "ked5385d5": "创建面板", diff --git a/client/shared/model/user.ts b/client/shared/model/user.ts index 4285f44f..13900283 100644 --- a/client/shared/model/user.ts +++ b/client/shared/model/user.ts @@ -1,7 +1,10 @@ import { request } from '../api/request'; import { buildCachedRequest } from '../cache/utils'; +import { sharedEvent } from '../event'; import { SYSTEM_USERID } from '../utils/consts'; import { createAutoMergedRequest } from '../utils/request'; +import _pick from 'lodash/pick'; +import { t } from '../i18n'; export interface UserBaseInfo { _id: string; @@ -24,12 +27,23 @@ export interface UserSettings { messageListVirtualization?: boolean; } +export function pickUserBaseInfo(userInfo: UserLoginInfo) { + return _pick(userInfo, [ + '_id', + 'email', + 'nickname', + 'discriminator', + 'avatar', + 'temporary', + ]); +} + // 内置用户信息 const builtinUserInfo: Record = { [SYSTEM_USERID]: { _id: SYSTEM_USERID, email: 'admin@msgbyte.com', - nickname: '系统', + nickname: t('系统'), discriminator: '0000', avatar: null, temporary: false, @@ -58,6 +72,8 @@ export async function loginWithEmail( password, }); + sharedEvent.emit('loginSuccess', pickUserBaseInfo(data)); + return data; } @@ -70,6 +86,8 @@ export async function loginWithToken(token: string): Promise { token, }); + sharedEvent.emit('loginSuccess', pickUserBaseInfo(data)); + return data; } diff --git a/client/web/plugins/com.msgbyte.sentry/src/index.tsx b/client/web/plugins/com.msgbyte.sentry/src/index.tsx index ab09c757..c11dbb9c 100644 --- a/client/web/plugins/com.msgbyte.sentry/src/index.tsx +++ b/client/web/plugins/com.msgbyte.sentry/src/index.tsx @@ -1,5 +1,6 @@ import * as Sentry from '@sentry/react'; import { BrowserTracing } from '@sentry/tracing'; +import { sharedEvent } from '@capital/common'; Sentry.init({ dsn: 'https://177fd98a1e9e4deba84146a769633c32@o4504196236836864.ingest.sentry.io/4504196241293312', @@ -10,3 +11,13 @@ Sentry.init({ // We recommend adjusting this value in production tracesSampleRate: 1.0, }); + +sharedEvent.on('loginSuccess', (userInfo) => { + Sentry.setUser({ + id: userInfo._id, + email: userInfo.email, + username: `${userInfo.nickname}#${userInfo.discriminator}`, + avatar: userInfo.avatar, + temporary: userInfo.temporary, + }); +}); diff --git a/client/web/plugins/com.msgbyte.sentry/types/tailchat.d.ts b/client/web/plugins/com.msgbyte.sentry/types/tailchat.d.ts index 49f524ae..1ab8bc10 100644 --- a/client/web/plugins/com.msgbyte.sentry/types/tailchat.d.ts +++ b/client/web/plugins/com.msgbyte.sentry/types/tailchat.d.ts @@ -1,2 +1,419 @@ -declare module '@capital/common'; -declare module '@capital/component'; +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/// + +/** + * 该文件由 Tailchat 自动生成 + * 用于插件的类型声明 + * 生成命令: pnpm run plugins:declaration:generate + */ + +/** + * Tailchat 通用 + */ +declare module '@capital/common' { + export const useGroupPanelParams: any; + + /** + * 打开模态框 + * @deprecated 请从 @capital/component 引入 + */ + export const openModal: ( + content: React.ReactNode, + + props?: { + /** + * 是否显示右上角的关闭按钮 + * @default false + */ + closable?: boolean; + + /** + * 遮罩层是否可关闭 + */ + maskClosable?: boolean; + + /** + * 关闭modal的回调 + */ + onCloseModal?: () => void; + } + ) => number; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const closeModal: any; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const ModalWrapper: any; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const useModalContext: any; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const openConfirmModal: any; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const openReconfirmModal: any; + + /** + * @deprecated 请从 @capital/component 引入 + */ + export const Loadable: any; + + export const getGlobalState: any; + + export const useGlobalSocketEvent: ( + eventName: string, + callback: (data: T) => void + ) => void; + + export const getJWTUserInfo: () => Promise<{ + _id?: string; + nickname?: string; + email?: string; + avatar?: string; + }>; + + export const dataUrlToFile: any; + + export const urlSearchStringify: any; + + export const urlSearchParse: any; + + export const appendUrlSearch: any; + + export const getServiceWorkerRegistration: any; + + export const getServiceUrl: () => string; + + export const getCachedUserInfo: ( + userId: string, + refetch?: boolean + ) => Promise<{ + _id: string; + email: string; + nickname: string; + discriminator: string; + avatar: string | null; + temporary: boolean; + }>; + + export const getCachedConverseInfo: any; + + /** + * 本地翻译 + * @example + * localTrans({'zh-CN': '你好', 'en-US': 'Hello'}); + * + * @param trans 翻译对象 + */ + export const localTrans: (trans: Record<'zh-CN' | 'en-US', string>) => string; + + export const getLanguage: any; + + export const sharedEvent: any; + + export const useAsync: any; + + export const useAsyncFn: any; + + export const useAsyncRefresh: Promise>( + fn: T, + deps?: React.DependencyList + ) => [{ loading: boolean; value?: any; error?: Error }, T]; + + export const useAsyncRequest: Promise>( + fn: T, + deps?: React.DependencyList + ) => [{ loading: boolean; value?: any }, T]; + + export const uploadFile: any; + + export const showToasts: ( + message: string, + type?: 'info' | 'success' | 'error' | 'warning' + ) => void; + + export const showSuccessToasts: any; + + export const showErrorToasts: (error: any) => void; + + export const fetchAvailableServices: any; + + export const isValidStr: (str: any) => str is string; + + export const useGroupPanelInfo: any; + + export const sendMessage: any; + + export const showMessageTime: any; + + export const useLocation: any; + + export const useNavigate: any; + + export const createFastFormSchema: any; + + export const fieldSchema: any; + + export const useCurrentUserInfo: any; + + export const createPluginRequest: (pluginName: string) => { + get: (actionName: string, config?: any) => Promise; + post: (actionName: string, data?: any, config?: any) => Promise; + }; + + export const postRequest: any; + + export const pluginCustomPanel: any; + + export const regCustomPanel: any; + + export const pluginGroupPanel: any; + + export const regGroupPanel: any; + + export const messageInterpreter: any; + + export const regMessageInterpreter: any; + + export const getMessageRender: any; + + export const regMessageRender: any; + + export const getMessageTextDecorators: any; + + export const regMessageTextDecorators: any; + + export const ChatInputActionContextProps: any; + + export const pluginChatInputActions: any; + + export const regChatInputAction: any; + + export const regSocketEventListener: (item: { + eventName: string; + eventFn: (...args: any[]) => void; + }) => void; + + export const pluginColorScheme: any; + + export const regPluginColorScheme: any; + + export const pluginInspectServices: any; + + export const regInspectService: any; + + export const pluginMessageExtraParsers: any; + + export const regMessageExtraParser: any; + + export const pluginRootRoute: any; + + export const regPluginRootRoute: any; + + export const pluginPanelActions: any; + + export const regPluginPanelAction: any; + + export const pluginPermission: any; + + export const regPluginPermission: (permission: { + /** + * 权限唯一key, 用于写入数据库 + * 如果为插件则权限点应当符合命名规范, 如: plugin.com.msgbyte.github.manage + */ + key: string; + /** + * 权限点显示名称 + */ + title: string; + /** + * 权限描述 + */ + desc: string; + /** + * 是否默认开启 + */ + default: boolean; + /** + * 是否依赖其他权限点 + */ + required?: string[]; + }) => void; + + export const pluginGroupPanelBadges: any; + + export const regGroupPanelBadge: any; + + export const pluginGroupTextPanelExtraMenus: any; + + export const regPluginGroupTextPanelExtraMenu: any; + + export const useGroupIdContext: () => string; + + export const useGroupPanelContext: () => { + groupId: string; + panelId: string; + } | null; + + export const useSocketContext: any; +} + +/** + * Tailchat 组件 + */ +declare module '@capital/component' { + export const Button: any; + + export const Checkbox: any; + + export const Input: any; + + export const Divider: any; + + export const Space: any; + + export const Menu: any; + + export const Table: any; + + export const Switch: any; + + export const Tooltip: any; + + /** + * @link https://ant.design/components/notification-cn/ + */ + export const notification: any; + + export const Empty: React.FC< + React.PropsWithChildren<{ + prefixCls?: string; + className?: string; + style?: React.CSSProperties; + imageStyle?: React.CSSProperties; + image?: React.ReactNode; + description?: React.ReactNode; + }> + >; + + export const Avatar: any; + + export const SensitiveText: React.FC<{ className?: string; text: string }>; + + export const TextArea: any; + + export const Image: any; + + export const Icon: React.FC<{ icon: string } & React.SVGProps>; + + export const IconBtn: React.FC<{ + icon: string; + className?: string; + iconClassName?: string; + size?: 'small' | 'middle' | 'large'; + shape?: 'circle' | 'square'; + title?: string; + onClick?: React.MouseEventHandler; + }>; + + export const PillTabs: any; + + export const PillTabPane: any; + + export const LoadingSpinner: React.FC<{ tip?: string }>; + + export const WebFastForm: any; + + export const WebMetaForm: any; + + export const createMetaFormSchema: any; + + export const metaFormFieldSchema: any; + + export const FullModalField: any; + + export const DefaultFullModalInputEditorRender: any; + + export const DefaultFullModalTextAreaEditorRender: any; + + export const openModal: ( + content: React.ReactNode, + + props?: { + /** + * 是否显示右上角的关闭按钮 + * @default false + */ + closable?: boolean; + + /** + * 遮罩层是否可关闭 + */ + maskClosable?: boolean; + + /** + * 关闭modal的回调 + */ + onCloseModal?: () => void; + } + ) => number; + + export const closeModal: any; + + export const ModalWrapper: any; + + export const useModalContext: any; + + export const openConfirmModal: any; + + export const openReconfirmModal: any; + + export const Loadable: any; + + export const Loading: React.FC<{ + spinning: boolean; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + }>; + + export const LoadingOnFirst: React.FC<{ + spinning: boolean; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + }>; + + export const SidebarView: any; + + export const GroupPanelSelector: any; + + export const Emoji: any; + + export const PortalAdd: any; + + export const PortalRemove: any; + + export const ErrorBoundary: any; + + export const UserAvatar: any; + + export const UserName: React.FC<{ + userId: string; + className?: string; + }>; + + export const Markdown: any; +} diff --git a/client/web/src/utils/user-helper.ts b/client/web/src/utils/user-helper.ts index 34b5c56f..7895ad50 100644 --- a/client/web/src/utils/user-helper.ts +++ b/client/web/src/utils/user-helper.ts @@ -1,4 +1,4 @@ -import { loginWithToken, UserLoginInfo } from 'tailchat-shared'; +import { UserLoginInfo, model } from 'tailchat-shared'; import _isNil from 'lodash/isNil'; import { getUserJWT } from './jwt-helper'; @@ -29,7 +29,7 @@ export async function tryAutoLogin(): Promise { } console.debug('正在尝试使用Token登录'); - userLoginInfo = await loginWithToken(token); + userLoginInfo = await model.user.loginWithToken(token); if (userLoginInfo === null) { throw new Error('Token 内容不合法'); }