Compare commits

...

3 Commits

7 changed files with 612 additions and 321 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Alessandro Copyright (c) 2020 Alessandro
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -30,6 +30,7 @@ interface Client {
interface PollingClient extends Client { interface PollingClient extends Client {
lastPing: number, lastPing: number,
queue: Array<any>, queue: Array<any>,
token: string,
} }
interface Channels { interface Channels {
@ -428,6 +429,7 @@ pollingRouter.get("/connect", (req, res, next) => {
client.client = req; client.client = req;
client.queue = []; client.queue = [];
client.token = sessionToken;
client.send = function (data: string | object) { client.send = function (data: string | object) {
if (typeof data === "string") data = JSON.parse(data); if (typeof data === "string") data = JSON.parse(data);
@ -457,11 +459,6 @@ pollingRouter.use("*", function (req, res, next) {
}) })
} }
let client = clients[pollingTokens[sessionToken]] as PollingClient;
client.lastPing = Date.now();
clients[pollingTokens[sessionToken]] = client; // you're never 100% sure
next(); next();
}) })
@ -547,6 +544,7 @@ pollingRouter.post("/update", (req, res, next) => {
queue: client.queue, queue: client.queue,
}); });
client.lastPing = Date.now();
client.queue = []; client.queue = [];
}) })
@ -577,7 +575,10 @@ app.listen(config.port, () => {
let v = clients[id] as PollingClient; let v = clients[id] as PollingClient;
if ((Date.now() - v.lastPing) > 60000) { if ((Date.now() - v.lastPing) > 60000) {
console.log("[POL]", `Client connected: ${id}`);
let clientToken = (clients[id] as PollingClient).token;
delete clients[id]; delete clients[id];
delete pollingTokens[clientToken]
} }
}) })
}, 60000) }, 60000)

View File

@ -1,95 +0,0 @@
local component = require("component")
local modem = component.modem
if not modem then
error("Missing network card", 2)
end
local serial = require("serialization")
local event = require("event")
local soqet = {
uuid = nil,
running = false,
}
local lastid
local function open()
modem.open(1010)
end
local function receive()
if not modem.isOpen(1010) then
open()
end
while true do
local ev = {event.pull(nil, "modem_message")}
local ch = ev[4]
local message = serial.unserialize(ev[6])
--print(serial.serialize(message))
soqet.uuid = message.uuid
if lastid ~= message.mid then
if message.message and message.channel and message.meta then
lastid = message.mid
return message.channel, message.message, message.meta
end
end
end
end
local function send(data)
if not modem.isOpen(1010) then
open()
end
modem.broadcast(1010, serial.serialize(data))
end
function soqet.open(channel)
send({
type = "open",
channel = channel,
})
end
function soqet.close(channel)
send({
type = "close",
channel = channel,
})
end
function soqet.auth(token)
send({
type = "auth",
token = token,
})
end
function soqet.send(channel, message, meta)
send({
type = "send",
channel = channel,
message = message,
meta = meta,
})
end
function soqet.listen()
open()
soqet.running = true
while soqet.running do
event.push("soqet_message", receive())
end
end
function soqet.receive()
open()
return receive()
end
function soqet.unlisten()
soqet.running = false
end
return soqet

View File

@ -1,46 +0,0 @@
local modem = peripheral.find("modem")
modem.open(1010)
local soqet = require("soqet")
local function action(data)
if data.type == "open" and data.channel then
print("Opening " .. data.channel)
soqet.open(data.channel)
elseif data.type == "close" and data.channel then
print("Closing " .. data.channel)
soqet.close(data.channel)
elseif data.type == "send" then
print("Sending message...")
soqet.send(data.channel, data.message, data.meta)
elseif data.type == "auth" then
print("Authenticating...")
soqet.auth(data.token)
end
end
local function main()
while true do
local ev = {os.pullEvent()}
--for k,v in pairs(ev) do
--print(k, v)
--end
if ev[1] == "soqet_message" then
modem.transmit(1010, 0, textutils.serialise({
channel = ev[2],
message = ev[3],
meta = ev[4],
uuid = soqet.uuid,
mid = math.random(0,99999)
}))
elseif ev[1] == "modem_message" and ev[3] == 1010 then
local data = textutils.unserialize(ev[5])
action(data)
end
end
end
parallel.waitForAny(soqet.listen, main)

290
oc_soqet.lua Normal file
View File

