| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtWebSockets module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwebsocket_p.h" |
| |
| #include <QtCore/qcoreapplication.h> |
| |
| #include <emscripten.h> |
| #include <emscripten/bind.h> |
| |
| using namespace emscripten; |
| |
| static void q_onErrorCallback(val event) |
| { |
| val target = event["target"]; |
| |
| QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>()); |
| Q_ASSERT (wsp); |
| |
| emit wsp->q_func()->error(wsp->error()); |
| } |
| |
| static void q_onCloseCallback(val event) |
| { |
| val target = event["target"]; |
| |
| QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>()); |
| Q_ASSERT (wsp); |
| |
| wsp->setSocketState(QAbstractSocket::UnconnectedState); |
| emit wsp->q_func()->disconnected(); |
| } |
| |
| static void q_onOpenCallback(val event) |
| { |
| val target = event["target"]; |
| |
| QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>()); |
| Q_ASSERT (wsp); |
| |
| wsp->setSocketState(QAbstractSocket::ConnectedState); |
| emit wsp->q_func()->connected(); |
| } |
| |
| static void q_onIncomingMessageCallback(val event) |
| { |
| val target = event["target"]; |
| |
| if (event["data"].typeOf().as<std::string>() == "string") { |
| QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(target["data-context"].as<quintptr>()); |
| Q_ASSERT (wsp); |
| |
| const QString message = QString::fromStdString(event["data"].as<std::string>()); |
| if (!message.isEmpty()) |
| wsp->q_func()->textMessageReceived(message); |
| } else { |
| val reader = val::global("FileReader").new_(); |
| reader.set("onload", val::module_property("QWebSocketPrivate_readBlob")); |
| reader.set("data-context", target["data-context"]); |
| reader.call<void>("readAsArrayBuffer", event["data"]); |
| } |
| } |
| |
| static void q_readBlob(val event) |
| { |
| val fileReader = event["target"]; |
| |
| QWebSocketPrivate *wsp = reinterpret_cast<QWebSocketPrivate*>(fileReader["data-context"].as<quintptr>()); |
| Q_ASSERT (wsp); |
| |
| // Set up source typed array |
| val result = fileReader["result"]; // ArrayBuffer |
| val Uint8Array = val::global("Uint8Array"); |
| val sourceTypedArray = Uint8Array.new_(result); |
| |
| // Allocate and set up destination typed array |
| const size_t size = result["byteLength"].as<size_t>(); |
| QByteArray buffer(size, Qt::Uninitialized); |
| |
| val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"], |
| reinterpret_cast<quintptr>(buffer.data()), size); |
| destinationTypedArray.call<void>("set", sourceTypedArray); |
| |
| wsp->q_func()->binaryMessageReceived(buffer); |
| } |
| |
| |
| EMSCRIPTEN_BINDINGS(wasm_module) { |
| function("QWebSocketPrivate_onErrorCallback", q_onErrorCallback); |
| function("QWebSocketPrivate_onCloseCallback", q_onCloseCallback); |
| function("QWebSocketPrivate_onOpenCallback", q_onOpenCallback); |
| function("QWebSocketPrivate_onIncomingMessageCallback", q_onIncomingMessageCallback); |
| function("QWebSocketPrivate_readBlob", q_readBlob); |
| } |
| |
| qint64 QWebSocketPrivate::sendTextMessage(const QString &message) |
| { |
| socketContext.call<void>("send", message.toStdString()); |
| return message.length(); |
| } |
| |
| qint64 QWebSocketPrivate::sendBinaryMessage(const QByteArray &data) |
| { |
| // Make a copy of the payload data; we don't know how long WebSocket.send() will |
| // retain the memory view, while the QByteArray passed to this function may be |
| // destroyed as soon as this function returns. In addition, the WebSocket.send() |
| // API does not accept data from a view backet by a SharedArrayBuffer, which will |
| // be the case for the view produced by typed_memory_view() when threads are enabled. |
| val Uint8Array = val::global("Uint8Array"); |
| val dataCopy = Uint8Array.new_(data.size()); |
| val dataView = val(typed_memory_view(data.size(), |
| reinterpret_cast<const unsigned char *> |
| (data.constData()))); |
| dataCopy.call<void>("set", dataView); |
| |
| socketContext.call<void>("send", dataCopy); |
| return data.length(); |
| } |
| |
| void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString reason) |
| { |
| Q_Q(QWebSocket); |
| m_closeCode = closeCode; |
| m_closeReason = reason; |
| Q_EMIT q->aboutToClose(); |
| setSocketState(QAbstractSocket::ClosingState); |
| |
| socketContext.call<void>("close", static_cast<quint16>(closeCode), |
| reason.toLatin1().toStdString()); |
| } |
| |
| void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask) |
| { |
| Q_UNUSED(mask) |
| Q_Q(QWebSocket); |
| const QUrl url = request.url(); |
| if (!url.isValid() || url.toString().contains(QStringLiteral("\r\n"))) { |
| setErrorString(QWebSocket::tr("Invalid URL.")); |
| Q_EMIT q->error(QAbstractSocket::ConnectionRefusedError); |
| return; |
| } |
| |
| setSocketState(QAbstractSocket::ConnectingState); |
| const std::string urlbytes = url.toString().toStdString(); |
| |
| // HTML WebSockets do not support arbitrary request headers, but |
| // do support the WebSocket protocol header. This header is |
| // required for some use cases like MQTT. |
| const std::string protocolHeaderValue = request.rawHeader("Sec-WebSocket-Protocol").toStdString(); |
| val webSocket = val::global("WebSocket"); |
| |
| socketContext = !protocolHeaderValue.empty() |
| ? webSocket.new_(urlbytes, protocolHeaderValue) |
| : webSocket.new_(urlbytes); |
| |
| socketContext.set("onerror", val::module_property("QWebSocketPrivate_onErrorCallback")); |
| socketContext.set("onclose", val::module_property("QWebSocketPrivate_onCloseCallback")); |
| socketContext.set("onopen", val::module_property("QWebSocketPrivate_onOpenCallback")); |
| socketContext.set("onmessage", val::module_property("QWebSocketPrivate_onIncomingMessageCallback")); |
| socketContext.set("data-context", val(quintptr(reinterpret_cast<void *>(this)))); |
| } |
| |
| bool QWebSocketPrivate::isValid() const |
| { |
| return (!socketContext.isUndefined() && |
| (m_socketState == QAbstractSocket::ConnectedState)); |
| } |