diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue index e5e499a8..49a81f06 100644 --- a/src/components/dialogs/UserDialog/UserDialog.vue +++ b/src/components/dialogs/UserDialog/UserDialog.vue @@ -363,7 +363,7 @@ - {{ userOnlineFor(userDialog) }} + {{ userOnlineFor(userDialog.ref) }} @@ -538,7 +538,7 @@
diff --git a/src/localization/en.json b/src/localization/en.json index a7a15bc2..fe8fac48 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -2350,7 +2350,8 @@ "vpn_in_use": "VRChat currently blocks most VPNs. Please disable any connected VPNs and try again.", "login_error": "Login Error", "invalid_json_response": "Invalid JSON response", - "403_404_bailing_request": "Bailing request due to recent 404/403" + "403_404_bailing_request": "Bailing request due to recent 404/403", + "unavailable": "Service may be unavailable due to VRChat internal issues" } } } diff --git a/src/localization/ja.json b/src/localization/ja.json index 5cdd41fd..a32db581 100644 --- a/src/localization/ja.json +++ b/src/localization/ja.json @@ -2223,7 +2223,8 @@ "vpn_in_use": "VRChatは現在ほとんどのVPNをブロックしています。接続中のVPNを無効にして、もう一度お試しください。", "login_error": "ログインエラー", "invalid_json_response": "無効なJSONレスポンス", - "403_404_bailing_request": "最近の404/403エラーのため、リクエストを中止しました" + "403_404_bailing_request": "最近の404/403エラーのため、リクエストを中止しました", + "unavailable": "サービスはVRChatの内部問題により利用できない可能性があります" } } } diff --git a/src/localization/zh-CN.json b/src/localization/zh-CN.json index 734fe9db..b811351b 100644 --- a/src/localization/zh-CN.json +++ b/src/localization/zh-CN.json @@ -2349,7 +2349,8 @@ "vpn_in_use": "VRChat 目前限制了大多数的 VPN 服务。请断开所有已连接的 VPN 后重试。", "login_error": "登录失败", "invalid_json_response": "无效的 JSON 响应", - "403_404_bailing_request": "由于最近出现的 403/404 错误,请求已中止" + "403_404_bailing_request": "由于最近出现的 403/404 错误,请求已中止", + "unavailable": "服务可能由于 VRChat 内部问题而不可用" } } } diff --git a/src/service/request.js b/src/service/request.js index b2445993..50722f91 100644 --- a/src/service/request.js +++ b/src/service/request.js @@ -284,7 +284,6 @@ export function $throw(code, error, endpoint) { `${t('api.error.message.endpoint')}: "${typeof endpoint === 'string' ? endpoint : JSON.stringify(endpoint)}"` ); } - const text = message.map((s) => escapeTag(s)).join('
'); let ignoreError = false; if ( (code === 404 || code === -1) && @@ -298,11 +297,13 @@ export function $throw(code, error, endpoint) { ) { ignoreError = true; } - if ( - (code === 403 || code === 404 || code === -1) && - endpoint?.startsWith('instances/') - ) { - ignoreError = true; + if (code === 403 || code === 404 || code === -1) { + if (endpoint?.startsWith('instances/')) { + ignoreError = true; + } + if (endpoint?.includes('/mutuals/friends')) { + message[1] = `${t('api.error.message.error_message')}: "${t('api.error.message.unavailable')}"`; + } } if (endpoint?.startsWith('analysis/')) { ignoreError = true; @@ -310,6 +311,8 @@ export function $throw(code, error, endpoint) { if (endpoint.endsWith('/mutuals') && (code === 403 || code === -1)) { ignoreError = true; } + const text = message.map((s) => escapeTag(s)).join('
'); + if (text.length && !ignoreError) { if (AppDebug.errorNoty) { AppDebug.errorNoty.close(); diff --git a/src/shared/utils/user.js b/src/shared/utils/user.js index 278bcc73..af92910c 100644 --- a/src/shared/utils/user.js +++ b/src/shared/utils/user.js @@ -259,16 +259,16 @@ function parseUserUrl(user) { /** * - * @param {object} ctx + * @param {object} ref * @returns {string} */ -function userOnlineFor(ctx) { - if (ctx.ref.state === 'online' && ctx.ref.$online_for) { - return timeToText(Date.now() - ctx.ref.$online_for); - } else if (ctx.ref.state === 'active' && ctx.ref.$active_for) { - return timeToText(Date.now() - ctx.ref.$active_for); - } else if (ctx.ref.$offline_for) { - return timeToText(Date.now() - ctx.ref.$offline_for); +function userOnlineFor(ref) { + if (ref.state === 'online' && ref.$online_for) { + return timeToText(Date.now() - ref.$online_for); + } else if (ref.state === 'active' && ref.$active_for) { + return timeToText(Date.now() - ref.$active_for); + } else if (ref.$offline_for) { + return timeToText(Date.now() - ref.$offline_for); } return '-'; } diff --git a/src/views/Charts/components/MutualFriends.vue b/src/views/Charts/components/MutualFriends.vue index 7499985b..ff815730 100644 --- a/src/views/Charts/components/MutualFriends.vue +++ b/src/views/Charts/components/MutualFriends.vue @@ -268,6 +268,8 @@ } } + const isCancelled = () => status.cancelRequested === true; + async function startFetch() { const rateLimiter = createRateLimiter({ limitPerInterval: 5, @@ -278,12 +280,34 @@ const collected = []; let offset = 0; while (true) { + if (isCancelled()) { + break; + } await rateLimiter.wait(); - const args = await executeWithBackoff(() => userRequest.getMutualFriends({ userId, offset, n: 100 }), { - maxRetries: 4, - baseDelay: 500, - shouldRetry: (err) => err?.status === 429 || (err?.message || '').includes('429') + if (isCancelled()) { + break; + } + const args = await executeWithBackoff( + () => { + if (isCancelled()) { + throw new Error('cancelled'); + } + return userRequest.getMutualFriends({ userId, offset, n: 100 }); + }, + { + maxRetries: 4, + baseDelay: 500, + shouldRetry: (err) => err?.status === 429 || (err?.message || '').includes('429') + } + ).catch((err) => { + if ((err?.message || '') === 'cancelled') { + return null; + } + throw err; }); + if (!args || isCancelled()) { + break; + } collected.push(...args.json); if (args.json.length < 100) { break; @@ -320,10 +344,22 @@ if (!friend?.id) { continue; } + if (isCancelled()) { + cancelled = true; + break; + } try { const mutuals = await fetchMutualFriends(friend.id); + if (isCancelled()) { + cancelled = true; + break; + } mutualMap.set(friend.id, { friend, mutuals }); } catch (err) { + if ((err?.message || '') === 'cancelled' || isCancelled()) { + cancelled = true; + break; + } console.warn('[MutualNetworkGraph] Skipping friend due to fetch error', friend.id, err); continue; }