Mercurial > irccd
view libirccd-js/irccd/js/server_jsapi.cpp @ 733:bd12709b1975
Irccd: rework server to be simpler
Server no longer has signals, now user is responsible of calling connect(),
recv() and pass a completion handler. The recv function will complete with a
std::variant of all possible events.
The server does not manage itself anymore, the reconnection system has been
moved to server_service instead.
To simplify reconnection, the limit has been removed now you can only enable
indefinite reconnection or disable it at all.
closes #893
closes #892
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 24 Jul 2018 21:30:00 +0200 |
parents | e53b013c8938 |
children | c216d148558d |
line wrap: on
line source
/* * server_jsapi.cpp -- Irccd.Server API * * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <cassert> #include <sstream> #include <unordered_map> #include <irccd/daemon/irccd.hpp> #include <irccd/daemon/server_util.hpp> #include <irccd/daemon/service/server_service.hpp> #include "duktape_vector.hpp" #include "irccd_jsapi.hpp" #include "js_plugin.hpp" #include "server_jsapi.hpp" namespace irccd { namespace { const char *signature("\xff""\xff""irccd-server-ptr"); const char *prototype("\xff""\xff""irccd-server-prototype"); std::shared_ptr<server> self(duk_context* ctx) { dukx_stack_assert sa(ctx); duk_push_this(ctx); duk_get_prop_string(ctx, -1, signature); auto ptr = duk_to_pointer(ctx, -1); duk_pop_2(ctx); if (!ptr) duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object"); return *static_cast<std::shared_ptr<server>*>(ptr); } template <typename Handler> duk_ret_t wrap(duk_context* ctx, Handler handler) { try { return handler(ctx); } catch (const server_error& ex) { dukx_throw(ctx, ex); } catch (const std::exception& ex) { dukx_throw(ctx, ex); } return 0; } // {{{ Irccd.Server.prototype.info /* * Method: Irccd.Server.prototype.info() * ------------------------------------------------------------------ * * Get the server information as an object containing the following properties: * * name: the server unique name * host: the host name * port: the port number * ssl: true if using ssl * sslVerify: true if ssl was verified * channels: an array of all channels */ duk_ret_t Server_prototype_info(duk_context* ctx) { const auto server = self(ctx); const auto& channels = server->get_channels(); duk_push_object(ctx); dukx_push(ctx, server->get_id()); duk_put_prop_string(ctx, -2, "name"); dukx_push(ctx, server->get_host()); duk_put_prop_string(ctx, -2, "host"); duk_push_int(ctx, server->get_port()); duk_put_prop_string(ctx, -2, "port"); duk_push_boolean(ctx, (server->get_options() & server::options::ssl) == server::options::ssl); duk_put_prop_string(ctx, -2, "ssl"); duk_push_boolean(ctx, (server->get_options() & server::options::ssl_verify) == server::options::ssl_verify); duk_put_prop_string(ctx, -2, "sslVerify"); dukx_push(ctx, server->get_command_char()); duk_put_prop_string(ctx, -2, "commandChar"); dukx_push(ctx, server->get_realname()); duk_put_prop_string(ctx, -2, "realname"); dukx_push(ctx, server->get_nickname()); duk_put_prop_string(ctx, -2, "nickname"); dukx_push(ctx, server->get_username()); duk_put_prop_string(ctx, -2, "username"); dukx_push(ctx, std::vector<std::string>(channels.begin(), channels.end())); duk_put_prop_string(ctx, -2, "channels"); return 1; } // }}} // {{{ Irccd.Server.prototype.invite /* * Method: Irccd.Server.prototype.invite(target, channel) * ------------------------------------------------------------------ * * Invite someone to a channel. * * Arguments: * - target, the target to invite, * - channel, the channel. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_invite(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); auto channel = dukx_require<std::string>(ctx, 1); if (target.empty()) throw server_error(server_error::invalid_nickname); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->invite(std::move(target), std::move(channel)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.isSelf /* * Method: Irccd.Server.prototype.isSelf(nickname) * ------------------------------------------------------------------ * * Arguments: * - nickname, the nickname to check. * Returns: * True if the nickname targets this server. * Throws: * - Irccd.SystemError on errors. */ duk_ret_t Server_prototype_isSelf(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { return dukx_push(ctx, self(ctx)->is_self(dukx_require<std::string>(ctx, 0))); }); } // }}} // {{{ Irccd.Server.prototype.join /* * Method: Irccd.Server.prototype.join(channel, password = undefined) * ------------------------------------------------------------------ * * Join a channel with an optional password. * * Arguments: * - channel, the channel to join, * - password, the password or undefined to not use. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_join(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto channel = dukx_require<std::string>(ctx, 0); auto password = dukx_get<std::string>(ctx, 1); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->join(std::move(channel), std::move(password)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.kick /* * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined) * ------------------------------------------------------------------ * * Kick someone from a channel. * * Arguments: * - target, the target to kick, * - channel, the channel, * - reason, the optional reason or undefined to not set. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_kick(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); auto channel = dukx_require<std::string>(ctx, 1); auto reason = dukx_get<std::string>(ctx, 2); if (target.empty()) throw server_error(server_error::invalid_nickname); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->kick(std::move(target), std::move(channel), std::move(reason)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.me /* * Method: Irccd.Server.prototype.me(target, message) * ------------------------------------------------------------------ * * Send a CTCP Action. * * Arguments: * - target, the target or a channel, * - message, the message. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_me(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); auto message = dukx_get<std::string>(ctx, 1); if (target.empty()) throw server_error(server_error::invalid_nickname); self(ctx)->me(std::move(target), std::move(message)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.message /* * Method: Irccd.Server.prototype.message(target, message) * ------------------------------------------------------------------ * * Send a message. * * Arguments: * - target, the target or a channel, * - message, the message. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_message(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); auto message = dukx_get<std::string>(ctx, 1); if (target.empty()) throw server_error(server_error::invalid_nickname); self(ctx)->message(std::move(target), std::move(message)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.mode /* * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask) * ------------------------------------------------------------------ * * Change your mode. * * Arguments: * - mode, the new mode. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_mode(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto channel = dukx_require<std::string>(ctx, 0); auto mode = dukx_require<std::string>(ctx, 1); auto limit = dukx_get<std::string>(ctx, 2); auto user = dukx_get<std::string>(ctx, 3); auto mask = dukx_get<std::string>(ctx, 4); if (channel.empty()) throw server_error(server_error::invalid_channel); if (mode.empty()) throw server_error(server_error::invalid_mode); self(ctx)->mode( std::move(channel), std::move(mode), std::move(limit), std::move(user), std::move(mask) ); return 0; }); } // }}} // {{{ Irccd.Server.prototype.names /* * Method: Irccd.Server.prototype.names(channel) * ------------------------------------------------------------------ * * Get the list of names from a channel. * * Arguments: * - channel, the channel. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_names(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto channel = dukx_require<std::string>(ctx, 0); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->names(std::move(channel)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.nick /* * Method: Irccd.Server.prototype.nick(nickname) * ------------------------------------------------------------------ * * Change the nickname. * * Arguments: * - nickname, the nickname. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_nick(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto nickname = dukx_require<std::string>(ctx, 0); if (nickname.empty()) throw server_error(server_error::invalid_nickname); self(ctx)->set_nickname(std::move(nickname)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.notice /* * Method: Irccd.Server.prototype.notice(target, message) * ------------------------------------------------------------------ * * Send a private notice. * * Arguments: * - target, the target, * - message, the notice message. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_notice(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); auto message = dukx_get<std::string>(ctx, 1); if (target.empty()) throw server_error(server_error::invalid_nickname); self(ctx)->notice(std::move(target), std::move(message)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.part /* * Method: Irccd.Server.prototype.part(channel, reason = undefined) * ------------------------------------------------------------------ * * Leave a channel. * * Arguments: * - channel, the channel to leave, * - reason, the optional reason, keep undefined for portability. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_part(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto channel = dukx_require<std::string>(ctx, 0); auto reason = dukx_get<std::string>(ctx, 1); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->part(std::move(channel), std::move(reason)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.send /* * Method: Irccd.Server.prototype.send(raw) * ------------------------------------------------------------------ * * Send a raw message to the IRC server. * * Arguments: * - raw, the raw message (without terminators). * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_send(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto raw = dukx_require<std::string>(ctx, 0); if (raw.empty()) throw server_error(server_error::invalid_message); self(ctx)->send(std::move(raw)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.topic /* * Method: Server.prototype.topic(channel, topic) * ------------------------------------------------------------------ * * Change a channel topic. * * Arguments: * - channel, the channel, * - topic, the new topic. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_topic(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto channel = dukx_require<std::string>(ctx, 0); auto topic = dukx_get<std::string>(ctx, 1); if (channel.empty()) throw server_error(server_error::invalid_channel); self(ctx)->topic(std::move(channel), std::move(topic)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.whois /* * Method: Irccd.Server.prototype.whois(target) * ------------------------------------------------------------------ * * Get whois information. * * Arguments: * - target, the target. * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_prototype_whois(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto target = dukx_require<std::string>(ctx, 0); if (target.empty()) throw server_error(server_error::invalid_nickname); self(ctx)->whois(std::move(target)); return 0; }); } // }}} // {{{ Irccd.Server.prototype.toString /* * Method: Irccd.Server.prototype.toString() * ------------------------------------------------------------------ * * Convert the object to std::string, convenience for adding the object * as property key. * * Returns: * The server name (unique). * Throws: * - Irccd.SystemError on errors. */ duk_ret_t Server_prototype_toString(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { dukx_push(ctx, self(ctx)->get_id()); return 1; }); } // }}} // {{{ Irccd.Server [constructor] /* * Function: Irccd.Server(params) [constructor] * ------------------------------------------------------------------ * * Construct a new server. * * Params must be filled with the following properties: * * name: the name, * host: the host, * ipv6: true to use ipv6, (Optional: default false) * port: the port number, (Optional: default 6667) * password: the password, (Optional: default none) * channels: array of channels (Optiona: default empty) * ssl: true to use ssl, (Optional: default false) * sslVerify: true to verify (Optional: default true) * nickname: "nickname", (Optional, default: irccd) * username: "user name", (Optional, default: irccd) * realname: "real name", (Optional, default: IRC Client Daemon) * commandChar: "!", (Optional, the command char, default: "!") * * Arguments: * - params, the server properties * Throws: * - Irccd.ServerError on server related errors, * - Irccd.SystemError on other errors. */ duk_ret_t Server_constructor(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { if (!duk_is_constructor_call(ctx)) return 0; duk_check_type(ctx, 0, DUK_TYPE_OBJECT); auto json = nlohmann::json::parse(duk_json_encode(ctx, 0)); auto s = server_util::from_json(dukx_type_traits<irccd>::self(ctx).get_service(), json); duk_push_this(ctx); duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s))); duk_put_prop_string(ctx, -2, signature); duk_pop(ctx); return 0; }); } // }}} // {{{ Irccd.Server [destructor] /* * Function: Irccd.Server() [destructor] * ------------------------------------------------------------------ * * Delete the property. */ duk_ret_t Server_destructor(duk_context* ctx) { duk_get_prop_string(ctx, 0, signature); delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1)); duk_pop(ctx); duk_del_prop_string(ctx, 0, signature); return 0; } // }}} // {{{ Irccd.Server.add /* * Function: Irccd.Server.add(s) * ------------------------------------------------------------------ * * Register a new server to the irccd instance. * * Arguments: * - s, the server to add. * Throws: * - Irccd.SystemError on errors. */ duk_ret_t Server_add(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { dukx_type_traits<irccd>::self(ctx).servers().add( dukx_require<std::shared_ptr<server>>(ctx, 0)); return 0; }); } // }}} // {{{ Irccd.Server.find /* * Function: Irccd.Server.find(name) * ------------------------------------------------------------------ * * Find a server by name. * * Arguments: * - name, the server name * Returns: * The server object or undefined if not found. * Throws: * - Irccd.SystemError on errors. */ duk_ret_t Server_find(duk_context* ctx) { return wrap(ctx, [] (auto ctx) { auto id = dukx_require<std::string>(ctx, 0); auto server = dukx_type_traits<irccd>::self(ctx).servers().get(id); if (!server) return 0; dukx_push(ctx, server); return 1; }); } // }}} // {{{ Irccd.Server.list /* * Function: Irccd.Server.list() * ------------------------------------------------------------------ * * Get the map of all loaded servers. * * Returns: * An object with string-to-servers pairs. */ duk_ret_t Server_list(duk_context* ctx) { duk_push_object(ctx); for (const auto& server : dukx_type_traits<irccd>::self(ctx).servers().all()) { dukx_push(ctx, server); duk_put_prop_string(ctx, -2, server->get_id().c_str()); } return 1; } // }}} // {{{ Irccd.Server.remove /* * Function: Irccd.Server.remove(name) * ------------------------------------------------------------------ * * Remove a server from the irccd instance. You can pass the server object since * it's coercible to a string. * * Arguments: * - name the server name. */ duk_ret_t Server_remove(duk_context* ctx) { dukx_type_traits<irccd>::self(ctx).servers().remove(duk_require_string(ctx, 0)); return 0; } // }}} // {{{ Irccd.ServerError /* * Function: Irccd.ServerError(code, message) * ------------------------------------------------------------------ * * Create an Irccd.ServerError object. * * Arguments: * - code, the error code, * - message, the error message. */ duk_ret_t ServerError_constructor(duk_context* ctx) { duk_push_this(ctx); duk_push_int(ctx, duk_require_int(ctx, 0)); duk_put_prop_string(ctx, -2, "code"); duk_push_string(ctx, duk_require_string(ctx, 1)); duk_put_prop_string(ctx, -2, "message"); duk_push_string(ctx, "ServerError"); duk_put_prop_string(ctx, -2, "name"); duk_pop(ctx); return 0; } // }}} const duk_function_list_entry methods[] = { { "info", Server_prototype_info, 0 }, { "invite", Server_prototype_invite, 2 }, { "isSelf", Server_prototype_isSelf, 1 }, { "join", Server_prototype_join, DUK_VARARGS }, { "kick", Server_prototype_kick, DUK_VARARGS }, { "me", Server_prototype_me, 2 }, { "message", Server_prototype_message, 2 }, { "mode", Server_prototype_mode, 1 }, { "names", Server_prototype_names, 1 }, { "nick", Server_prototype_nick, 1 }, { "notice", Server_prototype_notice, 2 }, { "part", Server_prototype_part, DUK_VARARGS }, { "send", Server_prototype_send, 1 }, { "topic", Server_prototype_topic, 2 }, { "toString", Server_prototype_toString, 0 }, { "whois", Server_prototype_whois, 1 }, { nullptr, nullptr, 0 } }; const duk_function_list_entry functions[] = { { "add", Server_add, 1 }, { "find", Server_find, 1 }, { "list", Server_list, 0 }, { "remove", Server_remove, 1 }, { nullptr, nullptr, 0 } }; } // !namespace std::string server_jsapi::get_name() const { return "Irccd.Server"; } void server_jsapi::load(irccd&, std::shared_ptr<js_plugin> plugin) { dukx_stack_assert sa(plugin->get_context()); duk_get_global_string(plugin->get_context(), "Irccd"); // ServerError function. duk_push_c_function(plugin->get_context(), ServerError_constructor, 2); duk_push_object(plugin->get_context()); duk_get_global_string(plugin->get_context(), "Error"); duk_get_prop_string(plugin->get_context(), -1, "prototype"); duk_remove(plugin->get_context(), -2); duk_set_prototype(plugin->get_context(), -2); duk_put_prop_string(plugin->get_context(), -2, "prototype"); duk_put_prop_string(plugin->get_context(), -2, "ServerError"); // Server constructor. duk_push_c_function(plugin->get_context(), Server_constructor, 1); duk_put_function_list(plugin->get_context(), -1, functions); duk_push_object(plugin->get_context()); duk_put_function_list(plugin->get_context(), -1, methods); duk_push_c_function(plugin->get_context(), Server_destructor, 1); duk_set_finalizer(plugin->get_context(), -2); duk_dup_top(plugin->get_context()); duk_put_global_string(plugin->get_context(), prototype); duk_put_prop_string(plugin->get_context(), -2, "prototype"); duk_put_prop_string(plugin->get_context(), -2, "Server"); duk_pop(plugin->get_context()); } using server_traits = dukx_type_traits<std::shared_ptr<server>>; using server_error_traits = dukx_type_traits<server_error>; void server_traits::push(duk_context* ctx, std::shared_ptr<server> server) { assert(ctx); assert(server); dukx_stack_assert sa(ctx, 1); duk_push_object(ctx); duk_push_pointer(ctx, new std::shared_ptr<class server>(std::move(server))); duk_put_prop_string(ctx, -2, signature); duk_get_global_string(ctx, prototype); duk_set_prototype(ctx, -2); } std::shared_ptr<server> server_traits::require(duk_context* ctx, duk_idx_t index) { if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature)) duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object"); duk_get_prop_string(ctx, index, signature); auto file = *static_cast<std::shared_ptr<server> *>(duk_to_pointer(ctx, -1)); duk_pop(ctx); return file; } void server_error_traits::raise(duk_context* ctx, const server_error& ex) { dukx_stack_assert sa(ctx, 1); duk_get_global_string(ctx, "Irccd"); duk_get_prop_string(ctx, -1, "ServerError"); duk_remove(ctx, -2); dukx_push(ctx, ex.code().value()); dukx_push(ctx, ex.code().message()); duk_new(ctx, 2); (void)duk_throw(ctx); } } // !irccd