diff --git a/grunt_config.json b/grunt_config.json
--- a/grunt_config.json
+++ b/grunt_config.json
@@ -66,7 +66,6 @@
"<%= dirs.js.src %>/rhodecode/utils/topics.js",
"<%= dirs.js.src %>/rhodecode/widgets/multiselect.js",
"<%= dirs.js.src %>/rhodecode/init.js",
- "<%= dirs.js.src %>/rhodecode/connection_controller.js",
"<%= dirs.js.src %>/rhodecode/codemirror.js",
"<%= dirs.js.src %>/rhodecode/comments.js",
"<%= dirs.js.src %>/rhodecode/constants.js",
@@ -81,7 +80,6 @@
"<%= dirs.js.src %>/rhodecode/select2_widgets.js",
"<%= dirs.js.src %>/rhodecode/tooltips.js",
"<%= dirs.js.src %>/rhodecode/users.js",
- "<%= dirs.js.src %>/rhodecode/utils/notifications.js",
"<%= dirs.js.src %>/rhodecode/appenlight.js",
"<%= dirs.js.src %>/rhodecode.js"
],
diff --git a/rhodecode/channelstream/__init__.py b/rhodecode/channelstream/__init__.py
--- a/rhodecode/channelstream/__init__.py
+++ b/rhodecode/channelstream/__init__.py
@@ -29,7 +29,9 @@ from rhodecode.lib.ext_json import json
def url_gen(request):
urls = {
'connect': request.route_url('channelstream_connect'),
- 'subscribe': request.route_url('channelstream_subscribe')
+ 'subscribe': request.route_url('channelstream_subscribe'),
+ 'longpoll': request.registry.settings.get('channelstream.longpoll_url', ''),
+ 'ws': request.registry.settings.get('channelstream.ws_url', '')
}
return json.dumps(urls)
diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html
new file mode 100644
--- /dev/null
+++ b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js
new file mode 100644
--- /dev/null
+++ b/rhodecode/public/js/src/components/channelstream-connection/channelstream-connection.js
@@ -0,0 +1,502 @@
+Polymer({
+ is: 'channelstream-connection',
+
+ /**
+ * Fired when `channels` array changes.
+ *
+ * @event channelstream-channels-changed
+ */
+
+ /**
+ * Fired when `connect()` method succeeds.
+ *
+ * @event channelstream-connected
+ */
+
+ /**
+ * Fired when `connect` fails.
+ *
+ * @event channelstream-connect-error
+ */
+
+ /**
+ * Fired when `disconnect()` succeeds.
+ *
+ * @event channelstream-disconnected
+ */
+
+ /**
+ * Fired when `message()` succeeds.
+ *
+ * @event channelstream-message-sent
+ */
+
+ /**
+ * Fired when `message()` fails.
+ *
+ * @event channelstream-message-error
+ */
+
+ /**
+ * Fired when `subscribe()` succeeds.
+ *
+ * @event channelstream-subscribed
+ */
+
+ /**
+ * Fired when `subscribe()` fails.
+ *
+ * @event channelstream-subscribe-error
+ */
+
+ /**
+ * Fired when `unsubscribe()` succeeds.
+ *
+ * @event channelstream-unsubscribed
+ */
+
+ /**
+ * Fired when `unsubscribe()` fails.
+ *
+ * @event channelstream-unsubscribe-error
+ */
+
+ /**
+ * Fired when listening connection receives a message.
+ *
+ * @event channelstream-listen-message
+ */
+
+ /**
+ * Fired when listening connection is opened.
+ *
+ * @event channelstream-listen-opened
+ */
+
+ /**
+ * Fired when listening connection is closed.
+ *
+ * @event channelstream-listen-closed
+ */
+
+ /**
+ * Fired when listening connection suffers an error.
+ *
+ * @event channelstream-listen-error
+ */
+
+ properties: {
+ isReady: Boolean,
+ /** List of channels user should be subscribed to. */
+ channels: {
+ type: Array,
+ value: function () {
+ return []
+ },
+ notify: true
+ },
+ /** Username of connecting user. */
+ username: {
+ type: String,
+ value: 'Anonymous',
+ reflectToAttribute: true
+ },
+ /** Connection identifier. */
+ connectionId: {
+ type: String,
+ reflectToAttribute: true
+ },
+ /** Websocket instance. */
+ websocket: {
+ type: Object,
+ value: null
+ },
+ /** Websocket connection url. */
+ websocketUrl: {
+ type: String,
+ value: ''
+ },
+ /** URL used in `connect()`. */
+ connectUrl: {
+ type: String,
+ value: ''
+ },
+ /** URL used in `disconnect()`. */
+ disconnectUrl: {
+ type: String,
+ value: ''
+ },
+ /** URL used in `subscribe()`. */
+ subscribeUrl: {
+ type: String,
+ value: ''
+ },
+ /** URL used in `unsubscribe()`. */
+ unsubscribeUrl: {
+ type: String,
+ value: ''
+ },
+ /** URL used in `message()`. */
+ messageUrl: {
+ type: String,
+ value: ''
+ },
+ /** Long-polling connection url. */
+ longPollUrl: {
+ type: String,
+ value: ''
+ },
+ /** Long-polling connection url. */
+ shouldReconnect: {
+ type: Boolean,
+ value: true
+ },
+ /** Should send heartbeats. */
+ heartbeats: {
+ type: Boolean,
+ value: true
+ },
+ /** How much should every retry interval increase (in milliseconds) */
+ increaseBounceIv: {
+ type: Number,
+ value: 2000
+ },
+ _currentBounceIv: {
+ type: Number,
+ reflectToAttribute: true,
+ value: 0
+ },
+ /** Should use websockets or long-polling by default */
+ useWebsocket: {
+ type: Boolean,
+ reflectToAttribute: true,
+ value: true
+ },
+ connected: {
+ type: Boolean,
+ reflectToAttribute: true,
+ value: false
+ }
+ },
+
+ observers: [
+ '_handleChannelsChange(channels.splices)'
+ ],
+
+ listeners: {
+ 'channelstream-connected': 'startListening',
+ 'channelstream-connect-error': 'retryConnection',
+ },
+
+ /**
+ * Mutators hold functions that you can set locally to change the data
+ * that the client is sending to all endpoints
+ * you can call it like `elem.mutators('connect', yourFunc())`
+ * mutators will be executed in order they were pushed onto arrays
+ *
+ */
+ mutators: {
+ connect: function () {
+ return []
+ }(),
+ message: function () {
+ return []
+ }(),
+ subscribe: function () {
+ return []
+ }(),
+ unsubscribe: function () {
+ return []
+ }(),
+ disconnect: function () {
+ return []
+ }()
+ },
+ ready: function () {
+ this.isReady = true;
+ },
+
+ /**
+ * Connects user and fetches connection id from the server.
+ *
+ */
+ connect: function () {
+ var request = this.$['ajaxConnect'];
+ request.url = this.connectUrl;
+ request.body = {
+ username: this.username,
+ channels: this.channels
+ };
+ for (var i = 0; i < this.mutators.connect.length; i++) {
+ this.mutators.connect[i](request);
+ }
+ request.generateRequest()
+ },
+ /**
+ * Overwrite with custom function that will
+ */
+ addMutator: function (type, func) {
+ this.mutators[type].push(func);
+ },
+ /**
+ * Subscribes user to channels.
+ *
+ */
+ subscribe: function (channels) {
+ var request = this.$['ajaxSubscribe'];
+ request.url = this.subscribeUrl;
+ request.body = {
+ channels: channels,
+ conn_id: this.connectionId
+ };
+ for (var i = 0; i < this.mutators.subscribe.length; i++) {
+ this.mutators.subscribe[i](request);
+ }
+ if (request.body.channels.length) {
+ request.generateRequest();
+ }
+ },
+ /**
+ * Unsubscribes user from channels.
+ *
+ */
+ unsubscribe: function (unsubscribe) {
+ var request = this.$['ajaxUnsubscribe'];
+
+ request.url = this.unsubscribeUrl;
+ request.body = {
+ channels: unsubscribe,
+ conn_id: this.connectionId
+ };
+ for (var i = 0; i < this.mutators.unsubscribe.length; i++) {
+ this.mutators.unsubscribe[i](request);
+ }
+ request.generateRequest()
+ },
+
+ /**
+ * calculates list of channels we should add user to based on difference
+ * between channels property and passed channel list
+ */
+ calculateSubscribe: function (channels) {
+ var currentlySubscribed = this.channels;
+ var toSubscribe = [];
+ for (var i = 0; i < channels.length; i++) {
+ if (currentlySubscribed.indexOf(channels[i]) === -1) {
+ toSubscribe.push(channels[i]);
+ }
+ }
+ return toSubscribe
+ },
+ /**
+ * calculates list of channels we should remove user from based difference
+ * between channels property and passed channel list
+ */
+ calculateUnsubscribe: function (channels) {
+ var currentlySubscribed = this.channels;
+ var toUnsubscribe = [];
+ for (var i = 0; i < channels.length; i++) {
+ if (currentlySubscribed.indexOf(channels[i]) !== -1) {
+ toUnsubscribe.push(channels[i]);
+ }
+ }
+ return toUnsubscribe
+ },
+ /**
+ * Marks the connection as expired.
+ *
+ */
+ disconnect: function () {
+ var request = this.$['ajaxDisconnect'];
+ request.url = this.disconnectUrl;
+ request.params = {
+ conn_id: this.connectionId
+ };
+ for (var i = 0; i < this.mutators.disconnect.length; i++) {
+ this.mutators.disconnect[i](request);
+ }
+ // mark connection as expired
+ request.generateRequest();
+ // disconnect existing connection
+ this.closeConnection();
+ },
+
+ /**
+ * Sends a message to the server.
+ *
+ */
+ message: function (message) {
+ var request = this.$['ajaxMessage'];
+ request.url = this.messageUrl;
+ request.body = message;
+ for (var i = 0; i < this.mutators.message.length; i++) {
+ this.mutators.message[i](request)
+ }
+ request.generateRequest();
+ },
+ /**
+ * Opens "long lived" (websocket/longpoll) connection to the channelstream server.
+ *
+ */
+ startListening: function (event) {
+ this.fire('start-listening', {});
+ if (this.useWebsocket) {
+ this.useWebsocket = window.WebSocket ? true : false;
+ }
+ if (this.useWebsocket) {
+ this.openWebsocket();
+ }
+ else {
+ this.openLongPoll();
+ }
+ },
+ /**
+ * Opens websocket connection.
+ *
+ */
+ openWebsocket: function () {
+ var url = this.websocketUrl + '?conn_id=' + this.connectionId;
+ this.websocket = new WebSocket(url);
+ this.websocket.onopen = this._handleListenOpen.bind(this);
+ this.websocket.onclose = this._handleListenCloseEvent.bind(this);
+ this.websocket.onerror = this._handleListenErrorEvent.bind(this);
+ this.websocket.onmessage = this._handleListenMessageEvent.bind(this);
+ },
+ /**
+ * Opens long-poll connection.
+ *
+ */
+ openLongPoll: function () {
+ var request = this.$['ajaxListen'];
+ request.url = this.longPollUrl + '?conn_id=' + this.connectionId;
+ request.generateRequest()
+ },
+ /**
+ * Retries `connect()` call while incrementing interval between tries up to 1 minute.
+ *
+ */
+ retryConnection: function () {
+ if (!this.shouldReconnect) {
+ return;
+ }
+ if (this._currentBounceIv < 60000) {
+ this._currentBounceIv = this._currentBounceIv + this.increaseBounceIv;
+ }
+ else {
+ this._currentBounceIv = 60000;
+ }
+ setTimeout(this.connect.bind(this), this._currentBounceIv);
+ },
+ /**
+ * Closes listening connection.
+ *
+ */
+ closeConnection: function () {
+ var request = this.$['ajaxListen'];
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
+ this.websocket.onclose = null;
+ this.websocket.onerror = null;
+ this.websocket.close();
+ }
+ if (request.loading) {
+ request.lastRequest.abort();
+ }
+ this.connected = false;
+ },
+
+ _handleChannelsChange: function (event) {
+ // do not fire the event if set() didn't mutate anything
+ // is this a reliable way to do it?
+ if (!this.isReady || event === undefined) {
+ return
+ }
+ this.fire('channelstream-channels-changed', event)
+ },
+
+ _handleListenOpen: function (event) {
+ this.connected = true;
+ this.fire('channelstream-listen-opened', event);
+ this.createHeartBeats();
+ },
+
+ createHeartBeats: function () {
+ if (typeof self._heartbeat === 'undefined' && this.websocket !== null
+ && this.heartbeats) {
+ self._heartbeat = setInterval(this._sendHeartBeat.bind(this), 10000);
+ }
+ },
+
+ _sendHeartBeat: function () {
+ if (this.websocket.readyState === WebSocket.OPEN && this.heartbeats) {
+ this.websocket.send(JSON.stringify({type: 'heartbeat'}));
+ }
+ },
+
+ _handleListenError: function (event) {
+ this.connected = false;
+ this.retryConnection();
+ },
+ _handleConnectError: function (event) {
+ this.connected = false;
+ this.fire('channelstream-connect-error', event.detail);
+ },
+
+ _handleListenMessageEvent: function (event) {
+ var data = null;
+ // comes from iron-ajax
+ if (event.detail) {
+ data = JSON.parse(event.detail.response)
+ // comes from websocket
+ setTimeout(this.openLongPoll.bind(this), 0);
+ } else {
+ data = JSON.parse(event.data)
+ }
+ this.fire('channelstream-listen-message', data);
+
+ },
+
+ _handleListenCloseEvent: function (event) {
+ this.connected = false;
+ this.fire('channelstream-listen-closed', event.detail);
+ this.retryConnection();
+ },
+
+ _handleListenErrorEvent: function (event) {
+ this.connected = false;
+ this.fire('channelstream-listen-error', {})
+ },
+
+ _handleConnect: function (event) {
+ this.currentBounceIv = 0;
+ this.connectionId = event.detail.response.conn_id;
+ this.fire('channelstream-connected', event.detail.response);
+ },
+
+ _handleDisconnect: function (event) {
+ this.connected = false;
+ this.fire('channelstream-disconnected', {});
+ },
+
+ _handleMessage: function (event) {
+ this.fire('channelstream-message-sent', event.detail.response);
+ },
+ _handleMessageError: function (event) {
+ this.fire('channelstream-message-error', event.detail);
+ },
+
+ _handleSubscribe: function (event) {
+ this.fire('channelstream-subscribed', event.detail.response);
+ },
+
+ _handleSubscribeError: function (event) {
+ this.fire('channelstream-subscribe-error', event.detail);
+ },
+
+ _handleUnsubscribe: function (event) {
+ this.fire('channelstream-unsubscribed', event.detail.response);
+ },
+
+ _handleUnsubscribeError: function (event) {
+ this.fire('channelstream-unsubscribe-error', event.detail);
+ }
+});
diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html
new file mode 100644
--- /dev/null
+++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js
new file mode 100644
--- /dev/null
+++ b/rhodecode/public/js/src/components/rhodecode-app/rhodecode-app.js
@@ -0,0 +1,127 @@
+ccLog = Logger.get('RhodeCodeApp');
+ccLog.setLevel(Logger.OFF);
+
+var rhodeCodeApp = Polymer({
+ is: 'rhodecode-app',
+ created: function () {
+ ccLog.debug('rhodeCodeApp created');
+ $.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
+
+ $.Topic('/plugins/__REGISTER__').subscribe(
+ this.kickoffChannelstreamPlugin.bind(this)
+ );
+
+ $.Topic('/connection_controller/subscribe').subscribe(
+ this.subscribeToChannelTopic.bind(this));
+ },
+
+ /** proxy to channelstream connection */
+ getChannelStreamConnection: function () {
+ return this.$['channelstream-connection'];
+ },
+
+ handleNotifications: function (data) {
+ this.$['notifications'].handleNotification(data);
+ },
+
+ /** opens connection to ws server */
+ kickoffChannelstreamPlugin: function (data) {
+ ccLog.debug('kickoffChannelstreamPlugin');
+ var channels = ['broadcast'];
+ var addChannels = this.checkViewChannels();
+ for (var i = 0; i < addChannels.length; i++) {
+ channels.push(addChannels[i]);
+ }
+ var channelstreamConnection = this.$['channelstream-connection'];
+ channelstreamConnection.connectUrl = CHANNELSTREAM_URLS.connect;
+ channelstreamConnection.subscribeUrl = CHANNELSTREAM_URLS.subscribe;
+ channelstreamConnection.websocketUrl = CHANNELSTREAM_URLS.ws + '/ws';
+ channelstreamConnection.longPollUrl = CHANNELSTREAM_URLS.longpoll + '/listen';
+ // some channels might already be registered by topic
+ for (var i = 0; i < channels.length; i++) {
+ channelstreamConnection.push('channels', channels[i]);
+ }
+ // append any additional channels registered in other plugins
+ $.Topic('/connection_controller/subscribe').processPrepared();
+ channelstreamConnection.connect();
+ },
+
+ checkViewChannels: function () {
+ var channels = []
+ // subscribe to PR repo channel for PR's'
+ if (templateContext.pull_request_data.pull_request_id) {
+ var channelName = '/repo$' + templateContext.repo_name + '$/pr/' +
+ String(templateContext.pull_request_data.pull_request_id);
+ channels.push(channelName);
+ }
+ return channels;
+ },
+
+ /** subscribes users from channels in channelstream */
+ subscribeToChannelTopic: function (channels) {
+ var channelstreamConnection = this.$['channelstream-connection'];
+ var toSubscribe = channelstreamConnection.calculateSubscribe(channels);
+ ccLog.debug('subscribeToChannelTopic', toSubscribe);
+ if (toSubscribe.length > 0) {
+ // if we are connected then subscribe
+ if (channelstreamConnection.connected) {
+ channelstreamConnection.subscribe(toSubscribe);
+ }
+ // not connected? just push channels onto the stack
+ else {
+ for (var i = 0; i < toSubscribe.length; i++) {
+ channelstreamConnection.push('channels', toSubscribe[i]);
+ }
+ }
+ }
+ },
+
+ /** publish received messages into correct topic */
+ receivedMessage: function (event) {
+ for (var i = 0; i < event.detail.length; i++) {
+ var message = event.detail[i];
+ if (message.message.topic) {
+ ccLog.debug('publishing', message.message.topic);
+ $.Topic(message.message.topic).publish(message);
+ }
+ else if (message.type === 'presence'){
+ $.Topic('/connection_controller/presence').publish(message);
+ }
+ else {
+ ccLog.warn('unhandled message', message);
+ }
+ }
+ },
+
+ handleConnected: function (event) {
+ var channelstreamConnection = this.$['channelstream-connection'];
+ channelstreamConnection.set('channelsState',
+ event.detail.channels_info);
+ channelstreamConnection.set('userState', event.detail.state);
+ channelstreamConnection.set('channels', event.detail.channels);
+ this.propagageChannelsState();
+ },
+ handleSubscribed: function (event) {
+ var channelstreamConnection = this.$['channelstream-connection'];
+ var channelInfo = event.detail.channels_info;
+ var channelKeys = Object.keys(event.detail.channels_info);
+ for (var i = 0; i < channelKeys.length; i++) {
+ var key = channelKeys[i];
+ channelstreamConnection.set(['channelsState', key], channelInfo[key]);
+ }
+ channelstreamConnection.set('channels', event.detail.channels);
+ this.propagageChannelsState();
+ },
+ /** propagates channel states on topics */
+ propagageChannelsState: function (event) {
+ var channelstreamConnection = this.$['channelstream-connection'];
+ var channel_data = channelstreamConnection.channelsState;
+ var channels = channelstreamConnection.channels;
+ for (var i = 0; i < channels.length; i++) {
+ var key = channels[i];
+ $.Topic('/connection_controller/channel_update').publish(
+ {channel: key, state: channel_data[key]}
+ );
+ }
+ }
+});
diff --git a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js
--- a/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js
+++ b/rhodecode/public/js/src/components/rhodecode-toast/rhodecode-toast.js
@@ -11,10 +11,6 @@ Polymer({
observers: [
'_changedToasts(toasts.splices)'
],
- ready: function(){
-
- },
-
_changedToasts: function(newValue, oldValue){
this.$['p-toast'].notifyResize();
},
@@ -27,5 +23,16 @@ Polymer({
open: function(){
this.$['p-toast'].open();
},
+ handleNotification: function(data){
+ if (!templateContext.rhodecode_user.notification_status && !data.message.force) {
+ // do not act if notifications are disabled
+ return
+ }
+ this.push('toasts',{
+ level: data.message.level,
+ message: data.message.message
+ });
+ this.open();
+ },
_gettext: _gettext
});
diff --git a/rhodecode/public/js/src/components/shared-components.html b/rhodecode/public/js/src/components/shared-components.html
--- a/rhodecode/public/js/src/components/shared-components.html
+++ b/rhodecode/public/js/src/components/shared-components.html
@@ -1,6 +1,8 @@
+
+
diff --git a/rhodecode/public/js/src/rhodecode/connection_controller.js b/rhodecode/public/js/src/rhodecode/connection_controller.js
deleted file mode 100644
--- a/rhodecode/public/js/src/rhodecode/connection_controller.js
+++ /dev/null
@@ -1,208 +0,0 @@
-"use strict";
-/** leak object to top level scope **/
-var ccLog = undefined;
-// global code-mirror logger;, to enable run
-// Logger.get('ConnectionController').setLevel(Logger.DEBUG)
-ccLog = Logger.get('ConnectionController');
-ccLog.setLevel(Logger.OFF);
-
-var ConnectionController;
-var connCtrlr;
-var registerViewChannels;
-
-(function () {
- ConnectionController = function (webappUrl, serverUrl, urls) {
- var self = this;
-
- var channels = ['broadcast'];
- this.state = {
- open: false,
- webappUrl: webappUrl,
- serverUrl: serverUrl,
- connId: null,
- socket: null,
- channels: channels,
- heartbeat: null,
- channelsInfo: {},
- urls: urls
- };
-
- this.listen = function () {
- if (window.WebSocket) {
- ccLog.debug('attempting to create socket');
- var socket_url = self.state.serverUrl + "/ws?conn_id=" + self.state.connId;
- var socket_conf = {
- url: socket_url,
- handleAs: 'json',
- headers: {
- "Accept": "application/json",
- "Content-Type": "application/json"
- }
- };
- self.state.socket = new WebSocket(socket_conf.url);
-
- self.state.socket.onopen = function (event) {
- ccLog.debug('open event', event);
- if (self.state.heartbeat === null) {
- self.state.heartbeat = setInterval(function () {
- if (self.state.socket.readyState === WebSocket.OPEN) {
- self.state.socket.send('heartbeat');
- }
- }, 10000)
- }
- };
- self.state.socket.onmessage = function (event) {
- var data = $.parseJSON(event.data);
- for (var i = 0; i < data.length; i++) {
- if (data[i].message.topic) {
- ccLog.debug('publishing',
- data[i].message.topic, data[i]);
- $.Topic(data[i].message.topic).publish(data[i])
- }
- else {
- ccLog.warn('unhandled message', data);
- }
- }
- };
- self.state.socket.onclose = function (event) {
- ccLog.debug('closed event', event);
- setTimeout(function () {
- self.connect(true);
- }, 5000);
- };
-
- self.state.socket.onerror = function (event) {
- ccLog.debug('error event', event);
- };
- }
- else {
- ccLog.debug('attempting to create long polling connection');
- var poolUrl = self.state.serverUrl + "/listen?conn_id=" + self.state.connId;
- self.state.socket = $.ajax({
- url: poolUrl
- }).done(function (data) {
- ccLog.debug('data', data);
- var data = $.parseJSON(data);
- for (var i = 0; i < data.length; i++) {
- if (data[i].message.topic) {
- ccLog.info('publishing',
- data[i].message.topic, data[i]);
- $.Topic(data[i].message.topic).publish(data[i])
- }
- else {
- ccLog.warn('unhandled message', data);
- }
- }
- self.listen();
- }).fail(function () {
- ccLog.debug('longpoll error');
- setTimeout(function () {
- self.connect(true);
- }, 5000);
- });
- }
-
- };
-
- this.connect = function (create_new_socket) {
- var connReq = {'channels': self.state.channels};
- ccLog.debug('try obtaining connection info', connReq);
- $.ajax({
- url: self.state.urls.connect,
- type: "POST",
- contentType: "application/json",
- data: JSON.stringify(connReq),
- dataType: "json"
- }).done(function (data) {
- ccLog.debug('Got connection:', data.conn_id);
- self.state.channels = data.channels;
- self.state.channelsInfo = data.channels_info;
- self.state.connId = data.conn_id;
- if (create_new_socket) {
- self.listen();
- }
- self.update();
- }).fail(function () {
- setTimeout(function () {
- self.connect(create_new_socket);
- }, 5000);
- });
- self.update();
- };
-
- this.subscribeToChannels = function (channels) {
- var new_channels = [];
- for (var i = 0; i < channels.length; i++) {
- var channel = channels[i];
- if (self.state.channels.indexOf(channel)) {
- self.state.channels.push(channel);
- new_channels.push(channel)
- }
- }
- /**
- * only execute the request if socket is present because subscribe
- * can actually add channels before initial app connection
- **/
- if (new_channels && self.state.socket !== null) {
- var connReq = {
- 'channels': self.state.channels,
- 'conn_id': self.state.connId
- };
- $.ajax({
- url: self.state.urls.subscribe,
- type: "POST",
- contentType: "application/json",
- data: JSON.stringify(connReq),
- dataType: "json"
- }).done(function (data) {
- self.state.channels = data.channels;
- self.state.channelsInfo = data.channels_info;
- self.update();
- });
- }
- self.update();
- };
-
- this.update = function () {
- for (var key in this.state.channelsInfo) {
- if (this.state.channelsInfo.hasOwnProperty(key)) {
- // update channels with latest info
- $.Topic('/connection_controller/channel_update').publish(
- {channel: key, state: this.state.channelsInfo[key]});
- }
- }
- };
-
- this.run = function () {
- this.connect(true);
- };
-
- $.Topic('/connection_controller/subscribe').subscribe(
- self.subscribeToChannels);
- };
-
- $.Topic('/plugins/__REGISTER__').subscribe(function (data) {
- if (window.CHANNELSTREAM_SETTINGS && window.CHANNELSTREAM_SETTINGS.enabled) {
- connCtrlr = new ConnectionController(
- CHANNELSTREAM_SETTINGS.webapp_location,
- CHANNELSTREAM_SETTINGS.ws_location,
- CHANNELSTREAM_URLS
- );
- registerViewChannels();
-
- $(document).ready(function () {
- connCtrlr.run();
- });
- }
- });
-
-registerViewChannels = function (){
- // subscribe to PR repo channel for PR's'
- if (templateContext.pull_request_data.pull_request_id) {
- var channelName = '/repo$' + templateContext.repo_name + '$/pr/' +
- String(templateContext.pull_request_data.pull_request_id);
- connCtrlr.state.channels.push(channelName);
- }
-}
-
-})();
diff --git a/rhodecode/public/js/src/rhodecode/utils/notifications.js b/rhodecode/public/js/src/rhodecode/utils/notifications.js
deleted file mode 100644
--- a/rhodecode/public/js/src/rhodecode/utils/notifications.js
+++ /dev/null
@@ -1,47 +0,0 @@
-"use strict";
-
-
-function notifySystem(data) {
- var notification = new Notification(data.message.level + ': ' + data.message.message);
-};
-
-function notifyToaster(data){
- var notifications = document.getElementById('notifications');
- notifications.push('toasts',
- { level: data.message.level,
- message: data.message.message
- });
- notifications.open();
-}
-
-function handleNotifications(data) {
- if (!templateContext.rhodecode_user.notification_status && !data.message.force) {
- // do not act if notifications are disabled
- return
- }
- // use only js notifications for now
- var onlyJS = true;
- if (!("Notification" in window) || onlyJS) {
- // use legacy notificartion
- notifyToaster(data);
- }
- else {
- // Let's check whether notification permissions have already been granted
- if (Notification.permission === "granted") {
- notifySystem(data);
- }
- // Otherwise, we need to ask the user for permission
- else if (Notification.permission !== 'denied') {
- Notification.requestPermission(function (permission) {
- if (permission === "granted") {
- notifySystem(data);
- }
- });
- }
- else{
- notifyToaster(data);
- }
- }
-};
-
-$.Topic('/notifications').subscribe(handleNotifications);
diff --git a/rhodecode/public/js/topics_list.txt b/rhodecode/public/js/topics_list.txt
--- a/rhodecode/public/js/topics_list.txt
+++ b/rhodecode/public/js/topics_list.txt
@@ -1,4 +1,7 @@
/plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
/ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
/ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
-/notifications - shows new event notifications
\ No newline at end of file
+/notifications - shows new event notifications
+/connection_controller/subscribe - subscribes user to new channels
+/connection_controller/presence - receives presence change messages
+/connection_controller/channel_update - receives channel states
diff --git a/rhodecode/templates/base/root.html b/rhodecode/templates/base/root.html
--- a/rhodecode/templates/base/root.html
+++ b/rhodecode/templates/base/root.html
@@ -146,7 +146,9 @@ c.template_context['visual']['default_re
<%def name="head_extra()">%def>
${self.head_extra()}
## extra stuff
%if c.pre_code:
@@ -174,6 +176,6 @@ c.template_context['visual']['default_re
%if c.post_code:
${c.post_code|n}
%endif
-
+