Skip to content

Instantly share code, notes, and snippets.

@tvbarajas
Last active August 29, 2015 14:20
Show Gist options
  • Select an option

  • Save tvbarajas/d3e9a717322a836a7b76 to your computer and use it in GitHub Desktop.

Select an option

Save tvbarajas/d3e9a717322a836a7b76 to your computer and use it in GitHub Desktop.
Changes necessary to disable Python's default SSL certificate verification behavior.
#PebbleSDK-3.0-dp9/Pebble/common/phonesim/javascript/pebble.py
__author__ = 'katharine'
import collections
import logging
import requests
import struct
import traceback
import itertools
from uuid import UUID
import urllib
import PyV8 as v8
from pebblecomm.pebble import AppMessage, Notification, Attribute, PebbleHardware
import events
from exceptions import JSRuntimeException
logger = logging.getLogger('pypkjs.javascript.pebble')
class TokenException(Exception):
pass
class Pebble(events.EventSourceMixin, v8.JSClass):
def __init__(self, runtime, pebble):
self.extension = v8.JSExtension(runtime.ext_name("pebble"), """
Pebble = new (function() {
native function _internal_pebble();
_make_proxies(this, _internal_pebble(),
['sendAppMessage', 'showSimpleNotificationOnPebble', 'getAccountToken', 'getWatchToken',
'addEventListener', 'removeEventListener', 'openURL', 'getTimelineToken', 'timelineSubscribe',
'timelineUnsubscribe', 'timelineSubscriptions', 'getActiveWatchInfo']);
this.platform = 'pypkjs';
})();
""", lambda f: lambda: self, dependencies=["runtime/internal/proxy"])
self.pebble = pebble.pebble
self.runtime = runtime
self.tid = 0
self.uuid = UUID(runtime.manifest['uuid'])
self.app_keys = runtime.manifest['appKeys']
self.pending_acks = {}
self.is_ready = False
self._timeline_token = None
super(Pebble, self).__init__(runtime)
def _connect(self):
self._ready()
def _ready(self):
self.pebble.register_endpoint("APPLICATION_MESSAGE", self._handle_appmessage, preprocess=False)
self.is_ready = True
self.triggerEvent("ready")
def _shutdown(self):
self.pebble.unregister_endpoint("APPLICATION_MESSAGE", self._handle_appmessage, preprocess=False)
def _configure(self):
self.triggerEvent("showConfiguration")
def _handle_appmessage(self, endpoint, data):
command, tid = struct.unpack_from("BB", data, 0)
if command in (0x7f, 0xff):
self._handle_ack(command, tid)
elif command == 0x01:
target_uuid = UUID(bytes=data[2:18])
self._handle_message(tid, target_uuid, data[18:])
def _handle_ack(self, command, tid):
try:
success, failure = self.pending_acks[tid]
except KeyError:
return
callback_param = {"data": {"transactionId": tid}}
if command == 0x7f: # NACK
callback_param['data']['error'] = 'Something went wrong.'
if callable(failure):
self.runtime.enqueue(failure, callback_param)
elif command == 0xff: # ACK
if callable(success):
self.runtime.enqueue(success, callback_param)
del self.pending_acks[tid]
def _handle_message(self, tid, uuid, encoded_dict):
if uuid != self.uuid:
logger.warning("Discarded message for %s (expected %s)", uuid, self.uuid)
self.pebble._send_message("APPLICATION_MESSAGE", struct.pack('<BB', 0x7F, tid)) # ACK
return
app_keys = dict(zip(self.app_keys.values(), self.app_keys.keys()))
try:
tuple_count, = struct.unpack_from('<B', encoded_dict, 0)
offset = 1
d = self.runtime.context.eval("({})") # This is kinda absurd.
for i in xrange(tuple_count):
k, t, l = struct.unpack_from('<IBH', encoded_dict, offset)
offset += 7
if t == 0: # BYTE_ARRAY
v = v8.JSArray(list(struct.unpack_from('<%dB' % l, encoded_dict, offset)))
elif t == 1: # CSTRING
v, = struct.unpack_from('<%ds' % l, encoded_dict, offset)
try:
v = v[:v.index('\x00')]
except ValueError:
pass
elif t in (2, 3): # UINT, INT
widths = {
(2, 1): 'B',
(2, 2): 'H',
(2, 4): 'I',
(3, 1): 'b',
(3, 2): 'h',
(3, 4): 'i',
}
v, = struct.unpack_from('<%s' % widths[(t, l)], encoded_dict, offset)
else:
raise Exception("Received bad appmessage dict.")
d[str(k)] = v
if k in app_keys:
d[str(app_keys[k])] = v
offset += l
except:
self.pebble._send_message("APPLICATION_MESSAGE", struct.pack('<BB', 0x7F, tid)) # NACK
raise
else:
self.pebble._send_message("APPLICATION_MESSAGE", struct.pack('<BB', 0xFF, tid)) # ACK
e = events.Event(self.runtime, "AppMessage")
e.payload = d
self.triggerEvent("appmessage", e)
def _check_ready(self):
if not self.is_ready:
raise JSRuntimeException("Can't interact with the watch before the ready event is fired.")
def sendAppMessage(self, message, success=None, failure=None):
self._check_ready()
to_send = {}
message = {k: message[str(k)] for k in message.keys()}
for k, v in message.iteritems():
if k in self.app_keys:
k = self.app_keys[k]
try:
to_send[int(k)] = v
except ValueError:
raise JSRuntimeException("Unknown message key '%s'" % k)
tuples = []
appmessage = AppMessage()
for k, v in to_send.iteritems():
if isinstance(v, v8.JSArray):
v = list(v)
if isinstance(v, basestring):
t = "CSTRING"
v += '\x00'
elif isinstance(v, int):
t = "INT"
v = struct.pack('<i', v)
elif isinstance(v, float): # thanks, javascript
t = "INT"
try:
intv = int(round(v))
except ValueError:
self.runtime.log_output("WARNING: illegal float value %s for appmessage key %s" % (v, k))
intv = 0
v = struct.pack('<i', intv)
elif isinstance(v, collections.Sequence):
t = "BYTE_ARRAY"
fmt = ['<']
for byte in v:
if isinstance(byte, int):
if 0 <= byte <= 255:
fmt.append('B')
else:
raise JSRuntimeException("Bytes must be between 0 and 255 inclusive.")
elif isinstance(byte, str): # This is intentionally not basestring; unicode won't work.
fmt.append('%ss' % len(byte))
else:
raise JSRuntimeException("Unexpected value in byte array.")
v = struct.pack(''.join(fmt), *v)
elif v is None:
continue
else:
raise JSRuntimeException("Invalid value data type for key %s: %s" % (k, type(v)))
tuples.append(appmessage.build_tuple(k, t, v))
d = appmessage.build_dict(tuples)
message = appmessage.build_message(d, "PUSH", self.uuid.bytes, struct.pack('B', self.tid))
self.pending_acks[self.tid] = (success, failure)
self.tid = (self.tid + 1) % 256
self.pebble._send_message("APPLICATION_MESSAGE", message)
def showSimpleNotificationOnPebble(self, title, message):
self._check_ready()
notification = Notification(self.pebble, title, attributes=[Attribute("BODY", message)])
notification.send()
def showNotificationOnPebble(self, opts):
pass
def getAccountToken(self):
self._check_ready()
return self.runtime.runner.account_token
def getWatchToken(self):
self._check_ready()
return self.runtime.runner.watch_token
def openURL(self, url):
self.runtime.open_config_page(url, self._handle_config_response)
def _get_timeline_token(self):
if self._timeline_token is not None:
return self._timeline_token
result = requests.get(self.runtime.runner.urls.sandbox_token % self.uuid,
headers={'Authorization': 'Bearer %s' % self.runtime.runner.oauth_token}, verify=False)
if result.status_code == 404:
raise TokenException("No token available for this app and user.")
result.raise_for_status()
logger.debug("get_timeline_token result: %s", result.json())
self._timeline_token = result.json()['token']
return self._timeline_token
def getTimelineToken(self, success=None, failure=None):
def go():
try:
token = self._get_timeline_token()
except (requests.RequestException, TokenException) as e:
if callable(failure):
self.runtime.enqueue(failure, str(e))
except Exception:
traceback.print_exc()
if callable(failure):
self.runtime.enqueue(failure, "Internal failure.")
else:
if callable(success):
self.runtime.enqueue(success, token)
self.runtime.group.spawn(go)
def _do_timeline_thing(self, method, topic, success, failure):
try:
token = self._get_timeline_token()
result = requests.request(method, self.runtime.runner.urls.manage_subscription % urllib.quote(topic, safe=''),
headers={'X-User-Token': token})
result.raise_for_status()
except (requests.RequestException, TokenException) as e:
if callable(failure):
self.runtime.enqueue(failure, str(e))
except Exception as e:
traceback.print_exc()
if callable(failure):
self.runtime.enqueue(failure, "Internal failure.")
else:
if callable(success):
self.runtime.enqueue(success)
def timelineSubscribe(self, topic, success=None, failure=None):
self.runtime.group.spawn(self._do_timeline_thing, "POST", topic, success, failure)
def timelineUnsubscribe(self, topic, success=None, failure=None):
self.runtime.group.spawn(self._do_timeline_thing, "DELETE", topic, success, failure)
def timelineSubscriptions(self, success=None, failure=None):
def go():
try:
token = self._get_timeline_token()
result = requests.get(self.runtime.runner.urls.app_subscription_list, headers={'X-User-Token': token},verify=False)
result.raise_for_status()
subs = v8.JSArray(result.json()['topics'])
except (requests.RequestException, TokenException) as e:
if callable(failure):
self.runtime.enqueue(failure, str(e))
except Exception:
traceback.print_exc()
if callable(failure):
self.runtime.enqueue(failure, "Internal failure.")
else:
if callable(success):
self.runtime.enqueue(success, subs)
self.runtime.group.spawn(go)
def getActiveWatchInfo(self):
watch_info = self.runtime.runner.pebble.watch_version_info
js_object = self.runtime.context.eval("({})")
platform = PebbleHardware.hardware_platform(watch_info['normal_fw']['hardware_platform'])
js_object['platform'] = platform
model = self.pebble.request_model() # Note: this could take a while.
if model is None:
model = 'qemu_platform_%s' % platform
js_object['model'] = model
js_object['language'] = 'en_US' # TODO: Actually use a real value for this?
firmware_obj = self.runtime.context.eval("({})")
version_str = watch_info['normal_fw']['version'][1:]
number, suffix = version_str.split('-', 1)
number_parts = number.split('.')
firmware_obj['major'] = int(number_parts[0])
firmware_obj['minor'] = int(number_parts[1]) if len(number_parts) >= 2 else 0
firmware_obj['patch'] = int(number_parts[2]) if len(number_parts) >= 3 else 0
firmware_obj['suffix'] = suffix
js_object['firmware'] = firmware_obj
return js_object
def _handle_config_response(self, response):
def go():
e = events.Event(self.runtime, "WebviewClosed")
e.response = response
self.triggerEvent("webviewclosed", e)
self.runtime.enqueue(go)
#PebbleSDK-3.0-dp9/Pebble/common/phonesim/timeline/websync.py
__author__ = 'katharine'
import logging
import requests
from model import TimelineItem, TimelineState, db as database
logger = logging.getLogger('pypkjs.timeline.websync')
class TimelineWebSync(object):
def __init__(self, urls, oauth):
self.urls = urls
self.oauth = oauth
def _make_request(self, url):
result = requests.get(url, headers={'Authorization': 'Bearer %s' % self.oauth},verify=False)
result.raise_for_status()
return result.json()
def _set_url(self, url):
TimelineState.set("syncURL", url or self.urls.initial_sync)
return url
def _get_url(self):
return TimelineState.get("syncURL", self.urls.initial_sync)
def update_iter(self):
url = self._get_url()
while True:
logger.debug("requesting %s", url)
try:
result = self._make_request(url)
except requests.RequestException as e:
logger.error("Request failed: %s", e)
break
logger.debug("result: %s", result)
if result.get('mustResync', False):
yield 'sync.resync', None
url = self._set_url(result['syncURL'])
continue
for update in result['updates']:
yield update['type'], update['data']
if result.get('nextPageURL', None) is not None:
url = self._set_url(result['nextPageURL'])
else:
self._set_url(result.get('syncURL'))
break
#PebbleSDK-3.0-dp9/Pebble/common/phonesim/javascript/xhr.py
__author__ = 'katharine'
from gevent import monkey; monkey.patch_all()
import PyV8 as v8
import requests
import requests.exceptions
import exceptions
import events
progress_event = v8.JSExtension("runtime/events/progress", """
ProgressEvent = function(computable, loaded, total) {
Event.call(this);
computable = computable || false;
loaded = loaded || 0;
total = total || 0;
Object.defineProperties(this, {
lengthComputable: {
get: function() { return computable; },
enumerable: true,
},
loaded: {
get: function() { return loaded; },
enumerable: true,
},
total: {
get: function() { return total; },
enumerable: true,
},
});
}
ProgressEvent.prototype = Object.create(Event.prototype);
ProgressEvent.prototype.constructor = ProgressEvent;
""", dependencies=["runtime/event"])
ProgressEvent = lambda runtime, *args: v8.JSObject.create(runtime.context.locals.ProgressEvent, args)
xml_http_request = v8.JSExtension("runtime/xhr", """
_init_xhr = function(runtime, session) {
this.XMLHttpRequest = function() {
native function _xhr();
var origin = new _xhr(runtime, session);
_make_proxies(this, origin, ['open', 'setRequestHeader', 'overrideMimeType', 'send', 'getResponseHeader',
'getAllResponseHeaders', 'abort', 'addEventListener', 'removeEventListener']);
_make_properties(this, origin, ['readyState', 'response', 'responseText', 'responseType', 'status',
'statusText', 'timeout', 'onreadystatechange', 'ontimeout', 'onload',
'onloadstart', 'onloadend', 'onprogress', 'onerror', 'onabort']);
}
}
""", lambda f: XMLHttpRequest, dependencies=[progress_event.name])
class XMLHttpRequest(events.EventSourceMixin):
UNSENT = 0
OPENED = 1
HEADERS_RECEIVED = 2
LOADING = 3
DONE = 4
def __init__(self, runtime, session):
# properties
self.readyState = self.UNSENT
self.response = None
self.responseText = None
self.responseType = ""
self.status = None
self.statusText = None
self.timeout = None
# handlers
self.onreadystatechange = None
self.ontimeout = None
self.onload = None
self.onloadstart = None
self.onloadend = None
self.onprogress = None
self.onerror = None
self.onabort = None
# internal
self._request = None
self._response = None
self._async = False
self._mime_override = None
self._runtime = runtime
self._session = session
self._thread = None
self._sent = False
super(XMLHttpRequest, self).__init__(runtime)
def open(self, method, url, async=True, user=None, password=None):
self._request = requests.Request(method, url)
if user is not None:
self._request.auth = (user, password or "")
self._async = async
self.readyState = self.OPENED
self._trigger_async_event("readystatechange")
def setRequestHeader(self, header, value):
if self.readyState != self.OPENED:
raise exceptions.JSRuntimeException("Request headers can only be set in the OPENED state.")
if self._sent:
raise exceptions.JSRuntimeException("Request headers cannot be set after sending a request.")
self._request.headers[header] = value
def overrideMimeType(self, mimetype):
if self.readyState >= self.LOADING:
raise exceptions.JSRuntimeException("The mime type cannot be overridden after the request starts loading.")
self._mime_override = mimetype
def _do_request_error(self, exception, event):
self.readyState = self.DONE
if not self._async:
raise Exception(exception)
self._trigger_async_event("readystatechange")
def _do_send(self):
self._sent = True
req = self._session.prepare_request(self._request)
try:
if self.timeout:
timeout = self.timeout / 1000.0
else:
timeout = None
self._response = self._session.send(req, timeout=timeout, verify=False)
self.readyState = self.DONE
self.status = self._response.status_code
self.statusText = self._response.reason
self.responseText = self._response.text
if self.responseType == "json":
self.response = self._response.json()
elif self.responseType == "arraybuffer":
self.response = v8.JSObject.create(self._runtime.context.locals.Uint8Array, (v8.JSArray(list(bytearray(self._response.content))),))
else:
self.response = self.responseText
self._trigger_async_event("load", ProgressEvent, (self._runtime,))
except requests.exceptions.Timeout:
self._trigger_async_event("timeout", ProgressEvent, (self._runtime,))
self.readyState = self.DONE
except requests.exceptions.RequestException:
self.readyState = self.DONE
finally:
self._trigger_async_event("loadend", ProgressEvent, (self._runtime,))
self._trigger_async_event("readystatechange")
def _trigger_async_event(self, event_name, event=None, event_params=(), params=()):
def go():
if event is not None:
self.triggerEvent(event_name, event(*event_params), *params)
else:
self.triggerEvent(event_name, *params)
if self._async:
go()
else:
self._runtime.enqueue(go)
def send(self, data=None):
if data is not None:
self._request.data = str(data)
self._thread = self._runtime.group.spawn(self._do_send)
if not self._async:
self._thread.join()
def getResponseHeader(self, header):
if self._response is not None:
return self._response.headers.get(header, None)
else:
return None
def getAllResponseHeaders(self):
if self._response is None:
return None
# https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method
return '\x0d\x0a'.join('%s\x3a\x20%s' % (k, v) for k, v in self._response.headers.iteritems())
def abort(self):
if self._sent and self._thread is not None:
self._thread.kill(block=False)
def prepare_xhr(runtime):
session = requests.Session()
return runtime.context.locals._init_xhr(runtime, session)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment