/// 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 ((response).errorMessage) msg += ": " + (response).errorMessage; Util.log(msg); var e:any = new Error(msg); e.wabStatus = (response).status; e.wabCrashInfo = (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({ 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({ 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({ 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({ 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(); (window).tdevWsUri = (uri, legal, appName) => { Util.log("ws uri: " + uri); URI = uri; Runtime.legalNotice = legal || ""; Runtime.appName = appName; (window).tdevWsUri = () => { }; r.success(uri); }; Util.log("notifying parent app"); var wsuriRetry = 5 function tryNotify() { try { (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({ action: Action.LOCK_ORIENTATION, portraitAllowed: p, landscapeAllowed: l, showClock: clock }).done(() => { SizeMgr.lastOrientationLockTime = Util.now(); }); } function arrivedAtHash(h:string) { sendRequestAsync({ action: Action.CURRENT_HASH, hash: h, isMainScreen: h == '#hub' || h == '#', }).done(); } var lastChannelUri: string; function refreshNotifications(enable: boolean) { var request = { 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({ action: Action.REVIEW_CURRENT_APP }).done(); }; lockOrientation(true, false, true); function waitForUpdate(id:string) { sendRequestAsync({ action: Action.CHECK_FOR_REFRESH }).done(); (TDev).updateLoop(id, "refreshing runtime"); return true; } var w = (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({ 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; } }