OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
Sergey Ulanov
2014/09/20 00:35:24
this is not a new file, so keep 2012 here?
| |
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 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Class that wraps low-level details of interacting with the client plugin. | 7 * Interface abstracting the ClientPlugin functionality. |
Sergey Ulanov
2014/09/20 00:35:24
Please update this comment - it's confusing after
Jamie
2014/09/20 00:54:22
Done.
| |
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 */ | 8 */ |
13 | 9 |
14 'use strict'; | 10 'use strict'; |
15 | 11 |
16 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
17 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
18 | 14 |
19 /** | 15 /** |
20 * @param {Element} container The container for the embed element. | 16 * @interface |
21 * @param {function(string, string):boolean} onExtensionMessage The handler for | 17 * @extends {base.Disposable} |
22 * protocol extension messages. Returns true if a message is recognized; | |
23 * false otherwise. | |
24 * @constructor | |
25 */ | 18 */ |
26 remoting.ClientPlugin = function(container, onExtensionMessage) { | 19 remoting.ClientPlugin = function() {}; |
27 this.plugin_ = remoting.ClientPlugin.createPluginElement_(); | |
28 this.plugin_.id = 'session-client-plugin'; | |
29 container.appendChild(this.plugin_); | |
30 | |
31 this.onExtensionMessage_ = onExtensionMessage; | |
32 | |
33 this.desktopWidth = 0; | |
34 this.desktopHeight = 0; | |
35 this.desktopXDpi = 96; | |
36 this.desktopYDpi = 96; | |
37 | |
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) {}; | |
42 /** | |
43 * @param {number} state The connection state. | |
44 * @param {number} error The error code, if any. | |
45 */ | |
46 this.onConnectionStatusUpdateHandler = function(state, error) {}; | |
47 /** @param {boolean} ready Connection ready state. */ | |
48 this.onConnectionReadyHandler = function(ready) {}; | |
49 | |
50 /** | |
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. | |
54 */ | |
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) {}; | |
63 /** | |
64 * @param {string} url | |
65 * @param {number} hotspotX | |
66 * @param {number} hotspotY | |
67 */ | |
68 this.updateMouseCursorImage = function(url, hotspotX, hotspotY) {}; | |
69 | |
70 /** @param {string} data Remote cast extension message. */ | |
71 this.onCastExtensionHandler = function(data) {}; | |
72 | |
73 /** @type {remoting.MediaSourceRenderer} */ | |
74 this.mediaSourceRenderer_ = null; | |
75 | |
76 /** @type {number} */ | |
77 this.pluginApiVersion_ = -1; | |
78 /** @type {Array.<string>} */ | |
79 this.pluginApiFeatures_ = []; | |
80 /** @type {number} */ | |
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(); | |
92 | |
93 /** @type {remoting.ClientPlugin} */ | |
94 var that = this; | |
95 /** @param {Event} event Message event from the plugin. */ | |
96 this.plugin_.addEventListener('message', function(event) { | |
97 that.handleMessage_(event.data); | |
98 }, false); | |
99 | |
100 if (remoting.settings.CLIENT_PLUGIN_TYPE == 'native') { | |
101 window.setTimeout(this.showPluginForClickToPlay_.bind(this), 500); | |
102 } | |
103 }; | |
104 | 20 |
105 /** | 21 /** |
106 * Creates plugin element without adding it to a container. | 22 * @return {number} The width of the remote desktop, in pixels. |
107 * | |
108 * @return {remoting.ViewerPlugin} Plugin element | |
109 */ | 23 */ |
110 remoting.ClientPlugin.createPluginElement_ = function() { | 24 remoting.ClientPlugin.prototype.getDesktopWidth = 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'; | |
119 } else { | |
120 plugin.src = 'about://none'; | |
121 plugin.type = 'application/vnd.chromium.remoting-viewer'; | |
122 } | |
123 plugin.width = 0; | |
124 plugin.height = 0; | |
125 plugin.tabIndex = 0; // Required, otherwise focus() doesn't work. | |
126 return plugin; | |
127 } | |
128 | 25 |
129 /** | 26 /** |
130 * Preloads the plugin to make instantiation faster when the user tries | 27 * @return {number} The height of the remote desktop, in pixels. |
131 * to connect. | |
132 */ | 28 */ |
133 remoting.ClientPlugin.preload = function() { | 29 remoting.ClientPlugin.prototype.getDesktopHeight = function() {}; |
134 if (remoting.settings.CLIENT_PLUGIN_TYPE != 'pnacl') { | |
135 return; | |
136 } | |
137 | |
138 var plugin = remoting.ClientPlugin.createPluginElement_(); | |
139 plugin.addEventListener( | |
140 'loadend', function() { document.body.removeChild(plugin); }, false); | |
141 document.body.appendChild(plugin); | |
142 } | |
143 | 30 |
144 /** | 31 /** |
145 * Set of features for which hasFeature() can be used to test. | 32 * @return {number} The x-DPI of the remote desktop. |
146 * | |
147 * @enum {string} | |
148 */ | 33 */ |
149 remoting.ClientPlugin.Feature = { | 34 remoting.ClientPlugin.prototype.getDesktopXDpi = function() {}; |
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', | |
158 TRAP_KEY: 'trapKey', | |
159 PINLESS_AUTH: 'pinlessAuth', | |
160 EXTENSION_MESSAGE: 'extensionMessage', | |
161 MEDIA_SOURCE_RENDERING: 'mediaSourceRendering', | |
162 VIDEO_CONTROL: 'videoControl' | |
163 }; | |
164 | 35 |
165 /** | 36 /** |
166 * Chromoting session API version (for this javascript). | 37 * @return {number} The y-DPI of the remote desktop. |
167 * This is compared with the plugin API version to verify that they are | |
168 * compatible. | |
169 * | |
170 * @const | |
171 * @private | |
172 */ | 38 */ |
173 remoting.ClientPlugin.prototype.API_VERSION_ = 6; | 39 remoting.ClientPlugin.prototype.getDesktopYDpi = function() {}; |
174 | 40 |
175 /** | 41 /** |
176 * The oldest API version that we support. | 42 * @return {HTMLElement} The DOM element representing the remote session. |
177 * This will differ from the |API_VERSION_| if we maintain backward | |
178 * compatibility with older API versions. | |
179 * | |
180 * @const | |
181 * @private | |
182 */ | 43 */ |
183 remoting.ClientPlugin.prototype.API_MIN_VERSION_ = 5; | 44 remoting.ClientPlugin.prototype.element = function() {}; |
184 | 45 |
185 /** | 46 /** |
186 * @param {string|{method:string, data:Object.<string,*>}} | 47 * @param {function():void} onDone Completion callback. |
187 * rawMessage Message from the plugin. | |
188 * @private | |
189 */ | 48 */ |
190 remoting.ClientPlugin.prototype.handleMessage_ = function(rawMessage) { | 49 remoting.ClientPlugin.prototype.initialize = function(onDone) {}; |
191 var message = | |
192 /** @type {{method:string, data:Object.<string,*>}} */ | |
193 ((typeof(rawMessage) == 'string') ? jsonParseSafe(rawMessage) | |
194 : rawMessage); | |
195 if (!message || !('method' in message) || !('data' in message)) { | |
196 console.error('Received invalid message from the plugin:', rawMessage); | |
197 return; | |
198 } | |
199 | |
200 try { | |
201 this.handleMessageMethod_(message); | |
202 } catch(e) { | |
203 console.error(/** @type {*} */ (e)); | |
204 } | |
205 } | |
206 | |
207 /** | |
208 * @param {{method:string, data:Object.<string,*>}} | |
209 * message Parsed message from the plugin. | |
210 * @private | |
211 */ | |
212 remoting.ClientPlugin.prototype.handleMessageMethod_ = function(message) { | |
213 /** | |
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. | |
217 */ | |
218 var tokenize = function(str) { | |
219 /** @type {Array.<string>} */ | |
220 var tokens = str.match(/\S+/g); | |
221 return tokens ? tokens : []; | |
222 }; | |
223 | |
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'); | |
229 | |
230 if (this.pluginApiVersion_ >= 7) { | |
231 this.pluginApiFeatures_ = | |
232 tokenize(getStringAttr(message.data, 'apiFeatures')); | |
233 | |
234 // Negotiate capabilities. | |
235 | |
236 /** @type {!Array.<string>} */ | |
237 var requestedCapabilities = []; | |
238 if ('requestedCapabilities' in message.data) { | |
239 requestedCapabilities = | |
240 tokenize(getStringAttr(message.data, 'requestedCapabilities')); | |
241 } | |
242 | |
243 /** @type {!Array.<string>} */ | |
244 var supportedCapabilities = []; | |
245 if ('supportedCapabilities' in message.data) { | |
246 supportedCapabilities = | |
247 tokenize(getStringAttr(message.data, 'supportedCapabilities')); | |
248 } | |
249 | |
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; | |
255 | |
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); | |
260 | |
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); | |
265 | |
266 // Let the host know that we can use the video framerecording extension. | |
267 this.capabilities_.push( | |
268 remoting.ClientSession.Capability.VIDEO_RECORDER); | |
269 | |
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); | |
275 | |
276 } else if (this.pluginApiVersion_ >= 6) { | |
277 this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent']; | |
278 } else { | |
279 this.pluginApiFeatures_ = ['highQualityScaling']; | |
280 } | |
281 this.helloReceived_ = true; | |
282 if (this.onInitializedCallback_ != null) { | |
283 this.onInitializedCallback_(true); | |
284 this.onInitializedCallback_ = null; | |
285 } | |
286 | |
287 } else if (message.method == 'sendOutgoingIq') { | |
288 this.onOutgoingIqHandler(getStringAttr(message.data, 'iq')); | |
289 | |
290 } else if (message.method == 'logDebugMessage') { | |
291 this.onDebugMessageHandler(getStringAttr(message.data, 'message')); | |
292 | |
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); | |
299 | |
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(); | |
306 | |
307 } else if (message.method == 'onPerfStats') { | |
308 // Return value is ignored. These calls will throw an error if the value | |
309 // is not a number. | |
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'); | |
317 this.perfStats_ = | |
318 /** @type {remoting.ClientSession.PerfStats} */ message.data; | |
319 | |
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); | |
325 } | |
326 | |
327 } else if (message.method == 'onFirstFrameReceived') { | |
328 if (remoting.clientSession) { | |
329 remoting.clientSession.onFirstFrameReceived(); | |
330 } | |
331 | |
332 } else if (message.method == 'onConnectionReady') { | |
333 var ready = getBooleanAttr(message.data, 'ready'); | |
334 this.onConnectionReadyHandler(ready); | |
335 | |
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', | |
341 false) | |
342 this.fetchPinHandler(pairingSupported); | |
343 | |
344 } else if (message.method == 'setCapabilities') { | |
345 /** @type {!Array.<string>} */ | |
346 var capabilities = tokenize(getStringAttr(message.data, 'capabilities')); | |
347 this.onSetCapabilitiesHandler(capabilities); | |
348 | |
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); | |
354 | |
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); | |
359 | |
360 } else if (message.method == 'extensionMessage') { | |
361 var extMsgType = getStringAttr(message.data, 'type'); | |
362 var extMsgData = getStringAttr(message.data, 'data'); | |
363 switch (extMsgType) { | |
364 case 'gnubby-auth': | |
365 this.onGnubbyAuthHandler(extMsgData); | |
366 break; | |
367 case 'test-echo-reply': | |
368 console.log('Got echo reply: ' + extMsgData); | |
369 break; | |
370 case 'cast_message': | |
371 this.onCastExtensionHandler(extMsgData); | |
372 break; | |
373 default: | |
374 this.onExtensionMessage_(extMsgType, extMsgData); | |
375 break; | |
376 } | |
377 | |
378 } else if (message.method == 'mediaSourceReset') { | |
379 if (!this.mediaSourceRenderer_) { | |
380 console.error('Unexpected mediaSourceReset.'); | |
381 return; | |
382 } | |
383 this.mediaSourceRenderer_.reset(getStringAttr(message.data, 'format')) | |
384 | |
385 } else if (message.method == 'mediaSourceData') { | |
386 if (!(message.data['buffer'] instanceof ArrayBuffer)) { | |
387 console.error('Invalid mediaSourceData message:', message.data); | |
388 return; | |
389 } | |
390 if (!this.mediaSourceRenderer_) { | |
391 console.error('Unexpected mediaSourceData.'); | |
392 return; | |
393 } | |
394 // keyframe flag may be absent from the message. | |
395 var keyframe = !!message.data['keyframe']; | |
396 this.mediaSourceRenderer_.onIncomingData( | |
397 (/** @type {ArrayBuffer} */ message.data['buffer']), keyframe); | |
398 | |
399 } else if (message.method == 'unsetCursorShape') { | |
400 this.updateMouseCursorImage('', 0, 0); | |
401 | |
402 } else if (message.method == 'setCursorShape') { | |
403 var width = getNumberAttr(message.data, 'width'); | |
404 var height = getNumberAttr(message.data, 'height'); | |
405 var hotspotX = getNumberAttr(message.data, 'hotspotX'); | |
406 var hotspotY = getNumberAttr(message.data, 'hotspotY'); | |
407 var srcArrayBuffer = getObjectAttr(message.data, 'data'); | |
408 | |
409 var canvas = | |
410 /** @type {HTMLCanvasElement} */ (document.createElement('canvas')); | |
411 canvas.width = width; | |
412 canvas.height = height; | |
413 | |
414 var context = | |
415 /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); | |
416 var imageData = context.getImageData(0, 0, width, height); | |
417 base.debug.assert(srcArrayBuffer instanceof ArrayBuffer); | |
418 var src = new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer)); | |
419 var dest = imageData.data; | |
420 for (var i = 0; i < /** @type {number} */(dest.length); i += 4) { | |
421 dest[i] = src[i + 2]; | |
422 dest[i + 1] = src[i + 1]; | |
423 dest[i + 2] = src[i]; | |
424 dest[i + 3] = src[i + 3]; | |
425 } | |
426 | |
427 context.putImageData(imageData, 0, 0); | |
428 this.updateMouseCursorImage(canvas.toDataURL(), hotspotX, hotspotY); | |
429 } | |
430 }; | |
431 | |
432 /** | |
433 * Deletes the plugin. | |
434 */ | |
435 remoting.ClientPlugin.prototype.cleanup = function() { | |
436 if (this.plugin_) { | |
437 this.plugin_.parentNode.removeChild(this.plugin_); | |
438 this.plugin_ = null; | |
439 } | |
440 }; | |
441 | |
442 /** | |
443 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin. | |
444 */ | |
445 remoting.ClientPlugin.prototype.element = function() { | |
446 return this.plugin_; | |
447 }; | |
448 | |
449 /** | |
450 * @param {function(boolean): void} onDone | |
451 */ | |
452 remoting.ClientPlugin.prototype.initialize = function(onDone) { | |
453 if (this.helloReceived_) { | |
454 onDone(true); | |
455 } else { | |
456 this.onInitializedCallback_ = onDone; | |
457 } | |
458 }; | |
459 | |
460 /** | |
461 * @return {boolean} True if the plugin and web-app versions are compatible. | |
462 */ | |
463 remoting.ClientPlugin.prototype.isSupportedVersion = function() { | |
464 if (!this.helloReceived_) { | |
465 console.error( | |
466 "isSupportedVersion() is called before the plugin is initialized."); | |
467 return false; | |
468 } | |
469 return this.API_VERSION_ >= this.pluginApiMinVersion_ && | |
470 this.pluginApiVersion_ >= this.API_MIN_VERSION_; | |
471 }; | |
472 | |
473 /** | |
474 * @param {remoting.ClientPlugin.Feature} feature The feature to test for. | |
475 * @return {boolean} True if the plugin supports the named feature. | |
476 */ | |
477 remoting.ClientPlugin.prototype.hasFeature = function(feature) { | |
478 if (!this.helloReceived_) { | |
479 console.error( | |
480 "hasFeature() is called before the plugin is initialized."); | |
481 return false; | |
482 } | |
483 return this.pluginApiFeatures_.indexOf(feature) > -1; | |
484 }; | |
485 | |
486 /** | |
487 * @return {boolean} True if the plugin supports the injectKeyEvent API. | |
488 */ | |
489 remoting.ClientPlugin.prototype.isInjectKeyEventSupported = function() { | |
490 return this.pluginApiVersion_ >= 6; | |
491 }; | |
492 | |
493 /** | |
494 * @param {string} iq Incoming IQ stanza. | |
495 */ | |
496 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) { | |
497 if (this.plugin_ && this.plugin_.postMessage) { | |
498 this.plugin_.postMessage(JSON.stringify( | |
499 { method: 'incomingIq', data: { iq: iq } })); | |
500 } else { | |
501 // plugin.onIq may not be set after the plugin has been shut | |
502 // down. Particularly this happens when we receive response to | |
503 // session-terminate stanza. | |
504 console.warn('plugin.onIq is not set so dropping incoming message.'); | |
505 } | |
506 }; | |
507 | 50 |
508 /** | 51 /** |
509 * @param {string} hostJid The jid of the host to connect to. | 52 * @param {string} hostJid The jid of the host to connect to. |
510 * @param {string} hostPublicKey The base64 encoded version of the host's | 53 * @param {string} hostPublicKey The base64 encoded version of the host's |
511 * public key. | 54 * public key. |
512 * @param {string} localJid Local jid. | 55 * @param {string} localJid Local jid. |
513 * @param {string} sharedSecret The access code for IT2Me or the PIN | 56 * @param {string} sharedSecret The access code for IT2Me or the PIN |
514 * for Me2Me. | 57 * for Me2Me. |
515 * @param {string} authenticationMethods Comma-separated list of | 58 * @param {string} authenticationMethods Comma-separated list of |
516 * authentication methods the client should attempt to use. | 59 * authentication methods the client should attempt to use. |
517 * @param {string} authenticationTag A host-specific tag to mix into | 60 * @param {string} authenticationTag A host-specific tag to mix into |
518 * authentication hashes. | 61 * authentication hashes. |
519 * @param {string} clientPairingId For paired Me2Me connections, the | 62 * @param {string} clientPairingId For paired Me2Me connections, the |
520 * pairing id for this client, as issued by the host. | 63 * pairing id for this client, as issued by the host. |
521 * @param {string} clientPairedSecret For paired Me2Me connections, the | 64 * @param {string} clientPairedSecret For paired Me2Me connections, the |
522 * paired secret for this client, as issued by the host. | 65 * paired secret for this client, as issued by the host. |
523 */ | 66 */ |
524 remoting.ClientPlugin.prototype.connect = function( | 67 remoting.ClientPlugin.prototype.connect = function( |
525 hostJid, hostPublicKey, localJid, sharedSecret, | 68 hostJid, hostPublicKey, localJid, sharedSecret, |
526 authenticationMethods, authenticationTag, | 69 authenticationMethods, authenticationTag, |
527 clientPairingId, clientPairedSecret) { | 70 clientPairingId, clientPairedSecret) {}; |
528 var keyFilter = ''; | |
529 if (remoting.platformIsMac()) { | |
530 keyFilter = 'mac'; | |
531 } else if (remoting.platformIsChromeOS()) { | |
532 keyFilter = 'cros'; | |
533 } | |
534 this.plugin_.postMessage(JSON.stringify( | |
535 { method: 'delegateLargeCursors', data: {} })); | |
536 this.plugin_.postMessage(JSON.stringify( | |
537 { method: 'connect', data: { | |
538 hostJid: hostJid, | |
539 hostPublicKey: hostPublicKey, | |
540 localJid: localJid, | |
541 sharedSecret: sharedSecret, | |
542 authenticationMethods: authenticationMethods, | |
543 authenticationTag: authenticationTag, | |
544 capabilities: this.capabilities_.join(" "), | |
545 clientPairingId: clientPairingId, | |
546 clientPairedSecret: clientPairedSecret, | |
547 keyFilter: keyFilter | |
548 } | |
549 })); | |
550 }; | |
551 | 71 |
552 /** | 72 /** |
553 * Release all currently pressed keys. | 73 * @param {number} key The keycode to inject. |
74 * @param {boolean} down True for press; false for a release. | |
554 */ | 75 */ |
555 remoting.ClientPlugin.prototype.releaseAllKeys = function() { | 76 remoting.ClientPlugin.prototype.injectKeyEvent = |
556 this.plugin_.postMessage(JSON.stringify( | 77 function(key, down) {}; |
557 { method: 'releaseAllKeys', data: {} })); | |
558 }; | |
559 | 78 |
560 /** | 79 /** |
561 * Send a key event to the host. | 80 * @param {number} from |
562 * | 81 * @param {number} to |
563 * @param {number} usbKeycode The USB-style code of the key to inject. | |
564 * @param {boolean} pressed True to inject a key press, False for a release. | |
565 */ | 82 */ |
566 remoting.ClientPlugin.prototype.injectKeyEvent = | 83 remoting.ClientPlugin.prototype.remapKey = function(from, to) {}; |
567 function(usbKeycode, pressed) { | |
568 this.plugin_.postMessage(JSON.stringify( | |
569 { method: 'injectKeyEvent', data: { | |
570 'usbKeycode': usbKeycode, | |
571 'pressed': pressed} | |
572 })); | |
573 }; | |
574 | 84 |
575 /** | 85 /** |
576 * Remap one USB keycode to another in all subsequent key events. | 86 * Release all keys currently being pressed. |
577 * | |
578 * @param {number} fromKeycode The USB-style code of the key to remap. | |
579 * @param {number} toKeycode The USB-style code to remap the key to. | |
580 */ | 87 */ |
581 remoting.ClientPlugin.prototype.remapKey = | 88 remoting.ClientPlugin.prototype.releaseAllKeys = function() {}; |
582 function(fromKeycode, toKeycode) { | |
583 this.plugin_.postMessage(JSON.stringify( | |
584 { method: 'remapKey', data: { | |
585 'fromKeycode': fromKeycode, | |
586 'toKeycode': toKeycode} | |
587 })); | |
588 }; | |
589 | 89 |
590 /** | 90 /** |
591 * Enable/disable redirection of the specified key to the web-app. | 91 * @param {number} width |
592 * | 92 * @param {number} height |
593 * @param {number} keycode The USB-style code of the key. | 93 * @param {number} dpi |
594 * @param {Boolean} trap True to enable trapping, False to disable. | |
595 */ | 94 */ |
596 remoting.ClientPlugin.prototype.trapKey = function(keycode, trap) { | 95 remoting.ClientPlugin.prototype.notifyClientResolution = |
597 this.plugin_.postMessage(JSON.stringify( | 96 function(width, height, dpi) {}; |
598 { method: 'trapKey', data: { | |
599 'keycode': keycode, | |
600 'trap': trap} | |
601 })); | |
602 }; | |
603 | 97 |
604 /** | 98 /** |
605 * Returns an associative array with a set of stats for this connecton. | 99 * @param {string} iq |
100 */ | |
101 remoting.ClientPlugin.prototype.onIncomingIq = function(iq) {}; | |
102 | |
103 /** | |
104 * @return {boolean} True if the web-app and plugin are compatible. | |
105 */ | |
106 remoting.ClientPlugin.prototype.isSupportedVersion = function() {}; | |
107 | |
108 /** | |
109 * @param {remoting.ClientPlugin.Feature} feature | |
110 * @return {boolean} True if the plugin support the specified feature. | |
111 */ | |
112 remoting.ClientPlugin.prototype.hasFeature = function(feature) {}; | |
113 | |
114 /** | |
115 * Enable MediaSource rendering via the specified renderer. | |
606 * | 116 * |
607 * @return {remoting.ClientSession.PerfStats} The connection statistics. | 117 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer |
608 */ | 118 */ |
609 remoting.ClientPlugin.prototype.getPerfStats = function() { | 119 remoting.ClientPlugin.prototype.enableMediaSourceRendering = |
610 return this.perfStats_; | 120 function(mediaSourceRenderer) {}; |
611 }; | |
612 | 121 |
613 /** | 122 /** |
614 * Sends a clipboard item to the host. | 123 * Sends a clipboard item to the host. |
615 * | 124 * |
616 * @param {string} mimeType The MIME type of the clipboard item. | 125 * @param {string} mimeType The MIME type of the clipboard item. |
617 * @param {string} item The clipboard item. | 126 * @param {string} item The clipboard item. |
618 */ | 127 */ |
619 remoting.ClientPlugin.prototype.sendClipboardItem = | 128 remoting.ClientPlugin.prototype.sendClipboardItem = |
620 function(mimeType, item) { | 129 function(mimeType, item) {}; |
621 if (!this.hasFeature(remoting.ClientPlugin.Feature.SEND_CLIPBOARD_ITEM)) | |
622 return; | |
623 this.plugin_.postMessage(JSON.stringify( | |
624 { method: 'sendClipboardItem', | |
625 data: { mimeType: mimeType, item: item }})); | |
626 }; | |
627 | 130 |
628 /** | 131 /** |
629 * Notifies the host that the client has the specified size and pixel density. | 132 * Tell the plugin to request a PIN asynchronously. |
630 * | |
631 * @param {number} width The available client width in DIPs. | |
632 * @param {number} height The available client height in DIPs. | |
633 * @param {number} device_scale The number of device pixels per DIP. | |
634 */ | 133 */ |
635 remoting.ClientPlugin.prototype.notifyClientResolution = | 134 remoting.ClientPlugin.prototype.useAsyncPinDialog = function() {}; |
636 function(width, height, device_scale) { | |
637 if (this.hasFeature(remoting.ClientPlugin.Feature.NOTIFY_CLIENT_RESOLUTION)) { | |
638 var dpi = Math.floor(device_scale * 96); | |
639 this.plugin_.postMessage(JSON.stringify( | |
640 { method: 'notifyClientResolution', | |
641 data: { width: Math.floor(width * device_scale), | |
642 height: Math.floor(height * device_scale), | |
643 x_dpi: dpi, y_dpi: dpi }})); | |
644 } | |
645 }; | |
646 | 135 |
647 /** | 136 /** |
648 * Requests that the host pause or resume sending video updates. | 137 * Request that this client be paired with the current host. |
649 * | 138 * |
650 * @param {boolean} pause True to suspend video updates, false otherwise. | 139 * @param {string} clientName The human-readable name of the client. |
140 * @param {function(string, string):void} onDone Callback to receive the | |
141 * client id and shared secret when they are available. | |
651 */ | 142 */ |
652 remoting.ClientPlugin.prototype.pauseVideo = | 143 remoting.ClientPlugin.prototype.requestPairing = |
653 function(pause) { | 144 function(clientName, onDone) {}; |
654 if (this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) { | |
655 this.plugin_.postMessage(JSON.stringify( | |
656 { method: 'videoControl', data: { pause: pause }})); | |
657 } else if (this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_VIDEO)) { | |
658 this.plugin_.postMessage(JSON.stringify( | |
659 { method: 'pauseVideo', data: { pause: pause }})); | |
660 } | |
661 }; | |
662 | |
663 /** | |
664 * Requests that the host pause or resume sending audio updates. | |
665 * | |
666 * @param {boolean} pause True to suspend audio updates, false otherwise. | |
667 */ | |
668 remoting.ClientPlugin.prototype.pauseAudio = | |
669 function(pause) { | |
670 if (!this.hasFeature(remoting.ClientPlugin.Feature.PAUSE_AUDIO)) { | |
671 return; | |
672 } | |
673 this.plugin_.postMessage(JSON.stringify( | |
674 { method: 'pauseAudio', data: { pause: pause }})); | |
675 }; | |
676 | |
677 /** | |
678 * Requests that the host configure the video codec for lossless encode. | |
679 * | |
680 * @param {boolean} wantLossless True to request lossless encoding. | |
681 */ | |
682 remoting.ClientPlugin.prototype.setLosslessEncode = | |
683 function(wantLossless) { | |
684 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) { | |
685 return; | |
686 } | |
687 this.plugin_.postMessage(JSON.stringify( | |
688 { method: 'videoControl', data: { losslessEncode: wantLossless }})); | |
689 }; | |
690 | |
691 /** | |
692 * Requests that the host configure the video codec for lossless color. | |
693 * | |
694 * @param {boolean} wantLossless True to request lossless color. | |
695 */ | |
696 remoting.ClientPlugin.prototype.setLosslessColor = | |
697 function(wantLossless) { | |
698 if (!this.hasFeature(remoting.ClientPlugin.Feature.VIDEO_CONTROL)) { | |
699 return; | |
700 } | |
701 this.plugin_.postMessage(JSON.stringify( | |
702 { method: 'videoControl', data: { losslessColor: wantLossless }})); | |
703 }; | |
704 | 145 |
705 /** | 146 /** |
706 * Called when a PIN is obtained from the user. | 147 * Called when a PIN is obtained from the user. |
707 * | 148 * |
708 * @param {string} pin The PIN. | 149 * @param {string} pin The PIN. |
709 */ | 150 */ |
710 remoting.ClientPlugin.prototype.onPinFetched = | 151 remoting.ClientPlugin.prototype.onPinFetched = function(pin) {}; |
711 function(pin) { | |
712 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { | |
713 return; | |
714 } | |
715 this.plugin_.postMessage(JSON.stringify( | |
716 { method: 'onPinFetched', data: { pin: pin }})); | |
717 }; | |
718 | |
719 /** | |
720 * Tells the plugin to ask for the PIN asynchronously. | |
721 */ | |
722 remoting.ClientPlugin.prototype.useAsyncPinDialog = | |
723 function() { | |
724 if (!this.hasFeature(remoting.ClientPlugin.Feature.ASYNC_PIN)) { | |
725 return; | |
726 } | |
727 this.plugin_.postMessage(JSON.stringify( | |
728 { method: 'useAsyncPinDialog', data: {} })); | |
729 }; | |
730 | 152 |
731 /** | 153 /** |
732 * Sets the third party authentication token and shared secret. | 154 * Sets the third party authentication token and shared secret. |
733 * | 155 * |
734 * @param {string} token The token received from the token URL. | 156 * @param {string} token The token received from the token URL. |
735 * @param {string} sharedSecret Shared secret received from the token URL. | 157 * @param {string} sharedSecret Shared secret received from the token URL. |
736 */ | 158 */ |
737 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = function( | 159 remoting.ClientPlugin.prototype.onThirdPartyTokenFetched = |
738 token, sharedSecret) { | 160 function(token, sharedSecret) {}; |
739 this.plugin_.postMessage(JSON.stringify( | |
740 { method: 'onThirdPartyTokenFetched', | |
741 data: { token: token, sharedSecret: sharedSecret}})); | |
742 }; | |
743 | 161 |
744 /** | 162 /** |
745 * Request pairing with the host for PIN-less authentication. | 163 * @param {boolean} pause True to pause the audio stream; false to resume it. |
746 * | |
747 * @param {string} clientName The human-readable name of the client. | |
748 * @param {function(string, string):void} onDone, Callback to receive the | |
749 * client id and shared secret when they are available. | |
750 */ | 164 */ |
751 remoting.ClientPlugin.prototype.requestPairing = | 165 remoting.ClientPlugin.prototype.pauseAudio = function(pause) {}; |
752 function(clientName, onDone) { | 166 |
753 if (!this.hasFeature(remoting.ClientPlugin.Feature.PINLESS_AUTH)) { | 167 /** |
754 return; | 168 * @param {boolean} pause True to pause the video stream; false to resume it. |
755 } | 169 */ |
756 this.onPairingComplete_ = onDone; | 170 remoting.ClientPlugin.prototype.pauseVideo = function(pause) {}; |
757 this.plugin_.postMessage(JSON.stringify( | 171 |
758 { method: 'requestPairing', data: { clientName: clientName } })); | 172 /** |
759 }; | 173 * @return {remoting.ClientSession.PerfStats} A summary of the connection |
174 * performance. | |
175 */ | |
176 remoting.ClientPlugin.prototype.getPerfStats = function() {}; | |
760 | 177 |
761 /** | 178 /** |
762 * Send an extension message to the host. | 179 * Send an extension message to the host. |
763 * | 180 * |
764 * @param {string} type The message type. | 181 * @param {string} name |
765 * @param {string} message The message payload. | 182 * @param {string} data |
766 */ | 183 */ |
767 remoting.ClientPlugin.prototype.sendClientMessage = | 184 remoting.ClientPlugin.prototype.sendClientMessage = |
768 function(type, message) { | 185 function(name, data) {}; |
769 if (!this.hasFeature(remoting.ClientPlugin.Feature.EXTENSION_MESSAGE)) { | |
770 return; | |
771 } | |
772 this.plugin_.postMessage(JSON.stringify( | |
773 { method: 'extensionMessage', | |
774 data: { type: type, data: message } })); | |
775 | 186 |
187 /** | |
188 * @param {function(string):void} handler Callback for sending an IQ stanza. | |
189 */ | |
190 remoting.ClientPlugin.prototype.setOnOutgoingIqHandler = | |
191 function(handler) {}; | |
192 | |
193 /** | |
194 * @param {function(string):void} handler Callback for logging debug messages. | |
195 */ | |
196 remoting.ClientPlugin.prototype.setOnDebugMessageHandler = | |
197 function(handler) {}; | |
198 | |
199 /** | |
200 * @param {function(number, number):void} handler Callback for connection status | |
201 * update notifications. The first parameter is the connection state; the | |
202 * second is the error code, if any. | |
203 */ | |
204 remoting.ClientPlugin.prototype.setConnectionStatusUpdateHandler = | |
205 function(handler) {}; | |
206 | |
207 /** | |
208 * @param {function(boolean):void} handler Callback for connection readiness | |
209 * notifications. | |
210 */ | |
211 remoting.ClientPlugin.prototype.setConnectionReadyHandler = | |
212 function(handler) {}; | |
213 | |
214 /** | |
215 * @param {function():void} handler Callback for desktop size change | |
216 * notifications. | |
217 */ | |
218 remoting.ClientPlugin.prototype.setDesktopSizeUpdateHandler = | |
219 function(handler) {}; | |
220 | |
221 /** | |
222 * @param {function(!Array.<string>):void} handler Callback to inform of | |
223 * capabilities negotiated between host and client. | |
224 */ | |
225 remoting.ClientPlugin.prototype.setCapabilitiesHandler = | |
226 function(handler) {}; | |
227 | |
228 /** | |
229 * @param {function(string):void} handler Callback for processing security key | |
230 * (Gnubby) protocol messages. | |
231 */ | |
232 remoting.ClientPlugin.prototype.setGnubbyAuthHandler = | |
233 function(handler) {}; | |
234 | |
235 /** | |
236 * @param {function(string):void} handler Callback for processing Cast protocol | |
237 * messages. | |
238 */ | |
239 remoting.ClientPlugin.prototype.setCastExtensionHandler = | |
240 function(handler) {}; | |
241 | |
242 /** | |
243 * @param {function(string, number, number):void} handler Callback for | |
244 * processing large mouse cursor images. The first parameter is a data: | |
245 * URL encoding the mouse cursor; the second and third parameters are | |
246 * the cursor hotspot's x- and y-coordinates, respectively. | |
247 */ | |
248 remoting.ClientPlugin.prototype.setMouseCursorHandler = | |
249 function(handler) {}; | |
250 | |
251 /** | |
252 * @param {function(string, string, string):void} handler Callback for | |
253 * fetching third-party tokens. The first parameter is the token URL; the | |
254 * second is the public key of the host; the third is the OAuth2 scope | |
255 * being requested. | |
256 */ | |
257 remoting.ClientPlugin.prototype.setFetchThirdPartyTokenHandler = | |
258 function(handler) {}; | |
259 | |
260 /** | |
261 * @param {function(boolean):void} handler Callback for fetching a PIN from | |
262 * the user. The parameter is true if PIN pairing is supported by the | |
263 * host, or false otherwise. | |
264 */ | |
265 remoting.ClientPlugin.prototype.setFetchPinHandler = | |
266 function(handler) {}; | |
267 | |
268 | |
269 /** | |
270 * Set of features for which hasFeature() can be used to test. | |
271 * | |
272 * @enum {string} | |
273 */ | |
274 remoting.ClientPlugin.Feature = { | |
Jamie
2014/09/19 23:55:44
This enum was defined on the implementation in the
| |
275 INJECT_KEY_EVENT: 'injectKeyEvent', | |
276 NOTIFY_CLIENT_RESOLUTION: 'notifyClientResolution', | |
277 ASYNC_PIN: 'asyncPin', | |
278 PAUSE_VIDEO: 'pauseVideo', | |
279 PAUSE_AUDIO: 'pauseAudio', | |
280 REMAP_KEY: 'remapKey', | |
281 SEND_CLIPBOARD_ITEM: 'sendClipboardItem', | |
282 THIRD_PARTY_AUTH: 'thirdPartyAuth', | |
283 TRAP_KEY: 'trapKey', | |
284 PINLESS_AUTH: 'pinlessAuth', | |
285 EXTENSION_MESSAGE: 'extensionMessage', | |
286 MEDIA_SOURCE_RENDERING: 'mediaSourceRendering', | |
287 VIDEO_CONTROL: 'videoControl' | |
776 }; | 288 }; |
777 | 289 |
778 /** | |
779 * Request MediaStream-based rendering. | |
780 * | |
781 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer | |
782 */ | |
783 remoting.ClientPlugin.prototype.enableMediaSourceRendering = | |
784 function(mediaSourceRenderer) { | |
785 if (!this.hasFeature(remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) { | |
786 return; | |
787 } | |
788 this.mediaSourceRenderer_ = mediaSourceRenderer; | |
789 this.plugin_.postMessage(JSON.stringify( | |
790 { method: 'enableMediaSourceRendering', data: {} })); | |
791 }; | |
792 | 290 |
793 /** | 291 /** |
794 * If we haven't yet received a "hello" message from the plugin, change its | 292 * @interface |
795 * size so that the user can confirm it if click-to-play is enabled, or can | |
796 * see the "this plugin is disabled" message if it is actually disabled. | |
797 * @private | |
798 */ | 293 */ |
799 remoting.ClientPlugin.prototype.showPluginForClickToPlay_ = function() { | 294 remoting.ClientPluginFactory = function() {}; |
800 if (!this.helloReceived_) { | |
801 var width = 200; | |
802 var height = 200; | |
803 this.plugin_.style.width = width + 'px'; | |
804 this.plugin_.style.height = height + 'px'; | |
805 // Center the plugin just underneath the "Connnecting..." dialog. | |
806 var dialog = document.getElementById('client-dialog'); | |
807 var dialogRect = dialog.getBoundingClientRect(); | |
808 this.plugin_.style.top = (dialogRect.bottom + 16) + 'px'; | |
809 this.plugin_.style.left = (window.innerWidth - width) / 2 + 'px'; | |
810 this.plugin_.style.position = 'fixed'; | |
811 } | |
812 }; | |
813 | 295 |
814 /** | 296 /** |
815 * Undo the CSS rules needed to make the plugin clickable for click-to-play. | 297 * @param {Element} container The container for the embed element. |
816 * @private | 298 * @param {function(string, string):boolean} onExtensionMessage The handler for |
299 * protocol extension messages. Returns true if a message is recognized; | |
300 * false otherwise. | |
301 * @return {remoting.ClientPlugin} A new client plugin instance. | |
817 */ | 302 */ |
818 remoting.ClientPlugin.prototype.hidePluginForClickToPlay_ = function() { | 303 remoting.ClientPluginFactory.prototype.createPlugin = |
819 this.plugin_.style.width = ''; | 304 function(container, onExtensionMessage) {}; |
820 this.plugin_.style.height = ''; | 305 |
821 this.plugin_.style.top = ''; | 306 /** |
822 this.plugin_.style.left = ''; | 307 * Preload the plugin to make instantiation faster when the user tries |
823 this.plugin_.style.position = ''; | 308 * to connect. |
824 }; | 309 */ |
310 remoting.ClientPluginFactory.prototype.preloadPlugin = function() {}; | |
Jamie
2014/09/19 23:55:44
This was previously a static member of the impleme
| |
311 | |
312 /** | |
313 * @type {remoting.ClientPluginFactory} | |
314 */ | |
315 remoting.ClientPlugin.factory = null; | |
OLD | NEW |