summaryrefslogtreecommitdiff
path: root/rockworkd/libpebble/jskit
diff options
context:
space:
mode:
authorAndrew Branson <andrew.branson@cern.ch>2016-02-11 23:55:16 +0100
committerAndrew Branson <andrew.branson@cern.ch>2016-02-11 23:55:16 +0100
commit29aaea2d80a9eb1715b6cddfac2d2aacf76358bd (patch)
tree012795b6bec16c72f38d33cff46324c9a0225868 /rockworkd/libpebble/jskit
launchpad ~mzanetti/rockwork/trunk r87
Diffstat (limited to 'rockworkd/libpebble/jskit')
-rw-r--r--rockworkd/libpebble/jskit/cacheLocalStorage.js11
-rw-r--r--rockworkd/libpebble/jskit/jsfiles.qrc7
-rw-r--r--rockworkd/libpebble/jskit/jskitconsole.cpp29
-rw-r--r--rockworkd/libpebble/jskit/jskitconsole.h20
-rw-r--r--rockworkd/libpebble/jskit/jskitgeolocation.cpp302
-rw-r--r--rockworkd/libpebble/jskit/jskitgeolocation.h66
-rw-r--r--rockworkd/libpebble/jskit/jskitlocalstorage.cpp117
-rw-r--r--rockworkd/libpebble/jskit/jskitlocalstorage.h40
-rw-r--r--rockworkd/libpebble/jskit/jskitmanager.cpp240
-rw-r--r--rockworkd/libpebble/jskit/jskitmanager.h72
-rw-r--r--rockworkd/libpebble/jskit/jskitpebble.cpp355
-rw-r--r--rockworkd/libpebble/jskit/jskitpebble.h47
-rw-r--r--rockworkd/libpebble/jskit/jskitperformance.cpp13
-rw-r--r--rockworkd/libpebble/jskit/jskitperformance.h20
-rw-r--r--rockworkd/libpebble/jskit/jskitsetup.js196
-rw-r--r--rockworkd/libpebble/jskit/jskittimer.cpp77
-rw-r--r--rockworkd/libpebble/jskit/jskittimer.h31
-rw-r--r--rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp318
-rw-r--r--rockworkd/libpebble/jskit/jskitxmlhttprequest.h96
-rw-r--r--rockworkd/libpebble/jskit/typedarray.js1037
20 files changed, 3094 insertions, 0 deletions
diff --git a/rockworkd/libpebble/jskit/cacheLocalStorage.js b/rockworkd/libpebble/jskit/cacheLocalStorage.js
new file mode 100644
index 0000000..22588a9
--- /dev/null
+++ b/rockworkd/libpebble/jskit/cacheLocalStorage.js
@@ -0,0 +1,11 @@
+//Since we don't have JS 6 support, this hack will allow us to save changes to localStorage when using dot or square bracket notation
+
+for (var key in localStorage) {
+ _jskit.localstorage.setItem(key, localStorage.getItem(key));
+}
+
+for (var key in _jskit.localstorage.keys()) {
+ if (localStorage[key] === undefined) {
+ _jskit.localstorage.removeItem(key);
+ }
+}
diff --git a/rockworkd/libpebble/jskit/jsfiles.qrc b/rockworkd/libpebble/jskit/jsfiles.qrc
new file mode 100644
index 0000000..4dfbc1d
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jsfiles.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/">
+ <file>typedarray.js</file>
+ <file>jskitsetup.js</file>
+ <file>cacheLocalStorage.js</file>
+ </qresource>
+</RCC>
diff --git a/rockworkd/libpebble/jskit/jskitconsole.cpp b/rockworkd/libpebble/jskit/jskitconsole.cpp
new file mode 100644
index 0000000..3d6c85c
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitconsole.cpp
@@ -0,0 +1,29 @@
+#include <QDebug>
+
+#include "jskitconsole.h"
+
+JSKitConsole::JSKitConsole(QObject *parent) :
+ QObject(parent),
+ l(metaObject()->className())
+{
+}
+
+void JSKitConsole::log(const QString &msg)
+{
+ qCDebug(l) << msg;
+}
+
+void JSKitConsole::warn(const QString &msg)
+{
+ qCWarning(l) << msg;
+}
+
+void JSKitConsole::error(const QString &msg)
+{
+ qCCritical(l) << msg;
+}
+
+void JSKitConsole::info(const QString &msg)
+{
+ qCDebug(l) << msg;
+}
diff --git a/rockworkd/libpebble/jskit/jskitconsole.h b/rockworkd/libpebble/jskit/jskitconsole.h
new file mode 100644
index 0000000..3896ae3
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitconsole.h
@@ -0,0 +1,20 @@
+#ifndef JSKITCONSOLE_H
+#define JSKITCONSOLE_H
+
+#include <QLoggingCategory>
+
+class JSKitConsole : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+public:
+ explicit JSKitConsole(QObject *parent=0);
+
+ Q_INVOKABLE void log(const QString &msg);
+ Q_INVOKABLE void warn(const QString &msg);
+ Q_INVOKABLE void error(const QString &msg);
+ Q_INVOKABLE void info(const QString &msg);
+};
+
+#endif // JSKITCONSOLE_H
diff --git a/rockworkd/libpebble/jskit/jskitgeolocation.cpp b/rockworkd/libpebble/jskit/jskitgeolocation.cpp
new file mode 100644
index 0000000..409cda1
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitgeolocation.cpp
@@ -0,0 +1,302 @@
+#include <limits>
+
+#include "jskitgeolocation.h"
+
+JSKitGeolocation::JSKitGeolocation(QJSEngine *engine) :
+ QObject(engine),
+ l(metaObject()->className()),
+ m_engine(engine),
+ m_source(0),
+ m_lastWatcherId(0)
+{
+}
+
+void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
+{
+ setupWatcher(successCallback, errorCallback, options, true);
+}
+
+int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
+{
+ return setupWatcher(successCallback, errorCallback, options, false);
+}
+
+void JSKitGeolocation::clearWatch(int watcherId)
+{
+ removeWatcher(watcherId);
+}
+
+void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error)
+{
+ qCWarning(l) << "positioning error: " << error;
+
+ if (m_watchers.empty()) {
+ qCWarning(l) << "got position error but no one is watching";
+ stopAndRemove();
+ }
+ else {
+ QJSValue obj;
+ if (error == QGeoPositionInfoSource::AccessError) {
+ obj = buildPositionErrorObject(PERMISSION_DENIED, "permission denied");
+ } else {
+ obj = buildPositionErrorObject(POSITION_UNAVAILABLE, "position unavailable");
+ }
+
+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
+ invokeCallback(it->errorCallback, obj);
+
+ if (it->once) {
+ it = m_watchers.erase(it);
+ } else {
+ it->timer.restart();
+ ++it;
+ }
+ }
+ }
+}
+
+void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos)
+{
+ qCDebug(l) << "got position at" << pos.timestamp() << "type" << pos.coordinate().type();
+
+ if (m_watchers.empty()) {
+ qCWarning(l) << "got position update but no one is watching";
+ stopAndRemove();
+ }
+ else {
+ QJSValue obj = buildPositionObject(pos);
+
+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
+ invokeCallback(it->successCallback, obj);
+
+ if (it->once) {
+ it = m_watchers.erase(it);
+ } else {
+ it->timer.restart();
+ ++it;
+ }
+ }
+ }
+}
+
+void JSKitGeolocation::handleTimeout()
+{
+ qCDebug(l) << "positioning timeout";
+
+ if (m_watchers.empty()) {
+ qCWarning(l) << "got position timeout but no one is watching";
+ stopAndRemove();
+ }
+ else {
+ QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout");
+
+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
+ if (it->timer.hasExpired(it->timeout)) {
+ qCDebug(l) << "positioning timeout for watch" << it->watcherId
+ << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout;
+ invokeCallback(it->errorCallback, obj);
+
+ if (it->once) {
+ it = m_watchers.erase(it);
+ } else {
+ it->timer.restart();
+ ++it;
+ }
+ } else {
+ ++it;
+ }
+ }
+
+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
+ }
+}
+
+void JSKitGeolocation::updateTimeouts()
+{
+ int once_timeout = -1, updates_timeout = -1;
+
+ Q_FOREACH(const Watcher &watcher, m_watchers) {
+ qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed();
+ qCDebug(l) << "watch" << watcher.watcherId << "rem timeout" << rem_timeout;
+
+ if (rem_timeout >= 0) {
+ // Make sure the limits aren't too large
+ rem_timeout = qMin<qint64>(rem_timeout, std::numeric_limits<int>::max());
+
+ if (watcher.once) {
+ once_timeout = once_timeout >= 0 ? qMin<int>(once_timeout, rem_timeout) : rem_timeout;
+ } else {
+ updates_timeout = updates_timeout >= 0 ? qMin<int>(updates_timeout, rem_timeout) : rem_timeout;
+ }
+ }
+ }
+
+ if (updates_timeout >= 0) {
+ qCDebug(l) << "setting location update interval to" << updates_timeout;
+ m_source->setUpdateInterval(updates_timeout);
+ m_source->startUpdates();
+ } else {
+ qCDebug(l) << "stopping updates";
+ m_source->stopUpdates();
+ }
+
+ if (once_timeout >= 0) {
+ qCDebug(l) << "requesting single location update with timeout" << once_timeout;
+ m_source->requestUpdate(once_timeout);
+ }
+
+ if (once_timeout == 0 && updates_timeout == 0) {
+ stopAndRemove();
+ }
+}
+
+int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once)
+{
+ Watcher watcher;
+ watcher.successCallback = successCallback;
+ watcher.errorCallback = errorCallback;
+ watcher.highAccuracy = options.value("enableHighAccuracy", false).toBool();
+ watcher.timeout = options.value("timeout", std::numeric_limits<int>::max() - 1).toInt();
+ watcher.maximumAge = options.value("maximumAge", 0).toLongLong();
+ watcher.once = once;
+ watcher.watcherId = ++m_lastWatcherId;
+
+ qCDebug(l) << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << watcher.maximumAge << "once=" << watcher.once;
+
+ if (!m_source) {
+ m_source = QGeoPositionInfoSource::createDefaultSource(this);
+
+ connect(m_source, static_cast<void (QGeoPositionInfoSource::*)(QGeoPositionInfoSource::Error)>(&QGeoPositionInfoSource::error),
+ this, &JSKitGeolocation::handleError);
+ connect(m_source, &QGeoPositionInfoSource::positionUpdated,
+ this, &JSKitGeolocation::handlePosition);
+ connect(m_source, &QGeoPositionInfoSource::updateTimeout,
+ this, &JSKitGeolocation::handleTimeout);
+ }
+
+ if (watcher.maximumAge > 0) {
+ QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(watcher.maximumAge));
+ QGeoPositionInfo pos = m_source->lastKnownPosition(watcher.highAccuracy);
+ qCDebug(l) << "got pos timestamp" << pos.timestamp() << " but we want" << threshold;
+
+ if (pos.isValid() && pos.timestamp() >= threshold) {
+ invokeCallback(watcher.successCallback, buildPositionObject(pos));
+
+ if (once) {
+ return -1;
+ }
+ } else if (watcher.timeout == 0 && once) {
+ // If the timeout has already expired, and we have no cached data
+ // Do not even bother to turn on the GPS; return error object now.
+ invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position"));
+ return -1;
+ }
+ }
+
+ watcher.timer.start();
+ m_watchers.append(watcher);
+
+ qCDebug(l) << "added new watcher" << watcher.watcherId;
+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
+
+ return watcher.watcherId;
+}
+
+void JSKitGeolocation::removeWatcher(int watcherId)
+{
+ Watcher watcher;
+
+ qCDebug(l) << "removing watcherId" << watcher.watcherId;
+
+ for (int i = 0; i < m_watchers.size(); i++) {
+ if (m_watchers[i].watcherId == watcherId) {
+ watcher = m_watchers.takeAt(i);
+ break;
+ }
+ }
+
+ if (watcher.watcherId != watcherId) {
+ qCWarning(l) << "watcherId not found";
+ return;
+ }
+
+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
+}
+
+QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos)
+{
+ QJSValue obj = m_engine->newObject();
+ QJSValue coords = m_engine->newObject();
+ QJSValue timestamp = m_engine->toScriptValue<quint64>(pos.timestamp().toMSecsSinceEpoch());
+
+ coords.setProperty("latitude", m_engine->toScriptValue(pos.coordinate().latitude()));
+ coords.setProperty("longitude", m_engine->toScriptValue(pos.coordinate().longitude()));
+ if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) {
+ coords.setProperty("altitude", m_engine->toScriptValue(pos.coordinate().altitude()));
+ } else {
+ coords.setProperty("altitude", m_engine->toScriptValue<void*>(0));
+ }
+
+ coords.setProperty("accuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy)));
+
+ if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) {
+ coords.setProperty("altitudeAccuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy)));
+ } else {
+ coords.setProperty("altitudeAccuracy", m_engine->toScriptValue<void*>(0));
+ }
+
+ if (pos.hasAttribute(QGeoPositionInfo::Direction)) {
+ coords.setProperty("heading", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction)));
+ } else {
+ coords.setProperty("heading", m_engine->toScriptValue<void*>(0));
+ }
+
+ if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) {
+ coords.setProperty("speed", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed)));
+ } else {
+ coords.setProperty("speed", m_engine->toScriptValue<void*>(0));
+ }
+
+ obj.setProperty("coords", coords);
+ obj.setProperty("timestamp", timestamp);
+
+ return obj;
+}
+
+QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message)
+{
+ QJSValue obj = m_engine->newObject();
+
+ obj.setProperty("code", m_engine->toScriptValue<unsigned short>(error));
+ obj.setProperty("message", m_engine->toScriptValue(message));
+
+ return obj;
+}
+
+void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event)
+{
+ if (callback.isCallable()) {
+ qCDebug(l) << "invoking callback" << callback.toString();
+ QJSValue result = callback.call(QJSValueList({event}));
+
+ if (result.isError()) {
+ qCWarning(l) << "error while invoking callback: " << QString("%1:%2: %3")
+ .arg(result.property("fileName").toString())
+ .arg(result.property("lineNumber").toInt())
+ .arg(result.toString());
+ }
+ } else {
+ qCWarning(l) << "callback is not callable";
+ }
+}
+
+void JSKitGeolocation::stopAndRemove()
+{
+ if (m_source) {
+ qCDebug(l) << "removing source";
+
+ m_source->stopUpdates();
+ m_source->deleteLater();
+ m_source = 0;
+ }
+}
diff --git a/rockworkd/libpebble/jskit/jskitgeolocation.h b/rockworkd/libpebble/jskit/jskitgeolocation.h
new file mode 100644
index 0000000..582ab32
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitgeolocation.h
@@ -0,0 +1,66 @@
+#ifndef JSKITGEOLOCATION_H
+#define JSKITGEOLOCATION_H
+
+#include <QElapsedTimer>
+#include <QGeoPositionInfoSource>
+#include <QJSValue>
+#include <QLoggingCategory>
+#include <QJSEngine>
+
+class JSKitGeolocation : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ struct Watcher;
+
+public:
+ explicit JSKitGeolocation(QJSEngine *engine);
+
+ enum PositionError {
+ PERMISSION_DENIED = 1,
+ POSITION_UNAVAILABLE = 2,
+ TIMEOUT = 3
+ };
+ Q_ENUMS(PositionError);
+
+ Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
+ Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
+ Q_INVOKABLE void clearWatch(int watcherId);
+
+private slots:
+ void handleError(const QGeoPositionInfoSource::Error error);
+ void handlePosition(const QGeoPositionInfo &pos);
+ void handleTimeout();
+ void updateTimeouts();
+
+private:
+ int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once);
+ void removeWatcher(int watcherId);
+
+ QJSValue buildPositionObject(const QGeoPositionInfo &pos);
+ QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString());
+ QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error);
+ void invokeCallback(QJSValue callback, QJSValue event);
+ void stopAndRemove();
+
+private:
+ QJSEngine *m_engine;
+ QGeoPositionInfoSource *m_source;
+
+ struct Watcher {
+ QJSValue successCallback;
+ QJSValue errorCallback;
+ int watcherId;
+ bool once;
+ bool highAccuracy;
+ int timeout;
+ QElapsedTimer timer;
+ qlonglong maximumAge;
+ };
+
+ QList<Watcher> m_watchers;
+ int m_lastWatcherId;
+};
+
+#endif // JSKITGEOLOCATION_H
diff --git a/rockworkd/libpebble/jskit/jskitlocalstorage.cpp b/rockworkd/libpebble/jskit/jskitlocalstorage.cpp
new file mode 100644
index 0000000..d69b6ad
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitlocalstorage.cpp
@@ -0,0 +1,117 @@
+#include <QDesktopServices>
+#include <QDir>
+#include <QDebug>
+
+#include "jskitlocalstorage.h"
+
+JSKitLocalStorage::JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid):
+ QObject(engine),
+ m_engine(engine),
+ m_storage(new QSettings(getStorageFileFor(storagePath, uuid), QSettings::IniFormat, this))
+{
+}
+
+int JSKitLocalStorage::length() const
+{
+ return m_storage->allKeys().size();
+}
+
+QJSValue JSKitLocalStorage::getItem(const QJSValue &key) const
+{
+ QVariant value = m_storage->value(key.toString());
+
+ if (value.isValid()) {
+ return QJSValue(value.toString());
+ } else {
+ return QJSValue(QJSValue::NullValue);
+ }
+}
+
+bool JSKitLocalStorage::setItem(const QJSValue &key, const QJSValue &value)
+{
+ m_storage->setValue(key.toString(), QVariant::fromValue(value.toString()));
+ return true;
+}
+
+bool JSKitLocalStorage::removeItem(const QJSValue &key)
+{
+ if (m_storage->contains(key.toString())) {
+ m_storage->remove(key.toString());
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void JSKitLocalStorage::clear()
+{
+ m_storage->clear();
+}
+
+QJSValue JSKitLocalStorage::key(int index)
+{
+ QStringList allKeys = m_storage->allKeys();
+ QJSValue key(QJSValue::NullValue);
+
+ if (allKeys.size() > index) {
+ key = QJSValue(allKeys[index]);
+ }
+
+ return key;
+}
+
+QJSValue JSKitLocalStorage::get(const QJSValue &proxy, const QJSValue &key) const
+{
+ Q_UNUSED(proxy);
+ return getItem(key);
+}
+
+bool JSKitLocalStorage::set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value)
+{
+ Q_UNUSED(proxy);
+ return setItem(key, value);
+}
+
+bool JSKitLocalStorage::has(const QJSValue &proxy, const QJSValue &key)
+{
+ Q_UNUSED(proxy);
+ return m_storage->contains(key.toString());
+}
+
+bool JSKitLocalStorage::deleteProperty(const QJSValue &proxy, const QJSValue &key)
+{
+ Q_UNUSED(proxy);
+ return removeItem(key);
+}
+
+QJSValue JSKitLocalStorage::keys(const QJSValue &proxy)
+{
+ Q_UNUSED(proxy);
+
+ QStringList allKeys = m_storage->allKeys();
+ QJSValue keyArray = m_engine->newArray(allKeys.size());
+ for (int i = 0; i < allKeys.size(); i++) {
+ keyArray.setProperty(i, allKeys[i]);
+ }
+
+ return keyArray;
+}
+
+QJSValue JSKitLocalStorage::enumerate()
+{
+ return keys(0);
+}
+
+QString JSKitLocalStorage::getStorageFileFor(const QString &storageDir, const QUuid &uuid)
+{
+ QDir dataDir(storageDir + "/js-storage");
+ if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) {
+ qWarning() << "Error creating jskit storage dir";
+ return QString();
+ }
+
+ QString fileName = uuid.toString();
+ fileName.remove('{');
+ fileName.remove('}');
+ return dataDir.absoluteFilePath(fileName + ".ini");
+}
diff --git a/rockworkd/libpebble/jskit/jskitlocalstorage.h b/rockworkd/libpebble/jskit/jskitlocalstorage.h
new file mode 100644
index 0000000..9719f83
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitlocalstorage.h
@@ -0,0 +1,40 @@
+#ifndef JSKITLOCALSTORAGE_P_H
+#define JSKITLOCALSTORAGE_P_H
+
+#include <QSettings>
+#include <QJSEngine>
+#include <QUuid>
+
+class JSKitLocalStorage : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int length READ length)
+
+public:
+ explicit JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid);
+
+ int length() const;
+
+ Q_INVOKABLE QJSValue getItem(const QJSValue &key) const;
+ Q_INVOKABLE bool setItem(const QJSValue &key, const QJSValue &value);
+ Q_INVOKABLE bool removeItem(const QJSValue &key);
+ Q_INVOKABLE void clear();
+ Q_INVOKABLE QJSValue key(int index);
+
+ Q_INVOKABLE QJSValue get(const QJSValue &proxy, const QJSValue &key) const;
+ Q_INVOKABLE bool set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value);
+ Q_INVOKABLE bool has(const QJSValue &proxy, const QJSValue &key);
+ Q_INVOKABLE bool deleteProperty(const QJSValue &proxy, const QJSValue &key);
+ Q_INVOKABLE QJSValue keys(const QJSValue &proxy=0);
+ Q_INVOKABLE QJSValue enumerate();
+
+private:
+ static QString getStorageFileFor(const QString &storageDir, const QUuid &uuid);
+
+private:
+ QJSEngine *m_engine;
+ QSettings *m_storage;
+};
+
+#endif // JSKITLOCALSTORAGE_P_H
diff --git a/rockworkd/libpebble/jskit/jskitmanager.cpp b/rockworkd/libpebble/jskit/jskitmanager.cpp
new file mode 100644
index 0000000..04bf674
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitmanager.cpp
@@ -0,0 +1,240 @@
+#include <QFile>
+#include <QDir>
+#include <QUrl>
+
+#include "jskitmanager.h"
+#include "jskitpebble.h"
+
+JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) :
+ QObject(parent),
+ l(metaObject()->className()),
+ m_pebble(pebble),
+ m_connection(connection),
+ m_apps(apps),
+ m_appmsg(appmsg),
+ m_engine(0),
+ m_configurationUuid(0)
+{
+ connect(m_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted);
+ connect(m_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped);
+}
+
+JSKitManager::~JSKitManager()
+{
+ if (m_engine) {
+ stopJsApp();
+ }
+}
+
+QJSEngine * JSKitManager::engine()
+{
+ return m_engine;
+}
+
+bool JSKitManager::isJSKitAppRunning() const
+{
+ return m_engine != 0;
+}
+
+QString JSKitManager::describeError(QJSValue error)
+{
+ return QString("%1:%2: %3")
+ .arg(error.property("fileName").toString())
+ .arg(error.property("lineNumber").toInt())
+ .arg(error.toString());
+}
+
+void JSKitManager::showConfiguration()
+{
+ if (m_engine) {
+ qCDebug(l) << "requesting configuration";
+ m_jspebble->invokeCallbacks("showConfiguration");
+ } else {
+ qCWarning(l) << "requested to show configuration, but JS engine is not running";
+ }
+}
+
+void JSKitManager::handleWebviewClosed(const QString &result)
+{
+ if (m_engine) {
+ QJSValue eventObj = m_engine->newObject();
+ eventObj.setProperty("response", QUrl::fromPercentEncoding(result.toUtf8()));
+
+ qCDebug(l) << "Sending" << eventObj.property("response").toString();
+ m_jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj}));
+
+ loadJsFile(":/cacheLocalStorage.js");
+ } else {
+ qCWarning(l) << "webview closed event, but JS engine is not running";
+ }
+}
+
+void JSKitManager::setConfigurationId(const QUuid &uuid)
+{
+ m_configurationUuid = uuid;
+}
+
+AppInfo JSKitManager::currentApp()
+{
+ return m_curApp;
+}
+
+void JSKitManager::handleAppStarted(const QUuid &uuid)
+{
+ AppInfo info = m_apps->info(uuid);
+ if (!info.uuid().isNull() && info.isJSKit()) {
+ qCDebug(l) << "Preparing to start JSKit app" << info.uuid() << info.shortName();
+
+ m_curApp = info;
+ startJsApp();
+ }
+}
+
+void JSKitManager::handleAppStopped(const QUuid &uuid)
+{
+ if (!m_curApp.uuid().isNull()) {
+ if (m_curApp.uuid() != uuid) {
+ qCWarning(l) << "Closed app with invalid UUID";
+ }
+
+ stopJsApp();
+ m_curApp = AppInfo();
+ qCDebug(l) << "App stopped" << uuid;
+ }
+}
+
+void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg)
+{
+ if (m_curApp.uuid() == uuid) {
+ qCDebug(l) << "handling app message" << uuid << msg;
+
+ if (m_engine) {
+ QJSValue eventObj = m_engine->newObject();
+ eventObj.setProperty("payload", m_engine->toScriptValue(msg));
+
+ m_jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj}));
+
+ loadJsFile(":/cacheLocalStorage.js");
+ }
+ else {
+ qCDebug(l) << "but engine is stopped";
+ }
+ }
+}
+
+bool JSKitManager::loadJsFile(const QString &filename)
+{
+ Q_ASSERT(m_engine);
+
+ QFile file(filename);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qCWarning(l) << "Failed to load JS file:" << file.fileName();
+ return false;
+ }
+
+ qCDebug(l) << "evaluating js file" << file.fileName();
+
+ QJSValue result = m_engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName());
+ if (result.isError()) {
+ qCWarning(l) << "error while evaluating JS script:" << describeError(result);
+ return false;
+ }
+
+ qCDebug(l) << "JS script evaluated";
+ return true;
+}
+
+void JSKitManager::startJsApp()
+{
+ if (m_engine) stopJsApp();
+
+ if (m_curApp.uuid().isNull()) {
+ qCWarning(l) << "Attempting to start JS app with invalid UUID";
+ return;
+ }
+
+ m_engine = new QJSEngine(this);
+ m_jspebble = new JSKitPebble(m_curApp, this, m_engine);
+ m_jsconsole = new JSKitConsole(m_engine);
+ m_jsstorage = new JSKitLocalStorage(m_engine, m_pebble->storagePath(), m_curApp.uuid());
+ m_jsgeo = new JSKitGeolocation(m_engine);
+ m_jstimer = new JSKitTimer(m_engine);
+ m_jsperformance = new JSKitPerformance(m_engine);
+
+ qCDebug(l) << "starting JS app" << m_curApp.shortName();
+
+ QJSValue globalObj = m_engine->globalObject();
+ QJSValue jskitObj = m_engine->newObject();
+
+ jskitObj.setProperty("pebble", m_engine->newQObject(m_jspebble));
+ jskitObj.setProperty("console", m_engine->newQObject(m_jsconsole));
+ jskitObj.setProperty("localstorage", m_engine->newQObject(m_jsstorage));
+ jskitObj.setProperty("geolocation", m_engine->newQObject(m_jsgeo));
+ jskitObj.setProperty("timer", m_engine->newQObject(m_jstimer));
+ jskitObj.setProperty("performance", m_engine->newQObject(m_jsperformance));
+ globalObj.setProperty("_jskit", jskitObj);
+
+ QJSValue navigatorObj = m_engine->newObject();
+ navigatorObj.setProperty("language", m_engine->toScriptValue(QLocale().name()));
+ globalObj.setProperty("navigator", navigatorObj);
+
+ // Set this.window = this
+ globalObj.setProperty("window", globalObj);
+
+ // Shims for compatibility...
+ loadJsFile(":/jskitsetup.js");
+
+ // Polyfills...
+ loadJsFile(":/typedarray.js");
+
+ // Now the actual script
+ QString jsApp = m_curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown);
+ QFile f(jsApp);
+ if (!f.open(QFile::ReadOnly)) {
+ qCWarning(l) << "Error opening" << jsApp;
+ return;
+ }
+ QJSValue ret = m_engine->evaluate(QString::fromUtf8(f.readAll()));
+ qCDebug(l) << "loaded script" << ret.toString();
+
+ // Setup the message callback
+ QUuid uuid = m_curApp.uuid();
+ m_appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) {
+ QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection,
+ Q_ARG(QUuid, uuid),
+ Q_ARG(QVariantMap, msg));
+
+ // Invoke the slot as a queued connection to give time for the ACK message
+ // to go through first.
+
+ return true;
+ });
+
+ // We try to invoke the callbacks even if script parsing resulted in error...
+ m_jspebble->invokeCallbacks("ready");
+
+ loadJsFile(":/cacheLocalStorage.js");
+
+ if (m_configurationUuid == m_curApp.uuid()) {
+ qCDebug(l) << "going to launch config for" << m_configurationUuid;
+ showConfiguration();
+ }
+
+ m_configurationUuid = QUuid();
+}
+
+void JSKitManager::stopJsApp()
+{
+ qCDebug(l) << "stop js app" << m_curApp.uuid();
+ if (!m_engine) return; // Nothing to do!
+
+ loadJsFile(":/cacheLocalStorage.js");
+
+ if (!m_curApp.uuid().isNull()) {
+ m_appmsg->clearMessageHandler(m_curApp.uuid());
+ }
+
+ m_engine->collectGarbage();
+ m_engine->deleteLater();
+ m_engine = 0;
+}
diff --git a/rockworkd/libpebble/jskit/jskitmanager.h b/rockworkd/libpebble/jskit/jskitmanager.h
new file mode 100644
index 0000000..570948e
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitmanager.h
@@ -0,0 +1,72 @@
+#ifndef JSKITMANAGER_H
+#define JSKITMANAGER_H
+
+#include <QJSEngine>
+#include <QPointer>
+#include <QLoggingCategory>
+
+#include "../appmanager.h"
+#include "../watchconnection.h"
+#include "../pebble.h"
+#include "../appmsgmanager.h"
+
+#include "jskitconsole.h"
+#include "jskitgeolocation.h"
+#include "jskitlocalstorage.h"
+#include "jskittimer.h"
+#include "jskitperformance.h"
+
+class JSKitPebble;
+
+class JSKitManager : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+public:
+ explicit JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0);
+ ~JSKitManager();
+
+ QJSEngine * engine();
+ bool isJSKitAppRunning() const;
+
+ static QString describeError(QJSValue error);
+
+ void showConfiguration();
+ void handleWebviewClosed(const QString &result);
+ void setConfigurationId(const QUuid &uuid);
+ AppInfo currentApp();
+
+signals:
+ void appNotification(const QUuid &uuid, const QString &title, const QString &body);
+ void openURL(const QString &uuid, const QString &url);
+
+private slots:
+ void handleAppStarted(const QUuid &uuid);
+ void handleAppStopped(const QUuid &uuid);
+ void handleAppMessage(const QUuid &uuid, const QVariantMap &msg);
+
+private:
+ bool loadJsFile(const QString &filename);
+ void startJsApp();
+ void stopJsApp();
+
+private:
+ friend class JSKitPebble;
+
+ Pebble *m_pebble;
+ WatchConnection *m_connection;
+ AppManager *m_apps;
+ AppMsgManager *m_appmsg;
+ AppInfo m_curApp;
+ QJSEngine *m_engine;
+ QPointer<JSKitPebble> m_jspebble;
+ QPointer<JSKitConsole> m_jsconsole;
+ QPointer<JSKitLocalStorage> m_jsstorage;
+ QPointer<JSKitGeolocation> m_jsgeo;
+ QPointer<JSKitTimer> m_jstimer;
+ QPointer<JSKitPerformance> m_jsperformance;
+ QUuid m_configurationUuid;
+};
+
+#endif // JSKITMANAGER_H
diff --git a/rockworkd/libpebble/jskit/jskitpebble.cpp b/rockworkd/libpebble/jskit/jskitpebble.cpp
new file mode 100644
index 0000000..a300aef
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitpebble.cpp
@@ -0,0 +1,355 @@
+#include <QUrl>
+#include <QCryptographicHash>
+#include <QSettings>
+
+#include "jskitpebble.h"
+#include "jskitxmlhttprequest.h"
+
+static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1";
+
+JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr, QObject *parent) :
+ QObject(parent),
+ l(metaObject()->className()),
+ m_appInfo(info),
+ m_mgr(mgr)
+{
+}
+
+void JSKitPebble::addEventListener(const QString &type, QJSValue function)
+{
+ m_listeners[type].append(function);
+}
+
+void JSKitPebble::removeEventListener(const QString &type, QJSValue function)
+{
+ if (!m_listeners.contains(type)) return;
+
+ QList<QJSValue> &callbacks = m_listeners[type];
+ for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ) {
+ if (it->strictlyEquals(function)) {
+ it = callbacks.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ if (callbacks.empty()) {
+ m_listeners.remove(type);
+ }
+}
+
+void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body)
+{
+ qCDebug(l) << "showSimpleNotificationOnPebble" << title << body;
+ emit m_mgr->appNotification(m_appInfo.uuid(), title, body);
+}
+
+uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack)
+{
+ QVariantMap data = message.toVariant().toMap();
+ QPointer<JSKitPebble> pebbObj = this;
+ uint transactionId = m_mgr->m_appmsg->nextTransactionId();
+
+ qCDebug(l) << "sendAppMessage" << data;
+
+ m_mgr->m_appmsg->send(
+ m_appInfo.uuid(),
+ data,
+ [this, pebbObj, transactionId, callbackForAck]() mutable {
+ if (pebbObj.isNull()) return;
+
+ if (callbackForAck.isCallable()) {
+ QJSValue event = pebbObj->buildAckEventObject(transactionId);
+ QJSValue result = callbackForAck.call(QJSValueList({event}));
+
+ if (result.isError()) {
+ qCWarning(l) << "error while invoking ACK callback"
+ << callbackForAck.toString() << ":"
+ << JSKitManager::describeError(result);
+ }
+ }
+ },
+ [this, pebbObj, transactionId, callbackForNack]() mutable {
+ if (pebbObj.isNull()) return;
+
+ if (callbackForNack.isCallable()) {
+ QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch");
+ QJSValue result = callbackForNack.call(QJSValueList({event}));
+
+ if (result.isError()) {
+ qCWarning(l) << "error while invoking NACK callback"
+ << callbackForNack.toString() << ":"
+ << JSKitManager::describeError(result);
+ }
+ }
+ }
+ );
+
+ return transactionId;
+}
+
+void JSKitPebble::getTimelineToken(QJSValue successCallback, QJSValue failureCallback)
+{
+ //TODO actually implement this
+ qCDebug(l) << "call to unsupported method Pebble.getTimelineToken";
+ Q_UNUSED(successCallback);
+
+ if (failureCallback.isCallable()) {
+ failureCallback.call();
+ }
+}
+
+void JSKitPebble::timelineSubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback)
+{
+ //TODO actually implement this
+ qCDebug(l) << "call to unsupported method Pebble.timelineSubscribe";
+ Q_UNUSED(topic);
+ Q_UNUSED(successCallback);
+
+ if (failureCallback.isCallable()) {
+ failureCallback.call();
+ }
+}
+
+void JSKitPebble::timelineUnsubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback)
+{
+ //TODO actually implement this
+ qCDebug(l) << "call to unsupported method Pebble.timelineUnsubscribe";
+ Q_UNUSED(topic);
+ Q_UNUSED(successCallback);
+
+ if (failureCallback.isCallable()) {
+ failureCallback.call();
+ }
+}
+
+void JSKitPebble::timelineSubscriptions(QJSValue successCallback, QJSValue failureCallback)
+{
+ //TODO actually implement this
+ qCDebug(l) << "call to unsupported method Pebble.timelineSubscriptions";
+ Q_UNUSED(successCallback);
+
+ if (failureCallback.isCallable()) {
+ failureCallback.call();
+ }
+}
+
+
+QString JSKitPebble::getAccountToken() const
+{
+ // We do not have any account system, so we just fake something up.
+ QCryptographicHash hasher(QCryptographicHash::Md5);
+
+ hasher.addData(token_salt, strlen(token_salt));
+ hasher.addData(m_appInfo.uuid().toByteArray());
+
+ QSettings settings;
+ QString token = settings.value("accountToken").toString();
+
+ if (token.isEmpty()) {
+ token = QUuid::createUuid().toString();
+ qCDebug(l) << "created new account token" << token;
+ settings.setValue("accountToken", token);
+ }
+
+ hasher.addData(token.toLatin1());
+
+ QString hash = hasher.result().toHex();
+ qCDebug(l) << "returning account token" << hash;
+
+ return hash;
+}
+
+QString JSKitPebble::getWatchToken() const
+{
+ QCryptographicHash hasher(QCryptographicHash::Md5);
+
+ hasher.addData(token_salt, strlen(token_salt));
+ hasher.addData(m_appInfo.uuid().toByteArray());
+ hasher.addData(m_mgr->m_pebble->serialNumber().toLatin1());
+
+ QString hash = hasher.result().toHex();
+ qCDebug(l) << "returning watch token" << hash;
+
+ return hash;
+}
+
+QJSValue JSKitPebble::getActiveWatchInfo() const
+{
+ QJSValue watchInfo = m_mgr->m_engine->newObject();
+
+ switch (m_mgr->m_pebble->hardwarePlatform()) {
+ case HardwarePlatformBasalt:
+ watchInfo.setProperty("platform", "basalt");
+ break;
+
+ case HardwarePlatformChalk:
+ watchInfo.setProperty("platform", "chalk");
+ break;
+
+ default:
+ watchInfo.setProperty("platform", "aplite");
+ break;
+ }
+
+ switch (m_mgr->m_pebble->model()) {
+ case ModelTintinWhite:
+ watchInfo.setProperty("model", "pebble_white");
+ break;
+
+ case ModelTintinRed:
+ watchInfo.setProperty("model", "pebble_red");
+ break;
+
+ case ModelTintinOrange:
+ watchInfo.setProperty("model", "pebble_orange");
+ break;
+
+ case ModelTintinGrey:
+ watchInfo.setProperty("model", "pebble_grey");
+ break;
+
+ case ModelBiancaSilver:
+ watchInfo.setProperty("model", "pebble_steel_silver");
+ break;
+
+ case ModelBiancaBlack:
+ watchInfo.setProperty("model", "pebble_steel_black");
+ break;
+
+ case ModelTintinBlue:
+ watchInfo.setProperty("model", "pebble_blue");
+ break;
+
+ case ModelTintinGreen:
+ watchInfo.setProperty("model", "pebble_green");
+ break;
+
+ case ModelTintinPink:
+ watchInfo.setProperty("model", "pebble_pink");
+ break;
+
+ case ModelSnowyWhite:
+ watchInfo.setProperty("model", "pebble_time_white");
+ break;
+
+ case ModelSnowyBlack:
+ watchInfo.setProperty("model", "pebble_time_black");
+ break;
+
+ case ModelSnowyRed:
+ watchInfo.setProperty("model", "pebble_time_read");
+ break;
+
+ case ModelBobbySilver:
+ watchInfo.setProperty("model", "pebble_time_steel_silver");
+ break;
+
+ case ModelBobbyBlack:
+ watchInfo.setProperty("model", "pebble_time_steel_black");
+ break;
+
+ case ModelBobbyGold:
+ watchInfo.setProperty("model", "pebble_time_steel_gold");
+ break;
+
+ case ModelSpalding14Silver:
+ watchInfo.setProperty("model", "pebble_time_round_silver_14mm");
+ break;
+
+ case ModelSpalding14Black:
+ watchInfo.setProperty("model", "pebble_time_round_black_14mm");
+ break;
+
+ case ModelSpalding20Silver:
+ watchInfo.setProperty("model", "pebble_time_round_silver_20mm");
+ break;
+
+ case ModelSpalding20Black:
+ watchInfo.setProperty("model", "pebble_time_round_black_20mm");
+ break;
+
+ case ModelSpalding14RoseGold:
+ watchInfo.setProperty("model", "pebble_time_round_rose_gold_14mm");
+ break;
+
+ default:
+ watchInfo.setProperty("model", "pebble_black");
+ break;
+ }
+
+ watchInfo.setProperty("language", m_mgr->m_pebble->language());
+
+ QJSValue firmware = m_mgr->m_engine->newObject();
+ QString version = m_mgr->m_pebble->softwareVersion().remove("v");
+ QStringList versionParts = version.split(".");
+
+ if (versionParts.count() >= 1) {
+ firmware.setProperty("major", versionParts[0].toInt());
+ }
+
+ if (versionParts.count() >= 2) {
+ firmware.setProperty("minor", versionParts[1].toInt());
+ }
+
+ if (versionParts.count() >= 3) {
+ if (versionParts[2].contains("-")) {
+ QStringList patchParts = version.split("-");
+ firmware.setProperty("patch", patchParts[0].toInt());
+ firmware.setProperty("suffix", patchParts[1]);
+ } else {
+ firmware.setProperty("patch", versionParts[2].toInt());
+ firmware.setProperty("suffix", "");
+ }
+ }
+
+ watchInfo.setProperty("firmware", firmware);
+ return watchInfo;
+}
+
+void JSKitPebble::openURL(const QUrl &url)
+{
+ emit m_mgr->openURL(m_appInfo.uuid().toString(), url.toString());
+}
+
+QJSValue JSKitPebble::createXMLHttpRequest()
+{
+ JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(m_mgr->engine());
+ // Should be deleted by JS engine.
+ return m_mgr->engine()->newQObject(xhr);
+}
+
+QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const
+{
+ QJSEngine *engine = m_mgr->engine();
+ QJSValue eventObj = engine->newObject();
+ QJSValue dataObj = engine->newObject();
+
+ dataObj.setProperty("transactionId", engine->toScriptValue(transaction));
+ eventObj.setProperty("data", dataObj);
+
+ if (!message.isEmpty()) {
+ QJSValue errorObj = engine->newObject();
+
+ errorObj.setProperty("message", engine->toScriptValue(message));
+ eventObj.setProperty("error", errorObj);
+ }
+
+ return eventObj;
+}
+
+void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args)
+{
+ if (!m_listeners.contains(type)) return;
+ QList<QJSValue> &callbacks = m_listeners[type];
+
+ for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ++it) {
+ qCDebug(l) << "invoking callback" << type << it->toString();
+ QJSValue result = it->call(args);
+ if (result.isError()) {
+ qCWarning(l) << "error while invoking callback"
+ << type << it->toString() << ":"
+ << JSKitManager::describeError(result);
+ }
+ }
+}
diff --git a/rockworkd/libpebble/jskit/jskitpebble.h b/rockworkd/libpebble/jskit/jskitpebble.h
new file mode 100644
index 0000000..d9cd670
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitpebble.h
@@ -0,0 +1,47 @@
+#ifndef JSKITPEBBLE_P_H
+#define JSKITPEBBLE_P_H
+
+#include <QLoggingCategory>
+
+#include "jskitmanager.h"
+#include "../appinfo.h"
+
+class JSKitPebble : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+public:
+ explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr, QObject *parent=0);
+
+ Q_INVOKABLE void addEventListener(const QString &type, QJSValue function);
+ Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function);
+
+ Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body);
+ Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue());
+
+ Q_INVOKABLE void getTimelineToken(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
+ Q_INVOKABLE void timelineSubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
+ Q_INVOKABLE void timelineUnsubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
+ Q_INVOKABLE void timelineSubscriptions(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
+
+ Q_INVOKABLE QString getAccountToken() const;
+ Q_INVOKABLE QString getWatchToken() const;
+ Q_INVOKABLE QJSValue getActiveWatchInfo() const;
+
+ Q_INVOKABLE void openURL(const QUrl &url);
+
+ Q_INVOKABLE QJSValue createXMLHttpRequest();
+
+ void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList());
+
+private:
+ QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const;
+
+private:
+ AppInfo m_appInfo;
+ JSKitManager *m_mgr;
+ QHash<QString, QList<QJSValue>> m_listeners;
+};
+
+#endif // JSKITPEBBLE_P_H
diff --git a/rockworkd/libpebble/jskit/jskitperformance.cpp b/rockworkd/libpebble/jskit/jskitperformance.cpp
new file mode 100644
index 0000000..23b0e08
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitperformance.cpp
@@ -0,0 +1,13 @@
+#include "jskitperformance.h"
+
+JSKitPerformance::JSKitPerformance(QObject *parent) :
+ QObject(parent),
+ m_start(QTime::currentTime())
+{
+}
+
+int JSKitPerformance::now()
+{
+ QTime now = QTime::currentTime();
+ return m_start.msecsTo(now);
+}
diff --git a/rockworkd/libpebble/jskit/jskitperformance.h b/rockworkd/libpebble/jskit/jskitperformance.h
new file mode 100644
index 0000000..5f118be
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitperformance.h
@@ -0,0 +1,20 @@
+#ifndef JSKITPERFORMANCE_H
+#define JSKITPERFORMANCE_H
+
+#include <QObject>
+#include <QTime>
+
+class JSKitPerformance : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit JSKitPerformance(QObject *parent=0);
+
+ Q_INVOKABLE int now();
+
+private:
+ QTime m_start;
+};
+
+#endif // JSKITPERFORMANCE_H
diff --git a/rockworkd/libpebble/jskit/jskitsetup.js b/rockworkd/libpebble/jskit/jskitsetup.js
new file mode 100644
index 0000000..340c4f1
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitsetup.js
@@ -0,0 +1,196 @@
+//Borrowed from https://github.com/pebble/pypkjs/blob/master/pypkjs/javascript/runtime.py#L17
+_jskit.make_proxies = function(proxy, origin, names) {
+ names.forEach(function(name) {
+ proxy[name] = eval("(function " + name + "() { return origin[name].apply(origin, arguments); })");
+ });
+
+ return proxy;
+}
+
+_jskit.make_properties = function(proxy, origin, names) {
+ names.forEach(function(name) {
+ Object.defineProperty(proxy, name, {
+ configurable: false,
+ enumerable: true,
+ get: function() {
+ return origin[name];
+ },
+ set: function(value) {
+ origin[name] = value;
+ }
+ });
+ });
+
+ return proxy;
+}
+
+Pebble = new (function() {
+ _jskit.make_proxies(this, _jskit.pebble,
+ ['sendAppMessage', 'showSimpleNotificationOnPebble', 'getAccountToken', 'getWatchToken',
+ 'addEventListener', 'removeEventListener', 'openURL', 'getTimelineToken', 'timelineSubscribe',
+ 'timelineUnsubscribe', 'timelineSubscriptions', 'getActiveWatchInfo']
+ );
+})();
+
+performance = new (function() {
+ _jskit.make_proxies(this, _jskit.performance, ['now']);
+})();
+
+function XMLHttpRequest() {
+ var xhr = _jskit.pebble.createXMLHttpRequest();
+ _jskit.make_proxies(this, xhr,
+ ['open', 'setRequestHeader', 'overrideMimeType', 'send', 'getResponseHeader',
+ 'getAllResponseHeaders', 'abort', 'addEventListener', 'removeEventListener']);
+ _jskit.make_properties(this, xhr,
+ ['readyState', 'response', 'responseText', 'responseType', 'status',
+ 'statusText', 'timeout', 'onreadystatechange', 'ontimeout', 'onload',
+ 'onloadstart', 'onloadend', 'onprogress', 'onerror', 'onabort']);
+
+ this.UNSENT = 0;
+ this.OPENED = 1;
+ this.HEADERS_RECEIVED = 2;
+ this.LOADING = 3;
+ this.DONE = 4;
+}
+
+function setInterval(func, time) {
+ return _jskit.timer.setInterval(func, time);
+}
+
+function clearInterval(id) {
+ _jskit.timer.clearInterval(id);
+}
+
+function setTimeout(func, time) {
+ return _jskit.timer.setTimeout(func, time);
+}
+
+function clearTimeout(id) {
+ _jskit.timer.clearTimeout(id);
+}
+
+navigator.geolocation = new (function() {
+ _jskit.make_proxies(this, _jskit.geolocation,
+ ['getCurrentPosition', 'watchPosition', 'clearWatch']
+ );
+})();
+
+console = new (function() {
+ _jskit.make_proxies(this, _jskit.console,
+ ['log', 'warn', 'error', 'info']
+ );
+})();
+
+/*localStorage = new (function() {
+ _jskit.make_proxies(this, _jskit.localstorage,
+ ['clear', 'getItem', 'setItem', 'removeItem', 'key']
+ );
+
+ _jskit.make_properties(this, _jskit.localstorage,
+ ['length']
+ );
+})();*/
+
+//It appears that Proxy is not available since Qt is using Javascript v5
+/*(function() {
+ var proxy = _jskit.make_proxies({}, _jskit.localstorage, ['set', 'has', 'deleteProperty', 'keys', 'enumerate']);
+ var methods = _jskit.make_proxies({}, _jskit.localstorage, ['clear', 'getItem', 'setItem', 'removeItem', 'key']);
+ proxy.get = function get(p, name) { return methods[name] || _jskit.localstorage.get(p, name); }
+ this.localStorage = Proxy.create(proxy);
+})();*/
+
+//inspired by https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage
+Object.defineProperty(window, "localStorage", new (function () {
+ var storage = {};
+ Object.defineProperty(storage, "getItem", {
+ value: function (key) {
+ var value = null;
+ if (key !== undefined && key !== null && storage[key] !== undefined) {
+ value = storage[key];
+ }
+
+ return value;
+ },
+ writable: false,
+ configurable: false,
+ enumerable: false
+ });
+
+ Object.defineProperty(storage, "key", {
+ value: function (index) {
+ return Object.keys(storage)[index];
+ },
+ writable: false,
+ configurable: false,
+ enumerable: false
+ });
+
+ Object.defineProperty(storage, "setItem", {
+ value: function (key, value) {
+ if (key !== undefined && key !== null) {
+ _jskit.localstorage.setItem(key, value);
+ storage[key] = (value && value.toString) ? value.toString() : value;
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ writable: false,
+ configurable: false,
+ enumerable: false
+ });
+
+ Object.defineProperty(storage, "length", {
+ get: function () {
+ return Object.keys(storage).length;
+ },
+ configurable: false,
+ enumerable: false
+ });
+
+ Object.defineProperty(storage, "removeItem", {
+ value: function (key) {
+ if (key && storage[key]) {
+ _jskit.localstorage.removeItem(key);
+ delete storage[key];
+
+ return true;
+ }
+ else {
+ return false;
+ }
+ },
+ writable: false,
+ configurable: false,
+ enumerable: false
+ });
+
+ Object.defineProperty(storage, "clear", {
+ value: function (key) {
+ for (var key in storage) {
+ storage.removeItem(key);
+ }
+
+ return true;
+ },
+ writable: false,
+ configurable: false,
+ enumerable: false
+ });
+
+ this.get = function () {
+ return storage;
+ };
+
+ this.configurable = false;
+ this.enumerable = true;
+})());
+
+(function() {
+ var keys = _jskit.localstorage.keys();
+ for (var index in keys) {
+ var value = _jskit.localstorage.getItem(keys[index]);
+ localStorage.setItem(keys[index], value);
+ }
+})();
diff --git a/rockworkd/libpebble/jskit/jskittimer.cpp b/rockworkd/libpebble/jskit/jskittimer.cpp
new file mode 100644
index 0000000..6ab5b4a
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskittimer.cpp
@@ -0,0 +1,77 @@
+#include <QTimerEvent>
+
+#include "jskittimer.h"
+
+JSKitTimer::JSKitTimer(QJSEngine *engine) :
+ QObject(engine),
+ l(metaObject()->className()),
+ m_engine(engine)
+{
+}
+
+int JSKitTimer::setInterval(QJSValue expression, int delay) //TODO support optional parameters
+{
+ qCDebug(l) << "Setting interval for " << delay << "ms: " << expression.toString();
+
+ if (expression.isString() || expression.isCallable()) {
+ int timerId = startTimer(delay);
+ m_intervals.insert(timerId, expression);
+
+ return timerId;
+ }
+
+ return -1;
+}
+
+void JSKitTimer::clearInterval(int timerId)
+{
+ qCDebug(l) << "Killing interval " << timerId ;
+ killTimer(timerId);
+ m_intervals.remove(timerId);
+}
+
+int JSKitTimer::setTimeout(QJSValue expression, int delay) //TODO support optional parameters
+{
+ qCDebug(l) << "Setting timeout for " << delay << "ms: " << expression.toString();
+
+ if (expression.isString() || expression.isCallable()) {
+ int timerId = startTimer(delay);
+ m_timeouts.insert(timerId, expression);
+
+ return timerId;
+ }
+
+ return -1;
+}
+
+void JSKitTimer::clearTimeout(int timerId)
+{
+ qCDebug(l) << "Killing timeout " << timerId ;
+ killTimer(timerId);
+ m_timeouts.remove(timerId);
+}
+
+void JSKitTimer::timerEvent(QTimerEvent *event)
+{
+ int id = event->timerId();
+
+ QJSValue expression; // find in either intervals or timeouts
+ if (m_intervals.contains(id)) {
+ expression = m_intervals.value(id);
+ } else if (m_timeouts.contains(id)) {
+ expression = m_timeouts.value(id);
+ killTimer(id); // timeouts don't repeat
+ } else {
+ qCWarning(l) << "Unknown timer event";
+ killTimer(id); // interval nor timeout exist. kill the timer
+
+ return;
+ }
+
+ if (expression.isCallable()) { // call it if it's a function
+ expression.call().toString();
+ }
+ else { // otherwise evaluate it
+ m_engine->evaluate(expression.toString());
+ }
+}
diff --git a/rockworkd/libpebble/jskit/jskittimer.h b/rockworkd/libpebble/jskit/jskittimer.h
new file mode 100644
index 0000000..50b394d
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskittimer.h
@@ -0,0 +1,31 @@
+#ifndef JSKITTIMER_P_H
+#define JSKITTIMER_P_H
+
+#include <QLoggingCategory>
+#include <QJSValue>
+#include <QJSEngine>
+
+class JSKitTimer : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+public:
+ explicit JSKitTimer(QJSEngine *engine);
+
+ Q_INVOKABLE int setInterval(QJSValue expression, int delay);
+ Q_INVOKABLE void clearInterval(int timerId);
+
+ Q_INVOKABLE int setTimeout(QJSValue expression, int delay);
+ Q_INVOKABLE void clearTimeout(int timerId);
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+private:
+ QJSEngine *m_engine;
+ QHash<int, QJSValue> m_intervals;
+ QHash<int, QJSValue> m_timeouts;
+};
+
+#endif // JSKITTIMER_P_H
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";
+ }
+ }
+}
diff --git a/rockworkd/libpebble/jskit/jskitxmlhttprequest.h b/rockworkd/libpebble/jskit/jskitxmlhttprequest.h
new file mode 100644
index 0000000..70b8136
--- /dev/null
+++ b/rockworkd/libpebble/jskit/jskitxmlhttprequest.h
@@ -0,0 +1,96 @@
+#ifndef JSKITXMLHTTPREQUEST_P_H
+#define JSKITXMLHTTPREQUEST_P_H
+
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QJSEngine>
+#include <QLoggingCategory>
+
+class JSKitXMLHttpRequest : public QObject
+{
+ Q_OBJECT
+ QLoggingCategory l;
+
+ Q_PROPERTY(QJSValue onload READ onload WRITE setOnload)
+ Q_PROPERTY(QJSValue onreadystatechange READ onreadystatechange WRITE setOnreadystatechange)
+ Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout)
+ Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror)
+ Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged)
+ Q_PROPERTY(uint timeout READ timeout WRITE setTimeout)
+ Q_PROPERTY(uint status READ status NOTIFY statusChanged)
+ Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
+ Q_PROPERTY(QString responseType READ responseType WRITE setResponseType)
+ Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged)
+ Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged)
+
+public:
+ explicit JSKitXMLHttpRequest(QJSEngine *engine);
+
+ enum ReadyStates {
+ UNSENT = 0,
+ OPENED = 1,
+ HEADERS_RECEIVED = 2,
+ LOADING = 3,
+ DONE = 4
+ };
+ Q_ENUMS(ReadyStates)
+
+ Q_INVOKABLE void open(const QString &method, const QString &url, bool async = true, const QString &username = QString(), const QString &password = QString());
+ Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value);
+ Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue));
+ Q_INVOKABLE void abort();
+
+ QJSValue onload() const;
+ void setOnload(const QJSValue &value);
+ QJSValue onreadystatechange() const;
+ void setOnreadystatechange(const QJSValue &value);
+ QJSValue ontimeout() const;
+ void setOntimeout(const QJSValue &value);
+ QJSValue onerror() const;
+ void setOnerror(const QJSValue &value);
+
+ uint readyState() const;
+
+ uint timeout() const;
+ void setTimeout(uint value);
+
+ uint status() const;
+ QString statusText() const;
+
+ QString responseType() const;
+ void setResponseType(const QString& type);
+
+ QJSValue response() const;
+ QString responseText() const;
+
+signals:
+ void readyStateChanged();
+ void statusChanged();
+ void statusTextChanged();
+ void responseChanged();
+ void responseTextChanged();
+
+private slots:
+ void handleReplyFinished();
+ void handleReplyError(QNetworkReply::NetworkError code);
+ void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth);
+
+private:
+ QJSEngine *m_engine;
+ QNetworkAccessManager *m_net;
+ QString m_verb;
+ bool m_async = true;
+ uint m_timeout;
+ QString m_username;
+ QString m_password;
+ QNetworkRequest m_request;
+ QNetworkReply *m_reply;
+ QString m_responseType;
+ QByteArray m_response;
+ QJSValue m_onload;
+ QJSValue m_onreadystatechange;
+ QJSValue m_ontimeout;
+ QJSValue m_onerror;
+};
+
+#endif // JSKITXMLHTTPREQUEST_P_H
diff --git a/rockworkd/libpebble/jskit/typedarray.js b/rockworkd/libpebble/jskit/typedarray.js
new file mode 100644
index 0000000..d4e00c6
--- /dev/null
+++ b/rockworkd/libpebble/jskit/typedarray.js
@@ -0,0 +1,1037 @@
+/*
+ Copyright (c) 2010, Linden Research, Inc.
+ Copyright (c) 2014, Joshua Bell
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ $/LicenseInfo$
+ */
+
+// Original can be found at:
+// https://bitbucket.org/lindenlab/llsd
+// Modifications by Joshua Bell inexorabletash@gmail.com
+// https://github.com/inexorabletash/polyfill
+
+// ES3/ES5 implementation of the Krhonos Typed Array Specification
+// Ref: http://www.khronos.org/registry/typedarray/specs/latest/
+// Date: 2011-02-01
+//
+// Variations:
+// * Allows typed_array.get/set() as alias for subscripts (typed_array[])
+// * Gradually migrating structure from Khronos spec to ES6 spec
+(function(global) {
+ 'use strict';
+ var undefined = (void 0); // Paranoia
+
+ // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to
+ // create, and consume so much memory, that the browser appears frozen.
+ var MAX_ARRAY_LENGTH = 1e5;
+
+ // Approximations of internal ECMAScript conversion functions
+ function Type(v) {
+ switch(typeof v) {
+ case 'undefined': return 'undefined';
+ case 'boolean': return 'boolean';
+ case 'number': return 'number';
+ case 'string': return 'string';
+ default: return v === null ? 'null' : 'object';
+ }
+ }
+
+ // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues:
+ function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); }
+ function IsCallable(o) { return typeof o === 'function'; }
+ function ToObject(v) {
+ if (v === null || v === undefined) throw TypeError();
+ return Object(v);
+ }
+ function ToInt32(v) { return v >> 0; }
+ function ToUint32(v) { return v >> 0; } //ROCKWORK HACK ALERT: it appears that QT doesn't do the >>> properly, using >> here instead (should be close enough)
+
+ // Snapshot intrinsics
+ var LN2 = Math.LN2,
+ abs = Math.abs,
+ floor = Math.floor,
+ log = Math.log,
+ max = Math.max,
+ min = Math.min,
+ pow = Math.pow,
+ round = Math.round;
+
+ // emulate ES5 getter/setter API using legacy APIs
+ // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
+ // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but
+ // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless)
+
+ (function() {
+ var orig = Object.defineProperty;
+ var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}());
+
+ if (!orig || dom_only) {
+ Object.defineProperty = function (o, prop, desc) {
+ // In IE8 try built-in implementation for defining properties on DOM prototypes.
+ if (orig)
+ try { return orig(o, prop, desc); } catch (_) {}
+ if (o !== Object(o))
+ throw TypeError('Object.defineProperty called on non-object');
+ if (Object.prototype.__defineGetter__ && ('get' in desc))
+ Object.prototype.__defineGetter__.call(o, prop, desc.get);
+ if (Object.prototype.__defineSetter__ && ('set' in desc))
+ Object.prototype.__defineSetter__.call(o, prop, desc.set);
+ if ('value' in desc)
+ o[prop] = desc.value;
+ return o;
+ };
+ }
+ }());
+
+ // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value)
+ // for index in 0 ... obj.length
+ function makeArrayAccessors(obj) {
+ if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill');
+
+ function makeArrayAccessor(index) {
+ Object.defineProperty(obj, index, {
+ 'get': function() { return obj._getter(index); },
+ 'set': function(v) { obj._setter(index, v); },
+ enumerable: true,
+ configurable: false
+ });
+ }
+
+ var i;
+ for (i = 0; i < obj.length; i += 1) {
+ makeArrayAccessor(i);
+ }
+ }
+
+ // Internal conversion functions:
+ // pack<Type>() - take a number (interpreted as Type), output a byte array
+ // unpack<Type>() - take a byte array, output a Type-like number
+
+ function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; }
+ function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; }
+
+ function packI8(n) { return [n & 0xff]; }
+ function unpackI8(bytes) { return as_signed(bytes[0], 8); }
+
+ function packU8(n) { return [n & 0xff]; }
+ function unpackU8(bytes) { return as_unsigned(bytes[0], 8); }
+
+ function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; }
+
+ function packI16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
+ function unpackI16(bytes) { return as_signed(bytes[1] << 8 | bytes[0], 16); }
+
+ function packU16(n) { return [n & 0xff, (n >> 8) & 0xff]; }
+ function unpackU16(bytes) { return as_unsigned(bytes[1] << 8 | bytes[0], 16); }
+
+ function packI32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
+ function unpackI32(bytes) { return as_signed(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
+
+ function packU32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; }
+ function unpackU32(bytes) { return as_unsigned(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); }
+
+ function packIEEE754(v, ebits, fbits) {
+
+ var bias = (1 << (ebits - 1)) - 1;
+
+ function roundToEven(n) {
+ var w = floor(n), f = n - w;
+ if (f < 0.5)
+ return w;
+ if (f > 0.5)
+ return w + 1;
+ return w % 2 ? w + 1 : w;
+ }
+
+ // Compute sign, exponent, fraction
+ var s, e, f;
+ if (v !== v) {
+ // NaN
+ // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
+ e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0;
+ } else if (v === Infinity || v === -Infinity) {
+ e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0;
+ } else if (v === 0) {
+ e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
+ } else {
+ s = v < 0;
+ v = abs(v);
+
+ if (v >= pow(2, 1 - bias)) {
+ // Normalized
+ e = min(floor(log(v) / LN2), 1023);
+ var significand = v / pow(2, e);
+ if (significand < 1) {
+ e -= 1;
+ significand *= 2;
+ }
+ if (significand >= 2) {
+ e += 1;
+ significand /= 2;
+ }
+ var d = pow(2, fbits);
+ f = roundToEven(significand * d) - d;
+ e += bias;
+ if (f / d >= 1) {
+ e += 1;
+ f = 0;
+ }
+ if (e > 2 * bias) {
+ // Overflow
+ e = (1 << ebits) - 1;
+ f = 0;
+ }
+ } else {
+ // Denormalized
+ e = 0;
+ f = roundToEven(v / pow(2, 1 - bias - fbits));
+ }
+ }
+
+ // Pack sign, exponent, fraction
+ var bits = [], i;
+ for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); }
+ for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); }
+ bits.push(s ? 1 : 0);
+ bits.reverse();
+ var str = bits.join('');
+
+ // Bits to bytes
+ var bytes = [];
+ while (str.length) {
+ bytes.unshift(parseInt(str.substring(0, 8), 2));
+ str = str.substring(8);
+ }
+ return bytes;
+ }
+
+ function unpackIEEE754(bytes, ebits, fbits) {
+ // Bytes to bits
+ var bits = [], i, j, b, str,
+ bias, s, e, f;
+
+ for (i = 0; i < bytes.length; ++i) {
+ b = bytes[i];
+ for (j = 8; j; j -= 1) {
+ bits.push(b % 2 ? 1 : 0); b = b >> 1;
+ }
+ }
+ bits.reverse();
+ str = bits.join('');
+
+ // Unpack sign, exponent, fraction
+ bias = (1 << (ebits - 1)) - 1;
+ s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
+ e = parseInt(str.substring(1, 1 + ebits), 2);
+ f = parseInt(str.substring(1 + ebits), 2);
+
+ // Produce number
+ if (e === (1 << ebits) - 1) {
+ return f !== 0 ? NaN : s * Infinity;
+ } else if (e > 0) {
+ // Normalized
+ return s * pow(2, e - bias) * (1 + f / pow(2, fbits));
+ } else if (f !== 0) {
+ // Denormalized
+ return s * pow(2, -(bias - 1)) * (f / pow(2, fbits));
+ } else {
+ return s < 0 ? -0 : 0;
+ }
+ }
+
+ function unpackF64(b) { return unpackIEEE754(b, 11, 52); }
+ function packF64(v) { return packIEEE754(v, 11, 52); }
+ function unpackF32(b) { return unpackIEEE754(b, 8, 23); }
+ function packF32(v) { return packIEEE754(v, 8, 23); }
+
+ //
+ // 3 The ArrayBuffer Type
+ //
+
+ (function() {
+
+ function ArrayBuffer(length) {
+ length = ToInt32(length);
+ if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.');
+ Object.defineProperty(this, 'byteLength', {value: length});
+ Object.defineProperty(this, '_bytes', {value: Array(length)});
+
+ for (var i = 0; i < length; i += 1)
+ this._bytes[i] = 0;
+ }
+
+ global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;
+
+ //
+ // 5 The Typed Array View Types
+ //
+
+ function $TypedArray$() {
+
+ // %TypedArray% ( length )
+ if (!arguments.length || typeof arguments[0] !== 'object') {
+ return (function(length) {
+ length = ToInt32(length);
+ if (length < 0) throw RangeError('length is not a small enough positive integer.');
+ Object.defineProperty(this, 'length', {value: length});
+ Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT});
+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)});
+ Object.defineProperty(this, 'byteOffset', {value: 0});
+
+ }).apply(this, arguments);
+ }
+
+ // %TypedArray% ( typedArray )
+ if (arguments.length >= 1 &&
+ Type(arguments[0]) === 'object' &&
+ arguments[0] instanceof $TypedArray$) {
+ return (function(typedArray){
+ if (this.constructor !== typedArray.constructor) throw TypeError();
+
+ var byteLength = typedArray.length * this.BYTES_PER_ELEMENT;
+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
+ Object.defineProperty(this, 'byteLength', {value: byteLength});
+ Object.defineProperty(this, 'byteOffset', {value: 0});
+ Object.defineProperty(this, 'length', {value: typedArray.length});
+
+ for (var i = 0; i < this.length; i += 1)
+ this._setter(i, typedArray._getter(i));
+
+ }).apply(this, arguments);
+ }
+
+ // %TypedArray% ( array )
+ if (arguments.length >= 1 &&
+ Type(arguments[0]) === 'object' &&
+ !(arguments[0] instanceof $TypedArray$) &&
+ !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
+ return (function(array) {
+
+ var byteLength = array.length * this.BYTES_PER_ELEMENT;
+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
+ Object.defineProperty(this, 'byteLength', {value: byteLength});
+ Object.defineProperty(this, 'byteOffset', {value: 0});
+ Object.defineProperty(this, 'length', {value: array.length});
+
+ for (var i = 0; i < this.length; i += 1) {
+ var s = array[i];
+ this._setter(i, Number(s));
+ }
+ }).apply(this, arguments);
+ }
+
+ // %TypedArray% ( buffer, byteOffset=0, length=undefined )
+ if (arguments.length >= 1 &&
+ Type(arguments[0]) === 'object' &&
+ (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
+ return (function(buffer, byteOffset, length) {
+
+ byteOffset = ToUint32(byteOffset);
+ if (byteOffset > buffer.byteLength)
+ throw RangeError('byteOffset out of range');
+
+ // The given byteOffset must be a multiple of the element
+ // size of the specific type, otherwise an exception is raised.
+ if (byteOffset % this.BYTES_PER_ELEMENT)
+ throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.');
+
+ if (length === undefined) {
+ var byteLength = buffer.byteLength - byteOffset;
+ if (byteLength % this.BYTES_PER_ELEMENT)
+ throw RangeError('length of buffer minus byteOffset not a multiple of the element size');
+ length = byteLength / this.BYTES_PER_ELEMENT;
+
+ } else {
+ length = ToUint32(length);
+ byteLength = length * this.BYTES_PER_ELEMENT;
+ }
+
+ if ((byteOffset + byteLength) > buffer.byteLength)
+ throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
+
+ Object.defineProperty(this, 'buffer', {value: buffer});
+ Object.defineProperty(this, 'byteLength', {value: byteLength});
+ Object.defineProperty(this, 'byteOffset', {value: byteOffset});
+ Object.defineProperty(this, 'length', {value: length});
+
+ }).apply(this, arguments);
+ }
+
+ // %TypedArray% ( all other argument combinations )
+ throw TypeError();
+ }
+
+ // Properties of the %TypedArray Instrinsic Object
+
+ // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined )
+ Object.defineProperty($TypedArray$, 'from', {value: function(iterable) {
+ return new this(iterable);
+ }});
+
+ // %TypedArray%.of ( ...items )
+ Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) {
+ return new this(arguments);
+ }});
+
+ // %TypedArray%.prototype
+ var $TypedArrayPrototype$ = {};
+ $TypedArray$.prototype = $TypedArrayPrototype$;
+
+ // WebIDL: getter type (unsigned long index);
+ Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) {
+ if (arguments.length < 1) throw SyntaxError('Not enough arguments');
+
+ index = ToUint32(index);
+ if (index >= this.length)
+ return undefined;
+
+ var bytes = [], i, o;
+ for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
+ i < this.BYTES_PER_ELEMENT;
+ i += 1, o += 1) {
+ bytes.push(this.buffer._bytes[o]);
+ }
+ return this._unpack(bytes);
+ }});
+
+ // NONSTANDARD: convenience alias for getter: type get(unsigned long index);
+ Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter});
+
+ // WebIDL: setter void (unsigned long index, type value);
+ Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) {
+ if (arguments.length < 2) throw SyntaxError('Not enough arguments');
+
+ index = ToUint32(index);
+ if (index >= this.length)
+ return;
+
+ var bytes = this._pack(value), i, o;
+ for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
+ i < this.BYTES_PER_ELEMENT;
+ i += 1, o += 1) {
+ this.buffer._bytes[o] = bytes[i];
+ }
+ }});
+
+ // get %TypedArray%.prototype.buffer
+ // get %TypedArray%.prototype.byteLength
+ // get %TypedArray%.prototype.byteOffset
+ // -- applied directly to the object in the constructor
+
+ // %TypedArray%.prototype.constructor
+ Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$});
+
+ // %TypedArray%.prototype.copyWithin (target, start, end = this.length )
+ Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) {
+ var end = arguments[2];
+
+ var o = ToObject(this);
+ var lenVal = o.length;
+ var len = ToUint32(lenVal);
+ len = max(len, 0);
+ var relativeTarget = ToInt32(target);
+ var to;
+ if (relativeTarget < 0)
+ to = max(len + relativeTarget, 0);
+ else
+ to = min(relativeTarget, len);
+ var relativeStart = ToInt32(start);
+ var from;
+ if (relativeStart < 0)
+ from = max(len + relativeStart, 0);
+ else
+ from = min(relativeStart, len);
+ var relativeEnd;
+ if (end === undefined)
+ relativeEnd = len;
+ else
+ relativeEnd = ToInt32(end);
+ var final;
+ if (relativeEnd < 0)
+ final = max(len + relativeEnd, 0);
+ else
+ final = min(relativeEnd, len);
+ var count = min(final - from, len - to);
+ var direction;
+ if (from < to && to < from + count) {
+ direction = -1;
+ from = from + count - 1;
+ to = to + count - 1;
+ } else {
+ direction = 1;
+ }
+ while (count > 0) {
+ o._setter(to, o._getter(from));
+ from = from + direction;
+ to = to + direction;
+ count = count - 1;
+ }
+ return o;
+ }});
+
+ // %TypedArray%.prototype.entries ( )
+ // -- defined in es6.js to shim browsers w/ native TypedArrays
+
+ // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ var thisArg = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (!callbackfn.call(thisArg, t._getter(i), i, t))
+ return false;
+ }
+ return true;
+ }});
+
+ // %TypedArray%.prototype.fill (value, start = 0, end = this.length )
+ Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) {
+ var start = arguments[1],
+ end = arguments[2];
+
+ var o = ToObject(this);
+ var lenVal = o.length;
+ var len = ToUint32(lenVal);
+ len = max(len, 0);
+ var relativeStart = ToInt32(start);
+ var k;
+ if (relativeStart < 0)
+ k = max((len + relativeStart), 0);
+ else
+ k = min(relativeStart, len);
+ var relativeEnd;
+ if (end === undefined)
+ relativeEnd = len;
+ else
+ relativeEnd = ToInt32(end);
+ var final;
+ if (relativeEnd < 0)
+ final = max((len + relativeEnd), 0);
+ else
+ final = min(relativeEnd, len);
+ while (k < final) {
+ o._setter(k, value);
+ k += 1;
+ }
+ return o;
+ }});
+
+ // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ var res = [];
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ var val = t._getter(i); // in case fun mutates this
+ if (callbackfn.call(thisp, val, i, t))
+ res.push(val);
+ }
+ return new this.constructor(res);
+ }});
+
+ // %TypedArray%.prototype.find (predicate, thisArg = undefined)
+ Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) {
+ var o = ToObject(this);
+ var lenValue = o.length;
+ var len = ToUint32(lenValue);
+ if (!IsCallable(predicate)) throw TypeError();
+ var t = arguments.length > 1 ? arguments[1] : undefined;
+ var k = 0;
+ while (k < len) {
+ var kValue = o._getter(k);
+ var testResult = predicate.call(t, kValue, k, o);
+ if (Boolean(testResult))
+ return kValue;
+ ++k;
+ }
+ return undefined;
+ }});
+
+ // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) {
+ var o = ToObject(this);
+ var lenValue = o.length;
+ var len = ToUint32(lenValue);
+ if (!IsCallable(predicate)) throw TypeError();
+ var t = arguments.length > 1 ? arguments[1] : undefined;
+ var k = 0;
+ while (k < len) {
+ var kValue = o._getter(k);
+ var testResult = predicate.call(t, kValue, k, o);
+ if (Boolean(testResult))
+ return k;
+ ++k;
+ }
+ return -1;
+ }});
+
+ // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ callbackfn.call(thisp, t._getter(i), i, t);
+ }});
+
+ // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 )
+ Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (len === 0) return -1;
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) {
+ n = 0;
+ } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
+ n = (n > 0 || -1) * floor(abs(n));
+ }
+ }
+ if (n >= len) return -1;
+ var k = n >= 0 ? n : max(len - abs(n), 0);
+ for (; k < len; k++) {
+ if (t._getter(k) === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ }});
+
+ // %TypedArray%.prototype.join ( separator )
+ Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ var tmp = Array(len);
+ for (var i = 0; i < len; ++i)
+ tmp[i] = t._getter(i);
+ return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7
+ }});
+
+ // %TypedArray%.prototype.keys ( )
+ // -- defined in es6.js to shim browsers w/ native TypedArrays
+
+ // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 )
+ Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (len === 0) return -1;
+ var n = len;
+ if (arguments.length > 1) {
+ n = Number(arguments[1]);
+ if (n !== n) {
+ n = 0;
+ } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
+ n = (n > 0 || -1) * floor(abs(n));
+ }
+ }
+ var k = n >= 0 ? min(n, len - 1) : len - abs(n);
+ for (; k >= 0; k--) {
+ if (t._getter(k) === searchElement)
+ return k;
+ }
+ return -1;
+ }});
+
+ // get %TypedArray%.prototype.length
+ // -- applied directly to the object in the constructor
+
+ // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ var res = []; res.length = len;
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ res[i] = callbackfn.call(thisp, t._getter(i), i, t);
+ return new this.constructor(res);
+ }});
+
+ // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] )
+ Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ // no value to return if no initial value and an empty array
+ if (len === 0 && arguments.length === 1) throw TypeError();
+ var k = 0;
+ var accumulator;
+ if (arguments.length >= 2) {
+ accumulator = arguments[1];
+ } else {
+ accumulator = t._getter(k++);
+ }
+ while (k < len) {
+ accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
+ k++;
+ }
+ return accumulator;
+ }});
+
+ // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] )
+ Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ // no value to return if no initial value, empty array
+ if (len === 0 && arguments.length === 1) throw TypeError();
+ var k = len - 1;
+ var accumulator;
+ if (arguments.length >= 2) {
+ accumulator = arguments[1];
+ } else {
+ accumulator = t._getter(k--);
+ }
+ while (k >= 0) {
+ accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
+ k--;
+ }
+ return accumulator;
+ }});
+
+ // %TypedArray%.prototype.reverse ( )
+ Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ var half = floor(len / 2);
+ for (var i = 0, j = len - 1; i < half; ++i, --j) {
+ var tmp = t._getter(i);
+ t._setter(i, t._getter(j));
+ t._setter(j, tmp);
+ }
+ return t;
+ }});
+
+ // %TypedArray%.prototype.set(array, offset = 0 )
+ // %TypedArray%.prototype.set(typedArray, offset = 0 )
+ // WebIDL: void set(TypedArray array, optional unsigned long offset);
+ // WebIDL: void set(sequence<type> array, optional unsigned long offset);
+ Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) {
+ if (arguments.length < 1) throw SyntaxError('Not enough arguments');
+ var array, sequence, offset, len,
+ i, s, d,
+ byteOffset, byteLength, tmp;
+
+ if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) {
+ // void set(TypedArray array, optional unsigned long offset);
+ array = arguments[0];
+ offset = ToUint32(arguments[1]);
+
+ if (offset + array.length > this.length) {
+ throw RangeError('Offset plus length of array is out of range');
+ }
+
+ byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT;
+ byteLength = array.length * this.BYTES_PER_ELEMENT;
+
+ if (array.buffer === this.buffer) {
+ tmp = [];
+ for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) {
+ tmp[i] = array.buffer._bytes[s];
+ }
+ for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) {
+ this.buffer._bytes[d] = tmp[i];
+ }
+ } else {
+ for (i = 0, s = array.byteOffset, d = byteOffset;
+ i < byteLength; i += 1, s += 1, d += 1) {
+ this.buffer._bytes[d] = array.buffer._bytes[s];
+ }
+ }
+ } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') {
+ // void set(sequence<type> array, optional unsigned long offset);
+ sequence = arguments[0];
+ len = ToUint32(sequence.length);
+ offset = ToUint32(arguments[1]);
+
+ if (offset + len > this.length) {
+ throw RangeError('Offset plus length of array is out of range');
+ }
+
+ for (i = 0; i < len; i += 1) {
+ s = sequence[i];
+ this._setter(offset + i, Number(s));
+ }
+ } else {
+ throw TypeError('Unexpected argument type(s)');
+ }
+ }});
+
+ // %TypedArray%.prototype.slice ( start, end )
+ Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) {
+ var o = ToObject(this);
+ var lenVal = o.length;
+ var len = ToUint32(lenVal);
+ var relativeStart = ToInt32(start);
+ var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len);
+ var relativeEnd = (end === undefined) ? len : ToInt32(end);
+ var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len);
+ var count = final - k;
+ var c = o.constructor;
+ var a = new c(count);
+ var n = 0;
+ while (k < final) {
+ var kValue = o._getter(k);
+ a._setter(n, kValue);
+ ++k;
+ ++n;
+ }
+ return a;
+ }});
+
+ // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined )
+ Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ if (!IsCallable(callbackfn)) throw TypeError();
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (callbackfn.call(thisp, t._getter(i), i, t)) {
+ return true;
+ }
+ }
+ return false;
+ }});
+
+ // %TypedArray%.prototype.sort ( comparefn )
+ Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) {
+ if (this === undefined || this === null) throw TypeError();
+ var t = Object(this);
+ var len = ToUint32(t.length);
+ var tmp = Array(len);
+ for (var i = 0; i < len; ++i)
+ tmp[i] = t._getter(i);
+ if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9
+ for (i = 0; i < len; ++i)
+ t._setter(i, tmp[i]);
+ return t;
+ }});
+
+ // %TypedArray%.prototype.subarray(begin = 0, end = this.length )
+ // WebIDL: TypedArray subarray(long begin, optional long end);
+ Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) {
+ function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
+
+ start = ToInt32(start);
+ end = ToInt32(end);
+
+ if (arguments.length < 1) { start = 0; }
+ if (arguments.length < 2) { end = this.length; }
+
+ if (start < 0) { start = this.length + start; }
+ if (end < 0) { end = this.length + end; }
+
+ start = clamp(start, 0, this.length);
+ end = clamp(end, 0, this.length);
+
+ var len = end - start;
+ if (len < 0) {
+ len = 0;
+ }
+
+ return new this.constructor(
+ this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len);
+ }});
+
+ // %TypedArray%.prototype.toLocaleString ( )
+ // %TypedArray%.prototype.toString ( )
+ // %TypedArray%.prototype.values ( )
+ // %TypedArray%.prototype [ @@iterator ] ( )
+ // get %TypedArray%.prototype [ @@toStringTag ]
+ // -- defined in es6.js to shim browsers w/ native TypedArrays
+
+ function makeTypedArray(elementSize, pack, unpack) {
+ // Each TypedArray type requires a distinct constructor instance with
+ // identical logic, which this produces.
+ var TypedArray = function() {
+ Object.defineProperty(this, 'constructor', {value: TypedArray});
+ $TypedArray$.apply(this, arguments);
+ makeArrayAccessors(this);
+ };
+ if ('__proto__' in TypedArray) {
+ TypedArray.__proto__ = $TypedArray$;
+ } else {
+ TypedArray.from = $TypedArray$.from;
+ TypedArray.of = $TypedArray$.of;
+ }
+
+ TypedArray.BYTES_PER_ELEMENT = elementSize;
+
+ var TypedArrayPrototype = function() {};
+ TypedArrayPrototype.prototype = $TypedArrayPrototype$;
+
+ TypedArray.prototype = new TypedArrayPrototype();
+
+ Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize});
+ Object.defineProperty(TypedArray.prototype, '_pack', {value: pack});
+ Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack});
+
+ return TypedArray;
+ }
+
+ var Int8Array = makeTypedArray(1, packI8, unpackI8);
+ var Uint8Array = makeTypedArray(1, packU8, unpackU8);
+ var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8);
+ var Int16Array = makeTypedArray(2, packI16, unpackI16);
+ var Uint16Array = makeTypedArray(2, packU16, unpackU16);
+ var Int32Array = makeTypedArray(4, packI32, unpackI32);
+ var Uint32Array = makeTypedArray(4, packU32, unpackU32);
+ var Float32Array = makeTypedArray(4, packF32, unpackF32);
+ var Float64Array = makeTypedArray(8, packF64, unpackF64);
+
+ global.Int8Array = global.Int8Array || Int8Array;
+ global.Uint8Array = global.Uint8Array || Uint8Array;
+ global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray;
+ global.Int16Array = global.Int16Array || Int16Array;
+ global.Uint16Array = global.Uint16Array || Uint16Array;
+ global.Int32Array = global.Int32Array || Int32Array;
+ global.Uint32Array = global.Uint32Array || Uint32Array;
+ global.Float32Array = global.Float32Array || Float32Array;
+ global.Float64Array = global.Float64Array || Float64Array;
+ }());
+
+ //
+ // 6 The DataView View Type
+ //
+
+ (function() {
+ function r(array, index) {
+ return IsCallable(array.get) ? array.get(index) : array[index];
+ }
+
+ var IS_BIG_ENDIAN = (function() {
+ var u16array = new Uint16Array([0x1234]),
+ u8array = new Uint8Array(u16array.buffer);
+ return r(u8array, 0) === 0x12;
+ }());
+
+ // DataView(buffer, byteOffset=0, byteLength=undefined)
+ // WebIDL: Constructor(ArrayBuffer buffer,
+ // optional unsigned long byteOffset,
+ // optional unsigned long byteLength)
+ function DataView(buffer, byteOffset, byteLength) {
+ if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError();
+
+ byteOffset = ToUint32(byteOffset);
+ if (byteOffset > buffer.byteLength)
+ throw RangeError('byteOffset out of range');
+
+ if (byteLength === undefined)
+ byteLength = buffer.byteLength - byteOffset;
+ else
+ byteLength = ToUint32(byteLength);
+
+ if ((byteOffset + byteLength) > buffer.byteLength)
+ throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
+
+ Object.defineProperty(this, 'buffer', {value: buffer});
+ Object.defineProperty(this, 'byteLength', {value: byteLength});
+ Object.defineProperty(this, 'byteOffset', {value: byteOffset});
+ };
+
+ // get DataView.prototype.buffer
+ // get DataView.prototype.byteLength
+ // get DataView.prototype.byteOffset
+ // -- applied directly to instances by the constructor
+
+ function makeGetter(arrayType) {
+ return function GetViewValue(byteOffset, littleEndian) {
+ byteOffset = ToUint32(byteOffset);
+
+ if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
+ throw RangeError('Array index out of range');
+
+ byteOffset += this.byteOffset;
+
+ var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT),
+ bytes = [];
+ for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
+ bytes.push(r(uint8Array, i));
+
+ if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
+ bytes.reverse();
+
+ return r(new arrayType(new Uint8Array(bytes).buffer), 0);
+ };
+ }
+
+ Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)});
+ Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)});
+ Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)});
+ Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)});
+ Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)});
+ Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)});
+ Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)});
+ Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)});
+
+ function makeSetter(arrayType) {
+ return function SetViewValue(byteOffset, value, littleEndian) {
+ byteOffset = ToUint32(byteOffset);
+ if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
+ throw RangeError('Array index out of range');
+
+ // Get bytes
+ var typeArray = new arrayType([value]),
+ byteArray = new Uint8Array(typeArray.buffer),
+ bytes = [], i, byteView;
+
+ for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
+ bytes.push(r(byteArray, i));
+
+ // Flip if necessary
+ if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
+ bytes.reverse();
+
+ // Write them
+ byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT);
+ byteView.set(bytes);
+ };
+ }
+
+ Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)});
+ Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)});
+ Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)});
+ Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)});
+ Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)});
+ Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)});
+ Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)});
+ Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)});
+
+ global.DataView = global.DataView || DataView;
+
+ }());
+
+}(this));