summaryrefslogtreecommitdiff
path: root/rockworkd/libpebble/jskit/jskitwebsocket.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'rockworkd/libpebble/jskit/jskitwebsocket.cpp')
-rw-r--r--rockworkd/libpebble/jskit/jskitwebsocket.cpp311
1 files changed, 311 insertions, 0 deletions
diff --git a/rockworkd/libpebble/jskit/jskitwebsocket.cpp b/rockworkd/libpebble/jskit/jskitwebsocket.cpp
new file mode 100644
index 0000000..e66cdfb
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitwebsocket.cpp
@@ -0,0 +1,311 @@
+#include "jskitwebsocket.h"
+#include "jskitmanager.h"
+
+JSKitWebSocket::JSKitWebSocket(QJSEngine *engine, const QString &url, const QJSValue &protocols) :
+ QObject(engine),
+ l(metaObject()->className()),
+ m_engine(engine),
+ m_webSocket(new QWebSocket("", QWebSocketProtocol::VersionLatest, this)),
+ m_url(url)
+{
+ //As of QT 5.5: "QWebSocket currently does not support extensions and subprotocols"
+ Q_UNUSED(protocols)
+
+ connect(m_webSocket, &QWebSocket::connected,
+ this, &JSKitWebSocket::handleConnected);
+ connect(m_webSocket, &QWebSocket::disconnected,
+ this, &JSKitWebSocket::handleDisconnected);
+ connect(m_webSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error),
+ this, &JSKitWebSocket::handleError);
+ connect(m_webSocket, &QWebSocket::sslErrors,
+ this, &JSKitWebSocket::handleSslErrors);
+ connect(m_webSocket, &QWebSocket::textMessageReceived,
+ this, &JSKitWebSocket::handleTextMessageReceived);
+ connect(m_webSocket, &QWebSocket::binaryMessageReceived,
+ this, &JSKitWebSocket::handleBinaryMessageReceived);
+
+ qCDebug(l) << "WebSocket opened for" << url;
+ //m_webSocket->ignoreSslErrors();
+ m_webSocket->open(QUrl(url));
+}
+
+void JSKitWebSocket::send(const QJSValue &data)
+{
+ //TODO throw SYNTAX_ERR if "The data is a string that has unpaired surrogates" - https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Exceptions_thrown_2
+
+ if (m_readyState != OPEN) {
+ //TODO throw INVALID_STATE_ERR if not opened - https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Exceptions_thrown_2
+ qCDebug(l) << "trying to send when connection is not yet open";
+
+ return;
+ }
+
+ if (data.isUndefined() || data.isNull()) {
+ qCDebug(l) << "Refusing to send a null or undefined message";
+ } else if (data.isString()) {
+ qCDebug(l) << "Sending text message:" << data.toString();
+
+ QByteArray byteData = data.toString().toUtf8();
+ m_bufferedAmount += byteData.size();
+
+ m_webSocket->sendTextMessage(data.toString());
+ } else if (data.isObject()) {
+ if (data.hasProperty("byteLength")) {
+ // Looks like an ArrayView or an ArrayBufferView!
+ QJSValue buffer = data.property("buffer");
+ if (buffer.isUndefined()) {
+ // We must assume we've been passed an ArrayBuffer directly
+ buffer = data;
+ }
+
+ QJSValue array = buffer.property("_bytes");
+ int byteLength = buffer.property("byteLength").toInt();
+
+ if (array.isArray()) {
+ QByteArray byteData;
+ byteData.reserve(byteLength);
+
+ for (int i = 0; i < byteLength; i++) {
+ byteData.append(array.property(i).toInt());
+ }
+
+ qCDebug(l) << "sending binary message with" << byteData.length() << "bytes";
+
+ m_bufferedAmount += byteData.size();
+ m_webSocket->sendBinaryMessage(byteData);
+ } else {
+ qCWarning(l) << "Refusing to send an unknown/invalid ArrayBuffer" << data.toString();
+ }
+ } else {
+ qCWarning(l) << "Refusing to send an unknown object:" << data.toString();
+ }
+ }
+}
+
+void JSKitWebSocket::close(quint32 code, const QString &reason)
+{
+ //TODO throw SYNTAX_ERR if "The reason string contains unpaired surrogates" - https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Exceptions_thrown_2
+
+
+ QByteArray byteData = reason.toUtf8();
+ if (byteData.size() >= 123) {
+ //TODO throw SYNTAX_ERR for invalid reason - https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Exceptions_thrown
+ qCDebug(l) << "Invalid reason";
+
+ return;
+ }
+
+ QWebSocketProtocol::CloseCode closeCode = QWebSocketProtocol::CloseCodeNormal;
+ if ((code >= 1000 && code <= 1011) || code == 1015) {
+ closeCode = static_cast<QWebSocketProtocol::CloseCode>(code);;
+ }
+ else {
+ //TODO throw INVALID_ACCESS_ERR for invalide code - https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Exceptions_thrown
+ qCDebug(l) << "Invalid close code";
+
+ return;
+ }
+
+ m_webSocket->close(closeCode, reason);
+ m_readyState = CLOSING;
+}
+
+void JSKitWebSocket::setOnclose(const QJSValue &onclose)
+{
+ m_onclose = onclose;
+}
+
+void JSKitWebSocket::setOnerror(const QJSValue &onerror)
+{
+ m_onerror = onerror;
+}
+
+void JSKitWebSocket::setOnmessage(const QJSValue &onmessage)
+{
+ m_onmessage = onmessage;
+}
+
+void JSKitWebSocket::setOnopen(const QJSValue &onopen)
+{
+ m_onopen = onopen;
+}
+
+QJSValue JSKitWebSocket::onclose() const
+{
+ return m_onclose;
+}
+
+QJSValue JSKitWebSocket::onerror() const
+{
+ return m_onerror;
+}
+
+QJSValue JSKitWebSocket::onmessage() const
+{
+ return m_onmessage;
+}
+
+QJSValue JSKitWebSocket::onopen() const
+{
+ return m_onopen;
+}
+
+quint32 JSKitWebSocket::bufferedAmount()
+{
+ return m_bufferedAmount;
+}
+
+quint8 JSKitWebSocket::readyState()
+{
+ return m_readyState;
+}
+
+QString JSKitWebSocket::url()
+{
+ return m_url;
+}
+
+void JSKitWebSocket::handleConnected()
+{
+ m_readyState = OPEN;
+ qCDebug(l) << "Connection opened";
+
+ if (m_onopen.isCallable()) {
+ qCDebug(l) << "Going to call onopen";
+
+ QJSValueList eventArgs;
+ eventArgs.append("open");
+ QJSValue event = m_engine->globalObject().property("Event").property("_init").call(eventArgs);
+
+ QJSValueList args;
+ args.append(event);
+ QJSValue result = m_onopen.callWithInstance(m_engine->newQObject(this), args);
+ if (result.isError()) {
+ qCWarning(l) << "JS error in onopen handler:" << JSKitManager::describeError(result);
+ }
+ }
+}
+
+void JSKitWebSocket::handleDisconnected()
+{
+ m_readyState = CLOSED;
+ qCDebug(l) << "Connection closed";
+
+ if (m_onclose.isCallable()) {
+ qCDebug(l) << "Going to call onclose";
+
+ QJSValueList eventArgs;
+ eventArgs.append(QJSValue(true)); //wasClean
+ eventArgs.append(QJSValue(m_webSocket->closeCode()));
+ eventArgs.append(QJSValue(m_webSocket->closeReason()));
+
+ QJSValue event = m_engine->globalObject().property("CloseEvent").property("_init").call(eventArgs);
+
+ QJSValueList args;
+ args.append(event);
+
+ QJSValue result = m_onclose.callWithInstance(m_engine->newQObject(this));
+ if (result.isError()) {
+ qCWarning(l) << "JS error in onclose handler:" << JSKitManager::describeError(result);
+ }
+ }
+}
+
+void JSKitWebSocket::handleError(QAbstractSocket::SocketError error)
+{
+ qCDebug(l) << "Error:" << error;
+
+ if (m_onerror.isCallable()) {
+ qCDebug(l) << "Going to call onerror";
+
+ QJSValueList eventArgs;
+ eventArgs.append(QJSValue("error"));
+ QJSValue event = m_engine->globalObject().property("Event").property("_init").call(eventArgs);
+
+ QJSValueList args;
+ args.append(event);
+ QJSValue result = m_onerror.callWithInstance(m_engine->newQObject(this), args);
+ if (result.isError()) {
+ qCWarning(l) << "JS error in onclose handler:" << JSKitManager::describeError(result);
+ }
+ }
+}
+
+void JSKitWebSocket::handleSslErrors(const QList<QSslError> &errors)
+{
+ qCDebug(l) << "Ssl Errors:" << errors;
+
+ if (m_onerror.isCallable()) {
+ qCDebug(l) << "Going to call onerror";
+
+ QJSValueList eventArgs;
+ eventArgs.append(QJSValue("error"));
+ QJSValue event = m_engine->globalObject().property("Event").property("_init").call(eventArgs);
+
+ QJSValueList args;
+ args.append(event);
+ QJSValue result = m_onerror.callWithInstance(m_engine->newQObject(this), args);
+ if (result.isError()) {
+ qCWarning(l) << "JS error in onclose handler:" << JSKitManager::describeError(result);
+ }
+ }
+}
+
+void JSKitWebSocket::handleTextMessageReceived(const QString &message)
+{
+ qCDebug(l) << "Text message recieved: " << message;
+
+ callOnmessage(QJSValue(message));
+}
+
+void JSKitWebSocket::handleBinaryMessageReceived(const QByteArray &message)
+{
+ qCDebug(l) << "Binary message recieved";
+
+ if (m_onmessage.isCallable()) {
+ if (m_binaryType == "arraybuffer") {
+ QJSValue arrayBufferProto = m_engine->globalObject().property("ArrayBuffer").property("prototype");
+ QJSValue arrayBuf = m_engine->newObject();
+
+ if (arrayBufferProto.isUndefined()) {
+ qCWarning(l) << "Cannot find proto of ArrayBuffer";
+ } else {
+ arrayBuf.setPrototype(arrayBufferProto);
+ arrayBuf.setProperty("byteLength", m_engine->toScriptValue<uint>(message.size()));
+
+ QJSValue array = m_engine->newArray(message.size());
+ for (int i = 0; i < message.size(); i++) {
+ array.setProperty(i, m_engine->toScriptValue<int>(message[i]));
+ }
+
+ arrayBuf.setProperty("_bytes", array);
+ qCDebug(l) << "calling onmessage with ArrayBuffer of" << message.size() << "bytes";
+
+ callOnmessage(arrayBuf);
+ }
+ } else {
+ qCWarning(l) << "unsupported binaryType:" << m_binaryType;
+ }
+ }
+}
+
+void JSKitWebSocket::callOnmessage(QJSValue data)
+{
+ if (m_onmessage.isCallable()) {
+ qCDebug(l) << "Going to call onmessage";
+
+ QJSValueList eventArgs;
+ eventArgs.append(QJSValue(m_webSocket->origin()));
+ eventArgs.append(data);
+
+ QJSValue messageEvent = m_engine->globalObject().property("MessageEvent").property("_init").call(eventArgs);
+
+ QJSValueList args;
+ args.append(messageEvent);
+
+ QJSValue result = m_onmessage.callWithInstance(m_engine->newQObject(this), args);
+ if (result.isError()) {
+ qCWarning(l) << "JS error in onmessage handler:" << JSKitManager::describeError(result);
+ }
+ }
+}