diff options
| author | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-05-20 20:05:31 +0100 |
|---|---|---|
| committer | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-05-20 20:56:10 +0100 |
| commit | 71c859616524a5af903d3c736e93d7655e8a144d (patch) | |
| tree | b09dbfcf20fe8230fb5e9b38e206ce0a5abb7623 /app/assets/javascripts/lib | |
| parent | f5f99c9037e52392ca388b6e839d93df88421c31 (diff) | |
| download | gitlab-ce-sockets-frontend.tar.gz | |
Added SocketManager, SubscriptionStore, Subscription, VisibilitySocketManager and refactored currently instances of Pollsockets-frontend
Diffstat (limited to 'app/assets/javascripts/lib')
5 files changed, 227 insertions, 105 deletions
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js deleted file mode 100644 index e31cc5fbabe..00000000000 --- a/app/assets/javascripts/lib/utils/poll.js +++ /dev/null @@ -1,105 +0,0 @@ -import httpStatusCodes from './http_status'; - -/** - * Polling utility for handling realtime updates. - * Service for vue resouce and method need to be provided as props - * - * @example - * new Poll({ - * resource: resource, - * method: 'name', - * data: {page: 1, scope: 'all'}, // optional - * successCallback: () => {}, - * errorCallback: () => {}, - * notificationCallback: () => {}, // optional - * }).makeRequest(); - * - * Usage in pipelines table with visibility lib: - * - * const poll = new Poll({ - * resource: this.service, - * method: 'getPipelines', - * data: { page: pageNumber, scope }, - * successCallback: this.successCallback, - * errorCallback: this.errorCallback, - * notificationCallback: this.updateLoading, - * }); - * - * if (!Visibility.hidden()) { - * poll.makeRequest(); - * } - * - * Visibility.change(() => { - * if (!Visibility.hidden()) { - * poll.restart(); - * } else { - * poll.stop(); - * } -* }); - * - * 1. Checks for response and headers before start polling - * 2. Interval is provided by `Poll-Interval` header. - * 3. If `Poll-Interval` is -1, we stop polling - * 4. If HTTP response is 200, we poll. - * 5. If HTTP response is different from 200, we stop polling. - * - */ -export default class Poll { - constructor(options = {}) { - this.options = options; - this.options.data = options.data || {}; - this.options.notificationCallback = options.notificationCallback || - function notificationCallback() {}; - - this.intervalHeader = 'POLL-INTERVAL'; - this.timeoutID = null; - this.canPoll = true; - } - - checkConditions(response) { - const headers = gl.utils.normalizeHeaders(response.headers); - const pollInterval = parseInt(headers[this.intervalHeader], 10); - - if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { - this.timeoutID = setTimeout(() => { - this.makeRequest(); - }, pollInterval); - } - this.options.successCallback(response); - } - - makeRequest() { - const { resource, method, data, errorCallback, notificationCallback } = this.options; - - // It's called everytime a new request is made. Useful to update the status. - notificationCallback(true); - - return resource[method](data) - .then((response) => { - this.checkConditions(response); - notificationCallback(false); - }) - .catch((error) => { - notificationCallback(false); - errorCallback(error); - }); - } - - /** - * Stops the polling recursive chain - * and guarantees if the timeout is already running it won't make another request by - * cancelling the previously established timeout. - */ - stop() { - this.canPoll = false; - clearTimeout(this.timeoutID); - } - - /** - * Restarts polling after it has been stoped - */ - restart() { - this.canPoll = true; - this.makeRequest(); - } -} diff --git a/app/assets/javascripts/lib/utils/socket/socket_manager.js b/app/assets/javascripts/lib/utils/socket/socket_manager.js new file mode 100644 index 00000000000..2f4f428ebee --- /dev/null +++ b/app/assets/javascripts/lib/utils/socket/socket_manager.js @@ -0,0 +1,108 @@ +import socketIO from 'socket.io-client'; +import Socket from 'socket.io-client/lib/socket'; +import Subscription from './subscription'; +import SubscriptionStore from './subscription_store'; + +const SocketManager = { + socketPath: '', + socket: {}, + store: SubscriptionStore, + subscriptionsCount: 0, + + init(socketPath) { + this.socketPath = socketPath; + + this.removeAll(); + }, + + connect() { + if (this.socket instanceof Socket) return Promise.resolve(); + + return new Promise((resolve, reject) => { + this.socket = socketIO(this.socketPath); + + this.socket.on('connect', resolve); + this.socket.on('connect_error', reject); + this.socket.on('connect_timeout', reject); + }); + }, + + subscribe(endpointOrSubscription, data, callbacks) { + this.connect().then(() => { + const subscription = this.getSubscription(endpointOrSubscription, data, callbacks); + + subscription.subscribe(); + + return subscription; + }).catch((error) => { + // temporary + // eslint-disable-next-line no-console + console.log('connect error', error); + }); + }, + + remove(subscription) { + subscription.unsubscribe(); + + this.store.remove(subscription); + }, + + unsubscribeAll() { + const subscriptions = this.store.getAll(); + + if (!subscriptions) return; + + subscriptions.forEach(subscription => subscription.unsubscribe()); + }, + + subscribeAll() { + const subscriptions = this.store.getAll(); + + if (!subscriptions) return; + + subscriptions.forEach(subscription => subscription.subscribe()); + }, + + removeAll() { + this.unsubscribeAll(); + this.store.removeAll(); + }, + + getSubscription(endpointOrSubscription, data, callbacks) { + let subscription; + + if (endpointOrSubscription instanceof Subscription) { + subscription = endpointOrSubscription; + } else { + subscription = this.createSubscription({ + endpoint: endpointOrSubscription, + data, + callbacks, + }); + } + + return subscription; + }, + + createSubscription({ + endpoint, + data, + callbacks, + }) { + this.subscriptionsCount += 1; + + const subscription = new Subscription({ + endpoint, + data, + callbacks, + socket: this.socket, + id: this.subscriptionsCount, + }); + + this.store.add(subscription); + + return subscription; + }, +}; + +export default SocketManager; diff --git a/app/assets/javascripts/lib/utils/socket/subscription.js b/app/assets/javascripts/lib/utils/socket/subscription.js new file mode 100644 index 00000000000..46373a78acb --- /dev/null +++ b/app/assets/javascripts/lib/utils/socket/subscription.js @@ -0,0 +1,68 @@ +class Subscription { + constructor({ + id, + endpoint, + data, + socket, + callbacks, + }) { + this.id = id; + this.endpoint = endpoint; + this.data = data; + this.socket = socket; + this.updateCallback = callbacks.updateCallback; + this.errorCallback = callbacks.errorCallback; + + this.setPayload(); + this.setEventNames(); + } + + subscribe() { + this.socket.emit(this.eventNames.subscribe, this.payload, this.acknowledge); + + this.bindListeners(); + } + + unsubscribe() { + this.socket.emit(this.eventNames.unsubscribe, this.payload, this.acknowledge); + + this.unbindListeners(); + } + + bindListeners() { + this.socket.on(this.eventNames.update, this.updateCallback); + this.socket.on(this.eventNames.error, this.errorCallback); + } + + unbindListeners() { + this.socket.removeListener(this.eventNames.update, this.updateCallback); + this.socket.removeListener(this.eventNames.error, this.errorCallback); + } + + setPayload() { + this.payload = { + id: this.id, + endpoint: this.endpoint, + data: this.data, + }; + } + + setEventNames() { + this.eventNames = { + subscribe: `subscribe:${this.endpoint}`, + unsubscribe: `unsubscribe:${this.endpoint}`, + update: `update:${this.id}`, + error: `error:${this.id}`, + }; + } + + acknowledge(response, ...args) { + if (response.error) this.errorCallback(response.error, ...args); + + // temporary + // eslint-disable-next-line no-console + console.log('ACK', ...args); + } +} + +export default Subscription; diff --git a/app/assets/javascripts/lib/utils/socket/subscription_store.js b/app/assets/javascripts/lib/utils/socket/subscription_store.js new file mode 100644 index 00000000000..06b43da1360 --- /dev/null +++ b/app/assets/javascripts/lib/utils/socket/subscription_store.js @@ -0,0 +1,27 @@ +const SocketStore = { + subscriptions: {}, + + add(subscription) { + this.subscriptions[subscription.id] = subscription; + + return subscription; + }, + + remove(subscription) { + delete this.subscriptions[subscription.id]; + }, + + get(subscriptionID) { + return this.subscriptions[subscriptionID]; + }, + + getAll() { + Object.values(this.subscriptions); + }, + + removeAll() { + this.subscriptions = {}; + }, +}; + +export default SocketStore; diff --git a/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js b/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js new file mode 100644 index 00000000000..d363ae4bd44 --- /dev/null +++ b/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js @@ -0,0 +1,24 @@ +import SocketManager from './socket_manager'; + +const VisibilitySocketManager = { + init(socketPath) { + super.init(socketPath); + + document.addEventListener('visibilitychange', () => this.toggleAllSockets()); + }, + + toggleAllSockets() { + if (document.hidden) { + super.unsubscribeAll(); + } else { + super.subscribeAll(); + } + }, +}; + +Object.setPrototypeOf(VisibilitySocketManager, SocketManager); + +// temporary +VisibilitySocketManager.init('/broker'); + +export default VisibilitySocketManager; |
