changeset 588:be1656362297

Irccd: refactor Javascript calls, closes #747
author David Demelier <markand@malikania.fr>
date Tue, 05 Dec 2017 22:01:00 +0100
parents 312af09354e0
children 25d4a9f239ec
files libirccd-js/CMakeLists.txt libirccd-js/irccd/js/duktape.hpp libirccd-js/irccd/js/duktape_vector.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/util_jsapi.cpp tests/src/file-jsapi/main.cpp
diffstat 8 files changed, 287 insertions(+), 310 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-js/CMakeLists.txt	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/CMakeLists.txt	Tue Dec 05 22:01:00 2017 +0100
@@ -23,6 +23,7 @@
 set(
     HEADERS
     ${libirccd-js_SOURCE_DIR}/irccd/js/duktape.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/duktape_vector.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.hpp
--- a/libirccd-js/irccd/js/duktape.hpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/irccd/js/duktape.hpp	Tue Dec 05 22:01:00 2017 +0100
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#ifndef DUKTAPE_HPP
-#define DUKTAPE_HPP
+#ifndef IRCCD_JS_DUKTAPE_HPP
+#define IRCCD_JS_DUKTAPE_HPP
 
 /**
  * \file duktape.hpp
@@ -580,6 +580,151 @@
 };
 
 /**
+ * \brief Partial specialization for collections.
+ *
+ * Derive from this class to implement type traits for collections.
+ *
+ * \see duktape_vector.hpp
+ */
+template <typename Container>
+class dukx_array_type_traits : public std::true_type {
+public:
+    /**
+     * Push a Javascript array by copying values.
+     *
+     * Uses dukx_push for each T values in the collection.
+     *
+     * \param ctx the Duktape context
+     * \param c the container
+     */
+    static void push(duk_context* ctx, const Container& c)
+    {
+        using Type = typename Container::value_type;
+
+        duk_push_array(ctx);
+
+        int i = 0;
+
+        for (auto v : c) {
+            dukx_type_traits<Type>::push(ctx, v);
+            duk_put_prop_index(ctx, -2, i++);
+        }
+    }
+
+    /**
+     * Get an array from the Javascript array.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the container
+     */
+    static Container get(duk_context* ctx, duk_idx_t index)
+    {
+        using Type = typename Container::value_type;
+        using Size = typename Container::size_type;
+
+        Container result;
+        Size length = duk_get_length(ctx, index);
+
+        for (Size i = 0; i < length; ++i) {
+            duk_get_prop_index(ctx, index, i);
+            result.push_back(dukx_type_traits<Type>::get(ctx, -1));
+            duk_pop(ctx);
+        }
+
+        return result;
+    }
+
+    /**
+     * Require an array from the Javascript array.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the container
+     */
+    static Container require(duk_context* ctx, duk_idx_t index)
+    {
+        duk_check_type(ctx, index, DUK_TYPE_OBJECT);
+
+        return get(ctx, index);
+    }
+};
+
+/**
+ * \brief Partial specialization for maps.
+ *
+ * Derive from this class to implement type traits for maps.
+ *
+ * \see duktape_vector.hpp
+ */
+template <typename Container>
+class dukx_object_type_traits : public std::true_type {
+public:
+    /**
+     * Push a container by copying values.
+     *
+     * Uses dukx_push for each key/value pair in the container.
+     *
+     * \param ctx the Duktape context
+     * \param c the container
+     */
+    static void push(duk_context* ctx, const Container& c)
+    {
+        using Type = typename Container::mapped_type;
+
+        duk_push_object(ctx);
+
+        for (const auto& pair : c) {
+            dukx_type_traits<std::string>::push(ctx, pair.first);
+            dukx_type_traits<Type>::push(ctx, pair.second);
+            duk_put_prop(ctx, -3);
+        }
+    }
+
+    /**
+     * Get a object from the Javascript array.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the container
+     */
+    static Container get(duk_context* ctx, duk_idx_t index)
+    {
+        using Type = typename Container::mapped_type;
+
+        Container result;
+
+        duk_enum(ctx, index, 0);
+
+        while (duk_next(ctx, -1, true)) {
+            result.emplace(
+                dukx_type_traits<std::string>::get(ctx, -2),
+                dukx_type_traits<Type>::get(ctx, -1)
+            );
+            duk_pop_n(ctx, 2);
+        }
+
+        duk_pop(ctx);
+
+        return result;
+    }
+
+    /**
+     * Require a object from the Javascript array.
+     *
+     * \param ctx the Duktape context
+     * \param index the value index
+     * \return the container
+     */
+    static Container require(duk_context* ctx, duk_idx_t index)
+    {
+        duk_check_type(ctx, index, DUK_TYPE_OBJECT);
+
+        return get(ctx, index);
+    }
+};
+
+/**
  * Generic push function.
  *
  * This function calls dukx_type_traits<T>::push if specialized.
@@ -639,163 +784,6 @@
 }
 
 /**
- * Push a Javascript array to the stack.
- *
- * This function push the value using duk_push_array and dukx_push for each
- * value between the range
- *
- * \param ctx the Duktape context
- * \param it the input iterator
- * \param end the end iterator
- * \return 1 for convenience
- * \warning experimental and may change in the future
- */
-template <typename InputIt>
-int dukx_push_array(duk_context* ctx, InputIt it, InputIt end)
-{
-    duk_push_array(ctx);
-
-    for (int i = 0; it != end; ++it) {
-        dukx_push(ctx, *it);
-        duk_put_prop_index(ctx, -2, i++);
-    }
-
-    return 1;
-}
-
-/**
- * Overloaded function.
- *
- * Alias for `dukx_push_array(ctx, values.begin(), values.end());`
- *
- * \param ctx the Duktape context
- * \param values the list of values
- * \return 1 for convenience
- * \warning experimental and may change in the future
- */
-template <typename T>
-int dukx_push_array(duk_context* ctx, std::initializer_list<T> values)
-{
-    return dukx_push_array(ctx, values.begin(), values.end());
-}
-
-/**
- * Push a Javascript object to the stack.
- *
- * Fhis function push the value using duk_push_object and dukx_push for each
- * key/value pair combination in InputIt.
- *
- * The input iterator must have first and second values (usually `std::pair`).
- *
- * \param ctx the Duktape context
- * \param it the input iterator
- * \param end the end iterator
- * \return 1 for convenience
- * \warning experimental and may change in the future
- */
-template <typename InputIt>
-int dukx_push_object(duk_context* ctx, InputIt it, InputIt end)
-{
-    duk_push_object(ctx);
-
-    while (it != end) {
-        dukx_push(ctx, it->first);
-        dukx_push(ctx, it++->second);
-        duk_put_prop(ctx, -3);
-    }
-
-    return 1;
-}
-
-/**
- * Overloaded function.
- *
- * Alias for `dukx_push_object(ctx, values.begin(), values.end());`
- *
- * \param ctx the Duktape context
- * \param values the list of key/value values
- * \return 1 for convenience
- * \warning experimental and may change in the future
- */
-template <typename T>
-int dukx_push_object(duk_context* ctx, std::initializer_list<std::pair<std::string, T>> values)
-{
-    return dukx_push_object(ctx, values.begin(), values.end());
-}
-
-/**
- * Get values from a Javascript array.
- *
- * This function uses dukx_get<T> for each value in the Javascript array and
- * append them to the output iterator.
- *
- * \param ctx the Duktape context
- * \param index the array index
- * \param output the output iterator
- * \warning experimental and may change in the future
- */
-template <typename T, typename OutputIt>
-void dukx_get_array(duk_context* ctx, duk_idx_t index, OutputIt output)
-{
-    std::size_t length = duk_get_length(ctx, index);
-
-    for (std::size_t i = 0; i < length; ++i) {
-        duk_get_prop_index(ctx, index, i);
-        *output++ = dukx_get<T>(ctx, -1);
-        duk_pop(ctx);
-    }
-}
-
-/**
- * Overloaded function.
- *
- * Construct a container and return it.
- *
- * \param ctx the Duktape context
- * \param index the array index
- * \return the container of values (e.g. `std::vector<int>`)
- * \warning experimental and may change in the future
- */
-template <typename Container>
-Container dukx_get_array(duk_context* ctx, duk_idx_t index)
-{
-    using T = typename Container::value_type;
-
-    Container result;
-
-    dukx_get_array<T>(ctx, index, std::back_inserter(result));
-
-    return result;
-}
-
-/**
- * Get values from a Javascript object.
- *
- * \param ctx the Duktape context
- * \param index the object index
- * \return the container of values (e.g. `std::map<std::string, int>`)
- * \warning experimental and may change in the future
- */
-template <typename Container>
-Container dukx_get_object(duk_context* ctx, duk_idx_t index)
-{
-    using T = typename Container::mapped_type;
-
-    Container result;
-
-    duk_enum(ctx, index, 0);
-
-    while (duk_next(ctx, -1, true)) {
-        result.emplace(dukx_get<std::string>(ctx, -2), dukx_get<T>(ctx, -1));
-        duk_pop_n(ctx, 2);
-    }
-
-    duk_pop(ctx);
-
-    return result;
-}
-
-/**
  * \brief Base ECMAScript error class.
  * \warning Override the function create for your own exceptions
  */
@@ -995,4 +983,4 @@
 
 } // !irccd
 
