diff options
Diffstat (limited to 'rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp')
| -rw-r--r-- | rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp b/rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp new file mode 100644 index 0000000..5948683 --- /dev/null +++ b/rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp @@ -0,0 +1,318 @@ +#include <QBuffer> +#include <QAuthenticator> +#include <QEventLoop> + +#include "jskitxmlhttprequest.h" +#include "jskitmanager.h" + +JSKitXMLHttpRequest::JSKitXMLHttpRequest(QJSEngine *engine) : + QObject(engine), + l(metaObject()->className()), + m_engine(engine), + m_net(new QNetworkAccessManager(this)), + m_timeout(0), + m_reply(0) +{ + connect(m_net, &QNetworkAccessManager::authenticationRequired, + this, &JSKitXMLHttpRequest::handleAuthenticationRequired); +} + +void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password) +{ + if (m_reply) { + m_reply->deleteLater(); + m_reply = 0; + } + + m_username = username; + m_password = password; + m_request = QNetworkRequest(QUrl(url)); + m_verb = method; + m_async = async; + + qCDebug(l) << "opened to URL" << m_request.url().toString() << "Async:" << async; +} + +void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value) +{ + qCDebug(l) << "setRequestHeader" << header << value; + m_request.setRawHeader(header.toLatin1(), value.toLatin1()); +} + +void JSKitXMLHttpRequest::send(const QJSValue &data) +{ + QByteArray byteData; + + if (data.isUndefined() || data.isNull()) { + // Do nothing, byteData is empty. + } else if (data.isString()) { + byteData = data.toString().toUtf8(); + } 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 = data.property("_bytes"); + int byteLength = data.property("byteLength").toInt(); + + if (array.isArray()) { + byteData.reserve(byteLength); + + for (int i = 0; i < byteLength; i++) { + byteData.append(array.property(i).toInt()); + } + + qCDebug(l) << "passed an ArrayBufferView of" << byteData.length() << "bytes"; + } else { + qCWarning(l) << "passed an unknown/invalid ArrayBuffer" << data.toString(); + } + } else { + qCWarning(l) << "passed an unknown object" << data.toString(); + } + + } + + QBuffer *buffer; + if (!byteData.isEmpty()) { + buffer = new QBuffer; + buffer->setData(byteData); + } else { + buffer = 0; + } + + qCDebug(l) << "sending" << m_verb << "to" << m_request.url() << "with" << QString::fromUtf8(byteData); + m_reply = m_net->sendCustomRequest(m_request, m_verb.toLatin1(), buffer); + + connect(m_reply, &QNetworkReply::finished, + this, &JSKitXMLHttpRequest::handleReplyFinished); + connect(m_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), + this, &JSKitXMLHttpRequest::handleReplyError); + + if (buffer) { + // So that it gets deleted alongside the reply object. + buffer->setParent(m_reply); + } + + if (!m_async) { + QEventLoop loop; //Hacky way to get QNetworkReply be synchronous + + connect(m_reply, &QNetworkReply::finished, + &loop, &QEventLoop::quit); + connect(m_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), + &loop, &QEventLoop::quit); + + loop.exec(); + } +} + +void JSKitXMLHttpRequest::abort() +{ + if (m_reply) { + m_reply->deleteLater(); + m_reply = 0; + } +} + +QJSValue JSKitXMLHttpRequest::onload() const +{ + return m_onload; +} + +void JSKitXMLHttpRequest::setOnload(const QJSValue &value) +{ + m_onload = value; +} + +QJSValue JSKitXMLHttpRequest::onreadystatechange() const +{ + return m_onreadystatechange; +} + +void JSKitXMLHttpRequest::setOnreadystatechange(const QJSValue &value) +{ + m_onreadystatechange = value; +} + +QJSValue JSKitXMLHttpRequest::ontimeout() const +{ + return m_ontimeout; +} + +void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value) +{ + m_ontimeout = value; +} + +QJSValue JSKitXMLHttpRequest::onerror() const +{ + return m_onerror; +} + +void JSKitXMLHttpRequest::setOnerror(const QJSValue &value) +{ + m_onerror = value; +} + +uint JSKitXMLHttpRequest::readyState() const +{ + if (!m_reply) { + return UNSENT; + } else if (m_reply->isFinished()) { + return DONE; + } else { + return LOADING; + } +} + +uint JSKitXMLHttpRequest::timeout() const +{ + return m_timeout; +} + +void JSKitXMLHttpRequest::setTimeout(uint value) +{ + m_timeout = value; + // TODO Handle fetch in-progress. +} + +uint JSKitXMLHttpRequest::status() const +{ + if (!m_reply || !m_reply->isFinished()) { + return 0; + } else { + return m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); + } +} + +QString JSKitXMLHttpRequest::statusText() const +{ + if (!m_reply || !m_reply->isFinished()) { + return QString(); + } else { + return m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + } +} + +QString JSKitXMLHttpRequest::responseType() const +{ + return m_responseType; +} + +void JSKitXMLHttpRequest::setResponseType(const QString &type) +{ + qCDebug(l) << "response type set to" << type; + m_responseType = type; +} + +QJSValue JSKitXMLHttpRequest::response() const +{ + if (m_responseType.isEmpty() || m_responseType == "text") { + return m_engine->toScriptValue(QString::fromUtf8(m_response)); + } else if (m_responseType == "arraybuffer") { + QJSValue arrayBufferProto = m_engine->globalObject().property("ArrayBuffer").property("prototype"); + QJSValue arrayBuf = m_engine->newObject(); + + if (!arrayBufferProto.isUndefined()) { + arrayBuf.setPrototype(arrayBufferProto); + arrayBuf.setProperty("byteLength", m_engine->toScriptValue<uint>(m_response.size())); + + QJSValue array = m_engine->newArray(m_response.size()); + for (int i = 0; i < m_response.size(); i++) { + array.setProperty(i, m_engine->toScriptValue<int>(m_response[i])); + } + + arrayBuf.setProperty("_bytes", array); + qCDebug(l) << "returning ArrayBuffer of" << m_response.size() << "bytes"; + } else { + qCWarning(l) << "Cannot find proto of ArrayBuffer"; + } + + return arrayBuf; + } else { + qCWarning(l) << "unsupported responseType:" << m_responseType; + return m_engine->toScriptValue<void*>(0); + } +} + +QString JSKitXMLHttpRequest::responseText() const +{ + return QString::fromUtf8(m_response); +} + +void JSKitXMLHttpRequest::handleReplyFinished() +{ + if (!m_reply) { + qCDebug(l) << "reply finished too late"; + return; + } + + m_response = m_reply->readAll(); + qCDebug(l) << "reply finished, reply text:" << QString::fromUtf8(m_response) << "status:" << status(); + + emit readyStateChanged(); + emit statusChanged(); + emit statusTextChanged(); + emit responseChanged(); + emit responseTextChanged(); + + if (m_onload.isCallable()) { + qCDebug(l) << "going to call onload handler:" << m_onload.toString(); + + QJSValue result = m_onload.callWithInstance(m_engine->newQObject(this)); + if (result.isError()) { + qCWarning(l) << "JS error on onload handler:" << JSKitManager::describeError(result); + } + } else { + qCDebug(l) << "No onload set"; + } + + if (m_onreadystatechange.isCallable()) { + qCDebug(l) << "going to call onreadystatechange handler:" << m_onreadystatechange.toString(); + QJSValue result = m_onreadystatechange.callWithInstance(m_engine->newQObject(this)); + if (result.isError()) { + qCWarning(l) << "JS error on onreadystatechange handler:" << JSKitManager::describeError(result); + } + } +} + +void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code) +{ + if (!m_reply) { + qCDebug(l) << "reply error too late"; + return; + } + + qCDebug(l) << "reply error" << code; + + emit readyStateChanged(); + emit statusChanged(); + emit statusTextChanged(); + + if (m_onerror.isCallable()) { + qCDebug(l) << "going to call onerror handler:" << m_onload.toString(); + QJSValue result = m_onerror.callWithInstance(m_engine->newQObject(this)); + if (result.isError()) { + qCWarning(l) << "JS error on onerror handler:" << JSKitManager::describeError(result); + } + } +} + +void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth) +{ + if (m_reply == reply) { + qCDebug(l) << "authentication required"; + + if (!m_username.isEmpty() || !m_password.isEmpty()) { + qCDebug(l) << "using provided authorization:" << m_username; + + auth->setUser(m_username); + auth->setPassword(m_password); + } else { + qCDebug(l) << "no username or password provided"; + } + } +} |
