Merge lp:~bhdouglass/rockwork/gps-fix into lp:rockwork

Proposed by Brian Douglass
Status: Superseded
Proposed branch: lp:~bhdouglass/rockwork/gps-fix
Merge into: lp:rockwork
Diff against target: 5721 lines (+3075/-2444)
29 files modified
rockworkd/libpebble/jsfiles.qrc (+0/-5)
rockworkd/libpebble/jskit/cacheLocalStorage.js (+11/-0)
rockworkd/libpebble/jskit/jsfiles.qrc (+7/-0)
rockworkd/libpebble/jskit/jskitconsole.cpp (+29/-0)
rockworkd/libpebble/jskit/jskitconsole.h (+20/-0)
rockworkd/libpebble/jskit/jskitgeolocation.cpp (+302/-0)
rockworkd/libpebble/jskit/jskitgeolocation.h (+66/-0)
rockworkd/libpebble/jskit/jskitlocalstorage.cpp (+117/-0)
rockworkd/libpebble/jskit/jskitlocalstorage.h (+40/-0)
rockworkd/libpebble/jskit/jskitmanager.cpp (+240/-0)
rockworkd/libpebble/jskit/jskitmanager.h (+72/-0)
rockworkd/libpebble/jskit/jskitpebble.cpp (+355/-0)
rockworkd/libpebble/jskit/jskitpebble.h (+47/-0)
rockworkd/libpebble/jskit/jskitperformance.cpp (+13/-0)
rockworkd/libpebble/jskit/jskitperformance.h (+20/-0)
rockworkd/libpebble/jskit/jskitsetup.js (+191/-0)
rockworkd/libpebble/jskit/jskittimer.cpp (+77/-0)
rockworkd/libpebble/jskit/jskittimer.h (+31/-0)
rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp (+288/-0)
rockworkd/libpebble/jskit/jskitxmlhttprequest.h (+91/-0)
rockworkd/libpebble/jskit/typedarray.js (+1030/-0)
rockworkd/libpebble/jskitmanager.cpp (+0/-243)
rockworkd/libpebble/jskitmanager.h (+0/-65)
rockworkd/libpebble/jskitobjects.cpp (+0/-859)
rockworkd/libpebble/jskitobjects.h (+0/-235)
rockworkd/libpebble/pebble.cpp (+8/-2)
rockworkd/libpebble/pebble.h (+3/-0)
rockworkd/libpebble/typedarray.js (+0/-1030)
rockworkd/rockworkd.pro (+17/-5)
To merge this branch: bzr merge lp:~bhdouglass/rockwork/gps-fix
Reviewer Review Type Date Requested Status
Michael Zanetti Pending
Review via email: mp+285263@code.launchpad.net

This proposal has been superseded by a proposal from 2016-02-06.

Description of the change

Also includes the jskit changes.

Removes the gps source when not needed.

To post a comment you must log in.
lp:~bhdouglass/rockwork/gps-fix updated
60. By Brian Douglass

Reverted pot file changes

Unmerged revisions

57. By Launchpad Translations on behalf of mzanetti

Launchpad automatic translations update.

56. By Launchpad Translations on behalf of mzanetti

Launchpad automatic translations update.

55. By Launchpad Translations on behalf of mzanetti

