Evernote, JavaScript and the Pebble watch
Pebble just released a public beta of their new SDK, version 2.0, and one of the more intriguing features is the ability to write JavaScript code that executes within the Pebble phone app.
No, that’s not a typo, the JavaScript executes on the phone.
Why would you possibly want to write JavaScript on the your phone? For very good reasons: Pebble watch apps can generally do very little by themselves. Instead they communicate with a custom app that runs on the phone, and that custom phone app does stuff on behalf of the watch app.
For example if I wanted to write an Evernote client than ran on the Pebble watch, I’d code the watch app in C, and then create a custom Android app in Java. My watch app could talk to my Android app, which could in turn talk to Evernote. This is in fact what I did in an experimental client I talked about recently at a conference.
Android and iOS
But the Pebble doesn’t just support Android: it also supports the iPhone. If I wanted to extend my app to iOS I’d need to write an equivalent app in Objective C, or C#. I’d have to duplicate my coding and testing. There are also Bluetooth issues on iOS with the Pebble, which stop more than one app from communicating with the watch at a time, and the app needs to be whitelisted.
So when Pebble announced that they supported writing JavaScript apps that ran on the phone, hosted within a JavaScript engine that ran within the Pebble phone app on both Android and iOS, this seemed appealing. I’d not need to write a custom Android app, and a custom iOS app. Instead I could code up the phone functionality in JavaScript, and all would be good.
But first, I need to check that I could talk to Evernote’s cloud API using the Pebble JavaScript implementation ...
Evernote and Thrift
The Evernote service API is exposed using a technology called Thrift. Originally developed at Facebook, and now hosted by the Apache Foundation, you define your API using a Thrift Interface Definition Language (IDL). This IDL is consumed by Thrift code generators which generate language-specific bindings allowing you to access the interface from Java, C#, PHP, and many other languages, including, of course, JavaScript. The generated code talks to a Thrift runtime, which sends and receives bits over the wire to the corresponding service.
I thought I’d start off by making the Pebble phone-based JavaScript app talk to Evernote, and then once I had that working, I could make it talk in turn to my C watch app.
I downloaded the Evernote JavaScript SDK from GitHub which contains both the generated “proxy” classes generated from the Thrift IDL, and the Thrift Runtime classes, both in JavaScript.
I decided to start simple, and just list my Evernote notebooks:
// Get these by creating an account and logging in to sandbox.evernote.com and then
// going to https://sandbox.evernote.com/api/DeveloperToken.action
var authTokenEvernote = "...";
var noteStoreURL = "https://sandbox.evernote.com/shard/s1/notestore";
// We want to talk the Thrift Binary protocol over HTTP. These classes are in the
// Thrift runtime
var noteStoreTransport = new Thrift.BinaryHttpTransport(noteStoreURL);
var noteStoreProtocol = new Thrift.BinaryProtocol(noteStoreTransport);
// We want to talk to Evernote's NoteStore service. This is generated code
var noteStore = new NoteStoreClient(noteStoreProtocol);
// Ask Evernote what notebooks I have
noteStore.listNotebooks(authTokenEvernote, function (notebooks) {
console.log(notebooks);
});
I put this into a file I called main.js but I also needed to include the Thrift runtime (thrift.js and pebble-js-app.js) and the generated Evernote proxies (including NoteStore_types.js and NoteStore.js).
Since this release of the Pebble SDK only supports a single JavaScript file, named pebble-js-app.js, I borrowed a script volunteered by Matthew Tole that validated the JavaScript and merged all the files together in a single file, and built and ran the Pebble app:
clear
jshint js/main.js || { exit 1; }
jshint pebble/appinfo.json || { exit 1; }
#uglifyjs js/libs/evernote-sdk-js/evernote-sdk-js/thrift/lib/thrift.js ... js/main.js -o pebble/src/js/pebble-js-app.js
cat js/libs/evernote-sdk-js/evernote-sdk-js/thrift/lib/thrift.js ... js/main.js > pebble/src/js/pebble-js-app.js
cd pebble
pebble clean
pebble build || { exit 1; }
if [ "$1" = "install" ]; then
pebble install --logs
fi
The first error I hit when I tried running the code, is that the Thrift JavaScript runtime expects to be able to use the DataView class, which is part of the JavaScript Typed Arrays mechanism, a work in progress:
[INFO ] * :0 JS: pbnote: JavaScript app ready and running!
[INFO ] * :0 Error: pbnote: ReferenceError: Can't find variable: DataView at line 819 in pebble-js-app.js
It turns out although it is not documented, or supported, that there is a partial Typed Array implementation: the Pebble JavaScript Engine has an implementation of the ArrayBuffer type, as well as Int8Array, Uint8Array, etc. There are some peculiarities, such as the ArrayBuffer supporting the slice method, but not the byteLength property.
So I went looking for an ArrayBuffer implementation I could use, and came across Joshua Bell’s implementation on GitHub.
I included it in the build script, but it did nothing. Upon further examination, I discovered that it looks to see if there is an existing implementation and (very politely) does nothing if it finds one. I didn’t want that:
global.ArrayBuffer = ArrayBuffer;
// global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;
I ran once again, but this time I hit a brick wall. This is the code flow for the call to noteStore.ListNotebooks above:
- My code calls listNotebooks which is in the NoteStore.js generated class
- listNotebooks calls send_listNotebooks in NoteStore.js
- send_listNotebooks writes out a listNotebooks message to the BinaryProtocol Thrift Runtime type I initialized in my code above
- listNotebooks continues with a call to send in the BinaryHttpTransport Thrift Runtime type I also initialized above
- the BinaryHttpTransport initializes an XMLHttpRequest and sends off the message that was built up previously.
The trick though, is how it sends the message, which you can see in the last line of this code:
var xhr = new XMLHttpRequest();
xhr.open('POST', this.url, /*async*/true);
xhr.setRequestHeader('Content-Type', 'application/x-thrift');
xhr.setRequestHeader('Accept', 'application/x-thrift');
xhr.responseType = 'arraybuffer';
xhr.onload = function (evt) {
this.received = xhr.response;
this.offset = 0;
try {
var value = recv_method.call(client);
} catch (exception) {
//console.log(JSON.stringify(exception));
value = exception;
callback = onerror;
}
callback(value);
}.bind(this);
xhr.onerror = function (evt) {
//console.log(JSON.stringify(evt));
onerror(evt);
};
xhr.send(postData.buffer);
It attempts to send a Uint8Array’s buffer. I hacked up a dummy web server, and it turns out I was receiving the result of a toString call, which was something like “object ArrayBuffer”…
The Pebble JavaScript engine’s XMLHttpRequest only supports sending string data, not binary data. It doesn’t support sending Typed Arrays.
I tried all kinds of things, but finally admitted defeat, for now.
The concept of having your watch app talk to JavaScript code, which in turn talks to the outside world is very appealing. It means that you don’t have to write, test and maintain separate iOS and Android “companion” apps for your watch app.
I am sure that for most web based services what Pebble provides will be more than enough. Unfortunately for binary based interfaces, such as the Evernote interface, the current XMLHttpRequest support isn’t quite rich enough.
Yet.