Skip to content

Commit 5cee8ac

Browse files
committed
fix: bound network request recovery
1 parent 5be1081 commit 5cee8ac

12 files changed

Lines changed: 587 additions & 160 deletions

File tree

src/components/MarketPulse.vue

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
ref="pulseViewport"
66
class="market-pulse-viewport"
77
>
8+
<div v-if="error" class="market-pulse-placeholder is-error" role="status">
9+
{{ $t('market.unavailable') }}
10+
</div>
811
<div
9-
v-if="visibleItems.length"
12+
v-else-if="visibleItems.length"
1013
ref="trackRef"
1114
:class="[
1215
'market-pulse-track',
@@ -42,9 +45,6 @@
4245
</button>
4346
</div>
4447
<div v-else-if="loading" class="market-pulse-placeholder">{{ $t('market.loading') }}</div>
45-
<div v-else-if="error" class="market-pulse-placeholder is-error" role="status">
46-
{{ $t('market.unavailable') }}
47-
</div>
4848
</div>
4949
<v-btn
5050
class="market-pulse-close"
@@ -71,7 +71,7 @@ import { useI18n } from 'vue-i18n';
7171
import { useRoute } from 'vue-router';
7272
import MarketPulseChart from '@/components/MarketPulseChart.vue';
7373
import { trackMarketPulseEvent } from '@/services/analytics';
74-
import { loadCachedMarketQuotes, subscribeMarketQuotes } from '@/services/marketApi';
74+
import { DEFAULT_MARKET_SOURCE, loadCachedMarketQuotes, subscribeMarketQuotes } from '@/services/marketApi';
7575
7676
const emit = defineEmits(['close']);
7777
const route = useRoute();
@@ -80,6 +80,7 @@ const { locale, t } = useI18n();
8080
const quotes = ref(loadCachedMarketQuotes());
8181
const loading = ref(false);
8282
const error = ref('');
83+
const terminalMarketFailure = ref(false);
8384
const flashingAssets = ref({});
8485
const renderedSlotCount = ref(4);
8586
const windowStart = ref(0);
@@ -109,6 +110,11 @@ let activePointerGesture = null;
109110
let suppressNextClick = false;
110111
let lastViewportWidth = 0;
111112
let lastQuoteSignature = createQuoteSignature(quotes.value);
113+
let marketProviderState = {
114+
source: DEFAULT_MARKET_SOURCE,
115+
switchUsed: false,
116+
reconnectAttempts: 0
117+
};
112118
const MARKET_PULSE_RENDER_BUFFER_SLOTS = 2;
113119
const MARKET_PULSE_SLOT_WIDTH = 200;
114120
const MARKET_PULSE_SLOT_DURATION_MS = 18_000;
@@ -257,6 +263,7 @@ function markPriceChanges(nextQuotes) {
257263
}
258264
259265
function handleMarketUpdate(nextQuotes) {
266+
if (terminalMarketFailure.value) return;
260267
const nextSignature = createQuoteSignature(nextQuotes);
261268
if (nextSignature === lastQuoteSignature) {
262269
loading.value = false;
@@ -485,13 +492,19 @@ function onWheel(e) {
485492
}
486493
487494
function startMarketService() {
488-
if (unsubscribeMarket || document.hidden) return;
495+
if (unsubscribeMarket || document.hidden || terminalMarketFailure.value) return;
489496
loading.value = quotes.value.length === 0;
490497
unsubscribeMarket = subscribeMarketQuotes({
498+
providerState: marketProviderState,
499+
onProviderStateChange: state => {
500+
marketProviderState = state;
501+
},
491502
onUpdate: handleMarketUpdate,
492503
onError: err => {
504+
terminalMarketFailure.value = true;
493505
error.value = err.message || String(err);
494506
loading.value = false;
507+
stopMarketService();
495508
}
496509
});
497510
}

src/components/MarketPulseChart.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ async function loadCandles({ silent = false } = {}) {
316316
startTime,
317317
endTime,
318318
maxCandles,
319+
fallback: false,
320+
marketId: props.item.sourceMarketId,
319321
source: props.item.source,
320322
signal: requestController.signal
321323
});

src/lang/chs.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export default {
77
alert: {
88
missingPartNumber: '请输入料号',
99
fetchFailed: '获取失败:{0}',
10-
missingFlashId: '请输入闪存ID'
10+
missingFlashId: '请输入闪存ID',
11+
requestTimeout: '请求超时,请检查网络或 HTTP 解析服务器后重试。'
1112
},
1213
nav: {
1314
decodePartNumber: '料号',

src/lang/eng.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export default {
77
alert: {
88
missingPartNumber: 'Enter a PN',
99
fetchFailed: 'Request failed: {0}',
10-
missingFlashId: 'Enter a Flash ID'
10+
missingFlashId: 'Enter a Flash ID',
11+
requestTimeout: 'Request timed out. Check the network or HTTP parser server and try again.'
1112
},
1213
nav: {
1314
decodePartNumber: 'Parts',

src/services/flashApi.js

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
warmEmbeddedParser as warmEmbeddedFdnextParser
99
} from '@/services/fdnextApi';
1010
import { FDNEXT_CAPABILITIES_SCHEMA_VERSIONS, summaryText } from '@/services/fdnextResultView';
11+
import { DEFAULT_HTTP_REQUEST_TIMEOUT_MS, runWithRequestTimeout } from '@/services/requestControl';
1112

1213
const makeUrl = (endpoint, params = {}) => {
1314
const base = store.getServerAddress().replace(/\/+$/, '');
@@ -38,18 +39,24 @@ const parseResponsePayload = async response => {
3839
}
3940
};
4041

41-
const request = async (endpoint, params = {}, schemaVersion = 'fdnext.result.v1') => {
42-
const response = await fetch(makeUrl(endpoint, params));
43-
const payload = await parseResponsePayload(response);
44-
if (payload) {
45-
try {
46-
return assertFdnextPayload(payload, schemaVersion, endpoint);
47-
} catch (err) {
48-
if (response.ok) throw err;
42+
const request = async (endpoint, params = {}, schemaVersion = 'fdnext.result.v1', options = {}) => {
43+
return runWithRequestTimeout(async signal => {
44+
const response = await fetch(makeUrl(endpoint, params), { signal });
45+
const payload = await parseResponsePayload(response);
46+
if (payload) {
47+
try {
48+
return assertFdnextPayload(payload, schemaVersion, endpoint);
49+
} catch (err) {
50+
if (response.ok) throw err;
51+
}
4952
}
50-
}
51-
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
52-
throw new Error(`Unsupported fdnext response from ${endpoint}`);
53+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
54+
throw new Error(`Unsupported fdnext response from ${endpoint}`);
55+
}, {
56+
signal: options.signal,
57+
timeoutMs: options.timeoutMs ?? DEFAULT_HTTP_REQUEST_TIMEOUT_MS,
58+
timeoutMessage: `Request to ${endpoint} timed out.`
59+
});
5360
};
5461

5562
const useEmbeddedParser = () => store.isEmbeddedParser();
@@ -67,51 +74,51 @@ const controllerGroupParams = () => ({
6774
controllerGroup: store.getControllerGroupParam()
6875
});
6976

70-
export const getServerInfo = async () => useEmbeddedParser()
77+
export const getServerInfo = async (options = {}) => useEmbeddedParser()
7178
? getEmbeddedInfo()
72-
: request('capabilities', langParams(), FDNEXT_CAPABILITIES_SCHEMA_VERSIONS);
79+
: request('capabilities', langParams(), FDNEXT_CAPABILITIES_SCHEMA_VERSIONS, options);
7380

7481
export const warmEmbeddedParser = () => {
7582
if (!useEmbeddedParser()) return Promise.resolve();
7683
return warmEmbeddedFdnextParser();
7784
};
7885

79-
export const decodePartNumber = async pn => {
86+
export const decodePartNumber = async (pn, options = {}) => {
8087
return useEmbeddedParser() ? decodeEmbeddedPartNumber(pn) : request('parts/decode', {
8188
...langParams(),
8289
...controllerGroupParams(),
8390
query: pn
84-
});
91+
}, 'fdnext.result.v1', options);
8592
};
8693

87-
export const searchPartNumber = async (pn, limit = 0) => {
94+
export const searchPartNumber = async (pn, limit = 0, options = {}) => {
8895
return useEmbeddedParser() ? searchEmbeddedPartNumber(pn, limit) : request('parts/search', {
8996
...langParams(),
9097
query: pn,
9198
...limitParams(limit)
92-
});
99+
}, 'fdnext.result.v1', options);
93100
};
94101

95-
export const summarizePartNumber = async pn => summaryText(await decodePartNumber(pn));
102+
export const summarizePartNumber = async (pn, options = {}) => summaryText(await decodePartNumber(pn, options));
96103

97-
export const decodeFlashId = async id => {
104+
export const decodeFlashId = async (id, options = {}) => {
98105
const input = { idScheme: 'nand.flash_id' };
99106
return useEmbeddedParser() ? decodeEmbeddedFlashId(id) : request('identifiers/decode', {
100107
...langParams(),
101108
query: id,
102109
...input,
103110
...controllerGroupParams()
104-
});
111+
}, 'fdnext.result.v1', options);
105112
};
106113

107-
export const searchFlashId = async (id, limit = 0) => {
114+
export const searchFlashId = async (id, limit = 0, options = {}) => {
108115
const input = { idScheme: 'nand.flash_id' };
109116
return useEmbeddedParser() ? searchEmbeddedFlashId(id, limit) : request('identifiers/search', {
110117
...langParams(),
111118
query: id,
112119
...input,
113120
...limitParams(limit)
114-
});
121+
}, 'fdnext.result.v1', options);
115122
};
116123

117-
export const summarizeFlashId = async id => summaryText(await decodeFlashId(id));
124+
export const summarizeFlashId = async (id, options = {}) => summaryText(await decodeFlashId(id, options));

0 commit comments

Comments
 (0)