@ -0,0 +1,290 @@
--[[
-- OC_Soqet.lua --
https://github.com/Ale32bit/Soqet/
MIT License
Copyright (c) 2020 Alessandro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
--[[
-- json.lua --
https://github.com/rxi/json.lua
Copyright (c) 2019 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
local http = require("internet")
local fs = require("filesystem")
if not http then
error("Missing internet card", 2)
end
local function _request(...)
local handle = http.request(...)
handle.finishConnect()
local content = ""
for chunk in handle do
content = content .. chunk
end
return content
end
local function get(url)
return _request(url)
end
local function post(url, data, headers)
return _request(url, data, headers, "POST")
end
if not fs.exists("/lib/json.lua") then
local con = get("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua")
local f = io.open("/lib/json.lua", "w")
f:write(con)
f:close()
end
local function expect(index, value, ...)
local types = {...}
local valueType = type(value)
local valid = false
for _, v in ipairs(types) do
if valueType == v then
valid = true
break
end
end
if not valid then
error(("bad argument #%d (expected %s, got %s)"):format(index, table.concat(types, ", "), valueType), 3)
end
return value
end
local soqet = {
ENDPOINT = "soqet.alexdevs.pw",
ssl = false,
json = require("json"),
credits = "OC_Soqet.lua by AlexDevs"
}
local function postJson(url, data)
return _request(
url,
soqet.json.encode(data),
{
["Content-Type"] = "application/json"
}
)
end
function soqet.new()
error("WebSocket client is not supported. Use long polling instead.", 2)
end
function soqet.poll()
local client = {
channels = {},
uuid = nil,
sessionId = math.random(0xffffff),
sessionToken = nil,
ssl = soqet.ssl,
connected = false,
listening = true,
updateInterval = 1
}
if ssl then
client.ENDPOINT = "https://" .. soqet.ENDPOINT
else
client.ENDPOINT = "http://" .. soqet.ENDPOINT
end
local function send(path, body)
return postJson(client.ENDPOINT .. "/api/" .. path, body)
end
local function rawreceive()
if not client.connected then
client.connect()
end
while true do
local h =
send(
"update",
{
token = client.sessionToken
}
)
local data = soqet.json.decode(h)
local queue = {}
for i, v in ipairs(data.queue) do
if v.type == "message" then
table.insert(
queue,
{
channel = v.channel,
message = v.message,
meta = v.meta
}
)
end
end
if #queue > 0 then
return queue
else
os.sleep(client.updateInterval)
end
end
end
function client.connect(token)
if nil and type(token) ~= "string" then
error("bad argument #1", 2)
end
local h = get(client.ENDPOINT .. "/api/connect?token=" .. (token or ""))
local data = soqet.json.decode(h)
client.uuid = data.uuid
client.sessionToken = data.token
client.motd = data.motd
client.connected = true
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end
function client.open(channel)
expect(1, channel, "string", "number")
client.channels[#client.channels + 1] = channel
send(
"open",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.close(channel)
expect(1, channel, "string", "number")
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
send(
"close",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
meta = meta or {}
meta.library = meta.library or soqet.credits
send(
"send",
{
token = client.sessionToken,
channel = channel,
message = message,
meta = meta
}
)
end
function client.receive()
return rawreceive()
end
function client.listen()
client.listening = true
while client.listening do
local queue = rawreceive()
for i, v in ipairs(queue) do
computer.pushSignal("soqet_message", v.channel, v.message, v.meta, client.sessionId)
end
sleep(client.updateInterval)
end
end
function client.unlisten()
client.listening = false
return true
end
return client
end
return soqet

View File

@ -297,7 +297,7 @@
<code>token</code> field.</p> <code>token</code> field.</p>
<p>Field <code>motd</code> is also supplied upon connection.</p> <p>Field <code>motd</code> is also supplied upon connection.</p>
<p><b>Once connected you need to request at least once every 60 seconds to keep the session token alive!</b> <p><b>Once connected you need to request <code>/api/update</code> at least once every 60 seconds to keep the session token alive!</b>
</p> </p>
</div> </div>
@ -487,7 +487,7 @@
<tr> <tr>
<td>motd</td> <td>motd</td>
<td>string</td> <td>string</td>
<td><i>Any message the </i></td> <td><i>Inspiring quotes to help the developer and the user get over problems and easily achieve life goals.</i></td>
</tr> </tr>
</table> </table>

475
soqet.lua
View File

@ -4,7 +4,7 @@ https://github.com/Ale32bit/Soqet/
MIT License MIT License
Copyright (c) 2019 Alessandro Copyright (c) 2020 Alessandro
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -23,7 +23,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]] --
--[[ --[[
-- json.lua -- -- json.lua --
@ -50,220 +50,361 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]] --
local h = http.get("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua") local expect = dofile("rom/modules/main/cc/expect.lua").expect
local f = fs.open("json.lua", "w")
f.write(h.readAll())
f.close()
h.close()
local json = require("json")
local soqet = { local soqet = {
ENDPOINT = "wss://soqet.alexdevs.pw", ENDPOINT = "soqet.alexdevs.pw",
channels = {}, ssl = true,
socket = nil, json = json,
running = false, credits = "Soqet.lua v2 by AlexDevs"
uuid = nil,
sessionId = nil,
} }
local function send(data) if not soqet.json then
if not soqet.socket then if not fs.exists("json.lua") then
soqet.connect() local h = http.get("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua")
local f = fs.open("json.lua", "w")
f.write(h.readAll())
f.close()
h.close()
end end
return soqet.socket.send(json.encode(data)) soqet.json = require("json")
end end
local function receive() function soqet.new()
if not soqet.socket then if not http then
soqet.connect() return false, "HTTP is not enabled!"
end end
while true do if not http.websocket then
local data = soqet.socket.receive() return false, "HTTP WebSocket feature is not enabled!"
end
data = json.decode(data) local client = {
soqet.uuid = data.uuid channels = {},
if data.type == "message" then uuid = nil,
local message = data.message socket = nil,
local channel = data.channel sessionId = math.random(0xffffff),
local meta = data.meta ssl = soqet.ssl
}
return channel, message, meta local function rawsend(data)
elseif data.type == "ping" then if not client.socket then
send({ return false
type = "ping", end
id = 5,
}) client.socket.send(soqet.json.encode(data))
return true
end
local function rawreceive()
if not client.socket then
client.connect()
end
while true do
local data = client.socket.receive()
data = soqet.json.decode(data)
client.uuid = data.uuid
if data.type == "ping" then
rawsend(
{
type = "ping",
id = 99
}
)
elseif data.type == "motd" then
client.motd = data.motd
elseif data.type == "message" then
return data.channel, data.message, data.meta
end
end end
end end
end
function soqet.connect() if ssl then
assert(http.websocket, "WebSocket not enabled or not compatible with this ComputerCraft version.") client.ENDPOINT = "wss://" .. soqet.ENDPOINT .. "/" .. client.sessionId
soqet.sessionId = tostring(math.random(0xffffff)) else
local socket, err = http.websocket(soqet.ENDPOINT .. "/" .. soqet.sessionId) client.ENDPOINT = "ws://" .. soqet.ENDPOINT .. "/" .. client.sessionId
if not socket then
error(err, 1);
end
soqet.socket = socket;
end
function soqet.open(channel)
send({
type = "open",
channel = channel,
id = 2,
})
end
function soqet.close(channel)
send({
type = "close",
channel = channel,
id = 3,
})
end
function soqet.auth(token)
send({
type = "auth",
token = token,
id = 4,
})
end
function soqet.send(channel, message, meta)
send({
type = "send",
channel = channel,
message = message,
meta = meta or {},
id = 1,
})
end
function soqet.receive()
return receive()
end
function soqet.listen()
soqet.running = true
while soqet.running do
local channel, message, meta = receive()
os.queueEvent("soqet_message", channel, message, meta)
end
end
function soqet.unlisten()
soqet.running = false
end
soqet.polling = {
host = "https://soqet.alexdevs.pw",
token = nil,
uuid = nil,
motd = "soqet",
connected = false,
};
function soqet.polling.connect(token)
local h, err = http.get(soqet.polling.host .. "/api/connect?token=" .. textutils.urlEncode(token));
if not h then
return false, err
end end
local result = json.decode(h.readAll()) function client.connect()
if client.socket then
pcall(client.socket.close)
end
if not result.ok then local socket, err = http.websocket(client.ENDPOINT)
return false, result.error if not socket then
return false, err
end
client.socket = socket
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end end
soqet.polling.token = result.token function client.open(channel)
soqet.polling.motd = result.motd expect(1, channel, "string", "number")
soqet.polling.connected = true
return true client.channels[#client.channels + 1] = channel
return rawsend(
{
type = "open",
channel = channel
}
)
end
function client.close(channel)
expect(1, channel, "string", "number")
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
return rawsend(
{
type = "close",
channel = channel
}
)
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
meta = meta or {}
meta.library = meta.library or soqet.credits
return rawsend(
{
type = "send",
channel = channel,
message = message,
meta = meta
}
)
end
function client.auth(token)
expect(1, token, "string")
return rawsend(
{
type = "auth",
token = token
}
)
end
function client.receive()
return rawreceive()
end
function client.listen()
client.listening = true
while client.listening do
local channel, message, meta = rawreceive()
os.queueEvent("soqet_message", channel, message, meta, client.sessionId)
end
return true
end
function client.unlisten()
client.listening = false
return true
end
return client, client.sessionId
end end
function soqet.polling.update() function soqet.poll(token)
local h, err = http.post(soqet.polling.host .. "/api/update", textutils.serialiseJSON({ local client = {
token = soqet.polling.token, channels = {},
})) uuid = nil,
sessionId = math.random(0xffffff),
sessionToken = nil,
ssl = soqet.ssl,
connected = false,
listening = true,
updateInterval = 1
}
if not h then if ssl then
return false, err client.ENDPOINT = "https://" .. soqet.ENDPOINT
else
client.ENDPOINT = "http://" .. soqet.ENDPOINT
end end
local result = json.decode(h.readAll()); local function postJson(path, body)
return http.post(
if not result.ok then client.ENDPOINT .. "/api/" .. path,
return false, result.err soqet.json.encode(body),
{
["Content-Type"] = "application/json"
}
)
end end
return result.queue local function rawreceive()
end if not client.connected then
client.connect()
end
while true do
local h, err =
postJson(
"update",
{
token = client.sessionToken
}
)
function soqet.polling.open(channel) if not h then
local h, err = http.post(soqet.polling.host .. "/api/open", textutils.serialiseJSON({ error(err)
token = soqet.polling.token, end
channel = channel,
}))
if not h then local data = soqet.json.decode(h.readAll())
return false, err h.close()
local queue = {}
for i, v in ipairs(data.queue) do
if v.type == "message" then
table.insert(
queue,
{
channel = v.channel,
message = v.message,
meta = v.meta
}
)
end
end
if #queue > 0 then
return queue
else
sleep(client.updateInterval)
end
end
end end
local result = json.decode(h.readAll()); function client.connect(token)
expect(1, token, "nil", "string")
if not result.ok then local h, err, eh = http.get(client.ENDPOINT .. "/api/connect?token=" .. textutils.urlEncode(token or ""))
return false, result.err
if not h then
return false, err, eh
end
local data = soqet.json.decode(h.readAll())
h.close()
client.uuid = data.uuid
client.sessionToken = data.token
client.motd = data.motd
client.connected = true
for i, v in pairs(client.channels) do
client.open(v)
end
return true
end end
return true function client.open(channel)
end expect(1, channel, "string", "number")
function soqet.polling.close(channel) client.channels[#client.channels + 1] = channel
local h, err = http.post(soqet.polling.host .. "/api/close", textutils.serialiseJSON({
token = soqet.polling.token,
channel = channel,
}))
if not h then postJson(
return false, err "open",
{
token = client.sessionToken,
channel = channel
}
)
return true
end end
local result = json.decode(h.readAll()); function client.close(channel)
expect(1, channel, "string", "number")
if not result.ok then for i, v in pairs(client.channels) do
return false, result.err if v == channel then
client.channels[i] = nil
end
end
postJson(
"close",
{
token = client.sessionToken,
channel = channel
}
)
return true
end end
return true function client.send(channel, message, meta)
end expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
function soqet.polling.send(channel, message, meta) meta = meta or {}
local h, err = http.post(soqet.polling.host .. "/api/send", textutils.serialiseJSON({ meta.library = meta.library or soqet.credits
token = soqet.polling.token,
channel = channel,
message = message,
meta = meta,
}))
if not h then postJson(
return false, err "send",
{
token = client.sessionToken,
channel = channel,
message = message,
meta = meta
}
)
end end
local result = json.decode(h.readAll()); function client.receive()
return rawreceive()
if not result.ok then
return false, result.err
end end
return true function client.listen()
client.listening = true
while client.listening do
local queue = rawreceive()
for i, v in ipairs(queue) do
os.queueEvent("soqet_message", v.channel, v.message, v.meta, client.sessionId)
end
sleep(client.updateInterval)
end
end
function client.unlisten()
client.listening = false
return true
end
return client
end end
return soqet return soqet