channel concept

This commit is contained in:
Thomas Rudin 2018-12-06 09:29:03 +01:00
parent 9c9397d6b3
commit 6ba185b376
6 changed files with 142 additions and 172 deletions

80
util/channel.lua Normal file
View File

@ -0,0 +1,80 @@
-- bi-directional http-channel
-- with long-poll GET and POST on the same URL
local Channel = function(http, url, cfg)
cfg = cfg or {}
local extra_headers = cfg.extra_headers or {}
local timeout = cfg.timeout or 1
local long_poll_timeout = cfg.long_poll_timeout or 30
local error_retry = cfg.error_retry or 10
-- assemble post-header with json content
local post_headers = { "Content-Type: application/json" }
for _,header in pairs(cfg.extra_headers) do
table.insert(post_headers, header)
end
local recv_listeners = {}
local run = true
local recv_loop
recv_loop = function()
-- long-poll GET
http.fetch({
url = url,
extra_headers = extra_headers,
timeout = long_poll_timeout
}, function(res)
if res.succeeded and res.code == 200 then
local data = minetest.parse_json(res.data)
if data then
for _,listener in pairs(recv_listeners) do
listener(data)
end
end
-- reschedule immediately
minetest.after(0, recv_loop)
else
-- error, retry after some time
minetest.after(error_retry, recv_loop)
end
end)
end
local send = function(data)
-- POST
http.fetch({
url = url,
extra_headers = post_headers,
timeout = timeout,
post_data = minetest.write_json(data)
}, function(res)
-- TODO: error-handling
end)
end
local receive = function(listener)
table.insert(recv_listeners, listener)
end
local close = function()
run = false
end
recv_loop();
return {
send = send,
receive = receive,
close = close
}
end
return Channel

View File

