@subiz/wsclient 3.1.5
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/.prettierrc.js +19 -0
- package/README.md +27 -0
- package/index.js +1 -0
- package/package.json +22 -0
- package/realtime.js +312 -0
- package/realtime.test.js +99 -0
package/.prettierrc.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
arrowParens: 'always',
|
|
3
|
+
bracketSpacing: false,
|
|
4
|
+
embeddedLanguageFormatting: 'auto',
|
|
5
|
+
htmlWhitespaceSensitivity: 'css',
|
|
6
|
+
insertPragma: false,
|
|
7
|
+
jsxBracketSameLine: true,
|
|
8
|
+
jsxSingleQuote: true,
|
|
9
|
+
printWidth: 120,
|
|
10
|
+
proseWrap: 'preserve',
|
|
11
|
+
quoteProps: 'as-needed',
|
|
12
|
+
requirePragma: false,
|
|
13
|
+
semi: false,
|
|
14
|
+
singleQuote: true,
|
|
15
|
+
tabWidth: 2,
|
|
16
|
+
trailingComma: 'all',
|
|
17
|
+
useTabs: true,
|
|
18
|
+
vueIndentScriptAndStyle: false,
|
|
19
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Subiz rtclient
|
|
2
|
+
|
|
3
|
+
This library implements subiz long polling protocol
|
|
4
|
+
|
|
5
|
+
## Method
|
|
6
|
+
### subscribe
|
|
7
|
+
### onEvents
|
|
8
|
+
### onInterrupted
|
|
9
|
+
### stop
|
|
10
|
+
|
|
11
|
+
## Example
|
|
12
|
+
```
|
|
13
|
+
var Realtime = require('@subiz/wsclient')
|
|
14
|
+
var realtime = new Realtime('https://realtime-0.subiz.net/', {
|
|
15
|
+
account_id: 'ac1234',
|
|
16
|
+
getAccessToken:()=> '333344',
|
|
17
|
+
user_mask: 'mask123'
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
realtime.onEvent(ev => console.log(ev))
|
|
21
|
+
realtime.onInterrupted(() => console.log('some message may have been losted'))
|
|
22
|
+
|
|
23
|
+
// subscribe events
|
|
24
|
+
realtime.subscribe([ev1, ev2, ev3])
|
|
25
|
+
|
|
26
|
+
realtime.stop()
|
|
27
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./realtime.js')
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@subiz/wsclient",
|
|
3
|
+
"version": "3.1.5",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node realtime.test.js"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/subiz/wsclient.git"
|
|
11
|
+
},
|
|
12
|
+
"author": "Thanh Pham <thanhpham@vietnambiz.com>",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://github.com/subiz/wsclient#readme",
|
|
15
|
+
"description": "",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"tape": "^5.3.1"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@subiz/flow": "^1.0.9"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/realtime.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
var flow = require('@subiz/flow')
|
|
2
|
+
|
|
3
|
+
// Conn represents a realtime connection between an user and the realtime
|
|
4
|
+
// server (https://rt-0.subiz.com)
|
|
5
|
+
//
|
|
6
|
+
// You can subscribe for realtime events and it will sent you new events
|
|
7
|
+
// as fast as possible through onEvents callback.
|
|
8
|
+
//
|
|
9
|
+
// It will try it best to keep the communication with the realtime
|
|
10
|
+
// server. But if the user is offline for too long or her network can't
|
|
11
|
+
// keep up with the incomming events (rarely happend with today
|
|
12
|
+
// internet speed). The connection will be halt. Dead.
|
|
13
|
+
//
|
|
14
|
+
// Once dead, onDead will be called. After that, the connection is
|
|
15
|
+
// useless, all resources will be released.
|
|
16
|
+
function Conn(apiUrl, credential, onDead, onEvents, callAPI) {
|
|
17
|
+
callAPI = callAPI || xhrsend // allow hook
|
|
18
|
+
credential = credential || {}
|
|
19
|
+
// tell if connection is dead
|
|
20
|
+
var dead = false
|
|
21
|
+
|
|
22
|
+
var lastToken = ''
|
|
23
|
+
|
|
24
|
+
// kill force the connection to dead state
|
|
25
|
+
this.kill = function() {
|
|
26
|
+
dead = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// long polling loop, polling will run sequentialy. Each polling will start right
|
|
30
|
+
// after the previous returned. In case of error, polling loop will pause based on
|
|
31
|
+
// exponential backoff algorithm. Polling loop will terminate if it encounter an
|
|
32
|
+
// un-retryable error
|
|
33
|
+
// the polling loop starts after the first subscribe call finished successfully
|
|
34
|
+
var polling = function(backoff) {
|
|
35
|
+
if (dead) return
|
|
36
|
+
callAPI('get', apiUrl + 'poll?token=' + lastToken, undefined, function(body, code) {
|
|
37
|
+
if (dead) return
|
|
38
|
+
if (retryable(code)) return setTimeout(polling, calcNextBackoff(backoff), backoff + 1)
|
|
39
|
+
if (code !== 200) {
|
|
40
|
+
// unretryable error, should kill
|
|
41
|
+
dead = true
|
|
42
|
+
return onDead('poll')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 200, success
|
|
46
|
+
body = parseJSON(body) || {}
|
|
47
|
+
if (body.host) apiUrl = absUrl(body.host)
|
|
48
|
+
|
|
49
|
+
var seqToken = body.sequential_token
|
|
50
|
+
// the server returns a malform payload. We should retry and hope it heal soon
|
|
51
|
+
if (!seqToken) return setTimeout(polling, calcNextBackoff(backoff), backoff + 1)
|
|
52
|
+
lastToken = seqToken
|
|
53
|
+
onEvents(body.events || [])
|
|
54
|
+
return polling(0)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
var subQueue = flow.batch(50, 100, function(events) {
|
|
59
|
+
if (dead) return ['dead']
|
|
60
|
+
if (events.length <= 0) return []
|
|
61
|
+
|
|
62
|
+
var out = []
|
|
63
|
+
return flow
|
|
64
|
+
.loop(function() {
|
|
65
|
+
return new Promise(function(rs) {
|
|
66
|
+
var query = '?token=' + lastToken
|
|
67
|
+
credential.getAccessToken().then(function(access_token) {
|
|
68
|
+
if (credential.user_mask) query += '&user-mask=' + encodeURIComponent(credential.user_mask)
|
|
69
|
+
else if (access_token) query += '&access-token=' + access_token
|
|
70
|
+
|
|
71
|
+
callAPI('post', apiUrl + 'subs' + query, JSON.stringify({events: events}), function(body, code) {
|
|
72
|
+
if (dead) {
|
|
73
|
+
out = repeat('dead', events.length)
|
|
74
|
+
return rs(false) // break loop
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (retryable(code)) return setTimeout(rs, 3000, true)
|
|
78
|
+
// unretryable error
|
|
79
|
+
if (code !== 200) {
|
|
80
|
+
// we are unable to start the communication with realtime server.
|
|
81
|
+
// Must notify the user by killing the connection
|
|
82
|
+
dead = true
|
|
83
|
+
onDead('subscribe', body, code)
|
|
84
|
+
out = repeat('dead', events.length)
|
|
85
|
+
return rs(false)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (lastToken) return rs(false)
|
|
89
|
+
|
|
90
|
+
// first time sub, should grab the initial token
|
|
91
|
+
body = parseJSON(body) || {}
|
|
92
|
+
var initialToken = body.initial_token
|
|
93
|
+
// the server returns a malform payload. We should retry and hope it heal soon
|
|
94
|
+
if (!initialToken) return setTimeout(rs, 3000, true)
|
|
95
|
+
if (body.host) apiUrl = absUrl(body.host)
|
|
96
|
+
|
|
97
|
+
lastToken = initialToken
|
|
98
|
+
polling(0)
|
|
99
|
+
return rs(false)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
.then(function() {
|
|
105
|
+
return out
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// subscribe call /subs API to tell server that we are listening for those events
|
|
110
|
+
// this function may start polling loop if it hasn't been started yet
|
|
111
|
+
// Note: this function is not design to be thread safed so, do not call this
|
|
112
|
+
// function multiple times at once
|
|
113
|
+
this.subscribe = subQueue.push.bind(subQueue)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Realtime is just a stronger Conn.
|
|
117
|
+
// This class helps you subscribe and listen realtime event from the
|
|
118
|
+
// realtime server
|
|
119
|
+
// additional features compare to conn:
|
|
120
|
+
// + auto recreate and resub if the last conn is dead
|
|
121
|
+
// + don't subscribe already subscribed events
|
|
122
|
+
function Realtime(apiUrls, credential, callAPI) {
|
|
123
|
+
console.log('FUCK', apiUrls, '|', credential)
|
|
124
|
+
if (typeof apiUrls === 'string' || apiUrls instanceof String) apiUrls = [apiUrls]
|
|
125
|
+
credential = credential || {}
|
|
126
|
+
if (!credential.getAccessToken) {
|
|
127
|
+
credential.getAccessToken = function() {
|
|
128
|
+
return Promise.resolve('')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var pubsub = new Pubsub()
|
|
133
|
+
|
|
134
|
+
// holds all topics that realtime must subscribed
|
|
135
|
+
// {
|
|
136
|
+
// a: 'online',
|
|
137
|
+
// b: 'offline',
|
|
138
|
+
// c: 'connecting',
|
|
139
|
+
// }
|
|
140
|
+
var topics = {}
|
|
141
|
+
|
|
142
|
+
// stop the connection
|
|
143
|
+
var stop = false
|
|
144
|
+
this.stop = function() {
|
|
145
|
+
if (stop) return
|
|
146
|
+
stop = true
|
|
147
|
+
conn.kill()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.onEvent = function(cb) {
|
|
151
|
+
return pubsub.on('event', cb)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.onInterrupted = function(cb) {
|
|
155
|
+
return pubsub.on('interrupted', cb)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var conn
|
|
159
|
+
this.subscribe = function(events) {
|
|
160
|
+
if (stop) return Promise.resolve({})
|
|
161
|
+
if (typeof events === 'string') events = [events]
|
|
162
|
+
if (!Array.isArray(events)) return Promise.resolve({error: 'param should be an array or string'})
|
|
163
|
+
|
|
164
|
+
// ignore already subscribed events
|
|
165
|
+
var all = []
|
|
166
|
+
for (var i = 0; i < events.length; i++) {
|
|
167
|
+
var topic = events[i]
|
|
168
|
+
if (!topic) continue
|
|
169
|
+
if (!topics[topic]) all.push(conn.subscribe(topic))
|
|
170
|
+
}
|
|
171
|
+
if (all.length === 0) return Promise.resolve({})
|
|
172
|
+
return Promise.all(all).then(function(errs) {
|
|
173
|
+
for (var i = 0; i < errs.length; i++) if (errs[i]) return {error: errs[i]}
|
|
174
|
+
for (var j = 0; j < events.length; j++) topics[events[j]] = true
|
|
175
|
+
return {}
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// reconnect make sure there is alway a Conn running in the background
|
|
180
|
+
// if the Conn is dead, it recreate a new one
|
|
181
|
+
var reconnect = function() {
|
|
182
|
+
if (stop) return
|
|
183
|
+
// reset subscribed topic
|
|
184
|
+
var allTopics = Object.keys(topics)
|
|
185
|
+
for (var i = 0; i < allTopics.length; i++) topics[allTopics[i]] = false
|
|
186
|
+
|
|
187
|
+
var randomUrl = apiUrls[Math.floor(Math.random() * apiUrls.length)]
|
|
188
|
+
conn = new Conn(
|
|
189
|
+
randomUrl,
|
|
190
|
+
credential,
|
|
191
|
+
function(code, body, status) {
|
|
192
|
+
if (stop) return
|
|
193
|
+
pubsub.emit('interrupted', code, body, status)
|
|
194
|
+
setTimeout(reconnect, 2000) // reconnect and resubscribe after 2 sec
|
|
195
|
+
},
|
|
196
|
+
function(topics) {
|
|
197
|
+
if (stop) return
|
|
198
|
+
for (var i = 0; i < topics.length; i++) pubsub.emit('event', topics[i])
|
|
199
|
+
},
|
|
200
|
+
callAPI,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
// resubscribe all subscribed events
|
|
204
|
+
var topicKeys = Object.keys(topics)
|
|
205
|
+
for (var i = 0; i < topicKeys.length; i++) conn.subscribe(topicKeys[i])
|
|
206
|
+
}
|
|
207
|
+
reconnect()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var dofetch = function(method, url, body, cb) {
|
|
211
|
+
let headers = {}
|
|
212
|
+
if (body) headers['content-type'] = 'text/plain'
|
|
213
|
+
fetch(url, {
|
|
214
|
+
method: method,
|
|
215
|
+
headers: header,
|
|
216
|
+
body: body,
|
|
217
|
+
})
|
|
218
|
+
.then((res) => res.text().then((text) => cb && cb(text, res.status)))
|
|
219
|
+
.catch((err) => cb(err, -1))
|
|
220
|
+
}
|
|
221
|
+
// xhrsend sends an HTTP request
|
|
222
|
+
var xhrsend = function(method, url, body, cb) {
|
|
223
|
+
if (!XMLHttpRequest) return dofetch(method, url, body, cb)
|
|
224
|
+
var request = new XMLHttpRequest()
|
|
225
|
+
request.onreadystatechange = function(e) {
|
|
226
|
+
if (request.readyState !== 4) return
|
|
227
|
+
cb && cb(request.responseText, request.status)
|
|
228
|
+
cb = undefined // dont call cb anymore
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
request.onerror = function() {
|
|
232
|
+
cb && cb(request.responseText, -1) // network error
|
|
233
|
+
cb = undefined // dont call cb anymore
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
request.open(method, url)
|
|
237
|
+
if (body) request.setRequestHeader('content-type', 'text/plain')
|
|
238
|
+
request.send(body)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// JSON.parse without exeption
|
|
242
|
+
// return undefined when parsing invalid JSON string
|
|
243
|
+
function parseJSON(str) {
|
|
244
|
+
try {
|
|
245
|
+
return JSON.parse(str)
|
|
246
|
+
} catch (e) {}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function Pubsub() {
|
|
250
|
+
// holds all subscriptions for all topics
|
|
251
|
+
// example {
|
|
252
|
+
// topic1: [cb1, cb2],
|
|
253
|
+
// topic2: [cb3, cb4],
|
|
254
|
+
// }
|
|
255
|
+
var listeners = {}
|
|
256
|
+
|
|
257
|
+
// emit notifies any callback functions that previously
|
|
258
|
+
// subscribed to the topic (by the on function)
|
|
259
|
+
this.emit = function(topic) {
|
|
260
|
+
// skip the first item in arguments, which is the topic
|
|
261
|
+
var args = []
|
|
262
|
+
for (var i = 1; i < arguments.length; i++) args.push(arguments[i])
|
|
263
|
+
|
|
264
|
+
var cbs = listeners[topic]
|
|
265
|
+
if (!cbs) return
|
|
266
|
+
for (var i = 0; i < cbs.length; i++) cbs[i].apply(undefined, args)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// register a callback function for a topic
|
|
270
|
+
// when something is sent to the topic (by the emit function),
|
|
271
|
+
// the callback function will be called
|
|
272
|
+
this.on = function(topic, cb) {
|
|
273
|
+
if (!listeners[topic]) listeners[topic] = []
|
|
274
|
+
listeners[topic].push(cb)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// calcNextBackoff returns number of seconds we must wait
|
|
279
|
+
// before sending a next request
|
|
280
|
+
// the results is determind based on exponential backoff algorithm
|
|
281
|
+
// see https://en.wikipedia.org/wiki/Exponential_backoff
|
|
282
|
+
function calcNextBackoff(attempts) {
|
|
283
|
+
var RECONNECT_INTERVAL = 1000
|
|
284
|
+
var MAX_RECONNECT_INTERVAL = 10000
|
|
285
|
+
var RECONNECT_DECAY = 1.5
|
|
286
|
+
|
|
287
|
+
if (!attempts || attempts === -1) return 0 // first time connect
|
|
288
|
+
var delaytime = RECONNECT_INTERVAL * Math.pow(RECONNECT_DECAY, attempts)
|
|
289
|
+
return Math.min(MAX_RECONNECT_INTERVAL, delaytime)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// retryable decides whether we should resent the HTTP request
|
|
293
|
+
// based on last response or network state
|
|
294
|
+
function retryable(code) {
|
|
295
|
+
// code = 0 means server return response without CORS header, so we are unable to read it
|
|
296
|
+
// code = -1 means the network connection is dead
|
|
297
|
+
return (code >= 500 && code < 600) || code === 0 || code === -1 || code === 429
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function repeat(ele, n) {
|
|
301
|
+
var arr = []
|
|
302
|
+
for (var i = 0; i < n; i++) arr.push(ele)
|
|
303
|
+
return arr
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function absUrl(url) {
|
|
307
|
+
url = url || ''
|
|
308
|
+
if (url.startsWith('//')) return 'https:' + url
|
|
309
|
+
return url
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = Realtime
|
package/realtime.test.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
var test = require('tape')
|
|
2
|
+
var Realtime = require('./realtime.js')
|
|
3
|
+
|
|
4
|
+
function makeMockServer(script) {
|
|
5
|
+
var i = -1
|
|
6
|
+
return (method, url, body, cb) => {
|
|
7
|
+
i++
|
|
8
|
+
script[i](method, url, body, cb)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test('realtime', async (t) => {
|
|
13
|
+
var callapi = makeMockServer([
|
|
14
|
+
async (method, url, body, cb) => {
|
|
15
|
+
// must first call sub and must attach user-mask in the query
|
|
16
|
+
t.equal(method, 'post')
|
|
17
|
+
t.true(url.indexOf('subs?') !== -1)
|
|
18
|
+
t.true(url.indexOf('user-mask=mask123') !== -1)
|
|
19
|
+
cb(`{"initial_token": "token0"}`, 200)
|
|
20
|
+
},
|
|
21
|
+
async (method, url, body, cb) => {
|
|
22
|
+
// start the poll and must attach initial token in the query
|
|
23
|
+
t.equal(method, 'get')
|
|
24
|
+
t.true(url.indexOf('poll?') !== -1)
|
|
25
|
+
t.true(url.indexOf('token=token0') !== -1)
|
|
26
|
+
cb(`{"events": [], "sequential_token": "token1"}`, 200)
|
|
27
|
+
},
|
|
28
|
+
async (method, url, body, cb) => {
|
|
29
|
+
// next poll must attach last received token
|
|
30
|
+
t.equal(method, 'get')
|
|
31
|
+
t.true(url.indexOf('poll?') !== -1)
|
|
32
|
+
t.true(url.indexOf('token=token1') !== -1)
|
|
33
|
+
realtime.stop()
|
|
34
|
+
cb(`{"events": [], "token": "-"}`, 200)
|
|
35
|
+
},
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
var realtime = new Realtime('', {user_mask: 'mask123'}, callapi)
|
|
39
|
+
var err = await realtime.subscribe(['message_pong'])
|
|
40
|
+
t.true(Object.keys(err).length === 0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('realtime subs when server down and then backup', async (t) => {
|
|
44
|
+
var callapi = makeMockServer([
|
|
45
|
+
async (method, url, body, cb) => {
|
|
46
|
+
// must first call sub and must attach user-mask in the query
|
|
47
|
+
t.equal(method, 'post')
|
|
48
|
+
t.true(url.indexOf('subs?') !== -1)
|
|
49
|
+
t.true(url.indexOf('user-mask=mask123') !== -1)
|
|
50
|
+
cb(`502 too busy`, 502)
|
|
51
|
+
},
|
|
52
|
+
async (method, url, body, cb) => {
|
|
53
|
+
// should retry
|
|
54
|
+
t.equal(method, 'post')
|
|
55
|
+
t.true(url.indexOf('subs?') !== -1)
|
|
56
|
+
t.true(url.indexOf('user-mask=mask123') !== -1)
|
|
57
|
+
cb(`502 still busy`, 502)
|
|
58
|
+
},
|
|
59
|
+
async (method, url, body, cb) => {
|
|
60
|
+
// should retry
|
|
61
|
+
t.equal(method, 'post')
|
|
62
|
+
t.true(url.indexOf('subs?') !== -1)
|
|
63
|
+
t.true(url.indexOf('user-mask=mask123') !== -1)
|
|
64
|
+
cb(`{"initial_token": "token0"}`, 200)
|
|
65
|
+
},
|
|
66
|
+
async (method, url, body, cb) => {
|
|
67
|
+
t.equal(method, 'get')
|
|
68
|
+
t.true(url.indexOf('poll?') !== -1)
|
|
69
|
+
t.true(url.indexOf('token=token0') !== -1)
|
|
70
|
+
realtime.stop()
|
|
71
|
+
cb(`{"events": [], "token": "-"}`, 200)
|
|
72
|
+
},
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
var realtime = new Realtime('', {user_mask: 'mask123'}, callapi)
|
|
76
|
+
var err = await realtime.subscribe(['message_pong'])
|
|
77
|
+
t.true(Object.keys(err).length === 0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('realtime must stop if there is something wrong with our credential or our code', async (t) => {
|
|
81
|
+
var subcode
|
|
82
|
+
var callapi = makeMockServer([
|
|
83
|
+
async (method, url, bkody, cb) => {
|
|
84
|
+
// must first call sub and must attach user-mask in the query
|
|
85
|
+
t.equal(method, 'post')
|
|
86
|
+
t.true(url.indexOf('subs?') !== -1)
|
|
87
|
+
t.true(url.indexOf('user-mask=mask123') !== -1)
|
|
88
|
+
cb(`400 invalid mask`, 400)
|
|
89
|
+
realtime.stop()
|
|
90
|
+
},
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
var realtime = new Realtime('', {user_mask: 'mask123'}, callapi)
|
|
94
|
+
realtime.onInterrupted((_, _1, code) => {
|
|
95
|
+
subcode = code
|
|
96
|
+
})
|
|
97
|
+
var err = await realtime.subscribe(['message_pong'])
|
|
98
|
+
t.false(Object.keys(err).length === 0)
|
|
99
|
+
})
|