Skip to content

Commit 137def6

Browse files
refactor: update WebSocket client to use a function for token retrieval
- Changed the BotsChatWSClient to accept a function that retrieves the current access token instead of passing the token directly. - Added logic to refresh the access token on authentication failure during WebSocket reconnections. - Updated related API functions and App component to accommodate these changes. - Cleaned up .gitignore by removing unnecessary entries.
1 parent 134f7bb commit 137def6

4 files changed

Lines changed: 27 additions & 10 deletions

File tree

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,4 @@ Thumbs.db
3737

3838
# AI / Cursor generated plan files
3939
*.plan.md
40-
41-
# VS Code / Cursor workspace files (contain local paths)
4240
*.code-workspace

packages/web/src/App.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,15 +698,14 @@ export default function App() {
698698
useEffect(() => {
699699
if (!state.user) return;
700700

701-
const token = getToken();
702-
if (!token) return;
701+
if (!getToken()) return;
703702

704703
const sessionId = randomUUID();
705704
dlog.info("WS", `Connecting WebSocket (session=${sessionId.slice(0, 8)}...)`);
706705
const client = new BotsChatWSClient({
707706
userId: state.user.id,
708707
sessionId,
709-
token,
708+
getToken,
710709
onMessage: (msg) => {
711710
handleWSMessageRef.current(msg);
712711
},

packages/web/src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function getRefreshToken(): string | null {
2828
}
2929

3030
/** Try to refresh the access token using the refresh token. */
31-
async function tryRefreshAccessToken(): Promise<boolean> {
31+
export async function tryRefreshAccessToken(): Promise<boolean> {
3232
if (!_refreshToken) return false;
3333
try {
3434
const res = await fetch(`${API_BASE}/auth/refresh`, {

packages/web/src/ws.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { dlog } from "./debug-log";
44
import { E2eService } from "./e2e";
5+
import { getToken, tryRefreshAccessToken } from "./api";
56

67
export type WSMessage = {
78
type: string;
@@ -11,7 +12,8 @@ export type WSMessage = {
1112
export type WSClientOptions = {
1213
userId: string;
1314
sessionId: string;
14-
token: string;
15+
/** Function that returns the current access token (reads from localStorage). */
16+
getToken: () => string | null;
1517
onMessage: (msg: WSMessage) => void;
1618
onStatusChange: (connected: boolean) => void;
1719
};
@@ -41,8 +43,14 @@ export class BotsChatWSClient {
4143

4244
this.ws.onopen = () => {
4345
dlog.info("WS", "Socket opened, sending auth");
46+
const token = this.opts.getToken();
47+
if (!token) {
48+
dlog.error("WS", "No access token available, closing");
49+
this.ws?.close();
50+
return;
51+
}
4452
// Authenticate with the ConnectionDO
45-
this.ws!.send(JSON.stringify({ type: "auth", token: this.opts.token }));
53+
this.ws!.send(JSON.stringify({ type: "auth", token }));
4654
};
4755

4856
this.ws.onmessage = async (evt) => {
@@ -124,9 +132,21 @@ export class BotsChatWSClient {
124132
this._connected = false;
125133
this.opts.onStatusChange(false);
126134
if (!this.intentionalClose) {
127-
dlog.warn("WS", `Connection closed (code=${evt.code}), reconnecting in ${this.backoffMs}ms`);
128-
this.reconnectTimer = setTimeout(() => {
135+
const isAuthFail = evt.code === 4001;
136+
dlog.warn("WS", `Connection closed (code=${evt.code}), reconnecting in ${this.backoffMs}ms${isAuthFail ? " (will refresh token)" : ""}`);
137+
this.reconnectTimer = setTimeout(async () => {
129138
this.backoffMs = Math.min(this.backoffMs * 2, 30000);
139+
// On auth failure (4001), refresh the access token before reconnecting
140+
if (isAuthFail) {
141+
dlog.info("WS", "Refreshing access token before reconnect...");
142+
const ok = await tryRefreshAccessToken();
143+
if (ok) {
144+
dlog.info("WS", "Token refreshed, reconnecting");
145+
this.backoffMs = 1000; // reset backoff on successful refresh
146+
} else {
147+
dlog.error("WS", "Token refresh failed — will retry on next cycle");
148+
}
149+
}
130150
this.connect();
131151
}, this.backoffMs);
132152
} else {

0 commit comments

Comments
 (0)