Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/aphlict/Aphlict.js
12242 views
1
/**
2
* @provides javelin-aphlict
3
* @requires javelin-install
4
* javelin-util
5
* javelin-websocket
6
* javelin-leader
7
* javelin-json
8
*/
9
10
/**
11
* Client for the notification server. Example usage:
12
*
13
* var aphlict = new JX.Aphlict('ws://localhost:22280', subscriptions)
14
* .setHandler(function(message) {
15
* // ...
16
* })
17
* .start();
18
*
19
*/
20
JX.install('Aphlict', {
21
22
construct: function(uri, subscriptions) {
23
if (__DEV__) {
24
if (JX.Aphlict._instance) {
25
JX.$E('Aphlict object is a singleton.');
26
}
27
}
28
29
this._uri = uri;
30
this._subscriptions = subscriptions;
31
this._setStatus('setup');
32
this._startTime = new Date().getTime();
33
34
JX.Aphlict._instance = this;
35
},
36
37
events: ['didChangeStatus'],
38
39
members: {
40
_uri: null,
41
_socket: null,
42
_subscriptions: null,
43
_status: null,
44
_isReconnect: false,
45
_keepaliveInterval: false,
46
_startTime: null,
47
48
start: function() {
49
JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead));
50
JX.Leader.listen('onReceiveBroadcast', JX.bind(this, this._receive));
51
JX.Leader.start();
52
53
JX.Leader.call(JX.bind(this, this._begin));
54
},
55
56
getSubscriptions: function() {
57
return this._subscriptions;
58
},
59
60
setSubscriptions: function(subscriptions) {
61
this._subscriptions = subscriptions;
62
JX.Leader.broadcast(
63
null,
64
{type: 'aphlict.subscribe', data: this._subscriptions});
65
},
66
67
clearSubscriptions: function(subscriptions) {
68
this._subscriptions = null;
69
JX.Leader.broadcast(
70
null,
71
{type: 'aphlict.unsubscribe', data: subscriptions});
72
},
73
74
getStatus: function() {
75
return this._status;
76
},
77
78
getWebsocket: function() {
79
return this._socket;
80
},
81
82
_begin: function() {
83
JX.Leader.broadcast(
84
null,
85
{type: 'aphlict.getstatus'});
86
JX.Leader.broadcast(
87
null,
88
{type: 'aphlict.subscribe', data: this._subscriptions});
89
},
90
91
_lead: function() {
92
this._socket = new JX.WebSocket(this._uri);
93
this._socket.setOpenHandler(JX.bind(this, this._open));
94
this._socket.setMessageHandler(JX.bind(this, this._message));
95
this._socket.setCloseHandler(JX.bind(this, this._close));
96
97
this._socket.open();
98
},
99
100
_open: function() {
101
// If this is a reconnect, ask the server to replay recent messages
102
// after other tabs have had a chance to subscribe. Do this before we
103
// broadcast that the connection status is now open.
104
if (this._isReconnect) {
105
setTimeout(JX.bind(this, this._didReconnect), 100);
106
}
107
108
this._broadcastStatus('open');
109
JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'});
110
111
// By default, ELBs terminate connections after 60 seconds with no
112
// traffic. Other load balancers may have similar configuration. Send
113
// a keepalive message every 15 seconds to prevent load balancers from
114
// deciding they can reap this connection.
115
116
var keepalive = JX.bind(this, this._keepalive);
117
this._keepaliveInterval = setInterval(keepalive, 15000);
118
},
119
120
_didReconnect: function() {
121
this.replay();
122
this.reconnect();
123
},
124
125
replay: function() {
126
var age = 60000;
127
128
// If the page was loaded a few moments ago, only query for recent
129
// history. This keeps us from replaying events over and over again as
130
// a user browses normally.
131
132
// Allow a small margin of error for the actual page load time. It's
133
// also fine to replay a notification which the user saw for a brief
134
// moment on the previous page.
135
var extra_time = 500;
136
var now = new Date().getTime();
137
138
age = Math.min(extra_time + (now - this._startTime), age);
139
140
var replay = {
141
age: age
142
};
143
144
JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay});
145
},
146
147
reconnect: function() {
148
JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null});
149
},
150
151
_close: function() {
152
if (this._keepaliveInterval) {
153
clearInterval(this._keepaliveInterval);
154
this._keepaliveInterval = null;
155
}
156
157
this._broadcastStatus('closed');
158
},
159
160
_broadcastStatus: function(status) {
161
JX.Leader.broadcast(null, {type: 'aphlict.status', data: status});
162
},
163
164
_message: function(raw) {
165
var message = JX.JSON.parse(raw);
166
var id = message.uniqueID || null;
167
168
// If this is just a keepalive response, don't bother broadcasting it.
169
if (message.type == 'pong') {
170
return;
171
}
172
173
JX.Leader.broadcast(id, {type: 'aphlict.server', data: message});
174
},
175
176
_receive: function(message, is_leader) {
177
switch (message.type) {
178
case 'aphlict.status':
179
this._setStatus(message.data);
180
break;
181
182
case 'aphlict.getstatus':
183
if (is_leader) {
184
this._broadcastStatus(this.getStatus());
185
}
186
break;
187
188
case 'aphlict.getsubscribers':
189
JX.Leader.broadcast(
190
null,
191
{type: 'aphlict.subscribe', data: this._subscriptions});
192
break;
193
194
case 'aphlict.subscribe':
195
if (is_leader) {
196
this._writeCommand('subscribe', message.data);
197
}
198
break;
199
200
case 'aphlict.replay':
201
if (is_leader) {
202
this._writeCommand('replay', message.data);
203
}
204
break;
205
206
default:
207
var handler = this.getHandler();
208
handler && handler(message);
209
break;
210
}
211
},
212
213
_setStatus: function(status) {
214
this._status = status;
215
216
// If we've ever seen an open connection, any new connection we make
217
// is a reconnect and should replay history.
218
if (status == 'open') {
219
this._isReconnect = true;
220
}
221
222
this.invoke('didChangeStatus');
223
},
224
225
_write: function(message) {
226
this._socket.send(JX.JSON.stringify(message));
227
},
228
229
_writeCommand: function(command, message) {
230
var frame = {
231
command: command,
232
data: message
233
};
234
235
return this._write(frame);
236
},
237
238
_keepalive: function() {
239
this._writeCommand('ping', null);
240
}
241
242
},
243
244
properties: {
245
handler: null
246
},
247
248
statics: {
249
_instance: null,
250
251
getInstance: function() {
252
var self = JX.Aphlict;
253
if (!self._instance) {
254
return null;
255
}
256
return self._instance;
257
}
258
259
}
260
261
});
262
263