-#endif // !DUKTAPE_HPP
+#endif // !IRCCD_JS_DUKTAPE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/duktape_vector.hpp	Tue Dec 05 22:01:00 2017 +0100
@@ -0,0 +1,42 @@
+/*
+ * duktape_vector.hpp -- miscellaneous Duktape extras (std::vector support)
+ *
+ * Copyright (c) 2013-2017 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.
+ */
+
+#ifndef IRCCD_JS_DUKTAPE_VECTOR_HPP
+#define IRCCD_JS_DUKTAPE_VECTOR_HPP
+
+/**
+ * \file duktape_vector.hpp
+ * \brief Miscellaneous Duktape extras (std::vector support).
+ */
+
+#include <vector>
+
+#include "duktape.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Specialization for std::vector<T>.
+ */
+template <typename T>
+class dukx_type_traits<std::vector<T>> : public dukx_array_type_traits<std::vector<T>> {
+};
+
+} // !irccd
+
+#endif // !IRCCD_JS_DUKTAPE_VECTOR_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Tue Dec 05 22:01:00 2017 +0100
@@ -22,6 +22,7 @@
 #include <iterator>
 #include <stdexcept>
 
+#include "duktape_vector.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "js_plugin.hpp"
@@ -65,28 +66,35 @@
     duk_pop(context_);
 }
 
