Ignore non-active fullscreen windows for shelf state.
[chromium-blink-merge.git] / remoting / webapp / host_native_messaging.js
blob8c76c424a3f535e45d05498212683b89b56af6f5
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview
7 * Class to communicate with the Host components via Native Messaging.
8 */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16 * @constructor
18 remoting.HostNativeMessaging = function() {
19 /**
20 * @type {number}
21 * @private
23 this.nextId_ = 0;
25 /**
26 * @type {Object.<number, remoting.HostNativeMessaging.PendingReply>}
27 * @private
29 this.pendingReplies_ = {};
31 /** @type {?chrome.extension.Port} @private */
32 this.port_ = null;
34 /** @type {string} @private */
35 this.version_ = '';
37 /** @type {Array.<remoting.HostController.Feature>} @private */
38 this.supportedFeatures_ = [];
41 /**
42 * Type used for entries of |pendingReplies_| list.
44 * @param {string} type Type of the originating request.
45 * @param {?function(...):void} onDone The callback, if any, to be triggered
46 * on response. The actual parameters depend on the original request type.
47 * @param {function(remoting.Error):void} onError The callback to be triggered
48 * on error.
49 * @constructor
51 remoting.HostNativeMessaging.PendingReply = function(type, onDone, onError) {
52 this.type = type;
53 this.onDone = onDone;
54 this.onError = onError;
57 /**
58 * Sets up connection to the Native Messaging host process and exchanges
59 * 'hello' messages. If Native Messaging is not available or the host
60 * process is not installed, this returns false to the callback.
62 * @param {function(): void} onDone Called after successful initialization.
63 * @param {function(remoting.Error): void} onError Called if initialization
64 * failed.
65 * @return {void} Nothing.
67 remoting.HostNativeMessaging.prototype.initialize = function(onDone, onError) {
68 if (!chrome.runtime.connectNative) {
69 console.log('Native Messaging API not available');
70 onError(remoting.Error.UNEXPECTED);
71 return;
74 // NativeMessaging API exists on Chrome 26.xxx but fails to notify
75 // onDisconnect in the case where the Host components are not installed. Need
76 // to blacklist these versions of Chrome.
77 var majorVersion = navigator.appVersion.match('Chrome/(\\d+)\.')[1];
78 if (!majorVersion || majorVersion <= 26) {
79 console.log('Native Messaging not supported on this version of Chrome');
80 onError(remoting.Error.UNEXPECTED);
81 return;
84 try {
85 this.port_ = chrome.runtime.connectNative(
86 'com.google.chrome.remote_desktop');
87 this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
88 this.port_.onDisconnect.addListener(this.onDisconnect_.bind(this));
89 this.postMessage_({type: 'hello'}, onDone,
90 onError.bind(null, remoting.Error.UNEXPECTED));
91 } catch (err) {
92 console.log('Native Messaging initialization failed: ',
93 /** @type {*} */ (err));
94 onError(remoting.Error.UNEXPECTED);
95 return;
99 /**
100 * Verifies that |object| is of type |type|, logging an error if not.
102 * @param {string} name Name of the object, to be included in the error log.
103 * @param {*} object Object to test.
104 * @param {string} type Expected type of the object.
105 * @return {boolean} Result of test.
107 function checkType_(name, object, type) {
108 if (typeof(object) !== type) {
109 console.error('NativeMessaging: "' + name + '" expected to be of type "' +
110 type + '", got: ' + object);
111 return false;
113 return true;
117 * Returns |result| as an AsyncResult. If |result| is not valid, returns null
118 * and logs an error.
120 * @param {*} result
121 * @return {remoting.HostController.AsyncResult?} Converted result.
123 function asAsyncResult_(result) {
124 if (!checkType_('result', result, 'string')) {
125 return null;
127 if (!remoting.HostController.AsyncResult.hasOwnProperty(result)) {
128 console.error('NativeMessaging: unexpected result code: ', result);
129 return null;
131 return remoting.HostController.AsyncResult[result];
135 * Returns |result| as a HostController.State. If |result| is not valid,
136 * returns null and logs an error.
138 * @param {*} result
139 * @return {remoting.HostController.State?} Converted result.
141 function asHostState_(result) {
142 if (!checkType_('result', result, 'string')) {
143 return null;
145 if (!remoting.HostController.State.hasOwnProperty(result)) {
146 console.error('NativeMessaging: unexpected result code: ', result);
147 return null;
149 return remoting.HostController.State[result];
153 * @param {remoting.HostController.Feature} feature The feature to test for.
154 * @return {boolean} True if the implementation supports the named feature.
156 remoting.HostNativeMessaging.prototype.hasFeature = function(feature) {
157 return this.supportedFeatures_.indexOf(feature) >= 0;
161 * Attaches a new ID to the supplied message, and posts it to the Native
162 * Messaging port, adding |onDone| to the list of pending replies.
163 * |message| should have its 'type' field set, and any other fields set
164 * depending on the message type.
166 * @param {{type: string}} message The message to post.
167 * @param {?function(...):void} onDone The callback, if any, to be triggered
168 * on response.
169 * @param {function(remoting.Error):void} onError The callback to be triggered
170 * on error.
171 * @return {void} Nothing.
172 * @private
174 remoting.HostNativeMessaging.prototype.postMessage_ =
175 function(message, onDone, onError) {
176 var id = this.nextId_++;
177 message['id'] = id;
178 this.pendingReplies_[id] = new remoting.HostNativeMessaging.PendingReply(
179 message.type + 'Response', onDone, onError);
180 this.port_.postMessage(message);
184 * Handler for incoming Native Messages.
186 * @param {Object} message The received message.
187 * @return {void} Nothing.
188 * @private
190 remoting.HostNativeMessaging.prototype.onIncomingMessage_ = function(message) {
191 /** @type {number} */
192 var id = message['id'];
193 if (typeof(id) != 'number') {
194 console.error('NativeMessaging: missing or non-numeric id');
195 return;
197 var reply = this.pendingReplies_[id];
198 if (!reply) {
199 console.error('NativeMessaging: unexpected id: ', id);
200 return;
202 delete this.pendingReplies_[id];
204 var onDone = reply.onDone;
205 var onError = reply.onError;
207 /** @type {string} */
208 var type = message['type'];
209 if (!checkType_('type', type, 'string')) {
210 onError(remoting.Error.UNEXPECTED);
211 return;
213 if (type != reply.type) {
214 console.error('NativeMessaging: expected reply type: ', reply.type,
215 ', got: ', type);
216 onError(remoting.Error.UNEXPECTED);
217 return;
220 switch (type) {
221 case 'helloResponse':
222 /** @type {string} */
223 var version = message['version'];
224 if (checkType_('version', version, 'string')) {
225 this.version_ = version;
226 if (message['supportedFeatures'] instanceof Array) {
227 this.supportedFeatures_ = message['supportedFeatures'];
228 } else {
229 // Old versions of the native messaging host do not return this list.
230 // Those versions don't support any new feature.
231 this.supportedFeatures_ = [];
233 onDone();
234 } else {
235 onError(remoting.Error.UNEXPECTED);
237 break;
239 case 'getHostNameResponse':
240 /** @type {*} */
241 var hostname = message['hostname'];
242 if (checkType_('hostname', hostname, 'string')) {
243 onDone(hostname);
244 } else {
245 onError(remoting.Error.UNEXPECTED);
247 break;
249 case 'getPinHashResponse':
250 /** @type {*} */
251 var hash = message['hash'];
252 if (checkType_('hash', hash, 'string')) {
253 onDone(hash);
254 } else {
255 onError(remoting.Error.UNEXPECTED);
257 break;
259 case 'generateKeyPairResponse':
260 /** @type {*} */
261 var privateKey = message['privateKey'];
262 /** @type {*} */
263 var publicKey = message['publicKey'];
264 if (checkType_('privateKey', privateKey, 'string') &&
265 checkType_('publicKey', publicKey, 'string')) {
266 onDone(privateKey, publicKey);
267 } else {
268 onError(remoting.Error.UNEXPECTED);
270 break;
272 case 'updateDaemonConfigResponse':
273 var result = asAsyncResult_(message['result']);
274 if (result != null) {
275 onDone(result);
276 } else {
277 onError(remoting.Error.UNEXPECTED);
279 break;
281 case 'getDaemonConfigResponse':
282 /** @type {*} */
283 var config = message['config'];
284 if (checkType_('config', config, 'object')) {
285 onDone(config);
286 } else {
287 onError(remoting.Error.UNEXPECTED);
289 break;
291 case 'getUsageStatsConsentResponse':
292 /** @type {*} */
293 var supported = message['supported'];
294 /** @type {*} */
295 var allowed = message['allowed'];
296 /** @type {*} */
297 var setByPolicy = message['setByPolicy'];
298 if (checkType_('supported', supported, 'boolean') &&
299 checkType_('allowed', allowed, 'boolean') &&
300 checkType_('setByPolicy', setByPolicy, 'boolean')) {
301 onDone(supported, allowed, setByPolicy);
302 } else {
303 onError(remoting.Error.UNEXPECTED);
305 break;
307 case 'startDaemonResponse':
308 case 'stopDaemonResponse':
309 var result = asAsyncResult_(message['result']);
310 if (result != null) {
311 onDone(result);
312 } else {
313 onError(remoting.Error.UNEXPECTED);
315 break;
317 case 'getDaemonStateResponse':
318 var state = asHostState_(message['state']);
319 if (state != null) {
320 onDone(state);
321 } else {
322 onError(remoting.Error.UNEXPECTED);
324 break;
326 case 'getPairedClientsResponse':
327 var pairedClients = remoting.PairedClient.convertToPairedClientArray(
328 message['pairedClients']);
329 if (pairedClients != null) {
330 onDone(pairedClients);
331 } else {
332 onError(remoting.Error.UNEXPECTED);
334 break;
336 case 'clearPairedClientsResponse':
337 case 'deletePairedClientResponse':
338 /** @type {boolean} */
339 var success = message['result'];
340 if (checkType_('success', success, 'boolean')) {
341 onDone(success);
342 } else {
343 onError(remoting.Error.UNEXPECTED);
345 break;
347 case 'getHostClientIdResponse':
348 /** @type {string} */
349 var clientId = message['clientId'];
350 if (checkType_('clientId', clientId, 'string')) {
351 onDone(clientId);
352 } else {
353 onError(remoting.Error.UNEXPECTED);
355 break;
357 case 'getCredentialsFromAuthCodeResponse':
358 /** @type {string} */
359 var userEmail = message['userEmail'];
360 /** @type {string} */
361 var refreshToken = message['refreshToken'];
362 if (checkType_('userEmail', userEmail, 'string') && userEmail &&
363 checkType_('refreshToken', refreshToken, 'string') && refreshToken) {
364 onDone(userEmail, refreshToken);
365 } else {
366 onError(remoting.Error.UNEXPECTED);
368 break;
370 default:
371 console.error('Unexpected native message: ', message);
372 onError(remoting.Error.UNEXPECTED);
377 * @return {void} Nothing.
378 * @private
380 remoting.HostNativeMessaging.prototype.onDisconnect_ = function() {
381 console.error('Native Message port disconnected');
383 // Notify the error-handlers of any requests that are still outstanding.
384 for (var id in this.pendingReplies_) {
385 this.pendingReplies_[/** @type {number} */(id)].onError(
386 remoting.Error.UNEXPECTED);
388 this.pendingReplies_ = {};
392 * @param {function(string):void} onDone Callback to be called with the
393 * local hostname.
394 * @param {function(remoting.Error):void} onError The callback to be triggered
395 * on error.
396 * @return {void} Nothing.
398 remoting.HostNativeMessaging.prototype.getHostName =
399 function(onDone, onError) {
400 this.postMessage_({type: 'getHostName'}, onDone, onError);
404 * Calculates PIN hash value to be stored in the config, passing the resulting
405 * hash value base64-encoded to the callback.
407 * @param {string} hostId The host ID.
408 * @param {string} pin The PIN.
409 * @param {function(string):void} onDone Callback.
410 * @param {function(remoting.Error):void} onError The callback to be triggered
411 * on error.
412 * @return {void} Nothing.
414 remoting.HostNativeMessaging.prototype.getPinHash =
415 function(hostId, pin, onDone, onError) {
416 this.postMessage_({
417 type: 'getPinHash',
418 hostId: hostId,
419 pin: pin
420 }, onDone, onError);
424 * Generates new key pair to use for the host. The specified callback is called
425 * when the key is generated. The key is returned in format understood by the
426 * host (PublicKeyInfo structure encoded with ASN.1 DER, and then BASE64).
428 * @param {function(string, string):void} onDone Callback.
429 * @param {function(remoting.Error):void} onError The callback to be triggered
430 * on error.
431 * @return {void} Nothing.
433 remoting.HostNativeMessaging.prototype.generateKeyPair =
434 function(onDone, onError) {
435 this.postMessage_({type: 'generateKeyPair'}, onDone, onError);
439 * Updates host config with the values specified in |config|. All
440 * fields that are not specified in |config| remain
441 * unchanged. Following parameters cannot be changed using this
442 * function: host_id, xmpp_login. Error is returned if |config|
443 * includes these parameters. Changes take effect before the callback
444 * is called.
446 * @param {Object} config The new config parameters.
447 * @param {function(remoting.HostController.AsyncResult):void} onDone
448 * Callback to be called when finished.
449 * @param {function(remoting.Error):void} onError The callback to be triggered
450 * on error.
451 * @return {void} Nothing.
453 remoting.HostNativeMessaging.prototype.updateDaemonConfig =
454 function(config, onDone, onError) {
455 this.postMessage_({
456 type: 'updateDaemonConfig',
457 config: config
458 }, onDone, onError);
462 * Loads daemon config. The config is passed as a JSON formatted string to the
463 * callback.
465 * @param {function(Object):void} onDone Callback.
466 * @param {function(remoting.Error):void} onError The callback to be triggered
467 * on error.
468 * @return {void} Nothing.
470 remoting.HostNativeMessaging.prototype.getDaemonConfig =
471 function(onDone, onError) {
472 this.postMessage_({type: 'getDaemonConfig'}, onDone, onError);
476 * Retrieves daemon version. The version is returned as a dotted decimal string
477 * of the form major.minor.build.patch.
478 * @return {string} The daemon version, or the empty string if not available.
480 remoting.HostNativeMessaging.prototype.getDaemonVersion = function() {
481 // Return the cached version from the 'hello' exchange.
482 return this.version_;
486 * Get the user's consent to crash reporting. The consent flags are passed to
487 * the callback as booleans: supported, allowed, set-by-policy.
489 * @param {function(boolean, boolean, boolean):void} onDone Callback.
490 * @param {function(remoting.Error):void} onError The callback to be triggered
491 * on error.
492 * @return {void} Nothing.
494 remoting.HostNativeMessaging.prototype.getUsageStatsConsent =
495 function(onDone, onError) {
496 this.postMessage_({type: 'getUsageStatsConsent'}, onDone, onError);
500 * Starts the daemon process with the specified configuration.
502 * @param {Object} config Host configuration.
503 * @param {boolean} consent Consent to report crash dumps.
504 * @param {function(remoting.HostController.AsyncResult):void} onDone
505 * Callback.
506 * @param {function(remoting.Error):void} onError The callback to be triggered
507 * on error.
508 * @return {void} Nothing.
510 remoting.HostNativeMessaging.prototype.startDaemon =
511 function(config, consent, onDone, onError) {
512 this.postMessage_({
513 type: 'startDaemon',
514 config: config,
515 consent: consent
516 }, onDone, onError);
520 * Stops the daemon process.
522 * @param {function(remoting.HostController.AsyncResult):void} onDone
523 * Callback.
524 * @param {function(remoting.Error):void} onError The callback to be triggered
525 * on error.
526 * @return {void} Nothing.
528 remoting.HostNativeMessaging.prototype.stopDaemon =
529 function(onDone, onError) {
530 this.postMessage_({type: 'stopDaemon'}, onDone, onError);
534 * Gets the installed/running state of the Host process.
536 * @param {function(remoting.HostController.State):void} onDone Callback.
537 * @param {function(remoting.Error):void} onError The callback to be triggered
538 * on error.
539 * @return {void} Nothing.
541 remoting.HostNativeMessaging.prototype.getDaemonState =
542 function(onDone, onError) {
543 this.postMessage_({type: 'getDaemonState'}, onDone, onError);
547 * Retrieves the list of paired clients.
549 * @param {function(Array.<remoting.PairedClient>):void} onDone Callback to be
550 * called with the result.
551 * @param {function(remoting.Error):void} onError Callback to be triggered
552 * on error.
554 remoting.HostNativeMessaging.prototype.getPairedClients =
555 function(onDone, onError) {
556 this.postMessage_({type: 'getPairedClients'}, onDone, onError);
560 * Clears all paired clients from the registry.
562 * @param {function(boolean):void} onDone Callback to be called when finished.
563 * @param {function(remoting.Error):void} onError Callback to be triggered
564 * on error.
566 remoting.HostNativeMessaging.prototype.clearPairedClients =
567 function(onDone, onError) {
568 this.postMessage_({type: 'clearPairedClients'}, onDone, onError);
572 * Deletes a paired client referenced by client id.
574 * @param {string} client Client to delete.
575 * @param {function(boolean):void} onDone Callback to be called when finished.
576 * @param {function(remoting.Error):void} onError Callback to be triggered
577 * on error.
579 remoting.HostNativeMessaging.prototype.deletePairedClient =
580 function(client, onDone, onError) {
581 this.postMessage_({
582 type: 'deletePairedClient',
583 clientId: client
584 }, onDone, onError);
588 * Gets the API keys to obtain/use service account credentials.
590 * @param {function(string):void} onDone Callback.
591 * @param {function(remoting.Error):void} onError The callback to be triggered
592 * on error.
593 * @return {void} Nothing.
595 remoting.HostNativeMessaging.prototype.getHostClientId =
596 function(onDone, onError) {
597 this.postMessage_({type: 'getHostClientId'}, onDone, onError);
602 * @param {string} authorizationCode OAuth authorization code.
603 * @param {function(string, string):void} onDone Callback.
604 * @param {function(remoting.Error):void} onError The callback to be triggered
605 * on error.
606 * @return {void} Nothing.
608 remoting.HostNativeMessaging.prototype.getCredentialsFromAuthCode =
609 function(authorizationCode, onDone, onError) {
610 this.postMessage_({
611 type: 'getCredentialsFromAuthCode',
612 authorizationCode: authorizationCode
613 }, onDone, onError);