| Index: remoting/webapp/client_plugin.js
|
| diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js
|
| index 8cd8f1cc33e9194a101559031f4e688fd9a56504..cc621f2cf35ba5206e4ec76a790f718e36560394 100644
|
| --- a/remoting/webapp/client_plugin.js
|
| +++ b/remoting/webapp/client_plugin.js
|
| @@ -2,54 +2,80 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +/**
|
| + * @fileoverview
|
| + * Class that wraps low-level details of interacting with the client plugin.
|
| + *
|
| + * This abstracts a <embed> element and controls the plugin which does
|
| + * the actual remoting work. It also handles differences between
|
| + * client plugins versions when it is necessary.
|
| + */
|
| +
|
| +'use strict';
|
| +
|
| /** @suppress {duplicate} */
|
| var remoting = remoting || {};
|
|
|
| /**
|
| - * Interface used for ClientPlugin objects.
|
| - * @interface
|
| + * @param {remoting.ViewerPlugin} plugin The plugin embed element.
|
| + * @constructor
|
| */
|
| -remoting.ClientPlugin = function() {
|
| -};
|
| +remoting.ClientPlugin = function(plugin) {
|
| + this.plugin = plugin;
|
|
|
| -/** @type {number} Desktop width */
|
| -remoting.ClientPlugin.prototype.desktopWidth;
|
| -/** @type {number} Desktop height */
|
| -remoting.ClientPlugin.prototype.desktopHeight;
|
| -/** @type {number} Desktop x DPI */
|
| -remoting.ClientPlugin.prototype.desktopXDpi;
|
| -/** @type {number} Desktop y DPI */
|
| -remoting.ClientPlugin.prototype.desktopYDpi;
|
| + this.desktopWidth = 0;
|
| + this.desktopHeight = 0;
|
| + this.desktopXDpi = 96;
|
| + this.desktopYDpi = 96;
|
|
|
| -/** @type {function(string): void} Outgoing signaling message callback. */
|
| -remoting.ClientPlugin.prototype.onOutgoingIqHandler;
|
| -/** @type {function(string): void} Debug messages callback. */
|
| -remoting.ClientPlugin.prototype.onDebugMessageHandler;
|
| -/** @type {function(number, number): void} State change callback. */
|
| -remoting.ClientPlugin.prototype.onConnectionStatusUpdateHandler;
|
| -/** @type {function(boolean): void} Connection ready state callback. */
|
| -remoting.ClientPlugin.prototype.onConnectionReadyHandler;
|
| -/** @type {function(): void} Desktop size change callback. */
|
| -remoting.ClientPlugin.prototype.onDesktopSizeUpdateHandler;
|
| -/** @type {function(!Array.<string>): void} Capabilities negotiated callback. */
|
| -remoting.ClientPlugin.prototype.onSetCapabilitiesHandler;
|
| -/** @type {function(boolean): void} Request a PIN from the user. */
|
| -remoting.ClientPlugin.prototype.fetchPinHandler;
|
| + /** @param {string} iq The Iq stanza received from the host. */
|
| + this.onOutgoingIqHandler = function (iq) {};
|
| + /** @param {string} message Log message. */
|
| + this.onDebugMessageHandler = function (message) {};
|
| + /**
|
| + * @param {number} state The connection state.
|
| + * @param {number} error The error code, if any.
|
| + */
|
| + this.onConnectionStatusUpdateHandler = function(state, error) {};
|
| + /** @param {boolean} ready Connection ready state. */
|
| + this.onConnectionReadyHandler = function(ready) {};
|
| + /**
|
| + * @param {string} tokenUrl Token-request URL, received from the host.
|
| + * @param {string} hostPublicKey Public key for the host.
|
| + * @param {string} scope OAuth scope to request the token for.
|
| + */
|
| + this.fetchThirdPartyTokenHandler = function(
|
| + tokenUrl, hostPublicKey, scope) {};
|
| + this.onDesktopSizeUpdateHandler = function () {};
|
| + /** @param {!Array.<string>} capabilities The negotiated capabilities. */
|
| + this.onSetCapabilitiesHandler = function (capabilities) {};
|
| + this.fetchPinHandler = function (supportsPairing) {};
|
|
|
| -/**
|
| - * Initializes the plugin asynchronously and calls specified function
|
| - * when done.
|
| - *
|
| - * @param {function(boolean): void} onDone Function to be called when
|
| - * the plugin is initialized. Parameter is set to true when the plugin
|
| - * is loaded successfully.
|
| - */
|
| -remoting.ClientPlugin.prototype.initialize = function(onDone) {};
|
| + /** @type {number} */
|
| + this.pluginApiVersion_ = -1;
|
| + /** @type {Array.<string>} */
|
| + this.pluginApiFeatures_ = [];
|
| + /** @type {number} */
|
| + this.pluginApiMinVersion_ = -1;
|
| + /** @type {!Array.<string>} */
|
| + this.capabilities_ = [];
|
| + /** @type {boolean} */
|
| + this.helloReceived_ = false;
|
| + /** @type {function(boolean)|null} */
|
| + this.onInitializedCallback_ = null;
|
| + /** @type {function(string, string):void} */
|
| + this.onPairingComplete_ = function(clientId, sharedSecret) {};
|
| + /** @type {remoting.ClientSession.PerfStats} */
|
| + this.perfStats_ = new remoting.ClientSession.PerfStats();
|
|
|
| -/**
|
| - * @return {boolean} True if the plugin and web-app versions are compatible.
|
| - */
|
| -remoting.ClientPlugin.prototype.isSupportedVersion = function() {};
|
| + /** @type {remoting.ClientPlugin} */
|
| + var that = this;
|
| + /** @param {Event} event Message event from the plugin. */
|
| + this.plugin.addEventListener('message', function(event) {
|
| + that.handleMessage_(event.data);
|
| + }, false);
|
| + window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500);
|
| +};
|
|
|
| /**
|
| * Set of features for which hasFeature() can be used to test.
|
| @@ -71,26 +97,329 @@ remoting.ClientPlugin.Feature = {
|
| };
|
|
|
| /**
|
| - * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
|
| - * @return {boolean} True if the plugin supports the named feature.
|
| + * Chromoting session API version (for this javascript).
|
| + * This is compared with the plugin API version to verify that they are
|
| + * compatible.
|
| + *
|
| + * @const
|
| + * @private
|
| */
|
| -remoting.ClientPlugin.prototype.hasFeature = function(feature) {};
|
| +remoting.ClientPlugin.prototype.API_VERSION_ = 6;
|
|
|
| /**
|
| - * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
|
| + * The oldest API version that we support.
|
| + * This will differ from the |API_VERSION_| if we maintain backward
|
| + * compatibility with older API versions.
|
| + *
|
| + * @const
|
| + * @private
|
| */
|
| -remoting.ClientPlugin.prototype.element = function() {};
|
| +remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5;
|
| +
|
| +/**
|
| + * @param {string} messageStr Message from the plugin.
|
| + */
|
| +remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) {
|
| + var message = /** @type {{method:string, data:Object.<string,string>}} */
|
| + jsonParseSafe(messageStr);
|
| +
|
| + if (!message || !('method' in message) || !('data' in message)) {
|
| + console.error('Received invalid message from the plugin: ' + messageStr);
|
| + return;
|
| + }
|
| +
|
| + /**
|
| + * Splits a string into a list of words delimited by spaces.
|
| + * @param {string} str String that should be split.
|
| + * @return {!Array.<string>} List of words.
|
| + */
|
| + var tokenize = function(str) {
|
| + /** @type {Array.<string>} */
|
| + var tokens = str.match(/\S+/g);
|
| + return tokens ? tokens : [];
|
| + };
|
| +
|
| + if (message.method == 'hello') {
|
| + // Reset the size in case we had to enlarge it to support click-to-play.
|
| + this.plugin.width = 0;
|
| + this.plugin.height = 0;
|
| + if (typeof message.data['apiVersion'] != 'number' ||
|
| + typeof message.data['apiMinVersion'] != 'number') {
|
| + console.error('Received invalid hello message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion'];
|
| +
|
| + if (this.pluginApiVersion_ >= 7) {
|
| + if (typeof message.data['apiFeatures'] != 'string') {
|
| + console.error('Received invalid hello message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.pluginApiFeatures_ =
|
| + /** @type {Array.<string>} */ tokenize(message.data['apiFeatures']);
|
| +
|
| + // Negotiate capabilities.
|
| +
|
| + /** @type {!Array.<string>} */
|
| + var requestedCapabilities = [];
|
| + if ('requestedCapabilities' in message.data) {
|
| + if (typeof message.data['requestedCapabilities'] != 'string') {
|
| + console.error('Received invalid hello message: ' + messageStr);
|
| + return;
|
| + }
|
| + requestedCapabilities = tokenize(message.data['requestedCapabilities']);
|
| + }
|
| +
|
| + /** @type {!Array.<string>} */
|
| + var supportedCapabilities = [];
|
| + if ('supportedCapabilities' in message.data) {
|
| + if (typeof message.data['supportedCapabilities'] != 'string') {
|
| + console.error('Received invalid hello message: ' + messageStr);
|
| + return;
|
| + }
|
| + supportedCapabilities = tokenize(message.data['supportedCapabilities']);
|
| + }
|
| +
|
| + // At the moment the webapp does not recognize any of
|
| + // 'requestedCapabilities' capabilities (so they all should be disabled)
|
| + // and do not care about any of 'supportedCapabilities' capabilities (so
|
| + // they all can be enabled).
|
| + this.capabilities_ = supportedCapabilities;
|
| +
|
| + // Let the host know that the webapp can be requested to always send
|
| + // the client's dimensions.
|
| + this.capabilities_.push(
|
| + remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION);
|
| +
|
| + // Let the host know that we're interested in knowing whether or not
|
| + // it rate-limits desktop-resize requests.
|
| + this.capabilities_.push(
|
| + remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS);
|
| + } else if (this.pluginApiVersion_ >= 6) {
|
| + this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
|
| + } else {
|
| + this.pluginApiFeatures_ = ['highQualityScaling'];
|
| + }
|
| + this.pluginApiMinVersion_ =
|
| + /** @type {number} */ message.data['apiMinVersion'];
|
| + this.helloReceived_ = true;
|
| + if (this.onInitializedCallback_ != null) {
|
| + this.onInitializedCallback_(true);
|
| + this.onInitializedCallback_ = null;
|
| + }
|
| + } else if (message.method == 'sendOutgoingIq') {
|
| + if (typeof message.data['iq'] != 'string') {
|
| + console.error('Received invalid sendOutgoingIq message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.onOutgoingIqHandler(message.data['iq']);
|
| + } else if (message.method == 'logDebugMessage') {
|
| + if (typeof message.data['message'] != 'string') {
|
| + console.error('Received invalid logDebugMessage message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.onDebugMessageHandler(message.data['message']);
|
| + } else if (message.method == 'onConnectionStatus') {
|
| + if (typeof message.data['state'] != 'string' ||
|
| + !remoting.ClientSession.State.hasOwnProperty(message.data['state']) ||
|
| + typeof message.data['error'] != 'string') {
|
| + console.error('Received invalid onConnectionState message: ' +
|
| + messageStr);
|
| + return;
|
| + }
|
| +
|
| + /** @type {remoting.ClientSession.State} */
|
| + var state = remoting.ClientSession.State[message.data['state']];
|
| + var error;
|
| + if (remoting.ClientSession.ConnectionError.hasOwnProperty(
|
| + message.data['error'])) {
|
| + error = /** @type {remoting.ClientSession.ConnectionError} */
|
| + remoting.ClientSession.ConnectionError[message.data['error']];
|
| + } else {
|
| + error = remoting.ClientSession.ConnectionError.UNKNOWN;
|
| + }
|
| +
|
| + this.onConnectionStatusUpdateHandler(state, error);
|
| + } else if (message.method == 'onDesktopSize') {
|
| + if (typeof message.data['width'] != 'number' ||
|
| + typeof message.data['height'] != 'number') {
|
| + console.error('Received invalid onDesktopSize message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.desktopWidth = /** @type {number} */ message.data['width'];
|
| + this.desktopHeight = /** @type {number} */ message.data['height'];
|
| + this.desktopXDpi = (typeof message.data['x_dpi'] == 'number') ?
|
| + /** @type {number} */ (message.data['x_dpi']) : 96;
|
| + this.desktopYDpi = (typeof message.data['y_dpi'] == 'number') ?
|
| + /** @type {number} */ (message.data['y_dpi']) : 96;
|
| + this.onDesktopSizeUpdateHandler();
|
| + } else if (message.method == 'onPerfStats') {
|
| + if (typeof message.data['videoBandwidth'] != 'number' ||
|
| + typeof message.data['videoFrameRate'] != 'number' ||
|
| + typeof message.data['captureLatency'] != 'number' ||
|
| + typeof message.data['encodeLatency'] != 'number' ||
|
| + typeof message.data['decodeLatency'] != 'number' ||
|
| + typeof message.data['renderLatency'] != 'number' ||
|
| + typeof message.data['roundtripLatency'] != 'number') {
|
| + console.error('Received incorrect onPerfStats message: ' + messageStr);
|
| + return;
|
| + }
|
| + this.perfStats_ =
|
| + /** @type {remoting.ClientSession.PerfStats} */ message.data;
|
| + } else if (message.method == 'injectClipboardItem') {
|
| + if (typeof message.data['mimeType'] != 'string' ||
|
| + typeof message.data['item'] != 'string') {
|
| + console.error('Received incorrect injectClipboardItem message.');
|
| + return;
|
| + }
|
| + if (remoting.clipboard) {
|
| + remoting.clipboard.fromHost(message.data['mimeType'],
|
| + message.data['item']);
|
| + }
|
| + } else if (message.method == 'onFirstFrameReceived') {
|
| + if (remoting.clientSession) {
|
| + remoting.clientSession.onFirstFrameReceived();
|
| + }
|
| + } else if (message.method == 'onConnectionReady') {
|
| + if (typeof message.data['ready'] != 'boolean') {
|
| + console.error('Received incorrect onConnectionReady message.');
|
| + return;
|
| + }
|
| + var ready = /** @type {boolean} */ message.data['ready'];
|
| + this.onConnectionReadyHandler(ready);
|
| + } else if (message.method == 'fetchPin') {
|
| + // The pairingSupported value in the dictionary indicates whether both
|
| + // client and host support pairing. If the client doesn't support pairing,
|
| + // then the value won't be there at all, so give it a default of false.
|
| + /** @type {boolean} */
|
| + var pairingSupported = false;
|
| + if ('pairingSupported' in message.data) {
|
| + pairingSupported =
|
| + /** @type {boolean} */ message.data['pairingSupported'];
|
| + if (typeof pairingSupported != 'boolean') {
|
| + console.error('Received incorrect fetchPin message.');
|
| + return;
|
| + }
|
| + }
|
| + this.fetchPinHandler(pairingSupported);
|
| + } else if (message.method == 'setCapabilities') {
|
| + if (typeof message.data['capabilities'] != 'string') {
|
| + console.error('Received incorrect setCapabilities message.');
|
| + return;
|
| + }
|
| +
|
| + /** @type {!Array.<string>} */
|
| + var capabilities = tokenize(message.data['capabilities']);
|
| + this.onSetCapabilitiesHandler(capabilities);
|
| + } else if (message.method == 'fetchThirdPartyToken') {
|
| + if (typeof message.data['tokenUrl'] != 'string' ||
|
| + typeof message.data['hostPublicKey'] != 'string' ||
|
| + typeof message.data['scope'] != 'string') {
|
| + console.error('Received incorrect fetchThirdPartyToken message.');
|
| + return;
|
| + }
|
| + var tokenUrl = /** @type {string} */ message.data['tokenUrl'];
|
| + var hostPublicKey =
|
| + /** @type {string} */ message.data['hostPublicKey'];
|
| + var scope = /** @type {string} */ message.data['scope'];
|
| + this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope);
|
| + } else if (message.method == 'pairingResponse') {
|
| + var clientId = /** @type {string} */ message.data['clientId'];
|
| + var sharedSecret = /** @type {string} */ message.data['sharedSecret'];
|
| + if (typeof clientId != 'string' || typeof sharedSecret != 'string') {
|
| + console.error('Received incorrect pairingResponse message.');
|
| + return;
|
| + }
|
| + this.onPairingComplete_(clientId, sharedSecret);
|
| + } else if (message.method == 'extensionMessage') {
|
| + if (typeof(message.data['type']) != 'string' ||
|
| + typeof(message.data['data']) != 'string') {
|
| + console.error('Invalid extension message:', message.data);
|
| + return;
|
| + }
|
| + switch (message.data['type']) {
|
| + case 'test-echo-reply':
|
| + console.log('Got echo reply: ' + message.data['data']);
|
| + break;
|
| + default:
|
| + console.log('Unexpected message received: ' +
|
| + message.data['type'] + ': ' + message.data['data']);
|
| + }
|
| + }
|
| +};
|
|
|
| /**
|
| * Deletes the plugin.
|
| */
|
| -remoting.ClientPlugin.prototype.cleanup = function() {};
|
| +remoting.ClientPlugin.prototype.cleanup = function() {
|
| + this.plugin.parentNode.removeChild(this.plugin);
|
| +};
|
| +
|
| +/**
|
| + * @return {HTMLEmbedElement} HTML element that correspods to the plugin.
|
| + */
|
| +remoting.ClientPlugin.prototype.element = function() {
|
| + return this.plugin;
|
| +};
|
| +
|
| +/**
|
| + * @param {function(boolean): void} onDone
|
| + */
|
| +remoting.ClientPlugin.prototype.initialize = function(onDone) {
|
| + if (this.helloReceived_) {
|
| + onDone(true);
|
| + } else {
|
| + this.onInitializedCallback_ = onDone;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * @return {boolean} True if the plugin and web-app versions are compatible.
|
| + */
|
| +remoting.ClientPlugin.prototype.isSupportedVersion = function() {
|
| + if (!this.helloReceived_) {
|
| + console.error(
|
| + "isSupportedVersion() is called before the plugin is initialized.");
|
| + return false;
|
| + }
|
| + return this.API_VERSION_ >= this.pluginApiMinVersion_ &&
|
| + this.pluginApiVersion_ >= this.API_MIN_VERSION_;
|
| +};
|
| +
|
| +/**
|
| + * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
|
| + * @return {boolean} True if the plugin supports the named feature.
|
| + */
|
| +remoting.ClientPlugin.prototype.hasFeature = function(feature) {
|
| + if (!this.helloReceived_) {
|
| + console.error(
|
| + "hasFeature() is called before the plugin is initialized.");
|
| + return false;
|
| + }
|
| + return this.pluginApiFeatures_.indexOf(feature) > -1;
|
| +};
|
| +
|
| +/**
|
| + * @return {boolean} True if the plugin supports the injectKeyEvent API.
|
| + */
|
| +remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() {
|
| + return this.pluginApiVersion_ >= 6;
|
| +};
|
|
|
| /**
|
| - * Must be called for each incoming stanza received from the host.
|
| * @param {string} iq Incoming IQ stanza.
|
| */
|
| -remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {};
|
| +remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {
|
| + if (this.plugin && this.plugin.postMessage) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'incomingIq', data: { iq: iq } }));
|
| + } else {
|
| + // plugin.onIq may not be set after the plugin has been shut
|
| + // down. Particularly this happens when we receive response to
|
| + // session-terminate stanza.
|
| + console.warn('plugin.onIq is not set so dropping incoming message.');
|
| + }
|
| +};
|
|
|
| /**
|
| * @param {string} hostJid The jid of the host to connect to.
|
| @@ -111,12 +440,29 @@ remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {};
|
| remoting.ClientPlugin.prototype.connect = function(
|
| hostJid, hostPublicKey, localJid, sharedSecret,
|
| authenticationMethods, authenticationTag,
|
| - clientPairingId, clientPairedSecret) {};
|
| + clientPairingId, clientPairedSecret) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'connect', data: {
|
| + hostJid: hostJid,
|
| + hostPublicKey: hostPublicKey,
|
| + localJid: localJid,
|
| + sharedSecret: sharedSecret,
|
| + authenticationMethods: authenticationMethods,
|
| + authenticationTag: authenticationTag,
|
| + capabilities: this.capabilities_.join(" "),
|
| + clientPairingId: clientPairingId,
|
| + clientPairedSecret: clientPairedSecret
|
| + }
|
| + }));
|
| +};
|
|
|
| /**
|
| * Release all currently pressed keys.
|
| */
|
| -remoting.ClientPlugin.prototype.releaseAllKeys = function() {};
|
| +remoting.ClientPlugin.prototype.releaseAllKeys = function() {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'releaseAllKeys', data: {} }));
|
| +};
|
|
|
| /**
|
| * Send a key event to the host.
|
| @@ -125,7 +471,13 @@ remoting.ClientPlugin.prototype.releaseAllKeys = function() {};
|
| * @param {boolean} pressed True to inject a key press, False for a release.
|
| */
|
| remoting.ClientPlugin.prototype.injectKeyEvent =
|
| - function(usbKeycode, pressed) {};
|
| + function(usbKeycode, pressed) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'injectKeyEvent', data: {
|
| + 'usbKeycode': usbKeycode,
|
| + 'pressed': pressed}
|
| + }));
|
| +};
|
|
|
| /**
|
| * Remap one USB keycode to another in all subsequent key events.
|
| @@ -134,7 +486,13 @@ remoting.ClientPlugin.prototype.injectKeyEvent =
|
| * @param {number} toKeycode The USB-style code to remap the key to.
|
| */
|
| remoting.ClientPlugin.prototype.remapKey =
|
| - function(fromKeycode, toKeycode) {};
|
| + function(fromKeycode, toKeycode) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'remapKey', data: {
|
| + 'fromKeycode': fromKeycode,
|
| + 'toKeycode': toKeycode}
|
| + }));
|
| +};
|
|
|
| /**
|
| * Enable/disable redirection of the specified key to the web-app.
|
| @@ -142,14 +500,22 @@ remoting.ClientPlugin.prototype.remapKey =
|
| * @param {number} keycode The USB-style code of the key.
|
| * @param {Boolean} trap True to enable trapping, False to disable.
|
| */
|
| -remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) {};
|
| +remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'trapKey', data: {
|
| + 'keycode': keycode,
|
| + 'trap': trap}
|
| + }));
|
| +};
|
|
|
| /**
|
| - * Returns an associative array with a set of stats for this connection.
|
| + * Returns an associative array with a set of stats for this connecton.
|
| *
|
| * @return {remoting.ClientSession.PerfStats} The connection statistics.
|
| */
|
| -remoting.ClientPlugin.prototype.getPerfStats = function() {};
|
| +remoting.ClientPlugin.prototype.getPerfStats = function() {
|
| + return this.perfStats_;
|
| +};
|
|
|
| /**
|
| * Sends a clipboard item to the host.
|
| @@ -157,7 +523,14 @@ remoting.ClientPlugin.prototype.getPerfStats = function() {};
|
| * @param {string} mimeType The MIME type of the clipboard item.
|
| * @param {string} item The clipboard item.
|
| */
|
| -remoting.ClientPlugin.prototype.sendClipboardItem = function(mimeType, item) {};
|
| +remoting.ClientPlugin.prototype.sendClipboardItem =
|
| + function(mimeType, item) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM))
|
| + return;
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'sendClipboardItem',
|
| + data: { mimeType: mimeType, item: item }}));
|
| +};
|
|
|
| /**
|
| * Notifies the host that the client has the specified size and pixel density.
|
| @@ -167,7 +540,16 @@ remoting.ClientPlugin.prototype.sendClipboardItem = function(mimeType, item) {};
|
| * @param {number} device_scale The number of device pixels per DIP.
|
| */
|
| remoting.ClientPlugin.prototype.notifyClientResolution =
|
| - function(width, height, device_scale) {};
|
| + function(width, height, device_scale) {
|
| + if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) {
|
| + var dpi = Math.floor(device_scale * 96);
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'notifyClientResolution',
|
| + data: { width: Math.floor(width * device_scale),
|
| + height: Math.floor(height * device_scale),
|
| + x_dpi: dpi, y_dpi: dpi }}));
|
| + }
|
| +};
|
|
|
| /**
|
| * Requests that the host pause or resume sending video updates.
|
| @@ -175,7 +557,12 @@ remoting.ClientPlugin.prototype.notifyClientResolution =
|
| * @param {boolean} pause True to suspend video updates, false otherwise.
|
| */
|
| remoting.ClientPlugin.prototype.pauseVideo =
|
| - function(pause) {};
|
| + function(pause) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO))
|
| + return;
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'pauseVideo', data: { pause: pause }}));
|
| +};
|
|
|
| /**
|
| * Requests that the host pause or resume sending audio updates.
|
| @@ -183,19 +570,38 @@ remoting.ClientPlugin.prototype.pauseVideo =
|
| * @param {boolean} pause True to suspend audio updates, false otherwise.
|
| */
|
| remoting.ClientPlugin.prototype.pauseAudio =
|
| - function(pause) {};
|
| + function(pause) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO))
|
| + return;
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'pauseAudio', data: { pause: pause }}));
|
| +};
|
|
|
| /**
|
| - * Gives the client authenticator the PIN.
|
| + * Called when a PIN is obtained from the user.
|
| *
|
| * @param {string} pin The PIN.
|
| */
|
| -remoting.ClientPlugin.prototype.onPinFetched = function(pin) {};
|
| +remoting.ClientPlugin.prototype.onPinFetched =
|
| + function(pin) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
|
| + return;
|
| + }
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'onPinFetched', data: { pin: pin }}));
|
| +};
|
|
|
| /**
|
| * Tells the plugin to ask for the PIN asynchronously.
|
| */
|
| -remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {};
|
| +remoting.ClientPlugin.prototype.useAsyncPinDialog =
|
| + function() {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) {
|
| + return;
|
| + }
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'useAsyncPinDialog', data: {} }));
|
| +};
|
|
|
| /**
|
| * Sets the third party authentication token and shared secret.
|
| @@ -203,8 +609,12 @@ remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {};
|
| * @param {string} token The token received from the token URL.
|
| * @param {string} sharedSecret Shared secret received from the token URL.
|
| */
|
| -remoting.ClientPlugin.prototype.onThirdPartyTokenFetched =
|
| - function(token, sharedSecret) {};
|
| +remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function(
|
| + token, sharedSecret) {
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'onThirdPartyTokenFetched',
|
| + data: { token: token, sharedSecret: sharedSecret}}));
|
| +};
|
|
|
| /**
|
| * Request pairing with the host for PIN-less authentication.
|
| @@ -213,5 +623,50 @@ remoting.ClientPlugin.prototype.onThirdPartyTokenFetched =
|
| * @param {function(string, string):void} onDone, Callback to receive the
|
| * client id and shared secret when they are available.
|
| */
|
| -remoting.ClientPlugin.prototype.requestPairing = function(
|
| - clientName, onDone) {};
|
| +remoting.ClientPlugin.prototype.requestPairing =
|
| + function(clientName, onDone) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) {
|
| + return;
|
| + }
|
| + this.onPairingComplete_ = onDone;
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'requestPairing', data: { clientName: clientName } }));
|
| +};
|
| +
|
| +/**
|
| + * Send an extension message to the host.
|
| + *
|
| + * @param {string} type The message type.
|
| + * @param {Object} message The message payload.
|
| + */
|
| +remoting.ClientPlugin.prototype.sendClientMessage =
|
| + function(type, message) {
|
| + if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) {
|
| + return;
|
| + }
|
| + this.plugin.postMessage(JSON.stringify(
|
| + { method: 'extensionMessage',
|
| + data: { type: type, data: JSON.stringify(message) } }));
|
| +
|
| +};
|
| +
|
| +/**
|
| + * If we haven't yet received a "hello" message from the plugin, change its
|
| + * size so that the user can confirm it if click-to-play is enabled, or can
|
| + * see the "this plugin is disabled" message if it is actually disabled.
|
| + * @private
|
| + */
|
| +remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() {
|
| + if (!this.helloReceived_) {
|
| + var width = 200;
|
| + var height = 200;
|
| + this.plugin.width = width;
|
| + this.plugin.height = height;
|
| + // Center the plugin just underneath the "Connnecting..." dialog.
|
| + var parentNode = this.plugin.parentNode;
|
| + var dialog = document.getElementById('client-dialog');
|
| + var dialogRect = dialog.getBoundingClientRect();
|
| + parentNode.style.top = (dialogRect.bottom + 16) + 'px';
|
| + parentNode.style.left = (window.innerWidth - width) / 2 + 'px';
|
| + }
|
| +};
|
|
|