-void js_plugin::call(const std::string& name, unsigned nargs)
+void js_plugin::push() noexcept
 {
-    duk_get_global_string(context_, name.c_str());
+}
 
-    if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED)
-        // Function not defined, remove the undefined value and all arguments.
-        duk_pop_n(context_, nargs + 1);
-    else {
-        std::string error;
+template <typename Value, typename... Args>
+void js_plugin::push(Value&& value, Args&&... args)
+{
+    dukx_push(context_, std::forward<Value>(value));
+    push(std::forward<Args>(args)...);
+}
 
-        // Call the function and discard the result.
-        duk_insert(context_, -nargs - 1);
+template <typename... Args>
+void js_plugin::call(const std::string& func, Args&&... args)
+{
+    dukx_stack_assert sa(context_);
+
+    duk_get_global_string(context_, func.c_str());
 
-        // TODO: convert into a human readable string.
-        if (duk_pcall(context_, nargs) != 0)
-            error = duk_safe_to_string(context_, -1);
-
+    if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED) {
         duk_pop(context_);
+        return;
+    }
 
-        if (!error.empty())
-            throw plugin_error(plugin_error::exec_error, std::move(error));
-    }
+    push(std::forward<Args>(args)...);
+
+    if (duk_pcall(context_, sizeof... (Args)) != 0)
+        throw plugin_error(plugin_error::exec_error, name(), dukx_stack(context_, -1).stack());
+
+    duk_pop(context_);
 }
 
 js_plugin::js_plugin(std::string name, std::string path)
@@ -153,186 +161,90 @@
     duk_pop(context_);
 }
 
-void js_plugin::on_command(irccd& , const message_event &event)
+void js_plugin::on_command(irccd&, const message_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.message);
-    call("onCommand", 4);
+    call("onCommand", event.server, event.origin, event.channel, event.message);
 }
 
-void js_plugin::on_connect(irccd& , const connect_event &event)
+void js_plugin::on_connect(irccd&, const connect_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    call("onConnect", 1);
+    call("onConnect", event.server);
 }
 
-void js_plugin::on_invite(irccd& , const invite_event &event)
+void js_plugin::on_invite(irccd&, const invite_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    call("onInvite", 3);
+    call("onInvite", event.server, event.origin, event.channel);
 }
 
