overte-HifiExperiments/libraries/script-engine/src/WebSocketClass.cpp
2021-06-22 20:59:06 +12:00

232 lines
9.7 KiB
C++

//
// WebSocketClass.cpp
// libraries/script-engine/src/
//
// Created by Thijs Wenker on 8/4/15.
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
//
// This class is an implementation of the WebSocket object for scripting use. It provides a near-complete implementation
// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "WebSocketClass.h"
#include "ScriptEngine.h"
#include "ScriptEngineLogging.h"
WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) :
_webSocket(new QWebSocket()),
_engine(engine)
{
initialize();
_webSocket->open(url);
}
WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) :
_webSocket(qWebSocket),
_engine(engine)
{
initialize();
}
void WebSocketClass::initialize() {
connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose);
connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage);
connect(_webSocket, &QWebSocket::binaryMessageReceived, this, &WebSocketClass::handleOnBinaryMessage);
connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen);
connect(_webSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this,
&WebSocketClass::handleOnError);
_binaryType = QStringLiteral("arraybuffer");
}
QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QString url;
if (context->argumentCount() > 0) {
url = context->argument(0).toString();
}
return engine->newQObject(new WebSocketClass(engine, url), QScriptEngine::ScriptOwnership);
}
WebSocketClass::~WebSocketClass() {
_webSocket->deleteLater();
}
void WebSocketClass::send(QScriptValue message) {
if (message.isObject()) {
QByteArray ba = qscriptvalue_cast<QByteArray>(message);
_webSocket->sendBinaryMessage(ba);
} else {
_webSocket->sendTextMessage(message.toString());
}
}
void WebSocketClass::close() {
this->close(QWebSocketProtocol::CloseCodeNormal);
}
void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode) {
this->close(closeCode, QStringLiteral(""));
}
void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reason) {
_webSocket->close(closeCode, reason);
}
/*@jsdoc
* Called when the connection closes.
* @callback WebSocket~onCloseCallback
* @param {WebSocket.CloseData} data - Information on the connection closure.
*/
/*@jsdoc
* Information on a connection being closed.
* @typedef {object} WebSocket.CloseData
* @property {WebSocket.CloseCode} code - The reason why the connection was closed.
* @property {string} reason - Description of the reason why the connection was closed.
* @property {boolean} wasClean - <code>true</code> if the connection closed cleanly, <code>false</code> if it didn't.
*/
void WebSocketClass::handleOnClose() {
bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError);
if (_onCloseEvent.isFunction()) {
QScriptValueList args;
QScriptValue arg = _engine->newObject();
arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCodeAbnormalDisconnection : _webSocket->closeCode());
arg.setProperty("reason", _webSocket->closeReason());
arg.setProperty("wasClean", !hasError);
args << arg;
_onCloseEvent.call(QScriptValue(), args);
}
}
/*@jsdoc
* Called when an error occurs.
* @callback WebSocket~onErrorCallback
* @param {WebSocket.SocketError} error - The error.
*/
/*@jsdoc
* <p>The type of socket error.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Name</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>ConnectionRefusedError</td><td>The connection was refused or timed out.</td></tr>
* <tr><td><code>1</code></td><td>RemoteHostClosedError</td><td>The remote host closed the connection.</td></tr>
* <tr><td><code>2</code></td><td>HostNotFoundError</td><td>The host address was not found.</td></tr>
* <tr><td><code>3</code></td><td>SocketAccessError</td><td>The socket operation failed because the application doesn't have
* the necessary privileges.</td></tr>
* <tr><td><code>4</code></td><td>SocketResourceError</td><td>The local system ran out of resources (e.g., too many
* sockets).</td></tr>
* <tr><td><code>5</code></td><td>SocketTimeoutError</td><td>The socket operation timed out.</td></tr>
* <tr><td><code>6</code></td><td>DatagramTooLargeError</td><td>The datagram was larger than the OS's limit.</td></tr>
* <tr><td><code>7</code></td><td>NetworkError</td><td>An error occurred with the network.</td></tr>
* <tr><td><code>8</code></td><td>AddressInUseError</td><td>The is already in use and cannot be reused.</td></tr>
* <tr><td><code>9</code></td><td>SocketAddressNotAvailableError</td><td>The address specified does not belong to the
* host.</td></tr>
* <tr><td><code>10</code></td><td>UnsupportedSocketOperationError</td><td>The requested socket operation is not supported
* by the local OS.</td></tr>
* <tr><td><code>11</code></td><td>ProxyAuthenticationRequiredError</td><td>The socket is using a proxy and requires
* authentication.</td></tr>
* <tr><td><code>12</code></td><td>SslHandshakeFailedError</td><td>The SSL/TLS handshake failed.</td></tr>
* <tr><td><code>13</code></td><td>UnfinishedSocketOperationError</td><td>The last operation has not finished yet.</td></tr>
* <tr><td><code>14</code></td><td>ProxyConnectionRefusedError</td><td>Could not contact the proxy server because connection
* was denied.</td></tr>
* <tr><td><code>15</code></td><td>ProxyConnectionClosedError</td><td>The connection to the proxy server was unexpectedly
* closed.</td></tr>
* <tr><td><code>16</code></td><td>ProxyConnectionTimeoutError</td><td>The connection to the proxy server timed
* out.</td></tr>
* <tr><td><code>17</code></td><td>ProxyNotFoundError</td><td>The proxy address was not found.</td></tr>
* <tr><td><code>18</code></td><td>ProxyProtocolError</td><td>Connection to the proxy server failed because the server
* response could not be understood.</td></tr>
* <tr><td><code>19</code></td><td>OperationError</td><td>An operation failed because the socket state did not permit
* it.</td></tr>
* <tr><td><code>20</code></td><td>SslInternalError</td><td>Internal error in the SSL library being used.</td></tr>
* <tr><td><code>21</code></td><td>SslInvalidUserDataError</td><td>Error in the SSL library because of invalid
* data.</td></tr>
* <tr><td><code>22</code></td><td>TemporaryError</td><td>A temporary error occurred.</td></tr>
* <tr><td><code>-1</code></td><td>UnknownSocketError</td><td>An unknown error occurred.</td></tr>
* </tbody>
* </table>
* @typedef {number} WebSocket.SocketError
*/
void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) {
if (_onErrorEvent.isFunction()) {
_onErrorEvent.call();
}
}
/*@jsdoc
* Called when a message is received.
* @callback WebSocket~onMessageCallback
* @param {WebSocket.MessageData} message - The message received.
*/
/*@jsdoc
* A message received on a WebSocket connection.
* @typedef {object} WebSocket.MessageData
* @property {string} data - The message content.
*/
void WebSocketClass::handleOnMessage(const QString& message) {
if (_onMessageEvent.isFunction()) {
QScriptValueList args;
QScriptValue arg = _engine->newObject();
arg.setProperty("data", message);
args << arg;
_onMessageEvent.call(QScriptValue(), args);
}
}
void WebSocketClass::handleOnBinaryMessage(const QByteArray& message) {
if (_onMessageEvent.isFunction()) {
QScriptValueList args;
QScriptValue arg = _engine->newObject();
QScriptValue data = _engine->newVariant(QVariant::fromValue(message));
QScriptValue ctor = _engine->globalObject().property("ArrayBuffer");
auto array = qscriptvalue_cast<ArrayBufferClass*>(ctor.data());
QScriptValue arrayBuffer;
if (!array) {
qCWarning(scriptengine) << "WebSocketClass::handleOnBinaryMessage !ArrayBuffer";
} else {
arrayBuffer = _engine->newObject(array, data);
}
arg.setProperty("data", arrayBuffer);
args << arg;
_onMessageEvent.call(QScriptValue(), args);
}
}
/*@jsdoc
* Called when the connection opens.
* @callback WebSocket~onOpenCallback
*/
void WebSocketClass::handleOnOpen() {
if (_onOpenEvent.isFunction()) {
_onOpenEvent.call();
}
}
QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) {
return closeCode;
}
void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) {
closeCode = (QWebSocketProtocol::CloseCode)object.toUInt16();
}
QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) {
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
}
void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) {
out = qobject_cast<WebSocketClass*>(object.toQObject());
}
QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState) {
return readyState;
}
void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState) {
readyState = (WebSocketClass::ReadyState)object.toUInt16();
}