var remoting = remoting || {};
/**
+ * True if Cast capability is supported.
+ *
+ * @type {boolean}
+ */
+remoting.enableCast = false;
+
+/**
+ * @param {remoting.SignalStrategy} signalStrategy Signal strategy.
* @param {HTMLElement} container Container element for the client view.
* @param {string} hostDisplayName A human-readable name for the host.
* @param {string} accessCode The IT2Me access code. Blank for Me2Me.
* @constructor
* @extends {base.EventSource}
*/
-remoting.ClientSession = function(container, hostDisplayName, accessCode,
- fetchPin, fetchThirdPartyToken,
+remoting.ClientSession = function(signalStrategy, container, hostDisplayName,
+ accessCode, fetchPin, fetchThirdPartyToken,
authenticationMethods, hostId, hostJid,
hostPublicKey, mode, clientPairingId,
clientPairedSecret) {
this.hasReceivedFrame_ = false;
this.logToServer = new remoting.LogToServer();
+ /** @private */
+ this.signalStrategy_ = signalStrategy;
+ base.debug.assert(this.signalStrategy_.getState() ==
+ remoting.SignalStrategy.State.CONNECTED);
+ this.signalStrategy_.setIncomingStanzaCallback(
+ this.onIncomingMessage_.bind(this));
+ remoting.formatIq.setJids(this.signalStrategy_.getJid(), hostJid);
+
/** @type {number?} @private */
this.notifyClientResolutionTimer_ = null;
/** @type {number?} @private */
/** @private */
this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this);
/** @private */
- this.callToggleFullScreen_ = remoting.fullscreen.toggle.bind(
- remoting.fullscreen);
- /** @private */
this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this)
- /** @private */
- this.screenOptionsMenu_ = new remoting.MenuButton(
- document.getElementById('screen-options-menu'),
- this.onShowOptionsMenu_.bind(this));
- /** @private */
- this.sendKeysMenu_ = new remoting.MenuButton(
- document.getElementById('send-keys-menu')
- );
-
/** @type {HTMLMediaElement} @private */
this.video_ = null;
img.style.left = event.x + 'px';
};
- /** @type {HTMLElement} @private */
- this.resizeToClientButton_ =
- document.getElementById('screen-resize-to-client');
- /** @type {HTMLElement} @private */
- this.shrinkToFitButton_ = document.getElementById('screen-shrink-to-fit');
- /** @type {HTMLElement} @private */
- this.fullScreenButton_ = document.getElementById('toggle-full-screen');
-
/** @type {remoting.GnubbyAuthHandler} @private */
this.gnubbyAuthHandler_ = null;
- if (this.mode_ == remoting.ClientSession.Mode.IT2ME) {
- // Resize-to-client is not supported for IT2Me hosts.
- this.resizeToClientButton_.hidden = true;
- } else {
- this.resizeToClientButton_.hidden = false;
- }
+ /** @type {remoting.CastExtensionHandler} @private */
+ this.castExtensionHandler_ = null;
+
+ /** @type {remoting.VideoFrameRecorder} @private */
+ this.videoFrameRecorder_ = null;
- this.fullScreenButton_.addEventListener(
- 'click', this.callToggleFullScreen_, false);
this.defineEvents(Object.keys(remoting.ClientSession.Events));
};
// Determine whether or not horizontal or vertical scrollbars are
// required, taking into account their width.
var clientArea = this.getClientArea_();
- needsVerticalScroll = clientArea.height < this.plugin_.desktopHeight;
- needsHorizontalScroll = clientArea.width < this.plugin_.desktopWidth;
+ needsVerticalScroll = clientArea.height < this.plugin_.getDesktopHeight();
+ needsHorizontalScroll = clientArea.width < this.plugin_.getDesktopWidth();
var kScrollBarWidth = 16;
if (needsHorizontalScroll && !needsVerticalScroll) {
needsVerticalScroll =
- clientArea.height - kScrollBarWidth < this.plugin_.desktopHeight;
+ clientArea.height - kScrollBarWidth < this.plugin_.getDesktopHeight();
} else if (!needsHorizontalScroll && needsVerticalScroll) {
needsHorizontalScroll =
- clientArea.width - kScrollBarWidth < this.plugin_.desktopWidth;
+ clientArea.width - kScrollBarWidth < this.plugin_.getDesktopWidth();
}
}
// this.plugin_.notifyClientResolution().
SEND_INITIAL_RESOLUTION: 'sendInitialResolution',
RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests',
- VIDEO_RECORDER: 'videoRecorder'
+ VIDEO_RECORDER: 'videoRecorder',
+ CAST: 'casting'
};
/**
*/
remoting.ClientSession.prototype.createPluginAndConnect =
function(onExtensionMessage) {
- this.plugin_ = new remoting.ClientPlugin(
+ this.plugin_ = remoting.ClientPlugin.factory.createPlugin(
this.container_.querySelector('.client-plugin-container'),
onExtensionMessage);
remoting.HostSettings.load(this.hostId_,
* @param {remoting.Error} error
*/
remoting.ClientSession.prototype.resetWithError_ = function(error) {
- this.plugin_.cleanup();
+ this.signalStrategy_.setIncomingStanzaCallback(null);
+ this.plugin_.dispose();
this.plugin_ = null;
this.error_ = error;
this.setState_(remoting.ClientSession.State.FAILED);
this.applyRemapKeys_(true);
}
-
// Enable MediaSource-based rendering on Chrome 37 and above.
var chromeVersionMajor =
parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10);
this.container_.classList.remove('mediasource-rendering');
}
- /** @param {string} msg The IQ stanza to send. */
- this.plugin_.onOutgoingIqHandler = this.sendIq_.bind(this);
- /** @param {string} msg The message to log. */
- this.plugin_.onDebugMessageHandler = function(msg) {
- console.log('plugin: ' + msg.trimRight());
- };
+ this.plugin_.setOnOutgoingIqHandler(this.sendIq_.bind(this));
+ this.plugin_.setOnDebugMessageHandler(
+ /** @param {string} msg */
+ function(msg) {
+ console.log('plugin: ' + msg.trimRight());
+ });
- this.plugin_.onConnectionStatusUpdateHandler =
- this.onConnectionStatusUpdate_.bind(this);
- this.plugin_.onConnectionReadyHandler = this.onConnectionReady_.bind(this);
- this.plugin_.onDesktopSizeUpdateHandler =
- this.onDesktopSizeChanged_.bind(this);
- this.plugin_.onSetCapabilitiesHandler = this.onSetCapabilities_.bind(this);
- this.plugin_.onGnubbyAuthHandler = this.processGnubbyAuthMessage_.bind(this);
- this.plugin_.updateMouseCursorImage = this.updateMouseCursorImage_.bind(this);
+ this.plugin_.setConnectionStatusUpdateHandler(
+ this.onConnectionStatusUpdate_.bind(this));
+ this.plugin_.setConnectionReadyHandler(this.onConnectionReady_.bind(this));
+ this.plugin_.setDesktopSizeUpdateHandler(
+ this.onDesktopSizeChanged_.bind(this));
+ this.plugin_.setCapabilitiesHandler(this.onSetCapabilities_.bind(this));
+ this.plugin_.setGnubbyAuthHandler(
+ this.processGnubbyAuthMessage_.bind(this));
+ this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this));
+ this.plugin_.setCastExtensionHandler(
+ this.processCastExtensionMessage_.bind(this));
this.initiateConnection_();
};
'focus', this.callPluginGotFocus_, false);
this.plugin_.element().removeEventListener(
'blur', this.callPluginLostFocus_, false);
- this.plugin_.cleanup();
+ this.plugin_.dispose();
this.plugin_ = null;
}
- // Delete event handlers that aren't relevent when not connected.
- this.fullScreenButton_.removeEventListener(
- 'click', this.callToggleFullScreen_, false);
-
// Leave full-screen mode, and stop listening for related events.
var listener = this.callOnFullScreenChanged_;
- remoting.fullscreen.syncWithMaximize(false);
remoting.fullscreen.activate(
false,
function() {
} else {
remoting.toolbar.setClientSession(null);
}
+ remoting.optionsMenu.setClientSession(null);
+ document.body.classList.remove('connected');
// Remove mediasource-rendering class from the container - this will also
// hide the <video> element.
* @return {void} Nothing.
*/
remoting.ClientSession.prototype.cleanup = function() {
- remoting.wcsSandbox.setOnIq(null);
this.sendIq_(
'<cli:iq ' +
'to="' + this.hostJid_ + '" ' +
};
/**
- * Sends an IQ stanza via the http xmpp proxy.
+ * Sends a signaling message.
*
* @private
- * @param {string} msg XML string of IQ stanza to send to server.
+ * @param {string} message XML string of IQ stanza to send to server.
* @return {void} Nothing.
*/
-remoting.ClientSession.prototype.sendIq_ = function(msg) {
+remoting.ClientSession.prototype.sendIq_ = function(message) {
// Extract the session id, so we can close the session later.
var parser = new DOMParser();
- var iqNode = parser.parseFromString(msg, 'text/xml').firstChild;
+ var iqNode = parser.parseFromString(message, 'text/xml').firstChild;
var jingleNode = iqNode.firstChild;
if (jingleNode) {
var action = jingleNode.getAttribute('action');
}
}
- // HACK: Add 'x' prefix to the IDs of the outgoing messages to make sure that
- // stanza IDs used by host and client do not match. This is necessary to
- // workaround bug in the signaling endpoint used by chromoting.
- // TODO(sergeyu): Remove this hack once the server-side bug is fixed.
- var type = iqNode.getAttribute('type');
- if (type == 'set') {
- var id = iqNode.getAttribute('id');
- iqNode.setAttribute('id', 'x' + id);
- msg = (new XMLSerializer()).serializeToString(iqNode);
+ console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(message));
+ if (this.signalStrategy_.getState() !=
+ remoting.SignalStrategy.State.CONNECTED) {
+ console.log("Message above is dropped because signaling is not connected.");
+ return;
}
- console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(msg));
-
- // Send the stanza.
- remoting.wcsSandbox.sendIq(msg);
+ this.signalStrategy_.sendMessage(message);
};
-remoting.ClientSession.prototype.initiateConnection_ = function() {
- /** @type {remoting.ClientSession} */
- var that = this;
-
- remoting.wcsSandbox.connect(onWcsConnected, this.resetWithError_.bind(this));
-
- /** @param {string} localJid Local JID. */
- function onWcsConnected(localJid) {
- that.connectPluginToWcs_(localJid);
- that.getSharedSecret_(onSharedSecretReceived.bind(null, localJid));
+/**
+ * @private
+ * @param {Element} message
+ */
+remoting.ClientSession.prototype.onIncomingMessage_ = function(message) {
+ if (!this.plugin_) {
+ return;
}
-
- /** @param {string} localJid Local JID.
- * @param {string} sharedSecret Shared secret. */
- function onSharedSecretReceived(localJid, sharedSecret) {
- that.plugin_.connect(
- that.hostJid_, that.hostPublicKey_, localJid, sharedSecret,
- that.authenticationMethods_, that.hostId_, that.clientPairingId_,
- that.clientPairedSecret_);
- };
+ var formatted = new XMLSerializer().serializeToString(message);
+ console.log(remoting.timestamp(),
+ remoting.formatIq.prettifyReceiveIq(formatted));
+ this.plugin_.onIncomingIq(formatted);
}
/**
- * Connects the plugin to WCS.
- *
* @private
- * @param {string} localJid Local JID.
- * @return {void} Nothing.
*/
-remoting.ClientSession.prototype.connectPluginToWcs_ = function(localJid) {
- remoting.formatIq.setJids(localJid, this.hostJid_);
- var forwardIq = this.plugin_.onIncomingIq.bind(this.plugin_);
- /** @param {string} stanza The IQ stanza received. */
- var onIncomingIq = function(stanza) {
- // HACK: Remove 'x' prefix added to the id in sendIq_().
- try {
- var parser = new DOMParser();
- var iqNode = parser.parseFromString(stanza, 'text/xml').firstChild;
- var type = iqNode.getAttribute('type');
- var id = iqNode.getAttribute('id');
- if (type != 'set' && id.charAt(0) == 'x') {
- iqNode.setAttribute('id', id.substr(1));
- stanza = (new XMLSerializer()).serializeToString(iqNode);
- }
- } catch (err) {
- // Pass message as is when it is malformed.
- }
+remoting.ClientSession.prototype.initiateConnection_ = function() {
+ /** @type {remoting.ClientSession} */
+ var that = this;
- console.log(remoting.timestamp(),
- remoting.formatIq.prettifyReceiveIq(stanza));
- forwardIq(stanza);
+ /** @param {string} sharedSecret Shared secret. */
+ function onSharedSecretReceived(sharedSecret) {
+ that.plugin_.connect(
+ that.hostJid_, that.hostPublicKey_, that.signalStrategy_.getJid(),
+ sharedSecret, that.authenticationMethods_, that.hostId_,
+ that.clientPairingId_, that.clientPairedSecret_);
};
- remoting.wcsSandbox.setOnIq(onIncomingIq);
+
+ this.getSharedSecret_(onSharedSecretReceived);
}
/**
tokenUrl, hostPublicKey, scope,
that.plugin_.onThirdPartyTokenFetched.bind(that.plugin_));
};
- this.plugin_.fetchThirdPartyTokenHandler = fetchThirdPartyToken;
+ this.plugin_.setFetchThirdPartyTokenHandler(fetchThirdPartyToken);
}
if (this.accessCode_) {
// Shared secret was already supplied before connecting (It2Me case).
that.fetchPin_(pairingSupported,
that.plugin_.onPinFetched.bind(that.plugin_));
};
- this.plugin_.fetchPinHandler = fetchPin;
+ this.plugin_.setFetchPinHandler(fetchPin);
callback('');
} else {
// Clients that don't support asking for a PIN asynchronously also don't
}
// Activate full-screen related UX.
remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
- remoting.fullscreen.syncWithMaximize(true);
if (remoting.windowFrame) {
remoting.windowFrame.setClientSession(this);
} else {
remoting.toolbar.setClientSession(this);
}
+ remoting.optionsMenu.setClientSession(this);
+ document.body.classList.add('connected');
this.container_.addEventListener('mousemove',
this.updateMouseCursorPosition_,
clientArea.height,
window.devicePixelRatio);
}
+ if (this.hasCapability_(
+ remoting.ClientSession.Capability.VIDEO_RECORDER)) {
+ this.videoFrameRecorder_ = new remoting.VideoFrameRecorder(this.plugin_);
+ }
};
/**
this.logToServer.logClientSessionStateChange(state, this.error_, this.mode_);
if (this.state_ == remoting.ClientSession.State.CONNECTED) {
this.createGnubbyAuthHandler_();
+ this.createCastExtensionHandler_();
}
this.raiseEvent(remoting.ClientSession.Events.stateChanged,
*/
remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() {
console.log('desktop size changed: ' +
- this.plugin_.desktopWidth + 'x' +
- this.plugin_.desktopHeight +' @ ' +
- this.plugin_.desktopXDpi + 'x' +
- this.plugin_.desktopYDpi + ' DPI');
+ this.plugin_.getDesktopWidth() + 'x' +
+ this.plugin_.getDesktopHeight() +' @ ' +
+ this.plugin_.getDesktopXDpi() + 'x' +
+ this.plugin_.getDesktopYDpi() + ' DPI');
this.updateDimensions();
this.updateScrollbarVisibility();
};
* @return {void} Nothing.
*/
remoting.ClientSession.prototype.updateDimensions = function() {
- if (this.plugin_.desktopWidth == 0 ||
- this.plugin_.desktopHeight == 0) {
+ if (this.plugin_.getDesktopWidth() == 0 ||
+ this.plugin_.getDesktopHeight() == 0) {
return;
}
var clientArea = this.getClientArea_();
- var desktopWidth = this.plugin_.desktopWidth;
- var desktopHeight = this.plugin_.desktopHeight;
+ var desktopWidth = this.plugin_.getDesktopWidth();
+ var desktopHeight = this.plugin_.getDesktopHeight();
// When configured to display a host at its original size, we aim to display
// it as close to its physical size as possible, without losing data:
// an initial scale factor based on the client devicePixelRatio and host DPI.
// Determine the effective device pixel ratio of the host, based on DPI.
- var hostPixelRatioX = Math.ceil(this.plugin_.desktopXDpi / 96);
- var hostPixelRatioY = Math.ceil(this.plugin_.desktopYDpi / 96);
+ var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96);
+ var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96);
var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
// Down-scale by the smaller of the client and host ratios.
};
/**
- * Updates the options menu to reflect the current scale-to-fit and full-screen
- * settings.
- * @return {void} Nothing.
- * @private
- */
-remoting.ClientSession.prototype.onShowOptionsMenu_ = function() {
- remoting.MenuButton.select(this.resizeToClientButton_, this.resizeToClient_);
- remoting.MenuButton.select(this.shrinkToFitButton_, this.shrinkToFit_);
- remoting.MenuButton.select(this.fullScreenButton_,
- remoting.fullscreen.isActive());
-};
-
-/**
* Scroll the client plugin by the specified amount, keeping it visible.
* Note that this is only used in content full-screen mode (not windowed or
* browser full-screen modes), where window.scrollBy and the scrollTop and
left: parseFloat(style.marginLeft)
};
};
+
+/**
+ * Send a Cast extension message to the host.
+ * @param {Object} data The cast message data.
+ */
+remoting.ClientSession.prototype.sendCastExtensionMessage = function(data) {
+ if (!this.plugin_)
+ return;
+ this.plugin_.sendClientMessage('cast_message', JSON.stringify(data));
+};
+
+/**
+ * Process a remote Cast extension message from the host.
+ * @param {string} data Remote cast extension data message.
+ * @private
+ */
+remoting.ClientSession.prototype.processCastExtensionMessage_ = function(data) {
+ if (this.castExtensionHandler_) {
+ try {
+ this.castExtensionHandler_.onMessage(data);
+ } catch (err) {
+ console.error('Failed to process cast message: ',
+ /** @type {*} */ (err));
+ }
+ } else {
+ console.error('Received unexpected cast message');
+ }
+};
+
+/**
+ * Create a CastExtensionHandler and inform the host that cast extension
+ * is supported.
+ * @private
+ */
+remoting.ClientSession.prototype.createCastExtensionHandler_ = function() {
+ if (remoting.enableCast && this.mode_ == remoting.ClientSession.Mode.ME2ME) {
+ this.castExtensionHandler_ = new remoting.CastExtensionHandler(this);
+ }
+};
+
+/**
+ * Returns true if the ClientSession can record video frames to a file.
+ * @return {boolean}
+ */
+remoting.ClientSession.prototype.canRecordVideo = function() {
+ return !!this.videoFrameRecorder_;
+}
+
+/**
+ * Returns true if the ClientSession is currently recording video frames.
+ * @return {boolean}
+ */
+remoting.ClientSession.prototype.isRecordingVideo = function() {
+ if (!this.videoFrameRecorder_) {
+ return false;
+ }
+ return this.videoFrameRecorder_.isRecording();
+}
+
+/**
+ * Starts or stops recording of video frames.
+ */
+remoting.ClientSession.prototype.startStopRecording = function() {
+ if (this.videoFrameRecorder_) {
+ this.videoFrameRecorder_.startStopRecording();
+ }
+}
+
+/**
+ * Handles protocol extension messages.
+ * @param {string} type Type of extension message.
+ * @param {string} data Contents of the extension message.
+ * @return {boolean} True if the message was recognized, false otherwise.
+ */
+remoting.ClientSession.prototype.handleExtensionMessage =
+ function(type, data) {
+ if (this.videoFrameRecorder_) {
+ return this.videoFrameRecorder_.handleMessage(type, data);
+ }
+ return false;
+}