-void js_plugin::on_join(irccd& , const join_event &event)
+void js_plugin::on_join(irccd&, const join_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    call("onJoin", 3);
+    call("onJoin", event.server, event.origin, event.channel);
 }
 
-void js_plugin::on_kick(irccd& , const kick_event &event)
+void js_plugin::on_kick(irccd&, const kick_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.target);
-    dukx_push(context_, event.reason);
-    call("onKick", 5);
+    call("onKick", event.server, event.origin, event.channel, event.target, event.reason);
 }
 
 void js_plugin::on_load(irccd&)
 {
-    dukx_stack_assert sa(context_);
-
-    call("onLoad", 0);
+    call("onLoad");
 }
 
-void js_plugin::on_message(irccd& , const message_event &event)
+void js_plugin::on_message(irccd&, const message_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.message);
-    call("onMessage", 4);
+    call("onMessage", event.server, event.origin, event.channel, event.message);
 }
 
-void js_plugin::on_me(irccd& , const me_event &event)
+void js_plugin::on_me(irccd&, const me_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.message);
-    call("onMe", 4);
+    call("onMe", event.server, event.origin, event.channel, event.message);
 }
 
-void js_plugin::on_mode(irccd& , const mode_event &event)
+void js_plugin::on_mode(irccd&, const mode_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.mode);
-    dukx_push(context_, event.limit);
-    dukx_push(context_, event.user);
-    dukx_push(context_, event.mask);
-    call("onMode", 7);
+    call("onMode", event.server, event.origin, event.channel, event.mode,
+        event.limit, event.user, event.mask);
 }
 
-void js_plugin::on_names(irccd& , const names_event &event)
+void js_plugin::on_names(irccd&, const names_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.channel);
-    dukx_push_array(context_, event.names.begin(), event.names.end());
-
-    call("onNames", 3);
+    call("onNames", event.server, event.channel, event.names);
 }
 
-void js_plugin::on_nick(irccd& , const nick_event &event)
+void js_plugin::on_nick(irccd&, const nick_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.nickname);
-    call("onNick", 3);
+    call("onNick", event.server, event.origin, event.nickname);
 }
 
-void js_plugin::on_notice(irccd& , const notice_event &event)
+void js_plugin::on_notice(irccd&, const notice_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.message);
-    call("onNotice", 4);
+    call("onNotice", event.server, event.origin, event.channel, event.message);
 }
 
-void js_plugin::on_part(irccd& , const part_event &event)
+void js_plugin::on_part(irccd&, const part_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.reason);
-    call("onPart", 4);
+    call("onPart", event.server, event.origin, event.channel, event.reason);
 }
 
-void js_plugin::on_reload(irccd& )
+void js_plugin::on_reload(irccd&)
 {
-    dukx_stack_assert sa(context_);
-
     call("onReload");
 }
 
-void js_plugin::on_topic(irccd& , const topic_event &event)
+void js_plugin::on_topic(irccd&, const topic_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    dukx_push(context_, event.origin);
-    dukx_push(context_, event.channel);
-    dukx_push(context_, event.topic);
-    call("onTopic", 4);
+    call("onTopic", event.server, event.origin, event.channel, event.topic);
 }
 
-void js_plugin::on_unload(irccd& )
+void js_plugin::on_unload(irccd&)
 {
-    dukx_stack_assert sa(context_);
-
     call("onUnload");
 }
 
-void js_plugin::on_whois(irccd& , const whois_event &event)
+void js_plugin::on_whois(irccd&, const whois_event& event)
 {
-    dukx_stack_assert sa(context_);
-
-    dukx_push(context_, std::move(event.server));
-    duk_push_object(context_);
-    dukx_push(context_, event.whois.nick);
-    duk_put_prop_string(context_, -2, "nickname");
-    dukx_push(context_, event.whois.user);
-    duk_put_prop_string(context_, -2, "username");
-    dukx_push(context_, event.whois.realname);
-    duk_put_prop_string(context_, -2, "realname");
-    dukx_push(context_, event.whois.host);
-    duk_put_prop_string(context_, -2, "host");
-    dukx_push_array(context_, event.whois.channels.begin(), event.whois.channels.end());
-    duk_put_prop_string(context_, -2, "channels");
-
-    call("onWhois", 2);
+    call("onWhois", event.server, event.whois);
 }
 
 js_plugin_loader::js_plugin_loader(irccd& irccd) noexcept
