mirror of
https://github.com/google/pebble.git
synced 2025-03-21 11:21:21 +00:00
705 lines
23 KiB
JavaScript
705 lines
23 KiB
JavaScript
/**
|
|
* Copyright 2024 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.
|
|
*/
|
|
|
|
(function() {
|
|
var utf8 = require('utf8');
|
|
var POSTMESSAGE_DEBUG = false;
|
|
|
|
// Super simple polyfill for Array.from() that only deals with a Uint8Array:
|
|
var arrayFromUint8Array = Array.from ? Array.from : function(uint8Array) {
|
|
return [].slice.call(uint8Array);
|
|
};
|
|
|
|
function debugLog() {
|
|
if (POSTMESSAGE_DEBUG) {
|
|
console.log.apply(console, arguments);
|
|
}
|
|
}
|
|
|
|
function createHandlersList() {
|
|
var pos = 0;
|
|
var handlers = [];
|
|
|
|
return {
|
|
add : function(handler) {
|
|
handlers.push(handler);
|
|
},
|
|
clear : function() {
|
|
handlers = [];
|
|
pos = 0;
|
|
},
|
|
isEmpty : function() {
|
|
return (handlers.length == 0);
|
|
},
|
|
remove : function(handler) {
|
|
var idx = handlers.indexOf(handler);
|
|
if (idx < 0) { return; } // Not registered
|
|
if (idx < pos) { pos = Math.max(pos - 1, 0); } // We've iterated past it, and it's been removed
|
|
handlers.splice(idx, 1);
|
|
},
|
|
newIterator : function() {
|
|
pos = 0; // new iterator, reset position
|
|
return {
|
|
next : function() {
|
|
if (pos < handlers.length) {
|
|
return handlers[pos++];
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var EVENTS = {};
|
|
|
|
var _callHandler = function(handler, event_name, callback_data) {
|
|
var msg = { type: event_name };
|
|
if (callback_data !== undefined) {
|
|
msg.data = callback_data;
|
|
}
|
|
handler(msg);
|
|
};
|
|
|
|
var _callHandlersForEvent = function(event_name, callback_data) {
|
|
var handler;
|
|
if (!(event_name in EVENTS)) {
|
|
return;
|
|
}
|
|
|
|
var it = EVENTS[event_name].newIterator();
|
|
while ((handler = it.next())) {
|
|
_callHandler(handler, event_name, callback_data);
|
|
}
|
|
}
|
|
|
|
var _isPostMessageEvent = function(event_name) {
|
|
return (['message', 'postmessageerror',
|
|
'postmessageconnected', 'postmessagedisconnected'].indexOf(event_name)) > -1;
|
|
}
|
|
|
|
var __Pebble = Pebble;
|
|
|
|
// Create a new object with its prototype pointing to the original, using
|
|
// Object.create(). This way, we can rely on JavaScript's prototype chain
|
|
// traversal to make all properties on the original object "just work".
|
|
// Note however, that these won't be "own properties", so when using
|
|
// `for .. in`, Pebble.keys(), Object.getOwnPropertyNames(), etc. these
|
|
// "delegated properties" will not be found.
|
|
Pebble = Object.create(Pebble);
|
|
|
|
for (var attr in __Pebble) {
|
|
if (!__Pebble.hasOwnProperty(attr)) {
|
|
continue;
|
|
}
|
|
// Attributes of Pebble which can be bound, should be bound to the original object
|
|
if (__Pebble[attr].bind) {
|
|
Pebble[attr] = __Pebble[attr].bind(__Pebble);
|
|
} else {
|
|
Pebble[attr] = __Pebble[attr];
|
|
}
|
|
}
|
|
|
|
// Ensure that all exported functions exist.
|
|
["addEventListener", "removeEventListener", "showSimpleNotificationOnPebble",
|
|
"sendAppMessage", "getTimelineToken", "timelineSubscribe",
|
|
"timelineUnsubscribe", "timelineSubscriptions", "getActiveWatchInfo",
|
|
"getAccountToken", "getWatchToken", "appGlanceReload"].forEach(
|
|
function(elem, idx, arr) {
|
|
if ((elem in Pebble) || ((typeof __Pebble[elem]) !== 'function')) {
|
|
// This function has already been copied over or doesn't actually exist.
|
|
return;
|
|
}
|
|
Pebble[elem] = __Pebble[elem].bind(__Pebble);
|
|
}
|
|
);
|
|
|
|
// sendAppMessage is not supported, make it undefined so a user will get a
|
|
// "not a function" error, and can check `typeof Pebble.sendAppMessage === 'function'`
|
|
// to test for support.
|
|
Pebble["sendAppMessage"] = undefined;
|
|
|
|
// The rocky implementation!
|
|
|
|
function _scheduleAsyncPostMessageError(jsonString, reason) {
|
|
_callHandlersForEvent('postmessageerror', JSON.parse(jsonString));
|
|
console.error("postMessage() failed. Reason: " + reason);
|
|
}
|
|
|
|
Pebble.postMessage = function(obj) {
|
|
_out.sendObject(obj);
|
|
};
|
|
|
|
var on = function(event_name, handler) {
|
|
if (typeof(handler) !== 'function') {
|
|
throw TypeError("Handler for event expected, received " + typeof(handler));
|
|
}
|
|
if (!(event_name in EVENTS)) {
|
|
EVENTS[event_name] = createHandlersList();
|
|
}
|
|
EVENTS[event_name].add(handler);
|
|
|
|
if ((event_name == "postmessageconnected" && _control.state == ControlStateSessionOpen) ||
|
|
(event_name == "postmessagedisconnected" && _control.state != ControlStateSessionOpen)) {
|
|
_callHandler(handler, event_name);
|
|
}
|
|
};
|
|
|
|
Pebble.addEventListener = function(event_name, handler) {
|
|
if (_isPostMessageEvent(event_name)) {
|
|
return on(event_name, handler);
|
|
} else if (event_name == 'appmessage') {
|
|
throw Error("App Message not supported with Rocky.js apps. See Pebble.postMessage()");
|
|
} else {
|
|
return __Pebble.addEventListener(event_name, handler);
|
|
}
|
|
};
|
|
|
|
// Alias to the overridden implementation:
|
|
Pebble.on = Pebble.addEventListener;
|
|
|
|
var off = function(event_name, handler) {
|
|
if (handler === undefined) {
|
|
throw TypeError('Not enough arguments (missing handler)');
|
|
}
|
|
if (event_name in EVENTS) {
|
|
EVENTS[event_name].remove(handler);
|
|
}
|
|
}
|
|
|
|
Pebble.removeEventListener = function(event_name, handler) {
|
|
if (_isPostMessageEvent(event_name)) {
|
|
off(event_name, handler);
|
|
} else {
|
|
return __Pebble.removeEventListener(event_name, handler);
|
|
}
|
|
}
|
|
|
|
// Alias to the overridden implementation:
|
|
Pebble.off = Pebble.removeEventListener;
|
|
|
|
/*********************************************************************************
|
|
* postMessage(): Outbound object and control message queuing, sending & chunking.
|
|
********************************************************************************/
|
|
|
|
var _out = new Sender();
|
|
|
|
function Sender() {
|
|
this.controlQueue = [];
|
|
this.objectQueue = [];
|
|
|
|
this._currentMessageType = undefined;
|
|
this._failureCount = 0;
|
|
this._offsetBytes = 0;
|
|
this._chunkPayloadSize = 0;
|
|
|
|
this._resetCurrent = function() {
|
|
this._currentMessageType = undefined;
|
|
this._failureCount = 0;
|
|
this._offsetBytes = 0;
|
|
this._chunkPayloadSize = 0;
|
|
};
|
|
|
|
this._getNextMessageType = function() {
|
|
if (this.controlQueue.length > 0) {
|
|
return "control";
|
|
} else if (this.objectQueue.length > 0) {
|
|
return "object";
|
|
}
|
|
// No messages remaining
|
|
return undefined;
|
|
};
|
|
|
|
// Begin sending the next prioritized message
|
|
this._sendNext = function() {
|
|
if (this._currentMessageType !== undefined) {
|
|
return; // Already something in flight
|
|
}
|
|
|
|
var type = this._getNextMessageType();
|
|
if (type === undefined) {
|
|
return; // No message to send
|
|
}
|
|
|
|
if (type === "control") {
|
|
this._currentMessageType = type;
|
|
this._trySendNextControl();
|
|
} else if (type === "object") {
|
|
this._currentMessageType = type;
|
|
this._trySendNextChunk();
|
|
}
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Sender: Control Message Handling
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
this._controlSuccess = function() {
|
|
this.controlQueue.shift();
|
|
this._resetCurrent();
|
|
this._sendNext();
|
|
};
|
|
|
|
this._controlFailure = function(e) {
|
|
this._failureCount++;
|
|
var willRetry = (this._failureCount <= 3);
|
|
if (willRetry) {
|
|
setTimeout(this._trySendNextControl.bind(this), 1000); // 1s retry
|
|
} else {
|
|
debugLog("Failed to send control message: " + e +
|
|
", entering disconnected state.");
|
|
this.controlQueue.shift();
|
|
this._resetCurrent();
|
|
|
|
_control.enter(ControlStateDisconnected);
|
|
this._sendNext();
|
|
}
|
|
};
|
|
|
|
this._trySendNextControl = function() {
|
|
var msg = this.controlQueue[0];
|
|
__Pebble.sendAppMessage(msg,
|
|
this._controlSuccess.bind(this),
|
|
this._controlFailure.bind(this));
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Sender: Object Message Handling
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
this._createDataObject = function(obj) {
|
|
// Store obj as UTF-8 encoded JSON string into .data:
|
|
var native_str_msg;
|
|
try {
|
|
native_str_msg = JSON.stringify(obj);
|
|
} catch(e) {
|
|
throw Error("First argument must be JSON-serializable.");
|
|
}
|
|
// ECMA v5.1, 15.12.3, Note 5: Values that do not have a JSON
|
|
// representation (such as undefined and functions) do not produce a
|
|
// String. Instead they produce the undefined value.
|
|
if (native_str_msg === undefined) {
|
|
throw TypeError(
|
|
"Argument at index 0 is not a JSON.stringify()-able object");
|
|
}
|
|
var utf8_str_msg = utf8.encode(native_str_msg);
|
|
var data = [];
|
|
for (var i = 0; i < utf8_str_msg.length; i++) {
|
|
data.push(utf8_str_msg.charCodeAt(i));
|
|
}
|
|
data.push(0); // zero-terminate
|
|
|
|
return {
|
|
obj: obj,
|
|
data: data,
|
|
json: native_str_msg,
|
|
};
|
|
};
|
|
|
|
this._completeObject = function(failureReasonOrUndefined) {
|
|
var completeObject = this.objectQueue.shift();
|
|
this._resetCurrent();
|
|
|
|
if (failureReasonOrUndefined === undefined) {
|
|
debugLog("Complete!");
|
|
} else {
|
|
_scheduleAsyncPostMessageError(completeObject.json, failureReasonOrUndefined);
|
|
}
|
|
};
|
|
|
|
this._chunkSuccess = function(e) {
|
|
var data = this.objectQueue[0].data;
|
|
debugLog("Sent " + this._chunkPayloadSize + " of " + data.length + " bytes");
|
|
this._offsetBytes += this._chunkPayloadSize;
|
|
if (this._offsetBytes === data.length) {
|
|
this._completeObject();
|
|
this._sendNext();
|
|
} else {
|
|
this._trySendNextChunk();
|
|
}
|
|
};
|
|
|
|
this._chunkFailure = function(e) {
|
|
this._failureCount++;
|
|
var willRetry = (this._failureCount <= 3);
|
|
console.error("Chunk failed to send (willRetry=" + willRetry + "): " + e);
|
|
if (willRetry) {
|
|
setTimeout(this._trySendNextChunk.bind(this), 1000); // 1s retry
|
|
} else {
|
|
this._completeObject("Too many failed transfer attempts");
|
|
this._sendNext();
|
|
}
|
|
};
|
|
|
|
this._trySendNextChunk = function() {
|
|
if (this._getNextMessageType() !== "object") {
|
|
// This is no longer our highest priority outgoing message.
|
|
// Send that message instead, and this message will be left in the queue
|
|
// andrestarted when appropriate.
|
|
this._resetCurrent();
|
|
this._sendNext();
|
|
return;
|
|
}
|
|
|
|
if (!_control.isSessionOpen()) {
|
|
// Make sure to start over if session is closed while chunks have been
|
|
// sent for the head object:
|
|
this._offsetBytes = 0;
|
|
this._chunkFailure("Session not open. Hint: check out the \"postmessageconnected\" event.");
|
|
return;
|
|
}
|
|
|
|
var data = this.objectQueue[0].data;
|
|
var sizeRemaining = data.length - this._offsetBytes;
|
|
debugLog("Sending next chunk, sizeRemaining: " + sizeRemaining);
|
|
this._chunkPayloadSize =
|
|
Math.min(_control.protocol.tx_chunk_size, sizeRemaining);
|
|
var n;
|
|
var isFirst = (this._offsetBytes === 0);
|
|
var isFirstBit;
|
|
if (isFirst) {
|
|
isFirstBit = (1 << 7);
|
|
n = data.length;
|
|
} else {
|
|
isFirstBit = 0;
|
|
n = this._offsetBytes;
|
|
}
|
|
var chunk = [
|
|
n & 255,
|
|
(n >> 8) & 255,
|
|
(n >> 16) & 255,
|
|
((n >> 24) & ~(1 << 7)) | isFirstBit
|
|
];
|
|
var chunkPayload = data.slice(
|
|
this._offsetBytes, this._offsetBytes + this._chunkPayloadSize);
|
|
Array.prototype.push.apply(chunk, chunkPayload);
|
|
debugLog("Sending Chunk Size: " + this._chunkPayloadSize);
|
|
__Pebble.sendAppMessage({ControlKeyChunk: chunk},
|
|
this._chunkSuccess.bind(this),
|
|
this._chunkFailure.bind(this));
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Sender: Public Interface
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
this.sendObject = function(obj) {
|
|
debugLog("Queuing up object message: " + JSON.stringify(obj));
|
|
var dataObj = this._createDataObject(obj);
|
|
this.objectQueue.push(dataObj)
|
|
this._sendNext();
|
|
};
|
|
|
|
this.sendControl = function(obj) {
|
|
debugLog("Sending control message: " + JSON.stringify(obj));
|
|
this.controlQueue.push(obj);
|
|
this._sendNext();
|
|
}
|
|
};
|
|
|
|
/*****************************************************************************
|
|
* postMessage(): Receiving chunks of inbound objects and reassembly
|
|
****************************************************************************/
|
|
|
|
var _in = new ChunkReceiver();
|
|
|
|
function ChunkReceiver() {
|
|
this.utf8_json_string = "";
|
|
this.total_size_bytes = 0;
|
|
this.received_size_bytes = 0;
|
|
|
|
this.handleChunkReceived = function handleChunkReceived(chunk) {
|
|
if (!chunk) {
|
|
return false;
|
|
}
|
|
var isExpectingFirst = (this.utf8_json_string.length === 0);
|
|
if (chunk.is_first != isExpectingFirst) {
|
|
console.error(
|
|
"Protocol out of sync! chunk.is_first=" + chunk.is_first +
|
|
" isExpectingFirst=" + isExpectingFirst);
|
|
return false;
|
|
}
|
|
if (chunk.is_first) {
|
|
this.total_size_bytes = chunk.total_size_bytes;
|
|
this.received_size_bytes = 0;
|
|
} else {
|
|
if (this.received_size_bytes != chunk.offset_bytes) {
|
|
console.error(
|
|
"Protocol out of sync! received_size_bytes=" +
|
|
this.received_size_bytes + " chunk.offset_bytes=" + chunk.offset_bytes);
|
|
return false;
|
|
}
|
|
if (this.received_size_bytes + chunk.data.length > this.total_size_bytes) {
|
|
console.error(
|
|
"Protocol out of sync! received_size_bytes=" + this.received_size_bytes +
|
|
" chunk.data.length=" + chunk.data.length +
|
|
" total_size_bytes=" + this.total_size_bytes);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
debugLog("Received (" + this.received_size_bytes + " / " +
|
|
this.total_size_bytes + " bytes)");
|
|
debugLog("Payload size: " + chunk.data.length);
|
|
|
|
this.received_size_bytes += chunk.data.length;
|
|
var isLastChunk = (this.received_size_bytes == this.total_size_bytes);
|
|
var isLastChunkZeroTerminated = undefined;
|
|
if (isLastChunk) {
|
|
isLastChunkZeroTerminated = (chunk.data[chunk.data.length - 1] === 0);
|
|
}
|
|
|
|
// Copy the received data over:
|
|
var end = isLastChunk ? chunk.data.length - 1 : chunk.data.length;
|
|
for (var i = 0; i < end; i++) {
|
|
this.utf8_json_string += String.fromCharCode(chunk.data[i]);
|
|
}
|
|
|
|
if (isLastChunk) {
|
|
if (isLastChunkZeroTerminated) {
|
|
var json_string = utf8.decode(this.utf8_json_string);
|
|
var data;
|
|
try {
|
|
data = JSON.parse(json_string);
|
|
} catch (e) {
|
|
console.error(
|
|
"Dropping message, failed to parse JSON with error: " + e +
|
|
" (json_string=" + json_string + ")");
|
|
}
|
|
if (data !== undefined) {
|
|
_callHandlersForEvent('message', data);
|
|
}
|
|
} else {
|
|
console.error("Last Chunk wasn't zero terminated! Dropping message.");
|
|
}
|
|
|
|
this.utf8_json_string = "";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* postMessage() Session Control Protocol
|
|
****************************************************************************/
|
|
|
|
var ControlStateDisconnected = "ControlStateDisconnected";
|
|
var ControlStateAwaitingResetCompleteRemoteInitiated = "ControlStateAwaitingResetCompleteRemoteInitiated";
|
|
var ControlStateAwaitingResetCompleteLocalInitiated = "ControlStateAwaitingResetCompleteLocalInitiated";
|
|
var ControlStateSessionOpen = "ControlStateSessionOpen";
|
|
|
|
var ControlKeyResetRequest = "ControlKeyResetRequest";
|
|
var ControlKeyResetComplete = "ControlKeyResetComplete";
|
|
var ControlKeyChunk = "ControlKeyChunk";
|
|
var ControlKeyUnsupportedError = "ControlKeyUnsupportedError";
|
|
|
|
function _unpackResetCompleteMessage(data) {
|
|
debugLog("Got ResetComplete: " + data);
|
|
return {
|
|
min_version : data[0],
|
|
max_version : data[1],
|
|
max_tx_chunk_size : (data[2] << 8) | (data[3]),
|
|
max_rx_chunk_size : (data[4] << 8) | (data[5]),
|
|
};
|
|
};
|
|
|
|
function _unpackChunk(data) {
|
|
//debugLog("Got Chunk: " + data);
|
|
if (data.length <= 4) {
|
|
console.error("Chunk data too short to be valid!");
|
|
return;
|
|
}
|
|
var is_first_bit = (1 << 7);
|
|
var is_first = (is_first_bit === (data[3] & is_first_bit));
|
|
var chunk = {
|
|
is_first : is_first
|
|
};
|
|
var msbyte = (~is_first_bit) & data[3];
|
|
var num31bits = (msbyte << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
|
|
if (is_first) {
|
|
chunk.total_size_bytes = num31bits;
|
|
} else {
|
|
chunk.offset_bytes = num31bits;
|
|
}
|
|
chunk.data = data.slice(4);
|
|
return chunk;
|
|
}
|
|
|
|
function _remoteProtocolValidateAndSet(remote) {
|
|
debugLog("Remote min: " + remote.min_version);
|
|
debugLog("Remote max: " + remote.max_version);
|
|
if (remote.min_version == undefined || remote.max_version == undefined ||
|
|
remote.min_version > PROTOCOL.max_version || remote.max_version < PROTOCOL.min_version) {
|
|
return false;
|
|
}
|
|
|
|
_control.protocol = {
|
|
version : Math.min(remote.max_version, PROTOCOL.max_version),
|
|
tx_chunk_size : Math.min(remote.max_rx_chunk_size, PROTOCOL.max_tx_chunk_size),
|
|
rx_chunk_size : Math.min(remote.max_tx_chunk_size, PROTOCOL.max_rx_chunk_size),
|
|
};
|
|
|
|
return true;
|
|
};
|
|
|
|
function _sendControlMessage(msg) {
|
|
_out.sendControl(msg);
|
|
}
|
|
|
|
function _controlSendResetComplete() {
|
|
var data = new Uint8Array(6);
|
|
data[0] = PROTOCOL.min_version;
|
|
data[1] = PROTOCOL.max_version;
|
|
data[2] = PROTOCOL.max_tx_chunk_size >> 8;
|
|
data[3] = PROTOCOL.max_tx_chunk_size;
|
|
data[4] = PROTOCOL.max_rx_chunk_size >> 8;
|
|
data[5] = PROTOCOL.max_rx_chunk_size;
|
|
_sendControlMessage({ ControlKeyResetComplete : arrayFromUint8Array(data) });
|
|
}
|
|
|
|
function _controlSendResetRequest() {
|
|
_sendControlMessage({ ControlKeyResetRequest : 0 });
|
|
}
|
|
|
|
function _controlSendUnsupportedError() {
|
|
_sendControlMessage({ ControlKeyUnsupportedError : 0 });
|
|
}
|
|
|
|
var ControlHandlers = {
|
|
ControlStateDisconnected : function(payload) {
|
|
},
|
|
ControlStateAwaitingResetCompleteRemoteInitiated : function(payload) {
|
|
if (ControlKeyResetComplete in payload) {
|
|
var remote_protocol = _unpackResetCompleteMessage(payload[ControlKeyResetComplete]);
|
|
// NOTE: This should *always* be true, we should never receive a
|
|
// ResetComplete response from the Remote in this state since it already
|
|
// knows it is unsupported
|
|
if (_remoteProtocolValidateAndSet(remote_protocol)) {
|
|
_control.enter(ControlStateSessionOpen);
|
|
}
|
|
} else if (ControlKeyResetRequest in payload) {
|
|
_control.enter(ControlStateAwaitingResetCompleteRemoteInitiated); // Re-enter this state
|
|
} else if (ControlKeyChunk in payload) {
|
|
_control.enter(ControlStateAwaitingResetCompleteLocalInitiated);
|
|
} else if (ControlKeyUnsupportedError in payload) {
|
|
throw Error("Unsupported protocol error: " + payload[ControlKeyUnsupportedError]);
|
|
}
|
|
},
|
|
ControlStateAwaitingResetCompleteLocalInitiated : function(payload) {
|
|
if (ControlKeyResetComplete in payload) {
|
|
var remote_protocol = _unpackResetCompleteMessage(payload[ControlKeyResetComplete]);
|
|
debugLog("Remote Protocol: " + remote_protocol);
|
|
if (_remoteProtocolValidateAndSet(remote_protocol)) {
|
|
debugLog("OK Remote protocol...");
|
|
_controlSendResetComplete();
|
|
_control.enter(ControlStateSessionOpen);
|
|
} else {
|
|
_controlSendUnsupportedError();
|
|
}
|
|
} else {
|
|
; // Ignore, we're in this state because we already sent a ResetRequest
|
|
}
|
|
},
|
|
ControlStateSessionOpen : function(payload) {
|
|
if (ControlKeyChunk in payload) {
|
|
var chunk = _unpackChunk(payload[ControlKeyChunk]);
|
|
if (false === _in.handleChunkReceived(chunk)) {
|
|
_control.enter(ControlStateAwaitingResetCompleteLocalInitiated);
|
|
}
|
|
} else if (ControlKeyResetRequest in payload) {
|
|
_control.enter(ControlStateAwaitingResetCompleteRemoteInitiated);
|
|
} else {
|
|
// FIXME: This could be an UnsupportedError, we probably don't want to
|
|
// keep on trying to negotiate protocol
|
|
_control.enter(ControlStateAwaitingResetCompleteLocalInitiated);
|
|
}
|
|
},
|
|
};
|
|
|
|
var ControlTransitions = {
|
|
ControlStateDisconnected : function(from_state) {
|
|
_control.resetProtocol();
|
|
_control.state = ControlStateAwaitingResetCompleteRemoteInitiated;
|
|
},
|
|
ControlStateAwaitingResetCompleteRemoteInitiated : function(from_state) {
|
|
_control.resetProtocol();
|
|
_control.state = ControlStateAwaitingResetCompleteRemoteInitiated;
|
|
_controlSendResetComplete();
|
|
},
|
|
ControlStateAwaitingResetCompleteLocalInitiated : function(from_state) {
|
|
if (from_state != ControlStateAwaitingResetCompleteLocalInitiated) {
|
|
// Coming from elsewhere, send the ResetRequest
|
|
_controlSendResetRequest();
|
|
}
|
|
_control.resetProtocol();
|
|
_control.state = ControlStateAwaitingResetCompleteLocalInitiated;
|
|
},
|
|
ControlStateSessionOpen : function(from_state) {
|
|
_control.state = ControlStateSessionOpen;
|
|
_callHandlersForEvent('postmessageconnected');
|
|
},
|
|
};
|
|
|
|
var PROTOCOL = {
|
|
min_version : 1,
|
|
max_version : 1,
|
|
max_tx_chunk_size : 1000,
|
|
max_rx_chunk_size : 1000,
|
|
};
|
|
|
|
var _control = {
|
|
state : ControlStateDisconnected,
|
|
handle : function(msg) {
|
|
debugLog("Handle " + this.state + "(" + JSON.stringify(msg.payload) + "}");
|
|
ControlHandlers[this.state](msg.payload);
|
|
},
|
|
enter : function(to_state) {
|
|
debugLog("Enter " + this.state + " ===> " + to_state);
|
|
var prev_state = this.state;
|
|
ControlTransitions[to_state](this.state);
|
|
if (prev_state == ControlStateSessionOpen && to_state != ControlStateSessionOpen) {
|
|
_callHandlersForEvent('postmessagedisconnected');
|
|
}
|
|
},
|
|
isSessionOpen: function() {
|
|
return (this.state === ControlStateSessionOpen);
|
|
},
|
|
resetProtocol: function() {
|
|
this.protocol = {
|
|
version : 0,
|
|
tx_chunk_size : 0,
|
|
rx_chunk_size : 0,
|
|
};
|
|
},
|
|
protocol : {
|
|
version : 0,
|
|
tx_chunk_size : 0,
|
|
rx_chunk_size : 0,
|
|
},
|
|
};
|
|
|
|
__Pebble.addEventListener('appmessage', function(msg) {
|
|
_control.handle(msg);
|
|
});
|
|
|
|
__Pebble.addEventListener('ready', function(e) {
|
|
_control.enter(ControlStateAwaitingResetCompleteLocalInitiated);
|
|
});
|
|
})();
|