summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib
diff options
context:
space:
mode:
authorLuke "Jared" Bennett <lbennett@gitlab.com>2017-05-20 20:05:31 +0100
committerLuke "Jared" Bennett <lbennett@gitlab.com>2017-05-20 20:56:10 +0100
commit71c859616524a5af903d3c736e93d7655e8a144d (patch)
treeb09dbfcf20fe8230fb5e9b38e206ce0a5abb7623 /app/assets/javascripts/lib
parentf5f99c9037e52392ca388b6e839d93df88421c31 (diff)
downloadgitlab-ce-sockets-frontend.tar.gz
Added SocketManager, SubscriptionStore, Subscription, VisibilitySocketManager and refactored currently instances of Pollsockets-frontend
Diffstat (limited to 'app/assets/javascripts/lib')
-rw-r--r--app/assets/javascripts/lib/utils/poll.js105
-rw-r--r--app/assets/javascripts/lib/utils/socket/socket_manager.js108
-rw-r--r--app/assets/javascripts/lib/utils/socket/subscription.js68
-rw-r--r--app/assets/javascripts/lib/utils/socket/subscription_store.js27
-rw-r--r--app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js24
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;