94 lines
2.5 KiB
Plaintext
94 lines
2.5 KiB
Plaintext
local enum State
|
|
"reset"
|
|
"error"
|
|
"ok"
|
|
"viewer_connected"
|
|
end
|
|
|
|
local BAD_STATES <const> : {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 } |