diff --git a/images/marker.svg b/images/marker.svg
new file mode 100644
index 0000000..fa5f55b
--- /dev/null
+++ b/images/marker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/js/lucille.js b/js/lucille.js
index e548b84..c9d7a98 100644
--- a/js/lucille.js
+++ b/js/lucille.js
@@ -90,10 +90,12 @@ class Application {
if (app.map == null) {
return;
}
+ const zoodMarker = "./images/marker.svg";
let pos = { lat: locInfo.latitude, lng: locInfo.longitude };
// if we already have a marker for the user, update it. otherwise, build one.
if (app.marker == null) {
app.marker = new google.maps.Marker({
+ icon: zoodMarker,
position: pos,
map: app.map,
title: app.username
@@ -183,6 +185,9 @@ function initMap() {
app.map = new google.maps.Map(document.getElementById("map"), {
center: { lat: lat, lng: lng },
zoom: zoom,
+ zoomControlOptions: {
+ position: google.maps.ControlPosition.RIGHT_TOP
+ },
streetViewControl: false,
mapTypeControl: false,
fullscreenControl: false
diff --git a/js/lucille.ts b/js/lucille.ts
index 7088631..bcc383a 100644
--- a/js/lucille.ts
+++ b/js/lucille.ts
@@ -1,315 +1,334 @@
class Application {
- username: string;
- secretKey: Uint8Array;
- receivingBoxId: Uint8Array;
- userId: Uint8Array;
- senderPublicKey: Uint8Array;
- updateTimer: number | null;
+ username: string;
+ secretKey: Uint8Array;
+ receivingBoxId: Uint8Array;
+ userId: Uint8Array;
+ senderPublicKey: Uint8Array;
+ updateTimer: number | null;
- private lastLocation: LocationInfo | null;
+ private lastLocation: LocationInfo | null;
- map: google.maps.Map | null;
- marker: google.maps.Marker | null;
+ map: google.maps.Map | null;
+ marker: google.maps.Marker | null;
- constructor() {
- this.lastLocation = null;
- this.map = null;
- this.marker = null;
- }
+ constructor() {
+ this.lastLocation = null;
+ this.map = null;
+ this.marker = null;
+ }
- private getBatteryClassName(level: number): string {
- let clazzName = "battery ";
- if (level >= 95) {
- clazzName += "battery-100";
- } else if (level >= 85) {
- clazzName += "battery-90";
- } else if (level >= 75) {
- clazzName += "battery-80";
- } else if (level >= 65) {
- clazzName += "battery-70";
- } else if (level >= 55) {
- clazzName += "battery-60";
- } else if (level >= 45) {
- clazzName += "battery-50";
- } else if (level >= 35) {
- clazzName += "battery-40";
- } else if (level >= 25) {
- clazzName += "battery-30";
- } else if (level >= 15) {
- clazzName += "battery-20";
- } else if (level >= 5) {
- clazzName += "battery-10";
- } else {
- clazzName += "battery-0";
- }
+ private getBatteryClassName(level: number): string {
+ let clazzName = "battery ";
+ if (level >= 95) {
+ clazzName += "battery-100";
+ } else if (level >= 85) {
+ clazzName += "battery-90";
+ } else if (level >= 75) {
+ clazzName += "battery-80";
+ } else if (level >= 65) {
+ clazzName += "battery-70";
+ } else if (level >= 55) {
+ clazzName += "battery-60";
+ } else if (level >= 45) {
+ clazzName += "battery-50";
+ } else if (level >= 35) {
+ clazzName += "battery-40";
+ } else if (level >= 25) {
+ clazzName += "battery-30";
+ } else if (level >= 15) {
+ clazzName += "battery-20";
+ } else if (level >= 5) {
+ clazzName += "battery-10";
+ } else {
+ clazzName += "battery-0";
+ }
- return clazzName;
- }
+ return clazzName;
+ }
- getLocation(): LocationInfo | null {
- return this.lastLocation;
- }
+ getLocation(): LocationInfo | null {
+ return this.lastLocation;
+ }
- async setLocation(locInfo: LocationInfo) {
- app.lastLocation = locInfo;
+ async setLocation(locInfo: LocationInfo) {
+ app.lastLocation = locInfo;
- let batteryPowerDiv = document.getElementById("battery-power") as HTMLDivElement;
- if (locInfo.battery_level != null) {
- batteryPowerDiv.innerText = `${locInfo.battery_level}%`;
- } else {
- batteryPowerDiv.innerText = "";
- }
+ let batteryPowerDiv = document.getElementById(
+ "battery-power"
+ ) as HTMLDivElement;
+ if (locInfo.battery_level != null) {
+ batteryPowerDiv.innerText = `${locInfo.battery_level}%`;
+ } else {
+ batteryPowerDiv.innerText = "";
+ }
- this.updateTimeAgo();
- // clear out the old timer, and schedule a new one
- if (this.updateTimer != null) {
- clearInterval(this.updateTimer);
- }
- setInterval(this.updateTimeAgo, 60 * 1000);
+ this.updateTimeAgo();
+ // clear out the old timer, and schedule a new one
+ if (this.updateTimer != null) {
+ clearInterval(this.updateTimer);
+ }
+ setInterval(this.updateTimeAgo, 60 * 1000);
- let compass = document.getElementById("compass") as HTMLSpanElement;
- if (locInfo.bearing != null) {
- compass.style.display = "inline-block";
- compass.style.transform = `rotate(${locInfo.bearing}deg)`;
- } else {
- compass.style.display = "none";
- }
+ let compass = document.getElementById("compass") as HTMLSpanElement;
+ if (locInfo.bearing != null) {
+ compass.style.display = "inline-block";
+ compass.style.transform = `rotate(${locInfo.bearing}deg)`;
+ } else {
+ compass.style.display = "none";
+ }
- let transportationMode = document.getElementById("transportation-mode") as HTMLImageElement;
- switch (locInfo.movement) {
- case zood.MovementType.Bicycle:
- transportationMode.src = "../images/activities/bike.svg";
- break;
- case zood.MovementType.OnFoot:
- case zood.MovementType.Running:
- case zood.MovementType.Walking:
- transportationMode.src = "../images/activities/walk.svg";
- break;
- case zood.MovementType.Vehicle:
- transportationMode.src = "../images/activities/car.svg";
- break;
- case null:
- default:
- transportationMode.src = ""
- break;
- }
- transportationMode.hidden = transportationMode.src == ""
+ let transportationMode = document.getElementById(
+ "transportation-mode"
+ ) as HTMLImageElement;
+ switch (locInfo.movement) {
+ case zood.MovementType.Bicycle:
+ transportationMode.src = "../images/activities/bike.svg";
+ break;
+ case zood.MovementType.OnFoot:
+ case zood.MovementType.Running:
+ case zood.MovementType.Walking:
+ transportationMode.src = "../images/activities/walk.svg";
+ break;
+ case zood.MovementType.Vehicle:
+ transportationMode.src = "../images/activities/car.svg";
+ break;
+ case null:
+ default:
+ transportationMode.src = "";
+ break;
+ }
+ transportationMode.hidden = transportationMode.src == "";
- // Make sure the map has loaded first
- if (app.map == null) {
- return;
- }
+ // Make sure the map has loaded first
+ if (app.map == null) {
+ return;
+ }
+ const zoodMarker = "./images/marker.svg";
+ let pos = { lat: locInfo.latitude, lng: locInfo.longitude };
+ // if we already have a marker for the user, update it. otherwise, build one.
+ if (app.marker == null) {
+ app.marker = new google.maps.Marker({
+ icon: zoodMarker,
+ position: pos,
+ map: app.map,
+ title: app.username
+ });
+ let mapOpts = {
+ zoom: 15,
+ center: pos
+ };
+ app.map.setOptions(mapOpts);
+ } else {
+ app.marker.setOptions({ position: pos });
+ }
- let pos = { lat: locInfo.latitude, lng: locInfo.longitude };
- // if we already have a marker for the user, update it. otherwise, build one.
- if (app.marker == null) {
- app.marker = new google.maps.Marker({
- position: pos,
- map: app.map,
- title: app.username
- });
- let mapOpts = {
- zoom: 15,
- center: pos
- };
- app.map.setOptions(mapOpts);
- } else {
- app.marker.setOptions({ position: pos });
- }
+ // set the battery level
+ let batteryIcon = document.getElementById(
+ "battery-icon"
+ ) as HTMLSpanElement;
+ batteryIcon.className = this.getBatteryClassName(locInfo.battery_level);
- // set the battery level
- let batteryIcon = document.getElementById("battery-icon") as HTMLSpanElement;
- batteryIcon.className = this.getBatteryClassName(locInfo.battery_level);
+ try {
+ let rg = await locationiq.getReverseGeocoding(
+ locInfo.latitude,
+ locInfo.longitude
+ );
+ let addressElem = document.getElementById(
+ "address"
+ ) as HTMLParagraphElement;
+ addressElem.innerText = rg.getAddress();
+ } catch (err) {
+ console.log("failed to update address:", err);
+ }
+ }
- try {
- let rg = await locationiq.getReverseGeocoding(locInfo.latitude, locInfo.longitude);
- let addressElem = document.getElementById("address") as HTMLParagraphElement;
- addressElem.innerText = rg.getAddress();
- } catch (err) {
- console.log("failed to update address:", err)
- }
- }
-
- private updateTimeAgo(): void {
- let updateTimeDiv = document.getElementById("update-time") as HTMLDivElement;
- let location = app.lastLocation;
- if (location == null) {
- updateTimeDiv.innerHTML = ` • …`;
- return;
- }
- let ago = zdtime.relativeTime(new Date().getTime(), location.time, "en-US")
- updateTimeDiv.innerHTML = ` • ${ago}`;
- }
+ private updateTimeAgo(): void {
+ let updateTimeDiv = document.getElementById(
+ "update-time"
+ ) as HTMLDivElement;
+ let location = app.lastLocation;
+ if (location == null) {
+ updateTimeDiv.innerHTML = ` • …`;
+ return;
+ }
+ let ago = zdtime.relativeTime(new Date().getTime(), location.time, "en-US");
+ updateTimeDiv.innerHTML = ` • ${ago}`;
+ }
}
const app = new Application();
interface EncryptedData {
- cipher_text: Uint8Array;
- nonce: Uint8Array;
+ cipher_text: Uint8Array;
+ nonce: Uint8Array;
}
interface PublicKeyResponse {
- public_key: string;
+ public_key: string;
}
interface LocationInfo {
- accuracy: number;
- battery_charging: boolean;
- battery_level: number;
- bearing: number | null;
- latitude: number;
- longitude: number;
- movement: zood.MovementType | null;
- speed: number | null;
- time: number;
- type: string | null | undefined;
+ accuracy: number;
+ battery_charging: boolean;
+ battery_level: number;
+ bearing: number | null;
+ latitude: number;
+ longitude: number;
+ movement: zood.MovementType | null;
+ speed: number | null;
+ time: number;
+ type: string | null | undefined;
}
function extractDataFromFragment(): boolean {
- if (!window.document.location.hash) {
- return false;
- }
- let fragment = window.document.location.hash.substring(1);
+ if (!window.document.location.hash) {
+ return false;
+ }
+ let fragment = window.document.location.hash.substring(1);
- let keyVals = fragment.split("&");
- for (var kv of keyVals) {
- let parts = kv.split("=");
- if (parts.length != 2) {
- continue;
- }
- switch (parts[0]) {
- case "u":
- app.username = parts[1];
- break;
- case "k":
- app.secretKey = sodium.from_hex(parts[1]);
- break;
- case "b":
- app.receivingBoxId = sodium.from_hex(parts[1]);
- if (app.receivingBoxId.length != zood.DROP_BOX_ID_LENGTH) {
- console.log(
- "ERROR: box id in fragment is the incorrect length",
- app.receivingBoxId.length
- );
- return false;
- }
- break;
- case "i":
- app.userId = sodium.from_hex(parts[1]);
- if (app.userId.length != zood.USER_ID_LENGTH) {
- console.log(
- "ERROR: user id in fragment is the incorrect length",
- app.userId.length
- );
- return false;
- }
- break;
- }
- }
+ let keyVals = fragment.split("&");
+ for (var kv of keyVals) {
+ let parts = kv.split("=");
+ if (parts.length != 2) {
+ continue;
+ }
+ switch (parts[0]) {
+ case "u":
+ app.username = parts[1];
+ break;
+ case "k":
+ app.secretKey = sodium.from_hex(parts[1]);
+ break;
+ case "b":
+ app.receivingBoxId = sodium.from_hex(parts[1]);
+ if (app.receivingBoxId.length != zood.DROP_BOX_ID_LENGTH) {
+ console.log(
+ "ERROR: box id in fragment is the incorrect length",
+ app.receivingBoxId.length
+ );
+ return false;
+ }
+ break;
+ case "i":
+ app.userId = sodium.from_hex(parts[1]);
+ if (app.userId.length != zood.USER_ID_LENGTH) {
+ console.log(
+ "ERROR: user id in fragment is the incorrect length",
+ app.userId.length
+ );
+ return false;
+ }
+ break;
+ }
+ }
- return true;
+ return true;
}
// Gets called by the GMaps SDK once it's done loading
function initMap() {
- let lat = 0;
- let lng = 0;
- let zoom = 2;
- // if we already have a location, center us there
- let loc = app.getLocation();
- if (loc != null) {
- console.log("We already have the initial location");
- lat = loc.latitude;
- lng = loc.longitude;
- zoom = 15;
- }
- app.map = new google.maps.Map(document.getElementById("map"), {
- center: { lat: lat, lng: lng },
- zoom: zoom,
- streetViewControl: false,
- mapTypeControl: false,
- fullscreenControl: false
- });
+ let lat = 0;
+ let lng = 0;
+ let zoom = 2;
+ // if we already have a location, center us there
+ let loc = app.getLocation();
+ if (loc != null) {
+ console.log("We already have the initial location");
+ lat = loc.latitude;
+ lng = loc.longitude;
+ zoom = 15;
+ }
+ app.map = new google.maps.Map(document.getElementById("map"), {
+ center: { lat: lat, lng: lng },
+ zoom: zoom,
+ zoomControlOptions: {
+ position: google.maps.ControlPosition.RIGHT_TOP
+ },
+ streetViewControl: false,
+ mapTypeControl: false,
+ fullscreenControl: false
+ });
}
async function onPackageReceived(pkg: zood.Package) {
- // console.log("onPackageReceived:", pkg);
- // make sure this is the box we're interested in
- if (!sodium.memcmp(pkg.boxId, app.receivingBoxId)) {
- console.log(
- "Received a package from a drop box for which we're not interested",
- sodium.to_hex(pkg.boxId)
- );
- return;
- }
+ // console.log("onPackageReceived:", pkg);
+ // make sure this is the box we're interested in
+ if (!sodium.memcmp(pkg.boxId, app.receivingBoxId)) {
+ console.log(
+ "Received a package from a drop box for which we're not interested",
+ sodium.to_hex(pkg.boxId)
+ );
+ return;
+ }
- let msgStr = String.fromCharCode.apply(this, pkg.bytes);
- let msgObj = JSON.parse(msgStr);
- let encData = {
- cipher_text: sodium.from_base64(
- msgObj.cipher_text,
- sodium.base64_variants.ORIGINAL
- ),
- nonce: sodium.from_base64(msgObj.nonce, sodium.base64_variants.ORIGINAL)
- } as EncryptedData;
+ let msgStr = String.fromCharCode.apply(this, pkg.bytes);
+ let msgObj = JSON.parse(msgStr);
+ let encData = {
+ cipher_text: sodium.from_base64(
+ msgObj.cipher_text,
+ sodium.base64_variants.ORIGINAL
+ ),
+ nonce: sodium.from_base64(msgObj.nonce, sodium.base64_variants.ORIGINAL)
+ } as EncryptedData;
- let unencData = sodium.crypto_box_open_easy(
- encData.cipher_text,
- encData.nonce,
- app.senderPublicKey,
- app.secretKey
- );
- let locInfoStr = String.fromCharCode.apply(this, unencData);
- let locInfo = JSON.parse(locInfoStr) as LocationInfo;
- if (!locInfo.type) {
- console.log(
- "received package without a 'type'. Expecting LocationInfo, found: ",
- locInfoStr
- );
- return;
- }
- if (locInfo.type != "location_info") {
- console.log("unexpected message type:", locInfo.type);
- return;
- }
- console.log(locInfoStr);
- app.setLocation(locInfo);
+ let unencData = sodium.crypto_box_open_easy(
+ encData.cipher_text,
+ encData.nonce,
+ app.senderPublicKey,
+ app.secretKey
+ );
+ let locInfoStr = String.fromCharCode.apply(this, unencData);
+ let locInfo = JSON.parse(locInfoStr) as LocationInfo;
+ if (!locInfo.type) {
+ console.log(
+ "received package without a 'type'. Expecting LocationInfo, found: ",
+ locInfoStr
+ );
+ return;
+ }
+ if (locInfo.type != "location_info") {
+ console.log("unexpected message type:", locInfo.type);
+ return;
+ }
+ console.log(locInfoStr);
+ app.setLocation(locInfo);
}
async function run() {
- console.log("run");
- if (!extractDataFromFragment()) {
- return;
- }
+ console.log("run");
+ if (!extractDataFromFragment()) {
+ return;
+ }
- let usernameSpans = document.getElementsByClassName("username") as HTMLCollectionOf;
- for (let i = 0; i < usernameSpans.length; i++) {
- let span = usernameSpans.item(i);
- if (span == null) {
- break;
- }
- span.innerText = app.username;
- }
+ let usernameSpans = document.getElementsByClassName(
+ "username"
+ ) as HTMLCollectionOf;
+ for (let i = 0; i < usernameSpans.length; i++) {
+ let span = usernameSpans.item(i);
+ if (span == null) {
+ break;
+ }
+ span.innerText = app.username;
+ }
- try {
- let client = new zood.Client(null);
- let pkr = await client.getUserPublicKey(app.userId);
- app.senderPublicKey = pkr.public_key;
- // console.log("got spk:", app.senderPublicKey);
- } catch (err) {
- console.log("error retrieving public key: ", err);
- return;
- }
+ try {
+ let client = new zood.Client(null);
+ let pkr = await client.getUserPublicKey(app.userId);
+ app.senderPublicKey = pkr.public_key;
+ // console.log("got spk:", app.senderPublicKey);
+ } catch (err) {
+ console.log("error retrieving public key: ", err);
+ return;
+ }
- try {
- let socket = new zood.DropBoxWatcher();
- socket.onPackageReceived = onPackageReceived;
- await socket.connect(zood.DropBoxServer.production);
- console.log("drop box watcher is connected");
- socket.watch(app.receivingBoxId);
- } catch (err) {
- console.log("error connecting drop box watcher", err);
- return;
- }
+ try {
+ let socket = new zood.DropBoxWatcher();
+ socket.onPackageReceived = onPackageReceived;
+ await socket.connect(zood.DropBoxServer.production);
+ console.log("drop box watcher is connected");
+ socket.watch(app.receivingBoxId);
+ } catch (err) {
+ console.log("error connecting drop box watcher", err);
+ return;
+ }
}