diff --git a/main.js b/main.js index c1caf36..9409745 100644 --- a/main.js +++ b/main.js @@ -15,6 +15,20 @@ app.$mount() import { createSSRApp } from 'vue' export function createApp() { const app = createSSRApp(App) + + // #ifdef MP-TOUTIAO + // 抖音小程序:忽略开发工具日志通道的 socket 错误 + const originalError = console.error + console.error = function(...args) { + const message = args[0] + if (typeof message === 'string' && message.includes('APIScopeError: socket does not exist')) { + // 忽略这个已知的开发工具错误 + return + } + originalError.apply(console, args) + } + // #endif + return { app } diff --git a/manifest.json b/manifest.json index 0b117a4..308026c 100644 --- a/manifest.json +++ b/manifest.json @@ -76,6 +76,9 @@ "getPhoneNumber" : { "desc" : "用于登录和账号绑定" } + }, + "optimization" : { + "subPackages" : true } }, "uniStatistics" : { diff --git a/pages-game/game/minesweeper/play.vue b/pages-game/game/minesweeper/play.vue index 8d937a7..d7e3a06 100644 --- a/pages-game/game/minesweeper/play.vue +++ b/pages-game/game/minesweeper/play.vue @@ -508,7 +508,7 @@ export default { try { const res = await new Promise((resolve, reject) => { uni.request({ - url: 'https://game.1024tool.vip/api/internal/game/minesweeper/config', + url: 'https://kdy.1024tool.vip/api/internal/game/minesweeper/config', header: { 'X-Internal-Key': 'bindbox-internal-secret-2024' }, success: (res) => resolve(res), fail: (err) => reject(err) @@ -555,23 +555,28 @@ export default { }, async initNakama(token, server, key, stableUid = null) { try { - const serverUrl = server || 'wss://game.1024tool.vip'; + const serverUrl = server || 'wss://kdy.1024tool.vip'; const serverKey = key || 'defaultkey'; nakamaManager.initClient(serverUrl, serverKey); this.gameToken = token; + + // 只进行认证,不自动建立 WebSocket 连接 + // WebSocket 将在用户点击开始匹配时才连接(解决抖音小程序的 APIScopeError) const session = await nakamaManager.authenticateWithGameToken(token, stableUid); this.myUserId = session.user_id; - this.isConnected = true; - this.addLog('system', '✅ 已连接到远程节点'); + + // 注意:此时 isConnected = false,因为还未建立 WebSocket 连接 + // 连接将在 startMatchmaking() 或其他需要网络操作的方法中进行 + this.addLog('system', '✅ 认证成功,准备就绪'); this.setupSocketListeners(); - - // 获取在线人数 - this.fetchOnlineCount(); - this.onlineCountInterval = setInterval(() => this.fetchOnlineCount(), 10000); - + // 跨设备对局发现逻辑:调用 RPC 询问服务器我当前是否有正在进行的战局 if (!this.matchId) { try { + // RPC 需要 WebSocket 连接,所以先连接 + await nakamaManager.connect(); + this.isConnected = true; + const activeMatch = await nakamaManager.rpc('find_my_match', {}); if (activeMatch && activeMatch.match_id) { console.log('[RPC发现] 发现服务器端活跃对局:', activeMatch.match_id); @@ -593,6 +598,12 @@ export default { // 如果有比赛 ID(来自 URL 或 缓存),尝试加入 if (this.matchId) { + // 确保已连接 + if (!this.isConnected) { + await nakamaManager.connect(); + this.isConnected = true; + } + this.addLog('system', this.isSpectator ? '🔭 正在切入观察视角...' : '🚪 正在恢复战局...'); try { await nakamaManager.joinMatch(this.matchId); @@ -610,7 +621,12 @@ export default { } } } catch (e) { - this.addLog('system', '❌ 通讯异常: ' + e.message); + console.error('[初始化错误]', e); + this.addLog('system', '❌ 初始化失败: ' + e.message); + uni.showToast({ + title: '连接失败,请重试', + icon: 'none' + }); } }, setupSocketListeners() { @@ -706,12 +722,19 @@ export default { this._isReconnecting = true; console.log('[重连] 开始重新连接...'); - const session = await nakamaManager.authenticateWithGameToken(this.gameToken, this.stableUserId); - this.myUserId = session.user_id; + + // 先确保认证(如果 session 已失效) + if (!nakamaManager.session || !nakamaManager.session.token) { + console.log('[重连] 重新认证...'); + const session = await nakamaManager.authenticateWithGameToken(this.gameToken, this.stableUserId); + this.myUserId = session.user_id; + } + + // 建立WebSocket连接 + await nakamaManager.connect(); this.isConnected = true; - console.log('[重连] 认证成功'); - + console.log('[重连] WebSocket 连接成功'); this.addLog('system', `✅ 重连成功`); // 如果有进行中的比赛,尝试重新加入 @@ -894,19 +917,56 @@ export default { console.log('--- UI Click: Start Matchmaking ---'); this.isMatching = true; this.matchingTimer = 0; - this.logs = []; + this.logs = []; this.addLog('system', '🚀 发射匹配脉冲...'); - + + // 抖音小程序:确保在用户交互后才建立 WebSocket 连接 + if (!this.isConnected) { + this.addLog('system', '📡 正在建立连接...'); + try { + await nakamaManager.connect(); + this.isConnected = true; + this.addLog('system', '✅ 连接成功'); + } catch (connErr) { + console.error('[连接失败]', connErr); + this.addLog('system', '❌ 连接失败: ' + connErr.message); + uni.showToast({ + title: '连接失败,请重试', + icon: 'none' + }); + this.isMatching = false; + return; + } + } + clearInterval(this.matchInterval); this.matchInterval = setInterval(() => this.matchingTimer++, 1000); - + await nakamaManager.findMatch(this.matchPlayerCount, this.matchPlayerCount); console.log('Matchmaker ticket requested'); } catch (e) { console.error('Matchmaking error:', e); this.isMatching = false; clearInterval(this.matchInterval); - this.addLog('system', '❌ 发射失败'); + + // 提供更友好的错误提示 + let errorMsg = '❌ 匹配失败'; + if (e.message) { + if (e.message.includes('socket') || e.message.includes('APIScope')) { + errorMsg = '❌ 网络连接异常,请重试'; + } else if (e.message.includes('timeout')) { + errorMsg = '❌ 连接超时,请检查网络'; + } else { + errorMsg = '❌ ' + e.message; + } + } + this.addLog('system', errorMsg); + + uni.showToast({ + title: errorMsg.replace('❌ ', ''), + icon: 'none', + duration: 3000 + }); } }, cancelMatchmaking() { diff --git a/pages-game/game/minesweeper/room-list.vue b/pages-game/game/minesweeper/room-list.vue index 8fcdeec..da8f9f8 100644 --- a/pages-game/game/minesweeper/room-list.vue +++ b/pages-game/game/minesweeper/room-list.vue @@ -107,12 +107,18 @@ export default { this.loading = true; try { if (!nakamaManager.isConnected) { - nakamaManager.initClient(this.nakamaServer || 'wss://game.1024tool.vip', this.nakamaKey || 'defaultkey'); + nakamaManager.initClient(this.nakamaServer || 'wss://kdy.1024tool.vip', this.nakamaKey || 'defaultkey'); + // 只进行认证,不自动连接 await nakamaManager.authenticateWithGameToken(this.gameToken); } await this.loadRooms(); } catch (e) { - uni.showToast({ title: '连接通讯中心失败', icon: 'none' }); + console.error('[房间列表初始化错误]', e); + uni.showToast({ + title: '连接失败: ' + (e.message || '未知错误'), + icon: 'none', + duration: 3000 + }); } finally { this.loading = false; } @@ -120,12 +126,37 @@ export default { async loadRooms() { this.isRefreshing = true; try { + // 确保已连接(抖音小程序需要用户交互后才能连接) + if (!nakamaManager.isConnected) { + await nakamaManager.connect(); + } + const res = await nakamaManager.rpc('list_matches', {}); this.rooms = res || []; // 重置倒计时 this.countdown = 5; } catch (e) { console.error('Failed to load rooms', e); + + // 提供更友好的错误提示 + let errorMsg = '加载失败'; + if (e.message) { + if (e.message.includes('socket') || e.message.includes('APIScope')) { + errorMsg = '网络连接异常'; + } else if (e.message.includes('timeout')) { + errorMsg = '连接超时'; + } else { + errorMsg = e.message; + } + } + + // 只在非自动刷新时显示错误提示 + if (!this.refreshInterval || this.loading) { + uni.showToast({ + title: errorMsg, + icon: 'none' + }); + } } finally { this.isRefreshing = false; this.loading = false; diff --git a/utils/nakamaManager.js b/utils/nakamaManager.js index 608a5de..1ef15bc 100644 --- a/utils/nakamaManager.js +++ b/utils/nakamaManager.js @@ -114,10 +114,9 @@ class NakamaManager { }; console.log('[Nakama] Authenticated, user_id:', this.session.user_id); - // 认证成功后建立 WebSocket 连接 - this._connectWebSocket() - .then(() => resolve(this.session)) - .catch(reject); + // 抖音小程序需要在用户交互后才能建立 WebSocket 连接 + // 这里不自动连接,由业务层在适当时机调用 connect() + resolve(this.session); } else { reject(new Error('Authentication failed: ' + JSON.stringify(res.data))); } @@ -129,6 +128,23 @@ class NakamaManager { }); } + /** + * 手动建立 WebSocket 连接(抖音小程序专用) + * 在用户交互(如点击按钮)后调用 + */ + connect() { + if (this.isConnected) { + console.log('[Nakama] Already connected'); + return Promise.resolve(); + } + + if (!this.session || !this.session.token) { + return Promise.reject(new Error('Not authenticated. Call authenticateWithGameToken first.')); + } + + return this._connectWebSocket(); + } + /** * 建立 WebSocket 连接 */ @@ -157,47 +173,55 @@ class NakamaManager { console.log('[Nakama] WebSocket connecting...'); - this.socketTask = uni.connectSocket({ - url: wsUrl, - complete: () => { } - }); + try { + this.socketTask = uni.connectSocket({ + url: wsUrl, + complete: () => { } + }); - const connectTimeout = setTimeout(() => { - reject(new Error('WebSocket connection timeout')); - }, 15000); + const connectTimeout = setTimeout(() => { + this.isConnecting = false; + reject(new Error('WebSocket connection timeout')); + }, 15000); - this.socketTask.onOpen(() => { - clearTimeout(connectTimeout); - this.isConnected = true; + this.socketTask.onOpen(() => { + clearTimeout(connectTimeout); + this.isConnected = true; + this.isConnecting = false; + console.log('[Nakama] WebSocket connected'); + this._startHeartbeat(); + resolve(); + }); + + this.socketTask.onClose((res) => { + console.log('[Nakama] WebSocket closed:', res.code, res.reason); + this.isConnected = false; + this.isConnecting = false; + this._stopHeartbeat(); + if (this.listeners.ondisconnect) { + this.listeners.ondisconnect(res); + } + }); + + this.socketTask.onError((err) => { + clearTimeout(connectTimeout); + console.error('[Nakama] WebSocket error:', err); + this.isConnected = false; + this.isConnecting = false; + if (this.listeners.onerror) { + this.listeners.onerror(err); + } + reject(new Error('WebSocket connection failed: ' + JSON.stringify(err))); + }); + + this.socketTask.onMessage((res) => { + this._handleMessage(res.data); + }); + } catch (error) { this.isConnecting = false; - console.log('[Nakama] WebSocket connected'); - this._startHeartbeat(); - resolve(); - }); - - this.socketTask.onClose((res) => { - console.log('[Nakama] WebSocket closed:', res.code, res.reason); - this.isConnected = false; - this._stopHeartbeat(); - if (this.listeners.ondisconnect) { - this.listeners.ondisconnect(res); - } - }); - - this.socketTask.onError((err) => { - clearTimeout(connectTimeout); - console.error('[Nakama] WebSocket error:', err); - this.isConnected = false; - this.isConnecting = false; - if (this.listeners.onerror) { - this.listeners.onerror(err); - } - reject(new Error('WebSocket connection failed')); - }); - - this.socketTask.onMessage((res) => { - this._handleMessage(res.data); - }); + console.error('[Nakama] Failed to create socket:', error); + reject(error); + } }); } @@ -307,9 +331,15 @@ class NakamaManager { * 开始匹配 */ async findMatch(minCount, maxCount) { + // 如果未连接,先建立连接 if (!this.isConnected) { - console.log('[Nakama] Not connected, reconnecting...'); - await this.authenticateWithGameToken(this.gameToken); + console.log('[Nakama] Not connected, connecting now...'); + try { + await this.connect(); + } catch (error) { + console.error('[Nakama] Failed to connect:', error); + throw new Error('无法连接到游戏服务器,请稍后重试'); + } } if (!this.gameToken) {