oorja 1.5.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/bin/oorja +5 -0
- package/lib/commands/signout.js +2 -2
- package/lib/commands/teletype/index.js +3 -3
- package/lib/lib/config.d.ts +5 -6
- package/lib/lib/config.js +12 -7
- package/lib/lib/encryption.d.ts +1 -1
- package/lib/lib/encryption.js +12 -7
- package/lib/lib/index.js +1 -0
- package/lib/lib/oorja/index.js +14 -13
- package/lib/lib/oorja/preflight.d.ts +1 -1
- package/lib/lib/oorja/preflight.js +17 -11
- package/lib/lib/surya/index.d.ts +2 -2
- package/lib/lib/surya/index.js +15 -13
- package/lib/lib/surya/types.d.ts +10 -10
- package/lib/lib/surya/vendor/phoenix/ajax.d.ts +8 -0
- package/lib/lib/surya/vendor/phoenix/ajax.js +85 -0
- package/lib/lib/surya/vendor/phoenix/channel.d.ts +154 -0
- package/lib/lib/surya/vendor/phoenix/channel.js +311 -0
- package/lib/lib/surya/vendor/phoenix/constants.d.ts +33 -0
- package/lib/lib/surya/vendor/phoenix/constants.js +32 -0
- package/lib/lib/surya/vendor/phoenix/index.d.ts +199 -0
- package/lib/lib/surya/vendor/phoenix/index.js +207 -0
- package/lib/lib/surya/vendor/phoenix/longpoll.d.ts +12 -0
- package/lib/lib/surya/vendor/phoenix/longpoll.js +129 -0
- package/lib/lib/surya/vendor/phoenix/presence.d.ts +44 -0
- package/lib/lib/surya/vendor/phoenix/presence.js +155 -0
- package/lib/lib/surya/vendor/phoenix/push.d.ts +57 -0
- package/lib/lib/surya/vendor/phoenix/push.js +125 -0
- package/lib/lib/surya/vendor/phoenix/serializer.d.ts +53 -0
- package/lib/lib/surya/vendor/phoenix/serializer.js +102 -0
- package/lib/lib/surya/vendor/phoenix/socket.d.ts +222 -0
- package/lib/lib/surya/vendor/phoenix/socket.js +544 -0
- package/lib/lib/surya/vendor/phoenix/timer.d.ts +25 -0
- package/lib/lib/surya/vendor/phoenix/timer.js +43 -0
- package/lib/lib/surya/vendor/phoenix/utils.d.ts +1 -0
- package/lib/lib/surya/vendor/phoenix/utils.js +15 -0
- package/lib/lib/teletype/auxiliary.d.ts +1 -1
- package/lib/lib/teletype/auxiliary.js +8 -4
- package/lib/lib/teletype/index.d.ts +1 -1
- package/lib/lib/teletype/index.js +13 -12
- package/lib/lib/utils.js +2 -1
- package/oclif.manifest.json +1 -1
- package/package.json +10 -9
- package/lib/lib/surya/vendor/phoenix.d.ts +0 -486
- package/lib/lib/surya/vendor/phoenix.js +0 -1299
|
@@ -1,1299 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// @ts-nocheck
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.Presence = exports.Ajax = exports.LongPoll = exports.Socket = exports.Serializer = exports.Channel = void 0;
|
|
5
|
-
// MIT License
|
|
6
|
-
// Copyright (c) 2014 Chris McCord
|
|
7
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
-
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
10
|
-
// minor tweaks to work within node for oorja
|
|
11
|
-
const DEFAULT_VSN = "2.0.0";
|
|
12
|
-
const SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
|
|
13
|
-
const DEFAULT_TIMEOUT = 10000;
|
|
14
|
-
const WS_CLOSE_NORMAL = 1000;
|
|
15
|
-
const CHANNEL_STATES = {
|
|
16
|
-
closed: "closed",
|
|
17
|
-
errored: "errored",
|
|
18
|
-
joined: "joined",
|
|
19
|
-
joining: "joining",
|
|
20
|
-
leaving: "leaving",
|
|
21
|
-
};
|
|
22
|
-
const CHANNEL_EVENTS = {
|
|
23
|
-
close: "phx_close",
|
|
24
|
-
error: "phx_error",
|
|
25
|
-
join: "phx_join",
|
|
26
|
-
reply: "phx_reply",
|
|
27
|
-
leave: "phx_leave"
|
|
28
|
-
};
|
|
29
|
-
const CHANNEL_LIFECYCLE_EVENTS = [
|
|
30
|
-
CHANNEL_EVENTS.close,
|
|
31
|
-
CHANNEL_EVENTS.error,
|
|
32
|
-
CHANNEL_EVENTS.join,
|
|
33
|
-
CHANNEL_EVENTS.reply,
|
|
34
|
-
CHANNEL_EVENTS.leave
|
|
35
|
-
];
|
|
36
|
-
const TRANSPORTS = {
|
|
37
|
-
longpoll: "longpoll",
|
|
38
|
-
websocket: "websocket"
|
|
39
|
-
};
|
|
40
|
-
// wraps value in closure or returns closure
|
|
41
|
-
let closure = (value) => {
|
|
42
|
-
if (typeof value === "function") {
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
let closure = function () { return value; };
|
|
47
|
-
return closure;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Initializes the Push
|
|
52
|
-
* @param {Channel} channel - The Channel
|
|
53
|
-
* @param {string} event - The event, for example `"phx_join"`
|
|
54
|
-
* @param {Object} payload - The payload, for example `{user_id: 123}`
|
|
55
|
-
* @param {number} timeout - The push timeout in milliseconds
|
|
56
|
-
*/
|
|
57
|
-
class Push {
|
|
58
|
-
constructor(channel, event, payload, timeout) {
|
|
59
|
-
this.channel = channel;
|
|
60
|
-
this.event = event;
|
|
61
|
-
this.payload = payload || function () { return {}; };
|
|
62
|
-
this.receivedResp = null;
|
|
63
|
-
this.timeout = timeout;
|
|
64
|
-
this.timeoutTimer = null;
|
|
65
|
-
this.recHooks = [];
|
|
66
|
-
this.sent = false;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
* @param {number} timeout
|
|
71
|
-
*/
|
|
72
|
-
resend(timeout) {
|
|
73
|
-
this.timeout = timeout;
|
|
74
|
-
this.reset();
|
|
75
|
-
this.send();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
*
|
|
79
|
-
*/
|
|
80
|
-
send() {
|
|
81
|
-
if (this.hasReceived("timeout")) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
this.startTimeout();
|
|
85
|
-
this.sent = true;
|
|
86
|
-
this.channel.socket.push({
|
|
87
|
-
topic: this.channel.topic,
|
|
88
|
-
event: this.event,
|
|
89
|
-
payload: this.payload(),
|
|
90
|
-
ref: this.ref,
|
|
91
|
-
join_ref: this.channel.joinRef()
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
*
|
|
96
|
-
* @param {*} status
|
|
97
|
-
* @param {*} callback
|
|
98
|
-
*/
|
|
99
|
-
receive(status, callback) {
|
|
100
|
-
if (this.hasReceived(status)) {
|
|
101
|
-
callback(this.receivedResp.response);
|
|
102
|
-
}
|
|
103
|
-
this.recHooks.push({ status, callback });
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* @private
|
|
108
|
-
*/
|
|
109
|
-
reset() {
|
|
110
|
-
this.cancelRefEvent();
|
|
111
|
-
this.ref = null;
|
|
112
|
-
this.refEvent = null;
|
|
113
|
-
this.receivedResp = null;
|
|
114
|
-
this.sent = false;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* @private
|
|
118
|
-
*/
|
|
119
|
-
matchReceive({ status, response, ref }) {
|
|
120
|
-
this.recHooks.filter(h => h.status === status)
|
|
121
|
-
.forEach(h => h.callback(response));
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* @private
|
|
125
|
-
*/
|
|
126
|
-
cancelRefEvent() {
|
|
127
|
-
if (!this.refEvent) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
this.channel.off(this.refEvent);
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* @private
|
|
134
|
-
*/
|
|
135
|
-
cancelTimeout() {
|
|
136
|
-
clearTimeout(this.timeoutTimer);
|
|
137
|
-
this.timeoutTimer = null;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* @private
|
|
141
|
-
*/
|
|
142
|
-
startTimeout() {
|
|
143
|
-
if (this.timeoutTimer) {
|
|
144
|
-
this.cancelTimeout();
|
|
145
|
-
}
|
|
146
|
-
this.ref = this.channel.socket.makeRef();
|
|
147
|
-
this.refEvent = this.channel.replyEventName(this.ref);
|
|
148
|
-
this.channel.on(this.refEvent, payload => {
|
|
149
|
-
this.cancelRefEvent();
|
|
150
|
-
this.cancelTimeout();
|
|
151
|
-
this.receivedResp = payload;
|
|
152
|
-
this.matchReceive(payload);
|
|
153
|
-
});
|
|
154
|
-
this.timeoutTimer = setTimeout(() => {
|
|
155
|
-
this.trigger("timeout", {});
|
|
156
|
-
}, this.timeout);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
|
-
hasReceived(status) {
|
|
162
|
-
return this.receivedResp && this.receivedResp.status === status;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* @private
|
|
166
|
-
*/
|
|
167
|
-
trigger(status, response) {
|
|
168
|
-
this.channel.trigger(this.refEvent, { status, response });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
*
|
|
173
|
-
* @param {string} topic
|
|
174
|
-
* @param {(Object|function)} params
|
|
175
|
-
* @param {Socket} socket
|
|
176
|
-
*/
|
|
177
|
-
class Channel {
|
|
178
|
-
constructor(topic, params, socket) {
|
|
179
|
-
this.state = CHANNEL_STATES.closed;
|
|
180
|
-
this.topic = topic;
|
|
181
|
-
this.params = closure(params || {});
|
|
182
|
-
this.socket = socket;
|
|
183
|
-
this.bindings = [];
|
|
184
|
-
this.bindingRef = 0;
|
|
185
|
-
this.timeout = this.socket.timeout;
|
|
186
|
-
this.joinedOnce = false;
|
|
187
|
-
this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
|
|
188
|
-
this.pushBuffer = [];
|
|
189
|
-
this.stateChangeRefs = [];
|
|
190
|
-
this.rejoinTimer = new Timer(() => {
|
|
191
|
-
if (this.socket.isConnected()) {
|
|
192
|
-
this.rejoin();
|
|
193
|
-
}
|
|
194
|
-
}, this.socket.rejoinAfterMs);
|
|
195
|
-
this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));
|
|
196
|
-
this.stateChangeRefs.push(this.socket.onOpen(() => {
|
|
197
|
-
this.rejoinTimer.reset();
|
|
198
|
-
if (this.isErrored()) {
|
|
199
|
-
this.rejoin();
|
|
200
|
-
}
|
|
201
|
-
}));
|
|
202
|
-
this.joinPush.receive("ok", () => {
|
|
203
|
-
this.state = CHANNEL_STATES.joined;
|
|
204
|
-
this.rejoinTimer.reset();
|
|
205
|
-
this.pushBuffer.forEach(pushEvent => pushEvent.send());
|
|
206
|
-
this.pushBuffer = [];
|
|
207
|
-
});
|
|
208
|
-
this.joinPush.receive("error", () => {
|
|
209
|
-
this.state = CHANNEL_STATES.errored;
|
|
210
|
-
if (this.socket.isConnected()) {
|
|
211
|
-
this.rejoinTimer.scheduleTimeout();
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
this.onClose(() => {
|
|
215
|
-
this.rejoinTimer.reset();
|
|
216
|
-
if (this.socket.hasLogger())
|
|
217
|
-
this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`);
|
|
218
|
-
this.state = CHANNEL_STATES.closed;
|
|
219
|
-
this.socket.remove(this);
|
|
220
|
-
});
|
|
221
|
-
this.onError(reason => {
|
|
222
|
-
if (this.socket.hasLogger())
|
|
223
|
-
this.socket.log("channel", `error ${this.topic}`, reason);
|
|
224
|
-
if (this.isJoining()) {
|
|
225
|
-
this.joinPush.reset();
|
|
226
|
-
}
|
|
227
|
-
this.state = CHANNEL_STATES.errored;
|
|
228
|
-
if (this.socket.isConnected()) {
|
|
229
|
-
this.rejoinTimer.scheduleTimeout();
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
this.joinPush.receive("timeout", () => {
|
|
233
|
-
if (this.socket.hasLogger())
|
|
234
|
-
this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);
|
|
235
|
-
let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);
|
|
236
|
-
leavePush.send();
|
|
237
|
-
this.state = CHANNEL_STATES.errored;
|
|
238
|
-
this.joinPush.reset();
|
|
239
|
-
if (this.socket.isConnected()) {
|
|
240
|
-
this.rejoinTimer.scheduleTimeout();
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
this.on(CHANNEL_EVENTS.reply, (payload, ref) => {
|
|
244
|
-
this.trigger(this.replyEventName(ref), payload);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Join the channel
|
|
249
|
-
* @param {integer} timeout
|
|
250
|
-
* @returns {Push}
|
|
251
|
-
*/
|
|
252
|
-
join(timeout = this.timeout) {
|
|
253
|
-
if (this.joinedOnce) {
|
|
254
|
-
throw new Error(`tried to join multiple times. 'join' can only be called a single time per channel instance`);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
this.timeout = timeout;
|
|
258
|
-
this.joinedOnce = true;
|
|
259
|
-
this.rejoin();
|
|
260
|
-
return this.joinPush;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Hook into channel close
|
|
265
|
-
* @param {Function} callback
|
|
266
|
-
*/
|
|
267
|
-
onClose(callback) {
|
|
268
|
-
this.on(CHANNEL_EVENTS.close, callback);
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Hook into channel errors
|
|
272
|
-
* @param {Function} callback
|
|
273
|
-
*/
|
|
274
|
-
onError(callback) {
|
|
275
|
-
return this.on(CHANNEL_EVENTS.error, reason => callback(reason));
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Subscribes on channel events
|
|
279
|
-
*
|
|
280
|
-
* Subscription returns a ref counter, which can be used later to
|
|
281
|
-
* unsubscribe the exact event listener
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* const ref1 = channel.on("event", do_stuff)
|
|
285
|
-
* const ref2 = channel.on("event", do_other_stuff)
|
|
286
|
-
* channel.off("event", ref1)
|
|
287
|
-
* // Since unsubscription, do_stuff won't fire,
|
|
288
|
-
* // while do_other_stuff will keep firing on the "event"
|
|
289
|
-
*
|
|
290
|
-
* @param {string} event
|
|
291
|
-
* @param {Function} callback
|
|
292
|
-
* @returns {integer} ref
|
|
293
|
-
*/
|
|
294
|
-
on(event, callback) {
|
|
295
|
-
let ref = this.bindingRef++;
|
|
296
|
-
this.bindings.push({ event, ref, callback });
|
|
297
|
-
return ref;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Unsubscribes off of channel events
|
|
301
|
-
*
|
|
302
|
-
* Use the ref returned from a channel.on() to unsubscribe one
|
|
303
|
-
* handler, or pass nothing for the ref to unsubscribe all
|
|
304
|
-
* handlers for the given event.
|
|
305
|
-
*
|
|
306
|
-
* @example
|
|
307
|
-
* // Unsubscribe the do_stuff handler
|
|
308
|
-
* const ref1 = channel.on("event", do_stuff)
|
|
309
|
-
* channel.off("event", ref1)
|
|
310
|
-
*
|
|
311
|
-
* // Unsubscribe all handlers from event
|
|
312
|
-
* channel.off("event")
|
|
313
|
-
*
|
|
314
|
-
* @param {string} event
|
|
315
|
-
* @param {integer} ref
|
|
316
|
-
*/
|
|
317
|
-
off(event, ref) {
|
|
318
|
-
this.bindings = this.bindings.filter((bind) => {
|
|
319
|
-
return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref));
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* @private
|
|
324
|
-
*/
|
|
325
|
-
canPush() { return this.socket.isConnected() && this.isJoined(); }
|
|
326
|
-
/**
|
|
327
|
-
* Sends a message `event` to phoenix with the payload `payload`.
|
|
328
|
-
* Phoenix receives this in the `handle_in(event, payload, socket)`
|
|
329
|
-
* function. if phoenix replies or it times out (default 10000ms),
|
|
330
|
-
* then optionally the reply can be received.
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* channel.push("event")
|
|
334
|
-
* .receive("ok", payload => console.log("phoenix replied:", payload))
|
|
335
|
-
* .receive("error", err => console.log("phoenix errored", err))
|
|
336
|
-
* .receive("timeout", () => console.log("timed out pushing"))
|
|
337
|
-
* @param {string} event
|
|
338
|
-
* @param {Object} payload
|
|
339
|
-
* @param {number} [timeout]
|
|
340
|
-
* @returns {Push}
|
|
341
|
-
*/
|
|
342
|
-
push(event, payload, timeout = this.timeout) {
|
|
343
|
-
if (!this.joinedOnce) {
|
|
344
|
-
throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);
|
|
345
|
-
}
|
|
346
|
-
let pushEvent = new Push(this, event, function () { return payload; }, timeout);
|
|
347
|
-
if (this.canPush()) {
|
|
348
|
-
pushEvent.send();
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
pushEvent.startTimeout();
|
|
352
|
-
this.pushBuffer.push(pushEvent);
|
|
353
|
-
}
|
|
354
|
-
return pushEvent;
|
|
355
|
-
}
|
|
356
|
-
/** Leaves the channel
|
|
357
|
-
*
|
|
358
|
-
* Unsubscribes from server events, and
|
|
359
|
-
* instructs channel to terminate on server
|
|
360
|
-
*
|
|
361
|
-
* Triggers onClose() hooks
|
|
362
|
-
*
|
|
363
|
-
* To receive leave acknowledgements, use the `receive`
|
|
364
|
-
* hook to bind to the server ack, ie:
|
|
365
|
-
*
|
|
366
|
-
* @example
|
|
367
|
-
* channel.leave().receive("ok", () => alert("left!") )
|
|
368
|
-
*
|
|
369
|
-
* @param {integer} timeout
|
|
370
|
-
* @returns {Push}
|
|
371
|
-
*/
|
|
372
|
-
leave(timeout = this.timeout) {
|
|
373
|
-
this.rejoinTimer.reset();
|
|
374
|
-
this.joinPush.cancelTimeout();
|
|
375
|
-
this.state = CHANNEL_STATES.leaving;
|
|
376
|
-
let onClose = () => {
|
|
377
|
-
if (this.socket.hasLogger())
|
|
378
|
-
this.socket.log("channel", `leave ${this.topic}`);
|
|
379
|
-
this.trigger(CHANNEL_EVENTS.close, "leave");
|
|
380
|
-
};
|
|
381
|
-
let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);
|
|
382
|
-
leavePush.receive("ok", () => onClose())
|
|
383
|
-
.receive("timeout", () => onClose());
|
|
384
|
-
leavePush.send();
|
|
385
|
-
if (!this.canPush()) {
|
|
386
|
-
leavePush.trigger("ok", {});
|
|
387
|
-
}
|
|
388
|
-
return leavePush;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Overridable message hook
|
|
392
|
-
*
|
|
393
|
-
* Receives all events for specialized message handling
|
|
394
|
-
* before dispatching to the channel callbacks.
|
|
395
|
-
*
|
|
396
|
-
* Must return the payload, modified or unmodified
|
|
397
|
-
* @param {string} event
|
|
398
|
-
* @param {Object} payload
|
|
399
|
-
* @param {integer} ref
|
|
400
|
-
* @returns {Object}
|
|
401
|
-
*/
|
|
402
|
-
onMessage(event, payload, ref) { return payload; }
|
|
403
|
-
/**
|
|
404
|
-
* @private
|
|
405
|
-
*/
|
|
406
|
-
isLifecycleEvent(event) { return CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0; }
|
|
407
|
-
/**
|
|
408
|
-
* @private
|
|
409
|
-
*/
|
|
410
|
-
isMember(topic, event, payload, joinRef) {
|
|
411
|
-
if (this.topic !== topic) {
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
if (joinRef && joinRef !== this.joinRef() && this.isLifecycleEvent(event)) {
|
|
415
|
-
if (this.socket.hasLogger())
|
|
416
|
-
this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef });
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* @private
|
|
425
|
-
*/
|
|
426
|
-
joinRef() { return this.joinPush.ref; }
|
|
427
|
-
/**
|
|
428
|
-
* @private
|
|
429
|
-
*/
|
|
430
|
-
rejoin(timeout = this.timeout) {
|
|
431
|
-
if (this.isLeaving()) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
this.socket.leaveOpenTopic(this.topic);
|
|
435
|
-
this.state = CHANNEL_STATES.joining;
|
|
436
|
-
this.joinPush.resend(timeout);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* @private
|
|
440
|
-
*/
|
|
441
|
-
trigger(event, payload, ref, joinRef) {
|
|
442
|
-
let handledPayload = this.onMessage(event, payload, ref, joinRef);
|
|
443
|
-
if (payload && !handledPayload) {
|
|
444
|
-
throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");
|
|
445
|
-
}
|
|
446
|
-
let eventBindings = this.bindings.filter(bind => bind.event === event);
|
|
447
|
-
for (let i = 0; i < eventBindings.length; i++) {
|
|
448
|
-
let bind = eventBindings[i];
|
|
449
|
-
bind.callback(handledPayload, ref, joinRef || this.joinRef());
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* @private
|
|
454
|
-
*/
|
|
455
|
-
replyEventName(ref) { return `chan_reply_${ref}`; }
|
|
456
|
-
/**
|
|
457
|
-
* @private
|
|
458
|
-
*/
|
|
459
|
-
isClosed() { return this.state === CHANNEL_STATES.closed; }
|
|
460
|
-
/**
|
|
461
|
-
* @private
|
|
462
|
-
*/
|
|
463
|
-
isErrored() { return this.state === CHANNEL_STATES.errored; }
|
|
464
|
-
/**
|
|
465
|
-
* @private
|
|
466
|
-
*/
|
|
467
|
-
isJoined() { return this.state === CHANNEL_STATES.joined; }
|
|
468
|
-
/**
|
|
469
|
-
* @private
|
|
470
|
-
*/
|
|
471
|
-
isJoining() { return this.state === CHANNEL_STATES.joining; }
|
|
472
|
-
/**
|
|
473
|
-
* @private
|
|
474
|
-
*/
|
|
475
|
-
isLeaving() { return this.state === CHANNEL_STATES.leaving; }
|
|
476
|
-
}
|
|
477
|
-
exports.Channel = Channel;
|
|
478
|
-
/* The default serializer for encoding and decoding messages */
|
|
479
|
-
exports.Serializer = {
|
|
480
|
-
encode(msg, callback) {
|
|
481
|
-
let payload = [
|
|
482
|
-
msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload
|
|
483
|
-
];
|
|
484
|
-
return callback(JSON.stringify(payload));
|
|
485
|
-
},
|
|
486
|
-
decode(rawPayload, callback) {
|
|
487
|
-
let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);
|
|
488
|
-
return callback({ join_ref, ref, topic, event, payload });
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
/** Initializes the Socket
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
|
|
495
|
-
*
|
|
496
|
-
* @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`,
|
|
497
|
-
* `"wss://example.com"`
|
|
498
|
-
* `"/socket"` (inherited host & protocol)
|
|
499
|
-
* @param {Object} [opts] - Optional configuration
|
|
500
|
-
* @param {string} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
|
|
501
|
-
*
|
|
502
|
-
* Defaults to WebSocket with automatic LongPoll fallback.
|
|
503
|
-
* @param {Function} [opts.encode] - The function to encode outgoing messages.
|
|
504
|
-
*
|
|
505
|
-
* Defaults to JSON encoder.
|
|
506
|
-
*
|
|
507
|
-
* @param {Function} [opts.decode] - The function to decode incoming messages.
|
|
508
|
-
*
|
|
509
|
-
* Defaults to JSON:
|
|
510
|
-
*
|
|
511
|
-
* ```javascript
|
|
512
|
-
* (payload, callback) => callback(JSON.parse(payload))
|
|
513
|
-
* ```
|
|
514
|
-
*
|
|
515
|
-
* @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.
|
|
516
|
-
*
|
|
517
|
-
* Defaults `DEFAULT_TIMEOUT`
|
|
518
|
-
* @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message
|
|
519
|
-
* @param {number} [opts.reconnectAfterMs] - The optional function that returns the millsec
|
|
520
|
-
* socket reconnect interval.
|
|
521
|
-
*
|
|
522
|
-
* Defaults to stepped backoff of:
|
|
523
|
-
*
|
|
524
|
-
* ```javascript
|
|
525
|
-
* function(tries){
|
|
526
|
-
* return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000
|
|
527
|
-
* }
|
|
528
|
-
* ````
|
|
529
|
-
*
|
|
530
|
-
* @param {number} [opts.rejoinAfterMs] - The optional function that returns the millsec
|
|
531
|
-
* rejoin interval for individual channels.
|
|
532
|
-
*
|
|
533
|
-
* ```javascript
|
|
534
|
-
* function(tries){
|
|
535
|
-
* return [1000, 2000, 5000][tries - 1] || 10000
|
|
536
|
-
* }
|
|
537
|
-
* ````
|
|
538
|
-
*
|
|
539
|
-
* @param {Function} [opts.logger] - The optional function for specialized logging, ie:
|
|
540
|
-
*
|
|
541
|
-
* ```javascript
|
|
542
|
-
* function(kind, msg, data) {
|
|
543
|
-
* console.log(`${kind}: ${msg}`, data)
|
|
544
|
-
* }
|
|
545
|
-
* ```
|
|
546
|
-
*
|
|
547
|
-
* @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.
|
|
548
|
-
*
|
|
549
|
-
* Defaults to 20s (double the server long poll timer).
|
|
550
|
-
*
|
|
551
|
-
* @param {{Object|function)} [opts.params] - The optional params to pass when connecting
|
|
552
|
-
* @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.
|
|
553
|
-
*
|
|
554
|
-
* Defaults to "arraybuffer"
|
|
555
|
-
*
|
|
556
|
-
* @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.
|
|
557
|
-
*
|
|
558
|
-
* Defaults to DEFAULT_VSN.
|
|
559
|
-
*/
|
|
560
|
-
class Socket {
|
|
561
|
-
constructor(endPoint, opts = {}) {
|
|
562
|
-
this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
|
|
563
|
-
this.channels = [];
|
|
564
|
-
this.sendBuffer = [];
|
|
565
|
-
this.ref = 0;
|
|
566
|
-
this.timeout = opts.timeout || DEFAULT_TIMEOUT;
|
|
567
|
-
this.transport = require("ws");
|
|
568
|
-
this.defaultEncoder = exports.Serializer.encode;
|
|
569
|
-
this.defaultDecoder = exports.Serializer.decode;
|
|
570
|
-
this.closeWasClean = false;
|
|
571
|
-
this.unloaded = false;
|
|
572
|
-
this.binaryType = opts.binaryType || "arraybuffer";
|
|
573
|
-
if (this.transport !== LongPoll) {
|
|
574
|
-
this.encode = opts.encode || this.defaultEncoder;
|
|
575
|
-
this.decode = opts.decode || this.defaultDecoder;
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
578
|
-
this.encode = this.defaultEncoder;
|
|
579
|
-
this.decode = this.defaultDecoder;
|
|
580
|
-
}
|
|
581
|
-
this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
|
|
582
|
-
this.rejoinAfterMs = (tries) => {
|
|
583
|
-
if (opts.rejoinAfterMs) {
|
|
584
|
-
return opts.rejoinAfterMs(tries);
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
return [1000, 2000, 5000][tries - 1] || 10000;
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
this.reconnectAfterMs = (tries) => {
|
|
591
|
-
if (this.unloaded) {
|
|
592
|
-
return 100;
|
|
593
|
-
}
|
|
594
|
-
if (opts.reconnectAfterMs) {
|
|
595
|
-
return opts.reconnectAfterMs(tries);
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000;
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
this.logger = opts.logger || null;
|
|
602
|
-
this.longpollerTimeout = opts.longpollerTimeout || 20000;
|
|
603
|
-
this.params = closure(opts.params || {});
|
|
604
|
-
this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;
|
|
605
|
-
this.vsn = opts.vsn || DEFAULT_VSN;
|
|
606
|
-
this.heartbeatTimer = null;
|
|
607
|
-
this.pendingHeartbeatRef = null;
|
|
608
|
-
this.reconnectTimer = new Timer(() => {
|
|
609
|
-
this.teardown(() => this.connect());
|
|
610
|
-
}, this.reconnectAfterMs);
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Returns the socket protocol
|
|
614
|
-
*
|
|
615
|
-
* @returns {string}
|
|
616
|
-
*/
|
|
617
|
-
protocol() { return location.protocol.match(/^https/) ? "wss" : "ws"; }
|
|
618
|
-
/**
|
|
619
|
-
* The fully qualifed socket url
|
|
620
|
-
*
|
|
621
|
-
* @returns {string}
|
|
622
|
-
*/
|
|
623
|
-
endPointURL() {
|
|
624
|
-
let uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn });
|
|
625
|
-
return uri;
|
|
626
|
-
// if(uri.charAt(0) !== "/"){ return uri }
|
|
627
|
-
// if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` }
|
|
628
|
-
// return `${this.protocol()}://${location.host}${uri}`
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Disconnects the socket
|
|
632
|
-
*
|
|
633
|
-
* See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.
|
|
634
|
-
*
|
|
635
|
-
* @param {Function} callback - Optional callback which is called after socket is disconnected.
|
|
636
|
-
* @param {integer} code - A status code for disconnection (Optional).
|
|
637
|
-
* @param {string} reason - A textual description of the reason to disconnect. (Optional)
|
|
638
|
-
*/
|
|
639
|
-
disconnect(callback, code, reason) {
|
|
640
|
-
this.closeWasClean = true;
|
|
641
|
-
this.reconnectTimer.reset();
|
|
642
|
-
this.teardown(callback, code, reason);
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
*
|
|
646
|
-
* @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
|
|
647
|
-
*
|
|
648
|
-
* Passing params to connect is deprecated; pass them in the Socket constructor instead:
|
|
649
|
-
* `new Socket("/socket", {params: {user_id: userToken}})`.
|
|
650
|
-
*/
|
|
651
|
-
connect(params) {
|
|
652
|
-
if (params) {
|
|
653
|
-
console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
|
|
654
|
-
this.params = closure(params);
|
|
655
|
-
}
|
|
656
|
-
if (this.conn) {
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
this.closeWasClean = false;
|
|
660
|
-
this.conn = new this.transport(this.endPointURL());
|
|
661
|
-
this.conn.binaryType = this.binaryType;
|
|
662
|
-
this.conn.timeout = this.longpollerTimeout;
|
|
663
|
-
this.conn.onopen = () => this.onConnOpen();
|
|
664
|
-
this.conn.onerror = error => this.onConnError(error);
|
|
665
|
-
this.conn.onmessage = event => this.onConnMessage(event);
|
|
666
|
-
this.conn.onclose = event => this.onConnClose(event);
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* Logs the message. Override `this.logger` for specialized logging. noops by default
|
|
670
|
-
* @param {string} kind
|
|
671
|
-
* @param {string} msg
|
|
672
|
-
* @param {Object} data
|
|
673
|
-
*/
|
|
674
|
-
log(kind, msg, data) { this.logger(kind, msg, data); }
|
|
675
|
-
/**
|
|
676
|
-
* Returns true if a logger has been set on this socket.
|
|
677
|
-
*/
|
|
678
|
-
hasLogger() { return this.logger !== null; }
|
|
679
|
-
/**
|
|
680
|
-
* Registers callbacks for connection open events
|
|
681
|
-
*
|
|
682
|
-
* @example socket.onOpen(function(){ console.info("the socket was opened") })
|
|
683
|
-
*
|
|
684
|
-
* @param {Function} callback
|
|
685
|
-
*/
|
|
686
|
-
onOpen(callback) {
|
|
687
|
-
let ref = this.makeRef();
|
|
688
|
-
this.stateChangeCallbacks.open.push([ref, callback]);
|
|
689
|
-
return ref;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Registers callbacks for connection close events
|
|
693
|
-
* @param {Function} callback
|
|
694
|
-
*/
|
|
695
|
-
onClose(callback) {
|
|
696
|
-
let ref = this.makeRef();
|
|
697
|
-
this.stateChangeCallbacks.close.push([ref, callback]);
|
|
698
|
-
return ref;
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* Registers callbacks for connection error events
|
|
702
|
-
*
|
|
703
|
-
* @example socket.onError(function(error){ alert("An error occurred") })
|
|
704
|
-
*
|
|
705
|
-
* @param {Function} callback
|
|
706
|
-
*/
|
|
707
|
-
onError(callback) {
|
|
708
|
-
let ref = this.makeRef();
|
|
709
|
-
this.stateChangeCallbacks.error.push([ref, callback]);
|
|
710
|
-
return ref;
|
|
711
|
-
}
|
|
712
|
-
/**
|
|
713
|
-
* Registers callbacks for connection message events
|
|
714
|
-
* @param {Function} callback
|
|
715
|
-
*/
|
|
716
|
-
onMessage(callback) {
|
|
717
|
-
let ref = this.makeRef();
|
|
718
|
-
this.stateChangeCallbacks.message.push([ref, callback]);
|
|
719
|
-
return ref;
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* @private
|
|
723
|
-
*/
|
|
724
|
-
onConnOpen() {
|
|
725
|
-
if (this.hasLogger())
|
|
726
|
-
this.log("transport", `connected to ${this.endPointURL()}`);
|
|
727
|
-
this.unloaded = false;
|
|
728
|
-
this.closeWasClean = false;
|
|
729
|
-
this.flushSendBuffer();
|
|
730
|
-
this.reconnectTimer.reset();
|
|
731
|
-
this.resetHeartbeat();
|
|
732
|
-
this.stateChangeCallbacks.open.forEach(([, callback]) => callback());
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* @private
|
|
736
|
-
*/
|
|
737
|
-
resetHeartbeat() {
|
|
738
|
-
if (this.conn && this.conn.skipHeartbeat) {
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
this.pendingHeartbeatRef = null;
|
|
742
|
-
clearInterval(this.heartbeatTimer);
|
|
743
|
-
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
|
|
744
|
-
}
|
|
745
|
-
teardown(callback, code, reason) {
|
|
746
|
-
if (!this.conn) {
|
|
747
|
-
return callback && callback();
|
|
748
|
-
}
|
|
749
|
-
this.waitForBufferDone(() => {
|
|
750
|
-
if (this.conn) {
|
|
751
|
-
if (code) {
|
|
752
|
-
this.conn.close(code, reason || "");
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
this.conn.close();
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
this.waitForSocketClosed(() => {
|
|
759
|
-
if (this.conn) {
|
|
760
|
-
this.conn.onclose = function () { }; // noop
|
|
761
|
-
this.conn = null;
|
|
762
|
-
}
|
|
763
|
-
callback && callback();
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
waitForBufferDone(callback, tries = 1) {
|
|
768
|
-
if (tries === 5 || !this.conn || !this.conn.bufferedAmount) {
|
|
769
|
-
callback();
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
setTimeout(() => {
|
|
773
|
-
this.waitForBufferDone(callback, tries + 1);
|
|
774
|
-
}, 150 * tries);
|
|
775
|
-
}
|
|
776
|
-
waitForSocketClosed(callback, tries = 1) {
|
|
777
|
-
if (tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed) {
|
|
778
|
-
callback();
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
setTimeout(() => {
|
|
782
|
-
this.waitForSocketClosed(callback, tries + 1);
|
|
783
|
-
}, 150 * tries);
|
|
784
|
-
}
|
|
785
|
-
onConnClose(event) {
|
|
786
|
-
if (this.hasLogger())
|
|
787
|
-
this.log("transport", "close", event);
|
|
788
|
-
this.triggerChanError();
|
|
789
|
-
clearInterval(this.heartbeatTimer);
|
|
790
|
-
if (!this.closeWasClean) {
|
|
791
|
-
this.reconnectTimer.scheduleTimeout();
|
|
792
|
-
}
|
|
793
|
-
this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* @private
|
|
797
|
-
*/
|
|
798
|
-
onConnError(error) {
|
|
799
|
-
if (this.hasLogger())
|
|
800
|
-
this.log("transport", error);
|
|
801
|
-
this.triggerChanError();
|
|
802
|
-
this.stateChangeCallbacks.error.forEach(([, callback]) => callback(error));
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* @private
|
|
806
|
-
*/
|
|
807
|
-
triggerChanError() {
|
|
808
|
-
this.channels.forEach(channel => {
|
|
809
|
-
if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {
|
|
810
|
-
channel.trigger(CHANNEL_EVENTS.error);
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* @returns {string}
|
|
816
|
-
*/
|
|
817
|
-
connectionState() {
|
|
818
|
-
switch (this.conn && this.conn.readyState) {
|
|
819
|
-
case SOCKET_STATES.connecting: return "connecting";
|
|
820
|
-
case SOCKET_STATES.open: return "open";
|
|
821
|
-
case SOCKET_STATES.closing: return "closing";
|
|
822
|
-
default: return "closed";
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* @returns {boolean}
|
|
827
|
-
*/
|
|
828
|
-
isConnected() { return this.connectionState() === "open"; }
|
|
829
|
-
/**
|
|
830
|
-
* @private
|
|
831
|
-
*
|
|
832
|
-
* @param {Channel}
|
|
833
|
-
*/
|
|
834
|
-
remove(channel) {
|
|
835
|
-
this.off(channel.stateChangeRefs);
|
|
836
|
-
this.channels = this.channels.filter(c => c.joinRef() !== channel.joinRef());
|
|
837
|
-
}
|
|
838
|
-
/**
|
|
839
|
-
* Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.
|
|
840
|
-
*
|
|
841
|
-
* @param {refs} - list of refs returned by calls to
|
|
842
|
-
* `onOpen`, `onClose`, `onError,` and `onMessage`
|
|
843
|
-
*/
|
|
844
|
-
off(refs) {
|
|
845
|
-
for (let key in this.stateChangeCallbacks) {
|
|
846
|
-
this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {
|
|
847
|
-
return refs.indexOf(ref) === -1;
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Initiates a new channel for the given topic
|
|
853
|
-
*
|
|
854
|
-
* @param {string} topic
|
|
855
|
-
* @param {Object} chanParams - Parameters for the channel
|
|
856
|
-
* @returns {Channel}
|
|
857
|
-
*/
|
|
858
|
-
channel(topic, chanParams = {}) {
|
|
859
|
-
let chan = new Channel(topic, chanParams, this);
|
|
860
|
-
this.channels.push(chan);
|
|
861
|
-
return chan;
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* @param {Object} data
|
|
865
|
-
*/
|
|
866
|
-
push(data) {
|
|
867
|
-
if (this.hasLogger()) {
|
|
868
|
-
let { topic, event, payload, ref, join_ref } = data;
|
|
869
|
-
this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload);
|
|
870
|
-
}
|
|
871
|
-
if (this.isConnected()) {
|
|
872
|
-
this.encode(data, result => this.conn.send(result));
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result)));
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Return the next message ref, accounting for overflows
|
|
880
|
-
* @returns {string}
|
|
881
|
-
*/
|
|
882
|
-
makeRef() {
|
|
883
|
-
let newRef = this.ref + 1;
|
|
884
|
-
if (newRef === this.ref) {
|
|
885
|
-
this.ref = 0;
|
|
886
|
-
}
|
|
887
|
-
else {
|
|
888
|
-
this.ref = newRef;
|
|
889
|
-
}
|
|
890
|
-
return this.ref.toString();
|
|
891
|
-
}
|
|
892
|
-
sendHeartbeat() {
|
|
893
|
-
if (!this.isConnected()) {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
if (this.pendingHeartbeatRef) {
|
|
897
|
-
this.pendingHeartbeatRef = null;
|
|
898
|
-
if (this.hasLogger())
|
|
899
|
-
this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
|
|
900
|
-
this.abnormalClose("heartbeat timeout");
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
this.pendingHeartbeatRef = this.makeRef();
|
|
904
|
-
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
|
|
905
|
-
}
|
|
906
|
-
abnormalClose(reason) {
|
|
907
|
-
this.closeWasClean = false;
|
|
908
|
-
this.conn.close(WS_CLOSE_NORMAL, reason);
|
|
909
|
-
}
|
|
910
|
-
flushSendBuffer() {
|
|
911
|
-
if (this.isConnected() && this.sendBuffer.length > 0) {
|
|
912
|
-
this.sendBuffer.forEach(callback => callback());
|
|
913
|
-
this.sendBuffer = [];
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
onConnMessage(rawMessage) {
|
|
917
|
-
this.decode(rawMessage.data, msg => {
|
|
918
|
-
let { topic, event, payload, ref, join_ref } = msg;
|
|
919
|
-
if (ref && ref === this.pendingHeartbeatRef) {
|
|
920
|
-
this.pendingHeartbeatRef = null;
|
|
921
|
-
}
|
|
922
|
-
if (this.hasLogger())
|
|
923
|
-
this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload);
|
|
924
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
925
|
-
const channel = this.channels[i];
|
|
926
|
-
if (!channel.isMember(topic, event, payload, join_ref)) {
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
channel.trigger(event, payload, ref, join_ref);
|
|
930
|
-
}
|
|
931
|
-
for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {
|
|
932
|
-
let [, callback] = this.stateChangeCallbacks.message[i];
|
|
933
|
-
callback(msg);
|
|
934
|
-
}
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
leaveOpenTopic(topic) {
|
|
938
|
-
let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()));
|
|
939
|
-
if (dupChannel) {
|
|
940
|
-
if (this.hasLogger())
|
|
941
|
-
this.log("transport", `leaving duplicate topic "${topic}"`);
|
|
942
|
-
dupChannel.leave();
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
exports.Socket = Socket;
|
|
947
|
-
class LongPoll {
|
|
948
|
-
constructor(endPoint) {
|
|
949
|
-
this.endPoint = null;
|
|
950
|
-
this.token = null;
|
|
951
|
-
this.skipHeartbeat = true;
|
|
952
|
-
this.onopen = function () { }; // noop
|
|
953
|
-
this.onerror = function () { }; // noop
|
|
954
|
-
this.onmessage = function () { }; // noop
|
|
955
|
-
this.onclose = function () { }; // noop
|
|
956
|
-
this.pollEndpoint = this.normalizeEndpoint(endPoint);
|
|
957
|
-
this.readyState = SOCKET_STATES.connecting;
|
|
958
|
-
this.poll();
|
|
959
|
-
}
|
|
960
|
-
normalizeEndpoint(endPoint) {
|
|
961
|
-
return (endPoint
|
|
962
|
-
.replace("ws://", "http://")
|
|
963
|
-
.replace("wss://", "https://")
|
|
964
|
-
.replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll));
|
|
965
|
-
}
|
|
966
|
-
endpointURL() {
|
|
967
|
-
return Ajax.appendParams(this.pollEndpoint, { token: this.token });
|
|
968
|
-
}
|
|
969
|
-
closeAndRetry() {
|
|
970
|
-
this.close();
|
|
971
|
-
this.readyState = SOCKET_STATES.connecting;
|
|
972
|
-
}
|
|
973
|
-
ontimeout() {
|
|
974
|
-
this.onerror("timeout");
|
|
975
|
-
this.closeAndRetry();
|
|
976
|
-
}
|
|
977
|
-
poll() {
|
|
978
|
-
if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), (resp) => {
|
|
982
|
-
if (resp) {
|
|
983
|
-
var { status, token, messages } = resp;
|
|
984
|
-
this.token = token;
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
var status = 0;
|
|
988
|
-
}
|
|
989
|
-
switch (status) {
|
|
990
|
-
case 200:
|
|
991
|
-
messages.forEach(msg => this.onmessage({ data: msg }));
|
|
992
|
-
this.poll();
|
|
993
|
-
break;
|
|
994
|
-
case 204:
|
|
995
|
-
this.poll();
|
|
996
|
-
break;
|
|
997
|
-
case 410:
|
|
998
|
-
this.readyState = SOCKET_STATES.open;
|
|
999
|
-
this.onopen();
|
|
1000
|
-
this.poll();
|
|
1001
|
-
break;
|
|
1002
|
-
case 403:
|
|
1003
|
-
this.onerror();
|
|
1004
|
-
this.close();
|
|
1005
|
-
break;
|
|
1006
|
-
case 0:
|
|
1007
|
-
case 500:
|
|
1008
|
-
this.onerror();
|
|
1009
|
-
this.closeAndRetry();
|
|
1010
|
-
break;
|
|
1011
|
-
default: throw new Error(`unhandled poll status ${status}`);
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
send(body) {
|
|
1016
|
-
Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), (resp) => {
|
|
1017
|
-
if (!resp || resp.status !== 200) {
|
|
1018
|
-
this.onerror(resp && resp.status);
|
|
1019
|
-
this.closeAndRetry();
|
|
1020
|
-
}
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
close(code, reason) {
|
|
1024
|
-
this.readyState = SOCKET_STATES.closed;
|
|
1025
|
-
this.onclose();
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
exports.LongPoll = LongPoll;
|
|
1029
|
-
class Ajax {
|
|
1030
|
-
static request(method, endPoint, accept, body, timeout, ontimeout, callback) {
|
|
1031
|
-
if (global.XDomainRequest) {
|
|
1032
|
-
let req = new XDomainRequest(); // IE8, IE9
|
|
1033
|
-
this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
let req = new global.XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari
|
|
1037
|
-
this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
|
|
1041
|
-
req.timeout = timeout;
|
|
1042
|
-
req.open(method, endPoint);
|
|
1043
|
-
req.onload = () => {
|
|
1044
|
-
let response = this.parseJSON(req.responseText);
|
|
1045
|
-
callback && callback(response);
|
|
1046
|
-
};
|
|
1047
|
-
if (ontimeout) {
|
|
1048
|
-
req.ontimeout = ontimeout;
|
|
1049
|
-
}
|
|
1050
|
-
// Work around bug in IE9 that requires an attached onprogress handler
|
|
1051
|
-
req.onprogress = () => { };
|
|
1052
|
-
req.send(body);
|
|
1053
|
-
}
|
|
1054
|
-
static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
|
|
1055
|
-
req.open(method, endPoint, true);
|
|
1056
|
-
req.timeout = timeout;
|
|
1057
|
-
req.setRequestHeader("Content-Type", accept);
|
|
1058
|
-
req.onerror = () => { callback && callback(null); };
|
|
1059
|
-
req.onreadystatechange = () => {
|
|
1060
|
-
if (req.readyState === this.states.complete && callback) {
|
|
1061
|
-
let response = this.parseJSON(req.responseText);
|
|
1062
|
-
callback(response);
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
if (ontimeout) {
|
|
1066
|
-
req.ontimeout = ontimeout;
|
|
1067
|
-
}
|
|
1068
|
-
req.send(body);
|
|
1069
|
-
}
|
|
1070
|
-
static parseJSON(resp) {
|
|
1071
|
-
if (!resp || resp === "") {
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
try {
|
|
1075
|
-
return JSON.parse(resp);
|
|
1076
|
-
}
|
|
1077
|
-
catch (e) {
|
|
1078
|
-
console && console.log("failed to parse JSON response", resp);
|
|
1079
|
-
return null;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
static serialize(obj, parentKey) {
|
|
1083
|
-
let queryStr = [];
|
|
1084
|
-
for (var key in obj) {
|
|
1085
|
-
if (!obj.hasOwnProperty(key)) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
let paramKey = parentKey ? `${parentKey}[${key}]` : key;
|
|
1089
|
-
let paramVal = obj[key];
|
|
1090
|
-
if (typeof paramVal === "object") {
|
|
1091
|
-
queryStr.push(this.serialize(paramVal, paramKey));
|
|
1092
|
-
}
|
|
1093
|
-
else {
|
|
1094
|
-
queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
return queryStr.join("&");
|
|
1098
|
-
}
|
|
1099
|
-
static appendParams(url, params) {
|
|
1100
|
-
if (Object.keys(params).length === 0) {
|
|
1101
|
-
return url;
|
|
1102
|
-
}
|
|
1103
|
-
let prefix = url.match(/\?/) ? "&" : "?";
|
|
1104
|
-
return `${url}${prefix}${this.serialize(params)}`;
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
exports.Ajax = Ajax;
|
|
1108
|
-
Ajax.states = { complete: 4 };
|
|
1109
|
-
/**
|
|
1110
|
-
* Initializes the Presence
|
|
1111
|
-
* @param {Channel} channel - The Channel
|
|
1112
|
-
* @param {Object} opts - The options,
|
|
1113
|
-
* for example `{events: {state: "state", diff: "diff"}}`
|
|
1114
|
-
*/
|
|
1115
|
-
class Presence {
|
|
1116
|
-
constructor(channel, opts = {}) {
|
|
1117
|
-
let events = opts.events || { state: "presence_state", diff: "presence_diff" };
|
|
1118
|
-
this.state = {};
|
|
1119
|
-
this.pendingDiffs = [];
|
|
1120
|
-
this.channel = channel;
|
|
1121
|
-
this.joinRef = null;
|
|
1122
|
-
this.caller = {
|
|
1123
|
-
onJoin: function () { },
|
|
1124
|
-
onLeave: function () { },
|
|
1125
|
-
onSync: function () { }
|
|
1126
|
-
};
|
|
1127
|
-
this.channel.on(events.state, newState => {
|
|
1128
|
-
let { onJoin, onLeave, onSync } = this.caller;
|
|
1129
|
-
this.joinRef = this.channel.joinRef();
|
|
1130
|
-
this.state = Presence.syncState(this.state, newState, onJoin, onLeave);
|
|
1131
|
-
this.pendingDiffs.forEach(diff => {
|
|
1132
|
-
this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);
|
|
1133
|
-
});
|
|
1134
|
-
this.pendingDiffs = [];
|
|
1135
|
-
onSync();
|
|
1136
|
-
});
|
|
1137
|
-
this.channel.on(events.diff, diff => {
|
|
1138
|
-
let { onJoin, onLeave, onSync } = this.caller;
|
|
1139
|
-
if (this.inPendingSyncState()) {
|
|
1140
|
-
this.pendingDiffs.push(diff);
|
|
1141
|
-
}
|
|
1142
|
-
else {
|
|
1143
|
-
this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);
|
|
1144
|
-
onSync();
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
onJoin(callback) { this.caller.onJoin = callback; }
|
|
1149
|
-
onLeave(callback) { this.caller.onLeave = callback; }
|
|
1150
|
-
onSync(callback) { this.caller.onSync = callback; }
|
|
1151
|
-
list(by) { return Presence.list(this.state, by); }
|
|
1152
|
-
inPendingSyncState() {
|
|
1153
|
-
return !this.joinRef || (this.joinRef !== this.channel.joinRef());
|
|
1154
|
-
}
|
|
1155
|
-
// lower-level public static API
|
|
1156
|
-
/**
|
|
1157
|
-
* Used to sync the list of presences on the server
|
|
1158
|
-
* with the client's state. An optional `onJoin` and `onLeave` callback can
|
|
1159
|
-
* be provided to react to changes in the client's local presences across
|
|
1160
|
-
* disconnects and reconnects with the server.
|
|
1161
|
-
*
|
|
1162
|
-
* @returns {Presence}
|
|
1163
|
-
*/
|
|
1164
|
-
static syncState(currentState, newState, onJoin, onLeave) {
|
|
1165
|
-
let state = this.clone(currentState);
|
|
1166
|
-
let joins = {};
|
|
1167
|
-
let leaves = {};
|
|
1168
|
-
this.map(state, (key, presence) => {
|
|
1169
|
-
if (!newState[key]) {
|
|
1170
|
-
leaves[key] = presence;
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
this.map(newState, (key, newPresence) => {
|
|
1174
|
-
let currentPresence = state[key];
|
|
1175
|
-
if (currentPresence) {
|
|
1176
|
-
let newRefs = newPresence.metas.map(m => m.phx_ref);
|
|
1177
|
-
let curRefs = currentPresence.metas.map(m => m.phx_ref);
|
|
1178
|
-
let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0);
|
|
1179
|
-
let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0);
|
|
1180
|
-
if (joinedMetas.length > 0) {
|
|
1181
|
-
joins[key] = newPresence;
|
|
1182
|
-
joins[key].metas = joinedMetas;
|
|
1183
|
-
}
|
|
1184
|
-
if (leftMetas.length > 0) {
|
|
1185
|
-
leaves[key] = this.clone(currentPresence);
|
|
1186
|
-
leaves[key].metas = leftMetas;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
else {
|
|
1190
|
-
joins[key] = newPresence;
|
|
1191
|
-
}
|
|
1192
|
-
});
|
|
1193
|
-
return this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
*
|
|
1197
|
-
* Used to sync a diff of presence join and leave
|
|
1198
|
-
* events from the server, as they happen. Like `syncState`, `syncDiff`
|
|
1199
|
-
* accepts optional `onJoin` and `onLeave` callbacks to react to a user
|
|
1200
|
-
* joining or leaving from a device.
|
|
1201
|
-
*
|
|
1202
|
-
* @returns {Presence}
|
|
1203
|
-
*/
|
|
1204
|
-
static syncDiff(currentState, { joins, leaves }, onJoin, onLeave) {
|
|
1205
|
-
let state = this.clone(currentState);
|
|
1206
|
-
if (!onJoin) {
|
|
1207
|
-
onJoin = function () { };
|
|
1208
|
-
}
|
|
1209
|
-
if (!onLeave) {
|
|
1210
|
-
onLeave = function () { };
|
|
1211
|
-
}
|
|
1212
|
-
this.map(joins, (key, newPresence) => {
|
|
1213
|
-
let currentPresence = state[key];
|
|
1214
|
-
state[key] = newPresence;
|
|
1215
|
-
if (currentPresence) {
|
|
1216
|
-
let joinedRefs = state[key].metas.map(m => m.phx_ref);
|
|
1217
|
-
let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0);
|
|
1218
|
-
state[key].metas.unshift(...curMetas);
|
|
1219
|
-
}
|
|
1220
|
-
onJoin(key, currentPresence, newPresence);
|
|
1221
|
-
});
|
|
1222
|
-
this.map(leaves, (key, leftPresence) => {
|
|
1223
|
-
let currentPresence = state[key];
|
|
1224
|
-
if (!currentPresence) {
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
let refsToRemove = leftPresence.metas.map(m => m.phx_ref);
|
|
1228
|
-
currentPresence.metas = currentPresence.metas.filter(p => {
|
|
1229
|
-
return refsToRemove.indexOf(p.phx_ref) < 0;
|
|
1230
|
-
});
|
|
1231
|
-
onLeave(key, currentPresence, leftPresence);
|
|
1232
|
-
if (currentPresence.metas.length === 0) {
|
|
1233
|
-
delete state[key];
|
|
1234
|
-
}
|
|
1235
|
-
});
|
|
1236
|
-
return state;
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Returns the array of presences, with selected metadata.
|
|
1240
|
-
*
|
|
1241
|
-
* @param {Object} presences
|
|
1242
|
-
* @param {Function} chooser
|
|
1243
|
-
*
|
|
1244
|
-
* @returns {Presence}
|
|
1245
|
-
*/
|
|
1246
|
-
static list(presences, chooser) {
|
|
1247
|
-
if (!chooser) {
|
|
1248
|
-
chooser = function (key, pres) { return pres; };
|
|
1249
|
-
}
|
|
1250
|
-
return this.map(presences, (key, presence) => {
|
|
1251
|
-
return chooser(key, presence);
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
// private
|
|
1255
|
-
static map(obj, func) {
|
|
1256
|
-
return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]));
|
|
1257
|
-
}
|
|
1258
|
-
static clone(obj) { return JSON.parse(JSON.stringify(obj)); }
|
|
1259
|
-
}
|
|
1260
|
-
exports.Presence = Presence;
|
|
1261
|
-
/**
|
|
1262
|
-
*
|
|
1263
|
-
* Creates a timer that accepts a `timerCalc` function to perform
|
|
1264
|
-
* calculated timeout retries, such as exponential backoff.
|
|
1265
|
-
*
|
|
1266
|
-
* @example
|
|
1267
|
-
* let reconnectTimer = new Timer(() => this.connect(), function(tries){
|
|
1268
|
-
* return [1000, 5000, 10000][tries - 1] || 10000
|
|
1269
|
-
* })
|
|
1270
|
-
* reconnectTimer.scheduleTimeout() // fires after 1000
|
|
1271
|
-
* reconnectTimer.scheduleTimeout() // fires after 5000
|
|
1272
|
-
* reconnectTimer.reset()
|
|
1273
|
-
* reconnectTimer.scheduleTimeout() // fires after 1000
|
|
1274
|
-
*
|
|
1275
|
-
* @param {Function} callback
|
|
1276
|
-
* @param {Function} timerCalc
|
|
1277
|
-
*/
|
|
1278
|
-
class Timer {
|
|
1279
|
-
constructor(callback, timerCalc) {
|
|
1280
|
-
this.callback = callback;
|
|
1281
|
-
this.timerCalc = timerCalc;
|
|
1282
|
-
this.timer = null;
|
|
1283
|
-
this.tries = 0;
|
|
1284
|
-
}
|
|
1285
|
-
reset() {
|
|
1286
|
-
this.tries = 0;
|
|
1287
|
-
clearTimeout(this.timer);
|
|
1288
|
-
}
|
|
1289
|
-
/**
|
|
1290
|
-
* Cancels any previous scheduleTimeout and schedules callback
|
|
1291
|
-
*/
|
|
1292
|
-
scheduleTimeout() {
|
|
1293
|
-
clearTimeout(this.timer);
|
|
1294
|
-
this.timer = setTimeout(() => {
|
|
1295
|
-
this.tries = this.tries + 1;
|
|
1296
|
-
this.callback();
|
|
1297
|
-
}, this.timerCalc(this.tries + 1));
|
|
1298
|
-
}
|
|
1299
|
-
}
|