Compare commits

...

3 Commits

7 changed files with 612 additions and 321 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Alessandro
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

View File

@ -30,6 +30,7 @@ interface Client {
interface PollingClient extends Client {
lastPing: number,
queue: Array<any>,
token: string,
}
interface Channels {
@ -428,6 +429,7 @@ pollingRouter.get("/connect", (req, res, next) => {
client.client = req;
client.queue = [];
client.token = sessionToken;
client.send = function (data: string | object) {
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();
})
@ -547,6 +544,7 @@ pollingRouter.post("/update", (req, res, next) => {
queue: client.queue,
});
client.lastPing = Date.now();
client.queue = [];
})
@ -571,13 +569,16 @@ app.listen(config.port, () => {
return obj;
}, [] as Array<string>);
pClients.forEach(id => {
let v = clients[id] as PollingClient;
if ((Date.now() - v.lastPing) > 60000) {
console.log("[POL]", `Client connected: ${id}`);
let clientToken = (clients[id] as PollingClient).token;
delete clients[id];
delete pollingTokens[clientToken]
}
})
}, 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>
<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>
</div>
@ -487,7 +487,7 @@
<tr>
<td>motd</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>
</table>

483
soqet.lua
View File

@ -4,7 +4,7 @@ https://github.com/Ale32bit/Soqet/
MIT License
Copyright (c) 2019 Alessandro
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
@ -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,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
]] --
--[[
-- 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
SOFTWARE.
]]--
]] --
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()
local json = require("json")
local expect = dofile("rom/modules/main/cc/expect.lua").expect
local soqet = {
ENDPOINT = "wss://soqet.alexdevs.pw",
channels = {},
socket = nil,
running = false,
uuid = nil,
sessionId = nil,
ENDPOINT = "soqet.alexdevs.pw",
ssl = true,
json = json,
credits = "Soqet.lua v2 by AlexDevs"
}
local function send(data)
if not soqet.socket then
soqet.connect()
if not soqet.json then
if not fs.exists("json.lua") then
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
return soqet.socket.send(json.encode(data))
soqet.json = require("json")
end
local function receive()
if not soqet.socket then
soqet.connect()
function soqet.new()
if not http then
return false, "HTTP is not enabled!"
end
while true do
local data = soqet.socket.receive()
data = json.decode(data)
soqet.uuid = data.uuid
if data.type == "message" then
local message = data.message
local channel = data.channel
local meta = data.meta
return channel, message, meta
elseif data.type == "ping" then
send({
type = "ping",
id = 5,
})
if not http.websocket then
return false, "HTTP WebSocket feature is not enabled!"
end
local client = {
channels = {},
uuid = nil,
socket = nil,
sessionId = math.random(0xffffff),
ssl = soqet.ssl
}
local function rawsend(data)
if not client.socket then
return false
end
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
function soqet.connect()
assert(http.websocket, "WebSocket not enabled or not compatible with this ComputerCraft version.")
soqet.sessionId = tostring(math.random(0xffffff))
local socket, err = http.websocket(soqet.ENDPOINT .. "/" .. soqet.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
if ssl then
client.ENDPOINT = "wss://" .. soqet.ENDPOINT .. "/" .. client.sessionId
else
client.ENDPOINT = "ws://" .. soqet.ENDPOINT .. "/" .. client.sessionId
end
local result = json.decode(h.readAll())
function client.connect()
if client.socket then
pcall(client.socket.close)
end
if not result.ok then
return false, result.error
local socket, err = http.websocket(client.ENDPOINT)
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
soqet.polling.token = result.token
soqet.polling.motd = result.motd
soqet.polling.connected = true
function client.open(channel)
expect(1, channel, "string", "number")
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
function soqet.polling.update()
local h, err = http.post(soqet.polling.host .. "/api/update", textutils.serialiseJSON({
token = soqet.polling.token,
}))
function soqet.poll(token)
local client = {
channels = {},
uuid = nil,
sessionId = math.random(0xffffff),
sessionToken = nil,
ssl = soqet.ssl,
connected = false,
listening = true,
updateInterval = 1
}
if not h then
return false, err
if ssl then
client.ENDPOINT = "https://" .. soqet.ENDPOINT
else
client.ENDPOINT = "http://" .. soqet.ENDPOINT
end
local result = json.decode(h.readAll());
if not result.ok then
return false, result.err
local function postJson(path, body)
return http.post(
client.ENDPOINT .. "/api/" .. path,
soqet.json.encode(body),
{
["Content-Type"] = "application/json"
}
)
end
return result.queue
end
local function rawreceive()
if not client.connected then
client.connect()
end
while true do
local h, err =
postJson(
"update",
{
token = client.sessionToken
}
)
function soqet.polling.open(channel)
local h, err = http.post(soqet.polling.host .. "/api/open", textutils.serialiseJSON({
token = soqet.polling.token,
channel = channel,
}))
if not h then
error(err)
end
if not h then
return false, err
local data = soqet.json.decode(h.readAll())
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
local result = json.decode(h.readAll());
function client.connect(token)
expect(1, token, "nil", "string")
if not result.ok then
return false, result.err
local h, err, eh = http.get(client.ENDPOINT .. "/api/connect?token=" .. textutils.urlEncode(token or ""))
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
return true
end
function client.open(channel)
expect(1, channel, "string", "number")
function soqet.polling.close(channel)
local h, err = http.post(soqet.polling.host .. "/api/close", textutils.serialiseJSON({
token = soqet.polling.token,
channel = channel,
}))
client.channels[#client.channels + 1] = channel
if not h then
return false, err
postJson(
"open",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
local result = json.decode(h.readAll());
function client.close(channel)
expect(1, channel, "string", "number")
if not result.ok then
return false, result.err
for i, v in pairs(client.channels) do
if v == channel then
client.channels[i] = nil
end
end
postJson(
"close",
{
token = client.sessionToken,
channel = channel
}
)
return true
end
return true
end
function client.send(channel, message, meta)
expect(1, channel, "string", "number")
expect(3, meta, "nil", "table")
function soqet.polling.send(channel, message, meta)
local h, err = http.post(soqet.polling.host .. "/api/send", textutils.serialiseJSON({
token = soqet.polling.token,
channel = channel,
message = message,
meta = meta,
}))
meta = meta or {}
meta.library = meta.library or soqet.credits
if not h then
return false, err
postJson(
"send",
{
token = client.sessionToken,
channel = channel,
message = message,
meta = meta
}
)
end
local result = json.decode(h.readAll());
if not result.ok then
return false, result.err
function client.receive()
return rawreceive()
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
return soqet