mirror of
https://github.com/google/pebble.git
synced 2025-03-23 12:12:19 +00:00
433 lines
23 KiB
Markdown
433 lines
23 KiB
Markdown
|
---
|
|||
|
# Copyright 2025 Google LLC
|
|||
|
#
|
|||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
# you may not use this file except in compliance with the License.
|
|||
|
# You may obtain a copy of the License at
|
|||
|
#
|
|||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
#
|
|||
|
# Unless required by applicable law or agreed to in writing, software
|
|||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
# See the License for the specific language governing permissions and
|
|||
|
# limitations under the License.
|
|||
|
|
|||
|
title: Pebble Emulator 2/2 - JavaScript and CloudPebble
|
|||
|
author: katharine
|
|||
|
tags:
|
|||
|
- Down the Rabbit Hole
|
|||
|
date: 2015-01-30
|
|||
|
---
|
|||
|
|
|||
|
This is another in a series of technical articles provided by the members of the
|
|||
|
Pebble software engineering team. This article describes some recent work done
|
|||
|
at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short
|
|||
|
for Quick EMUlator, is a generic, open source machine emulator and virtualizer).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
> This post is part 2 of 2, and details the work undertaken to provide emulation
|
|||
|
> of the PebbleKit JS and CloudPebble aspects of emulating a Pebble in its
|
|||
|
> entirety. Read
|
|||
|
> [*Pebble Emulator 1/2 - QEMU For Pebble*][9] for information on
|
|||
|
> the creation of the Pebble Emulator.
|
|||
|
|
|||
|
For developers of third-party apps it is insufficient to emulate just the Pebble
|
|||
|
itself, as most non-trivial apps also run JavaScript on the phone to provide the
|
|||
|
watchapp with additional information. Furthermore, some apps are written
|
|||
|
entirely in JavaScript using Pebble.js; it is important to support those as
|
|||
|
well.
|
|||
|
|
|||
|
We therefore decided to implement support for running JavaScript in tandem with
|
|||
|
the emulated Pebble. The JavaScript simulator is called [*pypkjs*][11].
|
|||
|
|
|||
|
## JavaScript Runtimes
|
|||
|
|
|||
|
Since our JavaScript environment primarily consists of standard HTML5 APIs, we
|
|||
|
initially tried building on top of [PhantomJS][1]. However, we quickly ran into
|
|||
|
issues with the very old version of WebKit it uses and a lack of flexibility in
|
|||
|
implementing the functionality we needed, so we abandoned this plan.
|
|||
|
|
|||
|
Our second attempt was to use [node.js][2], but this proved impractical because
|
|||
|
it was difficult to inject additional APIs into modules before loading them, by
|
|||
|
which time they may have already tried to use them. Furthermore, some libraries
|
|||
|
detected that they were running in node and behaved differently than they would
|
|||
|
in the mobile apps; this discrepancy proved tricky to eliminate.
|
|||
|
|
|||
|
We ultimately chose to build directly on top of
|
|||
|
[Google’s V8 JavaScript Engine][3], making use of the [PyV8][4] Python bindings
|
|||
|
to reduce the effort involved in writing our APIs. This gave us the flexibility
|
|||
|
to provide exactly the set of APIs we wanted, without worrying about the
|
|||
|
namespace already being polluted. PyV8 made it easy to define these without
|
|||
|
worrying about the arcana of the C++ V8 interface.
|
|||
|
|
|||
|
## Threading and Event Handling
|
|||
|
|
|||
|
JavaScript is intrinsically single-threaded, and makes heavy use of events. In
|
|||
|
order to provide event handling, we used [gevent][5] to provide support the key
|
|||
|
support for our event loop using greenlets. The “main thread” of the interpreter
|
|||
|
is then a single greenlet, which first evaluates the JavaScript file and then
|
|||
|
enters a blocking event loop. The PebbleKit JS program is terminated when this
|
|||
|
event loop ends. Any calls to asynchronous APIs will then spawn a new greenlet,
|
|||
|
which will ultimately add a callback to the event queue to take effect on the
|
|||
|
main thread.
|
|||
|
|
|||
|
PebbleKit JS provides for single and repeating timers, which can take either a
|
|||
|
function to call or a string to be evaluated when the timer expires. We
|
|||
|
implemented these timers by spawning a new greenlet that sleeps for the given
|
|||
|
period of time, then places either the function call or a call to eval on the
|
|||
|
event queue.
|
|||
|
|
|||
|
## HTML5 APIs
|
|||
|
|
|||
|
Since we are using V8 without any additions (e.g. from Chromium), none of the
|
|||
|
standard HTML5 APIs are present. This works in our favour: we only support a
|
|||
|
restricted subset for PebbleKit JS. Our Android and iOS apps differ in their
|
|||
|
support, so we chose to make the emulator support only the common subset of
|
|||
|
officially supported APIs: XMLHttpRequest, localStorage, geolocation, timers,
|
|||
|
logging and performance measurement. We implemented each of these in Python,
|
|||
|
exposing them to the app via PyV8. We additionally support standard JavaScript
|
|||
|
language features; these are provided for us by V8.
|
|||
|
|
|||
|
## LocalStorage
|
|||
|
|
|||
|
LocalStorage provides persistent storage to PebbleKit JS apps. It is a tricky
|
|||
|
API to implement, as it has properties that are unlike most objects in
|
|||
|
JavaScript. In particular, it can be accessed using either a series of method
|
|||
|
calls (`getItem`, `setItem`, `removeItem`, etc.), as well as via direct property
|
|||
|
access. In either case, the values set should be immediately converted to
|
|||
|
strings and persisted. Furthermore, iterating over the object should iterate
|
|||
|
over exactly those keys set on it, without any additional properties. Correctly
|
|||
|
capturing all of these properties took three attempts.
|
|||
|
|
|||
|
Our first approach was to directly provide a Python object to PyV8, which
|
|||
|
implemented the python magic methods `__setattr__`, `__delattr__` and
|
|||
|
`__getattr__` to catch attribute access and handle them appropriately. However,
|
|||
|
this resulted in ugly code and made it impossible to correctly implement the
|
|||
|
LocalStorage iteration behaviour as PyV8 does not translate non-standard python
|
|||
|
iterators.
|
|||
|
|
|||
|
The second approach was to create a native JavaScript object using
|
|||
|
Object.create, set the functions on it such that they would not appear in
|
|||
|
iteration, and use an ECMAScript 6 (“ES6”) Observer to watch for changes to the
|
|||
|
object that should be handled. This approach failed on two fronts. The primary
|
|||
|
issue was that we could not catch use of the delete operator on keys set using
|
|||
|
property accessors, which would result in values not being removed. Secondly,
|
|||
|
Observers are asynchronous. This made it impossible to implement the immediate
|
|||
|
cast-to-string that LocalStorage performs.
|
|||
|
|
|||
|
The final approach was to use an ES6 Proxy to intercept all calls to the object.
|
|||
|
This enabled us to synchronously catc property accesses to cast and store them.
|
|||
|
It also provided the ability to provide custom iteration behaviour. This
|
|||
|
approach lead to a clean, workable and fully-compliant implementation.
|
|||
|
|
|||
|
## Timers
|
|||
|
|
|||
|
PebbleKit JS provides for single and repeating timers, which can take either a
|
|||
|
function to call or a string to be evaluated when the timer expires. We
|
|||
|
implemented these timers by spawning a new greenlet that sleeps for the given
|
|||
|
period of time, then places either the function call or a call to eval on the
|
|||
|
event queue.
|
|||
|
|
|||
|
## Geolocation
|
|||
|
|
|||
|
PebbleKit JS provides access to the phone’s geolocation facilities. According to
|
|||
|
the documentation, applications must specify that they will use geolocation by
|
|||
|
giving the ‘location’ capability in their manifest file. In practice, the mobile
|
|||
|
apps have never enforced this restriction, and implementing this check turned
|
|||
|
out to break many apps. As such, geolocation is always permitted in the
|
|||
|
emulator, too.
|
|||
|
|
|||
|
Since there is no geolocation capability readily available to the emulator, it
|
|||
|
instead uses the MaxMind GeoIP database to look up the user’s approximate
|
|||
|
location. In practice, this works reasonably well as long as the emulator is
|
|||
|
actually running on the user’s computer. However, when the emulator is *not*
|
|||
|
running on the user’s computer (e.g. when using CloudPebble), the result isn’t
|
|||
|
very useful.
|
|||
|
|
|||
|
## XMLHttpRequest
|
|||
|
|
|||
|
Support for XMLHttpRequest is primarily implemented using the Python
|
|||
|
[*requests* library][6]. Since requests only supports synchronous requests, and
|
|||
|
XMLHttpRequest is primarily asynchronous, we spawn a new greenlet to process the
|
|||
|
send request and fire the required callbacks. In synchronous mode we join that
|
|||
|
greenlet before returning. In synchronous mode we must also place the *creation*
|
|||
|
of the events on the event queue, as creating events requires interacting with
|
|||
|
V8, which may cause errors while the main greenlet is blocked.
|
|||
|
|
|||
|
## Pebble APIs
|
|||
|
|
|||
|
PebbleKit JS provides an additional Pebble object for communicating with the
|
|||
|
watch and handling Pebble accounts. Unlike the HTML5 APIs, these calls have no
|
|||
|
specification, instead having two conflicting implementations and often vague or
|
|||
|
inaccurate documentation that ignores the behaviour of edge cases. Testing real
|
|||
|
apps with these, especially those that do not strictly conform to the
|
|||
|
documentation, has required repeated revisions to the emulated implementation to
|
|||
|
match what the real mobile apps do. For instance, it is not clear what should be
|
|||
|
done when an app tries to send the float *NaN* as an integer.
|
|||
|
|
|||
|
## Watch Communication
|
|||
|
|
|||
|
The PebbleKit JS runtime creates a connection to a TCP socket exposed by QEMU
|
|||
|
and connected to the qemu_serial device. Messages from PebbleKit JS are
|
|||
|
exclusively Pebble Protocol messages sent to the bluetooth channel exposed over
|
|||
|
the Pebble QEMU Protocol. A greenlet is spawned to read from this channel.
|
|||
|
|
|||
|
The primary means of communication available to apps over this channel is
|
|||
|
AppMessage, a mechanism for communicating dictionaries of key-value pairs to the
|
|||
|
watch. These are constructed from the provided JavaScript object. It is possible
|
|||
|
for applications to use string keys; these are replaced with integer keys from
|
|||
|
the app’s manifest file before sending. If no such key can be found an exception
|
|||
|
is thrown. This is the documented behaviour, but diverges from the implemented
|
|||
|
behaviour; both mobile apps will silently discard the erroneous key.
|
|||
|
|
|||
|
When messages are received, a new JavaScript object is created and the messages
|
|||
|
parsed into JavaScript objects. Here we perform the reverse mapping, converting
|
|||
|
received integers to string keys, if any matching keys are specified in the
|
|||
|
app’s manifest. An event is then dispatched to the event loop on the main
|
|||
|
greenlet.
|
|||
|
|
|||
|
A method is also provided for showing a “simple notification”; again, it is not
|
|||
|
clear what sort of notification this should be. This implementation sends an SMS
|
|||
|
notification, which appears to be consistent with what the iOS app does.
|
|||
|
|
|||
|
## Configuration Pages
|
|||
|
|
|||
|
PebbleKit JS provides the option for developers to show a “configuration page”
|
|||
|
in response to the user pressing a Settings button in the Pebble mobile app.
|
|||
|
These configuration pages open a webview containing a user-specified webpage.
|
|||
|
The mobile apps capture navigation to the special URL ‘pebblejs://close’, at
|
|||
|
which point they dismiss the webview and pass the URL fragment to the PebbleKit
|
|||
|
JS app.
|
|||
|
|
|||
|
However, our emulator does not have the ability to present a webview and handle
|
|||
|
the custom URL scheme, so another approach is required. We therefore pass a new
|
|||
|
query parameter, ‘return_to’, to which we pass a URL that should be used in
|
|||
|
place of the custom URL scheme. Configuration pages therefore must be modified
|
|||
|
slightly: instead of using the fixed URL, they should use the value of the
|
|||
|
‘return_to’ parameter, defaulting to the old pebblejs://close URL if it is
|
|||
|
absent.
|
|||
|
|
|||
|
When a URL is opened, the PebbleKit JS simulator starts a temporary webserver
|
|||
|
and gives a URL for it in the ‘return_to’ parameter. When that page is loaded,
|
|||
|
it terminates the webserver and passes the result to the PebbleKit JS app.
|
|||
|
|
|||
|
## Exception Handling
|
|||
|
|
|||
|
There are three potential sources of exceptions: errors in the user’s
|
|||
|
JavaScript, error conditions for which we generate exceptions (e.g. invalid
|
|||
|
AppMessage keys), and unintentional exceptions thrown inside our JavaScript
|
|||
|
code. In all cases, we want to provide the user with useful messages and
|
|||
|
JavaScript stack traces.
|
|||
|
|
|||
|
PyV8 supports exceptions, and will translate exceptions between Python and
|
|||
|
JavaScript: most exceptions from JavaScript will become JSErrors in Python, and
|
|||
|
exceptions from Python will generally become Exceptions in JavaScript. JSErrors
|
|||
|
have a stack trace attached, which can be used to report to the user. PyV8 also
|
|||
|
has some explicit support for IndexErrors (RangeErrors in JavaScript),
|
|||
|
ReferenceErrors, SyntaxErrors and TypeErrors. When such an exception passes the
|
|||
|
Python/JavaScript boundary, it is converted to its matching type.
|
|||
|
|
|||
|
This exception conversion support causes a complication: when a support
|
|||
|
exception crosses from JavaScript to Python, it is turned into a standard Python
|
|||
|
exception rather than a JSError, and so has no stack trace or other JavaScript
|
|||
|
information attached. Since many exceptions become one of those (even when
|
|||
|
thrown from inside JavaScript), and all exception handling occurs in Python,
|
|||
|
many exceptions came through without any useful stack trace.
|
|||
|
|
|||
|
To resolve this issue, we [forked PyV8][7] and changed its exception handling.
|
|||
|
We now define new exceptions for each of the four supported classes that
|
|||
|
multiple- inherit from their respective Python exceptions and JSError, and still
|
|||
|
have JavaScript stack information attached. We can then catch these exceptions
|
|||
|
and display exception information as appropriate.
|
|||
|
|
|||
|
Due to the asynchronous nature of JavaScript, and the heavily greenlet-based
|
|||
|
implementation of pypkjs, we must ensure that every point at which we call into
|
|||
|
developer-provided JavaScript does something useful with any exceptions that may
|
|||
|
be thrown so that JavaScript traces can be passed back to the developer.
|
|||
|
Fortunately, the number of entry points is relatively small: the event system
|
|||
|
and the initial evaluation are the key points to handle exceptions.
|
|||
|
|
|||
|
## Sandboxing
|
|||
|
|
|||
|
While PyV8 makes it very easy to just pass Python objects into JavaScript
|
|||
|
programs and have them treated like standard JavaScript objects, this support is
|
|||
|
an approximation. The resulting objects still feature all the standard Python
|
|||
|
magic methods and properties, which the JavaScript program can access and call.
|
|||
|
Furthermore, Python has no concept of private properties; any object state that
|
|||
|
the Python code has access to can also be accessed by the JavaScript program.
|
|||
|
|
|||
|
While this behaviour is good enough when working with JavaScript programs that
|
|||
|
expect to be running in this environment, the programs that will be run here are
|
|||
|
not expecting to run in this environment. Furthermore, they are untrusted; with
|
|||
|
access to the runtime internals, they could potentially wreak havoc.
|
|||
|
|
|||
|
In order to present a cleaner interface to the JavaScript programs, we instead
|
|||
|
define JavaScript extensions that, inside a closure, feature a ‘native function’
|
|||
|
call. These objects then define proxy functions that call the equivalent
|
|||
|
functions in the Python implementation. By doing this, we both present genuine
|
|||
|
JavaScript objects that act like real objects in all ways, and prevent access to
|
|||
|
the implementation details of the runtime.
|
|||
|
|
|||
|
## Emulation in CloudPebble
|
|||
|
|
|||
|
The majority of our developers use our web-based development environment,
|
|||
|
CloudPebble. The QEMU Pebble emulator and PebbleKit JS simulator described above
|
|||
|
are designed for desktop use, and so would not in themselves be useful to the
|
|||
|
majority of our developers. Some arrangement therefore had to be made for those
|
|||
|
developers.
|
|||
|
|
|||
|
We decided to run a cluster of backend servers which would run QEMU on behalf of
|
|||
|
CloudPebble’s users; CloudPebble would then interact with these servers to
|
|||
|
handle input and display.
|
|||
|
|
|||
|
## Displaying the Screen
|
|||
|
|
|||
|
Displaying a remote framebuffer is a common problem; the most common solution to
|
|||
|
this problem is VNC’s Remote Framebuffer Protocol. QEMU has a VNC server built-
|
|||
|
in, making this the obvious choice to handle displaying the screen.
|
|||
|
|
|||
|
CloudPebble’s VNC client is based on [noVNC][8], an HTML5 JavaScript-based VNC
|
|||
|
client. Since JavaScript cannot create raw socket connections, noVNC instead
|
|||
|
connects to the QEMU VNC server via VNC-over-websockets. QEMU already had
|
|||
|
support for this protocol, but crashed on receiving a connection. We made a
|
|||
|
minor change to initialisation to resolve this.
|
|||
|
|
|||
|
While it would appear to make sense to use indexed colour instead of 24-bit true
|
|||
|
colour for our 1-bit display, QEMU does not support this mode. In practice,
|
|||
|
performance of the true colour display is entirely acceptable, so we did not
|
|||
|
pursue this optimisation.
|
|||
|
|
|||
|
## Communicating With the Emulator
|
|||
|
|
|||
|
CloudPebble expects to be able to communicate with the watch to install apps,
|
|||
|
retrieve logs, take screenshots, etc. With physical watches, this is done by
|
|||
|
connecting to the phone and communicating over the Pebble WebSocket Protocol
|
|||
|
(PWP). Due to restrictions on WebSocket connections within local networks,
|
|||
|
CloudPebble actually connects to an authenticated WebSocket proxy which the
|
|||
|
phone also connects to. In addition, this communication occurs over bluetooth —
|
|||
|
but the PebbleKit JS runtime is already connected to the qemu_serial socket,
|
|||
|
which can only support one client at a time.
|
|||
|
|
|||
|
We therefore chose to implement PWP in the PebbleKit JS simulator. This neatly
|
|||
|
the issue with multiple connections to the same socket, closely mimics how the
|
|||
|
real phone apps behave, and minimises the scope of the changes required to
|
|||
|
CloudPebble. CloudPebble’s use of a WebSocket proxy provides further opportunity
|
|||
|
to imitate that layer as well, enabling us to take advantage of the existing
|
|||
|
authentication mechanisms in CloudPebble.
|
|||
|
|
|||
|
The PebbleKit JS simulator was thus split out to have two ‘runners’; one that
|
|||
|
provides the standard terminal output (sendings logs to stdout and interacting
|
|||
|
with the user’s local machine) and one that implements PWP. The WebSocket runner
|
|||
|
additionally hooks into the low-level send and receive methods in order to
|
|||
|
provide the message echoing functionality specified by PWP. Since the emulator
|
|||
|
requires some communication that is not necessary with real phones, there are
|
|||
|
were some extensions added that are used only by the emulator. However, for the
|
|||
|
most part, existing code works exactly as before once pointed to the new
|
|||
|
WebSocket URL.
|
|||
|
|
|||
|
## Configuration Pages
|
|||
|
|
|||
|
The mechanism previously designed for configuration pages is only usable when
|
|||
|
running locally. To trigger a configuration page, CloudPebble sends a request
|
|||
|
using an extension to the PWP. If the PebbleKit JS app implements a
|
|||
|
configuration page, it receives a response giving it the developer’s intended
|
|||
|
URL. CloudPebble then inserts a return_to parameter and opens a new window with
|
|||
|
the developer’s page. Once the page navigates to the return URL, the page is
|
|||
|
closed and the configuration data sent back over the WebSocket.
|
|||
|
|
|||
|
Due to restrictions on how windows may communicate, CloudPebble must poll the
|
|||
|
new window to discover if it has navigated to the return URL. Once the
|
|||
|
navigation is detected, CloudPebble sends it a message and receives the
|
|||
|
configuration data in reply, after which the window closes.
|
|||
|
|
|||
|
A further complication is pop-up blockers. These usually only permit window
|
|||
|
opens in response to direct user action. Since we had to request a URL from the
|
|||
|
PebbleKit JS app before we could open the window, an active popup blocker will
|
|||
|
tend to block the window. We worked around this by detecting the failure of the
|
|||
|
window to open and providing a button to click, which will usually bypass the
|
|||
|
popup blocker.
|
|||
|
|
|||
|
## Input
|
|||
|
|
|||
|
Originally, button input from CloudPebble was performed by sending keypresses
|
|||
|
over VNC directly to QEMU. However, this turned out to cause issues with key-
|
|||
|
repeat and long button presses. Resolving these issues proved to be difficult,
|
|||
|
so we instead avoided sending VNC keypresses at all. Instead, another extension
|
|||
|
was added to the PWP that permitted sending arbitrary PQP messages to the
|
|||
|
emulator. We then sent packets indicating the state of each button to the
|
|||
|
emulator via PWP. However, mouse clicks tend to be quicker than Pebble watch
|
|||
|
button presses, and the time that the buttons appeared to be ‘pressed’ was too
|
|||
|
short for the firmware to react. To avoid this problem, we rate limited
|
|||
|
CloudPebble to send state changes no more rapidly than once every 100ms; more
|
|||
|
rapid changes are queued to be sent later.
|
|||
|
|
|||
|
The channel for sending PQP messages over PWP is also used to set the battery
|
|||
|
state and toggle bluetooth; in the future, we will also use it to set the
|
|||
|
accelerometer and compass readings.
|
|||
|
|
|||
|
## Compass and Accelerometer Sensors
|
|||
|
|
|||
|
Most computers do not have a compass or an accelerometer — and even if they did,
|
|||
|
it would be impractical to pick up the computer and shake it, tilt it, or rotate
|
|||
|
it to test apps. To deal with this, we took advantage of a piece of hardware
|
|||
|
owned by all Pebble owners, and likely most Pebble developers: their phones.
|
|||
|
|
|||
|
When developers want to use their phones to provide sensor data, a six-digit
|
|||
|
code is generated and stored with the information required to connect to the
|
|||
|
emulator. The user is prompted to open a short URL (cpbl.io) on their phone and
|
|||
|
enter the code on that webpage. The code is looked up and, if found, a
|
|||
|
connection to the emulator is established from the webpage on their phone. The
|
|||
|
webpage then collects accelerometer and compass data using the
|
|||
|
[HTML5 DeviceOrientation APIs][10] and streams it to the emulator.
|
|||
|
|
|||
|
The generated codes expire a few minutes after being generated.
|
|||
|
|
|||
|
## Emulator Management
|
|||
|
|
|||
|
CloudPebble must manage multiple emulators on potentially multiple hosts.
|
|||
|
Management is split between CloudPebble itself and a [controller program][12]
|
|||
|
responsible for managing the lifecycle of individual emulator instances.
|
|||
|
|
|||
|
CloudPebble is aware of a pool of emulator hosts. When a user requests an
|
|||
|
emulator, it picks one at random and requests that its manager spin up an
|
|||
|
emulator. If this is possible, it returns connection details for the emulator to
|
|||
|
the client; if not (e.g. because that host has reached capacity), it picks
|
|||
|
another host and tries again. If no hosts are available a failure message is
|
|||
|
reported to the client and logged in our analytics system.
|
|||
|
|
|||
|
The manager program selects some unused ports and spawns instances of QEMU and
|
|||
|
pypkjs configured to work together, and reports back a UUID and public port
|
|||
|
numbers for the VNC and Pebble WebSocket Protocol servers. The manager then
|
|||
|
expects to be pinged for that emulator periodically; if too long passes without
|
|||
|
being pinged or either QEMU or pypkjs fail, the QEMU and pypkjs instances will
|
|||
|
be terminated.
|
|||
|
|
|||
|
A complication arose when attempting to run this system over the Internet. Some
|
|||
|
users, especially behind corporate firewalls, cannot make connections to the
|
|||
|
non-standard ports that the manager was selecting. To avoid this issue, the
|
|||
|
manager (which runs on the standard HTTPS port 443) can proxy connections to the
|
|||
|
VNC and PWP websockets.
|
|||
|
|
|||
|
Finally, in order to restrict abuse and provide continuity across client
|
|||
|
reloads, CloudPebble tracks emulators assigned to users in a Redis database. If
|
|||
|
a user who already has an emulator requests one and CloudPebble can ping their
|
|||
|
emulator, they are given the same instance again. A new instance can be
|
|||
|
requested by explicitly killing the emulator in the CloudPebble UI, or closing
|
|||
|
the client and waiting for the manager to time out and kill the emulator.
|
|||
|
|
|||
|
|
|||
|
[1]: http://phantomjs.org
|
|||
|
[2]: http://nodejs.org
|
|||
|
[3]: https://code.google.com/p/v8/
|
|||
|
[4]: https://code.google.com/p/pyv8/
|
|||
|
[5]: http://www.gevent.org
|
|||
|
[6]: http://docs.python-requests.org/en/latest/
|
|||
|
[7]: https://github.com/pebble/pyv8
|
|||
|
[8]: https://github.com/kanaka/noVNC
|
|||
|
[9]: /blog/2015/01/30/Development-Of-The-Pebble-Emulator/
|
|||
|
[10]: http://w3c.github.io/deviceorientation/spec-source-orientation.html
|
|||
|
[11]: https://github.com/pebble/pypkjs
|
|||
|
[12]: https://github.com/pebble/cloudpebble-qemu-controller
|