Skip to content

Commit 382fa0e

Browse files
fix(web): device Reconnect starts one stream instead of two
Reconnect refreshed hub access and also restarted the client by hand. The refresh produces a new access value that re-runs the effect, so the manual restart was torn down at once and the helper saw two connection attempts per press. Restart the client only; an expired ticket still refreshes access through the unauthorized path. Co-Authored-By: Claude Fable 5 <[email protected]>
1 parent 9f73ca3 commit 382fa0e

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

‎apps/web/src/components/device/DeviceStreamView.test.tsx‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1-
import { act } from "react";
1+
import { act, useSyncExternalStore } from "react";
22
import { create, type ReactTestRenderer } from "react-test-renderer";
33
import { EnvironmentId } from "@t3tools/contracts";
4-
import { afterEach, expect, it, vi } from "vite-plus/test";
4+
import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
55

6+
// Like the real atom, a refresh yields a new access object and re-renders subscribers.
7+
const accessStore = {
8+
value: { httpBase: "http://test", wsBase: "ws://test", query: {}, credentials: true },
9+
listeners: new Set<() => void>(),
10+
refresh() {
11+
accessStore.value = { ...accessStore.value };
12+
for (const listener of accessStore.listeners) listener();
13+
},
14+
subscribe(listener: () => void) {
15+
accessStore.listeners.add(listener);
16+
return () => accessStore.listeners.delete(listener);
17+
},
18+
};
619
vi.mock("~/state/device", () => ({
7-
useDeviceHubAccess: () => access,
8-
refreshDeviceHubAccess: vi.fn(),
20+
useDeviceHubAccess: () => useSyncExternalStore(accessStore.subscribe, () => accessStore.value),
21+
refreshDeviceHubAccess: () => accessStore.refresh(),
922
}));
10-
const access = { httpBase: "http://test", wsBase: "ws://test", query: {}, credentials: true };
1123
import { DeviceStreamView } from "./DeviceStreamView";
1224

1325
class Image extends EventTarget {
@@ -19,6 +31,10 @@ class Image extends EventTarget {
1931
}
2032
}
2133
let renderer: ReactTestRenderer | undefined;
34+
let primes = 0;
35+
beforeEach(() => {
36+
primes = 0;
37+
});
2238
afterEach(async () => {
2339
await act(async () => renderer?.unmount());
2440
renderer = undefined;
@@ -29,7 +45,10 @@ afterEach(async () => {
2945
async function setup() {
3046
vi.useFakeTimers();
3147
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
32-
vi.stubGlobal("fetch", () => Promise.resolve(new Response("prime")));
48+
vi.stubGlobal("fetch", () => {
49+
primes++;
50+
return Promise.resolve(new Response("prime"));
51+
});
3352
vi.stubGlobal(
3453
"WebSocket",
3554
class {
@@ -106,3 +125,13 @@ it("offers Reconnect after the shared timeout and receives a frame after retry w
106125
expect(renderer!.root.findAllByType("button")).toHaveLength(0);
107126
expect(vi.getTimerCount()).toBe(0);
108127
});
128+
129+
it("starts exactly one new stream per Reconnect press", async () => {
130+
await setup();
131+
await act(async () => {
132+
await vi.advanceTimersByTimeAsync(15_000);
133+
});
134+
expect(primes).toBe(1);
135+
await act(async () => renderer!.root.findByType("button").props.onClick());
136+
expect(primes).toBe(2);
137+
});

‎apps/web/src/components/device/DeviceStreamView.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ export function DeviceStreamView(props: {
336336
size="sm"
337337
variant="outline"
338338
onClick={() => {
339-
refreshDeviceHubAccess(props.environmentId);
339+
// An expired ticket surfaces as unauthorized on restart and
340+
// refreshes access through the effect; no need to mint one here.
340341
clientRef.current?.stop();
341342
clientRef.current?.start();
342343
}}

0 commit comments

Comments
 (0)