From f5f8b6939322c7b2d9f131b5f12001f1e32b69d0 Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 21:36:19 -0700 Subject: [PATCH 1/9] implement reconnect logic --- computercraft/sigils.lua | 2 + computercraft/sigils/websocket.lua | 68 ++++++++++++++++++++---------- server/package-lock.json | 13 ++++++ server/package.json | 1 + server/src/index.ts | 62 ++++++++++++++++++++++++--- server/src/types/errors.ts | 3 +- server/src/types/messages.ts | 12 +++++- server/src/types/session.ts | 3 +- 8 files changed, 132 insertions(+), 32 deletions(-) diff --git a/computercraft/sigils.lua b/computercraft/sigils.lua index 6b0105c..8794fdc 100644 --- a/computercraft/sigils.lua +++ b/computercraft/sigils.lua @@ -73,6 +73,8 @@ local function init () local wsContext = { wsUrl = config.server or DEFAULT_SERVER_URL, ws = nil, + reconnectToken = nil, + sessionId = string, } parallel.waitForAll( diff --git a/computercraft/sigils/websocket.lua b/computercraft/sigils/websocket.lua index cd87231..80b3405 100644 --- a/computercraft/sigils/websocket.lua +++ b/computercraft/sigils/websocket.lua @@ -24,27 +24,39 @@ local MESSAGE_TYPES = { GroupDel = true, } ----Request a session from the editor session server once ----@param ws Websocket ComputerCraft Websocket handle +---Request a session from the editor session server once, or reconnect if +---there's a reconnect token +---@param wsContext table WebSocket context ---@return table response ConfirmationResponse as a Lua table ----@return string sessionId Session ID requested -local function requestSessionOnce (ws) - local sessionId = Utils.randomString(4) - local req = { - type = 'SessionCreate', - reqId = Utils.randomString(20), - sessionId = sessionId, - } +local function requestSessionOnce (wsContext) + local req + + if wsContext.reconnectToken then + req = { + type = 'SessionRejoin', + reqId = Utils.randomString(20), + ccReconnectToken = reconnectToken, + sessionId = wsContext.sessionId, + } + else + wsContext.sessionId = Utils.randomString(4) + req = { + type = 'SessionCreate', + reqId = Utils.randomString(20), + sessionId = wsContext.sessionId, + } + end + ws.send(textutils.serializeJSON(req)) local res = ws.receive(5) - return textutils.unserializeJSON(res), sessionId + return textutils.unserializeJSON(res) end ---Connect to the editor session server and request a session, retrying if needed ---@param wsContext table Shared WebSocket context ---@param maxAttempts number Max attempts to connect and get a session ----@return boolean ok True if session was acquired, false otherwise +---@return string ccReconnectToken A reconnect token for resuming the session if it breaks local function connectAndRequestSession (wsContext, maxAttempts) local attempts = 1 @@ -66,7 +78,7 @@ local function connectAndRequestSession (wsContext, maxAttempts) end wsContext.ws = ws - local res, sessionId = requestSessionOnce(ws) + local res = requestSessionOnce(wsContext) while res == nil or not res.ok do if attempts > maxAttempts then LOGGER:error( @@ -76,7 +88,7 @@ local function connectAndRequestSession (wsContext, maxAttempts) return false end print('Trying to create session. Attempt', attempts) - res, sessionId = requestSessionOnce(ws) + res = requestSessionOnce(wsContext) attempts = attempts + 1 end @@ -84,9 +96,9 @@ local function connectAndRequestSession (wsContext, maxAttempts) print('Connection to editor server successful!') print('Press E again to end the session.') print('**') - print('** Insert code', sessionId, 'into web editor to edit pipes.') + print('** Insert code', res.sessionId, 'into web editor to edit pipes.') print('**') - return true + return res.ccReconnectToken end ---Queue an OS event for a given message. The event name will always be in the @@ -120,14 +132,16 @@ local function doWebSocket (wsContext) print('Press E to create a factory editing session.') while true do if state == 'WAIT-FOR-USER' then + wsContext.reconnectToken = nil local event, char = os.pullEvent('char') if char == 'e' then state = 'START-CONNECT' end elseif state == 'START-CONNECT' then - local established = connectAndRequestSession(wsContext, 5) - if established then + local reconnectToken = connectAndRequestSession(wsContext, 5) + if reconnectToken then state = 'CONNECTED' + wsContext.reconnectToken = reconnectToken else print() print( @@ -142,10 +156,19 @@ local function doWebSocket (wsContext) local ok, res, isBinary = pcall(function () return wsContext.ws.receive() end) if not ok then print() - print('Lost connection to editor session server.') - print('Press E to try to create a factory editing session again.') - wsContext.ws = nil - state = 'WAIT-FOR-USER' + print('Attempting to reconnect') + local reconnectToken = connectAndRequestSession(wsContext, 5) + + if reconnectToken then + wsContext.reconnectToken = reconnectToken + else + print() + print('Lost connection to editor session server.') + print('Press E to try to create a factory editing session again.') + wsContext.ws = nil + state = 'WAIT-FOR-USER' + end + elseif res ~= nil and not isBinary then queueEventFromMessage(textutils.unserializeJSON(res)) end @@ -158,6 +181,7 @@ local function doWebSocket (wsContext) 'Editor session closed. Press E to create a new editing session ' .. 'or press Q to stop all pipes and quit.' ) + wsContext.reconnectToken = nil wsContext.ws.close() wsContext.ws = nil state = 'WAIT-FOR-USER' diff --git a/server/package-lock.json b/server/package-lock.json index 56d0793..5d18a31 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "dependencies": { + "uuid": "^9.0.1", "ws": "^8.16.0" }, "devDependencies": { @@ -3731,6 +3732,18 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/server/package.json b/server/package.json index 4d3ebf5..76289ce 100644 --- a/server/package.json +++ b/server/package.json @@ -12,6 +12,7 @@ "author": "", "license": "GPL-3.0-or-later", "dependencies": { + "uuid": "^9.0.1", "ws": "^8.16.0" }, "devDependencies": { diff --git a/server/src/index.ts b/server/src/index.ts index 784f4a8..8bc4b3f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,6 +1,7 @@ import { WebSocket, WebSocketServer } from "ws"; -import { FailResponse, IdleTimeout, MessageType, Request, SessionCreateReq, SessionJoinReq, SuccessResponse } from "./types/messages"; +import { FailResponse, IdleTimeout, MessageType, Request, SessionCreateReq, SessionCreateRes, SessionJoinReq, SessionRejoinReq, SuccessResponse } from "./types/messages"; import { Session, SessionId } from "./types/session"; +import { v4 as uuidv4 } from "uuid"; type Role = ('CC' | 'editor'); @@ -39,6 +40,11 @@ wss.on("connection", function connection(ws) { sessionId = joinSession(request as SessionJoinReq, ws); if (sessionId) role = 'editor'; break; + + case "SessionRejoin": + sessionId = rejoinSession(message as SessionRejoinReq, ws); + if (sessionId) role = 'CC'; + break; default: const destination = role === 'CC' ? 'editor' : 'CC'; @@ -88,8 +94,7 @@ wss.on("connection", function connection(ws) { ws.on("close", (data) => { if (role === "CC" && sessionId && sessions[sessionId]) { - sessions[sessionId].editor?.close(); - delete sessions[sessionId]; + sessions[sessionId].computerCraft = undefined; } else if (role === "editor" && sessionId && sessions[sessionId]) { sessions[sessionId].editor = undefined; } @@ -189,13 +194,45 @@ function joinSession({ reqId, sessionId }: SessionJoinReq, editor: WebSocket): S return sessionId; } +function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, computerCraft: WebSocket) { + if (!sessions[reqId]) { + const res: FailResponse = { + type: "ConfirmationResponse", + respondingTo: "SessionRejoin", + ok: false, + error: 'SessionIdNotExist', + message: "Cannot connect to an expired session ID", + reqId: reqId, + }; + computerCraft.send(JSON.stringify(res)); + return; + } + + if (sessions[reqId].ccReconnectToken !== ccReconnectToken) { + const res: FailResponse = { + type: "ConfirmationResponse", + respondingTo: "SessionRejoin", + ok: false, + error: 'BadReconnectToken', + message: "Reconnect token incorrect", + reqId: reqId, + }; + computerCraft.send(JSON.stringify(res)); + return; + } + + sessions[sessionId].computerCraft = computerCraft; + + return sessionId as SessionId; +} + /** * Create a session and add the ComputerCraft computer to it * @param param0 Session create request * @param computerCraft Websocket of the CC computer * @returns Session ID on success, undefined on failure */ -function createSession({ reqId, sessionId }: SessionCreateReq, computerCraft: WebSocket): SessionId { +function createSession({ reqId, sessionId }: SessionCreateReq, computerCraft: WebSocket) { if (sessionId in sessions) { const res: FailResponse = { type: "ConfirmationResponse", @@ -209,11 +246,22 @@ function createSession({ reqId, sessionId }: SessionCreateReq, computerCraft: We return; } + const ccReconnectToken = uuidv4(); + sessions[sessionId] = { id: sessionId, - computerCraft: computerCraft + computerCraft: computerCraft, + ccReconnectToken: ccReconnectToken, }; - sendGenericSuccess("SessionCreate", reqId, computerCraft); - return sessionId; + const res: SessionCreateRes = { + type: "ConfirmationResponse", + respondingTo: "SessionCreate", + ok: true, + reqId: reqId, + ccReconnectToken: ccReconnectToken, + }; + computerCraft.send(JSON.stringify(res)); + + return sessionId as SessionId; } \ No newline at end of file diff --git a/server/src/types/errors.ts b/server/src/types/errors.ts index f14d2a2..eae4111 100644 --- a/server/src/types/errors.ts +++ b/server/src/types/errors.ts @@ -3,5 +3,6 @@ export type ErrorType = ( 'PeerNotConnected' | // CC or editor peer tried to send a message to the other peer when the other peer is not connected 'SessionIdNotExist' | // Editor tried connecting to a nonexistent session ID 'SessionHasEditor' | // Editor tried connecting to a session ID that already has an editor - 'SessionIdTaken' // CC tried requesting a new session using an ID that's already taken + 'SessionIdTaken' | // CC tried requesting a new session using an ID that's already taken + 'BadReconnectToken' // Reconnect token supplied is incorrect ); \ No newline at end of file diff --git a/server/src/types/messages.ts b/server/src/types/messages.ts index 0dd3dbe..88b9444 100644 --- a/server/src/types/messages.ts +++ b/server/src/types/messages.ts @@ -15,7 +15,7 @@ export type MessageType = ( "BatchRequest" | "ConfirmationResponse" | "IdleTimeout" | - "SessionCreate" | "SessionJoin" | + "SessionCreate" | "SessionJoin" | "SessionRejoin" | "FactoryGet" | "FactoryGetResponse" | FactoryUpdateRequest | "CcUpdatedFactory" @@ -84,6 +84,10 @@ export interface SessionCreateReq extends Request { sessionId: SessionId, }; +export interface SessionCreateRes extends ConfirmationResponse { + ccReconnectToken: string, +} + /** * Request for joining an editor session via a session ID * @@ -95,6 +99,12 @@ export interface SessionJoinReq extends Request { sessionId: SessionId, } +export interface SessionRejoinReq extends Request { + type: "SessionRejoin", + ccReconnectToken: string, + sessionId: SessionId, +} + /** * Request for the full factory definition * diff --git a/server/src/types/session.ts b/server/src/types/session.ts index ffbb9a6..d5a3763 100644 --- a/server/src/types/session.ts +++ b/server/src/types/session.ts @@ -4,7 +4,8 @@ export type SessionId = string; export interface Session { id: SessionId, - computerCraft: WebSocket, + computerCraft?: WebSocket, editor?: WebSocket, idleTimerId?: ReturnType, + ccReconnectToken?: string, }; \ No newline at end of file From 65a6d4c9b0cb38f636fc67e44582936aa601a6d6 Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 21:58:54 -0700 Subject: [PATCH 2/9] fix sessionId check --- computercraft/sigils/websocket.lua | 9 ++++++--- server/src/index.ts | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/computercraft/sigils/websocket.lua b/computercraft/sigils/websocket.lua index 80b3405..fc50cc1 100644 --- a/computercraft/sigils/websocket.lua +++ b/computercraft/sigils/websocket.lua @@ -47,9 +47,9 @@ local function requestSessionOnce (wsContext) } end - ws.send(textutils.serializeJSON(req)) + wsContext.ws.send(textutils.serializeJSON(req)) - local res = ws.receive(5) + local res = wsContext.ws.receive(5) return textutils.unserializeJSON(res) end @@ -88,15 +88,18 @@ local function connectAndRequestSession (wsContext, maxAttempts) return false end print('Trying to create session. Attempt', attempts) + os.sleep(3) res = requestSessionOnce(wsContext) attempts = attempts + 1 end + print(textutils.serialiseJSON(res)) + print() print('Connection to editor server successful!') print('Press E again to end the session.') print('**') - print('** Insert code', res.sessionId, 'into web editor to edit pipes.') + print('** Insert code', wsContext.sessionId, 'into web editor to edit pipes.') print('**') return res.ccReconnectToken end diff --git a/server/src/index.ts b/server/src/index.ts index 8bc4b3f..cb8f735 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -195,7 +195,7 @@ function joinSession({ reqId, sessionId }: SessionJoinReq, editor: WebSocket): S } function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, computerCraft: WebSocket) { - if (!sessions[reqId]) { + if (!sessions[sessionId]) { const res: FailResponse = { type: "ConfirmationResponse", respondingTo: "SessionRejoin", @@ -208,7 +208,7 @@ function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, return; } - if (sessions[reqId].ccReconnectToken !== ccReconnectToken) { + if (sessions[sessionId].ccReconnectToken !== ccReconnectToken) { const res: FailResponse = { type: "ConfirmationResponse", respondingTo: "SessionRejoin", From a1f82b3a7ae2f95feae5ec92ef31c6b2458438f3 Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 22:18:28 -0700 Subject: [PATCH 3/9] respond to rejoin request --- server/src/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/index.ts b/server/src/index.ts index cb8f735..bc91c32 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -223,6 +223,15 @@ function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, sessions[sessionId].computerCraft = computerCraft; + const res: SessionCreateRes = { + type: "ConfirmationResponse", + respondingTo: "SessionRejoin", + ok: true, + reqId: reqId, + ccReconnectToken: ccReconnectToken, + }; + computerCraft.send(JSON.stringify(res)); + return sessionId as SessionId; } From 79c006c33a2854ce280723745f6f01c783d6375b Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 22:28:38 -0700 Subject: [PATCH 4/9] supply reconnect token from context --- computercraft/sigils/websocket.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/computercraft/sigils/websocket.lua b/computercraft/sigils/websocket.lua index fc50cc1..331b938 100644 --- a/computercraft/sigils/websocket.lua +++ b/computercraft/sigils/websocket.lua @@ -35,7 +35,7 @@ local function requestSessionOnce (wsContext) req = { type = 'SessionRejoin', reqId = Utils.randomString(20), - ccReconnectToken = reconnectToken, + ccReconnectToken = wsContext.reconnectToken, sessionId = wsContext.sessionId, } else @@ -93,8 +93,6 @@ local function connectAndRequestSession (wsContext, maxAttempts) attempts = attempts + 1 end - print(textutils.serialiseJSON(res)) - print() print('Connection to editor server successful!') print('Press E again to end the session.') @@ -151,6 +149,7 @@ local function doWebSocket (wsContext) 'Press E to try to create a factory editing session again ' .. 'or press Q to stop all pipes and quit.' ) + wsContext.reconnectToken = nil wsContext.ws = nil state = 'WAIT-FOR-USER' end @@ -168,6 +167,7 @@ local function doWebSocket (wsContext) print() print('Lost connection to editor session server.') print('Press E to try to create a factory editing session again.') + wsContext.reconnectToken = nil wsContext.ws = nil state = 'WAIT-FOR-USER' end From eda00fb9699bab463f2cf288fde7188c37ebe62e Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 22:29:19 -0700 Subject: [PATCH 5/9] update wording --- computercraft/sigils/websocket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/computercraft/sigils/websocket.lua b/computercraft/sigils/websocket.lua index 331b938..58e083a 100644 --- a/computercraft/sigils/websocket.lua +++ b/computercraft/sigils/websocket.lua @@ -158,7 +158,7 @@ local function doWebSocket (wsContext) local ok, res, isBinary = pcall(function () return wsContext.ws.receive() end) if not ok then print() - print('Attempting to reconnect') + print('Lost connection to editor session server. Attempting to reconnect.') local reconnectToken = connectAndRequestSession(wsContext, 5) if reconnectToken then From b5d74088224ec60d8d40c3a96d6b5311b1a5a66c Mon Sep 17 00:00:00 2001 From: fechan Date: Sat, 11 May 2024 22:35:57 -0700 Subject: [PATCH 6/9] delete session after timeout reached --- server/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/index.ts b/server/src/index.ts index bc91c32..dfa4297 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -122,6 +122,8 @@ function resetIdleTimer(session: Session) { session.editor.send(JSON.stringify(timeoutMsg)); session.editor.close(); } + + delete sessions[session.id]; }, 10 * 60 * 1000) } From 5cae86eae8c1fef9d9cd8213cc4102e07059d2f8 Mon Sep 17 00:00:00 2001 From: fechan Date: Wed, 15 May 2024 21:38:42 -0700 Subject: [PATCH 7/9] queue messages from editor for later relaying if CC is not connected --- server/src/index.ts | 52 ++++++++++++++++++++++++++++++++----- server/src/types/session.ts | 15 +++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index dfa4297..58bb2d4 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,5 +1,5 @@ import { WebSocket, WebSocketServer } from "ws"; -import { FailResponse, IdleTimeout, MessageType, Request, SessionCreateReq, SessionCreateRes, SessionJoinReq, SessionRejoinReq, SuccessResponse } from "./types/messages"; +import { ConfirmationResponse, FailResponse, IdleTimeout, Message, MessageType, Request, SessionCreateReq, SessionCreateRes, SessionJoinReq, SessionRejoinReq, SuccessResponse } from "./types/messages"; import { Session, SessionId } from "./types/session"; import { v4 as uuidv4 } from "uuid"; @@ -48,7 +48,7 @@ wss.on("connection", function connection(ws) { default: const destination = role === 'CC' ? 'editor' : 'CC'; - if ((role === 'CC' && !session.editor) || (role === 'editor' && !session.computerCraft)) { + if (role === 'CC' && !session.editor) { const res: FailResponse = { type: "ConfirmationResponse", respondingTo: message.type, @@ -58,24 +58,25 @@ wss.on("connection", function connection(ws) { message: `Tried sending a message to ${destination}, but it doesn't exist on this session.` }; ws.send(JSON.stringify(res)); + } else if (role === 'editor' && !session.computerCraft) { + queueRequestForCCForLater(message as Request, session); } else { relayMessage(json, sessionId, destination); } break; } } else if (message.type === "CcUpdatedFactory") { - const destination = role === 'CC' ? 'editor' : 'CC'; - if ((role === 'CC' && !session.editor) || (role === 'editor' && !session.computerCraft)) { + if (!session.editor) { const res: FailResponse = { type: "ConfirmationResponse", respondingTo: message.type, ok: false, error: 'PeerNotConnected', - message: `Tried sending a message to ${destination}, but it doesn't exist on this session.` + message: `Tried sending a message to the editor, but it isn't connected on this session.` }; ws.send(JSON.stringify(res)); } else { - relayMessage(json, sessionId, destination); + relayMessage(json, sessionId, 'editor'); } } } catch (error) { @@ -223,7 +224,8 @@ function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, return; } - sessions[sessionId].computerCraft = computerCraft; + const session = sessions[sessionId]; + session.computerCraft = computerCraft; const res: SessionCreateRes = { type: "ConfirmationResponse", @@ -234,6 +236,14 @@ function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, }; computerCraft.send(JSON.stringify(res)); + if (session.staleOutboxTimerId) { + clearTimeout(session.staleOutboxTimerId); + } + + while (session.editorOutbox.length > 0) { + computerCraft.send(JSON.stringify(session.editorOutbox.pop())); + } + return sessionId as SessionId; } @@ -263,6 +273,7 @@ function createSession({ reqId, sessionId }: SessionCreateReq, computerCraft: We id: sessionId, computerCraft: computerCraft, ccReconnectToken: ccReconnectToken, + editorOutbox: [], }; const res: SessionCreateRes = { @@ -275,4 +286,31 @@ function createSession({ reqId, sessionId }: SessionCreateReq, computerCraft: We computerCraft.send(JSON.stringify(res)); return sessionId as SessionId; +} + +/** + * Queue a request to be sent to CC for after CC reconnects to the session. + * If there's not a timer already, this starts a timer which clears after + * CC reconnects, otherwise it sends sends failure ConfirmationResponses back + * to the editor and closes the editor's websocket + * @param message Message to queue for later. + */ +function queueRequestForCCForLater(request: Request, session: Session) { + session.editorOutbox.push(request); + + if (!session.staleOutboxTimerId) { + session.staleOutboxTimerId = setTimeout(() => { + const failResponse: FailResponse = { + type: "ConfirmationResponse", + respondingTo: request.type, + reqId: request.reqId, + ok: false, + error: 'PeerNotConnected', + message: 'Tried sending a message to ComputerCraft, but it did not connect within 10 seconds.' + }; + session.editor.send(JSON.stringify(failResponse)); + + session.editor.close(); + }, 10 * 1000); + } } \ No newline at end of file diff --git a/server/src/types/session.ts b/server/src/types/session.ts index d5a3763..2bab855 100644 --- a/server/src/types/session.ts +++ b/server/src/types/session.ts @@ -1,11 +1,26 @@ import { WebSocket } from "ws"; +import { Message } from "./messages"; export type SessionId = string; export interface Session { id: SessionId, + computerCraft?: WebSocket, editor?: WebSocket, + idleTimerId?: ReturnType, ccReconnectToken?: string, + + /** + * Messages from editor pending relay to CC. Used when CC disconnects + * unexpectedly. + */ + editorOutbox: Message[], + /** + * If the editorOutbox gets populated after being empty, a timer is started + * which disconnects the editor if CC hasn't reconnected by the time the + * timer is reached + */ + staleOutboxTimerId?: ReturnType, }; \ No newline at end of file From 591fb66c30ca3e1999804c79ec23bf3d3512c941 Mon Sep 17 00:00:00 2001 From: fechan Date: Wed, 15 May 2024 22:00:51 -0700 Subject: [PATCH 8/9] reset outbox timer id after it is cleared --- server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/index.ts b/server/src/index.ts index 58bb2d4..a2f2b79 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -238,6 +238,7 @@ function rejoinSession({ reqId, sessionId, ccReconnectToken }: SessionRejoinReq, if (session.staleOutboxTimerId) { clearTimeout(session.staleOutboxTimerId); + session.staleOutboxTimerId = undefined; } while (session.editorOutbox.length > 0) { From 57ba430c276f805660dbf266642d453d29c7ad8c Mon Sep 17 00:00:00 2001 From: fechan Date: Wed, 15 May 2024 22:04:52 -0700 Subject: [PATCH 9/9] clear outbox if cc doesn't reconnect after 10 seconds --- server/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/index.ts b/server/src/index.ts index a2f2b79..c831081 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -311,6 +311,8 @@ function queueRequestForCCForLater(request: Request, session: Session) { }; session.editor.send(JSON.stringify(failResponse)); + session.editorOutbox = []; + session.editor.close(); }, 10 * 1000); }