@ -1,126 +1,54 @@
local url, key, http
local webmail = {}
-- polls the webmail server and processes the logins made there
webmail.auth_collector = function()
http.fetch({
url=url .. "/api/minetest/auth_collector",
extra_headers = { "webmailkey: " .. key },
timeout=15
}, function(res)
local MP = minetest.get_modpath(minetest.get_current_modname())
local Channel = dofile(MP .. "/util/channel.lua")
local channel
if res.code == 403 then
-- unauthorized, abort
minetest.log("error", "[webmail] invalid key specified!")
return
end
if res.succeeded and res.code == 200 then
local auth = minetest.parse_json(res.data)
if auth then
local auth_response = {}
local handler = minetest.get_auth_handler()
local auth_handler = function(auth)
local handler = minetest.get_auth_handler()
local success = false
local entry = handler.get_auth(auth.name)
if entry and minetest.check_password_entry(auth.name, entry.password, auth.password) then
success = true
end
local success = false
local entry = handler.get_auth(auth.name)
if entry and minetest.check_password_entry(auth.name, entry.password, auth.password) then
success = true
end
-- send back auth response data
http.fetch({
url=url .. "/api/minetest/auth_collector",
extra_headers = { "Content-Type: application/json", "webmailkey: " .. key },
post_data = minetest.write_json({
name = auth.name,
success = success
})
}, function(res)
-- stub
end)
channel.send({
type = "auth",
data = {
name = auth.name,
success = success
}
})
end
end
-- execute again
minetest.after(1, webmail.auth_collector)
else
-- execute again (error case)
minetest.after(10, webmail.auth_collector)
end
end)
local send_handler = function(sendmail)
-- send mail from webclient
minetest.log("action", "[webmail] sending mail from webclient: " .. sendmail.src .. " -> " .. sendmail.dst)
mail.send(sendmail.src, sendmail.dst, sendmail.subject, sendmail.body)
end
-- called on mail saving to disk (every change)
mail.webmail_save_hook = function()
http.fetch({
url=url .. "/api/minetest/messages",
extra_headers = { "Content-Type: application/json", "webmailkey: " .. key },
post_data = minetest.write_json(mail.messages)
}, function(res)
if not res.succeeded then
minetest.log("error", "[webmail] message sync to web failed")
end
end)
end
-- polls the message endpoint for commands from the webclient
webmail.message_command_loop = function()
http.fetch({
url=url .. "/api/minetest/messages",
extra_headers = { "webmailkey: " .. key },
timeout=15
}, function(res)
if res.code == 403 then
-- unauthorized, abort
minetest.log("error", "[webmail] invalid key specified!")
return
end
if res.succeeded and res.code == 200 then
local data = minetest.parse_json(res.data)
if data then
for _,cmd in pairs(data) do
if cmd.type == "send" and cmd.mail and cmd.mail.src and cmd.mail.dst then
-- send mail from webclient
local sendmail = cmd.mail
minetest.log("action", "[webmail] sending mail from webclient: " .. sendmail.src .. " -> " .. sendmail.dst)
mail.send(sendmail.src, sendmail.dst, sendmail.subject, sendmail.body)
end
end
end
-- execute again
minetest.after(1, webmail.message_command_loop)
else
-- execute again (error case)
minetest.after(10, webmail.message_command_loop)
-- update mails
minetest.after(10, mail.webmail_save_hook)
end
end)
end
mail.webmail_init = function(_http, webmail_url, webmail_key)
url = webmail_url
key = webmail_key
http = _http
minetest.after(4, function()
-- start auth collector loop
webmail.auth_collector()
-- start message command loop
webmail.message_command_loop()
-- sync messages after server start
if #mail.messages > 0 then
-- only if mails available
mail.webmail_save_hook()
channel.send({
type = "messages",
data = mail.messages
})
end
mail.webmail_init = function(http, url, key)
channel = Channel(http, url .. "/api/minetest/channel", {
extra_headers = { "webmailkey: " .. key }
})
channel.receive(function(data)
if data.type == "auth" then
auth_handler(data.data)
elseif data.type == "send" then
send_handler(data.data)
end
end)
end

View File

@ -6,30 +6,30 @@ const keycheck = require("./keycheck");
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()
app.get('/api/minetest/auth_collector', function(req, res){
// web -> mod
app.get('/api/minetest/channel', function(req, res){
if (!keycheck(req, res))
return;
function handleEvent(auth){
function handleEvent(obj){
clearTimeout(handle);
res.json(auth);
res.json(obj);
}
var handle = setTimeout(function(){
res.json(null);
events.removeListener("login", handleEvent);
events.removeListener("channel-send", handleEvent);
}, 10000);
events.once("login", handleEvent);
events.once("channel-send", handleEvent);
});
app.post('/api/minetest/auth_collector', jsonParser, function(req, res){
// mod -> web
app.post('/api/minetest/channel', jsonParser, function(req, res){
if (!keycheck(req, res))
return;
events.emit("login-response", req.body);
events.emit("channel-recv", req.body);
res.end();
});

View File

@ -1,4 +1,3 @@
// minetest mod related api endpoints
require("./auth_collector");
require("./messages");
require("./channel");

View File

@ -1,42 +0,0 @@
const app = require("../../app");
const events = require("../../events");
const store = require("../../store");
const keycheck = require("./keycheck");
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()
// web -> mod
app.get('/api/minetest/messages', function(req, res){
if (!keycheck(req, res))
return;
/*
possible message commands:
{ type: "send", mail: { src,dst,subject,body } }
{ type: "mark-read", data: {} }
{ type: "mark-unread", data: {} }
{ type: "delete", data: {} }
*/
setTimeout(function(){
res.json([]);
//res.json([{ type: "send", mail: { src:"admin", dst: "testuser", subject: "blah", body: "x\ny\nz" } }]);
}, 10000);
});
// mod -> web
app.post('/api/minetest/messages', jsonParser, function(req, res){
if (!keycheck(req, res))
return;
console.log(req.body);
store.messages = req.body;
events.emit("messages-update", req.body);
res.end()
});

View File

@ -4,21 +4,26 @@ const events = require("../events");
module.exports = (username, password) => new Promise(function(resolve, reject){
events.emit("login", {
name: username,
password: password
events.emit("channel-send", {
type: "auth",
data: {
name: username,
password: password
}
});
function handleEvent(result){
events.removeListener("login-response", handleEvent);
clearTimeout(handle);
resolve(result);
if (result.type == "auth" && result.data && result.data.name == username){
events.removeListener("channel-recv", handleEvent);
clearTimeout(handle);
resolve(result.data);
}
}
events.on("login-response", handleEvent);
events.on("channel-recv", handleEvent);
var handle = setTimeout(function(){
events.removeListener("login-response", handleEvent);
events.removeListener("channel-recv", handleEvent);
reject("mod-comm timeout");
}, 2500);
});