Skip to content

Commit 456066f

Browse files
πŸ› fix(webview): prevent message dedupe drops on page re-entry
1 parent 07f9f8a commit 456066f

File tree

7 files changed

+44
-34
lines changed

7 files changed

+44
-34
lines changed

β€Žsrc/services/webview/handlers/settings-view-message-handler.tsβ€Ž

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,15 @@ export class SettingsViewMessageHandler {
118118
return;
119119
}
120120

121+
const dataRequestId =
122+
message?.data && typeof message.data === "object"
123+
? message.data.requestId
124+
: undefined;
121125
const messageId =
122-
message.messageId || `${message.command}_${JSON.stringify(message.data)}`;
126+
message.messageId ||
127+
message.requestId ||
128+
dataRequestId ||
129+
`${message.command}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
123130

124131
try {
125132
await this._sessionManager.withIdempotency(messageId, async () => {

β€Žwebview-ui/src/components/settings/ModelCatalogViewer.tsxβ€Ž

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ export const ModelCatalogViewer: React.FC = () => {
2424
const [catalogSourceFilter, setCatalogSourceFilter] = useState("all");
2525

2626
useEffect(() => {
27-
postMessage(
28-
UIRequest.FeaturesGetModelCatalog,
29-
{},
30-
{ allowDuplicate: true },
31-
);
32-
3327
const handleMessage = (event: MessageEvent) => {
3428
const message = event.data;
3529
if (message.command === ExtensionResponse.FeaturesModelCatalogSynced) {
@@ -60,6 +54,11 @@ export const ModelCatalogViewer: React.FC = () => {
6054
};
6155

6256
window.addEventListener("message", handleMessage);
57+
postMessage(
58+
UIRequest.FeaturesGetModelCatalog,
59+
{},
60+
{ allowDuplicate: true },
61+
);
6362
return () => window.removeEventListener("message", handleMessage);
6463
}, [t]);
6564

β€Žwebview-ui/src/components/welcome/SetupWizard.tsxβ€Ž

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -211,16 +211,14 @@ const SetupWizard = ({
211211
type: selectedProviderMeta?.type,
212212
};
213213

214-
postMessage("upsertApiConfiguration", {
215-
text: currentApiConfigName,
216-
apiConfiguration: finalConfig,
217-
});
214+
let finished = false;
218215

219216
// 监听响应幢倄理
220217
const handleMessage = (event: MessageEvent) => {
221218
const message = event.data;
222219
if (message.command === "apiConfiguration.upserted") {
223220
window.removeEventListener("message", handleMessage);
221+
finished = true;
224222

225223
if (message.data?.success) {
226224
console.log("[SetupWizard] API configuration upserted successfully");
@@ -250,13 +248,19 @@ const SetupWizard = ({
250248

251249
window.addEventListener("message", handleMessage);
252250

251+
postMessage("upsertApiConfiguration", {
252+
text: currentApiConfigName,
253+
apiConfiguration: finalConfig,
254+
});
255+
253256
// 袅既倄理
254257
setTimeout(() => {
255-
window.removeEventListener("message", handleMessage);
256-
if (isLoading) {
257-
setError(t("errors.requestTimeout"));
258-
setIsLoading(false);
258+
if (finished) {
259+
return;
259260
}
261+
window.removeEventListener("message", handleMessage);
262+
setError(t("errors.requestTimeout"));
263+
setIsLoading(false);
260264
}, 10000);
261265
}, [
262266
apiConfiguration,
@@ -265,7 +269,6 @@ const SetupWizard = ({
265269
selectedProviderId,
266270
selectedProviderMeta,
267271
isFirstInstall,
268-
isLoading,
269272
t,
270273
]);
271274

β€Žwebview-ui/src/hooks/use-vscode-message.tsβ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { useEffect } from "react";
1+
import { useLayoutEffect } from "react";
22

33
export function useVSCodeMessage<T = unknown>(
44
command: string,
55
handler: (payload: T) => void,
66
) {
7-
useEffect(() => {
7+
// Use layout effect so listeners are ready before mount-time fetch effects.
8+
useLayoutEffect(() => {
89
const messageHandler = (event: MessageEvent) => {
910
if (event.data.command === command) {
1011
handler(event.data as T);

β€Žwebview-ui/src/pages/settings/FeaturesSettings.tsxβ€Ž

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ export const FeaturesSettings: React.FC = () => {
8888
useEffect(() => {
8989
if (hasLoadedRef.current) return;
9090
hasLoadedRef.current = true;
91-
postMessage(UIRequest.FeaturesLoadSettings);
92-
9391
const handleMessage = (event: MessageEvent) => {
9492
const message = event.data;
9593
if (
@@ -125,11 +123,12 @@ export const FeaturesSettings: React.FC = () => {
125123
};
126124

127125
window.addEventListener("message", handleMessage);
126+
postMessage(UIRequest.FeaturesLoadSettings);
128127
return () => {
129128
window.removeEventListener("message", handleMessage);
130129
hasLoadedRef.current = false;
131130
};
132-
}, []);
131+
}, [t]);
133132

134133
const handleFeatureToggle = (
135134
feature: keyof typeof features,

β€Žwebview-ui/src/pages/usage-page.tsxβ€Ž

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ export const UsagePage: React.FC = () => {
2121
const [detailedStats, setDetailedStats] = useState<DailyUsageStats[]>([]);
2222

2323
const fetchUsageStats = () => {
24-
postMessage(UIRequest.UsageGetStats, {});
24+
// Usage ι‘΅ι’ιœ€θ¦ζ”―ζŒι‡ε€θΏ›ε…₯ζ—Άι‡ζ–°ζ‹‰ε–οΌŒιΏε…θ’«ζΆˆζ―εŽ»ι‡ζ‹¦ζˆͺ
25+
postMessage(UIRequest.UsageGetStats, {}, { allowDuplicate: true });
2526
};
2627

27-
useEffect(() => {
28-
fetchUsageStats();
29-
}, []);
30-
3128
useEvent("message", (event: MessageEvent) => {
3229
const message = event.data;
3330
if (message.command === ExtensionResponse.UsageStatsLoaded) {
@@ -37,8 +34,12 @@ export const UsagePage: React.FC = () => {
3734
}
3835
});
3936

37+
useEffect(() => {
38+
fetchUsageStats();
39+
}, []);
40+
4041
const handleReset = () => {
41-
postMessage(UIRequest.UsageResetStats, {});
42+
postMessage(UIRequest.UsageResetStats, {}, { allowDuplicate: true });
4243
};
4344

4445
// Prepare data for lists

β€Žwebview-ui/src/utils/vscode.tsβ€Ž

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect } from "react";
1+
import { useCallback, useLayoutEffect } from "react";
22
import { ExtensionResponse, UIRequest } from "@shared/types/messages";
33

44
declare const acquireVsCodeApi: () => {
@@ -47,12 +47,11 @@ export function postMessage(
4747
) {
4848
const now = Date.now();
4949
const { allowDuplicate = false, requestId } = options ?? {};
50-
const providedMessageId = options?.messageId;
50+
// Always provide a unique messageId so backend idempotency won't treat
51+
// independent requests as duplicates across page re-entry/navigation.
5152
const messageId =
52-
providedMessageId ??
53-
(allowDuplicate
54-
? `${command}-${now}-${Math.random().toString(16).slice(2)}`
55-
: undefined);
53+
options?.messageId ??
54+
`${command}-${now}-${Math.random().toString(16).slice(2)}`;
5655
if (
5756
!allowDuplicate &&
5857
lastMessage &&
@@ -95,7 +94,8 @@ export function useMessageHandler(
9594
[messageCallback],
9695
);
9796

98-
useEffect(() => {
97+
// Register as early as possible to avoid missing fast extension responses.
98+
useLayoutEffect(() => {
9999
window.addEventListener("message", memoizedCallback);
100100
return () => {
101101
window.removeEventListener("message", memoizedCallback);

0 commit comments

Comments
Β (0)