Launchpad automatic translations update.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'rockworkd/libpebble/jsfiles.qrc'
2--- rockworkd/libpebble/jsfiles.qrc 2016-01-03 15:22:24 +0000
3+++ rockworkd/libpebble/jsfiles.qrc 1970-01-01 00:00:00 +0000
4@@ -1,5 +0,0 @@
5-<RCC>
6- <qresource prefix="/">
7- <file>typedarray.js</file>
8- </qresource>
9-</RCC>
10
11=== added directory 'rockworkd/libpebble/jskit'
12=== added file 'rockworkd/libpebble/jskit/cacheLocalStorage.js'
13--- rockworkd/libpebble/jskit/cacheLocalStorage.js 1970-01-01 00:00:00 +0000
14+++ rockworkd/libpebble/jskit/cacheLocalStorage.js 2016-02-06 14:32:32 +0000
15@@ -0,0 +1,11 @@
16+//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
17+
18+for (var key in localStorage) {
19+ _jskit.localstorage.setItem(key, localStorage.getItem(key));
20+}
21+
22+for (var key in _jskit.localstorage.keys()) {
23+ if (localStorage[key] === undefined) {
24+ _jskit.localstorage.removeItem(key);
25+ }
26+}
27
28=== added file 'rockworkd/libpebble/jskit/jsfiles.qrc'
29--- rockworkd/libpebble/jskit/jsfiles.qrc 1970-01-01 00:00:00 +0000
30+++ rockworkd/libpebble/jskit/jsfiles.qrc 2016-02-06 14:32:32 +0000
31@@ -0,0 +1,7 @@
32+<RCC>
33+ <qresource prefix="/">
34+ <file>typedarray.js</file>
35+ <file>jskitsetup.js</file>
36+ <file>cacheLocalStorage.js</file>
37+ </qresource>
38+</RCC>
39
40=== added file 'rockworkd/libpebble/jskit/jskitconsole.cpp'
41--- rockworkd/libpebble/jskit/jskitconsole.cpp 1970-01-01 00:00:00 +0000
42+++ rockworkd/libpebble/jskit/jskitconsole.cpp 2016-02-06 14:32:32 +0000
43@@ -0,0 +1,29 @@
44+#include <QDebug>
45+
46+#include "jskitconsole.h"
47+
48+JSKitConsole::JSKitConsole(QObject *parent) :
49+ QObject(parent),
50+ l(metaObject()->className())
51+{
52+}
53+
54+void JSKitConsole::log(const QString &msg)
55+{
56+ qCDebug(l) << msg;
57+}
58+
59+void JSKitConsole::warn(const QString &msg)
60+{
61+ qCWarning(l) << msg;
62+}
63+
64+void JSKitConsole::error(const QString &msg)
65+{
66+ qCCritical(l) << msg;
67+}
68+
69+void JSKitConsole::info(const QString &msg)
70+{
71+ qCDebug(l) << msg;
72+}
73
74=== added file 'rockworkd/libpebble/jskit/jskitconsole.h'
75--- rockworkd/libpebble/jskit/jskitconsole.h 1970-01-01 00:00:00 +0000
76+++ rockworkd/libpebble/jskit/jskitconsole.h 2016-02-06 14:32:32 +0000
77@@ -0,0 +1,20 @@
78+#ifndef JSKITCONSOLE_H
79+#define JSKITCONSOLE_H
80+
81+#include <QLoggingCategory>
82+
83+class JSKitConsole : public QObject
84+{
85+ Q_OBJECT
86+ QLoggingCategory l;
87+
88+public:
89+ explicit JSKitConsole(QObject *parent=0);
90+
91+ Q_INVOKABLE void log(const QString &msg);
92+ Q_INVOKABLE void warn(const QString &msg);
93+ Q_INVOKABLE void error(const QString &msg);
94+ Q_INVOKABLE void info(const QString &msg);
95+};
96+
97+#endif // JSKITCONSOLE_H
98
99=== added file 'rockworkd/libpebble/jskit/jskitgeolocation.cpp'
100--- rockworkd/libpebble/jskit/jskitgeolocation.cpp 1970-01-01 00:00:00 +0000
101+++ rockworkd/libpebble/jskit/jskitgeolocation.cpp 2016-02-06 14:32:32 +0000
102@@ -0,0 +1,302 @@
103+#include <limits>
104+
105+#include "jskitgeolocation.h"
106+
107+JSKitGeolocation::JSKitGeolocation(QJSEngine *engine) :
108+ QObject(engine),
109+ l(metaObject()->className()),
110+ m_engine(engine),
111+ m_source(0),
112+ m_lastWatcherId(0)
113+{
114+}
115+
116+void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
117+{
118+ setupWatcher(successCallback, errorCallback, options, true);
119+}
120+
121+int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
122+{
123+ return setupWatcher(successCallback, errorCallback, options, false);
124+}
125+
126+void JSKitGeolocation::clearWatch(int watcherId)
127+{
128+ removeWatcher(watcherId);
129+}
130+
131+void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error)
132+{
133+ qCWarning(l) << "positioning error: " << error;
134+
135+ if (m_watchers.empty()) {
136+ qCWarning(l) << "got position error but no one is watching";
137+ stopAndRemove();
138+ }
139+ else {
140+ QJSValue obj;
141+ if (error == QGeoPositionInfoSource::AccessError) {
142+ obj = buildPositionErrorObject(PERMISSION_DENIED, "permission denied");
143+ } else {
144+ obj = buildPositionErrorObject(POSITION_UNAVAILABLE, "position unavailable");
145+ }
146+
147+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
148+ invokeCallback(it->errorCallback, obj);
149+
150+ if (it->once) {
151+ it = m_watchers.erase(it);
152+ } else {
153+ it->timer.restart();
154+ ++it;
155+ }
156+ }
157+ }
158+}
159+
160+void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos)
161+{
162+ qCDebug(l) << "got position at" << pos.timestamp() << "type" << pos.coordinate().type();
163+
164+ if (m_watchers.empty()) {
165+ qCWarning(l) << "got position update but no one is watching";
166+ stopAndRemove();
167+ }
168+ else {
169+ QJSValue obj = buildPositionObject(pos);
170+
171+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
172+ invokeCallback(it->successCallback, obj);
173+
174+ if (it->once) {
175+ it = m_watchers.erase(it);
176+ } else {
177+ it->timer.restart();
178+ ++it;
179+ }
180+ }
181+ }
182+}
183+
184+void JSKitGeolocation::handleTimeout()
185+{
186+ qCDebug(l) << "positioning timeout";
187+
188+ if (m_watchers.empty()) {
189+ qCWarning(l) << "got position timeout but no one is watching";
190+ stopAndRemove();
191+ }
192+ else {
193+ QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout");
194+
195+ for (auto it = m_watchers.begin(); it != m_watchers.end(); /*no adv*/) {
196+ if (it->timer.hasExpired(it->timeout)) {
197+ qCDebug(l) << "positioning timeout for watch" << it->watcherId
198+ << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout;
199+ invokeCallback(it->errorCallback, obj);
200+
201+ if (it->once) {
202+ it = m_watchers.erase(it);
203+ } else {
204+ it->timer.restart();
205+ ++it;
206+ }
207+ } else {
208+ ++it;
209+ }
210+ }
211+
212+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
213+ }
214+}
215+
216+void JSKitGeolocation::updateTimeouts()
217+{
218+ int once_timeout = -1, updates_timeout = -1;
219+
220+ Q_FOREACH(const Watcher &watcher, m_watchers) {
221+ qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed();
222+ qCDebug(l) << "watch" << watcher.watcherId << "rem timeout" << rem_timeout;
223+
224+ if (rem_timeout >= 0) {
225+ // Make sure the limits aren't too large
226+ rem_timeout = qMin<qint64>(rem_timeout, std::numeric_limits<int>::max());
227+
228+ if (watcher.once) {
229+ once_timeout = once_timeout >= 0 ? qMin<int>(once_timeout, rem_timeout) : rem_timeout;
230+ } else {
231+ updates_timeout = updates_timeout >= 0 ? qMin<int>(updates_timeout, rem_timeout) : rem_timeout;
232+ }
233+ }
234+ }
235+
236+ if (updates_timeout >= 0) {
237+ qCDebug(l) << "setting location update interval to" << updates_timeout;
238+ m_source->setUpdateInterval(updates_timeout);
239+ m_source->startUpdates();
240+ } else {
241+ qCDebug(l) << "stopping updates";
242+ m_source->stopUpdates();
243+ }
244+
245+ if (once_timeout >= 0) {
246+ qCDebug(l) << "requesting single location update with timeout" << once_timeout;
247+ m_source->requestUpdate(once_timeout);
248+ }
249+
250+ if (once_timeout == 0 && updates_timeout == 0) {
251+ stopAndRemove();
252+ }
253+}
254+
255+int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once)
256+{
257+ Watcher watcher;
258+ watcher.successCallback = successCallback;
259+ watcher.errorCallback = errorCallback;
260+ watcher.highAccuracy = options.value("enableHighAccuracy", false).toBool();
261+ watcher.timeout = options.value("timeout", std::numeric_limits<int>::max() - 1).toInt();
262+ watcher.maximumAge = options.value("maximumAge", 0).toLongLong();
263+ watcher.once = once;
264+ watcher.watcherId = ++m_lastWatcherId;
265+
266+ qCDebug(l) << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << watcher.maximumAge << "once=" << watcher.once;
267+
268+ if (!m_source) {
269+ m_source = QGeoPositionInfoSource::createDefaultSource(this);
270+
271+ connect(m_source, static_cast<void (QGeoPositionInfoSource::*)(QGeoPositionInfoSource::Error)>(&QGeoPositionInfoSource::error),
272+ this, &JSKitGeolocation::handleError);
273+ connect(m_source, &QGeoPositionInfoSource::positionUpdated,
274+ this, &JSKitGeolocation::handlePosition);
275+ connect(m_source, &QGeoPositionInfoSource::updateTimeout,
276+ this, &JSKitGeolocation::handleTimeout);
277+ }
278+
279+ if (watcher.maximumAge > 0) {
280+ QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(watcher.maximumAge));
281+ QGeoPositionInfo pos = m_source->lastKnownPosition(watcher.highAccuracy);
282+ qCDebug(l) << "got pos timestamp" << pos.timestamp() << " but we want" << threshold;
283+
284+ if (pos.isValid() && pos.timestamp() >= threshold) {
285+ invokeCallback(watcher.successCallback, buildPositionObject(pos));
286+
287+ if (once) {
288+ return -1;
289+ }
290+ } else if (watcher.timeout == 0 && once) {
291+ // If the timeout has already expired, and we have no cached data
292+ // Do not even bother to turn on the GPS; return error object now.
293+ invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position"));
294+ return -1;
295+ }
296+ }
297+
298+ watcher.timer.start();
299+ m_watchers.append(watcher);
300+
301+ qCDebug(l) << "added new watcher" << watcher.watcherId;
302+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
303+
304+ return watcher.watcherId;
305+}
306+
307+void JSKitGeolocation::removeWatcher(int watcherId)
308+{
309+ Watcher watcher;
310+
311+ qCDebug(l) << "removing watcherId" << watcher.watcherId;
312+
313+ for (int i = 0; i < m_watchers.size(); i++) {
314+ if (m_watchers[i].watcherId == watcherId) {
315+ watcher = m_watchers.takeAt(i);
316+ break;
317+ }
318+ }
319+
320+ if (watcher.watcherId != watcherId) {
321+ qCWarning(l) << "watcherId not found";
322+ return;
323+ }
324+
325+ QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
326+}
327+
328+QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos)
329+{
330+ QJSValue obj = m_engine->newObject();
331+ QJSValue coords = m_engine->newObject();
332+ QJSValue timestamp = m_engine->toScriptValue<quint64>(pos.timestamp().toMSecsSinceEpoch());
333+
334+ coords.setProperty("latitude", m_engine->toScriptValue(pos.coordinate().latitude()));
335+ coords.setProperty("longitude", m_engine->toScriptValue(pos.coordinate().longitude()));
336+ if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) {
337+ coords.setProperty("altitude", m_engine->toScriptValue(pos.coordinate().altitude()));
338+ } else {
339+ coords.setProperty("altitude", m_engine->toScriptValue<void*>(0));
340+ }
341+
342+ coords.setProperty("accuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy)));
343+
344+ if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) {
345+ coords.setProperty("altitudeAccuracy", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy)));
346+ } else {
347+ coords.setProperty("altitudeAccuracy", m_engine->toScriptValue<void*>(0));
348+ }
349+
350+ if (pos.hasAttribute(QGeoPositionInfo::Direction)) {
351+ coords.setProperty("heading", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction)));
352+ } else {
353+ coords.setProperty("heading", m_engine->toScriptValue<void*>(0));
354+ }
355+
356+ if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) {
357+ coords.setProperty("speed", m_engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed)));
358+ } else {
359+ coords.setProperty("speed", m_engine->toScriptValue<void*>(0));
360+ }
361+
362+ obj.setProperty("coords", coords);
363+ obj.setProperty("timestamp", timestamp);
364+
365+ return obj;
366+}
367+
368+QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message)
369+{
370+ QJSValue obj = m_engine->newObject();
371+
372+ obj.setProperty("code", m_engine->toScriptValue<unsigned short>(error));
373+ obj.setProperty("message", m_engine->toScriptValue(message));
374+
375+ return obj;
376+}
377+
378+void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event)
379+{
380+ if (callback.isCallable()) {
381+ qCDebug(l) << "invoking callback" << callback.toString();
382+ QJSValue result = callback.call(QJSValueList({event}));
383+
384+ if (result.isError()) {
385+ qCWarning(l) << "error while invoking callback: " << QString("%1:%2: %3")
386+ .arg(result.property("fileName").toString())
387+ .arg(result.property("lineNumber").toInt())
388+ .arg(result.toString());
389+ }
390+ } else {
391+ qCWarning(l) << "callback is not callable";
392+ }
393+}
394+
395+void JSKitGeolocation::stopAndRemove()
396+{
397+ if (m_source) {
398+ qCDebug(l) << "removing source";
399+
400+ m_source->stopUpdates();
401+ m_source->deleteLater();
402+ m_source = 0;
403+ }
404+}
405
406=== added file 'rockworkd/libpebble/jskit/jskitgeolocation.h'
407--- rockworkd/libpebble/jskit/jskitgeolocation.h 1970-01-01 00:00:00 +0000
408+++ rockworkd/libpebble/jskit/jskitgeolocation.h 2016-02-06 14:32:32 +0000
409@@ -0,0 +1,66 @@
410+#ifndef JSKITGEOLOCATION_H
411+#define JSKITGEOLOCATION_H
412+
413+#include <QElapsedTimer>
414+#include <QGeoPositionInfoSource>
415+#include <QJSValue>
416+#include <QLoggingCategory>
417+#include <QJSEngine>
418+
419+class JSKitGeolocation : public QObject
420+{
421+ Q_OBJECT
422+ QLoggingCategory l;
423+
424+ struct Watcher;
425+
426+public:
427+ explicit JSKitGeolocation(QJSEngine *engine);
428+
429+ enum PositionError {
430+ PERMISSION_DENIED = 1,
431+ POSITION_UNAVAILABLE = 2,
432+ TIMEOUT = 3
433+ };
434+ Q_ENUMS(PositionError);
435+
436+ Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
437+ Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
438+ Q_INVOKABLE void clearWatch(int watcherId);
439+
440+private slots:
441+ void handleError(const QGeoPositionInfoSource::Error error);
442+ void handlePosition(const QGeoPositionInfo &pos);
443+ void handleTimeout();
444+ void updateTimeouts();
445+
446+private:
447+ int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once);
448+ void removeWatcher(int watcherId);
449+
450+ QJSValue buildPositionObject(const QGeoPositionInfo &pos);
451+ QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString());
452+ QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error);
453+ void invokeCallback(QJSValue callback, QJSValue event);
454+ void stopAndRemove();
455+
456+private:
457+ QJSEngine *m_engine;
458+ QGeoPositionInfoSource *m_source;
459+
460+ struct Watcher {
461+ QJSValue successCallback;
462+ QJSValue errorCallback;
463+ int watcherId;
464+ bool once;
465+ bool highAccuracy;
466+ int timeout;
467+ QElapsedTimer timer;
468+ qlonglong maximumAge;
469+ };
470+
471+ QList<Watcher> m_watchers;
472+ int m_lastWatcherId;
473+};
474+
475+#endif // JSKITGEOLOCATION_H
476
477=== added file 'rockworkd/libpebble/jskit/jskitlocalstorage.cpp'
478--- rockworkd/libpebble/jskit/jskitlocalstorage.cpp 1970-01-01 00:00:00 +0000
479+++ rockworkd/libpebble/jskit/jskitlocalstorage.cpp 2016-02-06 14:32:32 +0000
480@@ -0,0 +1,117 @@
481+#include <QDesktopServices>
482+#include <QDir>
483+#include <QDebug>
484+
485+#include "jskitlocalstorage.h"
486+
487+JSKitLocalStorage::JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid):
488+ QObject(engine),
489+ m_engine(engine),
490+ m_storage(new QSettings(getStorageFileFor(storagePath, uuid), QSettings::IniFormat, this))
491+{
492+}
493+
494+int JSKitLocalStorage::length() const
495+{
496+ return m_storage->allKeys().size();
497+}
498+
499+QJSValue JSKitLocalStorage::getItem(const QJSValue &key) const
500+{
501+ QVariant value = m_storage->value(key.toString());
502+
503+ if (value.isValid()) {
504+ return QJSValue(value.toString());
505+ } else {
506+ return QJSValue(QJSValue::NullValue);
507+ }
508+}
509+
510+bool JSKitLocalStorage::setItem(const QJSValue &key, const QJSValue &value)
511+{
512+ m_storage->setValue(key.toString(), QVariant::fromValue(value.toString()));
513+ return true;
514+}
515+
516+bool JSKitLocalStorage::removeItem(const QJSValue &key)
517+{
518+ if (m_storage->contains(key.toString())) {
519+ m_storage->remove(key.toString());
520+ return true;
521+ } else {
522+ return false;
523+ }
524+}
525+
526+void JSKitLocalStorage::clear()
527+{
528+ m_storage->clear();
529+}
530+
531+QJSValue JSKitLocalStorage::key(int index)
532+{
533+ QStringList allKeys = m_storage->allKeys();
534+ QJSValue key(QJSValue::NullValue);
535+
536+ if (allKeys.size() > index) {
537+ key = QJSValue(allKeys[index]);
538+ }
539+
540+ return key;
541+}
542+
543+QJSValue JSKitLocalStorage::get(const QJSValue &proxy, const QJSValue &key) const
544+{
545+ Q_UNUSED(proxy);
546+ return getItem(key);
547+}
548+
549+bool JSKitLocalStorage::set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value)
550+{
551+ Q_UNUSED(proxy);
552+ return setItem(key, value);
553+}
554+
555+bool JSKitLocalStorage::has(const QJSValue &proxy, const QJSValue &key)
556+{
557+ Q_UNUSED(proxy);
558+ return m_storage->contains(key.toString());
559+}
560+
561+bool JSKitLocalStorage::deleteProperty(const QJSValue &proxy, const QJSValue &key)
562+{
563+ Q_UNUSED(proxy);
564+ return removeItem(key);
565+}
566+
567+QJSValue JSKitLocalStorage::keys(const QJSValue &proxy)
568+{
569+ Q_UNUSED(proxy);
570+
571+ QStringList allKeys = m_storage->allKeys();
572+ QJSValue keyArray = m_engine->newArray(allKeys.size());
573+ for (int i = 0; i < allKeys.size(); i++) {
574+ keyArray.setProperty(i, allKeys[i]);
575+ }
576+
577+ return keyArray;
578+}
579+
580+QJSValue JSKitLocalStorage::enumerate()
581+{
582+ return keys(0);
583+}
584+
585+QString JSKitLocalStorage::getStorageFileFor(const QString &storageDir, const QUuid &uuid)
586+{
587+ QDir dataDir(storageDir + "/js-storage");
588+ if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) {
589+ qWarning() << "Error creating jskit storage dir";
590+ return QString();
591+ }
592+
593+ QString fileName = uuid.toString();
594+ fileName.remove('{');
595+ fileName.remove('}');
596+ return dataDir.absoluteFilePath(fileName + ".ini");
597+}
598
599=== added file 'rockworkd/libpebble/jskit/jskitlocalstorage.h'
600--- rockworkd/libpebble/jskit/jskitlocalstorage.h 1970-01-01 00:00:00 +0000
601+++ rockworkd/libpebble/jskit/jskitlocalstorage.h 2016-02-06 14:32:32 +0000
602@@ -0,0 +1,40 @@
603+#ifndef JSKITLOCALSTORAGE_P_H
604+#define JSKITLOCALSTORAGE_P_H
605+
606+#include <QSettings>
607+#include <QJSEngine>
608+#include <QUuid>
609+
610+class JSKitLocalStorage : public QObject
611+{
612+ Q_OBJECT
613+
614+ Q_PROPERTY(int length READ length)
615+
616+public:
617+ explicit JSKitLocalStorage(QJSEngine *engine, const QString &storagePath, const QUuid &uuid);
618+
619+ int length() const;
620+
621+ Q_INVOKABLE QJSValue getItem(const QJSValue &key) const;
622+ Q_INVOKABLE bool setItem(const QJSValue &key, const QJSValue &value);
623+ Q_INVOKABLE bool removeItem(const QJSValue &key);
624+ Q_INVOKABLE void clear();
625+ Q_INVOKABLE QJSValue key(int index);
626+
627+ Q_INVOKABLE QJSValue get(const QJSValue &proxy, const QJSValue &key) const;
628+ Q_INVOKABLE bool set(const QJSValue &proxy, const QJSValue &key, const QJSValue &value);
629+ Q_INVOKABLE bool has(const QJSValue &proxy, const QJSValue &key);
630+ Q_INVOKABLE bool deleteProperty(const QJSValue &proxy, const QJSValue &key);
631+ Q_INVOKABLE QJSValue keys(const QJSValue &proxy=0);
632+ Q_INVOKABLE QJSValue enumerate();
633+
634+private:
635+ static QString getStorageFileFor(const QString &storageDir, const QUuid &uuid);
636+
637+private:
638+ QJSEngine *m_engine;
639+ QSettings *m_storage;
640+};
641+
642+#endif // JSKITLOCALSTORAGE_P_H
643
644=== added file 'rockworkd/libpebble/jskit/jskitmanager.cpp'
645--- rockworkd/libpebble/jskit/jskitmanager.cpp 1970-01-01 00:00:00 +0000
646+++ rockworkd/libpebble/jskit/jskitmanager.cpp 2016-02-06 14:32:32 +0000
647@@ -0,0 +1,240 @@
648+#include <QFile>
649+#include <QDir>
650+
651+#include "jskitmanager.h"
652+#include "jskitpebble.h"
653+
654+JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) :
655+ QObject(parent),
656+ l(metaObject()->className()),
657+ m_pebble(pebble),
658+ m_connection(connection),
659+ m_apps(apps),
660+ m_appmsg(appmsg),
661+ m_engine(0),
662+ m_configurationUuid(0)
663+{
664+ connect(m_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted);
665+ connect(m_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped);
666+}
667+
668+JSKitManager::~JSKitManager()
669+{
670+ if (m_engine) {
671+ stopJsApp();
672+ }
673+}
674+
675+QJSEngine * JSKitManager::engine()
676+{
677+ return m_engine;
678+}
679+
680+bool JSKitManager::isJSKitAppRunning() const
681+{
682+ return m_engine != 0;
683+}
684+
685+QString JSKitManager::describeError(QJSValue error)
686+{
687+ return QString("%1:%2: %3")
688+ .arg(error.property("fileName").toString())
689+ .arg(error.property("lineNumber").toInt())
690+ .arg(error.toString());
691+}
692+
693+void JSKitManager::showConfiguration()
694+{
695+ if (m_engine) {
696+ qCDebug(l) << "requesting configuration";
697+ m_jspebble->invokeCallbacks("showConfiguration");
698+ } else {
699+ qCWarning(l) << "requested to show configuration, but JS engine is not running";
700+ }
701+}
702+
703+void JSKitManager::handleWebviewClosed(const QString &result)
704+{
705+ if (m_engine) {
706+ QJSValue eventObj = m_engine->newObject();
707+ QByteArray data = QByteArray::fromPercentEncoding(result.toUtf8());
708+ eventObj.setProperty("response", m_engine->toScriptValue(data));
709+
710+ qCDebug(l) << "Sending" << eventObj.property("response").toString();
711+ m_jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj}));
712+
713+ loadJsFile(":/cacheLocalStorage.js");
714+ } else {
715+ qCWarning(l) << "webview closed event, but JS engine is not running";
716+ }
717+}
718+
719+void JSKitManager::setConfigurationId(const QUuid &uuid)
720+{
721+ m_configurationUuid = uuid;
722+}
723+
724+AppInfo JSKitManager::currentApp()
725+{
726+ return m_curApp;
727+}
728+
729+void JSKitManager::handleAppStarted(const QUuid &uuid)
730+{
731+ AppInfo info = m_apps->info(uuid);
732+ if (!info.uuid().isNull() && info.isJSKit()) {
733+ qCDebug(l) << "Preparing to start JSKit app" << info.uuid() << info.shortName();
734+
735+ m_curApp = info;
736+ startJsApp();
737+ }
738+}
739+
740+void JSKitManager::handleAppStopped(const QUuid &uuid)
741+{
742+ if (!m_curApp.uuid().isNull()) {
743+ if (m_curApp.uuid() != uuid) {
744+ qCWarning(l) << "Closed app with invalid UUID";
745+ }
746+
747+ stopJsApp();
748+ m_curApp = AppInfo();
749+ qCDebug(l) << "App stopped" << uuid;
750+ }
751+}
752+
753+void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg)
754+{
755+ if (m_curApp.uuid() == uuid) {
756+ qCDebug(l) << "handling app message" << uuid << msg;
757+
758+ if (m_engine) {
759+ QJSValue eventObj = m_engine->newObject();
760+ eventObj.setProperty("payload", m_engine->toScriptValue(msg));
761+
762+ m_jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj}));
763+
764+ loadJsFile(":/cacheLocalStorage.js");
765+ }
766+ else {
767+ qCDebug(l) << "but engine is stopped";
768+ }
769+ }
770+}
771+
772+bool JSKitManager::loadJsFile(const QString &filename)
773+{
774+ Q_ASSERT(m_engine);
775+
776+ QFile file(filename);
777+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
778+ qCWarning(l) << "Failed to load JS file:" << file.fileName();
779+ return false;
780+ }
781+
782+ qCDebug(l) << "evaluating js file" << file.fileName();
783+
784+ QJSValue result = m_engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName());
785+ if (result.isError()) {
786+ qCWarning(l) << "error while evaluating JS script:" << describeError(result);
787+ return false;
788+ }
789+
790+ qCDebug(l) << "JS script evaluated";
791+ return true;
792+}
793+
794+void JSKitManager::startJsApp()
795+{
796+ if (m_engine) stopJsApp();
797+
798+ if (m_curApp.uuid().isNull()) {
799+ qCWarning(l) << "Attempting to start JS app with invalid UUID";
800+ return;
801+ }
802+
803+ m_engine = new QJSEngine(this);
804+ m_jspebble = new JSKitPebble(m_curApp, this, m_engine);
805+ m_jsconsole = new JSKitConsole(m_engine);
806+ m_jsstorage = new JSKitLocalStorage(m_engine, m_pebble->storagePath(), m_curApp.uuid());
807+ m_jsgeo = new JSKitGeolocation(m_engine);
808+ m_jstimer = new JSKitTimer(m_engine);
809+ m_jsperformance = new JSKitPerformance(m_engine);
810+
811+ qCDebug(l) << "starting JS app" << m_curApp.shortName();
812+
813+ QJSValue globalObj = m_engine->globalObject();
814+ QJSValue jskitObj = m_engine->newObject();
815+
816+ jskitObj.setProperty("pebble", m_engine->newQObject(m_jspebble));
817+ jskitObj.setProperty("console", m_engine->newQObject(m_jsconsole));
818+ jskitObj.setProperty("localstorage", m_engine->newQObject(m_jsstorage));
819+ jskitObj.setProperty("geolocation", m_engine->newQObject(m_jsgeo));
820+ jskitObj.setProperty("timer", m_engine->newQObject(m_jstimer));
821+ jskitObj.setProperty("performance", m_engine->newQObject(m_jsperformance));
822+ globalObj.setProperty("_jskit", jskitObj);
823+
824+ QJSValue navigatorObj = m_engine->newObject();
825+ navigatorObj.setProperty("language", m_engine->toScriptValue(QLocale().name()));
826+ globalObj.setProperty("navigator", navigatorObj);
827+
828+ // Set this.window = this
829+ globalObj.setProperty("window", globalObj);
830+
831+ // Shims for compatibility...
832+ loadJsFile(":/jskitsetup.js");
833+
834+ // Polyfills...
835+ loadJsFile(":/typedarray.js");
836+
837+ // Now the actual script
838+ QString jsApp = m_curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown);
839+ QFile f(jsApp);
840+ if (!f.open(QFile::ReadOnly)) {
841+ qCWarning(l) << "Error opening" << jsApp;
842+ return;
843+ }
844+ QJSValue ret = m_engine->evaluate(QString::fromUtf8(f.readAll()));
845+ qCDebug(l) << "loaded script" << ret.toString();
846+
847+ // Setup the message callback
848+ QUuid uuid = m_curApp.uuid();
849+ m_appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) {
850+ QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection,
851+ Q_ARG(QUuid, uuid),
852+ Q_ARG(QVariantMap, msg));
853+
854+ // Invoke the slot as a queued connection to give time for the ACK message
855+ // to go through first.
856+
857+ return true;
858+ });
859+
860+ // We try to invoke the callbacks even if script parsing resulted in error...
861+ m_jspebble->invokeCallbacks("ready");
862+
863+ loadJsFile(":/cacheLocalStorage.js");
864+
865+ if (m_configurationUuid == m_curApp.uuid()) {
866+ qCDebug(l) << "going to launch config for" << m_configurationUuid;
867+ showConfiguration();
868+ }
869+
870+ m_configurationUuid = QUuid();
871+}
872+
873+void JSKitManager::stopJsApp()
874+{
875+ qCDebug(l) << "stop js app" << m_curApp.uuid();
876+ if (!m_engine) return; // Nothing to do!
877+
878+ loadJsFile(":/cacheLocalStorage.js");
879+
880+ if (!m_curApp.uuid().isNull()) {
881+ m_appmsg->clearMessageHandler(m_curApp.uuid());
882+ }
883+
884+ m_engine->collectGarbage();
885+ m_engine->deleteLater();
886+ m_engine = 0;
887+}
888
889=== added file 'rockworkd/libpebble/jskit/jskitmanager.h'
890--- rockworkd/libpebble/jskit/jskitmanager.h 1970-01-01 00:00:00 +0000
891+++ rockworkd/libpebble/jskit/jskitmanager.h 2016-02-06 14:32:32 +0000
892@@ -0,0 +1,72 @@
893+#ifndef JSKITMANAGER_H
894+#define JSKITMANAGER_H
895+
896+#include <QJSEngine>
897+#include <QPointer>
898+#include <QLoggingCategory>
899+
900+#include "../appmanager.h"
901+#include "../watchconnection.h"
902+#include "../pebble.h"
903+#include "../appmsgmanager.h"
904+
905+#include "jskitconsole.h"
906+#include "jskitgeolocation.h"
907+#include "jskitlocalstorage.h"
908+#include "jskittimer.h"
909+#include "jskitperformance.h"
910+
911+class JSKitPebble;
912+
913+class JSKitManager : public QObject
914+{
915+ Q_OBJECT
916+ QLoggingCategory l;
917+
918+public:
919+ explicit JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0);
920+ ~JSKitManager();
921+
922+ QJSEngine * engine();
923+ bool isJSKitAppRunning() const;
924+
925+ static QString describeError(QJSValue error);
926+
927+ void showConfiguration();
928+ void handleWebviewClosed(const QString &result);
929+ void setConfigurationId(const QUuid &uuid);
930+ AppInfo currentApp();
931+
932+signals:
933+ void appNotification(const QUuid &uuid, const QString &title, const QString &body);
934+ void openURL(const QString &uuid, const QString &url);
935+
936+private slots:
937+ void handleAppStarted(const QUuid &uuid);
938+ void handleAppStopped(const QUuid &uuid);
939+ void handleAppMessage(const QUuid &uuid, const QVariantMap &msg);
940+
941+private:
942+ bool loadJsFile(const QString &filename);
943+ void startJsApp();
944+ void stopJsApp();
945+
946+private:
947+ friend class JSKitPebble;
948+
949+ Pebble *m_pebble;
950+ WatchConnection *m_connection;
951+ AppManager *m_apps;
952+ AppMsgManager *m_appmsg;
953+ AppInfo m_curApp;
954+ QJSEngine *m_engine;
955+ QPointer<JSKitPebble> m_jspebble;
956+ QPointer<JSKitConsole> m_jsconsole;
957+ QPointer<JSKitLocalStorage> m_jsstorage;
958+ QPointer<JSKitGeolocation> m_jsgeo;
959+ QPointer<JSKitTimer> m_jstimer;
960+ QPointer<JSKitPerformance> m_jsperformance;
961+ QUuid m_configurationUuid;
962+};
963+
964+#endif // JSKITMANAGER_H
965
966=== added file 'rockworkd/libpebble/jskit/jskitpebble.cpp'
967--- rockworkd/libpebble/jskit/jskitpebble.cpp 1970-01-01 00:00:00 +0000
968+++ rockworkd/libpebble/jskit/jskitpebble.cpp 2016-02-06 14:32:32 +0000
969@@ -0,0 +1,355 @@
970+#include <QUrl>
971+#include <QCryptographicHash>
972+#include <QSettings>
973+
974+#include "jskitpebble.h"
975+#include "jskitxmlhttprequest.h"
976+
977+static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1";
978+
979+JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr, QObject *parent) :
980+ QObject(parent),
981+ l(metaObject()->className()),
982+ m_appInfo(info),
983+ m_mgr(mgr)
984+{
985+}
986+
987+void JSKitPebble::addEventListener(const QString &type, QJSValue function)
988+{
989+ m_listeners[type].append(function);
990+}
991+
992+void JSKitPebble::removeEventListener(const QString &type, QJSValue function)
993+{
994+ if (!m_listeners.contains(type)) return;
995+
996+ QList<QJSValue> &callbacks = m_listeners[type];
997+ for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ) {
998+ if (it->strictlyEquals(function)) {
999+ it = callbacks.erase(it);
1000+ } else {
1001+ ++it;
1002+ }
1003+ }
1004+
1005+ if (callbacks.empty()) {
1006+ m_listeners.remove(type);
1007+ }
1008+}
1009+
1010+void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body)
1011+{
1012+ qCDebug(l) << "showSimpleNotificationOnPebble" << title << body;
1013+ emit m_mgr->appNotification(m_appInfo.uuid(), title, body);
1014+}
1015+
1016+uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack)
1017+{
1018+ QVariantMap data = message.toVariant().toMap();
1019+ QPointer<JSKitPebble> pebbObj = this;
1020+ uint transactionId = m_mgr->m_appmsg->nextTransactionId();
1021+
1022+ qCDebug(l) << "sendAppMessage" << data;
1023+
1024+ m_mgr->m_appmsg->send(
1025+ m_appInfo.uuid(),
1026+ data,
1027+ [this, pebbObj, transactionId, callbackForAck]() mutable {
1028+ if (pebbObj.isNull()) return;
1029+
1030+ if (callbackForAck.isCallable()) {
1031+ QJSValue event = pebbObj->buildAckEventObject(transactionId);
1032+ QJSValue result = callbackForAck.call(QJSValueList({event}));
1033+
1034+ if (result.isError()) {
1035+ qCWarning(l) << "error while invoking ACK callback"
1036+ << callbackForAck.toString() << ":"
1037+ << JSKitManager::describeError(result);
1038+ }
1039+ }
1040+ },
1041+ [this, pebbObj, transactionId, callbackForNack]() mutable {
1042+ if (pebbObj.isNull()) return;
1043+
1044+ if (callbackForNack.isCallable()) {
1045+ QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch");
1046+ QJSValue result = callbackForNack.call(QJSValueList({event}));
1047+
1048+ if (result.isError()) {
1049+ qCWarning(l) << "error while invoking NACK callback"
1050+ << callbackForNack.toString() << ":"
1051+ << JSKitManager::describeError(result);
1052+ }
1053+ }
1054+ }
1055+ );
1056+
1057+ return transactionId;
1058+}
1059+
1060+void JSKitPebble::getTimelineToken(QJSValue successCallback, QJSValue failureCallback)
1061+{
1062+ //TODO actually implement this
1063+ qCDebug(l) << "call to unsupported method Pebble.getTimelineToken";
1064+ Q_UNUSED(successCallback);
1065+
1066+ if (failureCallback.isCallable()) {
1067+ failureCallback.call();
1068+ }
1069+}
1070+
1071+void JSKitPebble::timelineSubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback)
1072+{
1073+ //TODO actually implement this
1074+ qCDebug(l) << "call to unsupported method Pebble.timelineSubscribe";
1075+ Q_UNUSED(topic);
1076+ Q_UNUSED(successCallback);
1077+
1078+ if (failureCallback.isCallable()) {
1079+ failureCallback.call();
1080+ }
1081+}
1082+
1083+void JSKitPebble::timelineUnsubscribe(const QString &topic, QJSValue successCallback, QJSValue failureCallback)
1084+{
1085+ //TODO actually implement this
1086+ qCDebug(l) << "call to unsupported method Pebble.timelineUnsubscribe";
1087+ Q_UNUSED(topic);
1088+ Q_UNUSED(successCallback);
1089+
1090+ if (failureCallback.isCallable()) {
1091+ failureCallback.call();
1092+ }
1093+}
1094+
1095+void JSKitPebble::timelineSubscriptions(QJSValue successCallback, QJSValue failureCallback)
1096+{
1097+ //TODO actually implement this
1098+ qCDebug(l) << "call to unsupported method Pebble.timelineSubscriptions";
1099+ Q_UNUSED(successCallback);
1100+
1101+ if (failureCallback.isCallable()) {
1102+ failureCallback.call();
1103+ }
1104+}
1105+
1106+
1107+QString JSKitPebble::getAccountToken() const
1108+{
1109+ // We do not have any account system, so we just fake something up.
1110+ QCryptographicHash hasher(QCryptographicHash::Md5);
1111+
1112+ hasher.addData(token_salt, strlen(token_salt));
1113+ hasher.addData(m_appInfo.uuid().toByteArray());
1114+
1115+ QSettings settings;
1116+ QString token = settings.value("accountToken").toString();
1117+
1118+ if (token.isEmpty()) {
1119+ token = QUuid::createUuid().toString();
1120+ qCDebug(l) << "created new account token" << token;
1121+ settings.setValue("accountToken", token);
1122+ }
1123+
1124+ hasher.addData(token.toLatin1());
1125+
1126+ QString hash = hasher.result().toHex();
1127+ qCDebug(l) << "returning account token" << hash;
1128+
1129+ return hash;
1130+}
1131+
1132+QString JSKitPebble::getWatchToken() const
1133+{
1134+ QCryptographicHash hasher(QCryptographicHash::Md5);
1135+
1136+ hasher.addData(token_salt, strlen(token_salt));
1137+ hasher.addData(m_appInfo.uuid().toByteArray());
1138+ hasher.addData(m_mgr->m_pebble->serialNumber().toLatin1());
1139+
1140+ QString hash = hasher.result().toHex();
1141+ qCDebug(l) << "returning watch token" << hash;
1142+
1143+ return hash;
1144+}
1145+
1146+QJSValue JSKitPebble::getActiveWatchInfo() const
1147+{
1148+ QJSValue watchInfo = m_mgr->m_engine->newObject();
1149+
1150+ switch (m_mgr->m_pebble->hardwarePlatform()) {
1151+ case HardwarePlatformBasalt:
1152+ watchInfo.setProperty("platform", "basalt");
1153+ break;
1154+
1155+ case HardwarePlatformChalk:
1156+ watchInfo.setProperty("platform", "chalk");
1157+ break;
1158+
1159+ default:
1160+ watchInfo.setProperty("platform", "aplite");
1161+ break;
1162+ }
1163+
1164+ switch (m_mgr->m_pebble->model()) {
1165+ case ModelTintinWhite:
1166+ watchInfo.setProperty("model", "pebble_white");
1167+ break;
1168+
1169+ case ModelTintinRed:
1170+ watchInfo.setProperty("model", "pebble_red");
1171+ break;
1172+
1173+ case ModelTintinOrange:
1174+ watchInfo.setProperty("model", "pebble_orange");
1175+ break;
1176+
1177+ case ModelTintinGrey:
1178+ watchInfo.setProperty("model", "pebble_grey");
1179+ break;
1180+
1181+ case ModelBiancaSilver:
1182+ watchInfo.setProperty("model", "pebble_steel_silver");
1183+ break;
1184+
1185+ case ModelBiancaBlack:
1186+ watchInfo.setProperty("model", "pebble_steel_black");
1187+ break;
1188+
1189+ case ModelTintinBlue:
1190+ watchInfo.setProperty("model", "pebble_blue");
1191+ break;
1192+
1193+ case ModelTintinGreen:
1194+ watchInfo.setProperty("model", "pebble_green");
1195+ break;
1196+
1197+ case ModelTintinPink:
1198+ watchInfo.setProperty("model", "pebble_pink");
1199+ break;
1200+
1201+ case ModelSnowyWhite:
1202+ watchInfo.setProperty("model", "pebble_time_white");
1203+ break;
1204+
1205+ case ModelSnowyBlack:
1206+ watchInfo.setProperty("model", "pebble_time_black");
1207+ break;
1208+
1209+ case ModelSnowyRed:
1210+ watchInfo.setProperty("model", "pebble_time_read");
1211+ break;
1212+
1213+ case ModelBobbySilver:
1214+ watchInfo.setProperty("model", "pebble_time_steel_silver");
1215+ break;
1216+
1217+ case ModelBobbyBlack:
1218+ watchInfo.setProperty("model", "pebble_time_steel_black");
1219+ break;
1220+
1221+ case ModelBobbyGold:
1222+ watchInfo.setProperty("model", "pebble_time_steel_gold");
1223+ break;
1224+
1225+ case ModelSpalding14Silver:
1226+ watchInfo.setProperty("model", "pebble_time_round_silver_14mm");
1227+ break;
1228+
1229+ case ModelSpalding14Black:
1230+ watchInfo.setProperty("model", "pebble_time_round_black_14mm");
1231+ break;
1232+
1233+ case ModelSpalding20Silver:
1234+ watchInfo.setProperty("model", "pebble_time_round_silver_20mm");
1235+ break;
1236+
1237+ case ModelSpalding20Black:
1238+ watchInfo.setProperty("model", "pebble_time_round_black_20mm");
1239+ break;
1240+
1241+ case ModelSpalding14RoseGold:
1242+ watchInfo.setProperty("model", "pebble_time_round_rose_gold_14mm");
1243+ break;
1244+
1245+ default:
1246+ watchInfo.setProperty("model", "pebble_black");
1247+ break;
1248+ }
1249+
1250+ watchInfo.setProperty("language", m_mgr->m_pebble->language());
1251+
1252+ QJSValue firmware = m_mgr->m_engine->newObject();
1253+ QString version = m_mgr->m_pebble->softwareVersion().remove("v");
1254+ QStringList versionParts = version.split(".");
1255+
1256+ if (versionParts.count() >= 1) {
1257+ firmware.setProperty("major", versionParts[0].toInt());
1258+ }
1259+
1260+ if (versionParts.count() >= 2) {
1261+ firmware.setProperty("minor", versionParts[1].toInt());
1262+ }
1263+
1264+ if (versionParts.count() >= 3) {
1265+ if (versionParts[2].contains("-")) {
1266+ QStringList patchParts = version.split("-");
1267+ firmware.setProperty("patch", patchParts[0].toInt());
1268+ firmware.setProperty("suffix", patchParts[1]);
1269+ } else {
1270+ firmware.setProperty("patch", versionParts[2].toInt());
1271+ firmware.setProperty("suffix", "");
1272+ }
1273+ }
1274+
1275+ watchInfo.setProperty("firmware", firmware);
1276+ return watchInfo;
1277+}
1278+
1279+void JSKitPebble::openURL(const QUrl &url)
1280+{
1281+ emit m_mgr->openURL(m_appInfo.uuid().toString(), url.toString());
1282+}
1283+
1284+QJSValue JSKitPebble::createXMLHttpRequest()
1285+{
1286+ JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(m_mgr->engine());
1287+ // Should be deleted by JS engine.
1288+ return m_mgr->engine()->newQObject(xhr);
1289+}
1290+
1291+QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const
1292+{
1293+ QJSEngine *engine = m_mgr->engine();
1294+ QJSValue eventObj = engine->newObject();
1295+ QJSValue dataObj = engine->newObject();
1296+
1297+ dataObj.setProperty("transactionId", engine->toScriptValue(transaction));
1298+ eventObj.setProperty("data", dataObj);
1299+
1300+ if (!message.isEmpty()) {
1301+ QJSValue errorObj = engine->newObject();
1302+
1303+ errorObj.setProperty("message", engine->toScriptValue(message));
1304+ eventObj.setProperty("error", errorObj);
1305+ }
1306+
1307+ return eventObj;
1308+}
1309+
1310+void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args)
1311+{
1312+ if (!m_listeners.contains(type)) return;
1313+ QList<QJSValue> &callbacks = m_listeners[type];
1314+
1315+ for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ++it) {
1316+ qCDebug(l) << "invoking callback" << type << it->toString();
1317+ QJSValue result = it->call(args);
1318+ if (result.isError()) {
1319+ qCWarning(l) << "error while invoking callback"
1320+ << type << it->toString() << ":"
1321+ << JSKitManager::describeError(result);
1322+ }
1323+ }
1324+}
1325
1326=== added file 'rockworkd/libpebble/jskit/jskitpebble.h'
1327--- rockworkd/libpebble/jskit/jskitpebble.h 1970-01-01 00:00:00 +0000
1328+++ rockworkd/libpebble/jskit/jskitpebble.h 2016-02-06 14:32:32 +0000
1329@@ -0,0 +1,47 @@
1330+#ifndef JSKITPEBBLE_P_H
1331+#define JSKITPEBBLE_P_H
1332+
1333+#include <QLoggingCategory>
1334+
1335+#include "jskitmanager.h"
1336+#include "../appinfo.h"
1337+
1338+class JSKitPebble : public QObject
1339+{
1340+ Q_OBJECT
1341+ QLoggingCategory l;
1342+
1343+public:
1344+ explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr, QObject *parent=0);
1345+
1346+ Q_INVOKABLE void addEventListener(const QString &type, QJSValue function);
1347+ Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function);
1348+
1349+ Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body);
1350+ Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue());
1351+
1352+ Q_INVOKABLE void getTimelineToken(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
1353+ Q_INVOKABLE void timelineSubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
1354+ Q_INVOKABLE void timelineUnsubscribe(const QString &topic, QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
1355+ Q_INVOKABLE void timelineSubscriptions(QJSValue successCallback = QJSValue(), QJSValue failureCallback = QJSValue());
1356+
1357+ Q_INVOKABLE QString getAccountToken() const;
1358+ Q_INVOKABLE QString getWatchToken() const;
1359+ Q_INVOKABLE QJSValue getActiveWatchInfo() const;
1360+
1361+ Q_INVOKABLE void openURL(const QUrl &url);
1362+
1363+ Q_INVOKABLE QJSValue createXMLHttpRequest();
1364+
1365+ void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList());
1366+
1367+private:
1368+ QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const;
1369+
1370+private:
1371+ AppInfo m_appInfo;
1372+ JSKitManager *m_mgr;
1373+ QHash<QString, QList<QJSValue>> m_listeners;
1374+};
1375+
1376+#endif // JSKITPEBBLE_P_H
1377
1378=== added file 'rockworkd/libpebble/jskit/jskitperformance.cpp'
1379--- rockworkd/libpebble/jskit/jskitperformance.cpp 1970-01-01 00:00:00 +0000
1380+++ rockworkd/libpebble/jskit/jskitperformance.cpp 2016-02-06 14:32:32 +0000
1381@@ -0,0 +1,13 @@
1382+#include "jskitperformance.h"
1383+
1384+JSKitPerformance::JSKitPerformance(QObject *parent) :
1385+ QObject(parent),
1386+ m_start(QTime::currentTime())
1387+{
1388+}
1389+
1390+int JSKitPerformance::now()
1391+{
1392+ QTime now = QTime::currentTime();
1393+ return m_start.msecsTo(now);
1394+}
1395
1396=== added file 'rockworkd/libpebble/jskit/jskitperformance.h'
1397--- rockworkd/libpebble/jskit/jskitperformance.h 1970-01-01 00:00:00 +0000
1398+++ rockworkd/libpebble/jskit/jskitperformance.h 2016-02-06 14:32:32 +0000
1399@@ -0,0 +1,20 @@
1400+#ifndef JSKITPERFORMANCE_H
1401+#define JSKITPERFORMANCE_H
1402+
1403+#include <QObject>
1404+#include <QTime>
1405+
1406+class JSKitPerformance : public QObject
1407+{
1408+ Q_OBJECT
1409+
1410+public:
1411+ explicit JSKitPerformance(QObject *parent=0);
1412+
1413+ Q_INVOKABLE int now();
1414+
1415+private:
1416+ QTime m_start;
1417+};
1418+
1419+#endif // JSKITPERFORMANCE_H
1420
1421=== added file 'rockworkd/libpebble/jskit/jskitsetup.js'
1422--- rockworkd/libpebble/jskit/jskitsetup.js 1970-01-01 00:00:00 +0000
1423+++ rockworkd/libpebble/jskit/jskitsetup.js 2016-02-06 14:32:32 +0000
1424@@ -0,0 +1,191 @@
1425+//Borrowed from https://github.com/pebble/pypkjs/blob/master/pypkjs/javascript/runtime.py#L17
1426+_jskit.make_proxies = function(proxy, origin, names) {
1427+ names.forEach(function(name) {
1428+ proxy[name] = eval("(function " + name + "() { return origin[name].apply(origin, arguments); })");
1429+ });
1430+
1431+ return proxy;
1432+}
1433+
1434+_jskit.make_properties = function(proxy, origin, names) {
1435+ names.forEach(function(name) {
1436+ Object.defineProperty(proxy, name, {
1437+ configurable: false,
1438+ enumerable: true,
1439+ get: function() {
1440+ return origin[name];
1441+ },
1442+ set: function(value) {
1443+ origin[name] = value;
1444+ }
1445+ });
1446+ });
1447+
1448+ return proxy;
1449+}
1450+
1451+Pebble = new (function() {
1452+ _jskit.make_proxies(this, _jskit.pebble,
1453+ ['sendAppMessage', 'showSimpleNotificationOnPebble', 'getAccountToken', 'getWatchToken',
1454+ 'addEventListener', 'removeEventListener', 'openURL', 'getTimelineToken', 'timelineSubscribe',
1455+ 'timelineUnsubscribe', 'timelineSubscriptions', 'getActiveWatchInfo']
1456+ );
1457+})();
1458+
1459+performance = new (function() {
1460+ _jskit.make_proxies(this, _jskit.performance, ['now']);
1461+})();
1462+
1463+function XMLHttpRequest() {
1464+ var xhr = _jskit.pebble.createXMLHttpRequest();
1465+ _jskit.make_proxies(this, xhr,
1466+ ['open', 'setRequestHeader', 'overrideMimeType', 'send', 'getResponseHeader',
1467+ 'getAllResponseHeaders', 'abort', 'addEventListener', 'removeEventListener']);
1468+ _jskit.make_properties(this, xhr,
1469+ ['readyState', 'response', 'responseText', 'responseType', 'status',
1470+ 'statusText', 'timeout', 'onreadystatechange', 'ontimeout', 'onload',
1471+ 'onloadstart', 'onloadend', 'onprogress', 'onerror', 'onabort']);
1472+
1473+ this.UNSENT = 0;
1474+ this.OPENED = 1;
1475+ this.HEADERS_RECEIVED = 2;
1476+ this.LOADING = 3;
1477+ this.DONE = 4;
1478+}
1479+
1480+function setInterval(func, time) {
1481+ return _jskit.timer.setInterval(func, time);
1482+}
1483+
1484+function clearInterval(id) {
1485+ _jskit.timer.clearInterval(id);
1486+}
1487+
1488+function setTimeout(func, time) {
1489+ return _jskit.timer.setTimeout(func, time);
1490+}
1491+
1492+function clearTimeout(id) {
1493+ _jskit.timer.clearTimeout(id);
1494+}
1495+
1496+navigator.geolocation = new (function() {
1497+ _jskit.make_proxies(this, _jskit.geolocation,
1498+ ['getCurrentPosition', 'watchPosition', 'clearWatch']
1499+ );
1500+})();
1501+
1502+console = new (function() {
1503+ _jskit.make_proxies(this, _jskit.console,
1504+ ['log', 'warn', 'error', 'info']
1505+ );
1506+})();
1507+
1508+/*localStorage = new (function() {
1509+ _jskit.make_proxies(this, _jskit.localstorage,
1510+ ['clear', 'getItem', 'setItem', 'removeItem', 'key']
1511+ );
1512+
1513+ _jskit.make_properties(this, _jskit.localstorage,
1514+ ['length']
1515+ );
1516+})();*/
1517+
1518+//It appears that Proxy is not available since Qt is using Javascript v5
1519+/*(function() {
1520+ var proxy = _jskit.make_proxies({}, _jskit.localstorage, ['set', 'has', 'deleteProperty', 'keys', 'enumerate']);
1521+ var methods = _jskit.make_proxies({}, _jskit.localstorage, ['clear', 'getItem', 'setItem', 'removeItem', 'key']);
1522+ proxy.get = function get(p, name) { return methods[name] || _jskit.localstorage.get(p, name); }
1523+ this.localStorage = Proxy.create(proxy);
1524+})();*/
1525+
1526+//inspired by https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage
1527+Object.defineProperty(window, "localStorage", new (function () {
1528+ var storage = {};
1529+ Object.defineProperty(storage, "getItem", {
1530+ value: function (key) {
1531+ return (key && storage[key]) ? storage[key] : null;
1532+ },
1533+ writable: false,
1534+ configurable: false,
1535+ enumerable: false
1536+ });
1537+
1538+ Object.defineProperty(storage, "key", {
1539+ value: function (index) {
1540+ return Object.keys(storage)[index];
1541+ },
1542+ writable: false,
1543+ configurable: false,
1544+ enumerable: false
1545+ });
1546+
1547+ Object.defineProperty(storage, "setItem", {
1548+ value: function (key, value) {
1549+ if (key) {
1550+ _jskit.localstorage.setItem(key, value);
1551+ storage[key] = (value && value.toString) ? value.toString() : value;
1552+ return true;
1553+ }
1554+ else {
1555+ return false;
1556+ }
1557+ },
1558+ writable: false,
1559+ configurable: false,
1560+ enumerable: false
1561+ });
1562+
1563+ Object.defineProperty(storage, "length", {
1564+ get: function () {
1565+ return Object.keys(storage).length;
1566+ },
1567+ configurable: false,
1568+ enumerable: false
1569+ });
1570+
1571+ Object.defineProperty(storage, "removeItem", {
1572+ value: function (key) {
1573+ if (key && storage[key]) {
1574+ _jskit.localstorage.removeItem(key);
1575+ delete storage[key];
1576+
1577+ return true;
1578+ }
1579+ else {
1580+ return false;
1581+ }
1582+ },
1583+ writable: false,
1584+ configurable: false,
1585+ enumerable: false
1586+ });
1587+
1588+ Object.defineProperty(storage, "clear", {
1589+ value: function (key) {
1590+ for (var key in storage) {
1591+ storage.removeItem(key);
1592+ }
1593+
1594+ return true;
1595+ },
1596+ writable: false,
1597+ configurable: false,
1598+ enumerable: false
1599+ });
1600+
1601+ this.get = function () {
1602+ return storage;
1603+ };
1604+
1605+ this.configurable = false;
1606+ this.enumerable = true;
1607+})());
1608+
1609+(function() {
1610+ var keys = _jskit.localstorage.keys();
1611+ for (var index in keys) {
1612+ var value = _jskit.localstorage.getItem(keys[index]);
1613+ localStorage.setItem(keys[index], value);
1614+ }
1615+})();
1616
1617=== added file 'rockworkd/libpebble/jskit/jskittimer.cpp'
1618--- rockworkd/libpebble/jskit/jskittimer.cpp 1970-01-01 00:00:00 +0000
1619+++ rockworkd/libpebble/jskit/jskittimer.cpp 2016-02-06 14:32:32 +0000
1620@@ -0,0 +1,77 @@
1621+#include <QTimerEvent>
1622+
1623+#include "jskittimer.h"
1624+
1625+JSKitTimer::JSKitTimer(QJSEngine *engine) :
1626+ QObject(engine),
1627+ l(metaObject()->className()),
1628+ m_engine(engine)
1629+{
1630+}
1631+
1632+int JSKitTimer::setInterval(QJSValue expression, int delay) //TODO support optional parameters
1633+{
1634+ qCDebug(l) << "Setting interval for " << delay << "ms: " << expression.toString();
1635+
1636+ if (expression.isString() || expression.isCallable()) {
1637+ int timerId = startTimer(delay);
1638+ m_intervals.insert(timerId, expression);
1639+
1640+ return timerId;
1641+ }
1642+
1643+ return -1;
1644+}
1645+
1646+void JSKitTimer::clearInterval(int timerId)
1647+{
1648+ qCDebug(l) << "Killing interval " << timerId ;
1649+ killTimer(timerId);
1650+ m_intervals.remove(timerId);
1651+}
1652+
1653+int JSKitTimer::setTimeout(QJSValue expression, int delay) //TODO support optional parameters
1654+{
1655+ qCDebug(l) << "Setting timeout for " << delay << "ms: " << expression.toString();
1656+
1657+ if (expression.isString() || expression.isCallable()) {
1658+ int timerId = startTimer(delay);
1659+ m_timeouts.insert(timerId, expression);
1660+
1661+ return timerId;
1662+ }
1663+
1664+ return -1;
1665+}
1666+
1667+void JSKitTimer::clearTimeout(int timerId)
1668+{
1669+ qCDebug(l) << "Killing timeout " << timerId ;
1670+ killTimer(timerId);
1671+ m_timeouts.remove(timerId);
1672+}
1673+
1674+void JSKitTimer::timerEvent(QTimerEvent *event)
1675+{
1676+ int id = event->timerId();
1677+
1678+ QJSValue expression; // find in either intervals or timeouts
1679+ if (m_intervals.contains(id)) {
1680+ expression = m_intervals.value(id);
1681+ } else if (m_timeouts.contains(id)) {
1682+ expression = m_timeouts.value(id);
1683+ killTimer(id); // timeouts don't repeat
1684+ } else {
1685+ qCWarning(l) << "Unknown timer event";
1686+ killTimer(id); // interval nor timeout exist. kill the timer
1687+
1688+ return;
1689+ }
1690+
1691+ if (expression.isCallable()) { // call it if it's a function
1692+ expression.call().toString();
1693+ }
1694+ else { // otherwise evaluate it
1695+ m_engine->evaluate(expression.toString());
1696+ }
1697+}
1698
1699=== added file 'rockworkd/libpebble/jskit/jskittimer.h'
1700--- rockworkd/libpebble/jskit/jskittimer.h 1970-01-01 00:00:00 +0000
1701+++ rockworkd/libpebble/jskit/jskittimer.h 2016-02-06 14:32:32 +0000
1702@@ -0,0 +1,31 @@
1703+#ifndef JSKITTIMER_P_H
1704+#define JSKITTIMER_P_H
1705+
1706+#include <QLoggingCategory>
1707+#include <QJSValue>
1708+#include <QJSEngine>
1709+
1710+class JSKitTimer : public QObject
1711+{
1712+ Q_OBJECT
1713+ QLoggingCategory l;
1714+
1715+public:
1716+ explicit JSKitTimer(QJSEngine *engine);
1717+
1718+ Q_INVOKABLE int setInterval(QJSValue expression, int delay);
1719+ Q_INVOKABLE void clearInterval(int timerId);
1720+
1721+ Q_INVOKABLE int setTimeout(QJSValue expression, int delay);
1722+ Q_INVOKABLE void clearTimeout(int timerId);
1723+
1724+protected:
1725+ void timerEvent(QTimerEvent *event);
1726+
1727+private:
1728+ QJSEngine *m_engine;
1729+ QHash<int, QJSValue> m_intervals;
1730+ QHash<int, QJSValue> m_timeouts;
1731+};
1732+
1733+#endif // JSKITTIMER_P_H
1734
1735=== added file 'rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp'
1736--- rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp 1970-01-01 00:00:00 +0000
1737+++ rockworkd/libpebble/jskit/jskitxmlhttprequest.cpp 2016-02-06 14:32:32 +0000
1738@@ -0,0 +1,288 @@
1739+#include <QBuffer>
1740+#include <QAuthenticator>
1741+
1742+#include "jskitxmlhttprequest.h"
1743+#include "jskitmanager.h"
1744+
1745+JSKitXMLHttpRequest::JSKitXMLHttpRequest(QJSEngine *engine) :
1746+ QObject(engine),
1747+ l(metaObject()->className()),
1748+ m_engine(engine),
1749+ m_net(new QNetworkAccessManager(this)),
1750+ m_timeout(0),
1751+ m_reply(0)
1752+{
1753+ connect(m_net, &QNetworkAccessManager::authenticationRequired,
1754+ this, &JSKitXMLHttpRequest::handleAuthenticationRequired);
1755+}
1756+
1757+void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password)
1758+{
1759+ if (m_reply) {
1760+ m_reply->deleteLater();
1761+ m_reply = 0;
1762+ }
1763+
1764+ m_username = username;
1765+ m_password = password;
1766+ m_request = QNetworkRequest(QUrl(url));
1767+ m_verb = method;
1768+ Q_UNUSED(async);
1769+
1770+ qCDebug(l) << "opened to URL" << m_request.url().toString();
1771+}
1772+
1773+void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value)
1774+{
1775+ qCDebug(l) << "setRequestHeader" << header << value;
1776+ m_request.setRawHeader(header.toLatin1(), value.toLatin1());
1777+}
1778+
1779+void JSKitXMLHttpRequest::send(const QJSValue &data)
1780+{
1781+ QByteArray byteData;
1782+
1783+ if (data.isUndefined() || data.isNull()) {
1784+ // Do nothing, byteData is empty.
1785+ } else if (data.isString()) {
1786+ byteData = data.toString().toUtf8();
1787+ } else if (data.isObject()) {
1788+ if (data.hasProperty("byteLength")) {
1789+ // Looks like an ArrayView or an ArrayBufferView!
1790+ QJSValue buffer = data.property("buffer");
1791+ if (buffer.isUndefined()) {
1792+ // We must assume we've been passed an ArrayBuffer directly
1793+ buffer = data;
1794+ }
1795+
1796+ QJSValue array = data.property("_bytes");
1797+ int byteLength = data.property("byteLength").toInt();
1798+
1799+ if (array.isArray()) {
1800+ byteData.reserve(byteLength);
1801+
1802+ for (int i = 0; i < byteLength; i++) {
1803+ byteData.append(array.property(i).toInt());
1804+ }
1805+
1806+ qCDebug(l) << "passed an ArrayBufferView of" << byteData.length() << "bytes";
1807+ } else {
1808+ qCWarning(l) << "passed an unknown/invalid ArrayBuffer" << data.toString();
1809+ }
1810+ } else {
1811+ qCWarning(l) << "passed an unknown object" << data.toString();
1812+ }
1813+
1814+ }
1815+
1816+ QBuffer *buffer;
1817+ if (!byteData.isEmpty()) {
1818+ buffer = new QBuffer;
1819+ buffer->setData(byteData);
1820+ } else {
1821+ buffer = 0;
1822+ }
1823+
1824+ qCDebug(l) << "sending" << m_verb << "to" << m_request.url() << "with" << QString::fromUtf8(byteData);
1825+ m_reply = m_net->sendCustomRequest(m_request, m_verb.toLatin1(), buffer);
1826+
1827+ connect(m_reply, &QNetworkReply::finished,
1828+ this, &JSKitXMLHttpRequest::handleReplyFinished);
1829+ connect(m_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
1830+ this, &JSKitXMLHttpRequest::handleReplyError);
1831+
1832+ if (buffer) {
1833+ // So that it gets deleted alongside the reply object.
1834+ buffer->setParent(m_reply);
1835+ }
1836+}
1837+
1838+void JSKitXMLHttpRequest::abort()
1839+{
1840+ if (m_reply) {
1841+ m_reply->deleteLater();
1842+ m_reply = 0;
1843+ }
1844+}
1845+
1846+QJSValue JSKitXMLHttpRequest::onload() const
1847+{
1848+ return m_onload;
1849+}
1850+
1851+void JSKitXMLHttpRequest::setOnload(const QJSValue &value)
1852+{
1853+ m_onload = value;
1854+}
1855+
1856+QJSValue JSKitXMLHttpRequest::ontimeout() const
1857+{
1858+ return m_ontimeout;
1859+}
1860+
1861+void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value)
1862+{
1863+ m_ontimeout = value;
1864+}
1865+
1866+QJSValue JSKitXMLHttpRequest::onerror() const
1867+{
1868+ return m_onerror;
1869+}
1870+
1871+void JSKitXMLHttpRequest::setOnerror(const QJSValue &value)
1872+{
1873+ m_onerror = value;
1874+}
1875+
1876+uint JSKitXMLHttpRequest::readyState() const
1877+{
1878+ if (!m_reply) {
1879+ return UNSENT;
1880+ } else if (m_reply->isFinished()) {
1881+ return DONE;
1882+ } else {
1883+ return LOADING;
1884+ }
1885+}
1886+
1887+uint JSKitXMLHttpRequest::timeout() const
1888+{
1889+ return m_timeout;
1890+}
1891+
1892+void JSKitXMLHttpRequest::setTimeout(uint value)
1893+{
1894+ m_timeout = value;
1895+ // TODO Handle fetch in-progress.
1896+}
1897+
1898+uint JSKitXMLHttpRequest::status() const
1899+{
1900+ if (!m_reply || !m_reply->isFinished()) {
1901+ return 0;
1902+ } else {
1903+ return m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt();
1904+ }
1905+}
1906+
1907+QString JSKitXMLHttpRequest::statusText() const
1908+{
1909+ if (!m_reply || !m_reply->isFinished()) {
1910+ return QString();
1911+ } else {
1912+ return m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
1913+ }
1914+}
1915+
1916+QString JSKitXMLHttpRequest::responseType() const
1917+{
1918+ return m_responseType;
1919+}
1920+
1921+void JSKitXMLHttpRequest::setResponseType(const QString &type)
1922+{
1923+ qCDebug(l) << "response type set to" << type;
1924+ m_responseType = type;
1925+}
1926+
1927+QJSValue JSKitXMLHttpRequest::response() const
1928+{
1929+ if (m_responseType.isEmpty() || m_responseType == "text") {
1930+ return m_engine->toScriptValue(QString::fromUtf8(m_response));
1931+ } else if (m_responseType == "arraybuffer") {
1932+ QJSValue arrayBufferProto = m_engine->globalObject().property("ArrayBuffer").property("prototype");
1933+ QJSValue arrayBuf = m_engine->newObject();
1934+
1935+ if (!arrayBufferProto.isUndefined()) {
1936+ arrayBuf.setPrototype(arrayBufferProto);
1937+ arrayBuf.setProperty("byteLength", m_engine->toScriptValue<uint>(m_response.size()));
1938+
1939+ QJSValue array = m_engine->newArray(m_response.size());
1940+ for (int i = 0; i < m_response.size(); i++) {
1941+ array.setProperty(i, m_engine->toScriptValue<int>(m_response[i]));
1942+ }
1943+
1944+ arrayBuf.setProperty("_bytes", array);
1945+ qCDebug(l) << "returning ArrayBuffer of" << m_response.size() << "bytes";
1946+ } else {
1947+ qCWarning(l) << "Cannot find proto of ArrayBuffer";
1948+ }
1949+
1950+ return arrayBuf;
1951+ } else {
1952+ qCWarning(l) << "unsupported responseType:" << m_responseType;
1953+ return m_engine->toScriptValue<void*>(0);
1954+ }
1955+}
1956+
1957+QString JSKitXMLHttpRequest::responseText() const
1958+{
1959+ return QString::fromUtf8(m_response);
1960+}
1961+
1962+void JSKitXMLHttpRequest::handleReplyFinished()
1963+{
1964+ if (!m_reply) {
1965+ qCDebug(l) << "reply finished too late";
1966+ return;
1967+ }
1968+
1969+ m_response = m_reply->readAll();
1970+ qCDebug(l) << "reply finished, reply text:" << QString::fromUtf8(m_response);
1971+
1972+ emit readyStateChanged();
1973+ emit statusChanged();
1974+ emit statusTextChanged();
1975+ emit responseChanged();
1976+ emit responseTextChanged();
1977+
1978+ if (m_onload.isCallable()) {
1979+ qCDebug(l) << "going to call onload handler:" << m_onload.toString();
1980+
1981+ QJSValue result = m_onload.callWithInstance(m_engine->newQObject(this));
1982+ if (result.isError()) {
1983+ qCWarning(l) << "JS error on onload handler:" << JSKitManager::describeError(result);
1984+ }
1985+ } else {
1986+ qCDebug(l) << "No onload set";
1987+ }
1988+}
1989+
1990+void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code)
1991+{
1992+ if (!m_reply) {
1993+ qCDebug(l) << "reply error too late";
1994+ return;
1995+ }
1996+
1997+ qCDebug(l) << "reply error" << code;
1998+
1999+ emit readyStateChanged();
2000+ emit statusChanged();
2001+ emit statusTextChanged();
2002+
2003+ if (m_onerror.isCallable()) {
2004+ qCDebug(l) << "going to call onerror handler:" << m_onload.toString();
2005+ QJSValue result = m_onerror.callWithInstance(m_engine->newQObject(this));
2006+ if (result.isError()) {
2007+ qCWarning(l) << "JS error on onerror handler:" << JSKitManager::describeError(result);
2008+ }
2009+ }
2010+}
2011+
2012+void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth)
2013+{
2014+ if (m_reply == reply) {
2015+ qCDebug(l) << "authentication required";
2016+
2017+ if (!m_username.isEmpty() || !m_password.isEmpty()) {
2018+ qCDebug(l) << "using provided authorization:" << m_username;
2019+
2020+ auth->setUser(m_username);
2021+ auth->setPassword(m_password);
2022+ } else {
2023+ qCDebug(l) << "no username or password provided";
2024+ }
2025+ }
2026+}
2027
2028=== added file 'rockworkd/libpebble/jskit/jskitxmlhttprequest.h'
2029--- rockworkd/libpebble/jskit/jskitxmlhttprequest.h 1970-01-01 00:00:00 +0000
2030+++ rockworkd/libpebble/jskit/jskitxmlhttprequest.h 2016-02-06 14:32:32 +0000
2031@@ -0,0 +1,91 @@
2032+#ifndef JSKITXMLHTTPREQUEST_P_H
2033+#define JSKITXMLHTTPREQUEST_P_H
2034+
2035+#include <QNetworkRequest>
2036+#include <QNetworkReply>
2037+#include <QJSEngine>
2038+#include <QLoggingCategory>
2039+
2040+class JSKitXMLHttpRequest : public QObject
2041+{
2042+ Q_OBJECT
2043+ QLoggingCategory l;
2044+
2045+ Q_PROPERTY(QJSValue onload READ onload WRITE setOnload)
2046+ Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout)
2047+ Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror)
2048+ Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged)
2049+ Q_PROPERTY(uint timeout READ timeout WRITE setTimeout)
2050+ Q_PROPERTY(uint status READ status NOTIFY statusChanged)
2051+ Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
2052+ Q_PROPERTY(QString responseType READ responseType WRITE setResponseType)
2053+ Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged)
2054+ Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged)
2055+
2056+public:
2057+ explicit JSKitXMLHttpRequest(QJSEngine *engine);
2058+
2059+ enum ReadyStates {
2060+ UNSENT = 0,
2061+ OPENED = 1,
2062+ HEADERS_RECEIVED = 2,
2063+ LOADING = 3,
2064+ DONE = 4
2065+ };
2066+ Q_ENUMS(ReadyStates)
2067+
2068+ Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false, const QString &username = QString(), const QString &password = QString());
2069+ Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value);
2070+ Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue));
2071+ Q_INVOKABLE void abort();
2072+
2073+ QJSValue onload() const;
2074+ void setOnload(const QJSValue &value);
2075+ QJSValue ontimeout() const;
2076+ void setOntimeout(const QJSValue &value);
2077+ QJSValue onerror() const;
2078+ void setOnerror(const QJSValue &value);
2079+
2080+ uint readyState() const;
2081+
2082+ uint timeout() const;
2083+ void setTimeout(uint value);
2084+
2085+ uint status() const;
2086+ QString statusText() const;
2087+
2088+ QString responseType() const;
2089+ void setResponseType(const QString& type);
2090+
2091+ QJSValue response() const;
2092+ QString responseText() const;
2093+
2094+signals:
2095+ void readyStateChanged();
2096+ void statusChanged();
2097+ void statusTextChanged();
2098+ void responseChanged();
2099+ void responseTextChanged();
2100+
2101+private slots:
2102+ void handleReplyFinished();
2103+ void handleReplyError(QNetworkReply::NetworkError code);
2104+ void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth);
2105+
2106+private:
2107+ QJSEngine *m_engine;
2108+ QNetworkAccessManager *m_net;
2109+ QString m_verb;
2110+ uint m_timeout;
2111+ QString m_username;
2112+ QString m_password;
2113+ QNetworkRequest m_request;
2114+ QNetworkReply *m_reply;
2115+ QString m_responseType;
2116+ QByteArray m_response;
2117+ QJSValue m_onload;
2118+ QJSValue m_ontimeout;
2119+ QJSValue m_onerror;
2120+};
2121+
2122+#endif // JSKITXMLHTTPREQUEST_P_H
2123
2124=== added file 'rockworkd/libpebble/jskit/typedarray.js'
2125--- rockworkd/libpebble/jskit/typedarray.js 1970-01-01 00:00:00 +0000
2126+++ rockworkd/libpebble/jskit/typedarray.js 2016-02-06 14:32:32 +0000
2127@@ -0,0 +1,1030 @@
2128+/*
2129+ Copyright (c) 2010, Linden Research, Inc.
2130+ Copyright (c) 2014, Joshua Bell
2131+
2132+ Permission is hereby granted, free of charge, to any person obtaining a copy
2133+ of this software and associated documentation files (the "Software"), to deal
2134+ in the Software without restriction, including without limitation the rights
2135+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2136+ copies of the Software, and to permit persons to whom the Software is
2137+ furnished to do so, subject to the following conditions:
2138+
2139+ The above copyright notice and this permission notice shall be included in
2140+ all copies or substantial portions of the Software.
2141+
2142+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2143+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2144+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2145+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2146+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2147+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2148+ THE SOFTWARE.
2149+ $/LicenseInfo$
2150+ */
2151+
2152+// Original can be found at:
2153+// https://bitbucket.org/lindenlab/llsd
2154+// Modifications by Joshua Bell inexorabletash@gmail.com
2155+// https://github.com/inexorabletash/polyfill
2156+
2157+// ES3/ES5 implementation of the Krhonos Typed Array Specification
2158+// Ref: http://www.khronos.org/registry/typedarray/specs/latest/
2159+// Date: 2011-02-01
2160+//
2161+// Variations:
2162+// * Allows typed_array.get/set() as alias for subscripts (typed_array[])
2163+// * Gradually migrating structure from Khronos spec to ES6 spec
2164+(function(global) {
2165+ 'use strict';
2166+ var undefined = (void 0); // Paranoia
2167+
2168+ // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to
2169+ // create, and consume so much memory, that the browser appears frozen.
2170+ var MAX_ARRAY_LENGTH = 1e5;
2171+
2172+ // Approximations of internal ECMAScript conversion functions
2173+ function Type(v) {
2174+ switch(typeof v) {
2175+ case 'undefined': return 'undefined';
2176+ case 'boolean': return 'boolean';
2177+ case 'number': return 'number';
2178+ case 'string': return 'string';
2179+ default: return v === null ? 'null' : 'object';
2180+ }
2181+ }
2182+
2183+ // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues:
2184+ function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); }
2185+ function IsCallable(o) { return typeof o === 'function'; }
2186+ function ToObject(v) {
2187+ if (v === null || v === undefined) throw TypeError();
2188+ return Object(v);
2189+ }
2190+ function ToInt32(v) { return v >> 0; }
2191+ function ToUint32(v) { return v >>> 0; }
2192+
2193+ // Snapshot intrinsics
2194+ var LN2 = Math.LN2,
2195+ abs = Math.abs,
2196+ floor = Math.floor,
2197+ log = Math.log,
2198+ max = Math.max,
2199+ min = Math.min,
2200+ pow = Math.pow,
2201+ round = Math.round;
2202+
2203+ // emulate ES5 getter/setter API using legacy APIs
2204+ // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
2205+ // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but
2206+ // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless)
2207+
2208+ (function() {
2209+ var orig = Object.defineProperty;
2210+ var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}());
2211+
2212+ if (!orig || dom_only) {
2213+ Object.defineProperty = function (o, prop, desc) {
2214+ // In IE8 try built-in implementation for defining properties on DOM prototypes.
2215+ if (orig)
2216+ try { return orig(o, prop, desc); } catch (_) {}
2217+ if (o !== Object(o))
2218+ throw TypeError('Object.defineProperty called on non-object');
2219+ if (Object.prototype.__defineGetter__ && ('get' in desc))
2220+ Object.prototype.__defineGetter__.call(o, prop, desc.get);
2221+ if (Object.prototype.__defineSetter__ && ('set' in desc))
2222+ Object.prototype.__defineSetter__.call(o, prop, desc.set);
2223+ if ('value' in desc)
2224+ o[prop] = desc.value;
2225+ return o;
2226+ };
2227+ }
2228+ }());
2229+
2230+ // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value)
2231+ // for index in 0 ... obj.length
2232+ function makeArrayAccessors(obj) {
2233+ if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill');
2234+
2235+ function makeArrayAccessor(index) {
2236+ Object.defineProperty(obj, index, {
2237+ 'get': function() { return obj._getter(index); },
2238+ 'set': function(v) { obj._setter(index, v); },
2239+ enumerable: true,
2240+ configurable: false
2241+ });
2242+ }
2243+
2244+ var i;
2245+ for (i = 0; i < obj.length; i += 1) {
2246+ makeArrayAccessor(i);
2247+ }
2248+ }
2249+
2250+ // Internal conversion functions:
2251+ // pack<Type>() - take a number (interpreted as Type), output a byte array
2252+ // unpack<Type>() - take a byte array, output a Type-like number
2253+
2254+ function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; }
2255+ function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; }
2256+
2257+ function packI8(n) { return [n & 0xff]; }
2258+ function unpackI8(bytes) { return as_signed(bytes[0], 8); }
2259+
2260+ function packU8(n) { return [n & 0xff]; }
2261+ function unpackU8(bytes) { return as_unsigned(bytes[0], 8); }
2262+
2263+ function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; }
2264+
2265+ function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
2266+ function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); }
2267+
2268+ function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
2269+ function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); }
2270+
2271+ function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
2272+ function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
2273+
2274+ function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
2275+ function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
2276+
2277+ function packIEEE754(v, ebits, fbits) {
2278+
2279+ var bias = (1 << (ebits - 1)) - 1,
2280+ s, e, f, ln,
2281+ i, bits, str, bytes;
2282+
2283+ function roundToEven(n) {
2284+ var w = floor(n), f = n - w;
2285+ if (f < 0.5)
2286+ return w;
2287+ if (f > 0.5)
2288+ return w + 1;
2289+ return w % 2 ? w + 1 : w;
2290+ }
2291+
2292+ // Compute sign, exponent, fraction
2293+ if (v !== v) {
2294+ // NaN
2295+ // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
2296+ e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0;
2297+ } else if (v === Infinity || v === -Infinity) {
2298+ e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0;
2299+ } else if (v === 0) {
2300+ e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
2301+ } else {
2302+ s = v < 0;
2303+ v = abs(v);
2304+
2305+ if (v >= pow(2, 1 - bias)) {
2306+ e = min(floor(log(v) / LN2), 1023);
2307+ f = roundToEven(v / pow(2, e) * pow(2, fbits));
2308+ if (f / pow(2, fbits) >= 2) {
2309+ e = e + 1;
2310+ f = 1;
2311+ }
2312+ if (e > bias) {
2313+ // Overflow
2314+ e = (1 << ebits) - 1;
2315+ f = 0;
2316+ } else {
2317+ // Normalized
2318+ e = e + bias;
2319+ f = f - pow(2, fbits);
2320+ }
2321+ } else {
2322+ // Denormalized
2323+ e = 0;
2324+ f = roundToEven(v / pow(2, 1 - bias - fbits));
2325+ }
2326+ }
2327+
2328+ // Pack sign, exponent, fraction
2329+ bits = [];
2330+ for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); }
2331+ for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); }
2332+ bits.push(s ? 1 : 0);
2333+ bits.reverse();
2334+ str = bits.join('');
2335+
2336+ // Bits to bytes
2337+ bytes = [];
2338+ while (str.length) {
2339+ bytes.push(parseInt(str.substring(0, 8), 2));
2340+ str = str.substring(8);
2341+ }
2342+ return bytes;
2343+ }
2344+
2345+ function unpackIEEE754(bytes, ebits, fbits) {
2346+ // Bytes to bits
2347+ var bits = [], i, j, b, str,
2348+ bias, s, e, f;
2349+
2350+ for (i = bytes.length; i; i -= 1) {
2351+ b = bytes[i - 1];
2352+ for (j = 8; j; j -= 1) {
2353+ bits.push(b % 2 ? 1 : 0); b = b >> 1;
2354+ }
2355+ }
2356+ bits.reverse();
2357+ str = bits.join('');
2358+
2359+ // Unpack sign, exponent, fraction
2360+ bias = (1 << (ebits - 1)) - 1;
2361+ s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
2362+ e = parseInt(str.substring(1, 1 + ebits), 2);
2363+ f = parseInt(str.substring(1 + ebits), 2);
2364+
2365+ // Produce number
2366+ if (e === (1 << ebits) - 1) {
2367+ return f !== 0 ? NaN : s * Infinity;
2368+ } else if (e > 0) {
2369+ // Normalized
2370+ return s * pow(2, e - bias) * (1 + f / pow(2, fbits));
2371+ } else if (f !== 0) {
2372+ // Denormalized
2373+ return s * pow(2, -(bias - 1)) * (f / pow(2, fbits));
2374+ } else {
2375+ return s < 0 ? -0 : 0;
2376+ }
2377+ }
2378+
2379+ function unpackF64(b) { return unpackIEEE754(b, 11, 52); }
2380+ function packF64(v) { return packIEEE754(v, 11, 52); }
2381+ function unpackF32(b) { return unpackIEEE754(b, 8, 23); }
2382+ function packF32(v) { return packIEEE754(v, 8, 23); }
2383+
2384+ //
2385+ // 3 The ArrayBuffer Type
2386+ //
2387+
2388+ (function() {
2389+
2390+ function ArrayBuffer(length) {
2391+ length = ToInt32(length);
2392+ if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.');
2393+ Object.defineProperty(this, 'byteLength', {value: length});
2394+ Object.defineProperty(this, '_bytes', {value: Array(length)});
2395+
2396+ for (var i = 0; i < length; i += 1)
2397+ this._bytes[i] = 0;
2398+ }
2399+
2400+ global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;
2401+
2402+ //
2403+ // 5 The Typed Array View Types
2404+ //
2405+
2406+ function $TypedArray$() {
2407+
2408+ // %TypedArray% ( length )
2409+ if (!arguments.length || typeof arguments[0] !== 'object') {
2410+ return (function(length) {
2411+ length = ToInt32(length);
2412+ if (length < 0) throw RangeError('length is not a small enough positive integer.');
2413+ Object.defineProperty(this, 'length', {value: length});
2414+ Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT});
2415+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)});
2416+ Object.defineProperty(this, 'byteOffset', {value: 0});
2417+
2418+ }).apply(this, arguments);
2419+ }
2420+
2421+ // %TypedArray% ( typedArray )
2422+ if (arguments.length >= 1 &&
2423+ Type(arguments[0]) === 'object' &&
2424+ arguments[0] instanceof $TypedArray$) {
2425+ return (function(typedArray){
2426+ if (this.constructor !== typedArray.constructor) throw TypeError();
2427+
2428+ var byteLength = typedArray.length * this.BYTES_PER_ELEMENT;
2429+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
2430+ Object.defineProperty(this, 'byteLength', {value: byteLength});
2431+ Object.defineProperty(this, 'byteOffset', {value: 0});
2432+ Object.defineProperty(this, 'length', {value: typedArray.length});
2433+
2434+ for (var i = 0; i < this.length; i += 1)
2435+ this._setter(i, typedArray._getter(i));
2436+
2437+ }).apply(this, arguments);
2438+ }
2439+
2440+ // %TypedArray% ( array )
2441+ if (arguments.length >= 1 &&
2442+ Type(arguments[0]) === 'object' &&
2443+ !(arguments[0] instanceof $TypedArray$) &&
2444+ !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
2445+ return (function(array) {
2446+
2447+ var byteLength = array.length * this.BYTES_PER_ELEMENT;
2448+ Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
2449+ Object.defineProperty(this, 'byteLength', {value: byteLength});
2450+ Object.defineProperty(this, 'byteOffset', {value: 0});
2451+ Object.defineProperty(this, 'length', {value: array.length});
2452+
2453+ for (var i = 0; i < this.length; i += 1) {
2454+ var s = array[i];
2455+ this._setter(i, Number(s));
2456+ }
2457+ }).apply(this, arguments);
2458+ }
2459+
2460+ // %TypedArray% ( buffer, byteOffset=0, length=undefined )
2461+ if (arguments.length >= 1 &&
2462+ Type(arguments[0]) === 'object' &&
2463+ (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
2464+ return (function(buffer, byteOffset, length) {
2465+
2466+ byteOffset = ToUint32(byteOffset);
2467+ if (byteOffset > buffer.byteLength)
2468+ throw RangeError('byteOffset out of range');
2469+
2470+ // The given byteOffset must be a multiple of the element
2471+ // size of the specific type, otherwise an exception is raised.
2472+ if (byteOffset % this.BYTES_PER_ELEMENT)
2473+ throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.');
2474+
2475+ if (length === undefined) {
2476+ var byteLength = buffer.byteLength - byteOffset;
2477+ if (byteLength % this.BYTES_PER_ELEMENT)
2478+ throw RangeError('length of buffer minus byteOffset not a multiple of the element size');
2479+ length = byteLength / this.BYTES_PER_ELEMENT;
2480+
2481+ } else {
2482+ length = ToUint32(length);
2483+ byteLength = length * this.BYTES_PER_ELEMENT;
2484+ }
2485+
2486+ if ((byteOffset + byteLength) > buffer.byteLength)
2487+ throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
2488+
2489+ Object.defineProperty(this, 'buffer', {value: buffer});
2490+ Object.defineProperty(this, 'byteLength', {value: byteLength});
2491+ Object.defineProperty(this, 'byteOffset', {value: byteOffset});
2492+ Object.defineProperty(this, 'length', {value: length});
2493+
2494+ }).apply(this, arguments);
2495+ }
2496+
2497+ // %TypedArray% ( all other argument combinations )
2498+ throw TypeError();
2499+ }
2500+
2501+ // Properties of the %TypedArray Instrinsic Object
2502+
2503+ // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined )
2504+ Object.defineProperty($TypedArray$, 'from', {value: function(iterable) {
2505+ return new this(iterable);
2506+ }});
2507+
2508+ // %TypedArray%.of ( ...items )
2509+ Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) {
2510+ return new this(arguments);
2511+ }});
2512+
2513+ // %TypedArray%.prototype
2514+ var $TypedArrayPrototype$ = {};
2515+ $TypedArray$.prototype = $TypedArrayPrototype$;
2516+
2517+ // WebIDL: getter type (unsigned long index);
2518+ Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) {
2519+ if (arguments.length < 1) throw SyntaxError('Not enough arguments');
2520+
2521+ index = ToUint32(index);
2522+ if (index >= this.length)
2523+ return undefined;
2524+
2525+ var bytes = [], i, o;
2526+ for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
2527+ i < this.BYTES_PER_ELEMENT;
2528+ i += 1, o += 1) {
2529+ bytes.push(this.buffer._bytes[o]);
2530+ }
2531+ return this._unpack(bytes);
2532+ }});
2533+
2534+ // NONSTANDARD: convenience alias for getter: type get(unsigned long index);
2535+ Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter});
2536+
2537+ // WebIDL: setter void (unsigned long index, type value);
2538+ Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) {
2539+ if (arguments.length < 2) throw SyntaxError('Not enough arguments');
2540+
2541+ index = ToUint32(index);
2542+ if (index >= this.length)
2543+ return;
2544+
2545+ var bytes = this._pack(value), i, o;
2546+ for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
2547+ i < this.BYTES_PER_ELEMENT;
2548+ i += 1, o += 1) {
2549+ this.buffer._bytes[o] = bytes[i];
2550+ }
2551+ }});
2552+
2553+ // get %TypedArray%.prototype.buffer
2554+ // get %TypedArray%.prototype.byteLength
2555+ // get %TypedArray%.prototype.byteOffset
2556+ // -- applied directly to the object in the constructor
2557+
2558+ // %TypedArray%.prototype.constructor
2559+ Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$});
2560+
2561+ // %TypedArray%.prototype.copyWithin (target, start, end = this.length )
2562+ Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) {
2563+ var end = arguments[2];
2564+
2565+ var o = ToObject(this);
2566+ var lenVal = o.length;
2567+ var len = ToUint32(lenVal);
2568+ len = max(len, 0);
2569+ var relativeTarget = ToInt32(target);
2570+ var to;
2571+ if (relativeTarget < 0)
2572+ to = max(len + relativeTarget, 0);
2573+ else
2574+ to = min(relativeTarget, len);
2575+ var relativeStart = ToInt32(start);
2576+ var from;
2577+ if (relativeStart < 0)
2578+ from = max(len + relativeStart, 0);
2579+ else
2580+ from = min(relativeStart, len);
2581+ var relativeEnd;
2582+ if (end === undefined)
2583+ relativeEnd = len;
2584+ else
2585+ relativeEnd = ToInt32(end);
2586+ var final;
2587+ if (relativeEnd < 0)
2588+ final = max(len + relativeEnd, 0);
2589+ else
2590+ final = min(relativeEnd, len);
2591+ var count = min(final - from, len - to);
2592+ var direction;
2593+ if (from < to && to < from + count) {
2594+ direction = -1;
2595+ from = from + count - 1;
2596+ to = to + count - 1;
2597+ } else {
2598+ direction = 1;
2599+ }
2600+ while (count > 0) {
2601+ o._setter(to, o._getter(from));
2602+ from = from + direction;
2603+ to = to + direction;
2604+ count = count - 1;
2605+ }
2606+ return o;
2607+ }});
2608+
2609+ // %TypedArray%.prototype.entries ( )
2610+ // -- defined in es6.js to shim browsers w/ native TypedArrays
2611+
2612+ // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined )
2613+ Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) {
2614+ if (this === undefined || this === null) throw TypeError();
2615+ var t = Object(this);
2616+ var len = ToUint32(t.length);
2617+ if (!IsCallable(callbackfn)) throw TypeError();
2618+ var thisArg = arguments[1];
2619+ for (var i = 0; i < len; i++) {
2620+ if (!callbackfn.call(thisArg, t._getter(i), i, t))
2621+ return false;
2622+ }
2623+ return true;
2624+ }});
2625+
2626+ // %TypedArray%.prototype.fill (value, start = 0, end = this.length )
2627+ Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) {
2628+ var start = arguments[1],
2629+ end = arguments[2];
2630+
2631+ var o = ToObject(this);
2632+ var lenVal = o.length;
2633+ var len = ToUint32(lenVal);
2634+ len = max(len, 0);
2635+ var relativeStart = ToInt32(start);
2636+ var k;
2637+ if (relativeStart < 0)
2638+ k = max((len + relativeStart), 0);
2639+ else
2640+ k = min(relativeStart, len);
2641+ var relativeEnd;
2642+ if (end === undefined)
2643+ relativeEnd = len;
2644+ else
2645+ relativeEnd = ToInt32(end);
2646+ var final;
2647+ if (relativeEnd < 0)
2648+ final = max((len + relativeEnd), 0);
2649+ else
2650+ final = min(relativeEnd, len);
2651+ while (k < final) {
2652+ o._setter(k, value);
2653+ k += 1;
2654+ }
2655+ return o;
2656+ }});
2657+
2658+ // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined )
2659+ Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) {
2660+ if (this === undefined || this === null) throw TypeError();
2661+ var t = Object(this);
2662+ var len = ToUint32(t.length);
2663+ if (!IsCallable(callbackfn)) throw TypeError();
2664+ var res = [];
2665+ var thisp = arguments[1];
2666+ for (var i = 0; i < len; i++) {
2667+ var val = t._getter(i); // in case fun mutates this
2668+ if (callbackfn.call(thisp, val, i, t))
2669+ res.push(val);
2670+ }
2671+ return new this.constructor(res);
2672+ }});
2673+
2674+ // %TypedArray%.prototype.find (predicate, thisArg = undefined)
2675+ Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) {
2676+ var o = ToObject(this);
2677+ var lenValue = o.length;
2678+ var len = ToUint32(lenValue);
2679+ if (!IsCallable(predicate)) throw TypeError();
2680+ var t = arguments.length > 1 ? arguments[1] : undefined;
2681+ var k = 0;
2682+ while (k < len) {
2683+ var kValue = o._getter(k);
2684+ var testResult = predicate.call(t, kValue, k, o);
2685+ if (Boolean(testResult))
2686+ return kValue;
2687+ ++k;
2688+ }
2689+ return undefined;
2690+ }});
2691+
2692+ // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined )
2693+ Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) {
2694+ var o = ToObject(this);
2695+ var lenValue = o.length;
2696+ var len = ToUint32(lenValue);
2697+ if (!IsCallable(predicate)) throw TypeError();
2698+ var t = arguments.length > 1 ? arguments[1] : undefined;
2699+ var k = 0;
2700+ while (k < len) {
2701+ var kValue = o._getter(k);
2702+ var testResult = predicate.call(t, kValue, k, o);
2703+ if (Boolean(testResult))
2704+ return k;
2705+ ++k;
2706+ }
2707+ return -1;
2708+ }});
2709+
2710+ // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined )
2711+ Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) {
2712+ if (this === undefined || this === null) throw TypeError();
2713+ var t = Object(this);
2714+ var len = ToUint32(t.length);
2715+ if (!IsCallable(callbackfn)) throw TypeError();
2716+ var thisp = arguments[1];
2717+ for (var i = 0; i < len; i++)
2718+ callbackfn.call(thisp, t._getter(i), i, t);
2719+ }});
2720+
2721+ // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 )
2722+ Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) {
2723+ if (this === undefined || this === null) throw TypeError();
2724+ var t = Object(this);
2725+ var len = ToUint32(t.length);
2726+ if (len === 0) return -1;
2727+ var n = 0;
2728+ if (arguments.length > 0) {
2729+ n = Number(arguments[1]);
2730+ if (n !== n) {
2731+ n = 0;
2732+ } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
2733+ n = (n > 0 || -1) * floor(abs(n));
2734+ }
2735+ }
2736+ if (n >= len) return -1;
2737+ var k = n >= 0 ? n : max(len - abs(n), 0);
2738+ for (; k < len; k++) {
2739+ if (t._getter(k) === searchElement) {
2740+ return k;
2741+ }
2742+ }
2743+ return -1;
2744+ }});
2745+
2746+ // %TypedArray%.prototype.join ( separator )
2747+ Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) {
2748+ if (this === undefined || this === null) throw TypeError();
2749+ var t = Object(this);
2750+ var len = ToUint32(t.length);
2751+ var tmp = Array(len);
2752+ for (var i = 0; i < len; ++i)
2753+ tmp[i] = t._getter(i);
2754+ return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7
2755+ }});
2756+
2757+ // %TypedArray%.prototype.keys ( )
2758+ // -- defined in es6.js to shim browsers w/ native TypedArrays
2759+
2760+ // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 )
2761+ Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) {
2762+ if (this === undefined || this === null) throw TypeError();
2763+ var t = Object(this);
2764+ var len = ToUint32(t.length);
2765+ if (len === 0) return -1;
2766+ var n = len;
2767+ if (arguments.length > 1) {
2768+ n = Number(arguments[1]);
2769+ if (n !== n) {
2770+ n = 0;
2771+ } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
2772+ n = (n > 0 || -1) * floor(abs(n));
2773+ }
2774+ }
2775+ var k = n >= 0 ? min(n, len - 1) : len - abs(n);
2776+ for (; k >= 0; k--) {
2777+ if (t._getter(k) === searchElement)
2778+ return k;
2779+ }
2780+ return -1;
2781+ }});
2782+
2783+ // get %TypedArray%.prototype.length
2784+ // -- applied directly to the object in the constructor
2785+
2786+ // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined )
2787+ Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) {
2788+ if (this === undefined || this === null) throw TypeError();
2789+ var t = Object(this);
2790+ var len = ToUint32(t.length);
2791+ if (!IsCallable(callbackfn)) throw TypeError();
2792+ var res = []; res.length = len;
2793+ var thisp = arguments[1];
2794+ for (var i = 0; i < len; i++)
2795+ res[i] = callbackfn.call(thisp, t._getter(i), i, t);
2796+ return new this.constructor(res);
2797+ }});
2798+
2799+ // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] )
2800+ Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) {
2801+ if (this === undefined || this === null) throw TypeError();
2802+ var t = Object(this);
2803+ var len = ToUint32(t.length);
2804+ if (!IsCallable(callbackfn)) throw TypeError();
2805+ // no value to return if no initial value and an empty array
2806+ if (len === 0 && arguments.length === 1) throw TypeError();
2807+ var k = 0;
2808+ var accumulator;
2809+ if (arguments.length >= 2) {
2810+ accumulator = arguments[1];
2811+ } else {
2812+ accumulator = t._getter(k++);
2813+ }
2814+ while (k < len) {
2815+ accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
2816+ k++;
2817+ }
2818+ return accumulator;
2819+ }});
2820+
2821+ // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] )
2822+ Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) {
2823+ if (this === undefined || this === null) throw TypeError();
2824+ var t = Object(this);
2825+ var len = ToUint32(t.length);
2826+ if (!IsCallable(callbackfn)) throw TypeError();
2827+ // no value to return if no initial value, empty array
2828+ if (len === 0 && arguments.length === 1) throw TypeError();
2829+ var k = len - 1;
2830+ var accumulator;
2831+ if (arguments.length >= 2) {
2832+ accumulator = arguments[1];
2833+ } else {
2834+ accumulator = t._getter(k--);
2835+ }
2836+ while (k >= 0) {
2837+ accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t);
2838+ k--;
2839+ }
2840+ return accumulator;
2841+ }});
2842+
2843+ // %TypedArray%.prototype.reverse ( )
2844+ Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() {
2845+ if (this === undefined || this === null) throw TypeError();
2846+ var t = Object(this);
2847+ var len = ToUint32(t.length);
2848+ var half = floor(len / 2);
2849+ for (var i = 0, j = len - 1; i < half; ++i, --j) {
2850+ var tmp = t._getter(i);
2851+ t._setter(i, t._getter(j));
2852+ t._setter(j, tmp);
2853+ }
2854+ return t;
2855+ }});
2856+
2857+ // %TypedArray%.prototype.set(array, offset = 0 )
2858+ // %TypedArray%.prototype.set(typedArray, offset = 0 )
2859+ // WebIDL: void set(TypedArray array, optional unsigned long offset);
2860+ // WebIDL: void set(sequence<type> array, optional unsigned long offset);
2861+ Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) {
2862+ if (arguments.length < 1) throw SyntaxError('Not enough arguments');
2863+ var array, sequence, offset, len,
2864+ i, s, d,
2865+ byteOffset, byteLength, tmp;
2866+
2867+ if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) {
2868+ // void set(TypedArray array, optional unsigned long offset);
2869+ array = arguments[0];
2870+ offset = ToUint32(arguments[1]);
2871+
2872+ if (offset + array.length > this.length) {
2873+ throw RangeError('Offset plus length of array is out of range');
2874+ }
2875+
2876+ byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT;
2877+ byteLength = array.length * this.BYTES_PER_ELEMENT;
2878+
2879+ if (array.buffer === this.buffer) {
2880+ tmp = [];
2881+ for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) {
2882+ tmp[i] = array.buffer._bytes[s];
2883+ }
2884+ for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) {
2885+ this.buffer._bytes[d] = tmp[i];
2886+ }
2887+ } else {
2888+ for (i = 0, s = array.byteOffset, d = byteOffset;
2889+ i < byteLength; i += 1, s += 1, d += 1) {
2890+ this.buffer._bytes[d] = array.buffer._bytes[s];
2891+ }
2892+ }
2893+ } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') {
2894+ // void set(sequence<type> array, optional unsigned long offset);
2895+ sequence = arguments[0];
2896+ len = ToUint32(sequence.length);
2897+ offset = ToUint32(arguments[1]);
2898+
2899+ if (offset + len > this.length) {
2900+ throw RangeError('Offset plus length of array is out of range');
2901+ }
2902+
2903+ for (i = 0; i < len; i += 1) {
2904+ s = sequence[i];
2905+ this._setter(offset + i, Number(s));
2906+ }
2907+ } else {
2908+ throw TypeError('Unexpected argument type(s)');
2909+ }
2910+ }});
2911+
2912+ // %TypedArray%.prototype.slice ( start, end )
2913+ Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) {
2914+ var o = ToObject(this);
2915+ var lenVal = o.length;
2916+ var len = ToUint32(lenVal);
2917+ var relativeStart = ToInt32(start);
2918+ var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len);
2919+ var relativeEnd = (end === undefined) ? len : ToInt32(end);
2920+ var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len);
2921+ var count = final - k;
2922+ var c = o.constructor;
2923+ var a = new c(count);
2924+ var n = 0;
2925+ while (k < final) {
2926+ var kValue = o._getter(k);
2927+ a._setter(n, kValue);
2928+ ++k;
2929+ ++n;
2930+ }
2931+ return a;
2932+ }});
2933+
2934+ // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined )
2935+ Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) {
2936+ if (this === undefined || this === null) throw TypeError();
2937+ var t = Object(this);
2938+ var len = ToUint32(t.length);
2939+ if (!IsCallable(callbackfn)) throw TypeError();
2940+ var thisp = arguments[1];
2941+ for (var i = 0; i < len; i++) {
2942+ if (callbackfn.call(thisp, t._getter(i), i, t)) {
2943+ return true;
2944+ }
2945+ }
2946+ return false;
2947+ }});
2948+
2949+ // %TypedArray%.prototype.sort ( comparefn )
2950+ Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) {
2951+ if (this === undefined || this === null) throw TypeError();
2952+ var t = Object(this);
2953+ var len = ToUint32(t.length);
2954+ var tmp = Array(len);
2955+ for (var i = 0; i < len; ++i)
2956+ tmp[i] = t._getter(i);
2957+ if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9
2958+ for (i = 0; i < len; ++i)
2959+ t._setter(i, tmp[i]);
2960+ return t;
2961+ }});
2962+
2963+ // %TypedArray%.prototype.subarray(begin = 0, end = this.length )
2964+ // WebIDL: TypedArray subarray(long begin, optional long end);
2965+ Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) {
2966+ function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
2967+
2968+ start = ToInt32(start);
2969+ end = ToInt32(end);
2970+
2971+ if (arguments.length < 1) { start = 0; }
2972+ if (arguments.length < 2) { end = this.length; }
2973+
2974+ if (start < 0) { start = this.length + start; }
2975+ if (end < 0) { end = this.length + end; }
2976+
2977+ start = clamp(start, 0, this.length);
2978+ end = clamp(end, 0, this.length);
2979+
2980+ var len = end - start;
2981+ if (len < 0) {
2982+ len = 0;
2983+ }
2984+
2985+ return new this.constructor(
2986+ this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len);
2987+ }});
2988+
2989+ // %TypedArray%.prototype.toLocaleString ( )
2990+ // %TypedArray%.prototype.toString ( )
2991+ // %TypedArray%.prototype.values ( )
2992+ // %TypedArray%.prototype [ @@iterator ] ( )
2993+ // get %TypedArray%.prototype [ @@toStringTag ]
2994+ // -- defined in es6.js to shim browsers w/ native TypedArrays
2995+
2996+ function makeTypedArray(elementSize, pack, unpack) {
2997+ // Each TypedArray type requires a distinct constructor instance with
2998+ // identical logic, which this produces.
2999+ var TypedArray = function() {
3000+ Object.defineProperty(this, 'constructor', {value: TypedArray});
3001+ $TypedArray$.apply(this, arguments);
3002+ makeArrayAccessors(this);
3003+ };
3004+ if ('__proto__' in TypedArray) {
3005+ TypedArray.__proto__ = $TypedArray$;
3006+ } else {
3007+ TypedArray.from = $TypedArray$.from;
3008+ TypedArray.of = $TypedArray$.of;
3009+ }
3010+
3011+ TypedArray.BYTES_PER_ELEMENT = elementSize;
3012+
3013+ var TypedArrayPrototype = function() {};
3014+ TypedArrayPrototype.prototype = $TypedArrayPrototype$;
3015+
3016+ TypedArray.prototype = new TypedArrayPrototype();
3017+
3018+ Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize});
3019+ Object.defineProperty(TypedArray.prototype, '_pack', {value: pack});
3020+ Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack});
3021+
3022+ return TypedArray;
3023+ }
3024+
3025+ var Int8Array = makeTypedArray(1, packI8, unpackI8);
3026+ var Uint8Array = makeTypedArray(1, packU8, unpackU8);
3027+ var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8);
3028+ var Int16Array = makeTypedArray(2, packI16, unpackI16);
3029+ var Uint16Array = makeTypedArray(2, packU16, unpackU16);
3030+ var Int32Array = makeTypedArray(4, packI32, unpackI32);
3031+ var Uint32Array = makeTypedArray(4, packU32, unpackU32);
3032+ var Float32Array = makeTypedArray(4, packF32, unpackF32);
3033+ var Float64Array = makeTypedArray(8, packF64, unpackF64);
3034+
3035+ global.Int8Array = global.Int8Array || Int8Array;
3036+ global.Uint8Array = global.Uint8Array || Uint8Array;
3037+ global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray;
3038+ global.Int16Array = global.Int16Array || Int16Array;
3039+ global.Uint16Array = global.Uint16Array || Uint16Array;
3040+ global.Int32Array = global.Int32Array || Int32Array;
3041+ global.Uint32Array = global.Uint32Array || Uint32Array;
3042+ global.Float32Array = global.Float32Array || Float32Array;
3043+ global.Float64Array = global.Float64Array || Float64Array;
3044+ }());
3045+
3046+ //
3047+ // 6 The DataView View Type
3048+ //
3049+
3050+ (function() {
3051+ function r(array, index) {
3052+ return IsCallable(array.get) ? array.get(index) : array[index];
3053+ }
3054+
3055+ var IS_BIG_ENDIAN = (function() {
3056+ var u16array = new Uint16Array([0x1234]),
3057+ u8array = new Uint8Array(u16array.buffer);
3058+ return r(u8array, 0) === 0x12;
3059+ }());
3060+
3061+ // DataView(buffer, byteOffset=0, byteLength=undefined)
3062+ // WebIDL: Constructor(ArrayBuffer buffer,
3063+ // optional unsigned long byteOffset,
3064+ // optional unsigned long byteLength)
3065+ function DataView(buffer, byteOffset, byteLength) {
3066+ if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError();
3067+
3068+ byteOffset = ToUint32(byteOffset);
3069+ if (byteOffset > buffer.byteLength)
3070+ throw RangeError('byteOffset out of range');
3071+
3072+ if (byteLength === undefined)
3073+ byteLength = buffer.byteLength - byteOffset;
3074+ else
3075+ byteLength = ToUint32(byteLength);
3076+
3077+ if ((byteOffset + byteLength) > buffer.byteLength)
3078+ throw RangeError('byteOffset and length reference an area beyond the end of the buffer');
3079+
3080+ Object.defineProperty(this, 'buffer', {value: buffer});
3081+ Object.defineProperty(this, 'byteLength', {value: byteLength});
3082+ Object.defineProperty(this, 'byteOffset', {value: byteOffset});
3083+ };
3084+
3085+ // get DataView.prototype.buffer
3086+ // get DataView.prototype.byteLength
3087+ // get DataView.prototype.byteOffset
3088+ // -- applied directly to instances by the constructor
3089+
3090+ function makeGetter(arrayType) {
3091+ return function GetViewValue(byteOffset, littleEndian) {
3092+ byteOffset = ToUint32(byteOffset);
3093+
3094+ if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
3095+ throw RangeError('Array index out of range');
3096+
3097+ byteOffset += this.byteOffset;
3098+
3099+ var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT),
3100+ bytes = [];
3101+ for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
3102+ bytes.push(r(uint8Array, i));
3103+
3104+ if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
3105+ bytes.reverse();
3106+
3107+ return r(new arrayType(new Uint8Array(bytes).buffer), 0);
3108+ };
3109+ }
3110+
3111+ Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)});
3112+ Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)});
3113+ Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)});
3114+ Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)});
3115+ Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)});
3116+ Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)});
3117+ Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)});
3118+ Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)});
3119+
3120+ function makeSetter(arrayType) {
3121+ return function SetViewValue(byteOffset, value, littleEndian) {
3122+ byteOffset = ToUint32(byteOffset);
3123+ if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength)
3124+ throw RangeError('Array index out of range');
3125+
3126+ // Get bytes
3127+ var typeArray = new arrayType([value]),
3128+ byteArray = new Uint8Array(typeArray.buffer),
3129+ bytes = [], i, byteView;
3130+
3131+ for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1)
3132+ bytes.push(r(byteArray, i));
3133+
3134+ // Flip if necessary
3135+ if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN))
3136+ bytes.reverse();
3137+
3138+ // Write them
3139+ byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT);
3140+ byteView.set(bytes);
3141+ };
3142+ }
3143+
3144+ Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)});
3145+ Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)});
3146+ Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)});
3147+ Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)});
3148+ Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)});
3149+ Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)});
3150+ Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)});
3151+ Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)});
3152+
3153+ global.DataView = global.DataView || DataView;
3154+
3155+ }());
3156+
3157+}(this));
3158
3159=== removed file 'rockworkd/libpebble/jskitmanager.cpp'
3160--- rockworkd/libpebble/jskitmanager.cpp 2016-01-10 22:35:04 +0000
3161+++ rockworkd/libpebble/jskitmanager.cpp 1970-01-01 00:00:00 +0000
3162@@ -1,243 +0,0 @@
3163-#include <QFile>
3164-#include <QDir>
3165-
3166-#include "jskitmanager.h"
3167-#include "jskitobjects.h"
3168-
3169-JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) :
3170- QObject(parent),
3171- m_pebble(pebble),
3172- m_connection(connection), _apps(apps), _appmsg(appmsg), _engine(0)
3173-{
3174- m_configurationUuid = QUuid();
3175- connect(_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted);
3176- connect(_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped);
3177-}
3178-
3179-JSKitManager::~JSKitManager()
3180-{
3181- if (_engine) {
3182- stopJsApp();
3183- }
3184-}
3185-
3186-QJSEngine * JSKitManager::engine()
3187-{
3188- return _engine;
3189-}
3190-
3191-bool JSKitManager::isJSKitAppRunning() const
3192-{
3193- return _engine != 0;
3194-}
3195-
3196-QString JSKitManager::describeError(QJSValue error)
3197-{
3198- return QString("%1:%2: %3")
3199- .arg(error.property("fileName").toString())
3200- .arg(error.property("lineNumber").toInt())
3201- .arg(error.toString());
3202-}
3203-
3204-void JSKitManager::showConfiguration()
3205-{
3206- if (_engine) {
3207- qDebug() << "requesting configuration";
3208- _jspebble->invokeCallbacks("showConfiguration");
3209- } else {
3210- qWarning() << "requested to show configuration, but JS engine is not running";
3211- }
3212-}
3213-
3214-void JSKitManager::handleWebviewClosed(const QString &result)
3215-{
3216- if (_engine) {
3217- QJSValue eventObj = _engine->newObject();
3218- QByteArray data = QByteArray::fromPercentEncoding(result.toUtf8());
3219- eventObj.setProperty("response", _engine->toScriptValue(data));
3220-
3221- qDebug() << "Sending" << eventObj.property("response").toString();
3222-
3223-
3224- _jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj}));
3225-// _jspebble->invokeCallbacks("webviewclosed", eventObj);
3226- } else {
3227- qWarning() << "webview closed event, but JS engine is not running";
3228- }
3229-}
3230-
3231-void JSKitManager::setConfigurationId(const QUuid &uuid)
3232-{
3233- m_configurationUuid = uuid;
3234-}
3235-
3236-AppInfo JSKitManager::currentApp()
3237-{
3238- return _curApp;
3239-}
3240-
3241-void JSKitManager::handleAppStarted(const QUuid &uuid)
3242-{
3243- qDebug() << "handleAppStarted!!!" << uuid;
3244- AppInfo info = _apps->info(uuid);
3245- if (!info.uuid().isNull() && info.isJSKit()) {
3246- qDebug() << "Preparing to start JSKit app" << info.uuid() << info.shortName();
3247- _curApp = info;
3248- startJsApp();
3249- }
3250-}
3251-
3252-void JSKitManager::handleAppStopped(const QUuid &uuid)
3253-{
3254- if (!_curApp.uuid().isNull()) {
3255- if (_curApp.uuid() != uuid) {
3256- qWarning() << "Closed app with invalid UUID";
3257- }
3258-
3259- stopJsApp();
3260- _curApp = AppInfo();
3261- qDebug() << "App stopped" << uuid;
3262- }
3263-}
3264-
3265-void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg)
3266-{
3267- qDebug() << "handleAppMessage" << uuid << msg;
3268- if (_curApp.uuid() == uuid) {
3269- qDebug() << "received a message for the current JSKit app";
3270-
3271- if (!_engine) {
3272- qDebug() << "but engine is stopped";
3273- return;
3274- }
3275-
3276- QJSValue eventObj = _engine->newObject();
3277- eventObj.setProperty("payload", _engine->toScriptValue(msg));
3278-
3279- _jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj}));
3280- }
3281-}
3282-
3283-bool JSKitManager::loadJsFile(const QString &filename)
3284-{
3285- Q_ASSERT(_engine);
3286-
3287- QFile file(filename);
3288- if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
3289- qWarning() << "Failed to load JS file:" << file.fileName();
3290- return false;
3291- }
3292-
3293- qDebug() << "now parsing" << file.fileName();
3294-
3295- QJSValue result = _engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName());
3296- if (result.isError()) {
3297- qWarning() << "error while evaluating JS script:" << describeError(result);
3298- return false;
3299- }
3300-
3301- qDebug() << "JS script evaluated";
3302-
3303- return true;
3304-}
3305-
3306-void JSKitManager::startJsApp()
3307-{
3308- qDebug() << "startJsApp called";
3309-
3310- if (_engine) stopJsApp();
3311-
3312- if (_curApp.uuid().isNull()) {
3313- qWarning() << "Attempting to start JS app with invalid UUID";
3314- return;
3315- }
3316-
3317- _engine = new QJSEngine(this);
3318- _jspebble = new JSKitPebble(_curApp, this, _engine);
3319- _jsconsole = new JSKitConsole(_engine);
3320- _jsstorage = new JSKitLocalStorage(m_pebble->storagePath(), _curApp.uuid(), _engine);
3321- _jsgeo = new JSKitGeolocation(this, _engine);
3322-
3323- qDebug() << "starting JS app" << _curApp.shortName();
3324-
3325- QJSValue globalObj = _engine->globalObject();
3326-
3327- globalObj.setProperty("RealPebble", _engine->newQObject(_jspebble));
3328- globalObj.setProperty("console", _engine->newQObject(_jsconsole));
3329- globalObj.setProperty("localStorage", _engine->newQObject(_jsstorage));
3330-
3331- QJSValue navigatorObj = _engine->newObject();
3332- navigatorObj.setProperty("RealGeolocation", _engine->newQObject(_jsgeo));
3333- navigatorObj.setProperty("language", _engine->toScriptValue(QLocale().name()));
3334- globalObj.setProperty("navigator", navigatorObj);
3335-
3336- // Set this.window = this
3337- globalObj.setProperty("window", globalObj);
3338-
3339- // Shims for compatibility...
3340- QJSValue result = _engine->evaluate(
3341- "function XMLHttpRequest() { return Pebble.createXMLHttpRequest(); }\n\
3342- function setInterval(func, time) { return Pebble.setInterval(func, time); }\n\
3343- function clearInterval(id) { Pebble.clearInterval(id); }\n\
3344- function setTimeout(func, time) { return Pebble.setTimeout(func, time); }\n\
3345- function clearTimeout(id) { Pebble.clearTimeout(id); }\n\
3346- Pebble = {}; for (var key in RealPebble) {Pebble[key] = RealPebble[key];}\n\
3347- navigator.geolocation = {}; for (var key in navigator.RealGeolocation) {navigator.geolocation[key] = navigator.RealGeolocation[key];}\n\
3348- ");
3349- qDebug() << result.toString();
3350- Q_ASSERT(!result.isError());
3351-
3352- // Polyfills...
3353- loadJsFile(":/typedarray.js");
3354-
3355- // Now the actual script
3356- QString jsApp = _curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown);
3357- QFile f(jsApp);
3358- if (!f.open(QFile::ReadOnly)) {
3359- qWarning() << "Error opening" << jsApp;
3360- return;
3361- }
3362- QJSValue ret = _engine->evaluate(QString::fromUtf8(f.readAll()));
3363- qDebug() << "loaded script" << ret.toString();
3364-
3365- // Setup the message callback
3366- QUuid uuid = _curApp.uuid();
3367- _appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) {
3368- QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection,
3369- Q_ARG(QUuid, uuid),
3370- Q_ARG(QVariantMap, msg));
3371-
3372- // Invoke the slot as a queued connection to give time for the ACK message
3373- // to go through first.
3374-
3375- return true;
3376- });
3377-
3378- // We try to invoke the callbacks even if script parsing resulted in error...
3379- qDebug() << "calling ready!";
3380- _jspebble->invokeCallbacks("ready");
3381-
3382- if (m_configurationUuid == _curApp.uuid()) {
3383- qDebug() << "going to launch config for" << m_configurationUuid;
3384- showConfiguration();
3385- }
3386-
3387- m_configurationUuid = QUuid();
3388-}
3389-
3390-void JSKitManager::stopJsApp()
3391-{
3392- qDebug() << "stop js app";
3393- if (!_engine) return; // Nothing to do!
3394-
3395- qDebug() << "stopping JS app";
3396-
3397- if (!_curApp.uuid().isNull()) {
3398- _appmsg->clearMessageHandler(_curApp.uuid());
3399- }
3400-
3401- _engine->collectGarbage();
3402-
3403- _engine->deleteLater();
3404- _engine = 0;
3405-}
3406
3407=== removed file 'rockworkd/libpebble/jskitmanager.h'
3408--- rockworkd/libpebble/jskitmanager.h 2016-01-03 05:06:13 +0000
3409+++ rockworkd/libpebble/jskitmanager.h 1970-01-01 00:00:00 +0000
3410@@ -1,65 +0,0 @@
3411-#ifndef JSKITMANAGER_H
3412-#define JSKITMANAGER_H
3413-
3414-#include <QJSEngine>
3415-#include <QPointer>
3416-#include "appmanager.h"
3417-#include "watchconnection.h"
3418-#include "pebble.h"
3419-#include "appmsgmanager.h"
3420-//#include "settings.h"
3421-
3422-class JSKitPebble;
3423-class JSKitConsole;
3424-class JSKitLocalStorage;
3425-class JSKitGeolocation;
3426-
3427-class JSKitManager : public QObject
3428-{
3429- Q_OBJECT
3430-
3431-public:
3432- explicit JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent = 0);
3433- ~JSKitManager();
3434-
3435- QJSEngine * engine();
3436- bool isJSKitAppRunning() const;
3437-
3438- static QString describeError(QJSValue error);
3439-
3440- void showConfiguration();
3441- void handleWebviewClosed(const QString &result);
3442- void setConfigurationId(const QUuid &uuid);
3443- AppInfo currentApp();
3444-
3445-signals:
3446- void appNotification(const QUuid &uuid, const QString &title, const QString &body);
3447- void openURL(const QString &uuid, const QString &url);
3448-
3449-private slots:
3450- void handleAppStarted(const QUuid &uuid);
3451- void handleAppStopped(const QUuid &uuid);
3452- void handleAppMessage(const QUuid &uuid, const QVariantMap &msg);
3453-
3454-private:
3455- bool loadJsFile(const QString &filename);
3456- void startJsApp();
3457- void stopJsApp();
3458-
3459-private:
3460- friend class JSKitPebble;
3461-
3462- Pebble *m_pebble;
3463- WatchConnection *m_connection;
3464- AppManager *_apps;
3465- AppMsgManager *_appmsg;
3466- AppInfo _curApp;
3467- QJSEngine *_engine;
3468- QPointer<JSKitPebble> _jspebble;
3469- QPointer<JSKitConsole> _jsconsole;
3470- QPointer<JSKitLocalStorage> _jsstorage;
3471- QPointer<JSKitGeolocation> _jsgeo;
3472- QUuid m_configurationUuid;
3473-};
3474-
3475-#endif // JSKITMANAGER_H
3476
3477=== removed file 'rockworkd/libpebble/jskitobjects.cpp'
3478--- rockworkd/libpebble/jskitobjects.cpp 2016-01-10 22:35:04 +0000
3479+++ rockworkd/libpebble/jskitobjects.cpp 1970-01-01 00:00:00 +0000
3480@@ -1,859 +0,0 @@
3481-#include <QDesktopServices>
3482-#include <QUrl>
3483-#include <QAuthenticator>
3484-#include <QBuffer>
3485-#include <QDir>
3486-#include <QTimerEvent>
3487-#include <QCryptographicHash>
3488-#include <limits>
3489-#include "jskitobjects.h"
3490-
3491-static const char *token_salt = "0feeb7416d3c4546a19b04bccd8419b1";
3492-
3493-JSKitPebble::JSKitPebble(const AppInfo &info, JSKitManager *mgr, QObject *parent)
3494- : QObject(parent), _appInfo(info), _mgr(mgr)
3495-{
3496-}
3497-
3498-void JSKitPebble::addEventListener(const QString &type, QJSValue function)
3499-{
3500- qDebug() << "Adding event listener for " << type;
3501- _callbacks[type].append(function);
3502-}
3503-
3504-void JSKitPebble::removeEventListener(const QString &type, QJSValue function)
3505-{
3506- if (!_callbacks.contains(type)) return;
3507- QList<QJSValue> &callbacks = _callbacks[type];
3508-
3509- for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ) {
3510- if (it->strictlyEquals(function)) {
3511- it = callbacks.erase(it);
3512- } else {
3513- ++it;
3514- }
3515- }
3516-
3517- if (callbacks.empty()) {
3518- _callbacks.remove(type);
3519- }
3520-}
3521-
3522-int JSKitPebble::setInterval(QJSValue expression, int delay)
3523-{
3524- qDebug() << "Setting interval for " << delay << "ms: " << expression.toString();
3525- if (expression.isString() || expression.isCallable()) {
3526- int timerId = startTimer(delay);
3527- _intervals.insert(timerId, expression);
3528- qDebug() << "Timer id: " << timerId;
3529- return timerId;
3530- }
3531- return -1;
3532-}
3533-
3534-void JSKitPebble::clearInterval(int timerId)
3535-{
3536- qDebug() << "Killing interval " << timerId ;
3537- killTimer(timerId);
3538- _intervals.remove(timerId);
3539-}
3540-
3541-int JSKitPebble::setTimeout(QJSValue expression, int delay)
3542-{
3543- qDebug() << "Setting timeout for " << delay << "ms: " << expression.toString();
3544- if (expression.isString() || expression.isCallable()) {
3545- int timerId = startTimer(delay);
3546- _timeouts.insert(timerId, expression);
3547- return timerId;
3548- }
3549- return -1;
3550-}
3551-
3552-void JSKitPebble::clearTimeout(int timerId)
3553-{
3554- qDebug() << "Killing timeout " << timerId ;
3555- killTimer(timerId);
3556- _timeouts.remove(timerId);
3557-}
3558-
3559-void JSKitPebble::timerEvent(QTimerEvent *event)
3560-{
3561- int id = event->timerId();
3562- QJSValue expression; // find in either intervals or timeouts
3563- if (_intervals.contains(id))
3564- expression = _intervals.value(id);
3565- else if (_timeouts.contains(id)) {
3566- expression = _timeouts.value(id);
3567- killTimer(id); // timeouts don't repeat
3568- }
3569- else {
3570- qWarning() << "Unknown timer event";
3571- killTimer(id); // interval nor timeout exist. kill the timer
3572- return;
3573- }
3574-
3575- if (expression.isCallable()) { // call it if it's a function
3576- QJSValue result = expression.call().toString();
3577- qDebug() << "Timer function result: " << result.toString();
3578- }
3579- else { // otherwise evaluate it
3580- QJSValue result = _mgr->engine()->evaluate(expression.toString());
3581- qDebug() << "Timer expression result: " << result.toString();
3582- }
3583-}
3584-
3585-uint JSKitPebble::sendAppMessage(QJSValue message, QJSValue callbackForAck, QJSValue callbackForNack)
3586-{
3587- QVariantMap data = message.toVariant().toMap();
3588- QPointer<JSKitPebble> pebbObj = this;
3589- uint transactionId = _mgr->_appmsg->nextTransactionId();
3590-
3591- qDebug() << "sendAppMessage" << data;
3592-
3593- _mgr->_appmsg->send(_appInfo.uuid(), data,
3594- [pebbObj, transactionId, callbackForAck]() mutable {
3595- if (pebbObj.isNull()) return;
3596- if (callbackForAck.isCallable()) {
3597- qDebug() << "Invoking ack callback";
3598- QJSValue event = pebbObj->buildAckEventObject(transactionId);
3599- QJSValue result = callbackForAck.call(QJSValueList({event}));
3600- if (result.isError()) {
3601- qWarning() << "error while invoking ACK callback" << callbackForAck.toString() << ":"
3602- << JSKitManager::describeError(result);
3603- }
3604- } else {
3605- qDebug() << "Ack callback not callable";
3606- }
3607- },
3608- [pebbObj, transactionId, callbackForNack]() mutable {
3609- if (pebbObj.isNull()) return;
3610- if (callbackForNack.isCallable()) {
3611- qDebug() << "Invoking nack callback";
3612- QJSValue event = pebbObj->buildAckEventObject(transactionId, "NACK from watch");
3613- QJSValue result = callbackForNack.call(QJSValueList({event}));
3614- if (result.isError()) {
3615- qWarning() << "error while invoking NACK callback" << callbackForNack.toString() << ":"
3616- << JSKitManager::describeError(result);
3617- }
3618- } else {
3619- qDebug() << "Nack callback not callable";
3620- }
3621- });
3622-
3623- return transactionId;
3624-}
3625-
3626-void JSKitPebble::showSimpleNotificationOnPebble(const QString &title, const QString &body)
3627-{
3628- qDebug() << "showSimpleNotificationOnPebble" << title << body;
3629- emit _mgr->appNotification(_appInfo.uuid(), title, body);
3630-}
3631-
3632-void JSKitPebble::openURL(const QUrl &url)
3633-{
3634- qDebug() << "opening url" << url.toString();
3635- emit _mgr->openURL(_appInfo.uuid().toString(), url.toString());
3636-}
3637-
3638-QString JSKitPebble::getAccountToken() const
3639-{
3640- // We do not have any account system, so we just fake something up.
3641- QCryptographicHash hasher(QCryptographicHash::Md5);
3642-
3643- hasher.addData(token_salt, strlen(token_salt));
3644- hasher.addData(_appInfo.uuid().toByteArray());
3645-
3646- QSettings settings;
3647- QString token = settings.value("accountToken").toString();
3648-// QString token = _mgr->_settings->property("accountToken").toString();
3649- if (token.isEmpty()) {
3650- token = QUuid::createUuid().toString();
3651- qDebug() << "created new account token" << token;
3652- settings.setValue("accountToken", token);
3653-// _mgr->_settings->setProperty("accountToken", token);
3654- }
3655- hasher.addData(token.toLatin1());
3656-
3657- QString hash = hasher.result().toHex();
3658- qDebug() << "returning account token" << hash;
3659-
3660- return hash;
3661-}
3662-
3663-QString JSKitPebble::getWatchToken() const
3664-{
3665- QCryptographicHash hasher(QCryptographicHash::Md5);
3666-
3667- hasher.addData(token_salt, strlen(token_salt));
3668- hasher.addData(_appInfo.uuid().toByteArray());
3669- hasher.addData(_mgr->m_pebble->serialNumber().toLatin1());
3670-
3671- QString hash = hasher.result().toHex();
3672- qDebug() << "returning watch token" << hash;
3673-
3674- return hash;
3675-}
3676-
3677-QJSValue JSKitPebble::createXMLHttpRequest()
3678-{
3679- JSKitXMLHttpRequest *xhr = new JSKitXMLHttpRequest(_mgr, 0);
3680- // Should be deleted by JS engine.
3681- return _mgr->engine()->newQObject(xhr);
3682-}
3683-
3684-QJSValue JSKitPebble::buildAckEventObject(uint transaction, const QString &message) const
3685-{
3686- QJSEngine *engine = _mgr->engine();
3687- QJSValue eventObj = engine->newObject();
3688- QJSValue dataObj = engine->newObject();
3689-
3690- dataObj.setProperty("transactionId", engine->toScriptValue(transaction));
3691- eventObj.setProperty("data", dataObj);
3692-
3693- if (!message.isEmpty()) {
3694- QJSValue errorObj = engine->newObject();
3695- errorObj.setProperty("message", engine->toScriptValue(message));
3696- eventObj.setProperty("error", errorObj);
3697- }
3698-
3699- return eventObj;
3700-}
3701-
3702-void JSKitPebble::invokeCallbacks(const QString &type, const QJSValueList &args)
3703-{
3704- if (!_callbacks.contains(type)) return;
3705- QList<QJSValue> &callbacks = _callbacks[type];
3706-
3707- for (QList<QJSValue>::iterator it = callbacks.begin(); it != callbacks.end(); ++it) {
3708- qDebug() << "invoking callback" << type << it->toString();
3709- QJSValue result = it->call(args);
3710- if (result.isError()) {
3711- qWarning() << "error while invoking callback" << type << it->toString() << ":"
3712- << JSKitManager::describeError(result);
3713- }
3714- }
3715-}
3716-
3717-JSKitConsole::JSKitConsole(QObject *parent)
3718- : QObject(parent), l(metaObject()->className())
3719-{
3720-}
3721-
3722-void JSKitConsole::log(const QString &msg)
3723-{
3724- qCDebug(l) << msg;
3725-}
3726-
3727-void JSKitConsole::warn(const QString &msg)
3728-{
3729- qCWarning(l) << msg;
3730-}
3731-
3732-void JSKitConsole::error(const QString &msg)
3733-{
3734- qCCritical(l) << msg;
3735-}
3736-
3737-void JSKitConsole::info(const QString &msg)
3738-{
3739- qCDebug(l) << msg;
3740-}
3741-
3742-JSKitLocalStorage::JSKitLocalStorage(const QString &storagePath, const QUuid &uuid, QObject *parent):
3743- QObject(parent),
3744- _storage(new QSettings(getStorageFileFor(storagePath, uuid), QSettings::IniFormat, this))
3745-{
3746- _len = _storage->allKeys().size();
3747-}
3748-
3749-int JSKitLocalStorage::length() const
3750-{
3751- return _len;
3752-}
3753-
3754-QJSValue JSKitLocalStorage::getItem(const QString &key) const
3755-{
3756- QVariant value = _storage->value(key);
3757- if (value.isValid()) {
3758- return QJSValue(value.toString());
3759- } else {
3760- return QJSValue(QJSValue::NullValue);
3761- }
3762-}
3763-
3764-void JSKitLocalStorage::setItem(const QString &key, const QString &value)
3765-{
3766- _storage->setValue(key, QVariant::fromValue(value));
3767- checkLengthChanged();
3768-}
3769-
3770-void JSKitLocalStorage::removeItem(const QString &key)
3771-{
3772- _storage->remove(key);
3773- checkLengthChanged();
3774-}
3775-
3776-void JSKitLocalStorage::clear()
3777-{
3778- _storage->clear();
3779- _len = 0;
3780- emit lengthChanged();
3781-}
3782-
3783-void JSKitLocalStorage::checkLengthChanged()
3784-{
3785- int curLen = _storage->allKeys().size();
3786- if (_len != curLen) {
3787- _len = curLen;
3788- emit lengthChanged();
3789- }
3790-}
3791-
3792-QString JSKitLocalStorage::getStorageFileFor(const QString &storageDir, const QUuid &uuid)
3793-{
3794- QDir dataDir(storageDir + "/js-storage");
3795- if (!dataDir.exists() && !dataDir.mkpath(dataDir.absolutePath())) {
3796- qWarning() << "Error creating jskit storage dir";
3797- return QString();
3798- }
3799- QString fileName = uuid.toString();
3800- fileName.remove('{');
3801- fileName.remove('}');
3802- return dataDir.absoluteFilePath(fileName + ".ini");
3803-}
3804-
3805-JSKitXMLHttpRequest::JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent)
3806- : QObject(parent), l(metaObject()->className()), _mgr(mgr),
3807- _net(new QNetworkAccessManager(this)), _timeout(0), _reply(0)
3808-{
3809- qCDebug(l) << "constructed";
3810- connect(_net, &QNetworkAccessManager::authenticationRequired,
3811- this, &JSKitXMLHttpRequest::handleAuthenticationRequired);
3812-}
3813-
3814-JSKitXMLHttpRequest::~JSKitXMLHttpRequest()
3815-{
3816- qCDebug(l) << "destructed";
3817-}
3818-
3819-void JSKitXMLHttpRequest::open(const QString &method, const QString &url, bool async, const QString &username, const QString &password)
3820-{
3821- if (_reply) {
3822- _reply->deleteLater();
3823- _reply = 0;
3824- }
3825-
3826- _username = username;
3827- _password = password;
3828- _request = QNetworkRequest(QUrl(url));
3829- _verb = method;
3830- Q_UNUSED(async);
3831-
3832- qCDebug(l) << "opened to URL" << _request.url().toString();
3833-}
3834-
3835-void JSKitXMLHttpRequest::setRequestHeader(const QString &header, const QString &value)
3836-{
3837- qCDebug(l) << "setRequestHeader" << header << value;
3838- _request.setRawHeader(header.toLatin1(), value.toLatin1());
3839-}
3840-
3841-void JSKitXMLHttpRequest::send(const QJSValue &data)
3842-{
3843- QByteArray byteData;
3844-
3845- if (data.isUndefined() || data.isNull()) {
3846- // Do nothing, byteData is empty.
3847- } else if (data.isString()) {
3848- byteData = data.toString().toUtf8();
3849- } else if (data.isObject()) {
3850- if (data.hasProperty("byteLength")) {
3851- // Looks like an ArrayView or an ArrayBufferView!
3852- QJSValue buffer = data.property("buffer");
3853- if (buffer.isUndefined()) {
3854- // We must assume we've been passed an ArrayBuffer directly
3855- buffer = data;
3856- }
3857-
3858- QJSValue array = data.property("_bytes");
3859- int byteLength = data.property("byteLength").toInt();
3860-
3861- if (array.isArray()) {
3862- byteData.reserve(byteLength);
3863-
3864- for (int i = 0; i < byteLength; i++) {
3865- byteData.append(array.property(i).toInt());
3866- }
3867-
3868- qCDebug(l) << "passed an ArrayBufferView of" << byteData.length() << "bytes";
3869- } else {
3870- qCWarning(l) << "passed an unknown/invalid ArrayBuffer" << data.toString();
3871- }
3872- } else {
3873- qCWarning(l) << "passed an unknown object" << data.toString();
3874- }
3875-
3876- }
3877-
3878- QBuffer *buffer;
3879- if (!byteData.isEmpty()) {
3880- buffer = new QBuffer;
3881- buffer->setData(byteData);
3882- } else {
3883- buffer = 0;
3884- }
3885-
3886- qCDebug(l) << "sending" << _verb << "to" << _request.url() << "with" << QString::fromUtf8(byteData);
3887- _reply = _net->sendCustomRequest(_request, _verb.toLatin1(), buffer);
3888-
3889- connect(_reply, &QNetworkReply::finished,
3890- this, &JSKitXMLHttpRequest::handleReplyFinished);
3891- connect(_reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
3892- this, &JSKitXMLHttpRequest::handleReplyError);
3893-
3894- if (buffer) {
3895- // So that it gets deleted alongside the reply object.
3896- buffer->setParent(_reply);
3897- }
3898-}
3899-
3900-void JSKitXMLHttpRequest::abort()
3901-{
3902- if (_reply) {
3903- _reply->deleteLater();
3904- _reply = 0;
3905- }
3906-}
3907-
3908-QJSValue JSKitXMLHttpRequest::onload() const
3909-{
3910- return _onload;
3911-}
3912-
3913-void JSKitXMLHttpRequest::setOnload(const QJSValue &value)
3914-{
3915- _onload = value;
3916-}
3917-
3918-QJSValue JSKitXMLHttpRequest::ontimeout() const
3919-{
3920- return _ontimeout;
3921-}
3922-
3923-void JSKitXMLHttpRequest::setOntimeout(const QJSValue &value)
3924-{
3925- _ontimeout = value;
3926-}
3927-
3928-QJSValue JSKitXMLHttpRequest::onerror() const
3929-{
3930- return _onerror;
3931-}
3932-
3933-void JSKitXMLHttpRequest::setOnerror(const QJSValue &value)
3934-{
3935- _onerror = value;
3936-}
3937-
3938-uint JSKitXMLHttpRequest::readyState() const
3939-{
3940- if (!_reply) {
3941- return UNSENT;
3942- } else if (_reply->isFinished()) {
3943- return DONE;
3944- } else {
3945- return LOADING;
3946- }
3947-}
3948-
3949-uint JSKitXMLHttpRequest::timeout() const
3950-{
3951- return _timeout;
3952-}
3953-
3954-void JSKitXMLHttpRequest::setTimeout(uint value)
3955-{
3956- _timeout = value;
3957- // TODO Handle fetch in-progress.
3958-}
3959-
3960-uint JSKitXMLHttpRequest::status() const
3961-{
3962- if (!_reply || !_reply->isFinished()) {
3963- return 0;
3964- } else {
3965- return _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt();
3966- }
3967-}
3968-
3969-QString JSKitXMLHttpRequest::statusText() const
3970-{
3971- if (!_reply || !_reply->isFinished()) {
3972- return QString();
3973- } else {
3974- return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
3975- }
3976-}
3977-
3978-QString JSKitXMLHttpRequest::responseType() const
3979-{
3980- return _responseType;
3981-}
3982-
3983-void JSKitXMLHttpRequest::setResponseType(const QString &type)
3984-{
3985- qCDebug(l) << "response type set to" << type;
3986- _responseType = type;
3987-}
3988-
3989-QJSValue JSKitXMLHttpRequest::response() const
3990-{
3991- QJSEngine *engine = _mgr->engine();
3992- if (_responseType.isEmpty() || _responseType == "text") {
3993- return engine->toScriptValue(QString::fromUtf8(_response));
3994- } else if (_responseType == "arraybuffer") {
3995- QJSValue arrayBufferProto = engine->globalObject().property("ArrayBuffer").property("prototype");
3996- QJSValue arrayBuf = engine->newObject();
3997- if (!arrayBufferProto.isUndefined()) {
3998- arrayBuf.setPrototype(arrayBufferProto);
3999- arrayBuf.setProperty("byteLength", engine->toScriptValue<uint>(_response.size()));
4000- QJSValue array = engine->newArray(_response.size());
4001- for (int i = 0; i < _response.size(); i++) {
4002- array.setProperty(i, engine->toScriptValue<int>(_response[i]));
4003- }
4004- arrayBuf.setProperty("_bytes", array);
4005- qCDebug(l) << "returning ArrayBuffer of" << _response.size() << "bytes";
4006- } else {
4007- qCWarning(l) << "Cannot find proto of ArrayBuffer";
4008- }
4009- return arrayBuf;
4010- } else {
4011- qCWarning(l) << "unsupported responseType:" << _responseType;
4012- return engine->toScriptValue<void*>(0);
4013- }
4014-}
4015-
4016-QString JSKitXMLHttpRequest::responseText() const
4017-{
4018- return QString::fromUtf8(_response);
4019-}
4020-
4021-void JSKitXMLHttpRequest::handleReplyFinished()
4022-{
4023- if (!_reply) {
4024- qCDebug(l) << "reply finished too late";
4025- return;
4026- }
4027-
4028- _response = _reply->readAll();
4029- qCDebug(l) << "reply finished, reply text:" << QString::fromUtf8(_response);
4030-
4031- emit readyStateChanged();
4032- emit statusChanged();
4033- emit statusTextChanged();
4034- emit responseChanged();
4035- emit responseTextChanged();
4036-
4037- if (_onload.isCallable()) {
4038- qCDebug(l) << "going to call onload handler:" << _onload.toString();
4039- QJSValue result = _onload.callWithInstance(_mgr->engine()->newQObject(this));
4040- if (result.isError()) {
4041- qCWarning(l) << "JS error on onload handler:" << JSKitManager::describeError(result);
4042- }
4043- } else {
4044- qCDebug(l) << "No onload set";
4045- }
4046-}
4047-
4048-void JSKitXMLHttpRequest::handleReplyError(QNetworkReply::NetworkError code)
4049-{
4050- if (!_reply) {
4051- qCDebug(l) << "reply error too late";
4052- return;
4053- }
4054-
4055- qCDebug(l) << "reply error" << code;
4056-
4057- emit readyStateChanged();
4058- emit statusChanged();
4059- emit statusTextChanged();
4060-
4061- if (_onerror.isCallable()) {
4062- qCDebug(l) << "going to call onerror handler:" << _onload.toString();
4063- QJSValue result = _onerror.callWithInstance(_mgr->engine()->newQObject(this));
4064- if (result.isError()) {
4065- qCWarning(l) << "JS error on onerror handler:" << JSKitManager::describeError(result);
4066- }
4067- }
4068-}
4069-
4070-void JSKitXMLHttpRequest::handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth)
4071-{
4072- if (_reply == reply) {
4073- qCDebug(l) << "authentication required";
4074-
4075- if (!_username.isEmpty() || !_password.isEmpty()) {
4076- qCDebug(l) << "using provided authorization:" << _username;
4077-
4078- auth->setUser(_username);
4079- auth->setPassword(_password);
4080- } else {
4081- qCDebug(l) << "no username or password provided";
4082- }
4083- }
4084-}
4085-
4086-JSKitGeolocation::JSKitGeolocation(JSKitManager *mgr, QObject *parent)
4087- : QObject(parent), l(metaObject()->className()),
4088- _mgr(mgr), _source(0), _lastWatchId(0)
4089-{
4090-}
4091-
4092-void JSKitGeolocation::getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
4093-{
4094- setupWatcher(successCallback, errorCallback, options, true);
4095-}
4096-
4097-int JSKitGeolocation::watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options)
4098-{
4099- return setupWatcher(successCallback, errorCallback, options, false);
4100-}
4101-
4102-void JSKitGeolocation::clearWatch(int watchId)
4103-{
4104- removeWatcher(watchId);
4105-}
4106-
4107-void JSKitGeolocation::handleError(QGeoPositionInfoSource::Error error)
4108-{
4109- qCWarning(l) << "positioning error: " << error;
4110- // TODO
4111-}
4112-
4113-void JSKitGeolocation::handlePosition(const QGeoPositionInfo &pos)
4114-{
4115- qCDebug(l) << "got position at" << pos.timestamp() << "type" << pos.coordinate().type();
4116-
4117- if (_watches.empty()) {
4118- qCWarning(l) << "got position update but no one is watching";
4119- _source->stopUpdates(); // Just in case.
4120- return;
4121- }
4122-
4123- QJSValue obj = buildPositionObject(pos);
4124-
4125- for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) {
4126- invokeCallback(it->successCallback, obj);
4127-
4128- if (it->once) {
4129- it = _watches.erase(it);
4130- } else {
4131- it->timer.restart();
4132- ++it;
4133- }
4134- }
4135-}
4136-
4137-void JSKitGeolocation::handleTimeout()
4138-{
4139- qCDebug(l) << "positioning timeout";
4140-
4141- if (_watches.empty()) {
4142- qCWarning(l) << "got position timeout but no one is watching";
4143- _source->stopUpdates();
4144- return;
4145- }
4146-
4147- QJSValue obj = buildPositionErrorObject(TIMEOUT, "timeout");
4148-
4149- for (auto it = _watches.begin(); it != _watches.end(); /*no adv*/) {
4150- if (it->timer.hasExpired(it->timeout)) {
4151- qCDebug(l) << "positioning timeout for watch" << it->watchId
4152- << ", watch is" << it->timer.elapsed() << "ms old, timeout is" << it->timeout;
4153- invokeCallback(it->errorCallback, obj);
4154-
4155- if (it->once) {
4156- it = _watches.erase(it);
4157- } else {
4158- it->timer.restart();
4159- ++it;
4160- }
4161- } else {
4162- ++it;
4163- }
4164- }
4165-
4166- QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
4167-}
4168-
4169-void JSKitGeolocation::updateTimeouts()
4170-{
4171- int once_timeout = -1, updates_timeout = -1;
4172-
4173- qCDebug(l) << Q_FUNC_INFO;
4174-
4175- Q_FOREACH(const Watcher &watcher, _watches) {
4176- qint64 rem_timeout = watcher.timeout - watcher.timer.elapsed();
4177- qCDebug(l) << "watch" << watcher.watchId << "rem timeout" << rem_timeout;
4178- if (rem_timeout >= 0) {
4179- // In case it is too large...
4180- rem_timeout = qMin<qint64>(rem_timeout, std::numeric_limits<int>::max());
4181- if (watcher.once) {
4182- once_timeout = once_timeout >= 0 ? qMin<int>(once_timeout, rem_timeout) : rem_timeout;
4183- } else {
4184- updates_timeout = updates_timeout >= 0 ? qMin<int>(updates_timeout, rem_timeout) : rem_timeout;
4185- }
4186- }
4187- }
4188-
4189- if (updates_timeout >= 0) {
4190- qCDebug(l) << "setting location update interval to" << updates_timeout;
4191- _source->setUpdateInterval(updates_timeout);
4192- _source->startUpdates();
4193- } else {
4194- qCDebug(l) << "stopping updates";
4195- _source->stopUpdates();
4196- }
4197-
4198- if (once_timeout >= 0) {
4199- qCDebug(l) << "requesting single location update with timeout" << once_timeout;
4200- _source->requestUpdate(once_timeout);
4201- }
4202-}
4203-
4204-int JSKitGeolocation::setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once)
4205-{
4206- Watcher watcher;
4207- watcher.successCallback = successCallback;
4208- watcher.errorCallback = errorCallback;
4209- watcher.highAccuracy = options.value("enableHighAccuracy").toBool();
4210- watcher.timeout = options.value("timeout", std::numeric_limits<int>::max() - 1).toInt();
4211- watcher.once = once;
4212- watcher.watchId = ++_lastWatchId;
4213-
4214- qlonglong maximumAge = options.value("maximumAge", 0).toLongLong();
4215-
4216- qCDebug(l) << "setting up watcher, gps=" << watcher.highAccuracy << "timeout=" << watcher.timeout << "maximumAge=" << maximumAge << "once=" << once;
4217-
4218- if (!_source) {
4219- _source = QGeoPositionInfoSource::createDefaultSource(this);
4220- connect(_source, static_cast<void (QGeoPositionInfoSource::*)(QGeoPositionInfoSource::Error)>(&QGeoPositionInfoSource::error),
4221- this, &JSKitGeolocation::handleError);
4222- connect(_source, &QGeoPositionInfoSource::positionUpdated,
4223- this, &JSKitGeolocation::handlePosition);
4224- connect(_source, &QGeoPositionInfoSource::updateTimeout,
4225- this, &JSKitGeolocation::handleTimeout);
4226- }
4227-
4228- if (maximumAge > 0) {
4229- QDateTime threshold = QDateTime::currentDateTime().addMSecs(-qint64(maximumAge));
4230- QGeoPositionInfo pos = _source->lastKnownPosition(watcher.highAccuracy);
4231- qCDebug(l) << "got pos timestamp" << pos.timestamp() << " but we want" << threshold;
4232- if (pos.isValid() && pos.timestamp() >= threshold) {
4233- invokeCallback(watcher.successCallback, buildPositionObject(pos));
4234- if (once) {
4235- return -1;
4236- }
4237- } else if (watcher.timeout == 0 && once) {
4238- // If the timeout has already expired, and we have no cached data
4239- // Do not even bother to turn on the GPS; return error object now.
4240- invokeCallback(watcher.errorCallback, buildPositionErrorObject(TIMEOUT, "no cached position"));
4241- return -1;
4242- }
4243- }
4244-
4245- watcher.timer.start();
4246- _watches.append(watcher);
4247-
4248- qCDebug(l) << "added new watch" << watcher.watchId;
4249-
4250- QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
4251-
4252- return watcher.watchId;
4253-}
4254-
4255-void JSKitGeolocation::removeWatcher(int watchId)
4256-{
4257- Watcher watcher;
4258-
4259- qCDebug(l) << "removing watchId" << watcher.watchId;
4260-
4261- for (int i = 0; i < _watches.size(); i++) {
4262- if (_watches[i].watchId == watchId) {
4263- watcher = _watches.takeAt(i);
4264- break;
4265- }
4266- }
4267-
4268- if (watcher.watchId != watchId) {
4269- qCWarning(l) << "watchId not found";
4270- return;
4271- }
4272-
4273- QMetaObject::invokeMethod(this, "updateTimeouts", Qt::QueuedConnection);
4274-}
4275-
4276-QJSValue JSKitGeolocation::buildPositionObject(const QGeoPositionInfo &pos)
4277-{
4278- QJSEngine *engine = _mgr->engine();
4279- QJSValue obj = engine->newObject();
4280- QJSValue coords = engine->newObject();
4281- QJSValue timestamp = engine->toScriptValue<quint64>(pos.timestamp().toMSecsSinceEpoch());
4282-
4283- coords.setProperty("latitude", engine->toScriptValue(pos.coordinate().latitude()));
4284- coords.setProperty("longitude", engine->toScriptValue(pos.coordinate().longitude()));
4285- if (pos.coordinate().type() == QGeoCoordinate::Coordinate3D) {
4286- coords.setProperty("altitude", engine->toScriptValue(pos.coordinate().altitude()));
4287- } else {
4288- coords.setProperty("altitude", engine->toScriptValue<void*>(0));
4289- }
4290-
4291- coords.setProperty("accuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::HorizontalAccuracy)));
4292-
4293- if (pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) {
4294- coords.setProperty("altitudeAccuracy", engine->toScriptValue(pos.attribute(QGeoPositionInfo::VerticalAccuracy)));
4295- } else {
4296- coords.setProperty("altitudeAccuracy", engine->toScriptValue<void*>(0));
4297- }
4298-
4299- if (pos.hasAttribute(QGeoPositionInfo::Direction)) {
4300- coords.setProperty("heading", engine->toScriptValue(pos.attribute(QGeoPositionInfo::Direction)));
4301- } else {
4302- coords.setProperty("heading", engine->toScriptValue<void*>(0));
4303- }
4304-
4305- if (pos.hasAttribute(QGeoPositionInfo::GroundSpeed)) {
4306- coords.setProperty("speed", engine->toScriptValue(pos.attribute(QGeoPositionInfo::GroundSpeed)));
4307- } else {
4308- coords.setProperty("speed", engine->toScriptValue<void*>(0));
4309- }
4310-
4311- obj.setProperty("coords", coords);
4312- obj.setProperty("timestamp", timestamp);
4313-
4314- return obj;
4315-}
4316-
4317-QJSValue JSKitGeolocation::buildPositionErrorObject(PositionError error, const QString &message)
4318-{
4319- QJSEngine *engine = _mgr->engine();
4320- QJSValue obj = engine->newObject();
4321-
4322- obj.setProperty("code", engine->toScriptValue<unsigned short>(error));
4323- obj.setProperty("message", engine->toScriptValue(message));
4324-
4325- return obj;
4326-}
4327-
4328-void JSKitGeolocation::invokeCallback(QJSValue callback, QJSValue event)
4329-{
4330- if (callback.isCallable()) {
4331- qCDebug(l) << "invoking callback" << callback.toString();
4332- QJSValue result = callback.call(QJSValueList({event}));
4333- if (result.isError()) {
4334- qCWarning(l) << "while invoking callback: " << JSKitManager::describeError(result);
4335- }
4336- } else {
4337- qCWarning(l) << "callback is not callable";
4338- }
4339-}
4340
4341=== removed file 'rockworkd/libpebble/jskitobjects.h'
4342--- rockworkd/libpebble/jskitobjects.h 2016-01-10 22:35:04 +0000
4343+++ rockworkd/libpebble/jskitobjects.h 1970-01-01 00:00:00 +0000
4344@@ -1,235 +0,0 @@
4345-#ifndef JSKITMANAGER_P_H
4346-#define JSKITMANAGER_P_H
4347-
4348-#include <QElapsedTimer>
4349-#include <QSettings>
4350-#include <QNetworkRequest>
4351-#include <QNetworkReply>
4352-#include <QGeoPositionInfoSource>
4353-#include "jskitmanager.h"
4354-#include "appinfo.h"
4355-
4356-class JSKitPebble : public QObject
4357-{
4358- Q_OBJECT
4359-
4360-public:
4361- explicit JSKitPebble(const AppInfo &appInfo, JSKitManager *mgr, QObject *parent=0);
4362-
4363- Q_INVOKABLE void addEventListener(const QString &type, QJSValue function);
4364- Q_INVOKABLE void removeEventListener(const QString &type, QJSValue function);
4365-
4366- Q_INVOKABLE int setInterval(QJSValue expression, int delay);
4367- Q_INVOKABLE void clearInterval(int timerId);
4368-
4369- Q_INVOKABLE int setTimeout(QJSValue expression, int delay);
4370- Q_INVOKABLE void clearTimeout(int timerId);
4371-
4372- Q_INVOKABLE uint sendAppMessage(QJSValue message, QJSValue callbackForAck = QJSValue(), QJSValue callbackForNack = QJSValue());
4373-
4374- Q_INVOKABLE void showSimpleNotificationOnPebble(const QString &title, const QString &body);
4375-
4376- Q_INVOKABLE void openURL(const QUrl &url);
4377-
4378- Q_INVOKABLE QString getAccountToken() const;
4379- Q_INVOKABLE QString getWatchToken() const;
4380-
4381- Q_INVOKABLE QJSValue createXMLHttpRequest();
4382-
4383- void invokeCallbacks(const QString &type, const QJSValueList &args = QJSValueList());
4384-
4385-protected:
4386- void timerEvent(QTimerEvent *event);
4387-
4388-private:
4389- QJSValue buildAckEventObject(uint transaction, const QString &message = QString()) const;
4390-
4391-private:
4392- AppInfo _appInfo;
4393- JSKitManager *_mgr;
4394- QHash<QString, QList<QJSValue>> _callbacks;
4395- QHash<int, QJSValue> _intervals;
4396- QHash<int, QJSValue> _timeouts;
4397-};
4398-
4399-class JSKitConsole : public QObject
4400-{
4401- Q_OBJECT
4402- QLoggingCategory l;
4403-
4404-public:
4405- explicit JSKitConsole(QObject *parent=0);
4406-
4407- Q_INVOKABLE void log(const QString &msg);
4408- Q_INVOKABLE void warn(const QString &msg);
4409- Q_INVOKABLE void error(const QString &msg);
4410- Q_INVOKABLE void info(const QString &msg);
4411-};
4412-
4413-class JSKitLocalStorage : public QObject
4414-{
4415- Q_OBJECT
4416-
4417- Q_PROPERTY(int length READ length NOTIFY lengthChanged)
4418-
4419-public:
4420- explicit JSKitLocalStorage(const QString &storagePath, const QUuid &uuid, QObject *parent=0);
4421-
4422- int length() const;
4423-
4424- Q_INVOKABLE QJSValue getItem(const QString &key) const;
4425- Q_INVOKABLE void setItem(const QString &key, const QString &value);
4426- Q_INVOKABLE void removeItem(const QString &key);
4427-
4428- Q_INVOKABLE void clear();
4429-
4430-signals:
4431- void lengthChanged();
4432-
4433-private:
4434- void checkLengthChanged();
4435- static QString getStorageFileFor(const QString &storageDir, const QUuid &uuid);
4436-
4437-private:
4438- QSettings *_storage;
4439- int _len;
4440-};
4441-
4442-class JSKitXMLHttpRequest : public QObject
4443-{
4444- Q_OBJECT
4445- QLoggingCategory l;
4446- Q_ENUMS(ReadyStates)
4447-
4448- Q_PROPERTY(QJSValue onload READ onload WRITE setOnload)
4449- Q_PROPERTY(QJSValue ontimeout READ ontimeout WRITE setOntimeout)
4450- Q_PROPERTY(QJSValue onerror READ onerror WRITE setOnerror)
4451- Q_PROPERTY(uint readyState READ readyState NOTIFY readyStateChanged)
4452- Q_PROPERTY(uint timeout READ timeout WRITE setTimeout)
4453- Q_PROPERTY(uint status READ status NOTIFY statusChanged)
4454- Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
4455- Q_PROPERTY(QString responseType READ responseType WRITE setResponseType)
4456- Q_PROPERTY(QJSValue response READ response NOTIFY responseChanged)
4457- Q_PROPERTY(QString responseText READ responseText NOTIFY responseTextChanged)
4458-
4459-public:
4460- explicit JSKitXMLHttpRequest(JSKitManager *mgr, QObject *parent = 0);
4461- ~JSKitXMLHttpRequest();
4462-
4463- enum ReadyStates {
4464- UNSENT = 0,
4465- OPENED = 1,
4466- HEADERS_RECEIVED = 2,
4467- LOADING = 3,
4468- DONE = 4
4469- };
4470-
4471- Q_INVOKABLE void open(const QString &method, const QString &url, bool async = false, const QString &username = QString(), const QString &password = QString());
4472- Q_INVOKABLE void setRequestHeader(const QString &header, const QString &value);
4473- Q_INVOKABLE void send(const QJSValue &data = QJSValue(QJSValue::NullValue));
4474- Q_INVOKABLE void abort();
4475-
4476- QJSValue onload() const;
4477- void setOnload(const QJSValue &value);
4478- QJSValue ontimeout() const;
4479- void setOntimeout(const QJSValue &value);
4480- QJSValue onerror() const;
4481- void setOnerror(const QJSValue &value);
4482-
4483- uint readyState() const;
4484-
4485- uint timeout() const;
4486- void setTimeout(uint value);
4487-
4488- uint status() const;
4489- QString statusText() const;
4490-
4491- QString responseType() const;
4492- void setResponseType(const QString& type);
4493-
4494- QJSValue response() const;
4495- QString responseText() const;
4496-
4497-signals:
4498- void readyStateChanged();
4499- void statusChanged();
4500- void statusTextChanged();
4501- void responseChanged();
4502- void responseTextChanged();
4503-
4504-private slots:
4505- void handleReplyFinished();
4506- void handleReplyError(QNetworkReply::NetworkError code);
4507- void handleAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth);
4508-
4509-private:
4510- JSKitManager *_mgr;
4511- QNetworkAccessManager *_net;
4512- QString _verb;
4513- uint _timeout;
4514- QString _username;
4515- QString _password;
4516- QNetworkRequest _request;
4517- QNetworkReply *_reply;
4518- QString _responseType;
4519- QByteArray _response;
4520- QJSValue _onload;
4521- QJSValue _ontimeout;
4522- QJSValue _onerror;
4523-};
4524-
4525-class JSKitGeolocation : public QObject
4526-{
4527- Q_OBJECT
4528- Q_ENUMS(PositionError)
4529- QLoggingCategory l;
4530-
4531- struct Watcher;
4532-
4533-public:
4534- explicit JSKitGeolocation(JSKitManager *mgr, QObject *parent=0);
4535-
4536- enum PositionError {
4537- PERMISSION_DENIED = 1,
4538- POSITION_UNAVAILABLE = 2,
4539- TIMEOUT = 3
4540- };
4541-
4542- Q_INVOKABLE void getCurrentPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
4543- Q_INVOKABLE int watchPosition(const QJSValue &successCallback, const QJSValue &errorCallback = QJSValue(), const QVariantMap &options = QVariantMap());
4544- Q_INVOKABLE void clearWatch(int watchId);
4545-
4546-private slots:
4547- void handleError(const QGeoPositionInfoSource::Error error);
4548- void handlePosition(const QGeoPositionInfo &pos);
4549- void handleTimeout();
4550- void updateTimeouts();
4551-
4552-private:
4553- int setupWatcher(const QJSValue &successCallback, const QJSValue &errorCallback, const QVariantMap &options, bool once);
4554- void removeWatcher(int watchId);
4555-
4556- QJSValue buildPositionObject(const QGeoPositionInfo &pos);
4557- QJSValue buildPositionErrorObject(PositionError error, const QString &message = QString());
4558- QJSValue buildPositionErrorObject(const QGeoPositionInfoSource::Error error);
4559- void invokeCallback(QJSValue callback, QJSValue event);
4560-
4561-private:
4562- JSKitManager *_mgr;
4563- QGeoPositionInfoSource *_source;
4564-
4565- struct Watcher {
4566- QJSValue successCallback;
4567- QJSValue errorCallback;
4568- int watchId;
4569- bool once;
4570- bool highAccuracy;
4571- int timeout;
4572- QElapsedTimer timer;
4573- };
4574-
4575- QList<Watcher> _watches;
4576- int _lastWatchId;
4577-};
4578-
4579-#endif // JSKITMANAGER_P_H
4580
4581=== modified file 'rockworkd/libpebble/pebble.cpp'
4582--- rockworkd/libpebble/pebble.cpp 2016-02-01 23:58:55 +0000
4583+++ rockworkd/libpebble/pebble.cpp 2016-02-06 14:32:32 +0000
4584@@ -7,7 +7,7 @@
4585 #include "phonecallendpoint.h"
4586 #include "appmanager.h"
4587 #include "appmsgmanager.h"
4588-#include "jskitmanager.h"
4589+#include "jskit/jskitmanager.h"
4590 #include "blobdb.h"
4591 #include "appdownloader.h"
4592 #include "screenshotendpoint.h"
4593@@ -166,6 +166,11 @@
4594 return m_serialNumber;
4595 }
4596
4597+QString Pebble::language() const
4598+{
4599+ return m_language;
4600+}
4601+
4602 Capabilities Pebble::capabilities() const
4603 {
4604 return m_capabilities;
4605@@ -409,7 +414,8 @@
4606 qDebug() << "BT address" << wd.readBytes(6).toHex();
4607 qDebug() << "CRC:" << wd.read<quint32>();
4608 qDebug() << "Resource timestamp:" << QDateTime::fromTime_t(wd.read<quint32>());
4609- qDebug() << "Language" << wd.readFixedString(6);
4610+ m_language = wd.readFixedString(6);
4611+ qDebug() << "Language" << m_language;
4612 qDebug() << "Language version" << wd.read<quint16>();
4613 // Capabilities is 64 bits but QFlags can only do 32 bits. lets split it into 2 * 32.
4614 // only 8 bits are used atm anyways.
4615
4616=== modified file 'rockworkd/libpebble/pebble.h'
4617--- rockworkd/libpebble/pebble.h 2016-02-01 23:58:55 +0000
4618+++ rockworkd/libpebble/pebble.h 2016-02-06 14:32:32 +0000
4619@@ -35,6 +35,7 @@
4620 Q_PROPERTY(HardwarePlatform hardwarePlatform MEMBER m_hardwarePlatform)
4621 Q_PROPERTY(QString softwareVersion MEMBER m_softwareVersion)
4622 Q_PROPERTY(QString serialNumber MEMBER m_serialNumber)
4623+ Q_PROPERTY(QString language MEMBER m_language)
4624
4625 public:
4626 explicit Pebble(const QBluetoothAddress &address, QObject *parent = 0);
4627@@ -56,6 +57,7 @@
4628 Model model() const;
4629 HardwarePlatform hardwarePlatform() const;
4630 QString serialNumber() const;
4631+ QString language() const;
4632 Capabilities capabilities() const;
4633 bool isUnfaithful() const;
4634 bool recovery() const;
4635@@ -143,6 +145,7 @@
4636 HardwarePlatform m_hardwarePlatform = HardwarePlatformUnknown;
4637 Model m_model = ModelUnknown;
4638 QString m_serialNumber;
4639+ QString m_language;
4640 Capabilities m_capabilities = CapabilityNone;
4641 bool m_isUnfaithful = false;
4642 bool m_recovery = false;
4643
4644=== removed file 'rockworkd/libpebble/typedarray.js'
4645--- rockworkd/libpebble/typedarray.js 2016-01-03 15:22:24 +0000
4646+++ rockworkd/libpebble/typedarray.js 1970-01-01 00:00:00 +0000
4647@@ -1,1030 +0,0 @@
4648-/*
4649- Copyright (c) 2010, Linden Research, Inc.
4650- Copyright (c) 2014, Joshua Bell
4651-
4652- Permission is hereby granted, free of charge, to any person obtaining a copy
4653- of this software and associated documentation files (the "Software"), to deal
4654- in the Software without restriction, including without limitation the rights
4655- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4656- copies of the Software, and to permit persons to whom the Software is
4657- furnished to do so, subject to the following conditions:
4658-
4659- The above copyright notice and this permission notice shall be included in
4660- all copies or substantial portions of the Software.
4661-
4662- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4663- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4664- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4665- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4666- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4667- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
4668- THE SOFTWARE.
4669- $/LicenseInfo$
4670- */
4671-
4672-// Original can be found at:
4673-// https://bitbucket.org/lindenlab/llsd
4674-// Modifications by Joshua Bell inexorabletash@gmail.com
4675-// https://github.com/inexorabletash/polyfill
4676-
4677-// ES3/ES5 implementation of the Krhonos Typed Array Specification
4678-// Ref: http://www.khronos.org/registry/typedarray/specs/latest/
4679-// Date: 2011-02-01
4680-//
4681-// Variations:
4682-// * Allows typed_array.get/set() as alias for subscripts (typed_array[])
4683-// * Gradually migrating structure from Khronos spec to ES6 spec
4684-(function(global) {
4685- 'use strict';
4686- var undefined = (void 0); // Paranoia
4687-
4688- // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to
4689- // create, and consume so much memory, that the browser appears frozen.
4690- var MAX_ARRAY_LENGTH = 1e5;
4691-
4692- // Approximations of internal ECMAScript conversion functions
4693- function Type(v) {
4694- switch(typeof v) {
4695- case 'undefined': return 'undefined';
4696- case 'boolean': return 'boolean';
4697- case 'number': return 'number';
4698- case 'string': return 'string';
4699- default: return v === null ? 'null' : 'object';
4700- }
4701- }
4702-
4703- // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues:
4704- function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); }
4705- function IsCallable(o) { return typeof o === 'function'; }
4706- function ToObject(v) {
4707- if (v === null || v === undefined) throw TypeError();
4708- return Object(v);
4709- }
4710- function ToInt32(v) { return v >> 0; }
4711- function ToUint32(v) { return v >>> 0; }
4712-
4713- // Snapshot intrinsics
4714- var LN2 = Math.LN2,
4715- abs = Math.abs,
4716- floor = Math.floor,
4717- log = Math.log,
4718- max = Math.max,
4719- min = Math.min,
4720- pow = Math.pow,
4721- round = Math.round;
4722-
4723- // emulate ES5 getter/setter API using legacy APIs
4724- // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
4725- // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but
4726- // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless)
4727-
4728- (function() {
4729- var orig = Object.defineProperty;
4730- var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}());
4731-
4732- if (!orig || dom_only) {
4733- Object.defineProperty = function (o, prop, desc) {
4734- // In IE8 try built-in implementation for defining properties on DOM prototypes.
4735- if (orig)
4736- try { return orig(o, prop, desc); } catch (_) {}
4737- if (o !== Object(o))
4738- throw TypeError('Object.defineProperty called on non-object');
4739- if (Object.prototype.__defineGetter__ && ('get' in desc))
4740- Object.prototype.__defineGetter__.call(o, prop, desc.get);
4741- if (Object.prototype.__defineSetter__ && ('set' in desc))
4742- Object.prototype.__defineSetter__.call(o, prop, desc.set);
4743- if ('value' in desc)
4744- o[prop] = desc.value;
4745- return o;
4746- };
4747- }
4748- }());
4749-
4750- // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value)
4751- // for index in 0 ... obj.length
4752- function makeArrayAccessors(obj) {
4753- if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill');
4754-
4755- function makeArrayAccessor(index) {
4756- Object.defineProperty(obj, index, {
4757- 'get': function() { return obj._getter(index); },
4758- 'set': function(v) { obj._setter(index, v); },
4759- enumerable: true,
4760- configurable: false
4761- });
4762- }
4763-
4764- var i;
4765- for (i = 0; i < obj.length; i += 1) {
4766- makeArrayAccessor(i);
4767- }
4768- }
4769-
4770- // Internal conversion functions:
4771- // pack<Type>() - take a number (interpreted as Type), output a byte array
4772- // unpack<Type>() - take a byte array, output a Type-like number
4773-
4774- function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; }
4775- function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; }
4776-
4777- function packI8(n) { return [n & 0xff]; }
4778- function unpackI8(bytes) { return as_signed(bytes[0], 8); }
4779-
4780- function packU8(n) { return [n & 0xff]; }
4781- function unpackU8(bytes) { return as_unsigned(bytes[0], 8); }
4782-
4783- function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; }
4784-
4785- function packI16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
4786- function unpackI16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); }
4787-
4788- function packU16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
4789- function unpackU16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); }
4790-
4791- function packI32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
4792- function unpackI32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
4793-
4794- function packU32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
4795- function unpackU32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
4796-
4797- function packIEEE754(v, ebits, fbits) {
4798-
4799- var bias = (1 << (ebits - 1)) - 1,
4800- s, e, f, ln,
4801- i, bits, str, bytes;
4802-
4803- function roundToEven(n) {
4804- var w = floor(n), f = n - w;
4805- if (f < 0.5)
4806- return w;
4807- if (f > 0.5)
4808- return w + 1;
4809- return w % 2 ? w + 1 : w;
4810- }
4811-
4812- // Compute sign, exponent, fraction
4813- if (v !== v) {
4814- // NaN
4815- // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
4816- e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0;
4817- } else if (v === Infinity || v === -Infinity) {
4818- e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0;
4819- } else if (v === 0) {
4820- e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
4821- } else {
4822- s = v < 0;
4823- v = abs(v);
4824-
4825- if (v >= pow(2, 1 - bias)) {
4826- e = min(floor(log(v) / LN2), 1023);
4827- f = roundToEven(v / pow(2, e) * pow(2, fbits));
4828- if (f / pow(2, fbits) >= 2) {
4829- e = e + 1;
4830- f = 1;
4831- }
4832- if (e > bias) {
4833- // Overflow
4834- e = (1 << ebits) - 1;
4835- f = 0;
4836- } else {
4837- // Normalized
4838- e = e + bias;
4839- f = f - pow(2, fbits);
4840- }
4841- } else {
4842- // Denormalized
4843- e = 0;
4844- f = roundToEven(v / pow(2, 1 - bias - fbits));
4845- }
4846- }
4847-
4848- // Pack sign, exponent, fraction
4849- bits = [];
4850- for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); }
4851- for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); }
4852- bits.push(s ? 1 : 0);
4853- bits.reverse();
4854- str = bits.join('');
4855-
4856- // Bits to bytes
4857- bytes = [];
4858- while (str.length) {
4859- bytes.push(parseInt(str.substring(0, 8), 2));
4860- str = str.substring(8);
4861- }
4862- return bytes;
4863- }
4864-
4865- function unpackIEEE754(bytes, ebits, fbits) {
4866- // Bytes to bits
4867- var bits = [], i, j, b, str,
4868- bias, s, e, f;
4869-
4870- for (i = bytes.length; i; i -= 1) {
4871- b = bytes[i - 1];
4872- for (j = 8; j; j -= 1) {
4873- bits.push(b % 2 ? 1 : 0); b = b >> 1;
4874- }
4875- }
4876- bits.reverse();
4877- str = bits.join('');
4878-
4879- // Unpack sign, exponent, fraction
4880- bias = (1 << (ebits - 1)) - 1;
4881- s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
4882- e = parseInt(str.substring(1, 1 + ebits), 2);
4883- f = parseInt(str.substring(1 + ebits), 2);
4884-
4885- // Produce number
4886- if (e === (1 << ebits) - 1) {
4887- return f !== 0 ? NaN : s * Infinity;
4888- } else if (e > 0) {
4889- // Normalized
4890- return s * pow(2, e - bias) * (1 + f / pow(2, fbits));
4891- } else if (f !== 0) {
4892- // Denormalized
4893- return s * pow(2, -(bias - 1)) * (f / pow(2, fbits));
4894- } else {
4895- return s < 0 ? -0 : 0;
4896- }
4897- }
4898-
4899- function unpackF64(b) { return unpackIEEE754(b, 11, 52); }
4900- function packF64(v) { return packIEEE754(v, 11, 52); }
4901- function unpackF32(b) { return unpackIEEE754(b, 8, 23); }
4902- function packF32(v) { return packIEEE754(v, 8, 23); }
4903-
4904- //
4905- // 3 The ArrayBuffer Type
4906- //
4907-
4908- (function() {
4909-
4910- function ArrayBuffer(length) {
4911- length = ToInt32(length);
4912- if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.');
4913- Object.defineProperty(this, 'byteLength', {value: length});
4914- Object.defineProperty(this, '_bytes', {value: Array(length)});
4915-
4916- for (var i = 0; i < length; i += 1)
4917- this._bytes[i] = 0;
4918- }
4919-
4920- global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;
4921-
4922- //
4923- // 5 The Typed Array View Types
4924- //
4925-
4926- function $TypedArray$() {
4927-
4928- // %TypedArray% ( length )
4929- if (!arguments.length || typeof arguments[0] !== 'object') {
4930- return (function(length) {
4931- length = ToInt32(length);
4932- if (length < 0) throw RangeError('length is not a small enough positive integer.');
4933- Object.defineProperty(this, 'length', {value: length});
4934- Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT});
4935- Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)});
4936- Object.defineProperty(this, 'byteOffset', {value: 0});
4937-
4938- }).apply(this, arguments);
4939- }
4940-
4941- // %TypedArray% ( typedArray )
4942- if (arguments.length >= 1 &&
4943- Type(arguments[0]) === 'object' &&
4944- arguments[0] instanceof $TypedArray$) {
4945- return (function(typedArray){
4946- if (this.constructor !== typedArray.constructor) throw TypeError();
4947-
4948- var byteLength = typedArray.length * this.BYTES_PER_ELEMENT;
4949- Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
4950- Object.defineProperty(this, 'byteLength', {value: byteLength});
4951- Object.defineProperty(this, 'byteOffset', {value: 0});
4952- Object.defineProperty(this, 'length', {value: typedArray.length});
4953-
4954- for (var i = 0; i < this.length; i += 1)
4955- this._setter(i, typedArray._getter(i));
4956-
4957- }).apply(this, arguments);
4958- }
4959-
4960- // %TypedArray% ( array )
4961- if (arguments.length >= 1 &&
4962- Type(arguments[0]) === 'object' &&
4963- !(arguments[0] instanceof $TypedArray$) &&
4964- !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
4965- return (function(array) {
4966-
4967- var byteLength = array.length * this.BYTES_PER_ELEMENT;
4968- Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)});
4969- Object.defineProperty(this, 'byteLength', {value: byteLength});
4970- Object.defineProperty(this, 'byteOffset', {value: 0});
4971- Object.defineProperty(this, 'length', {value: array.length});
4972-
4973- for (var i = 0; i < this.length; i += 1) {
4974- var s = array[i];
4975- this._setter(i, Number(s));
4976- }
4977- }).apply(this, arguments);
4978- }
4979-
4980- // %TypedArray% ( buffer, byteOffset=0, length=undefined )
4981- if (arguments.length >= 1 &&
4982- Type(arguments[0]) === 'object' &&
4983- (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) {
4984- return (function(buffer, byteOffset, length) {
4985-
4986- byteOffset = ToUint32(byteOffset);
4987- if (byteOffset > buffer.byteLength)
4988- throw RangeError('byteOffset out of range');
4989-
4990- // The given byteOffset must be a multiple of the element
4991- // size of the specific type, otherwise an exception is raised.
4992- if (byteOffset % this.BYTES_PER_ELEMENT)
4993- throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.');
4994-
4995- if (length === undefined) {
4996- var byteLength = buffer.byteLength - byteOffset;
4997- if (byteLength % this.BYTES_PER_ELEMENT)
4998- throw RangeError('length of buffer minus byteOffset not a multiple of the element size');
4999- length = byteLength / this.BYTES_PER_ELEMENT;
5000-
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches