pubsub-js-client 0.6.1
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 +70 -0
- package/package.json +26 -0
- package/src/IframeClient.js +182 -0
- package/src/IframeHost.js +174 -0
- package/src/PubsubDriver.js +177 -0
- package/src/PubsubSocket.js +186 -0
- package/src/PubsubTest.js +189 -0
- package/src/WebsocketClient.js +383 -0
- package/src/events.js +43 -0
- package/src/log.js +121 -0
- package/src/mymap.js +49 -0
- package/src/util.js +51 -0
@@ -0,0 +1,383 @@
|
|
1
|
+
|
2
|
+
import EventsDispatcher from "./events";
|
3
|
+
import logging from "./log";
|
4
|
+
import util from "./util";
|
5
|
+
import PubsubSocket from "./PubsubSocket";
|
6
|
+
import MyMap from "./mymap";
|
7
|
+
|
8
|
+
var logger = logging._getLogger("WebsocketClient");
|
9
|
+
|
10
|
+
const SOCKET_CLOSED_RECONNECT_TIME = 1 * 1000; // 1 second
|
11
|
+
const RESPONSE_TIMEOUT = 30 * 1000; // 30 seconds
|
12
|
+
const ERR_RESPONSE_TIMEOUT = "response timeout";
|
13
|
+
const NONCE_LENGTH = 30;
|
14
|
+
const FIRST_LISTEN_TIMEOUT = 45 * 1000; // 45 seconds
|
15
|
+
|
16
|
+
const addrProduction = "wss://pubsub-edge.twitch.tv:443/v1";
|
17
|
+
const addrDarklaunch = "wss://pubsub-edge-darklaunch.twitch.tv:443/v1";
|
18
|
+
const addrDevelopment = "ws://localhost:6900/v1";
|
19
|
+
|
20
|
+
class WebsocketClient extends EventsDispatcher {
|
21
|
+
|
22
|
+
constructor (opts) {
|
23
|
+
// opts should include: environment
|
24
|
+
super(opts);
|
25
|
+
this._opts = opts;
|
26
|
+
this._env = opts.env;
|
27
|
+
|
28
|
+
switch (this._env) {
|
29
|
+
case "production":
|
30
|
+
this._addr = addrProduction;
|
31
|
+
break;
|
32
|
+
case "darklaunch":
|
33
|
+
this._addr = addrDarklaunch;
|
34
|
+
break;
|
35
|
+
case "development":
|
36
|
+
this._addr = addrDevelopment;
|
37
|
+
break;
|
38
|
+
default:
|
39
|
+
this._addr = addrProduction;
|
40
|
+
}
|
41
|
+
|
42
|
+
// noop if WebSockets aren't supported
|
43
|
+
if (!window.WebSocket) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
// Keep track of Listen/Unlisten requests that have queued up while Driver is disconnected
|
48
|
+
this._queuedRequests = [];
|
49
|
+
// Keep track of pending responses; map is from nonce -> {timeout to clear, isListen bool, un/listen opts}
|
50
|
+
this._pendingResponses = new MyMap();
|
51
|
+
// Keep track of nonces from outstanding listen replays
|
52
|
+
this._pendingReplayResponses = new MyMap();
|
53
|
+
// Keep track of messages we are listening to, and their callbacks
|
54
|
+
this._listens = new EventsDispatcher();
|
55
|
+
// Keep track of topic+auth token for each successful LISTEN callback
|
56
|
+
this._replays = new MyMap();
|
57
|
+
this._replaysSize = 0;
|
58
|
+
|
59
|
+
// Track the 'time to first Listen'
|
60
|
+
this._firstConnectTime = this._firstListenTime = 0;
|
61
|
+
|
62
|
+
// Instantiate websocket connection
|
63
|
+
this._connectCalled = this._reconnecting = false;
|
64
|
+
this._primarySocket = new PubsubSocket({
|
65
|
+
addr: this._addr
|
66
|
+
});
|
67
|
+
this._bindPrimary(this._primarySocket);
|
68
|
+
}
|
69
|
+
|
70
|
+
verify () {
|
71
|
+
this._trigger("verified");
|
72
|
+
}
|
73
|
+
|
74
|
+
connect () {
|
75
|
+
// noop if WebSockets aren't supported
|
76
|
+
if (!window.WebSocket) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
if (this._connectCalled) {
|
81
|
+
// Noop for every "connect()" call after the first
|
82
|
+
if (this._primarySocket._isReady()) {
|
83
|
+
this._trigger("connected");
|
84
|
+
}
|
85
|
+
} else {
|
86
|
+
this._connectCalled = true;
|
87
|
+
this._primarySocket.connect();
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
_bindPrimary (socket) {
|
92
|
+
// Socket opening
|
93
|
+
socket.on('open', this._onPrimaryOpen, this);
|
94
|
+
// Pubsub messages
|
95
|
+
socket.on('response', this._onResponse, this);
|
96
|
+
socket.on('message', this._onMessage, this);
|
97
|
+
socket.on('reconnect', this._onReconnect, this);
|
98
|
+
// Errors
|
99
|
+
socket.on('connection_failure', this._onConnectionFailure, this);
|
100
|
+
}
|
101
|
+
|
102
|
+
_unbindPrimary (socket) {
|
103
|
+
// Socket opening
|
104
|
+
socket.off('open', this._onPrimaryOpen, this);
|
105
|
+
// Pubsub messages
|
106
|
+
socket.off('response', this._onResponse, this);
|
107
|
+
socket.off('message', this._onMessage, this);
|
108
|
+
socket.off('reconnect', this._onReconnect, this);
|
109
|
+
// Errors
|
110
|
+
socket.off('connection_failure', this._onConnectionFailure, this);
|
111
|
+
}
|
112
|
+
|
113
|
+
_onPrimaryOpen () {
|
114
|
+
logger.debug("primary open: " + this._primarySocket._id);
|
115
|
+
// Triggered when the PubsubDriver is ready to start receiving commands
|
116
|
+
if (this._firstConnectTime === 0) {
|
117
|
+
this._firstConnectTime = util.time.now();
|
118
|
+
}
|
119
|
+
|
120
|
+
this._connected = true;
|
121
|
+
this._trigger("connected");
|
122
|
+
|
123
|
+
this._flushQueuedRequests();
|
124
|
+
}
|
125
|
+
|
126
|
+
_onResponse (resp) {
|
127
|
+
logger.debug("primary response: " + JSON.stringify(resp));
|
128
|
+
if (this._pendingResponses.has(resp.nonce)) {
|
129
|
+
var responseInfo = this._pendingResponses.get(resp.nonce);
|
130
|
+
logger.debug("responseInfo: " + JSON.stringify(responseInfo));
|
131
|
+
clearTimeout(responseInfo.timeout);
|
132
|
+
this._pendingResponses.remove(resp.nonce);
|
133
|
+
|
134
|
+
if (resp.error === "") {
|
135
|
+
// Add/remove onMessage callback from the specified topic
|
136
|
+
// Also add/remove the auth token used for that topic/callback pair
|
137
|
+
if (responseInfo.message.type === "LISTEN") {
|
138
|
+
// Track time to first listen
|
139
|
+
if (this._firstListenTime === 0) {
|
140
|
+
this._firstListenTime = util.time.now();
|
141
|
+
}
|
142
|
+
|
143
|
+
this._replays.set(resp.nonce, {
|
144
|
+
nonce: resp.nonce,
|
145
|
+
message: responseInfo.callbacks.message,
|
146
|
+
topic: responseInfo.topic,
|
147
|
+
auth: responseInfo.auth
|
148
|
+
});
|
149
|
+
|
150
|
+
if (responseInfo.callbacks.message) {
|
151
|
+
this._listens.on(responseInfo.topic, responseInfo.callbacks.message, this);
|
152
|
+
}
|
153
|
+
} else if (responseInfo.message.type === "UNLISTEN") {
|
154
|
+
this._replays.remove(resp.nonce);
|
155
|
+
|
156
|
+
if (responseInfo.callbacks.message) {
|
157
|
+
this._listens.off(responseInfo.topic, responseInfo.callbacks.message, this);
|
158
|
+
}
|
159
|
+
}
|
160
|
+
// Call the specified onSuccess callback
|
161
|
+
if (responseInfo.callbacks.success) {
|
162
|
+
responseInfo.callbacks.success();
|
163
|
+
}
|
164
|
+
} else {
|
165
|
+
// Call the specified onFailure callback
|
166
|
+
if (responseInfo.callbacks.failure) {
|
167
|
+
responseInfo.callbacks.failure(resp.error);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
_onResponseTimeout (nonce) {
|
174
|
+
if (this._pendingResponses.has(nonce)) {
|
175
|
+
var info = this._pendingResponses.get(nonce);
|
176
|
+
this._pendingResponses.remove(nonce);
|
177
|
+
|
178
|
+
if (info.callbacks.failure) {
|
179
|
+
info.callbacks.failure(ERR_RESPONSE_TIMEOUT);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
_onMessage (msg) {
|
185
|
+
logger.debug("primary message: " + JSON.stringify(msg));
|
186
|
+
this._listens._trigger(msg.data.topic, msg.data.message);
|
187
|
+
}
|
188
|
+
|
189
|
+
_onConnectionFailure () {
|
190
|
+
logger.debug("connection failure");
|
191
|
+
// Call disconnection callback
|
192
|
+
this._trigger("disconnected");
|
193
|
+
// try to reconnect, using the same backupSocket flow as intentional reconnects
|
194
|
+
// will end up re-listening on all topics
|
195
|
+
this._notifyWhenOpen = true;
|
196
|
+
this._onReconnect();
|
197
|
+
}
|
198
|
+
|
199
|
+
// Smoothly reconnect, establishing a new socket before terminating the old one
|
200
|
+
_onReconnect () {
|
201
|
+
logger.debug("reconnecting...");
|
202
|
+
this._reconnecting = true;
|
203
|
+
this._backupSocket = new PubsubSocket({
|
204
|
+
addr: this._addr
|
205
|
+
});
|
206
|
+
this._bindBackup(this._backupSocket);
|
207
|
+
setTimeout(this._backupSocket.connect.bind(this._backupSocket), this._jitteredReconnectDelay());
|
208
|
+
}
|
209
|
+
|
210
|
+
_bindBackup (socket) {
|
211
|
+
socket.on('open', this._onBackupOpen, this);
|
212
|
+
socket.on('response', this._onBackupResponse, this);
|
213
|
+
}
|
214
|
+
|
215
|
+
_unbindBackup (socket) {
|
216
|
+
socket.off('open', this._onBackupOpen, this);
|
217
|
+
socket.off('response', this._onBackupResponse, this);
|
218
|
+
}
|
219
|
+
|
220
|
+
_onBackupOpen () {
|
221
|
+
logger.debug("Backup socket opened");
|
222
|
+
if (this._replays.size() > 0) {
|
223
|
+
this._replayBackup();
|
224
|
+
} else {
|
225
|
+
this._swapSockets();
|
226
|
+
if (this._notifyWhenOpen) {
|
227
|
+
logger.debug("triggering connected");
|
228
|
+
this._notifyWhenOpen = false;
|
229
|
+
this._trigger('connected');
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
// Get the backup socket up to speed by re-listening on topics
|
235
|
+
_replayBackup () {
|
236
|
+
var replays = this._replays.values();
|
237
|
+
for (var i = 0; i < replays.length; i++) {
|
238
|
+
var msg = {
|
239
|
+
type: "LISTEN",
|
240
|
+
nonce: this._generateNonce(),
|
241
|
+
data: {
|
242
|
+
topics: [replays[i].topic],
|
243
|
+
auth_token: replays[i].auth
|
244
|
+
}
|
245
|
+
};
|
246
|
+
this._pendingReplayResponses.set(msg.nonce, true);
|
247
|
+
this._backupSocket.send(msg);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
_onBackupResponse (resp) {
|
252
|
+
if (this._pendingReplayResponses.has(resp.nonce) && resp.error === "") {
|
253
|
+
this._pendingReplayResponses.remove(resp.nonce);
|
254
|
+
if (this._pendingReplayResponses.size() === 0) {
|
255
|
+
// Finished getting the backup socket up to speed
|
256
|
+
this._swapSockets();
|
257
|
+
if (this._notifyWhenOpen) {
|
258
|
+
// Flag set when the reconnection is accidental, and we need to notify the client that the pubsub is ready again, rather than just silently switching
|
259
|
+
logger.debug("triggering connected");
|
260
|
+
this._notifyWhenOpen = false;
|
261
|
+
this._trigger('connected');
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
_swapSockets () {
|
268
|
+
logger.debug("swapping primary " + this._primarySocket._id + " and backup " + this._backupSocket._id);
|
269
|
+
this._unbindPrimary(this._primarySocket);
|
270
|
+
this._unbindBackup(this._backupSocket);
|
271
|
+
this._bindPrimary(this._backupSocket);
|
272
|
+
this._primarySocket.close();
|
273
|
+
this._primarySocket = this._backupSocket;
|
274
|
+
this._reconnecting = false;
|
275
|
+
this._flushQueuedRequests();
|
276
|
+
}
|
277
|
+
|
278
|
+
Listen (opts) {
|
279
|
+
// noop if WebSockets aren't supported
|
280
|
+
if (!window.WebSocket) {
|
281
|
+
return;
|
282
|
+
}
|
283
|
+
|
284
|
+
// opts should include: topic, auth, success, failure, message
|
285
|
+
logger.debug("listening on " + opts.topic);
|
286
|
+
var nonce = this._generateNonce();
|
287
|
+
var msg = {
|
288
|
+
type: "LISTEN",
|
289
|
+
nonce: nonce,
|
290
|
+
data: {
|
291
|
+
topics: [opts.topic],
|
292
|
+
auth_token: opts.auth
|
293
|
+
}
|
294
|
+
};
|
295
|
+
this._queuedSend(nonce, msg, opts);
|
296
|
+
}
|
297
|
+
|
298
|
+
Unlisten (opts) {
|
299
|
+
// noop if WebSockets aren't supported
|
300
|
+
if (!window.WebSocket) {
|
301
|
+
return;
|
302
|
+
}
|
303
|
+
|
304
|
+
// opts should include: topic, success, failure, message
|
305
|
+
logger.debug("unlistening on " + opts.topic + "(" + this._listens.count(opts.topic) + " listeners)");
|
306
|
+
|
307
|
+
// If there are more than one callbacks waiting on this topic, we can just remove the specified one rather than sending an UNLISTEN
|
308
|
+
if (this._listens.count(opts.topic) > 1) {
|
309
|
+
this._listens.off(opts.topic, opts.message);
|
310
|
+
|
311
|
+
// Delete from replays
|
312
|
+
for (var key in this._replays.map()) {
|
313
|
+
if (this._replays.get(key).message === opts.message) {
|
314
|
+
this._replays.remove(key);
|
315
|
+
break;
|
316
|
+
}
|
317
|
+
}
|
318
|
+
|
319
|
+
if (opts.success) {
|
320
|
+
opts.success();
|
321
|
+
}
|
322
|
+
logger.debug("now have " + this._listens.count(opts.topic) + " listeners");
|
323
|
+
return;
|
324
|
+
}
|
325
|
+
|
326
|
+
var nonce = this._generateNonce();
|
327
|
+
var msg = {
|
328
|
+
type: "UNLISTEN",
|
329
|
+
nonce: nonce,
|
330
|
+
data: {
|
331
|
+
topics: [opts.topic]
|
332
|
+
}
|
333
|
+
};
|
334
|
+
this._queuedSend(nonce, msg, opts);
|
335
|
+
}
|
336
|
+
|
337
|
+
_queuedSend (nonce, msg, opts) {
|
338
|
+
if (this._reconnecting || this._primarySocket._isReady() === false) {
|
339
|
+
// queue the message
|
340
|
+
logger.debug("queuing");
|
341
|
+
this._queuedRequests.push({nonce: nonce, msg: msg, opts: opts});
|
342
|
+
} else {
|
343
|
+
// send
|
344
|
+
logger.debug("sending immediately");
|
345
|
+
this._send(nonce, msg, opts);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
_flushQueuedRequests () {
|
350
|
+
logger.debug("flushing " + this._queuedRequests.length + " listen/unlistens");
|
351
|
+
while (this._queuedRequests.length > 0) {
|
352
|
+
var req = this._queuedRequests.shift();
|
353
|
+
this._send(req.nonce, req.msg, req.opts);
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
357
|
+
_send (nonce, msg, opts) {
|
358
|
+
this._pendingResponses.set(nonce, {
|
359
|
+
timeout: setTimeout(this._onResponseTimeout.bind(this), RESPONSE_TIMEOUT, nonce),
|
360
|
+
topic: opts.topic,
|
361
|
+
auth: opts.auth,
|
362
|
+
message: msg,
|
363
|
+
callbacks: {
|
364
|
+
success: opts.success,
|
365
|
+
failure: opts.failure,
|
366
|
+
message: opts.message
|
367
|
+
}
|
368
|
+
});
|
369
|
+
this._primarySocket.send(msg);
|
370
|
+
}
|
371
|
+
|
372
|
+
// Utility functions
|
373
|
+
_generateNonce () {
|
374
|
+
return util.generateString(NONCE_LENGTH);
|
375
|
+
}
|
376
|
+
|
377
|
+
_jitteredReconnectDelay () {
|
378
|
+
return util.randomInt(2000);
|
379
|
+
}
|
380
|
+
|
381
|
+
}
|
382
|
+
|
383
|
+
export default WebsocketClient;
|
package/src/events.js
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class EventsDispatcher {
|
2
|
+
on (name, callback, context) {
|
3
|
+
this._events = this._events || {};
|
4
|
+
this._events[name] = this._events[name] || [];
|
5
|
+
this._events[name].push(callback, context);
|
6
|
+
return this;
|
7
|
+
}
|
8
|
+
|
9
|
+
off (name, callback) {
|
10
|
+
if (this._events) {
|
11
|
+
var callbacks = this._events[name] || [];
|
12
|
+
var keep = this._events[name] = [];
|
13
|
+
for (var i = 0; i < callbacks.length; i += 2) {
|
14
|
+
if (callbacks[i] !== callback) {
|
15
|
+
keep.push(callbacks[i]);
|
16
|
+
keep.push(callbacks[i + 1]);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
return this;
|
21
|
+
}
|
22
|
+
|
23
|
+
_trigger (name) {
|
24
|
+
if (this._events) {
|
25
|
+
var callbacks = this._events[name] || [];
|
26
|
+
for (var i = 1; i < callbacks.length; i += 2) {
|
27
|
+
callbacks[i - 1].apply(callbacks[i], Array.prototype.slice.call(arguments, 1));
|
28
|
+
}
|
29
|
+
}
|
30
|
+
return this;
|
31
|
+
}
|
32
|
+
|
33
|
+
count (name) {
|
34
|
+
if (this._events) {
|
35
|
+
var callbacks = this._events[name] || [];
|
36
|
+
return (callbacks.length / 2);
|
37
|
+
}
|
38
|
+
return 0;
|
39
|
+
}
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
export default EventsDispatcher;
|
package/src/log.js
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
import util from "./util";
|
2
|
+
|
3
|
+
var noopLogFunc = function () {};
|
4
|
+
var _logFunc = noopLogFunc;
|
5
|
+
var _loggers = {};
|
6
|
+
|
7
|
+
var _logLevels = {
|
8
|
+
"DEBUG": 1,
|
9
|
+
"INFO": 2,
|
10
|
+
"WARNING": 3,
|
11
|
+
"ERROR": 4,
|
12
|
+
"CRITICAL": 5
|
13
|
+
};
|
14
|
+
var _currentLogLevel = _logLevels.WARNING;
|
15
|
+
|
16
|
+
class Logger {
|
17
|
+
constructor (opts) {
|
18
|
+
this._opts = opts;
|
19
|
+
}
|
20
|
+
|
21
|
+
debug (msg) {
|
22
|
+
if (_currentLogLevel <= _logLevels.DEBUG) {
|
23
|
+
this._log(`DEBUG: ${msg}`);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
info (msg) {
|
28
|
+
if (_currentLogLevel <= _logLevels.INFO) {
|
29
|
+
this._log(`INFO: ${msg}`);
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
warning (msg) {
|
34
|
+
if (_currentLogLevel <= _logLevels.WARNING) {
|
35
|
+
this._log(`WARNING: ${msg}`);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
error (msg) {
|
40
|
+
if (_currentLogLevel <= _logLevels.ERROR) {
|
41
|
+
this._log(`ERROR: ${msg}`);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
critical (msg) {
|
46
|
+
if (_currentLogLevel <= _logLevels.CRITICAL) {
|
47
|
+
this._log(`CRITICAL: ${msg}`);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
_log (msg) {
|
52
|
+
var logMsg = this._opts.prefix + msg;
|
53
|
+
if (this._opts.logFunc) {
|
54
|
+
this._opts.logFunc(logMsg);
|
55
|
+
} else {
|
56
|
+
_logFunc(logMsg);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
var logging = {
|
62
|
+
|
63
|
+
setLogger: function (logFunc) {
|
64
|
+
_logFunc = (typeof(logFunc) === "function") ? logFunc : noopLogFunc;
|
65
|
+
},
|
66
|
+
|
67
|
+
setLevel: (function () {
|
68
|
+
var forcedLogLevel = (util.urlParams.pubsub_log_level || "").toUpperCase();
|
69
|
+
if (forcedLogLevel) {
|
70
|
+
var forced = _logLevels[forcedLogLevel];
|
71
|
+
if (forced) {
|
72
|
+
_currentLogLevel = forced;
|
73
|
+
// Return a noop -- attempting to change the log level should do nothing
|
74
|
+
return function () {};
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
return function (logLevel) {
|
79
|
+
if (!logLevel) {
|
80
|
+
_currentLogLevel = _logLevels.WARNING;
|
81
|
+
} else {
|
82
|
+
_currentLogLevel = _logLevels[logLevel.toUpperCase()] || _logLevels.WARNING;
|
83
|
+
}
|
84
|
+
};
|
85
|
+
})(),
|
86
|
+
|
87
|
+
_getLogger: function (name) {
|
88
|
+
if (!_loggers[name]) {
|
89
|
+
_loggers[name] = new Logger({
|
90
|
+
prefix: `pubsub.js [${name}] `
|
91
|
+
});
|
92
|
+
}
|
93
|
+
return _loggers[name];
|
94
|
+
},
|
95
|
+
|
96
|
+
_noopLogger: new Logger({
|
97
|
+
prefix: "",
|
98
|
+
logFunc: noopLogFunc
|
99
|
+
})
|
100
|
+
|
101
|
+
};
|
102
|
+
|
103
|
+
var console = window.console;
|
104
|
+
if (console && console.log) {
|
105
|
+
// Prefer console.log if it exists
|
106
|
+
if (console.log.apply) {
|
107
|
+
logging.setLogger(function () { console.log.apply(console, arguments); });
|
108
|
+
} else {
|
109
|
+
// IE
|
110
|
+
logging.setLogger(function () {
|
111
|
+
var args = [];
|
112
|
+
for (var i = 0; i < arguments.length; ++i) {
|
113
|
+
args.push(arguments[i]);
|
114
|
+
}
|
115
|
+
console.log(args.join(" "));
|
116
|
+
});
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
export default logging;
|
121
|
+
|
package/src/mymap.js
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
class MyMap {
|
3
|
+
constructor() {
|
4
|
+
this._map = {};
|
5
|
+
this._size = 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
set (key, value) {
|
9
|
+
if (!this._map.hasOwnProperty(key)) {
|
10
|
+
this._size += 1;
|
11
|
+
}
|
12
|
+
this._map[key] = value;
|
13
|
+
}
|
14
|
+
|
15
|
+
get (key) {
|
16
|
+
return this._map[key];
|
17
|
+
}
|
18
|
+
|
19
|
+
has (key) {
|
20
|
+
return this._map.hasOwnProperty(key);
|
21
|
+
}
|
22
|
+
|
23
|
+
remove (key) {
|
24
|
+
if (this._map.hasOwnProperty(key)) {
|
25
|
+
this._size -= 1;
|
26
|
+
}
|
27
|
+
delete this._map[key];
|
28
|
+
}
|
29
|
+
|
30
|
+
size () {
|
31
|
+
return this._size;
|
32
|
+
}
|
33
|
+
|
34
|
+
map () {
|
35
|
+
return this._map;
|
36
|
+
}
|
37
|
+
|
38
|
+
values () {
|
39
|
+
var vals = [];
|
40
|
+
for (var key in this._map) {
|
41
|
+
if (this._map.hasOwnProperty(key)) {
|
42
|
+
vals.push(this._map[key]);
|
43
|
+
}
|
44
|
+
}
|
45
|
+
return vals;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
export default MyMap;
|
package/src/util.js
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
var util = {};
|
3
|
+
|
4
|
+
util.randomInt = function (max) {
|
5
|
+
return Math.floor(Math.random() * max);
|
6
|
+
};
|
7
|
+
|
8
|
+
util.time = {
|
9
|
+
seconds: function (num) {
|
10
|
+
return num * 1000;
|
11
|
+
},
|
12
|
+
|
13
|
+
now: function () {
|
14
|
+
return new Date().getTime();
|
15
|
+
}
|
16
|
+
};
|
17
|
+
|
18
|
+
util.urlParams = (function () {
|
19
|
+
var urlParams = {};
|
20
|
+
var params = window.location.search.substr(1);
|
21
|
+
var keyValues = params.split("&");
|
22
|
+
for (var i = 0; i < keyValues.length; ++i) {
|
23
|
+
var keyValue = keyValues[i].split("=");
|
24
|
+
try {
|
25
|
+
urlParams[decodeURIComponent(keyValue[0])] = keyValue.length > 1 ? decodeURIComponent(keyValue[1]) : "";
|
26
|
+
} catch (e) {
|
27
|
+
// Sometimes decodeURIComponent throws errors if weird chars are in the URL
|
28
|
+
}
|
29
|
+
}
|
30
|
+
return urlParams;
|
31
|
+
}());
|
32
|
+
|
33
|
+
util.generateString = function (len) {
|
34
|
+
var text = "";
|
35
|
+
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
36
|
+
for (var i = 0; i < len; i++) {
|
37
|
+
text += possible.charAt(util.randomInt(possible.length));
|
38
|
+
}
|
39
|
+
return text;
|
40
|
+
};
|
41
|
+
|
42
|
+
util.inIframe = function () {
|
43
|
+
try {
|
44
|
+
return window.self !== window.top;
|
45
|
+
} catch (e) {
|
46
|
+
// Sometimes browsers block an iframe's access to window.top
|
47
|
+
return true;
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
export default util;
|