@@ -359,4 +271,19 @@
     return plugin;
 }
 
+void dukx_type_traits<whois>::push(duk_context* ctx, const whois& whois)
+{
+    duk_push_object(ctx);
+    dukx_push(ctx, whois.nick);
+    duk_put_prop_string(ctx, -2, "nickname");
+    dukx_push(ctx, whois.user);
+    duk_put_prop_string(ctx, -2, "username");
+    dukx_push(ctx, whois.realname);
+    duk_put_prop_string(ctx, -2, "realname");
+    dukx_push(ctx, whois.host);
+    duk_put_prop_string(ctx, -2, "host");
+    dukx_push(ctx, whois.channels);
+    duk_put_prop_string(ctx, -2, "channels");
+}
+
 } // !irccd
--- a/libirccd-js/irccd/js/js_plugin.hpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/irccd/js/js_plugin.hpp	Tue Dec 05 22:01:00 2017 +0100
@@ -61,7 +61,17 @@
     // Private helpers.
     std::unordered_map<std::string, std::string> get_table(const std::string&) const;
     void put_table(const std::string&, const std::unordered_map<std::string, std::string>&);
-    void call(const std::string&, unsigned = 0);
+
+    /*
+     * Helpers to call a Javascript function.
+     */
+    void push() noexcept;
+
+    template <typename Value, typename... Args>
+    void push(Value&& value, Args&&... args);
+
+    template <typename... Args>
+    void call(const std::string&, Args&&... args);
 
 public:
     /**
@@ -272,6 +282,12 @@
                                  const std::string& path) override;
 };
 
+template <>
+class dukx_type_traits<whois> : public std::true_type {
+public:
+    static void push(duk_context* ctx, const whois& who);
+};
+
 } // !irccd
 
 #endif // !IRCCD_PLUGIN_JS_HPP
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Tue Dec 05 22:01:00 2017 +0100
@@ -23,6 +23,7 @@
 #include <irccd.hpp>
 #include <irccd/server_service.hpp>
 
+#include "duktape_vector.hpp"
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
 #include "server_jsapi.hpp"
@@ -85,7 +86,7 @@
     duk_put_prop_string(ctx, -2, "nickname");
     dukx_push(ctx, server->username());
     duk_put_prop_string(ctx, -2, "username");
-    dukx_push_array(ctx, server->channels().begin(), server->channels().end());
+    dukx_push(ctx, server->channels());
     duk_put_prop_string(ctx, -2, "channels");
 
     return 1;
--- a/libirccd-js/irccd/js/util_jsapi.cpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/libirccd-js/irccd/js/util_jsapi.cpp	Tue Dec 05 22:01:00 2017 +0100
@@ -20,6 +20,7 @@
 
 #include <irccd/string_util.hpp>
 
+#include "duktape_vector.hpp"
 #include "js_plugin.hpp"
 #include "util_jsapi.hpp"
 
@@ -204,7 +205,7 @@
         return 1;
     }
 
-    return dukx_push_array(ctx, list.begin(), list.end());
+    return dukx_push(ctx, list);
 }
 
 /*
--- a/tests/src/file-jsapi/main.cpp	Tue Dec 05 21:42:55 2017 +0100
+++ b/tests/src/file-jsapi/main.cpp	Tue Dec 05 22:01:00 2017 +0100
@@ -21,6 +21,7 @@
 
 #include <fstream>
 
+#include <irccd/js/duktape_vector.hpp>
 #include <irccd/js/file_jsapi.hpp>
 
 #include <js_test.hpp>
@@ -148,7 +149,7 @@
     std::vector<std::string> expected{"a", "b", "c"};
 
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1));
 }
 
 BOOST_AUTO_TEST_CASE(method_seek1)
@@ -279,7 +280,7 @@
     std::vector<std::string> expected{"a", "b", "c"};
 
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1));
 }
 
 BOOST_AUTO_TEST_CASE(method_readline_closed)
@@ -299,7 +300,7 @@
     std::vector<std::string> expected;
 
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+    BOOST_TEST(expected == dukx_get<std::vector<std::string>>(plugin_->context(), -1));
 }
 
 BOOST_AUTO_TEST_SUITE_END()