OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 /** |
| 6 * @fileoverview |
| 7 * Class that wraps low-level details of interacting with the client plugin. |
| 8 * |
| 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. |
| 12 */ |
| 13 |
| 14 'use strict'; |
| 15 |
5 /** @suppress {duplicate} */ | 16 /** @suppress {duplicate} */ |
6 var remoting = remoting || {}; | 17 var remoting = remoting || {}; |
7 | 18 |
8 /** | 19 /** |
9 * Interface used for ClientPlugin objects. | 20 * @param {remoting.ViewerPlugin} plugin The plugin embed element. |
10 * @interface | 21 * @constructor |
11 */ | 22 */ |
12 remoting.ClientPlugin = function() { | 23 remoting.ClientPlugin = function(plugin) { |
| 24 this.plugin = plugin; |
| 25 |
| 26 this.desktopWidth = 0; |
| 27 this.desktopHeight = 0; |
| 28 this.desktopXDpi = 96; |
| 29 this.desktopYDpi = 96; |
| 30 |
| 31 /** @param {string} iq The Iq stanza received from the host. */ |
| 32 this.onOutgoingIqHandler = function (iq) {}; |
| 33 /** @param {string} message Log message. */ |
| 34 this.onDebugMessageHandler = function (message) {}; |
| 35 /** |
| 36 * @param {number} state The connection state. |
| 37 * @param {number} error The error code, if any. |
| 38 */ |
| 39 this.onConnectionStatusUpdateHandler = function(state, error) {}; |
| 40 /** @param {boolean} ready Connection ready state. */ |
| 41 this.onConnectionReadyHandler = function(ready) {}; |
| 42 /** |
| 43 * @param {string} tokenUrl Token-request URL, received from the host. |
| 44 * @param {string} hostPublicKey Public key for the host. |
| 45 * @param {string} scope OAuth scope to request the token for. |
| 46 */ |
| 47 this.fetchThirdPartyTokenHandler = function( |
| 48 tokenUrl, hostPublicKey, scope) {}; |
| 49 this.onDesktopSizeUpdateHandler = function () {}; |
| 50 /** @param {!Array.<string>} capabilities The negotiated capabilities. */ |
| 51 this.onSetCapabilitiesHandler = function (capabilities) {}; |
| 52 this.fetchPinHandler = function (supportsPairing) {}; |
| 53 |
| 54 /** @type {number} */ |
| 55 this.pluginApiVersion_ = -1; |
| 56 /** @type {Array.<string>} */ |
| 57 this.pluginApiFeatures_ = []; |
| 58 /** @type {number} */ |
| 59 this.pluginApiMinVersion_ = -1; |
| 60 /** @type {!Array.<string>} */ |
| 61 this.capabilities_ = []; |
| 62 /** @type {boolean} */ |
| 63 this.helloReceived_ = false; |
| 64 /** @type {function(boolean)|null} */ |
| 65 this.onInitializedCallback_ = null; |
| 66 /** @type {function(string, string):void} */ |
| 67 this.onPairingComplete_ = function(clientId, sharedSecret) {}; |
| 68 /** @type {remoting.ClientSession.PerfStats} */ |
| 69 this.perfStats_ = new remoting.ClientSession.PerfStats(); |
| 70 |
| 71 /** @type {remoting.ClientPlugin} */ |
| 72 var that = this; |
| 73 /** @param {Event} event Message event from the plugin. */ |
| 74 this.plugin.addEventListener('message', function(event) { |
| 75 that.handleMessage_(event.data); |
| 76 }, false); |
| 77 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500); |
13 }; | 78 }; |
14 | 79 |
15 /** @type {number} Desktop width */ | |
16 remoting.ClientPlugin.prototype.desktopWidth; | |
17 /** @type {number} Desktop height */ | |
18 remoting.ClientPlugin.prototype.desktopHeight; | |
19 /** @type {number} Desktop x DPI */ | |
20 remoting.ClientPlugin.prototype.desktopXDpi; | |
21 /** @type {number} Desktop y DPI */ | |
22 remoting.ClientPlugin.prototype.desktopYDpi; | |
23 | |
24 /** @type {function(string): void} Outgoing signaling message callback. */ | |
25 remoting.ClientPlugin.prototype.onOutgoingIqHandler; | |
26 /** @type {function(string): void} Debug messages callback. */ | |
27 remoting.ClientPlugin.prototype.onDebugMessageHandler; | |
28 /** @type {function(number, number): void} State change callback. */ | |
29 remoting.ClientPlugin.prototype.onConnectionStatusUpdateHandler; | |
30 /** @type {function(boolean): void} Connection ready state callback. */ | |
31 remoting.ClientPlugin.prototype.onConnectionReadyHandler; | |
32 /** @type {function(): void} Desktop size change callback. */ | |
33 remoting.ClientPlugin.prototype.onDesktopSizeUpdateHandler; | |
34 /** @type {function(!Array.<string>): void} Capabilities negotiated callback. */ | |
35 remoting.ClientPlugin.prototype.onSetCapabilitiesHandler; | |
36 /** @type {function(boolean): void} Request a PIN from the user. */ | |
37 remoting.ClientPlugin.prototype.fetchPinHandler; | |
38 | |
39 /** | |
40 * Initializes the plugin asynchronously and calls specified function | |
41 * when done. | |
42 * | |
43 * @param {function(boolean): void} onDone Function to be called when | |
44 * the plugin is initialized. Parameter is set to true when the plugin | |
45 * is loaded successfully. | |
46 */ | |
47 remoting.ClientPlugin.prototype.initialize = function(onDone) {}; | |
48 | |
49 /** | |
50 * @return {boolean} True if the plugin and web-app versions are compatible. | |
51 */ | |
52 remoting.ClientPlugin.prototype.isSupportedVersion = function() {}; | |
53 | |
54 /** | 80 /** |
55 * Set of features for which hasFeature() can be used to test. | 81 * Set of features for which hasFeature() can be used to test. |
56 * | 82 * |
57 * @enum {string} | 83 * @enum {string} |
58 */ | 84 */ |
59 remoting.ClientPlugin.Feature = { | 85 remoting.ClientPlugin.Feature = { |
60 INJECT_KEY_EVENT: 'injectKeyEvent', | 86 INJECT_KEY_EVENT: 'injectKeyEvent', |
61 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution', | 87 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution', |
62 ASYNC_PIN: 'asyncPin', | 88 ASYNC_PIN: 'asyncPin', |
63 PAUSE_VIDEO: 'pauseVideo', | 89 PAUSE_VIDEO: 'pauseVideo', |
64 PAUSE_AUDIO: 'pauseAudio', | 90 PAUSE_AUDIO: 'pauseAudio', |
65 REMAP_KEY: 'remapKey', | 91 REMAP_KEY: 'remapKey', |
66 SEND_CLIPBOARD_ITEM: 'sendClipboardItem', | 92 SEND_CLIPBOARD_ITEM: 'sendClipboardItem', |
67 THIRD_PARTY_AUTH: 'thirdPartyAuth', | 93 THIRD_PARTY_AUTH: 'thirdPartyAuth', |
68 TRAP_KEY: 'trapKey', | 94 TRAP_KEY: 'trapKey', |
69 PINLESS_AUTH: 'pinlessAuth', | 95 PINLESS_AUTH: 'pinlessAuth', |
70 EXTENSION_MESSAGE: 'extensionMessage' | 96 EXTENSION_MESSAGE: 'extensionMessage' |
71 }; | 97 }; |
72 | 98 |
73 /** | 99 /** |
| 100 * Chromoting session API version (for this javascript). |
| 101 * This is compared with the plugin API version to verify that they are |
| 102 * compatible. |
| 103 * |
| 104 * @const |
| 105 * @private |
| 106 */ |
| 107 remoting.ClientPlugin.prototype.API_VERSION_ = 6; |
| 108 |
| 109 /** |
| 110 * The oldest API version that we support. |
| 111 * This will differ from the |API_VERSION_| if we maintain backward |
| 112 * compatibility with older API versions. |
| 113 * |
| 114 * @const |
| 115 * @private |
| 116 */ |
| 117 remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5; |
| 118 |
| 119 /** |
| 120 * @param {string} messageStr Message from the plugin. |
| 121 */ |
| 122 remoting.ClientPlugin.prototype.handleMessage_ = function(messageStr) { |
| 123 var message = /** @type {{method:string, data:Object.<string,string>}} */ |
| 124 jsonParseSafe(messageStr); |
| 125 |
| 126 if (!message || !('method' in message) || !('data' in message)) { |
| 127 console.error('Received invalid message from the plugin: ' + messageStr); |
| 128 return; |
| 129 } |
| 130 |
| 131 /** |
| 132 * Splits a string into a list of words delimited by spaces. |
| 133 * @param {string} str String that should be split. |
| 134 * @return {!Array.<string>} List of words. |
| 135 */ |
| 136 var tokenize = function(str) { |
| 137 /** @type {Array.<string>} */ |
| 138 var tokens = str.match(/\S+/g); |
| 139 return tokens ? tokens : []; |
| 140 }; |
| 141 |
| 142 if (message.method == 'hello') { |
| 143 // Reset the size in case we had to enlarge it to support click-to-play. |
| 144 this.plugin.width = 0; |
| 145 this.plugin.height = 0; |
| 146 if (typeof message.data['apiVersion'] != 'number' || |
| 147 typeof message.data['apiMinVersion'] != 'number') { |
| 148 console.error('Received invalid hello message: ' + messageStr); |
| 149 return; |
| 150 } |
| 151 this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion']; |
| 152 |
| 153 if (this.pluginApiVersion_ >= 7) { |
| 154 if (typeof message.data['apiFeatures'] != 'string') { |
| 155 console.error('Received invalid hello message: ' + messageStr); |
| 156 return; |
| 157 } |
| 158 this.pluginApiFeatures_ = |
| 159 /** @type {Array.<string>} */ tokenize(message.data['apiFeatures']); |
| 160 |
| 161 // Negotiate capabilities. |
| 162 |
| 163 /** @type {!Array.<string>} */ |
| 164 var requestedCapabilities = []; |
| 165 if ('requestedCapabilities' in message.data) { |
| 166 if (typeof message.data['requestedCapabilities'] != 'string') { |
| 167 console.error('Received invalid hello message: ' + messageStr); |
| 168 return; |
| 169 } |
| 170 requestedCapabilities = tokenize(message.data['requestedCapabilities']); |
| 171 } |
| 172 |
| 173 /** @type {!Array.<string>} */ |
| 174 var supportedCapabilities = []; |
| 175 if ('supportedCapabilities' in message.data) { |
| 176 if (typeof message.data['supportedCapabilities'] != 'string') { |
| 177 console.error('Received invalid hello message: ' + messageStr); |
| 178 return; |
| 179 } |
| 180 supportedCapabilities = tokenize(message.data['supportedCapabilities']); |
| 181 } |
| 182 |
| 183 // At the moment the webapp does not recognize any of |
| 184 // 'requestedCapabilities' capabilities (so they all should be disabled) |
| 185 // and do not care about any of 'supportedCapabilities' capabilities (so |
| 186 // they all can be enabled). |
| 187 this.capabilities_ = supportedCapabilities; |
| 188 |
| 189 // Let the host know that the webapp can be requested to always send |
| 190 // the client's dimensions. |
| 191 this.capabilities_.push( |
| 192 remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION); |
| 193 |
| 194 // Let the host know that we're interested in knowing whether or not |
| 195 // it rate-limits desktop-resize requests. |
| 196 this.capabilities_.push( |
| 197 remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS); |
| 198 } else if (this.pluginApiVersion_ >= 6) { |
| 199 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent']; |
| 200 } else { |
| 201 this.pluginApiFeatures_ = ['highQualityScaling']; |
| 202 } |
| 203 this.pluginApiMinVersion_ = |
| 204 /** @type {number} */ message.data['apiMinVersion']; |
| 205 this.helloReceived_ = true; |
| 206 if (this.onInitializedCallback_ != null) { |
| 207 this.onInitializedCallback_(true); |
| 208 this.onInitializedCallback_ = null; |
| 209 } |
| 210 } else if (message.method == 'sendOutgoingIq') { |
| 211 if (typeof message.data['iq'] != 'string') { |
| 212 console.error('Received invalid sendOutgoingIq message: ' + messageStr); |
| 213 return; |
| 214 } |
| 215 this.onOutgoingIqHandler(message.data['iq']); |
| 216 } else if (message.method == 'logDebugMessage') { |
| 217 if (typeof message.data['message'] != 'string') { |
| 218 console.error('Received invalid logDebugMessage message: ' + messageStr); |
| 219 return; |
| 220 } |
| 221 this.onDebugMessageHandler(message.data['message']); |
| 222 } else if (message.method == 'onConnectionStatus') { |
| 223 if (typeof message.data['state'] != 'string' || |
| 224 !remoting.ClientSession.State.hasOwnProperty(message.data['state']) || |
| 225 typeof message.data['error'] != 'string') { |
| 226 console.error('Received invalid onConnectionState message: ' + |
| 227 messageStr); |
| 228 return; |
| 229 } |
| 230 |
| 231 /** @type {remoting.ClientSession.State} */ |
| 232 var state = remoting.ClientSession.State[message.data['state']]; |
| 233 var error; |
| 234 if (remoting.ClientSession.ConnectionError.hasOwnProperty( |
| 235 message.data['error'])) { |
| 236 error = /** @type {remoting.ClientSession.ConnectionError} */ |
| 237 remoting.ClientSession.ConnectionError[message.data['error']]; |
| 238 } else { |
| 239 error = remoting.ClientSession.ConnectionError.UNKNOWN; |
| 240 } |
| 241 |
| 242 this.onConnectionStatusUpdateHandler(state, error); |
| 243 } else if (message.method == 'onDesktopSize') { |
| 244 if (typeof message.data['width'] != 'number' || |
| 245 typeof message.data['height'] != 'number') { |
| 246 console.error('Received invalid onDesktopSize message: ' + messageStr); |
| 247 return; |
| 248 } |
| 249 this.desktopWidth = /** @type {number} */ message.data['width']; |
| 250 this.desktopHeight = /** @type {number} */ message.data['height']; |
| 251 this.desktopXDpi = (typeof message.data['x_dpi'] == 'number') ? |
| 252 /** @type {number} */ (message.data['x_dpi']) : 96; |
| 253 this.desktopYDpi = (typeof message.data['y_dpi'] == 'number') ? |
| 254 /** @type {number} */ (message.data['y_dpi']) : 96; |
| 255 this.onDesktopSizeUpdateHandler(); |
| 256 } else if (message.method == 'onPerfStats') { |
| 257 if (typeof message.data['videoBandwidth'] != 'number' || |
| 258 typeof message.data['videoFrameRate'] != 'number' || |
| 259 typeof message.data['captureLatency'] != 'number' || |
| 260 typeof message.data['encodeLatency'] != 'number' || |
| 261 typeof message.data['decodeLatency'] != 'number' || |
| 262 typeof message.data['renderLatency'] != 'number' || |
| 263 typeof message.data['roundtripLatency'] != 'number') { |
| 264 console.error('Received incorrect onPerfStats message: ' + messageStr); |
| 265 return; |
| 266 } |
| 267 this.perfStats_ = |
| 268 /** @type {remoting.ClientSession.PerfStats} */ message.data; |
| 269 } else if (message.method == 'injectClipboardItem') { |
| 270 if (typeof message.data['mimeType'] != 'string' || |
| 271 typeof message.data['item'] != 'string') { |
| 272 console.error('Received incorrect injectClipboardItem message.'); |
| 273 return; |
| 274 } |
| 275 if (remoting.clipboard) { |
| 276 remoting.clipboard.fromHost(message.data['mimeType'], |
| 277 message.data['item']); |
| 278 } |
| 279 } else if (message.method == 'onFirstFrameReceived') { |
| 280 if (remoting.clientSession) { |
| 281 remoting.clientSession.onFirstFrameReceived(); |
| 282 } |
| 283 } else if (message.method == 'onConnectionReady') { |
| 284 if (typeof message.data['ready'] != 'boolean') { |
| 285 console.error('Received incorrect onConnectionReady message.'); |
| 286 return; |
| 287 } |
| 288 var ready = /** @type {boolean} */ message.data['ready']; |
| 289 this.onConnectionReadyHandler(ready); |
| 290 } else if (message.method == 'fetchPin') { |
| 291 // The pairingSupported value in the dictionary indicates whether both |
| 292 // client and host support pairing. If the client doesn't support pairing, |
| 293 // then the value won't be there at all, so give it a default of false. |
| 294 /** @type {boolean} */ |
| 295 var pairingSupported = false; |
| 296 if ('pairingSupported' in message.data) { |
| 297 pairingSupported = |
| 298 /** @type {boolean} */ message.data['pairingSupported']; |
| 299 if (typeof pairingSupported != 'boolean') { |
| 300 console.error('Received incorrect fetchPin message.'); |
| 301 return; |
| 302 } |
| 303 } |
| 304 this.fetchPinHandler(pairingSupported); |
| 305 } else if (message.method == 'setCapabilities') { |
| 306 if (typeof message.data['capabilities'] != 'string') { |
| 307 console.error('Received incorrect setCapabilities message.'); |
| 308 return; |
| 309 } |
| 310 |
| 311 /** @type {!Array.<string>} */ |
| 312 var capabilities = tokenize(message.data['capabilities']); |
| 313 this.onSetCapabilitiesHandler(capabilities); |
| 314 } else if (message.method == 'fetchThirdPartyToken') { |
| 315 if (typeof message.data['tokenUrl'] != 'string' || |
| 316 typeof message.data['hostPublicKey'] != 'string' || |
| 317 typeof message.data['scope'] != 'string') { |
| 318 console.error('Received incorrect fetchThirdPartyToken message.'); |
| 319 return; |
| 320 } |
| 321 var tokenUrl = /** @type {string} */ message.data['tokenUrl']; |
| 322 var hostPublicKey = |
| 323 /** @type {string} */ message.data['hostPublicKey']; |
| 324 var scope = /** @type {string} */ message.data['scope']; |
| 325 this.fetchThirdPartyTokenHandler(tokenUrl, hostPublicKey, scope); |
| 326 } else if (message.method == 'pairingResponse') { |
| 327 var clientId = /** @type {string} */ message.data['clientId']; |
| 328 var sharedSecret = /** @type {string} */ message.data['sharedSecret']; |
| 329 if (typeof clientId != 'string' || typeof sharedSecret != 'string') { |
| 330 console.error('Received incorrect pairingResponse message.'); |
| 331 return; |
| 332 } |
| 333 this.onPairingComplete_(clientId, sharedSecret); |
| 334 } else if (message.method == 'extensionMessage') { |
| 335 if (typeof(message.data['type']) != 'string' || |
| 336 typeof(message.data['data']) != 'string') { |
| 337 console.error('Invalid extension message:', message.data); |
| 338 return; |
| 339 } |
| 340 switch (message.data['type']) { |
| 341 case 'test-echo-reply': |
| 342 console.log('Got echo reply: ' + message.data['data']); |
| 343 break; |
| 344 default: |
| 345 console.log('Unexpected message received: ' + |
| 346 message.data['type'] + ': ' + message.data['data']); |
| 347 } |
| 348 } |
| 349 }; |
| 350 |
| 351 /** |
| 352 * Deletes the plugin. |
| 353 */ |
| 354 remoting.ClientPlugin.prototype.cleanup = function() { |
| 355 this.plugin.parentNode.removeChild(this.plugin); |
| 356 }; |
| 357 |
| 358 /** |
| 359 * @return {HTMLEmbedElement} HTML element that correspods to the plugin. |
| 360 */ |
| 361 remoting.ClientPlugin.prototype.element = function() { |
| 362 return this.plugin; |
| 363 }; |
| 364 |
| 365 /** |
| 366 * @param {function(boolean): void} onDone |
| 367 */ |
| 368 remoting.ClientPlugin.prototype.initialize = function(onDone) { |
| 369 if (this.helloReceived_) { |
| 370 onDone(true); |
| 371 } else { |
| 372 this.onInitializedCallback_ = onDone; |
| 373 } |
| 374 }; |
| 375 |
| 376 /** |
| 377 * @return {boolean} True if the plugin and web-app versions are compatible. |
| 378 */ |
| 379 remoting.ClientPlugin.prototype.isSupportedVersion = function() { |
| 380 if (!this.helloReceived_) { |
| 381 console.error( |
| 382 "isSupportedVersion() is called before the plugin is initialized."); |
| 383 return false; |
| 384 } |
| 385 return this.API_VERSION_ >= this.pluginApiMinVersion_ && |
| 386 this.pluginApiVersion_ >= this.API_MIN_VERSION_; |
| 387 }; |
| 388 |
| 389 /** |
74 * @param {remoting.ClientPlugin.Feature} feature The feature to test for. | 390 * @param {remoting.ClientPlugin.Feature} feature The feature to test for. |
75 * @return {boolean} True if the plugin supports the named feature. | 391 * @return {boolean} True if the plugin supports the named feature. |
76 */ | 392 */ |
77 remoting.ClientPlugin.prototype.hasFeature = function(feature) {}; | 393 remoting.ClientPlugin.prototype.hasFeature = function(feature) { |
78 | 394 if (!this.helloReceived_) { |
79 /** | 395 console.error( |
80 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin. | 396 "hasFeature() is called before the plugin is initialized."); |
81 */ | 397 return false; |
82 remoting.ClientPlugin.prototype.element = function() {}; | 398 } |
83 | 399 return this.pluginApiFeatures_.indexOf(feature) > -1; |
84 /** | 400 }; |
85 * Deletes the plugin. | 401 |
86 */ | 402 /** |
87 remoting.ClientPlugin.prototype.cleanup = function() {}; | 403 * @return {boolean} True if the plugin supports the injectKeyEvent API. |
88 | 404 */ |
89 /** | 405 remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() { |
90 * Must be called for each incoming stanza received from the host. | 406 return this.pluginApiVersion_ >= 6; |
| 407 }; |
| 408 |
| 409 /** |
91 * @param {string} iq Incoming IQ stanza. | 410 * @param {string} iq Incoming IQ stanza. |
92 */ | 411 */ |
93 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {}; | 412 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) { |
| 413 if (this.plugin && this.plugin.postMessage) { |
| 414 this.plugin.postMessage(JSON.stringify( |
| 415 { method: 'incomingIq', data: { iq: iq } })); |
| 416 } else { |
| 417 // plugin.onIq may not be set after the plugin has been shut |
| 418 // down. Particularly this happens when we receive response to |
| 419 // session-terminate stanza. |
| 420 console.warn('plugin.onIq is not set so dropping incoming message.'); |
| 421 } |
| 422 }; |
94 | 423 |
95 /** | 424 /** |
96 * @param {string} hostJid The jid of the host to connect to. | 425 * @param {string} hostJid The jid of the host to connect to. |
97 * @param {string} hostPublicKey The base64 encoded version of the host's | 426 * @param {string} hostPublicKey The base64 encoded version of the host's |
98 * public key. | 427 * public key. |
99 * @param {string} localJid Local jid. | 428 * @param {string} localJid Local jid. |
100 * @param {string} sharedSecret The access code for IT2Me or the PIN | 429 * @param {string} sharedSecret The access code for IT2Me or the PIN |
101 * for Me2Me. | 430 * for Me2Me. |
102 * @param {string} authenticationMethods Comma-separated list of | 431 * @param {string} authenticationMethods Comma-separated list of |
103 * authentication methods the client should attempt to use. | 432 * authentication methods the client should attempt to use. |
104 * @param {string} authenticationTag A host-specific tag to mix into | 433 * @param {string} authenticationTag A host-specific tag to mix into |
105 * authentication hashes. | 434 * authentication hashes. |
106 * @param {string} clientPairingId For paired Me2Me connections, the | 435 * @param {string} clientPairingId For paired Me2Me connections, the |
107 * pairing id for this client, as issued by the host. | 436 * pairing id for this client, as issued by the host. |
108 * @param {string} clientPairedSecret For paired Me2Me connections, the | 437 * @param {string} clientPairedSecret For paired Me2Me connections, the |
109 * paired secret for this client, as issued by the host. | 438 * paired secret for this client, as issued by the host. |
110 */ | 439 */ |
111 remoting.ClientPlugin.prototype.connect = function( | 440 remoting.ClientPlugin.prototype.connect = function( |
112 hostJid, hostPublicKey, localJid, sharedSecret, | 441 hostJid, hostPublicKey, localJid, sharedSecret, |
113 authenticationMethods, authenticationTag, | 442 authenticationMethods, authenticationTag, |
114 clientPairingId, clientPairedSecret) {}; | 443 clientPairingId, clientPairedSecret) { |
| 444 this.plugin.postMessage(JSON.stringify( |
| 445 { method: 'connect', data: { |
| 446 hostJid: hostJid, |
| 447 hostPublicKey: hostPublicKey, |
| 448 localJid: localJid, |
| 449 sharedSecret: sharedSecret, |
| 450 authenticationMethods: authenticationMethods, |
| 451 authenticationTag: authenticationTag, |
| 452 capabilities: this.capabilities_.join(" "), |
| 453 clientPairingId: clientPairingId, |
| 454 clientPairedSecret: clientPairedSecret |
| 455 } |
| 456 })); |
| 457 }; |
115 | 458 |
116 /** | 459 /** |
117 * Release all currently pressed keys. | 460 * Release all currently pressed keys. |
118 */ | 461 */ |
119 remoting.ClientPlugin.prototype.releaseAllKeys = function() {}; | 462 remoting.ClientPlugin.prototype.releaseAllKeys = function() { |
| 463 this.plugin.postMessage(JSON.stringify( |
| 464 { method: 'releaseAllKeys', data: {} })); |
| 465 }; |
120 | 466 |
121 /** | 467 /** |
122 * Send a key event to the host. | 468 * Send a key event to the host. |
123 * | 469 * |
124 * @param {number} usbKeycode The USB-style code of the key to inject. | 470 * @param {number} usbKeycode The USB-style code of the key to inject. |
125 * @param {boolean} pressed True to inject a key press, False for a release. | 471 * @param {boolean} pressed True to inject a key press, False for a release. |
126 */ | 472 */ |
127 remoting.ClientPlugin.prototype.injectKeyEvent = | 473 remoting.ClientPlugin.prototype.injectKeyEvent = |
128 function(usbKeycode, pressed) {}; | 474 function(usbKeycode, pressed) { |
| 475 this.plugin.postMessage(JSON.stringify( |
| 476 { method: 'injectKeyEvent', data: { |
| 477 'usbKeycode': usbKeycode, |
| 478 'pressed': pressed} |
| 479 })); |
| 480 }; |
129 | 481 |
130 /** | 482 /** |
131 * Remap one USB keycode to another in all subsequent key events. | 483 * Remap one USB keycode to another in all subsequent key events. |
132 * | 484 * |
133 * @param {number} fromKeycode The USB-style code of the key to remap. | 485 * @param {number} fromKeycode The USB-style code of the key to remap. |
134 * @param {number} toKeycode The USB-style code to remap the key to. | 486 * @param {number} toKeycode The USB-style code to remap the key to. |
135 */ | 487 */ |
136 remoting.ClientPlugin.prototype.remapKey = | 488 remoting.ClientPlugin.prototype.remapKey = |
137 function(fromKeycode, toKeycode) {}; | 489 function(fromKeycode, toKeycode) { |
| 490 this.plugin.postMessage(JSON.stringify( |
| 491 { method: 'remapKey', data: { |
| 492 'fromKeycode': fromKeycode, |
| 493 'toKeycode': toKeycode} |
| 494 })); |
| 495 }; |
138 | 496 |
139 /** | 497 /** |
140 * Enable/disable redirection of the specified key to the web-app. | 498 * Enable/disable redirection of the specified key to the web-app. |
141 * | 499 * |
142 * @param {number} keycode The USB-style code of the key. | 500 * @param {number} keycode The USB-style code of the key. |
143 * @param {Boolean} trap True to enable trapping, False to disable. | 501 * @param {Boolean} trap True to enable trapping, False to disable. |
144 */ | 502 */ |
145 remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) {}; | 503 remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) { |
| 504 this.plugin.postMessage(JSON.stringify( |
| 505 { method: 'trapKey', data: { |
| 506 'keycode': keycode, |
| 507 'trap': trap} |
| 508 })); |
| 509 }; |
146 | 510 |
147 /** | 511 /** |
148 * Returns an associative array with a set of stats for this connection. | 512 * Returns an associative array with a set of stats for this connecton. |
149 * | 513 * |
150 * @return {remoting.ClientSession.PerfStats} The connection statistics. | 514 * @return {remoting.ClientSession.PerfStats} The connection statistics. |
151 */ | 515 */ |
152 remoting.ClientPlugin.prototype.getPerfStats = function() {}; | 516 remoting.ClientPlugin.prototype.getPerfStats = function() { |
| 517 return this.perfStats_; |
| 518 }; |
153 | 519 |
154 /** | 520 /** |
155 * Sends a clipboard item to the host. | 521 * Sends a clipboard item to the host. |
156 * | 522 * |
157 * @param {string} mimeType The MIME type of the clipboard item. | 523 * @param {string} mimeType The MIME type of the clipboard item. |
158 * @param {string} item The clipboard item. | 524 * @param {string} item The clipboard item. |
159 */ | 525 */ |
160 remoting.ClientPlugin.prototype.sendClipboardItem = function(mimeType, item) {}; | 526 remoting.ClientPlugin.prototype.sendClipboardItem = |
| 527 function(mimeType, item) { |
| 528 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM)) |
| 529 return; |
| 530 this.plugin.postMessage(JSON.stringify( |
| 531 { method: 'sendClipboardItem', |
| 532 data: { mimeType: mimeType, item: item }})); |
| 533 }; |
161 | 534 |
162 /** | 535 /** |
163 * Notifies the host that the client has the specified size and pixel density. | 536 * Notifies the host that the client has the specified size and pixel density. |
164 * | 537 * |
165 * @param {number} width The available client width in DIPs. | 538 * @param {number} width The available client width in DIPs. |
166 * @param {number} height The available client height in DIPs. | 539 * @param {number} height The available client height in DIPs. |
167 * @param {number} device_scale The number of device pixels per DIP. | 540 * @param {number} device_scale The number of device pixels per DIP. |
168 */ | 541 */ |
169 remoting.ClientPlugin.prototype.notifyClientResolution = | 542 remoting.ClientPlugin.prototype.notifyClientResolution = |
170 function(width, height, device_scale) {}; | 543 function(width, height, device_scale) { |
| 544 if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) { |
| 545 var dpi = Math.floor(device_scale * 96); |
| 546 this.plugin.postMessage(JSON.stringify( |
| 547 { method: 'notifyClientResolution', |
| 548 data: { width: Math.floor(width * device_scale), |
| 549 height: Math.floor(height * device_scale), |
| 550 x_dpi: dpi, y_dpi: dpi }})); |
| 551 } |
| 552 }; |
171 | 553 |
172 /** | 554 /** |
173 * Requests that the host pause or resume sending video updates. | 555 * Requests that the host pause or resume sending video updates. |
174 * | 556 * |
175 * @param {boolean} pause True to suspend video updates, false otherwise. | 557 * @param {boolean} pause True to suspend video updates, false otherwise. |
176 */ | 558 */ |
177 remoting.ClientPlugin.prototype.pauseVideo = | 559 remoting.ClientPlugin.prototype.pauseVideo = |
178 function(pause) {}; | 560 function(pause) { |
| 561 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) |
| 562 return; |
| 563 this.plugin.postMessage(JSON.stringify( |
| 564 { method: 'pauseVideo', data: { pause: pause }})); |
| 565 }; |
179 | 566 |
180 /** | 567 /** |
181 * Requests that the host pause or resume sending audio updates. | 568 * Requests that the host pause or resume sending audio updates. |
182 * | 569 * |
183 * @param {boolean} pause True to suspend audio updates, false otherwise. | 570 * @param {boolean} pause True to suspend audio updates, false otherwise. |
184 */ | 571 */ |
185 remoting.ClientPlugin.prototype.pauseAudio = | 572 remoting.ClientPlugin.prototype.pauseAudio = |
186 function(pause) {}; | 573 function(pause) { |
| 574 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) |
| 575 return; |
| 576 this.plugin.postMessage(JSON.stringify( |
| 577 { method: 'pauseAudio', data: { pause: pause }})); |
| 578 }; |
187 | 579 |
188 /** | 580 /** |
189 * Gives the client authenticator the PIN. | 581 * Called when a PIN is obtained from the user. |
190 * | 582 * |
191 * @param {string} pin The PIN. | 583 * @param {string} pin The PIN. |
192 */ | 584 */ |
193 remoting.ClientPlugin.prototype.onPinFetched = function(pin) {}; | 585 remoting.ClientPlugin.prototype.onPinFetched = |
| 586 function(pin) { |
| 587 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { |
| 588 return; |
| 589 } |
| 590 this.plugin.postMessage(JSON.stringify( |
| 591 { method: 'onPinFetched', data: { pin: pin }})); |
| 592 }; |
194 | 593 |
195 /** | 594 /** |
196 * Tells the plugin to ask for the PIN asynchronously. | 595 * Tells the plugin to ask for the PIN asynchronously. |
197 */ | 596 */ |
198 remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {}; | 597 remoting.ClientPlugin.prototype.useAsyncPinDialog = |
| 598 function() { |
| 599 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { |
| 600 return; |
| 601 } |
| 602 this.plugin.postMessage(JSON.stringify( |
| 603 { method: 'useAsyncPinDialog', data: {} })); |
| 604 }; |
199 | 605 |
200 /** | 606 /** |
201 * Sets the third party authentication token and shared secret. | 607 * Sets the third party authentication token and shared secret. |
202 * | 608 * |
203 * @param {string} token The token received from the token URL. | 609 * @param {string} token The token received from the token URL. |
204 * @param {string} sharedSecret Shared secret received from the token URL. | 610 * @param {string} sharedSecret Shared secret received from the token URL. |
205 */ | 611 */ |
206 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = | 612 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function( |
207 function(token, sharedSecret) {}; | 613 token, sharedSecret) { |
| 614 this.plugin.postMessage(JSON.stringify( |
| 615 { method: 'onThirdPartyTokenFetched', |
| 616 data: { token: token, sharedSecret: sharedSecret}})); |
| 617 }; |
208 | 618 |
209 /** | 619 /** |
210 * Request pairing with the host for PIN-less authentication. | 620 * Request pairing with the host for PIN-less authentication. |
211 * | 621 * |
212 * @param {string} clientName The human-readable name of the client. | 622 * @param {string} clientName The human-readable name of the client. |
213 * @param {function(string, string):void} onDone, Callback to receive the | 623 * @param {function(string, string):void} onDone, Callback to receive the |
214 * client id and shared secret when they are available. | 624 * client id and shared secret when they are available. |
215 */ | 625 */ |
216 remoting.ClientPlugin.prototype.requestPairing = function( | 626 remoting.ClientPlugin.prototype.requestPairing = |
217 clientName, onDone) {}; | 627 function(clientName, onDone) { |
| 628 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) { |
| 629 return; |
| 630 } |
| 631 this.onPairingComplete_ = onDone; |
| 632 this.plugin.postMessage(JSON.stringify( |
| 633 { method: 'requestPairing', data: { clientName: clientName } })); |
| 634 }; |
| 635 |
| 636 /** |
| 637 * Send an extension message to the host. |
| 638 * |
| 639 * @param {string} type The message type. |
| 640 * @param {Object} message The message payload. |
| 641 */ |
| 642 remoting.ClientPlugin.prototype.sendClientMessage = |
| 643 function(type, message) { |
| 644 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) { |
| 645 return; |
| 646 } |
| 647 this.plugin.postMessage(JSON.stringify( |
| 648 { method: 'extensionMessage', |
| 649 data: { type: type, data: JSON.stringify(message) } })); |
| 650 |
| 651 }; |
| 652 |
| 653 /** |
| 654 * If we haven't yet received a "hello" message from the plugin, change its |
| 655 * size so that the user can confirm it if click-to-play is enabled, or can |
| 656 * see the "this plugin is disabled" message if it is actually disabled. |
| 657 * @private |
| 658 */ |
| 659 remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() { |
| 660 if (!this.helloReceived_) { |
| 661 var width = 200; |
| 662 var height = 200; |
| 663 this.plugin.width = width; |
| 664 this.plugin.height = height; |
| 665 // Center the plugin just underneath the "Connnecting..." dialog. |
| 666 var parentNode = this.plugin.parentNode; |
| 667 var dialog = document.getElementById('client-dialog'); |
| 668 var dialogRect = dialog.getBoundingClientRect(); |
| 669 parentNode.style.top = (dialogRect.bottom + 16) + 'px'; |
| 670 parentNode.style.left = (window.innerWidth - width) / 2 + 'px'; |
| 671 } |
| 672 }; |
OLD | NEW |