|
|
@@ -589,6 +589,9 @@ const reconnectInterval = ref(5000) // 重连间隔(毫秒)
|
|
|
const reconnectTimer = ref<NodeJS.Timeout | null>(null)
|
|
|
const isConnecting = ref(false)
|
|
|
const connectionStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
|
|
|
+// 认证 token 相关变量
|
|
|
+const authToken = ref<string | null>(null)
|
|
|
+const authTokenUpdateTimer = ref<NodeJS.Timeout | null>(null)
|
|
|
|
|
|
// 同步数据最大保留时间(40分钟)
|
|
|
const SYNC_DATA_MAX_AGE = 40 * 60 * 1000 // 40分钟 = 40 * 60 * 1000毫秒
|
|
|
@@ -1354,7 +1357,48 @@ const processIframeLinks = async () => {
|
|
|
console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
|
|
|
return element
|
|
|
}
|
|
|
- }
|
|
|
+ }
|
|
|
+ else if (iframeSrc.includes('aichat.cocorobo') || iframeSrc.includes('knowledge.cocorobo')) {
|
|
|
+ hasIframe = true
|
|
|
+ try {
|
|
|
+ // 解析URL,处理hash部分
|
|
|
+ let baseUrl = iframeSrc
|
|
|
+ let hashPart = ''
|
|
|
+
|
|
|
+ // 分离base URL和hash部分
|
|
|
+ if (iframeSrc.includes('#')) {
|
|
|
+ const parts = iframeSrc.split('#')
|
|
|
+ baseUrl = parts[0]
|
|
|
+ hashPart = parts[1]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建新的hash部分,添加参数
|
|
|
+ // 使用当前幻灯片索引作为task参数
|
|
|
+ let newHash = hashPart
|
|
|
+ if (newHash.includes('?')) {
|
|
|
+ // 如果hash中已经有查询参数,添加&
|
|
|
+ newHash += `&courseid=${props.courseid || ''}`
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 如果hash中没有查询参数,添加?
|
|
|
+ newHash += `?courseid=${props.courseid || ''}`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建新的URL
|
|
|
+ const newUrl = `${baseUrl}#${newHash}`
|
|
|
+
|
|
|
+ console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
|
|
|
+ // 返回更新后的元素
|
|
|
+ return {
|
|
|
+ ...element,
|
|
|
+ url: newUrl
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
|
|
|
+ return element
|
|
|
+ }
|
|
|
+ }
|
|
|
else if (toolType == 73) {
|
|
|
hasIframe = true
|
|
|
|
|
|
@@ -3262,6 +3306,12 @@ onUnmounted(() => {
|
|
|
reconnectTimer.value = null
|
|
|
}
|
|
|
|
|
|
+ // 清理认证 token 更新定时器
|
|
|
+ if (authTokenUpdateTimer.value) {
|
|
|
+ clearTimeout(authTokenUpdateTimer.value)
|
|
|
+ authTokenUpdateTimer.value = null
|
|
|
+ }
|
|
|
+
|
|
|
if (providerSocket.value) {
|
|
|
providerSocket.value.destroy()
|
|
|
providerSocket.value = null
|
|
|
@@ -3295,6 +3345,82 @@ onUnmounted(() => {
|
|
|
handlePageUnload()
|
|
|
})
|
|
|
|
|
|
+// 获取认证 token
|
|
|
+const getAuthToken = async (): Promise<string> => {
|
|
|
+ try {
|
|
|
+ // 使用代理路径避免跨域问题
|
|
|
+ // 开发环境:通过 vite 代理 /yjs-auth/auth/token -> https://yjsredis.cocorobo.cn/auth/token
|
|
|
+ // 生产环境:需要配置服务器代理或使用后端 API
|
|
|
+
|
|
|
+ // 兼容性修复:不直接使用 import.meta.env.DEV
|
|
|
+ let isDev = false
|
|
|
+ // 判断如果有 window 对象且以 localhost/127.0.0.1 开头,则认为是开发环境
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
+ const hostname = window.location.hostname
|
|
|
+ if (
|
|
|
+ hostname === 'localhost' ||
|
|
|
+ hostname === '127.0.0.1' ||
|
|
|
+ hostname === '::1' ||
|
|
|
+ /^192\.168\.\d+\.\d+$/.test(hostname)
|
|
|
+ ) {
|
|
|
+ isDev = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let authUrl = ''
|
|
|
+ if (isDev) {
|
|
|
+ // 开发环境使用 vite 代理
|
|
|
+ authUrl = '/yjs-auth/auth/token'
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 生产环境:如果服务器有代理则使用代理,否则直接访问(需要服务器配置 CORS)
|
|
|
+ // 或者通过后端 API 获取 token
|
|
|
+ let baseUrl = 'https://yjsredis.cocorobo.cn/'
|
|
|
+ baseUrl = baseUrl.replace(/\/+$/, '')
|
|
|
+ authUrl = `${baseUrl}/auth/token`
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('🔐 获取认证 token,URL:', authUrl)
|
|
|
+ const response = await axios.get(authUrl)
|
|
|
+ console.log('🔐 获取认证 token 成功', response)
|
|
|
+
|
|
|
+ return response
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('🔐 获取认证 token 失败:', error)
|
|
|
+ throw error
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 定期更新认证 token
|
|
|
+const updateAuthToken = async () => {
|
|
|
+ try {
|
|
|
+ if (!providerSocket.value) return
|
|
|
+
|
|
|
+ const newToken = await getAuthToken()
|
|
|
+ authToken.value = newToken
|
|
|
+ // 更新 provider 的 auth 参数
|
|
|
+ if (providerSocket.value.params) {
|
|
|
+ providerSocket.value.params.yauth = newToken
|
|
|
+ }
|
|
|
+ console.log('🔐 认证 token 已更新')
|
|
|
+
|
|
|
+ // 30分钟后再次更新
|
|
|
+ if (authTokenUpdateTimer.value) {
|
|
|
+ clearTimeout(authTokenUpdateTimer.value)
|
|
|
+ }
|
|
|
+ authTokenUpdateTimer.value = setTimeout(updateAuthToken, 30 * 60 * 1000) as unknown as NodeJS.Timeout
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('🔐 更新认证 token 失败:', error)
|
|
|
+ // 失败后1秒重试
|
|
|
+ if (authTokenUpdateTimer.value) {
|
|
|
+ clearTimeout(authTokenUpdateTimer.value)
|
|
|
+ }
|
|
|
+ authTokenUpdateTimer.value = setTimeout(updateAuthToken, 1000) as unknown as NodeJS.Timeout
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 手动重连
|
|
|
const manualReconnect = () => {
|
|
|
if (isConnecting.value) return
|
|
|
@@ -3304,7 +3430,7 @@ const manualReconnect = () => {
|
|
|
}
|
|
|
|
|
|
// 创建WebSocket连接
|
|
|
-const createWebSocketConnection = () => {
|
|
|
+const createWebSocketConnection = async () => {
|
|
|
if (!api.yweb_socket || isConnecting.value) return
|
|
|
|
|
|
isConnecting.value = true
|
|
|
@@ -3317,13 +3443,36 @@ const createWebSocketConnection = () => {
|
|
|
providerSocket.value = null
|
|
|
}
|
|
|
|
|
|
+ // 清理之前的 token 更新定时器
|
|
|
+ if (authTokenUpdateTimer.value) {
|
|
|
+ clearTimeout(authTokenUpdateTimer.value)
|
|
|
+ authTokenUpdateTimer.value = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取认证 token
|
|
|
+ // try {
|
|
|
+ // authToken.value = await getAuthToken()
|
|
|
+ // console.log('🔐 认证 token 获取成功,准备连接 WebSocket')
|
|
|
+ // }
|
|
|
+ // catch (error) {
|
|
|
+ // console.error('🔐 获取认证 token 失败,连接可能失败:', error)
|
|
|
+ // connectionStatus.value = 'disconnected'
|
|
|
+ // isConnecting.value = false
|
|
|
+ // handleDisconnection()
|
|
|
+ // return
|
|
|
+ // }
|
|
|
+
|
|
|
docSocket.value = new Y.Doc()
|
|
|
docSocket.value.gc = true
|
|
|
providerSocket.value = new WebsocketProvider(
|
|
|
api.yweb_socket,
|
|
|
'PPT' + props.courseid,
|
|
|
- docSocket.value
|
|
|
+ docSocket.value,
|
|
|
+ // { params: { yauth: authToken.value } }
|
|
|
)
|
|
|
+
|
|
|
+ // 启动定期更新 token
|
|
|
+ // updateAuthToken()
|
|
|
|
|
|
providerSocket.value.on('status', (event: any) => {
|
|
|
console.log('👉 WebSocket状态:', event.status)
|