1 // Copyright (c) 2012 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.
7 * Class that wraps low-level details of interacting with the client plugin.
9 * This abstracts a <embed> element and controls the plugin which does
10 * the actual remoting work. It also handles differences between
11 * client plugins versions when it is necessary.
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
20 * @param {Element} container The container for the embed element.
21 * @param {function(string, string):boolean} onExtensionMessage The handler for
22 * protocol extension messages. Returns true if a message is recognized;
26 remoting.ClientPlugin = function(container, onExtensionMessage) {
27 this.plugin_ = remoting.ClientPlugin.createPluginElement_();
28 this.plugin_.id = 'session-client-plugin';
29 container.appendChild(this.plugin_);
31 this.onExtensionMessage_ = onExtensionMessage;
33 this.desktopWidth = 0;
34 this.desktopHeight = 0;
35 this.desktopXDpi = 96;
36 this.desktopYDpi = 96;
38 /** @param {string} iq The Iq stanza received from the host. */
39 this.onOutgoingIqHandler = function (iq) {};
40 /** @param {string} message Log message. */
41 this.onDebugMessageHandler = function (message) {};
43 * @param {number} state The connection state.
44 * @param {number} error The error code, if any.
46 this.onConnectionStatusUpdateHandler = function(state, error) {};
47 /** @param {boolean} ready Connection ready state. */
48 this.onConnectionReadyHandler = function(ready) {};
51 * @param {string} tokenUrl Token-request URL, received from the host.
52 * @param {string} hostPublicKey Public key for the host.
53 * @param {string} scope OAuth scope to request the token for.
55 this.fetchThirdPartyTokenHandler = function(
56 tokenUrl, hostPublicKey, scope) {};
57 this.onDesktopSizeUpdateHandler = function () {};
58 /** @param {!Array.<string>} capabilities The negotiated capabilities. */
59 this.onSetCapabilitiesHandler = function (capabilities) {};
60 this.fetchPinHandler = function (supportsPairing) {};
61 /** @param {string} data Remote gnubbyd data. */
62 this.onGnubbyAuthHandler = function(data) {};
65 * @param {number} hotspotX
66 * @param {number} hotspotY
68 this.updateMouseCursorImage = function(url, hotspotX, hotspotY) {};
70 /** @param {string} data Remote cast extension message. */
71 this.onCastExtensionHandler = function(data) {};
73 /** @type {remoting.MediaSourceRenderer} */
74 this.mediaSourceRenderer_ = null;
77 this.pluginApiVersion_ = -1;
78 /** @type {Array.<string>} */
79 this.pluginApiFeatures_ = [];
81 this.pluginApiMinVersion_ = -1;
82 /** @type {!Array.<string>} */
83 this.capabilities_ = [];
84 /** @type {boolean} */
85 this.helloReceived_ = false;
86 /** @type {function(boolean)|null} */
87 this.onInitializedCallback_ = null;
88 /** @type {function(string, string):void} */
89 this.onPairingComplete_ = function(clientId, sharedSecret) {};
90 /** @type {remoting.ClientSession.PerfStats} */
91 this.perfStats_ = new remoting.ClientSession.PerfStats();
93 /** @type {remoting.ClientPlugin} */
95 /** @param {Event} event Message event from the plugin. */
96 this.plugin_.addEventListener('message', function(event) {
97 that.handleMessage_(event.data);
100 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') {
101 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
106 * Creates plugin element without adding it to a container.
108 * @return {remoting.ViewerPlugin} Plugin element
110 remoting.ClientPlugin.createPluginElement_ = function() {
111 var plugin = /** @type {remoting.ViewerPlugin} */
112 document.createElement('embed');
113 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'pnacl') {
114 plugin.src = 'remoting_client_pnacl.nmf';
115 plugin.type = 'application/x-pnacl';
116 } else if (remoting.settings.CLIENT_PLUGIN_TYPE == 'nacl') {
117 plugin.src = 'remoting_client_nacl.nmf';
118 plugin.type = 'application/x-nacl';
120 plugin.src = 'about://none';
121 plugin.type = 'application/vnd.chromium.remoting-viewer';
125 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
130 * Preloads the plugin to make instantiation faster when the user tries
133 remoting.ClientPlugin.preload = function() {
134 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') {
138 var plugin = remoting.ClientPlugin.createPluginElement_();
139 plugin.addEventListener(
140 'loadend', function() { document.body.removeChild(plugin); }, false);
141 document.body.appendChild(plugin);
145 * Set of features for which hasFeature() can be used to test.
149 remoting.ClientPlugin.Feature = {
150 INJECT_KEY_EVENT: 'injectKeyEvent',
151 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution',
152 ASYNC_PIN: 'asyncPin',
153 PAUSE_VIDEO: 'pauseVideo',
154 PAUSE_AUDIO: 'pauseAudio',
155 REMAP_KEY: 'remapKey',
156 SEND_CLIPBOARD_ITEM: 'sendClipboardItem',
157 THIRD_PARTY_AUTH: 'thirdPartyAuth',
159 PINLESS_AUTH: 'pinlessAuth',
160 EXTENSION_MESSAGE: 'extensionMessage',
161 MEDIA_SOURCE_RENDERING: 'mediaSourceRendering',
162 VIDEO_CONTROL: 'videoControl'
166 * Chromoting session API version (for this javascript).
167 * This is compared with the plugin API version to verify that they are
173 remoting.ClientPlugin.prototype.API_VERSION_ = 6;
176 * The oldest API version that we support.
177 * This will differ from the |API_VERSION_| if we maintain backward
178 * compatibility with older API versions.
183 remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5;
186 * @param {string|{method:string, data:Object.<string,*>}}
187 * rawMessage Message from the plugin.
190 remoting.ClientPlugin.prototype.handleMessage_ = function(rawMessage) {
192 /** @type {{method:string, data:Object.<string,*>}} */
193 ((typeof(rawMessage) == 'string') ? jsonParseSafe(rawMessage)
195 if (!message || !('method' in message) || !('data' in message)) {
196 console.error('Received invalid message from the plugin:', rawMessage);
201 this.handleMessageMethod_(message);
203 console.error(/** @type {*} */ (e));
208 * @param {{method:string, data:Object.<string,*>}}
209 * message Parsed message from the plugin.
212 remoting.ClientPlugin.prototype.handleMessageMethod_ = function(message) {
214 * Splits a string into a list of words delimited by spaces.
215 * @param {string} str String that should be split.
216 * @return {!Array.<string>} List of words.
218 var tokenize = function(str) {
219 /** @type {Array.<string>} */
220 var tokens = str.match(/\S+/g);
221 return tokens ? tokens : [];
224 if (message.method == 'hello') {
225 // Resize in case we had to enlarge it to support click-to-play.
226 this.hidePluginForClickToPlay_();
227 this.pluginApiVersion_ = getNumberAttr(message.data, 'apiVersion');
228 this.pluginApiMinVersion_ = getNumberAttr(message.data, 'apiMinVersion');
230 if (this.pluginApiVersion_ >= 7) {
231 this.pluginApiFeatures_ =
232 tokenize(getStringAttr(message.data, 'apiFeatures'));
234 // Negotiate capabilities.
236 /** @type {!Array.<string>} */
237 var requestedCapabilities = [];
238 if ('requestedCapabilities' in message.data) {
239 requestedCapabilities =
240 tokenize(getStringAttr(message.data, 'requestedCapabilities'));
243 /** @type {!Array.<string>} */
244 var supportedCapabilities = [];
245 if ('supportedCapabilities' in message.data) {
246 supportedCapabilities =
247 tokenize(getStringAttr(message.data, 'supportedCapabilities'));
250 // At the moment the webapp does not recognize any of
251 // 'requestedCapabilities' capabilities (so they all should be disabled)
252 // and do not care about any of 'supportedCapabilities' capabilities (so
253 // they all can be enabled).
254 this.capabilities_ = supportedCapabilities;
256 // Let the host know that the webapp can be requested to always send
257 // the client's dimensions.
258 this.capabilities_.push(
259 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION);
261 // Let the host know that we're interested in knowing whether or not
262 // it rate-limits desktop-resize requests.
263 this.capabilities_.push(
264 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS);
266 // Let the host know that we can use the video framerecording extension.
267 this.capabilities_.push(
268 remoting.ClientSession.Capability.VIDEO_RECORDER);
270 // Let the host know that we can support casting of the screen.
271 // TODO(aiguha): Add this capability based on a gyp/command-line flag,
272 // rather than by default.
273 this.capabilities_.push(
274 remoting.ClientSession.Capability.CAST);
276 } else if (this.pluginApiVersion_ >= 6) {
277 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
279 this.pluginApiFeatures_ = ['highQualityScaling'];
281 this.helloReceived_ = true;
282 if (this.onInitializedCallback_ != null) {
283 this.onInitializedCallback_(true);
284 this.onInitializedCallback_ = null;
287 } else if (message.method == 'sendOutgoingIq') {
288 this.onOutgoingIqHandler(getStringAttr(message.data, 'iq'));
290 } else if (message.method == 'logDebugMessage') {
291 this.onDebugMessageHandler(getStringAttr(message.data, 'message'));
293 } else if (message.method == 'onConnectionStatus') {
294 var state = remoting.ClientSession.State.fromString(
295 getStringAttr(message.data, 'state'))
296 var error = remoting.ClientSession.ConnectionError.fromString(
297 getStringAttr(message.data, 'error'));
298 this.onConnectionStatusUpdateHandler(state, error);
300 } else if (message.method == 'onDesktopSize') {
301 this.desktopWidth = getNumberAttr(message.data, 'width');
302 this.desktopHeight = getNumberAttr(message.data, 'height');
303 this.desktopXDpi = getNumberAttr(message.data, 'x_dpi', 96);
304 this.desktopYDpi = getNumberAttr(message.data, 'y_dpi', 96);
305 this.onDesktopSizeUpdateHandler();
307 } else if (message.method == 'onPerfStats') {
308 // Return value is ignored. These calls will throw an error if the value
310 getNumberAttr(message.data, 'videoBandwidth');
311 getNumberAttr(message.data, 'videoFrameRate');
312 getNumberAttr(message.data, 'captureLatency');
313 getNumberAttr(message.data, 'encodeLatency');
314 getNumberAttr(message.data, 'decodeLatency');
315 getNumberAttr(message.data, 'renderLatency');
316 getNumberAttr(message.data, 'roundtripLatency');
318 /** @type {remoting.ClientSession.PerfStats} */ message.data;
320 } else if (message.method == 'injectClipboardItem') {
321 var mimetype = getStringAttr(message.data, 'mimeType');
322 var item = getStringAttr(message.data, 'item');
323 if (remoting.clipboard) {
324 remoting.clipboard.fromHost(mimetype, item);
327 } else if (message.method == 'onFirstFrameReceived') {
328 if (remoting.clientSession) {
329 remoting.clientSession.onFirstFrameReceived();
332 } else if (message.method == 'onConnectionReady') {
333 var ready = getBooleanAttr(message.data, 'ready');
334 this.onConnectionReadyHandler(ready);
336 } else if (message.method == 'fetchPin') {
337 // The pairingSupported value in the dictionary indicates whether both
338 // client and host support pairing. If the client doesn't support pairing,
339 // then the value won't be there at all, so give it a default of false.
340 var pairingSupported = getBooleanAttr(message.data, 'pairingSupported',
342 this.fetchPinHandler(pairingSupported);
344 } else if (message.method == 'setCapabilities') {
345 /** @type {!Array.<string>} */
346 var capabilities = tokenize(getStringAttr(message.data, 'capabilities'));
347 this.onSetCapabilitiesHandler(capabilities);
349 } else if (message.method == 'fetchThirdPartyToken') {
350 var tokenUrl = getStringAttr(message.data, 'tokenUrl');
351 var hostPublicKey = getStringAttr(message.data, 'hostPublicKey');
352 var scope = getStringAttr(message.data, 'scope');
353 this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope);
355 } else if (message.method == 'pairingResponse') {
356 var clientId = getStringAttr(message.data, 'clientId');
357 var sharedSecret = getStringAttr(message.data, 'sharedSecret');
358 this.onPairingComplete_(clientId, sharedSecret);
360 } else if (message.method == 'extensionMessage') {
361 var extMsgType = getStringAttr(message.data, 'type');
362 var extMsgData = getStringAttr(message.data, 'data');
363 switch (extMsgType) {
365 this.onGnubbyAuthHandler(extMsgData);
367 case 'test-echo-reply':
368 console.log('Got echo reply: ' + extMsgData);
371 this.onCastExtensionHandler(extMsgData);
374 if (!this.onExtensionMessage_(extMsgType, extMsgData)) {
375 console.log('Unexpected message received: ' +
376 extMsgType + ': ' + extMsgData);
380 } else if (message.method == 'mediaSourceReset') {
381 if (!this.mediaSourceRenderer_) {
382 console.error('Unexpected mediaSourceReset.');
385 this.mediaSourceRenderer_.reset(getStringAttr(message.data, 'format'))
387 } else if (message.method == 'mediaSourceData') {
388 if (!(message.data['buffer'] instanceof ArrayBuffer)) {
389 console.error('Invalid mediaSourceData message:', message.data);
392 if (!this.mediaSourceRenderer_) {
393 console.error('Unexpected mediaSourceData.');
396 // keyframe flag may be absent from the message.
397 var keyframe = !!message.data['keyframe'];
398 this.mediaSourceRenderer_.onIncomingData(
399 (/** @type {ArrayBuffer} */ message.data['buffer']), keyframe);
401 } else if (message.method == 'unsetCursorShape') {
402 this.updateMouseCursorImage('', 0, 0);
404 } else if (message.method == 'setCursorShape') {
405 var width = getNumberAttr(message.data, 'width');
406 var height = getNumberAttr(message.data, 'height');
407 var hotspotX = getNumberAttr(message.data, 'hotspotX');
408 var hotspotY = getNumberAttr(message.data, 'hotspotY');
409 var srcArrayBuffer = getObjectAttr(message.data, 'data');
412 /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
413 canvas.width = width;
414 canvas.height = height;
417 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
418 var imageData = context.getImageData(0, 0, width, height);
419 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer);
420 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer));
421 var dest = imageData.data;
422 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) {
423 dest[i] = src[i + 2];
424 dest[i + 1] = src[i + 1];
425 dest[i + 2] = src[i];
426 dest[i + 3] = src[i + 3];
429 context.putImageData(imageData, 0, 0);
430 this.updateMouseCursorImage(canvas.toDataURL(), hotspotX, hotspotY);
435 * Deletes the plugin.
437 remoting.ClientPlugin.prototype.cleanup = function() {
439 this.plugin_.parentNode.removeChild(this.plugin_);
445 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
447 remoting.ClientPlugin.prototype.element = function() {
452 * @param {function(boolean): void} onDone
454 remoting.ClientPlugin.prototype.initialize = function(onDone) {
455 if (this.helloReceived_) {
458 this.onInitializedCallback_ = onDone;
463 * @return {boolean} True if the plugin and web-app versions are compatible.
465 remoting.ClientPlugin.prototype.isSupportedVersion = function() {
466 if (!this.helloReceived_) {
468 "isSupportedVersion() is called before the plugin is initialized.");
471 return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
472 this.pluginApiVersion_ >= this.API_MIN_VERSION_;
476 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
477 * @return {boolean} True if the plugin supports the named feature.
479 remoting.ClientPlugin.prototype.hasFeature = function(feature) {
480 if (!this.helloReceived_) {
482 "hasFeature() is called before the plugin is initialized.");
485 return this.pluginApiFeatures_.indexOf(feature) > -1;
489 * @return {boolean} True if the plugin supports the injectKeyEvent API.
491 remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() {
492 return this.pluginApiVersion_ >= 6;
496 * @param {string} iq Incoming IQ stanza.
498 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {
499 if (this.plugin_ && this.plugin_.postMessage) {
500 this.plugin_.postMessage(JSON.stringify(
501 { method: 'incomingIq', data: { iq: iq } }));
503 // plugin.onIq may not be set after the plugin has been shut
504 // down. Particularly this happens when we receive response to
505 // session-terminate stanza.
506 console.warn('plugin.onIq is not set so dropping incoming message.');
511 * @param {string} hostJid The jid of the host to connect to.
512 * @param {string} hostPublicKey The base64 encoded version of the host's
514 * @param {string} localJid Local jid.
515 * @param {string} sharedSecret The access code for IT2Me or the PIN
517 * @param {string} authenticationMethods Comma-separated list of
518 * authentication methods the client should attempt to use.
519 * @param {string} authenticationTag A host-specific tag to mix into
520 * authentication hashes.
521 * @param {string} clientPairingId For paired Me2Me connections, the
522 * pairing id for this client, as issued by the host.
523 * @param {string} clientPairedSecret For paired Me2Me connections, the
524 * paired secret for this client, as issued by the host.
526 remoting.ClientPlugin.prototype.connect = function(
527 hostJid, hostPublicKey, localJid, sharedSecret,
528 authenticationMethods, authenticationTag,
529 clientPairingId, clientPairedSecret) {
531 if (remoting.platformIsMac()) {
533 } else if (remoting.platformIsChromeOS()) {
536 this.plugin_.postMessage(JSON.stringify(
537 { method: 'delegateLargeCursors', data: {} }));
538 this.plugin_.postMessage(JSON.stringify(
539 { method: 'connect', data: {
541 hostPublicKey: hostPublicKey,
543 sharedSecret: sharedSecret,
544 authenticationMethods: authenticationMethods,
545 authenticationTag: authenticationTag,
546 capabilities: this.capabilities_.join(" "),
547 clientPairingId: clientPairingId,
548 clientPairedSecret: clientPairedSecret,
555 * Release all currently pressed keys.
557 remoting.ClientPlugin.prototype.releaseAllKeys = function() {
558 this.plugin_.postMessage(JSON.stringify(
559 { method: 'releaseAllKeys', data: {} }));
563 * Send a key event to the host.
565 * @param {number} usbKeycode The USB-style code of the key to inject.
566 * @param {boolean} pressed True to inject a key press, False for a release.
568 remoting.ClientPlugin.prototype.injectKeyEvent =
569 function(usbKeycode, pressed) {
570 this.plugin_.postMessage(JSON.stringify(
571 { method: 'injectKeyEvent', data: {
572 'usbKeycode': usbKeycode,
578 * Remap one USB keycode to another in all subsequent key events.
580 * @param {number} fromKeycode The USB-style code of the key to remap.
581 * @param {number} toKeycode The USB-style code to remap the key to.
583 remoting.ClientPlugin.prototype.remapKey =
584 function(fromKeycode, toKeycode) {
585 this.plugin_.postMessage(JSON.stringify(
586 { method: 'remapKey', data: {
587 'fromKeycode': fromKeycode,
588 'toKeycode': toKeycode}
593 * Enable/disable redirection of the specified key to the web-app.
595 * @param {number} keycode The USB-style code of the key.
596 * @param {Boolean} trap True to enable trapping, False to disable.
598 remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) {
599 this.plugin_.postMessage(JSON.stringify(
600 { method: 'trapKey', data: {
607 * Returns an associative array with a set of stats for this connecton.
609 * @return {remoting.ClientSession.PerfStats} The connection statistics.
611 remoting.ClientPlugin.prototype.getPerfStats = function() {
612 return this.perfStats_;
616 * Sends a clipboard item to the host.
618 * @param {string} mimeType The MIME type of the clipboard item.
619 * @param {string} item The clipboard item.
621 remoting.ClientPlugin.prototype.sendClipboardItem =
622 function(mimeType, item) {
623 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
625 this.plugin_.postMessage(JSON.stringify(
626 { method: 'sendClipboardItem',
627 data: { mimeType: mimeType, item: item }}));
631 * Notifies the host that the client has the specified size and pixel density.
633 * @param {number} width The available client width in DIPs.
634 * @param {number} height The available client height in DIPs.
635 * @param {number} device_scale The number of device pixels per DIP.
637 remoting.ClientPlugin.prototype.notifyClientResolution =
638 function(width, height, device_scale) {
639 if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) {
640 var dpi = Math.floor(device_scale * 96);
641 this.plugin_.postMessage(JSON.stringify(
642 { method: 'notifyClientResolution',
643 data: { width: Math.floor(width * device_scale),
644 height: Math.floor(height * device_scale),
645 x_dpi: dpi, y_dpi: dpi }}));
650 * Requests that the host pause or resume sending video updates.
652 * @param {boolean} pause True to suspend video updates, false otherwise.
654 remoting.ClientPlugin.prototype.pauseVideo =
656 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
657 this.plugin_.postMessage(JSON.stringify(
658 { method: 'videoControl', data: { pause: pause }}));
659 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) {
660 this.plugin_.postMessage(JSON.stringify(
661 { method: 'pauseVideo', data: { pause: pause }}));
666 * Requests that the host pause or resume sending audio updates.
668 * @param {boolean} pause True to suspend audio updates, false otherwise.
670 remoting.ClientPlugin.prototype.pauseAudio =
672 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) {
675 this.plugin_.postMessage(JSON.stringify(
676 { method: 'pauseAudio', data: { pause: pause }}));
680 * Requests that the host configure the video codec for lossless encode.
682 * @param {boolean} wantLossless True to request lossless encoding.
684 remoting.ClientPlugin.prototype.setLosslessEncode =
685 function(wantLossless) {
686 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
689 this.plugin_.postMessage(JSON.stringify(
690 { method: 'videoControl', data: { losslessEncode: wantLossless }}));
694 * Requests that the host configure the video codec for lossless color.
696 * @param {boolean} wantLossless True to request lossless color.
698 remoting.ClientPlugin.prototype.setLosslessColor =
699 function(wantLossless) {
700 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) {
703 this.plugin_.postMessage(JSON.stringify(
704 { method: 'videoControl', data: { losslessColor: wantLossless }}));
708 * Called when a PIN is obtained from the user.
710 * @param {string} pin The PIN.
712 remoting.ClientPlugin.prototype.onPinFetched =
714 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
717 this.plugin_.postMessage(JSON.stringify(
718 { method: 'onPinFetched', data: { pin: pin }}));
722 * Tells the plugin to ask for the PIN asynchronously.
724 remoting.ClientPlugin.prototype.useAsyncPinDialog =
726 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
729 this.plugin_.postMessage(JSON.stringify(
730 { method: 'useAsyncPinDialog', data: {} }));
734 * Sets the third party authentication token and shared secret.
736 * @param {string} token The token received from the token URL.
737 * @param {string} sharedSecret Shared secret received from the token URL.
739 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function(
740 token, sharedSecret) {
741 this.plugin_.postMessage(JSON.stringify(
742 { method: 'onThirdPartyTokenFetched',
743 data: { token: token, sharedSecret: sharedSecret}}));
747 * Request pairing with the host for PIN-less authentication.
749 * @param {string} clientName The human-readable name of the client.
750 * @param {function(string, string):void} onDone, Callback to receive the
751 * client id and shared secret when they are available.
753 remoting.ClientPlugin.prototype.requestPairing =
754 function(clientName, onDone) {
755 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) {
758 this.onPairingComplete_ = onDone;
759 this.plugin_.postMessage(JSON.stringify(
760 { method: 'requestPairing', data: { clientName: clientName } }));
764 * Send an extension message to the host.
766 * @param {string} type The message type.
767 * @param {string} message The message payload.
769 remoting.ClientPlugin.prototype.sendClientMessage =
770 function(type, message) {
771 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
774 this.plugin_.postMessage(JSON.stringify(
775 { method: 'extensionMessage',
776 data: { type: type, data: message } }));
781 * Request MediaStream-based rendering.
783 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
785 remoting.ClientPlugin.prototype.enableMediaSourceRendering =
786 function(mediaSourceRenderer) {
787 if (!this.hasFeature(remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) {
790 this.mediaSourceRenderer_ = mediaSourceRenderer;
791 this.plugin_.postMessage(JSON.stringify(
792 { method: 'enableMediaSourceRendering', data: {} }));
796 * If we haven't yet received a "hello" message from the plugin, change its
797 * size so that the user can confirm it if click-to-play is enabled, or can
798 * see the "this plugin is disabled" message if it is actually disabled.
801 remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() {
802 if (!this.helloReceived_) {
805 this.plugin_.style.width = width + 'px';
806 this.plugin_.style.height = height + 'px';
807 // Center the plugin just underneath the "Connnecting..." dialog.
808 var dialog = document.getElementById('client-dialog');
809 var dialogRect = dialog.getBoundingClientRect();
810 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px';
811 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px';
812 this.plugin_.style.position = 'fixed';
817 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
820 remoting.ClientPlugin.prototype.hidePluginForClickToPlay_ = function() {
821 this.plugin_.style.width = '';
822 this.plugin_.style.height = '';
823 this.plugin_.style.top = '';
824 this.plugin_.style.left = '';
825 this.plugin_.style.position = '';