local enum State "reset" "error" "ok" "viewer_connected" end local BAD_STATES : {State: boolean} = { ["reset"] = true, ["error"] = true, } local type StateCallback = function(new_state: State) local record Socket state: State is_bad_state: function(self: Socket): boolean _set_state: function(self: Socket, state: State) on_state_change: function(self: Socket, cb: StateCallback) send: function(self: Socket, message: string) reconnect: function(self: Socket) close: function(self: Socket) signal_viewer_connect: function(self: Socket, connected: boolean) _endpoint: string _headers: {string: string} _callback: StateCallback _ws: http.Websocket end local impl: table = {} impl.is_bad_state = function(self: Socket): boolean return BAD_STATES[self.state] ~= nil end impl._set_state = function(self: Socket, state: State) self.state = state self._callback(state) end impl.on_state_change = function(self: Socket, cb: StateCallback) self._callback = cb end impl.send = function(self: Socket, message: string) -- "message" needs to be valid JSON -- otherwise the server will not accept it if self:is_bad_state() then return end local r = { pcall(self._ws.send, message) } if r[1] == false then if (r[2] as string):sub(-11) == "closed file" then self:_set_state("reset") elseif (r[2] as string):sub(-9) == "too large" then -- TODO handle -- the connection stays open though end end end impl.reconnect = function(self: Socket) local r = http.websocket(self._endpoint, self._headers) if r ~= false then self._ws = r as http.Websocket self:_set_state("ok") else self:_set_state("error") end end impl.close = function(self: Socket) if self:is_bad_state() then return end self._ws.close() end impl.signal_viewer_connect = function(self: Socket, connected: boolean) if self:is_bad_state() then return end --how? local new_state: State = connected and "viewer_connected" or "ok" self:_set_state(new_state) end local function new(endpoint: string, headers: {string: string}): Socket return setmetatable({ state = "reset", _endpoint = endpoint, _headers = headers, _callback = function(_: State) end, _ws = nil, }, { __index = impl }) end return { new = new, State = State, StateCallback = StateCallback, Socket = Socket }