TouchDevelop/libwab/runtime.ts

1189 строки
39 KiB
TypeScript

///<reference path='refs.ts'/>
module TDev.RT.Wab {
var URI = "ws://localhost:8042";
export enum Status {
OK = 0,
ERR_PERMISSION_DENIED = -1,
ERR_WEBSOCK_NOT_AVAILABLE = -2,
ERR_WEBSOCK_ACCESS_DENIED = -3,
ERR_WEBSOCK_NOT_CONNECTED = -4,
ERR_AUTHENTICATION_REQUIRED = -5,
ERR_CANCELLED = -6,
ERR_MALFORMED_REQUEST= -7,
ERR_NOT_AVAILABLE = -8,
ERR_NOT_AVAILABLE_WP8 = -43,
ERR_INTERNAL_ERROR= -500,
}
export module Action {
export var REQUEST_AUTHENTICATION = "REQUEST_AUTHENTICATION";
export var AUTHENTICATE = "AUTHENTICATE";
export var REQUEST_PERMISSIONS = "REQUEST_PERMISSIONS";
export var PICK_CONTACT = "PICK_CONTACT";
export var START_GYRO = "START_GYRO";
export var STOP_GYRO = "STOP_GYRO";
export var START_ACCELEROMETER = "START_ACCELEROMETER";
export var STOP_ACCELEROMETER = "STOP_ACCELEROMETER";
export var START_COMPASS = "START_COMPASS"; // new
export var STOP_COMPASS = "STOP_COMPASS"; // new
export var START_ORIENTATION = "START_ORIENTATION"; // new
export var STOP_ORIENTATION = "STOP_ORIENTATION"; // new
export var LOG = "LOG";
export var PROXY = "PROXY";
export var NOTIFICATION = "NOTIFICATION";
export var DB_GET = "DB_GET";
export var DB_SET = "DB_SET";
export var DB_KEYS = "DB_KEYS";
export var DB_DELETE = "DB_DELETE";
export var VIBRATE = "VIBRATE";
export var LIST_SONGS = "LIST_SONGS";
// player commands
export var PLAYER_COMMAND = "PLAYER_COMMAND"; // new
export var PLAYER_STATE = "PLAYER_STATE"; // new
export var ACTIVE_SONG = "ACTIVE_SONG"; // new
export var START_ACTIVE_SONG_CHANGED = "START_ACTIVE_SONG_CHANGED"; // new
export var STOP_ACTIVE_SONG_CHANGED = "STOP_ACTIVE_SONG_CHANGED"; // new
export var START_PLAYER_STATE_CHANGED = "START_PLAYER_STATE_CHANGED"; // new
export var STOP_PLAYER_STATE_CHANGED = "STOP_PLAYER_STATE_CHANGED"; // new
// picture commands
export var SAVE_TO_GALLERY = "SAVE_TO_GALLERY";
export var LIST_IMAGES = "LIST_IMAGES";
export var PICK_IMAGE = "PICK_IMAGE"; // new, Request -> UriResponse
export var TAKE_PHOTO = "TAKE_PHOTO";
export var RECORD_MICROPHONE = "RECORD_MICROPHONE"; // new, Request -> UriResponse
export var SHARE = "SHARE"; // new
export var PLAY_SOUND = "PLAY_SOUND"; // new
export var BROWSE = "BROWSE"; // new
export var DICTATE = "DICTATE"; // speech to text, new
export var OAUTH_AUTHENTICATION = "OAUTH_AUTHENTICATION"; // new
export var NETWORK_INFORMATION = "NETWORK_INFORMATION"; // new
export var LOCK_ORIENTATION = "LOCK_ORIENTATION"; // new
export var POWER_INFORMATION = "POWER_INFORMATION"; // new
export var REVIEW_CURRENT_APP = "REVIEW_CURRENT_APP"; // new
export var LIST_IMAGE_ALBUMS = "LIST_IMAGE_ALBUMS"; // new
export var LIST_IMAGE_ALBUM = "LIST_IMAGE_ALBUM"; // new, UriRequest -> ListAlbumsResponse[]
export var IMAGE = "IMAGE"; // new, UriRequest -> UriResponse
export var LIST_SONG_ALBUMS = "LIST_SONG_ALBUMS"; // new
export var LIST_SONG_ALBUM = "LIST_SONG_ALBUM"; // new
export var SONG_ALBUM = "SONG_ALBUM"; // new
export var SONG_ALBUM_ART = "SONG_ALBUM_ART"; // new
export var LIST_CONTACTS = "LIST_CONTACTS"; // new
export var LIST_APPOINTMENTS = "LIST_APPOINTMENTS"; // new
export var START_SEND_NFC_MESSAGE = "START_SEND_NFC_MESSAGE"; // new
export var STOP_SEND_NFC_MESSAGE = "STOP_SEND_NFC_MESSAGE"; // new
export var START_RECEIVE_NFC_MESSAGE = "START_RECEIVE_NFC_MESSAGE"; // new
export var STOP_RECEIVE_NFC_MESSAGE = "STOP_RECEIVE_NFC_MESSAGE"; // new
export var UPDATE_TILE = "UPDATE_TILE"; // new, UpdateTileRequest
export var SPEAK_TEXT = "SPEAK_TEXT"; // new
export var SPEAK_SSML = "SPEAK_SSML"; // new
export var STATUS = "STATUS"; // new
export var CURRENT_HASH = "CURRENT_HASH"; // new, wp8 specific
export var CHECK_FOR_REFRESH = "CHECK_FOR_REFRESH"; // new, wp8 specific
export var SWITCH_CHANNEL = "SWITCH_CHANNEL"; // new, wp8 specific
export var SEND_SMS = "SEND_SMS"; // new
export var COPY_TO_CLIPBOARD = "COPY_TO_CLIPBOARD"; // new
export var BLUETOOTH_DEVICES = "BLUETOOTH_DEVICES"; // new
export var BLUETOOTH_CONNECT = "BLUETOOTH_CONNECT"; // new
export var BLUETOOTH_READ = "BLUETOOTH_READ"; // new
export var BLUETOOTH_WRITE = "BLUETOOTH_WRITE"; // new
export var BLUETOOTH_DISCONNECT = "BLUETOOTH_DISCONNECT"; // new
export var BLUETOOTHLE_DEVICES = "BLUETOOTHLE_DEVICES"; // new
export var BLUETOOTHLE_READ = "BLUETOOTHLE_READ"; // new
export var BLUETOOTHLE_WRITE = "BLUETOOTHLE_WRITE"; // new
export var SCREENSHOT = "SCREENSHOT"; // new
export var RADIO_COMMAND = "RADIO_COMMAND"; // new
export var SHOW_AD = "SHOW_AD"; // only supported in exported apps
export var CURRENT_APP_INFO = "CURRENT_APP_INFO"; // only supported in exported apps
}
export module Permission {
export var READ_CONTACTS = "READ_CONTACTS";
export var READ_CALENDAR = "READ_CALENDAR";
export var GYRO = "GYRO";
export var ACCELEROMETER = "ACCELEROMETER";
export var AUDIO = "AUDIO";
export var GALLERY = "GALLERY";
export var CAMERA = "CAMERA";
export var VIBRATE = "VIBRATE";
export var RECORD_AUDIO = "RECORD_AUDIO";
export var BLUETOOTH = "BLUETOOTH";
}
export interface Request {
id?: number; // not really optional; just omitted at request construction time, but always defined by _sendRequest
noResponse?: boolean;
action: string; // one of Action.SOMETHING
}
export interface Response {
id?: number;
status: number; // Status
}
export interface CurrentAppInfoResponse extends Response {
storeid : string;
}
export interface SendSmsRequest extends Request {
to?: string;
body?: string;
}
export interface ListResponse extends Response {
removeCallbackId?: string;
lastForId?: string;
}
export function isLastResponse(r:ListResponse)
{
return !!r.lastForId || !!r.removeCallbackId
}
export interface RequestPermissionsRequest extends Request {
permissions: string[]; // array of Permission.SOMETHING
}
export interface CurrentHashRequest extends Request
{
hash:string;
isMainScreen:boolean;
}
export interface OsSettings
{
osVersion:string;
themeBackgroundColor?:string;
themeForegroundColor?:string;
themeSubtleColor?:string;
themeAccentColor?:string;
themeChromeColor?:string;
}
export interface StatusRequest extends Request {
message: string;
progress: boolean; // show moving dots?
kind?: string;
duration?: number;
}
export interface RadioCommandRequest extends Request {
command?: string; // play, stop
frequency?: number; //
}
export interface RadioCommandResponse extends Response {
isPlaying: boolean;
frequency: number;
signal: number; // signalstrengh normalized
}
export interface RequestPermissionsResponse extends Response
{
version:string;
supportedActions:string[]; // Action.SOMETHING
osVersion?:string;
wp8AppVersion?:number;
osSettings?:OsSettings;
}
export interface SpeakTextRequest extends Request {
language: string;
gender: string;
text: string;
}
export interface SpeakSsmlRequest extends Request {
markup: string;
}
export interface StartSendNfcMessageRequest extends Request {
value: string;
type: string; // url, text, vcard, picture, mime type
writeTag:boolean;
}
export interface SendNfcMessageResponse extends Response {
id: number;
transferred?: boolean; // true when message transfered
}
export interface StopNfcMessageRequest extends Request {
id: number;
}
export interface StartReceiveNfcMessageRequest extends Request {
type?: string; // url, text, vcard, picture, mime type
}
export interface ReceiveNfcMessageResponse extends Response {
value: string;
type: string;
id: number;
received?: boolean; // true when message transfered
}
export interface PowerInformationResponse extends Response {
level: number; // between 0 (discharged) and 1 (charged)
source: string; // battery, external
}
export interface CompassResponse extends Response {
v: number; // value
a: number; // accuracy
}
export interface AccelerometerResponse extends Response {
x: number;
y: number;
z: number;
orientation: number; // 0, 90, -90
}
export interface GyroResponse extends Response {
x: number;
y: number;
z: number;
orientation: number;
}
export interface OrientationResponse extends Response {
p: number; // pitch
r: number; // roll
y: number; // yaw
orientation: number;
}
export interface UpdateTileRequest extends UriRequest {
counter?: number;
content?: string;
title?: string;
background?: string;
pin?: boolean;
pictures?: string[];
icon?: string;
template?: string;
}
export interface NetworkInformationResponse extends Response {
connectionName?: string;
connectionType?: string; // unknown, none, ethernet, wifi, mobile
}
export interface ListImageAlbumsResponse extends ListResponse {
name: string;
uri: string;
}
export interface ListImagesResponse extends ListResponse {
uri: string;
}
export interface SongAlbumResponse extends Response {
name: string;
genre?: string;
artist?: string;
duration?: number;
thumbnail?: string;
}
export interface ListSongAlbumsResponse extends ListResponse {
name: string;
artist: string;
}
export interface ListSongsResponse extends ListResponse {
uri: string;
data: string;
title: string;
artist: string;
album: string;
duration?: number;
track?: number;
}
export interface ActiveSongResponse extends Response {
uri: string;
data: string;
title: string;
artist: string;
album: string;
duration?: number;
track?: number;
}
export interface PlaySoundRequest extends UriRequest {
soundid?: string;
pan?: number;
pitch?: number;
volume?: number;
}
export interface PlaySoundResponse extends Response {
soundid?: string;
cachemiss?: boolean;
}
export interface ShareRequest extends Request {
provider?: string; // optional preferred social network
text?: string;
uri?: string;
photoUri?: string;
}
export interface UriRequest extends Request {
uri: string;
}
export interface UriResponse extends Response {
uri: string;
}
export interface DictateRequest extends Request {
title?:string;
caption?: string;
}
export interface DictateResponse extends Response {
text: string;
}
export interface SaveToGalleryResponse extends Response {
name: string;
}
export interface PlayerCommandRequest extends Request {
command: string; // 'play', 'stop', 'resume', 'pause', 'next', 'previous'
uri?: string; // required for play
}
export interface PlayerStateRequest extends Request {
shuffle?: boolean;
repeat?: boolean;// a single song
}
export interface PlayerStateResponse extends Response {
state: string; // 'playing', 'paused', 'stopped'
shuffle: boolean;
muted: boolean;
repeat: boolean;
}
export interface SearchAppointmentsRequest extends Request {
start: number; // DateTime
end: number; // DateTime
}
export interface AppointmentContact {
nameDisplay: string;
email: string;
}
export interface CopyToClipboardRequest extends Request {
text: string;
}
export interface ListAppointmentsResponse extends ListResponse {
subject: string;
location: string;
start: number; // DateTime
end: number; // DateTime
source: string;
details?: string;
isAllDay?: boolean;
isPrivate?: boolean;
onlineStatus?: string;
organizer?: AppointmentContact; // display name of the organizer
attendees?: AppointmentContact[];
}
export interface SearchContactsRequest extends Request {
query: string;
}
export interface ListContactsResponse extends ListResponse {
nameDisplay: string;
email: string;
}
export interface ContactResponse extends Response {
nameGiven: string;
nameMiddle: string;
nameFamily: string;
nameDisplay: string;
phoneMain: string;
phoneHome: string;
phoneWork: string;
phoneMobile: string;
phoneOther: string;
faxHome: string;
faxWork: string;
emailHome: string;
emailWork: string;
emailOther: string;
addressHome: string;
addressWork: string;
addressOther: string;
photoUri: string;
source?: string;
phone: string; // legacy
email: string; // legacy
name: string; // legacy
}
export interface VibrateRequest extends Request {
millis: number;
}
interface RequestAuthenticationRequest extends Request {
path: string;
}
interface AuthenticateRequest extends Request {
token: string;
}
interface LogRequest extends Request {
texts: string[];
}
interface ProxyCredentials {
name: string;
password: string;
}
interface ProxyHeader {
name: string;
value: string;
}
interface ProxyRequest extends Request {
url: string;
method: string;
// content of web request, at most one present
contentText?: string;
content?: string; // base64 encoded
headers?: ProxyHeader[];
credentials?: ProxyCredentials;
responseType: string; // "base64" or "text"
}
export interface OAuthAuthenticationRequest extends UriRequest {
redirectUri: string; // registered redirect uri
state: string; // state value to be validated
}
interface ProxyResponse extends Response {
}
interface DbGetRequest extends Request {
table: string;
keys: string[];
}
interface DbGetResponse extends Response {
values: string[];
}
interface DbSetRequest extends Request {
table: string;
keys: string[];
values: string[];
}
interface DbSetResponse extends Response {
}
interface DbKeysRequest extends Request {
table: string;
}
interface DbKeysResponse extends Response {
keys: string[];
}
interface LockOrientationRequest extends Request {
portraitAllowed: boolean;
landscapeAllowed: boolean;
showClock: boolean;
}
export interface BluetoothDeviceName {
hostName: string;
serviceName: string;
}
export interface BluetoothDeviceFriendlyName extends BluetoothDeviceName {
displayName: string;
}
export interface BluetoothDevicesRequest extends Request {}
export interface BluetoothConnectRequest extends Request, BluetoothDeviceName {}
export interface BluetoothDisconnectRequest extends Request, BluetoothDeviceName {}
export interface BluetoothWriteRequest extends Request, BluetoothDeviceName {
data: string;
}
export interface BluetoothReadRequest extends Request, BluetoothDeviceName {
length: number;
}
export interface BluetoothDevicesResponse extends Response {
bluetoothOn: boolean;
devices?: BluetoothDeviceFriendlyName[];
}
export interface BluetoothConnectResponse extends Response {
connected: boolean;
}
export interface BluetoothDisconnectResponse extends BluetoothConnectResponse {}
export interface BluetoothWriteResponse extends BluetoothConnectResponse {}
export interface BluetoothReadResponse extends BluetoothConnectResponse {
data: string;
}
export interface BluetoothLeDeviceName {
deviceId: string;
displayName: string;
services: string[];
connected?: boolean;
}
export interface BluetoothLeDevicesRequest extends Request { }
export interface BluetoothLeDevicesResponses extends Response {
bluetoothOn: boolean;
devices?: BluetoothLeDeviceName[];
}
export interface BluetoothLeDeviceRequest extends Request {
deviceId: string;
serviceId: string;
characteristicId: string;
}
export interface BluetoothLeWriteRequest extends BluetoothLeDeviceRequest {
withResponse: boolean;
data: string;
}
export interface BluetoothLeReadRequest extends BluetoothLeDeviceRequest {}
export interface BluetoothLeConnectResponse extends Response {
connected: boolean;
}
export interface BluetoothLeWriteResponse extends BluetoothLeConnectResponse {}
export interface BluetoothLeReadResponse extends BluetoothLeConnectResponse {
data: string[];
}
interface NotificationRequest extends Request {
enable: boolean;
}
class PendingResponse {
constructor(public action: string, public onSuccess: (r: Response) => void , public onError: (any) => void ) {
}
}
var _nextId = 0;
var _pendingResponses: any = {}; // TODO: time out stale requests
var _webSocket: WebSocket = undefined;
var isWP8app = false;
var wp8AppVersion = -1;
var supportsAttachments = false;
var confirmedWP8app = false;
export function isActive() {
return !!_webSocket || isWP8app;
}
function openWp8Async(): Promise {
var ret = new PromiseInv();
_webSocket = new WebSocket(URI);
_webSocket.onopen = () => {
var r = ret;
ret = undefined;
Util.log("wp8ws: open");
if (r) r.success(null);
};
_webSocket.onmessage = _onmessage;
numAttachments = 0;
_webSocket.onerror = (e) =>
{
Util.log("wp8ws: error");
var r = ret;
ret = undefined;
_webSocket = undefined;
_onerror(e);
if (r) r.error(e);
}
_webSocket.onclose = () => {
Util.log("wp8ws: close");
_webSocket = undefined;
};
return ret;
}
var numAttachments = 0;
var attachments = [];
var finalAttachments = () => { };
var orphanedReported = false;
function handleMessage(r: any) {
if (r == undefined) return;
var id = r.id;
if (id == undefined) return;
var key = id + "";
var pending: PendingResponse = _pendingResponses[key];
if (pending) {
if (!/START_|LIST_/.test(pending.action) ||
isLastResponse(r)) {
delete _pendingResponses[key];
}
if (pending.onSuccess)
Util.setTimeout(0, () => {
try {
pending.onSuccess(r);
} catch (e) {
Util.reportError("wab-onmessage-success", e);
}
})
} else if (r.status) {
if (!orphanedReported) {
orphanedReported = true;
statusError(r);
}
}
}
function handleAttachments(r: any): boolean {
var scan = (repl) => {
Object.keys(r).forEach((k) => {
if (!/^attach:/.test(k)) return;
var v = r[k]
if (typeof v == "number") {
if (repl)
v = attachments[v];
else
maxId = Math.max(maxId, v);
} else if (Array.isArray(v)) {
v.forEach((z, i) => {
if (typeof z == "number") {
if (repl)
v[i] = attachments[z];
else
maxId = Math.max(maxId, z);
}
})
}
if (repl) {
delete r[k];
k = k.slice(7);
r[k] = v;
}
})
}
var maxId = -1;
scan(false);
maxId++;
if (maxId > 0) {
numAttachments = maxId;
attachments = [];
finalAttachments = () => {
scan(true);
attachments = [];
handleMessage(r);
};
return true;
}
return false;
}
function _onmessage(ev: MessageEvent): void {
try {
if (numAttachments > 0) {
numAttachments--;
attachments.push(ev.data);
if (numAttachments == 0)
finalAttachments();
return;
}
var r;
try {
r = JSON.parse(ev.data);
} catch (e) { return; }
if (supportsAttachments && handleAttachments(r)) return;
handleMessage(r);
} catch (e) {
Util.reportError("wab-onmessage", e);
}
}
function _onerror(ev: ErrorEvent): void {
var prs = _pendingResponses;
_pendingResponses = {};
Object.keys(prs).forEach(k => {
var h = prs[k].onError;
if (h) h(ev);
});
// TODO: reopen channel?
}
var sendingLock = new Lock();
function moveToAttachments(request: any): string[] {
var oob = []
function isLong(v) {
return typeof v == "string" && v.length > 4000;
}
function attach(v) {
if (isLong(v)) {
var r = oob.length;
oob.push(v);
return r;
} else {
return v;
}
}
Object.keys(request).forEach((k) => {
var v = request[k]
if (isLong(v)) {
delete request[k];
request["attach:" + k] = attach(v);
} else if (Array.isArray(v) && v.some(isLong)) {
delete request[k];
request["attach:" + k] = v.map(attach);
}
})
oob.unshift(JSON.stringify(request));
return oob;
}
function _sendRequest(request: Request, onSuccess: (r: Response) => void , onError: (any) => void ): string {
if (_webSocket === undefined) {
if (onSuccess) onSuccess({ status: Status.ERR_WEBSOCK_NOT_CONNECTED });
return;
}
request.id = _nextId++;
if (!(isWP8app && request.noResponse))
_pendingResponses[request.id + ""] = new PendingResponse(request.action, onSuccess, onError);
//Util.log("ws send");
if (supportsAttachments) {
var msgs = moveToAttachments(request);
msgs.forEach((s) => _webSocket.send(s));
} else {
_webSocket.send(JSON.stringify(request));
}
return request.id + "";
}
function wrapResponse(response: Response) {
var msg = ""
switch (response.status) {
case Status.ERR_INTERNAL_ERROR:
msg = "TouchDevelop runtime crashed"; break;
case Status.ERR_MALFORMED_REQUEST:
msg = "WebAppBooster reported a malformed request"; break;
case Status.ERR_NOT_AVAILABLE:
case Status.ERR_NOT_AVAILABLE_WP8:
msg = "Not available";
default:
msg = "Error " + response.status;
}
if ((<any>response).errorMessage)
msg += ": " + (<any>response).errorMessage;
Util.log(msg);
var e:any = new Error(msg);
e.wabStatus = (<any>response).status;
e.wabCrashInfo = (<any>response).crashInfo;
//I don't think we should be really getting these - the command should not be boosted
//if (response.status == Response.ERR_NOT_AVAILABLE)
// response.isUserError = true;
return e;
}
function requestError(err:any)
{
Util.reportError("wab-request", err);
}
function statusError(err:any)
{
Util.reportError("wab-status", wrapResponse(err));
}
export function statusErrorRaw(err:any):void
{
Util.reportError("wab-status", err);
}
export function sendRequest(request: Request, onSuccess: (r: Response) => void, onError = statusErrorRaw): string {
return _sendRequest(request, response => {
switch (response.status) {
case Status.OK:
case Status.ERR_CANCELLED:
onSuccess(response); break;
case Status.ERR_PERMISSION_DENIED: requestPermissionsAsync().done(() => {
_sendRequest(request, response => {
if (response.status == Status.OK || response.status == Status.ERR_CANCELLED)
onSuccess(response);
else onError(wrapResponse(response));
}, requestError);
}, requestError); break;
default:
onError(wrapResponse(response));
}
}, requestError)
}
export function cancelRequest(id: string) {
var pending: PendingResponse = _pendingResponses[id];
if (pending) {
delete _pendingResponses[id];
if (/^START_/.test(pending.action)) {
_sendRequest({ action: pending.action.replace(/^START_/, "STOP_") }, resp => { }, err => { });
}
}
}
export function sendRequestAsync(request: Request): Promise {
return new Promise((onSuccess, onError, onProgress) => {
sendRequest(request, onSuccess, onError);
});
}
function boostColors(s:OsSettings)
{
if (!s) return;
Util.log('wab: boosting COLORS');
function boost(n:string, v:string)
{
if (v) {
var c = Color.fromHtml(v);
Colors[n] = () => c;
}
}
boost("foreground_os", s.themeForegroundColor);
boost("background_os", s.themeBackgroundColor);
boost("chrome", s.themeChromeColor);
boost("subtle", s.themeSubtleColor);
boost("accent", s.themeAccentColor);
}
function requestPermissionsAsync(): Promise {
Util.log('wab: requesting permissions');
return new Promise((onSuccess, onError, onProgress) => {
_sendRequest(<RequestPermissionsRequest>{
action: Action.REQUEST_PERMISSIONS,
permissions: [Permission.READ_CONTACTS, Permission.READ_CALENDAR, Permission.GYRO, Permission.ACCELEROMETER,
Permission.AUDIO, Permission.GALLERY, Permission.CAMERA, Permission.VIBRATE, Permission.RECORD_AUDIO, Permission.BLUETOOTH],
}, (response: RequestPermissionsResponse) => {
if (response.status == Status.OK) {
supportedActions = response.supportedActions;
Util.log('wab: permissions: ' + supportedActions.join(', '));
if (response.wp8AppVersion && wp8AppVersion < 0) {
wp8AppVersion = response.wp8AppVersion;
Browser.platformCaps.push("wp8app-v" + wp8AppVersion)
Browser.platformCaps.push("wp8-v" + response.osVersion)
Util.log(Browser.platformCaps.join(", "))
boostColors(response.osSettings)
}
onSuccess(response);
}
else {
Util.log('wab: permissions request failed');
onError(undefined);
}
}, onError);
});
}
var pendingMsgs = []
function flushMsgs() {
if (pendingMsgs.length > 0) {
sendRequestAsync(<LogRequest>{ action: Action.LOG, noResponse: true, texts: pendingMsgs }).done();
pendingMsgs = [];
}
Util.setTimeout(500, flushMsgs);
}
class Wp8Table implements Storage.Table {
constructor(public name: string) {
}
public getValueAsync(key: string): Promise // of string
{
return this.getItemsAsync([key]).then((items) => items[key]);
}
public getItemsAsync(keys: string[]): Promise // of Object
{
return sendRequestAsync(<DbGetRequest>{ action: Action.DB_GET, table: this.name, keys: keys })
.then((resp: DbGetResponse) => {
var r = {}
keys.forEach((k, i) => {
r[k] = resp.values[i];
})
return r;
})
}
public getKeysAsync(): Promise // of string[]
{
return sendRequestAsync(<DbKeysRequest>{ action: Action.DB_KEYS, table: this.name })
.then((resp: DbKeysResponse) => resp.keys)
}
static dbSetLock = new Lock();
public setItemsAsync(items: any): Promise // of void
{
var keys = Object.keys(items);
var hasBig = false;
var vals = keys.map((k) => {
var v:string = items[k]
if (v && v.length > 100000) hasBig = true;
if (v === undefined) return null;
return v;
});
var req: DbSetRequest = { action: Action.DB_SET, table: this.name, keys: keys, values: vals };
if (hasBig) {
var ret = new PromiseInv();
Wp8Table.dbSetLock.acquire(() => {
sendRequestAsync(req).done((resp) => {
Wp8Table.dbSetLock.release();
ret.success(resp);
});
});
return ret;
} else
return sendRequestAsync(req);
}
}
function setWp8UriAsync() {
var r = new PromiseInv();
(<any>window).tdevWsUri = (uri, legal, appName) => {
Util.log("ws uri: " + uri);
URI = uri;
Runtime.legalNotice = legal || "";
Runtime.appName = appName;
(<any>window).tdevWsUri = () => { };
r.success(uri);
};
Util.log("notifying parent app");
var wsuriRetry = 5
function tryNotify() {
try {
(<any>window.external).Notify("WSURI");
} catch (e) {
if (wsuriRetry-- < 0) throw e;
else Util.setTimeout(500, tryNotify);
}
}
tryNotify();
Util.log("parent app notified");
return r;
}
function setupWp8app(): Promise {
if (!Browser.isWP8app) return null;
if (!window.external) return null;
isWP8app = true;
supportsAttachments = true;
Browser.deviceMotion = true;
Browser.webAppBooster = true;
Browser.audioWav = true;
Browser.canIndexedDB = false;
Storage.getTableAsync = (name: string) => Promise.as(new Wp8Table(name));
Storage.clearPreAsync = () =>
Promise.join(Storage.tableNames.map((t) => sendRequestAsync({ action: Action.DB_DELETE, table: t })));
Util.log("wp8: sending auth app request");
return setWp8UriAsync().then(() => openWp8Async()).then(() => {
_sendRequest({ action: Action.REQUEST_AUTHENTICATION, path: "app" }, resp => {
if (resp.status == Status.OK) {
Util.externalLog = (msg) => {
pendingMsgs.push(msg);
};
Util.log("wp8: auth app request OK");
confirmedWP8app = true;
flushMsgs();
} else {
Util.log("wp8: auth app request failed: " + JSON.stringify(resp));
isWP8app = false;
}
}, err => {
Util.log("wp8: auth app request failed (onError): " + err);
isWP8app = false;
});
})
}
var supportedActions: string[] = [];
export function isSupportedAction(name: string) {
return supportedActions.indexOf(name) > -1;
}
function lockOrientation(p: boolean, l: boolean, clock: boolean) {
SizeMgr.lastOrientationLockTime = Util.now();
sendRequestAsync(<LockOrientationRequest>{
action: Action.LOCK_ORIENTATION,
portraitAllowed: p,
landscapeAllowed: l,
showClock: clock
}).done(() => {
SizeMgr.lastOrientationLockTime = Util.now();
});
}
function arrivedAtHash(h:string) {
sendRequestAsync(<CurrentHashRequest>{
action: Action.CURRENT_HASH,
hash: h,
isMainScreen: h == '#hub' || h == '#',
}).done();
}
var lastChannelUri: string;
function refreshNotifications(enable: boolean) {
var request = <NotificationRequest >{
action: Action.NOTIFICATION,
enable: enable
};
//Util.log("Notification request: " + JSON.stringify(request));
sendRequestAsync(request).then(response => {
//Util.log("Notification response: " + JSON.stringify(response));
if (response.channelUri && lastChannelUri != response.channelUri) {
lastChannelUri = response.channelUri;
var webRequest = { subscriptionuri: response.channelUri, versionminor: response.versionMinor, versionmajor: response.versionMajor };
//Util.log("Notification web request: " + JSON.stringify(webRequest));
Cloud.postNotificationChannelAsync(webRequest).done(webResponse =>
{
//Util.log("Notification web response: " + JSON.stringify(webResponse));
},
ex => { });
}
}).done();
}
export function initAsync(): Promise {
var r = setupWp8app();
if (r) return r.then(() => requestPermissionsAsync()).then(() => {
Util.log('wab: boosting NOTIFICATION');
Runtime.refreshNotifications = refreshNotifications;
Runtime.lockOrientation = lockOrientation;
if (isSupportedAction(Action.CURRENT_HASH))
Screen.arrivedAtHash = arrivedAtHash;
Runtime.rateTouchDevelop = () => {
sendRequestAsync(<Request>{ action: Action.REVIEW_CURRENT_APP }).done();
};
lockOrientation(true, false, true);
function waitForUpdate(id:string)
{
sendRequestAsync(<Request>{ action: Action.CHECK_FOR_REFRESH }).done();
(<any>TDev).updateLoop(id, "refreshing runtime");
return true;
}
var w = (<any>TDev).World;
if (w) {
if (isSupportedAction(Action.CHECK_FOR_REFRESH)) {
Util.log('wab: boosting CHECK_FOR_REFRESH');
w.waitForUpdate = waitForUpdate;
} else {
w.waitForUpdate = () => false;
}
if (isSupportedAction(Action.SWITCH_CHANNEL)) {
Util.log('wab: boosting SWITCH_CHANNEL');
w.switchToChannel = (ch:string) => {
sendRequestAsync({
action: Action.SWITCH_CHANNEL,
channel: ch,
}).done();
ProgressOverlay.lockAndShow("switching to " + ch);
};
} else {
w.switchToChannel = null;
}
}
// we use this guy as a ping; it doesn't do much at all on the C# side
if (isSupportedAction(Action.CURRENT_HASH))
Util.log('wab: boosting CURRENT_HASH');
Runtime.continueAfter = (ms:number, f:()=>void) => {
sendRequestAsync(<CurrentHashRequest>{
action: Action.CURRENT_HASH,
hash: undefined,
isMainScreen: false
}).done(f);
};
});
return Promise.as();
}
export function getSupportedCapabilities(): string[] {
if (!isActive()) return [];
var caps: string[] = [];
if (isSupportedAction(Action.PICK_CONTACT) ||
isSupportedAction(Action.LIST_CONTACTS))
caps.push("contacts");
if (isSupportedAction(Action.START_GYRO))
caps.push("gyroscope");
if (isSupportedAction(Action.START_ACCELEROMETER))
caps.push("accelerometer");
if (isSupportedAction(Action.START_COMPASS))
caps.push("compass");
if (isSupportedAction(Action.START_ORIENTATION))
caps.push("orientation");
if (isSupportedAction(Action.LIST_APPOINTMENTS))
caps.push("calendar");
if (isSupportedAction(Action.UPDATE_TILE))
caps.push("tiles");
if (isSupportedAction(Action.SPEAK_TEXT) ||
isSupportedAction(Action.SPEAK_SSML) ||
isSupportedAction(Action.DICTATE))
caps.push("speech");
if (isSupportedAction(Action.LIST_SONGS)
|| isSupportedAction(Action.LIST_SONG_ALBUM)
|| isSupportedAction(Action.LIST_SONG_ALBUMS)
|| isSupportedAction(Action.SONG_ALBUM)
|| isSupportedAction(Action.PLAYER_COMMAND)
|| isSupportedAction(Action.PLAYER_STATE)
|| isSupportedAction(Action.ACTIVE_SONG)
|| isSupportedAction(Action.START_ACTIVE_SONG_CHANGED)
|| isSupportedAction(Action.START_PLAYER_STATE_CHANGED)
|| isSupportedAction(Action.PLAY_SOUND))
caps.push('musicandsounds');
if (isSupportedAction(Action.SAVE_TO_GALLERY)
|| isSupportedAction(Action.PICK_IMAGE))
caps.push('media');
if (isSupportedAction(Action.RECORD_MICROPHONE))
caps.push('microphone');
if (isSupportedAction(Action.START_SEND_NFC_MESSAGE) ||
isSupportedAction(Action.START_RECEIVE_NFC_MESSAGE))
caps.push('proximity');
if (isSupportedAction(Action.BLUETOOTH_DEVICES))
caps.push('bluetooth');
if (isSupportedAction(Action.RADIO_COMMAND))
caps.push('radio');
return caps;
}
}