pypkjs 1.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypkjs-1.1.2/LICENSE +21 -0
- pypkjs-1.1.2/MANIFEST.in +1 -0
- pypkjs-1.1.2/PKG-INFO +30 -0
- pypkjs-1.1.2/README.md +54 -0
- pypkjs-1.1.2/pypkjs/__init__.py +5 -0
- pypkjs-1.1.2/pypkjs/javascript/__init__.py +40 -0
- pypkjs-1.1.2/pypkjs/javascript/console.py +39 -0
- pypkjs-1.1.2/pypkjs/javascript/events.py +91 -0
- pypkjs-1.1.2/pypkjs/javascript/exceptions.py +11 -0
- pypkjs-1.1.2/pypkjs/javascript/localstorage.py +88 -0
- pypkjs-1.1.2/pypkjs/javascript/navigator/GeoLiteCity.dat +0 -0
- pypkjs-1.1.2/pypkjs/javascript/navigator/__init__.py +27 -0
- pypkjs-1.1.2/pypkjs/javascript/navigator/geolocation.py +61 -0
- pypkjs-1.1.2/pypkjs/javascript/pebble.py +363 -0
- pypkjs-1.1.2/pypkjs/javascript/performance.py +20 -0
- pypkjs-1.1.2/pypkjs/javascript/runtime.py +116 -0
- pypkjs-1.1.2/pypkjs/javascript/safe_requests.py +166 -0
- pypkjs-1.1.2/pypkjs/javascript/timers.py +63 -0
- pypkjs-1.1.2/pypkjs/javascript/ws.py +178 -0
- pypkjs-1.1.2/pypkjs/javascript/xhr.py +207 -0
- pypkjs-1.1.2/pypkjs/runner/__init__.py +168 -0
- pypkjs-1.1.2/pypkjs/runner/pebble_manager.py +76 -0
- pypkjs-1.1.2/pypkjs/runner/terminal.py +60 -0
- pypkjs-1.1.2/pypkjs/runner/websocket.py +272 -0
- pypkjs-1.1.2/pypkjs/timeline/__init__.py +271 -0
- pypkjs-1.1.2/pypkjs/timeline/actions.py +122 -0
- pypkjs-1.1.2/pypkjs/timeline/attributes.py +107 -0
- pypkjs-1.1.2/pypkjs/timeline/colours.py +69 -0
- pypkjs-1.1.2/pypkjs/timeline/layouts.json +281 -0
- pypkjs-1.1.2/pypkjs/timeline/model.py +324 -0
- pypkjs-1.1.2/pypkjs/timeline/urls.py +30 -0
- pypkjs-1.1.2/pypkjs/timeline/websync.py +52 -0
- pypkjs-1.1.2/pypkjs/version.py +4 -0
- pypkjs-1.1.2/pypkjs.egg-info/PKG-INFO +30 -0
- pypkjs-1.1.2/pypkjs.egg-info/SOURCES.txt +41 -0
- pypkjs-1.1.2/pypkjs.egg-info/dependency_links.txt +1 -0
- pypkjs-1.1.2/pypkjs.egg-info/entry_points.txt +2 -0
- pypkjs-1.1.2/pypkjs.egg-info/not-zip-safe +1 -0
- pypkjs-1.1.2/pypkjs.egg-info/requires.txt +14 -0
- pypkjs-1.1.2/pypkjs.egg-info/top_level.txt +1 -0
- pypkjs-1.1.2/requirements.txt +14 -0
- pypkjs-1.1.2/setup.cfg +4 -0
- pypkjs-1.1.2/setup.py +42 -0
pypkjs-1.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Pebble Technology
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pypkjs-1.1.2/MANIFEST.in
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include requirements.txt
|
pypkjs-1.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypkjs
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: A Pebble phone app simulator written in Python
|
|
5
|
+
Home-page: https://github.com/coredevices/pypkjs
|
|
6
|
+
Author: Core Devices LLC
|
|
7
|
+
Author-email: griffin@griffinli.com
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: gevent>=24.11.1
|
|
11
|
+
Requires-Dist: gevent-websocket>=0.10.1
|
|
12
|
+
Requires-Dist: greenlet>=3.2.3
|
|
13
|
+
Requires-Dist: peewee>=3.17.9
|
|
14
|
+
Requires-Dist: pygeoip>=0.3.2
|
|
15
|
+
Requires-Dist: pypng>=0.20220715.0
|
|
16
|
+
Requires-Dist: python-dateutil>=2.4.1
|
|
17
|
+
Requires-Dist: requests>=2.32.3
|
|
18
|
+
Requires-Dist: sh>=2.2.1
|
|
19
|
+
Requires-Dist: six>=1.17.0
|
|
20
|
+
Requires-Dist: websocket-client>=1.8.0
|
|
21
|
+
Requires-Dist: libpebble2>=0.0.27
|
|
22
|
+
Requires-Dist: netaddr>=0.7.18
|
|
23
|
+
Requires-Dist: stpyv8>=13.1.201.22
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: summary
|
pypkjs-1.1.2/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
pypkjs
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
PebbleKit JS in Python!
|
|
5
|
+
|
|
6
|
+
Setup
|
|
7
|
+
-----
|
|
8
|
+
|
|
9
|
+
To get started, clone the repo:
|
|
10
|
+
|
|
11
|
+
git clone https://github.com/coredevices/pypkjs.git
|
|
12
|
+
|
|
13
|
+
Set up a virtualenv and install everything
|
|
14
|
+
|
|
15
|
+
virtualenv .env
|
|
16
|
+
source .env/bin/activate
|
|
17
|
+
pip install -r requirements.txt
|
|
18
|
+
|
|
19
|
+
Running
|
|
20
|
+
-------
|
|
21
|
+
|
|
22
|
+
You'll need to get [QEMU](https://github.com/pebble/qemu) running and find some pbws.
|
|
23
|
+
Then invoke jskit like this:
|
|
24
|
+
|
|
25
|
+
./jskit.py localhost:12344 app1.pbw app2.pbw ...
|
|
26
|
+
|
|
27
|
+
It'll connect to the running QEMU instance (assuming it's serving bluetooth on port 12344,
|
|
28
|
+
as is the default) and provide JavaScript for the given PBWs.
|
|
29
|
+
|
|
30
|
+
You can also run it like this:
|
|
31
|
+
|
|
32
|
+
./phonesim.py
|
|
33
|
+
|
|
34
|
+
Then you can use the standard `pebble` command, giving `--phone localhost`, like so:
|
|
35
|
+
|
|
36
|
+
pebble install --phone localhost --logs
|
|
37
|
+
|
|
38
|
+
Configuration Pages
|
|
39
|
+
-------------------
|
|
40
|
+
|
|
41
|
+
Configuration pages are supported, but the pages must be modified. We cannot catch the
|
|
42
|
+
`pebble://close` link, so instead we add a `return_to` query param. Your configuration
|
|
43
|
+
page should check for the presence of `return_to` and, if present, use it in place of
|
|
44
|
+
`pebble://close#`. In its absence, it should behave as before (to support the real
|
|
45
|
+
mobile apps).
|
|
46
|
+
|
|
47
|
+
To open a configuration page using the command-line runner (`jskit.py`), send the running
|
|
48
|
+
process `SIGUSR1` (e.g. using `kill -SIGUSR1 somepid`). Configuration pages are not
|
|
49
|
+
currently supported using the phonesim setup.
|
|
50
|
+
|
|
51
|
+
Platforms
|
|
52
|
+
-------
|
|
53
|
+
|
|
54
|
+
Mac OS (10.10) and Linux are tested. Windows won't work.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
from .console import Console
|
|
5
|
+
from .performance import Performance
|
|
6
|
+
from .timers import Timers
|
|
7
|
+
from .localstorage import LocalStorage
|
|
8
|
+
from .pebble import Pebble
|
|
9
|
+
from .xhr import prepare_xhr
|
|
10
|
+
from .navigator import Navigator
|
|
11
|
+
from .ws import prepare_ws
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PebbleKitJS(object):
|
|
15
|
+
def __init__(self, runtime, pebble, persist=None):
|
|
16
|
+
self.runtime = runtime
|
|
17
|
+
self.pebble = Pebble(runtime, pebble)
|
|
18
|
+
self.local_storage = LocalStorage(runtime, persist)
|
|
19
|
+
|
|
20
|
+
self.extensions = [
|
|
21
|
+
Console(runtime),
|
|
22
|
+
Performance(runtime),
|
|
23
|
+
self.local_storage,
|
|
24
|
+
Navigator(runtime),
|
|
25
|
+
Timers(runtime),
|
|
26
|
+
self.pebble,
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
def get_extension_names(self):
|
|
30
|
+
return [x.extension.name for x in self.extensions] + ["runtime/events/progress", "runtime/xhr", "runtime/ws",
|
|
31
|
+
"runtime/geolocation/position",
|
|
32
|
+
"runtime/geolocation/coordinates"]
|
|
33
|
+
|
|
34
|
+
def do_post_setup(self):
|
|
35
|
+
prepare_xhr(self.runtime)
|
|
36
|
+
prepare_ws(self.runtime)
|
|
37
|
+
|
|
38
|
+
def shutdown(self):
|
|
39
|
+
self.local_storage._shutdown()
|
|
40
|
+
self.pebble._shutdown()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Console(object):
|
|
8
|
+
def __init__(self, runtime):
|
|
9
|
+
self.runtime = runtime
|
|
10
|
+
self.extension = v8.JSExtension(self.runtime.ext_name("console"), """
|
|
11
|
+
console = new (function () {
|
|
12
|
+
native function _internal_console();
|
|
13
|
+
_make_proxies(this, _internal_console(), ['log', 'warn', 'info', 'error']);
|
|
14
|
+
})();
|
|
15
|
+
""", lambda f: lambda: self, dependencies=["runtime/internal/proxy"])
|
|
16
|
+
|
|
17
|
+
def log(self, *params):
|
|
18
|
+
# kOverview == kLineNumber | kColumnOffset | kScriptName | kFunctionName
|
|
19
|
+
trace_str = str(v8.JSStackTrace.GetCurrentStackTrace(2, v8.JSStackTrace.Options.Overview))
|
|
20
|
+
try:
|
|
21
|
+
frames = v8.JSError.parse_stack(trace_str.strip())
|
|
22
|
+
caller_frame = frames[0]
|
|
23
|
+
filename = caller_frame[1]
|
|
24
|
+
line_num = caller_frame[2]
|
|
25
|
+
file_and_line = "{}:{}".format(filename, line_num)
|
|
26
|
+
except:
|
|
27
|
+
file_and_line = "???:?:?"
|
|
28
|
+
log_str = ' '.join([x.toString().decode('utf-8') if hasattr(x, 'toString')
|
|
29
|
+
else bytes(x).decode('utf-8') for x in params])
|
|
30
|
+
self.runtime.log_output("{} {}".format(file_and_line, log_str))
|
|
31
|
+
|
|
32
|
+
def warn(self, *params):
|
|
33
|
+
self.log(*params)
|
|
34
|
+
|
|
35
|
+
def info(self, *params):
|
|
36
|
+
self.log(*params)
|
|
37
|
+
|
|
38
|
+
def error(self, *params):
|
|
39
|
+
self.log(*params)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
|
|
6
|
+
from .exceptions import JSRuntimeException
|
|
7
|
+
|
|
8
|
+
event = v8.JSExtension("runtime/event", """
|
|
9
|
+
Event = function(event_type, event_init_dict) {
|
|
10
|
+
var self = this;
|
|
11
|
+
this.stopPropagation = function() {};
|
|
12
|
+
this.stopImmediatePropagation = function() { self._aborted = true; }
|
|
13
|
+
this.preventDefault = function() { self.defaultPrevented = true; }
|
|
14
|
+
this.initEvent = function(event_type, bubbles, cancelable) {
|
|
15
|
+
self.type = event_type;
|
|
16
|
+
self.bubbles = bubbles;
|
|
17
|
+
self.cancelable = cancelable
|
|
18
|
+
};
|
|
19
|
+
if(!event_init_dict) event_init_dict = {};
|
|
20
|
+
|
|
21
|
+
this.type = event_type;
|
|
22
|
+
this.bubbles = event_init_dict.bubbles || false;
|
|
23
|
+
this.cancelable = event_init_dict.cancelable || false;
|
|
24
|
+
this.defaultPrevented = false;
|
|
25
|
+
this.target = null;
|
|
26
|
+
this.currentTarget = null;
|
|
27
|
+
this.eventPhase = 2;
|
|
28
|
+
this._aborted = false;
|
|
29
|
+
};
|
|
30
|
+
Event.NONE = 0;
|
|
31
|
+
Event.CAPTURING_PHASE = 1;
|
|
32
|
+
Event.AT_TARGET = 2;
|
|
33
|
+
Event.BUBBLING_PHASE = 3;
|
|
34
|
+
""")
|
|
35
|
+
|
|
36
|
+
Event = lambda runtime, *args: v8.JSObject.create(runtime.context.locals.Event, args)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EventSourceMixin(object):
|
|
40
|
+
def __init__(self, runtime):
|
|
41
|
+
self.__listeners = {}
|
|
42
|
+
self.__runtime = runtime
|
|
43
|
+
super(EventSourceMixin, self).__init__()
|
|
44
|
+
|
|
45
|
+
def addEventListener(self, event, listener, capture=False):
|
|
46
|
+
self.__listeners.setdefault(event, []).append(listener)
|
|
47
|
+
|
|
48
|
+
def removeEventListener(self, event, listener=None):
|
|
49
|
+
if event not in self.__listeners:
|
|
50
|
+
return
|
|
51
|
+
if listener is None:
|
|
52
|
+
del self.__listeners[event]
|
|
53
|
+
else:
|
|
54
|
+
for i, listener_i in enumerate(self.__listeners[event]):
|
|
55
|
+
if listener_i == listener:
|
|
56
|
+
del listener[i]
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
def triggerEvent(self, event_name, event=None, *params):
|
|
60
|
+
if event is None:
|
|
61
|
+
event = Event(self.__runtime, event_name)
|
|
62
|
+
|
|
63
|
+
def go():
|
|
64
|
+
for listener in self.__listeners.get(event_name, []):
|
|
65
|
+
try:
|
|
66
|
+
listener.call(self, event, *params)
|
|
67
|
+
except (v8.JSError, JSRuntimeException) as e:
|
|
68
|
+
self.__runtime.log_output(e.stackTrace)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
self.__runtime.log_output(e.message)
|
|
71
|
+
raise
|
|
72
|
+
finally:
|
|
73
|
+
if event._aborted:
|
|
74
|
+
break
|
|
75
|
+
#TODO: Figure out if these come before or after addEventListener handlers.
|
|
76
|
+
try:
|
|
77
|
+
dom_event = getattr(self, "on" + event_name)
|
|
78
|
+
except AttributeError:
|
|
79
|
+
pass
|
|
80
|
+
else:
|
|
81
|
+
try:
|
|
82
|
+
if dom_event is not None:
|
|
83
|
+
dom_event.call(self, event, *params)
|
|
84
|
+
except (v8.JSError, JSRuntimeException) as e:
|
|
85
|
+
self.__runtime.log_output(e.stackTrace)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.__runtime.log_output(e.message)
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
self.__runtime.enqueue(go)
|
|
91
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JSRuntimeException(Exception):
|
|
8
|
+
def __init__(self, message):
|
|
9
|
+
trace = v8.JSStackTrace.GetCurrentStackTrace(20, v8.JSStackTrace.Options.Detailed)
|
|
10
|
+
self.stackTrace = "Error: %s\n%s" % (message, str(trace))
|
|
11
|
+
Exception.__init__(self, message)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
import errno
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import os.path
|
|
9
|
+
import dbm.dumb # This is the only one that actually syncs data if the process dies before I can close().
|
|
10
|
+
logger = logging.getLogger("pypkjs.javascript.localstorage")
|
|
11
|
+
|
|
12
|
+
_storage_cache = {} # This is used when filesystem-based storage is unavailable.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LocalStorage(object):
|
|
16
|
+
def __init__(self, runtime, persist_dir=None):
|
|
17
|
+
self.storage = None
|
|
18
|
+
if persist_dir is not None:
|
|
19
|
+
try:
|
|
20
|
+
try:
|
|
21
|
+
os.makedirs(os.path.join(persist_dir, 'localstorage'))
|
|
22
|
+
except OSError as e:
|
|
23
|
+
if e.errno != errno.EEXIST:
|
|
24
|
+
raise
|
|
25
|
+
|
|
26
|
+
self.storage = dbm.dumb.open(os.path.join(persist_dir, 'localstorage', str(runtime.pbw.uuid)), 'c')
|
|
27
|
+
except IOError:
|
|
28
|
+
pass
|
|
29
|
+
if self.storage is None:
|
|
30
|
+
logger.warning("Using transient store.")
|
|
31
|
+
self.storage = _storage_cache.setdefault(str(runtime.pbw.uuid), {})
|
|
32
|
+
|
|
33
|
+
self.extension = v8.JSExtension(runtime.ext_name("localstorage"), """
|
|
34
|
+
(function() {
|
|
35
|
+
native function _internal();
|
|
36
|
+
|
|
37
|
+
var proxy = _make_proxies({}, _internal(), ['set', 'has', 'delete_', 'keys', 'enumerate']);
|
|
38
|
+
var methods = _make_proxies({}, _internal(), ['clear', 'getItem', 'setItem', 'removeItem', 'key']);
|
|
39
|
+
proxy.get = function get(p, name) { return methods[name] || _internal().get(p, name); }
|
|
40
|
+
|
|
41
|
+
this.localStorage = Proxy.create(proxy);
|
|
42
|
+
})();
|
|
43
|
+
""", lambda f: lambda: self, dependencies=["runtime/internal/proxy"])
|
|
44
|
+
|
|
45
|
+
def get(self, p, name):
|
|
46
|
+
return self.storage.get(str(name), v8.JSNull())
|
|
47
|
+
|
|
48
|
+
def set(self, p, name, value):
|
|
49
|
+
self.storage[str(name)] = str(value)
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
def has(self, p, name):
|
|
53
|
+
return name in self.storage
|
|
54
|
+
|
|
55
|
+
def delete_(self, p, name):
|
|
56
|
+
if name in self.storage:
|
|
57
|
+
del self.storage[name]
|
|
58
|
+
return True
|
|
59
|
+
else:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def keys(self, p):
|
|
63
|
+
return v8.JSArray(list(self.storage.keys()))
|
|
64
|
+
|
|
65
|
+
def enumerate(self):
|
|
66
|
+
return v8.JSArray(list(self.storage.keys()))
|
|
67
|
+
|
|
68
|
+
def clear(self, *args):
|
|
69
|
+
self.storage.clear()
|
|
70
|
+
|
|
71
|
+
def getItem(self, name, *args):
|
|
72
|
+
return self.get(None, name)
|
|
73
|
+
|
|
74
|
+
def setItem(self, name, value, *args):
|
|
75
|
+
self.set(None, name, value)
|
|
76
|
+
|
|
77
|
+
def removeItem(self, name, *args):
|
|
78
|
+
return self.delete_(None, name)
|
|
79
|
+
|
|
80
|
+
def key(self, index, *args):
|
|
81
|
+
if len(self.storage) > index:
|
|
82
|
+
return list(self.storage.keys())[index]
|
|
83
|
+
else:
|
|
84
|
+
return v8.JSNull()
|
|
85
|
+
|
|
86
|
+
def _shutdown(self):
|
|
87
|
+
if hasattr(self.storage, 'close'):
|
|
88
|
+
self.storage.close()
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
from .geolocation import Geolocation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Navigator(object):
|
|
9
|
+
def __init__(self, runtime):
|
|
10
|
+
|
|
11
|
+
self._runtime = runtime
|
|
12
|
+
self._runtime = runtime
|
|
13
|
+
|
|
14
|
+
self.extension = v8.JSExtension(runtime.ext_name("navigator"), """
|
|
15
|
+
navigator = new (function() {
|
|
16
|
+
native function _internal_location();
|
|
17
|
+
this.language = 'en-GB';
|
|
18
|
+
|
|
19
|
+
var location = _internal_location();
|
|
20
|
+
if(true) { // TODO: this should be a check on geolocation being enabled.
|
|
21
|
+
this.geolocation = new (function() {
|
|
22
|
+
_make_proxies(this, location, ['getCurrentPosition', 'watchPosition', 'clearWatch']);
|
|
23
|
+
})();
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
26
|
+
""", lambda f: lambda: Geolocation(runtime), dependencies=["runtime/internal/proxy"])
|
|
27
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
__author__ = 'katharine'
|
|
3
|
+
|
|
4
|
+
import STPyV8 as v8
|
|
5
|
+
import time
|
|
6
|
+
import requests
|
|
7
|
+
import pygeoip
|
|
8
|
+
import os.path
|
|
9
|
+
|
|
10
|
+
position = v8.JSExtension("runtime/geolocation/position", """
|
|
11
|
+
Position = (function(coords, timestamp) {
|
|
12
|
+
this.coords = coords;
|
|
13
|
+
this.timestamp = timestamp;
|
|
14
|
+
});
|
|
15
|
+
""")
|
|
16
|
+
|
|
17
|
+
Position = lambda runtime, *args: v8.JSObject.create(runtime.context.locals.Position, args)
|
|
18
|
+
|
|
19
|
+
coordinates = v8.JSExtension("runtime/geolocation/coordinates", """
|
|
20
|
+
Coordinates = (function(long, lat, accuracy) {
|
|
21
|
+
this.longitude = long
|
|
22
|
+
this.latitude = lat
|
|
23
|
+
this.accuracy = accuracy
|
|
24
|
+
});
|
|
25
|
+
""")
|
|
26
|
+
|
|
27
|
+
Coordinates = lambda runtime, *args: v8.JSObject.create(runtime.context.locals.Coordinates, args)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Geolocation(object):
|
|
31
|
+
def __init__(self, runtime):
|
|
32
|
+
self.runtime = runtime
|
|
33
|
+
|
|
34
|
+
def _get_position(self, success, failure):
|
|
35
|
+
try:
|
|
36
|
+
resp = requests.get('http://ip.42.pl/raw')
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
ip = resp.text
|
|
39
|
+
gi = pygeoip.GeoIP('%s/GeoLiteCity.dat' % os.path.dirname(__file__))
|
|
40
|
+
record = gi.record_by_addr(ip)
|
|
41
|
+
if record is None:
|
|
42
|
+
if callable(failure):
|
|
43
|
+
self.runtime.enqueue(failure)
|
|
44
|
+
except (requests.RequestException, pygeoip.GeoIPError):
|
|
45
|
+
if callable(failure):
|
|
46
|
+
self.runtime.enqueue(failure)
|
|
47
|
+
else:
|
|
48
|
+
self.runtime.enqueue(success, Position(self.runtime, Coordinates(self.runtime, record['longitude'], record['latitude'], 1000), round(time.time() * 1000)))
|
|
49
|
+
|
|
50
|
+
def _enabled(self):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
def getCurrentPosition(self, success, failure=None, options=None):
|
|
54
|
+
self.runtime.group.spawn(self._get_position, success, failure)
|
|
55
|
+
|
|
56
|
+
def watchPosition(self, success, failure=None, options=None):
|
|
57
|
+
self.runtime.group.spawn(self._get_position, success, failure)
|
|
58
|
+
return 42
|
|
59
|
+
|
|
60
|
+
def clearWatch(self, thing):
|
|
61
|
+
pass
|