angelia.io 3.4.84 → 4.0.85

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/client/index.js CHANGED
@@ -1,429 +1,187 @@
1
- 'use strict'
1
+ import { ClientWebWorker } from './worker.js'
2
2
 
3
- class ClientWebWorker {
4
- constructor() {
5
- Blob.prototype.toJSON = function () {
6
- return {
7
- b: new FileReaderSync().readAsDataURL(this),
8
- }
9
- }
10
- ArrayBuffer.prototype.toJSON = function () {
11
- return {
12
- a: new FileReaderSync().readAsDataURL(this),
13
- }
14
- }
15
-
16
- Object.assign(this, {
17
- connid: this.generateId(),
18
-
19
- connected: true,
20
- reconnect: true,
21
- isReconnect: false,
22
-
23
- messages: [],
24
- buffered: [],
25
-
26
- onopen: this.onopen.bind(this),
27
- onclose: this.onclose.bind(this),
28
- onerror: this.onerror.bind(this),
29
- onmessage: this.onmessage.bind(this),
30
-
31
- postMessage: self.postMessage.bind(self),
32
- })
33
-
34
- self.onmessage = function (e) {
35
- switch (e.data[0]) {
36
- case 'emit': {
37
- this.emit(e.data[1])
38
- break
39
- }
40
- case 'connect': {
41
- this.connect(e.data[1])
42
- break
43
- }
44
- case 'disconnect': {
45
- this.disconnect(e.data[1])
46
- break
47
- }
48
- }
49
- }.bind(this)
50
- }
51
-
52
- connect(options) {
53
- if (
54
- this.reconnect &&
55
- (!this.io || this.io.readyState === WebSocket.CLOSED)
56
- ) {
57
- let url = new URL(options.url)
58
-
59
- // parms creation
60
- let params = options.params
61
- if (!params.connid) {
62
- params.connid = this.connid
63
- }
64
- for (let [k, v] of url.searchParams.entries()) {
65
- if (!params.hasOwnProperty(k)) {
66
- params[k] = v
67
- }
68
- }
69
- url.search = Object.entries(params)
70
- .filter(([k, v]) => {
71
- return !(
72
- k === undefined ||
73
- k === null ||
74
- v === undefined ||
75
- v === null
76
- )
77
- })
78
- .map(
79
- ([k, v]) =>
80
- encodeURIComponent(k) + '=' + encodeURIComponent(v),
81
- )
82
- .join('&')
83
-
84
- url = url.toString()
85
-
86
- // append buffered messages
87
- let oURL = url
88
- if (this.messages.length && !this.isReconnect) {
89
- url =
90
- (url.indexOf('?') === -1 ? url + '?' : url + '&') +
91
- 'angelia.io=' +
92
- encodeURIComponent(JSON.stringify(this.messages))
93
- if (url.length < 2048) {
94
- this.messages = []
95
- } else {
96
- url = oURL
97
- }
98
- } else if (this.messages.length && this.isReconnect) {
99
- for (let m of this.messages) {
100
- // dont buffer callbacks, they are gone for new connections
101
- if (m[0] !== '') this.buffered.push(m)
102
- }
103
- this.messages = []
104
- }
105
- this.io = new WebSocket(url)
106
- Object.assign(this.io, {
107
- onopen: this.onopen,
108
- onclose: this.onclose,
109
- onerror: this.onerror,
110
- onmessage: this.onmessage,
111
- })
112
- }
113
- }
114
-
115
- disconnect(noReconnect) {
116
- if (noReconnect) this.reconnect = false
117
-
118
- if (
119
- this.io &&
120
- this.io.readyState !== WebSocket.CLOSING &&
121
- this.io.readyState !== WebSocket.CLOSED
122
- ) {
123
- this.io.close()
124
- }
125
- }
126
-
127
- onopen() {
128
- this.connected = true
129
- this.postMessage(['connected', true])
130
-
131
- if (this.isReconnect) {
132
- this.postMessage(['dispatch', 'reconnect'])
133
- } else {
134
- this.isReconnect = true
135
- this.postMessage(['dispatch', 'connect'])
136
- }
137
-
138
- this.send()
139
- }
140
- onclose(event) {
141
- switch (event.code) {
142
- // normal close
143
- case 1000:
144
- /*console.log('ws - normal close', event.code, event.reason)*/
145
- break
146
- // closed by client
147
- case 1005:
148
- /*console.log(
149
- 'ws - we called socket.disconnect()',
150
- event.code,
151
- event.reason,
152
- )*/
153
- break
154
- // closed by server or
155
- // connection never opened and failed to connect
156
- case 1006: {
157
- /*console.log(
158
- 'ws - server killed the connection, or we failed to connect to server',
159
- event.code,
160
- event.reason,
161
- )*/
162
- break
163
- }
164
- default: {
165
- console.log('ws - unknown close', event.code, event.reason)
166
- break
167
- }
168
- }
169
-
170
- if (this.connected) {
171
- this.connected = false
172
- this.postMessage(['connected', false])
173
- this.postMessage(['dispatch', 'disconnect'])
174
- }
175
-
176
- if (this.reconnect) this.postMessage(['connect'])
177
- }
178
- // this happens when trying to connect while the server or
179
- // the internet connection is down
180
- onerror() {
181
- if (this.connected) {
182
- this.connected = false
183
- this.postMessage(['connected', false])
184
- this.postMessage(['dispatch', 'disconnect'])
185
- }
3
+ const isFunction = v => typeof v === 'function'
186
4
 
187
- if (this.reconnect) this.postMessage(['connect'])
188
- }
189
- onmessage(e) {
190
- if (e.data === '') {
191
- this.pong()
192
- } else {
193
- this.postMessage(['messages', JSON.parse(e.data)])
194
- }
195
- }
196
- emit(messages) {
197
- for (let m of messages) {
198
- this.messages.push(m)
199
- }
200
- this.send()
201
- }
202
- send() {
203
- if (this.io && this.io.readyState === WebSocket.OPEN) {
204
- if (this.messages.length) {
205
- let messages = this.messages
206
- this.messages = []
207
- this.io.send(JSON.stringify(messages))
208
- }
209
- if (this.buffered.length) {
210
- let buffered = this.buffered
211
- this.buffered = []
212
- this.io.send(JSON.stringify(buffered))
213
- }
214
- }
215
- }
216
- pong() {
217
- if (this.io && this.io.readyState === WebSocket.OPEN) {
218
- this.io.send('')
219
- }
220
- }
221
- generateId() {
222
- var id = ''
223
- while (!id) {
224
- id = Math.random().toString(36).substr(2, 10)
225
- }
226
- return id
227
- }
228
- }
5
+ const error = console.error
229
6
 
230
7
  export default class Client {
8
+ #callbacks
9
+ #listeners
10
+ #messages
11
+ #postMessage
12
+
13
+ /**
14
+ * Creates a Socket Client Instance
15
+ *
16
+ * @param options {string | {url:string, params?:Function,
17
+ * noConnect?:boolean }}
18
+ */
231
19
  constructor(options) {
232
- if (!options || typeof options === 'string') {
20
+ if (typeof options === 'string') {
233
21
  options = {
234
22
  url: options,
235
23
  }
236
24
  }
237
25
 
238
- let protocol = location.protocol === 'https:' ? 'wss' : 'ws'
239
- if (!options.url) {
240
- options.url = protocol + '://' + location.hostname + ':3001'
241
- } else if (options.url.indexOf('://') === -1) {
242
- options.url = protocol + '://' + options.url
243
- }
244
-
245
- const io = new Worker(
246
- URL.createObjectURL(
247
- new Blob(
248
- ["'use strict';new (" + ClientWebWorker.toString() + ')'],
249
- {
250
- type: 'application/javascript; charset=utf-8',
251
- },
252
- ),
253
- ),
254
- )
255
- let shouldConnect =
256
- options.dontConnect !== undefined ? !options.dontConnect : true
257
-
258
- Object.assign(this, {
259
- url: options.url,
260
- params: options.params,
261
- longLiveFlash: options.longLiveFlash,
262
- shouldConnect: shouldConnect,
263
- connected: shouldConnect,
264
- messages: [],
265
- listeners: {
266
- '': [this.oncallback.bind(this)],
267
- },
268
- callbacks: [() => {}],
269
-
270
- onworkermessage: this.onworkermessage.bind(this),
271
- postMessage: io.postMessage.bind(io),
272
- disconnect: this.disconnect.bind(this),
26
+ this.options = options
273
27
 
274
- send: this.send.bind(this),
275
- null: Object.create(null),
276
- })
28
+ this.#callbacks = [null]
29
+ this.#listeners = {
30
+ '': [this.#oncallback],
31
+ }
32
+ this.#messages = []
277
33
 
278
- io.onmessage = this.onworkermessage
34
+ this.connected = false
279
35
 
280
36
  // to force disconnect
281
37
  this.on('disconnect', this.disconnect)
282
38
 
283
- // to send messages fast without waiting for the connection
284
- shouldConnect && this.connect()
285
- }
39
+ const workerURL = URL.createObjectURL(
40
+ new Blob(['(' + ClientWebWorker + ')()']),
41
+ )
42
+ const io = new Worker(workerURL)
43
+ URL.revokeObjectURL(workerURL)
44
+
45
+ io.onmessage = this.#onworkermessage
46
+ this.#postMessage = io.postMessage.bind(io)
286
47
 
287
- // public API
48
+ options.noConnect === undefined && this.connect()
49
+ }
288
50
 
51
+ /** Connect to server */
289
52
  connect() {
290
- this.postMessage([
53
+ this.#postMessage([
291
54
  'connect',
292
55
  {
293
- url: this.url,
56
+ url: this.options.url,
294
57
  params:
295
- typeof this.params === 'function'
296
- ? this.params() || {}
297
- : {},
58
+ (isFunction(this.options.params)
59
+ ? this.options.params()
60
+ : this.options.params) || {},
298
61
  },
299
62
  ])
300
63
  }
301
64
 
302
- disconnect(noReconnect) {
303
- this.postMessage(['disconnect', noReconnect])
304
- }
305
-
306
- emit(k, v, c) {
307
- if (!this.messages.length) {
308
- Promise.resolve().then(this.send)
309
- }
310
- if (c) {
311
- const { i, promise } = this.callback(c)
312
- this.messages.push([k, v, i])
313
- return promise
314
- } else if (typeof v === 'function') {
315
- const { i, promise } = this.callback(v)
316
- this.messages.push([k, this.null, i])
317
- return promise
318
- } else if (v !== null && v !== undefined) {
319
- this.messages.push([k, v])
65
+ /**
66
+ * Disconnect from server
67
+ *
68
+ * @param {boolean} noReconnect - To prevent reconnections
69
+ */
70
+ disconnect = noReconnect => {
71
+ this.#postMessage(['disconnect', noReconnect])
72
+ }
73
+
74
+ /**
75
+ * Listen for data sent by the server
76
+ *
77
+ * @param {string} key
78
+ * @param {any | Function} [valueOrCallback]
79
+ * @param {Function} [callback]
80
+ * @returns {Promise<any> | undefined} When callback is provided it
81
+ * returns a promise
82
+ */
83
+ emit(key, valueOrCallback, callback) {
84
+ // empty `key` is reserved
85
+ if (key == '') {
86
+ error('`key` cannot be empty')
320
87
  } else {
321
- this.messages.push([k])
88
+ if (!this.#messages.length) {
89
+ queueMicrotask(() => queueMicrotask(this.#send))
90
+ }
91
+ if (callback) {
92
+ const [i, promise] = this.#callback(callback)
93
+ this.#messages.push([key, valueOrCallback, i])
94
+ return promise
95
+ } else if (isFunction(valueOrCallback)) {
96
+ const [i, promise] = this.#callback(valueOrCallback)
97
+ this.#messages.push([key, 0, i])
98
+ return promise
99
+ } else if (valueOrCallback !== undefined) {
100
+ this.#messages.push([key, valueOrCallback])
101
+ } else {
102
+ this.#messages.push([key])
103
+ }
322
104
  }
323
105
  }
324
106
 
325
- on(k, v) {
326
- if (k != '' && typeof v === 'function') {
327
- this.listeners[k] = this.listeners[k] || []
328
- this.listeners[k].push(v)
329
- return () => this.off(k, v)
330
- } else {
331
- console.error(
332
- 'ws - socket.on("' +
333
- k +
334
- '", callback) key and callback cannot be empty',
335
- )
336
- }
337
- }
338
- off(k, v) {
339
- if (!this.listeners[k]) {
340
- console.error(
341
- 'ws - socket.off("' + k + '", callback)',
342
- k,
343
- 'key not found',
344
- )
107
+ /**
108
+ * Listen for data sent by the server
109
+ *
110
+ * @param {string} key
111
+ * @param {Function} value
112
+ * @returns () => void | undefined - `off` function to remove the
113
+ * listener
114
+ */
115
+ on(key, value) {
116
+ // empty `key` is reserved
117
+ if (key == '') {
118
+ error('`key` cannot be empty')
345
119
  } else {
346
- let i = this.listeners[k].indexOf(v)
347
- if (i === -1) {
348
- console.error(
349
- 'ws - socket.off("' + k + '", callback)',
350
- v,
351
- 'callback not found',
352
- )
353
- } else {
354
- this.listeners[k].splice(i, 1)
355
- }
120
+ this.#listeners[key] = this.#listeners[key] || []
121
+ this.#listeners[key].push(value)
122
+ return () => this.#off(key, value)
356
123
  }
357
124
  }
358
125
 
359
126
  // PRIVATE API
360
127
 
361
- callback(c) {
128
+ #off(k, v) {
129
+ const i = this.#listeners[k].indexOf(v)
130
+ if (i !== -1) {
131
+ this.#listeners[k].splice(i, 1)
132
+ }
133
+ }
134
+
135
+ /** @returns {[number, Promise<any>]} */
136
+ #callback(c) {
362
137
  let resolve
363
138
  const promise = new Promise(r => (resolve = r))
364
- let i = this.callbacks.length
365
- this.callbacks[i] = (...args) => resolve(c(...args))
366
- return { i, promise }
367
- }
368
- oncallback(d) {
369
- this.callbacks[d[0]](...d[1])
370
- this.callbacks[d[0]] = null
371
- }
372
- dispatch(d) {
373
- if (this.longLiveFlash) {
374
- setTimeout(() => {
375
- for (let e of d) {
376
- if (this.listeners[e[0]]) {
377
- for (let fn of this.listeners[e[0]]) {
378
- fn(
379
- e[1],
380
- e[2]
381
- ? (...d) => {
382
- this.emit('', [e[2], d])
383
- }
384
- : null,
385
- )
386
- }
387
- }
388
- }
389
- })
390
- } else {
391
- for (let e of d) {
392
- if (this.listeners[e[0]]) {
393
- for (let fn of this.listeners[e[0]]) {
394
- fn(
395
- e[1],
396
- e[2]
397
- ? (...d) => {
398
- this.emit('', [e[2], d])
399
- }
400
- : null,
401
- )
402
- }
139
+ const i = this.#callbacks.length
140
+ this.#callbacks[i] = args => resolve(c(...args))
141
+ return [i, promise]
142
+ }
143
+ #oncallback = d => {
144
+ this.#callbacks[d[0]](d[1])
145
+ this.#callbacks[d[0]] = null
146
+ }
147
+
148
+ #dispatch(d) {
149
+ for (const e of d) {
150
+ if (this.#listeners[e[0]]) {
151
+ for (const fn of this.#listeners[e[0]]) {
152
+ fn(
153
+ e[1],
154
+ e[2]
155
+ ? (...d) => {
156
+ this.emit('', [e[2], d])
157
+ }
158
+ : undefined,
159
+ )
403
160
  }
404
161
  }
405
162
  }
406
163
  }
407
- send() {
408
- if (this.messages.length) {
409
- let messages = this.messages
410
- this.messages = []
411
- this.postMessage(['emit', messages])
164
+
165
+ #send = () => {
166
+ if (this.#messages.length) {
167
+ const messages = this.#messages
168
+ this.#messages = []
169
+ this.#postMessage(['emit', messages])
412
170
  }
413
171
  }
414
172
 
415
- onworkermessage(e) {
173
+ #onworkermessage = e => {
416
174
  switch (e.data[0]) {
417
175
  case 'messages': {
418
- this.dispatch(e.data[1])
176
+ this.#dispatch(e.data[1])
419
177
  break
420
178
  }
421
179
  case 'dispatch': {
422
- this.dispatch([[e.data[1], e.data[2]]])
180
+ this.#dispatch([[e.data[1], e.data[2]]])
423
181
  break
424
182
  }
425
183
  case 'connect': {
426
- if (this.shouldConnect) this.connect()
184
+ this.connect()
427
185
  break
428
186
  }
429
187
  case 'connected': {
@@ -432,6 +190,7 @@ export default class Client {
432
190
  }
433
191
  }
434
192
  }
193
+
435
194
  decode(s) {
436
195
  if (s.b) {
437
196
  return fetch(s.b).then(r => r.blob())