@basementuniverse/jsonpad-realtime-sdk 1.2.1 → 1.3.0

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 CHANGED
@@ -69,6 +69,27 @@ If you want to stop listening for events and disconnect from the server, you can
69
69
  jsonpadRealtime.close();
70
70
  ```
71
71
 
72
+ ## Connection limits
73
+
74
+ Each plan limits how many realtime connections one account can hold open at the same time, counted across all of its tokens. A connection beyond that limit is refused while it is connecting, and arrives as an `error` event:
75
+
76
+ ```js
77
+ jsonpadRealtime.addEventListener('error', e => {
78
+ console.log(e.detail); // The error message
79
+ console.log(e.errorName); // e.g. 'REALTIME_CONNECTION_LIMIT_EXCEEDED'
80
+ console.log(e.code); // e.g. 10015
81
+ console.log(e.max); // How many connections your plan allows
82
+ console.log(e.retryAfter); // How long the server asked us to wait, in seconds
83
+ console.log(e.retryIn); // How long until this client retries, or null
84
+ });
85
+ ```
86
+
87
+ A connection that drops without closing cleanly keeps its slot on the server until the server notices it has gone, which takes up to 45 seconds. A client that reconnects immediately can therefore be refused for a limit it is no longer over, so this SDK retries a refused connection by itself, backing off from the delay the server asked for. Pass `retry: false` if you would rather handle it yourself:
88
+
89
+ ```js
90
+ const jsonpadRealtime = new JSONPadRealtime('your-api-token', { retry: false });
91
+ ```
92
+
72
93
  ## `listen()` method
73
94
 
74
95
  The `listen()` method takes an array of event types to listen for. You can pass in any of the following event types:
@@ -18,6 +18,22 @@ type Item = {
18
18
  size: number;
19
19
  };
20
20
 
21
+ type JSONPadRealtimeOptions = {
22
+ /**
23
+ * Retry a connection that the server refused because the account is already
24
+ * holding as many concurrent connections as its plan allows
25
+ *
26
+ * A connection lost to a network problem keeps its slot on the server until
27
+ * the server notices it has gone, which takes up to 45 seconds. A client
28
+ * that reconnects immediately can be refused for a limit it isn't really
29
+ * over, so retrying with a backoff gets it back on once the old connection
30
+ * has been cleaned up.
31
+ *
32
+ * Defaults to true.
33
+ */
34
+ retry?: boolean;
35
+ };
36
+
21
37
  type List = {
22
38
  id: string;
23
39
  createdAt: Date;
@@ -46,6 +62,28 @@ type List = {
46
62
 
47
63
  type MessageType = 'error' | 'list-created' | 'list-updated' | 'list-deleted' | 'item-created' | 'item-updated' | 'item-restored' | 'item-deleted';
48
64
 
65
+ /**
66
+ * The data attached to the error the server sends when it refuses a connection
67
+ *
68
+ * The name, code and message are the same ones the API returns in the body of
69
+ * an error response
70
+ */
71
+ type RealtimeErrorData = {
72
+ name: string;
73
+ code: number;
74
+ message: string;
75
+ /**
76
+ * How many concurrent connections the account's plan allows, present when
77
+ * the connection was refused for holding too many of them
78
+ */
79
+ max?: number;
80
+ /**
81
+ * How many seconds to wait before trying again, present when trying again
82
+ * is worthwhile
83
+ */
84
+ retryAfter?: number;
85
+ };
86
+
49
87
  declare class ItemEvent extends CustomEvent<any> {
50
88
  constructor(messageType: MessageType, detail: EventDetail<Item>);
51
89
  }
@@ -54,16 +92,56 @@ declare class ListEvent extends CustomEvent<any> {
54
92
  constructor(messageType: MessageType, detail: EventDetail<List>);
55
93
  }
56
94
 
95
+ /**
96
+ * Dispatched as an 'error' event when the server refuses a connection, or when
97
+ * an event arrives that can't be parsed
98
+ *
99
+ * The detail is the error message, as it was before this class existed, so
100
+ * that anything already listening for 'error' keeps working
101
+ */
102
+ declare class RealtimeErrorEvent extends CustomEvent<string> {
103
+ /**
104
+ * The jsonpad error code, e.g. 10015, or null if the error didn't come from
105
+ * the server
106
+ */
107
+ readonly code: number | null;
108
+ /**
109
+ * The jsonpad error name, e.g. 'REALTIME_CONNECTION_LIMIT_EXCEEDED', or null
110
+ * if the error didn't come from the server
111
+ */
112
+ readonly errorName: string | null;
113
+ /**
114
+ * How many concurrent connections the account's plan allows, if the
115
+ * connection was refused for holding too many of them
116
+ */
117
+ readonly max: number | null;
118
+ /**
119
+ * How many seconds the server asked us to wait before trying again, or null
120
+ * if it didn't
121
+ */
122
+ readonly retryAfter: number | null;
123
+ /**
124
+ * How many seconds until this client retries the connection, or null if it
125
+ * won't retry
126
+ */
127
+ readonly retryIn: number | null;
128
+ constructor(message: string, data?: RealtimeErrorData, retryIn?: number | null);
129
+ }
130
+
57
131
  declare class JSONPadRealtime extends EventTarget {
58
132
  private token;
59
133
  private static readonly connected;
60
134
  private static readonly disconnected;
135
+ private static readonly defaultOptions;
136
+ private readonly options;
61
137
  private socket;
138
+ private retryTimeout;
139
+ private retryAttempt;
62
140
  get connected(): boolean;
63
141
  /**
64
142
  * Create a new JSONPad realtime client instance
65
143
  */
66
- constructor(token: string);
144
+ constructor(token: string, options?: JSONPadRealtimeOptions);
67
145
  /**
68
146
  * Close the connection to the realtime server
69
147
  */
@@ -72,10 +150,35 @@ declare class JSONPadRealtime extends EventTarget {
72
150
  * Start listening for realtime events
73
151
  */
74
152
  listen(eventTypes: EventType[], listIds?: string[], itemIds?: string[]): void;
153
+ /**
154
+ * Close the current socket, if there is one
155
+ *
156
+ * A socket that is still connecting, or that is reconnecting in the
157
+ * background, is holding a connection slot on the server just like a
158
+ * connected one, so it gets closed too
159
+ */
160
+ private closeSocket;
75
161
  private handleConnected;
76
162
  private handleDisconnected;
163
+ /**
164
+ * A connection attempt failed
165
+ *
166
+ * Socket.IO retries by itself when it couldn't reach the server, and leaves
167
+ * the socket inactive when the server refused the connection instead. A
168
+ * refusal for holding too many concurrent connections is worth retrying,
169
+ * since a connection the client has already lost is released once the
170
+ * server notices it has gone.
171
+ */
172
+ private handleConnectError;
173
+ /**
174
+ * Back off exponentially from the delay the server asked for, with a little
175
+ * jitter so that clients refused at the same time don't all come back at the
176
+ * same time
177
+ */
178
+ private retryDelay;
179
+ private cancelRetry;
77
180
  private handleError;
78
181
  private handleEvent;
79
182
  }
80
183
 
81
- export { type EventDetail, type EventType, type Item, ItemEvent, type List, ListEvent, type MessageType, JSONPadRealtime as default };
184
+ export { type EventDetail, type EventType, type Item, ItemEvent, type JSONPadRealtimeOptions, type List, ListEvent, type MessageType, type RealtimeErrorData, RealtimeErrorEvent, JSONPadRealtime as default };
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).JSONPadRealtime={})}(this,(function(t){"use strict";class e extends CustomEvent{constructor(t,e){if(e.model.data&&"string"==typeof e.model.data)try{e.model.data=JSON.parse(e.model.data)}catch(t){}super(t,{detail:e})}}class s extends CustomEvent{constructor(t,e){super(t,{detail:e})}}const n=Object.create(null);n.open="0",n.close="1",n.ping="2",n.pong="3",n.message="4",n.upgrade="5",n.noop="6";const i=Object.create(null);Object.keys(n).forEach((t=>{i[n[t]]=t}));const r={type:"error",data:"parser error"},o="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),a="function"==typeof ArrayBuffer,h=t=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer instanceof ArrayBuffer,c=({type:t,data:e},s,i)=>o&&e instanceof Blob?s?i(e):p(e,i):a&&(e instanceof ArrayBuffer||h(e))?s?i(e):p(new Blob([e]),i):i(n[t]+(e||"")),p=(t,e)=>{const s=new FileReader;return s.onload=function(){const t=s.result.split(",")[1];e("b"+(t||""))},s.readAsDataURL(t)};function u(t){return t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}let d;const l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",f="undefined"==typeof Uint8Array?[]:new Uint8Array(256);for(let t=0;t<64;t++)f[l.charCodeAt(t)]=t;const y="function"==typeof ArrayBuffer,g=(t,e)=>{if("string"!=typeof t)return{type:"message",data:_(t,e)};const s=t.charAt(0);if("b"===s)return{type:"message",data:m(t.substring(1),e)};return i[s]?t.length>1?{type:i[s],data:t.substring(1)}:{type:i[s]}:r},m=(t,e)=>{if(y){const s=(t=>{let e,s,n,i,r,o=.75*t.length,a=t.length,h=0;"="===t[t.length-1]&&(o--,"="===t[t.length-2]&&o--);const c=new ArrayBuffer(o),p=new Uint8Array(c);for(e=0;e<a;e+=4)s=f[t.charCodeAt(e)],n=f[t.charCodeAt(e+1)],i=f[t.charCodeAt(e+2)],r=f[t.charCodeAt(e+3)],p[h++]=s<<2|n>>4,p[h++]=(15&n)<<4|i>>2,p[h++]=(3&i)<<6|63&r;return c})(t);return _(s,e)}return{base64:!0,data:t}},_=(t,e)=>"blob"===e?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer,b=String.fromCharCode(30);function v(){return new TransformStream({transform(t,e){!function(t,e){o&&t.data instanceof Blob?t.data.arrayBuffer().then(u).then(e):a&&(t.data instanceof ArrayBuffer||h(t.data))?e(u(t.data)):c(t,!1,(t=>{d||(d=new TextEncoder),e(d.encode(t))}))}(t,(s=>{const n=s.length;let i;if(n<126)i=new Uint8Array(1),new DataView(i.buffer).setUint8(0,n);else if(n<65536){i=new Uint8Array(3);const t=new DataView(i.buffer);t.setUint8(0,126),t.setUint16(1,n)}else{i=new Uint8Array(9);const t=new DataView(i.buffer);t.setUint8(0,127),t.setBigUint64(1,BigInt(n))}t.data&&"string"!=typeof t.data&&(i[0]|=128),e.enqueue(i),e.enqueue(s)}))}})}let w;function k(t){return t.reduce(((t,e)=>t+e.length),0)}function E(t,e){if(t[0].length===e)return t.shift();const s=new Uint8Array(e);let n=0;for(let i=0;i<e;i++)s[i]=t[0][n++],n===t[0].length&&(t.shift(),n=0);return t.length&&n<t[0].length&&(t[0]=t[0].slice(n)),s}function A(t){if(t)return function(t){for(var e in A.prototype)t[e]=A.prototype[e];return t}(t)}A.prototype.on=A.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},A.prototype.once=function(t,e){function s(){this.off(t,s),e.apply(this,arguments)}return s.fn=e,this.on(t,s),this},A.prototype.off=A.prototype.removeListener=A.prototype.removeAllListeners=A.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s,n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var i=0;i<n.length;i++)if((s=n[i])===e||s.fn===e){n.splice(i,1);break}return 0===n.length&&delete this._callbacks["$"+t],this},A.prototype.emit=function(t){this._callbacks=this._callbacks||{};for(var e=new Array(arguments.length-1),s=this._callbacks["$"+t],n=1;n<arguments.length;n++)e[n-1]=arguments[n];if(s){n=0;for(var i=(s=s.slice(0)).length;n<i;++n)s[n].apply(this,e)}return this},A.prototype.emitReserved=A.prototype.emit,A.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks["$"+t]||[]},A.prototype.hasListeners=function(t){return!!this.listeners(t).length};const T="function"==typeof Promise&&"function"==typeof Promise.resolve?t=>Promise.resolve().then(t):(t,e)=>e(t,0),O="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")();function R(t,...e){return e.reduce(((e,s)=>(t.hasOwnProperty(s)&&(e[s]=t[s]),e)),{})}const C=O.setTimeout,B=O.clearTimeout;function x(t,e){e.useNativeTimers?(t.setTimeoutFn=C.bind(O),t.clearTimeoutFn=B.bind(O)):(t.setTimeoutFn=O.setTimeout.bind(O),t.clearTimeoutFn=O.clearTimeout.bind(O))}function S(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}class N extends Error{constructor(t,e,s){super(t),this.description=e,this.context=s,this.type="TransportError"}}class L extends A{constructor(t){super(),this.writable=!1,x(this,t),this.opts=t,this.query=t.query,this.socket=t.socket,this.supportsBinary=!t.forceBase64}onError(t,e,s){return super.emitReserved("error",new N(t,e,s)),this}open(){return this.readyState="opening",this.doOpen(),this}close(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this}send(t){"open"===this.readyState&&this.write(t)}onOpen(){this.readyState="open",this.writable=!0,super.emitReserved("open")}onData(t){const e=g(t,this.socket.binaryType);this.onPacket(e)}onPacket(t){super.emitReserved("packet",t)}onClose(t){this.readyState="closed",super.emitReserved("close",t)}pause(t){}createUri(t,e={}){return t+"://"+this._hostname()+this._port()+this.opts.path+this._query(e)}_hostname(){const t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"}_port(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""}_query(t){const e=function(t){let e="";for(let s in t)t.hasOwnProperty(s)&&(e.length&&(e+="&"),e+=encodeURIComponent(s)+"="+encodeURIComponent(t[s]));return e}(t);return e.length?"?"+e:""}}class q extends L{constructor(){super(...arguments),this._polling=!1}get name(){return"polling"}doOpen(){this._poll()}pause(t){this.readyState="pausing";const e=()=>{this.readyState="paused",t()};if(this._polling||!this.writable){let t=0;this._polling&&(t++,this.once("pollComplete",(function(){--t||e()}))),this.writable||(t++,this.once("drain",(function(){--t||e()})))}else e()}_poll(){this._polling=!0,this.doPoll(),this.emitReserved("poll")}onData(t){((t,e)=>{const s=t.split(b),n=[];for(let t=0;t<s.length;t++){const i=g(s[t],e);if(n.push(i),"error"===i.type)break}return n})(t,this.socket.binaryType).forEach((t=>{if("opening"===this.readyState&&"open"===t.type&&this.onOpen(),"close"===t.type)return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(t)})),"closed"!==this.readyState&&(this._polling=!1,this.emitReserved("pollComplete"),"open"===this.readyState&&this._poll())}doClose(){const t=()=>{this.write([{type:"close"}])};"open"===this.readyState?t():this.once("open",t)}write(t){this.writable=!1,((t,e)=>{const s=t.length,n=new Array(s);let i=0;t.forEach(((t,r)=>{c(t,!1,(t=>{n[r]=t,++i===s&&e(n.join(b))}))}))})(t,(t=>{this.doWrite(t,(()=>{this.writable=!0,this.emitReserved("drain")}))}))}uri(){const t=this.opts.secure?"https":"http",e=this.query||{};return!1!==this.opts.timestampRequests&&(e[this.opts.timestampParam]=S()),this.supportsBinary||e.sid||(e.b64=1),this.createUri(t,e)}}let P=!1;try{P="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(t){}const j=P;function D(){}class U extends q{constructor(t){if(super(t),"undefined"!=typeof location){const e="https:"===location.protocol;let s=location.port;s||(s=e?"443":"80"),this.xd="undefined"!=typeof location&&t.hostname!==location.hostname||s!==t.port}}doWrite(t,e){const s=this.request({method:"POST",data:t});s.on("success",e),s.on("error",((t,e)=>{this.onError("xhr post error",t,e)}))}doPoll(){const t=this.request();t.on("data",this.onData.bind(this)),t.on("error",((t,e)=>{this.onError("xhr poll error",t,e)})),this.pollXhr=t}}class I extends A{constructor(t,e,s){super(),this.createRequest=t,x(this,s),this._opts=s,this._method=s.method||"GET",this._uri=e,this._data=void 0!==s.data?s.data:null,this._create()}_create(){var t;const e=R(this._opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");e.xdomain=!!this._opts.xd;const s=this._xhr=this.createRequest(e);try{s.open(this._method,this._uri,!0);try{if(this._opts.extraHeaders){s.setDisableHeaderCheck&&s.setDisableHeaderCheck(!0);for(let t in this._opts.extraHeaders)this._opts.extraHeaders.hasOwnProperty(t)&&s.setRequestHeader(t,this._opts.extraHeaders[t])}}catch(t){}if("POST"===this._method)try{s.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{s.setRequestHeader("Accept","*/*")}catch(t){}null===(t=this._opts.cookieJar)||void 0===t||t.addCookies(s),"withCredentials"in s&&(s.withCredentials=this._opts.withCredentials),this._opts.requestTimeout&&(s.timeout=this._opts.requestTimeout),s.onreadystatechange=()=>{var t;3===s.readyState&&(null===(t=this._opts.cookieJar)||void 0===t||t.parseCookies(s.getResponseHeader("set-cookie"))),4===s.readyState&&(200===s.status||1223===s.status?this._onLoad():this.setTimeoutFn((()=>{this._onError("number"==typeof s.status?s.status:0)}),0))},s.send(this._data)}catch(t){return void this.setTimeoutFn((()=>{this._onError(t)}),0)}"undefined"!=typeof document&&(this._index=I.requestsCount++,I.requests[this._index]=this)}_onError(t){this.emitReserved("error",t,this._xhr),this._cleanup(!0)}_cleanup(t){if(void 0!==this._xhr&&null!==this._xhr){if(this._xhr.onreadystatechange=D,t)try{this._xhr.abort()}catch(t){}"undefined"!=typeof document&&delete I.requests[this._index],this._xhr=null}}_onLoad(){const t=this._xhr.responseText;null!==t&&(this.emitReserved("data",t),this.emitReserved("success"),this._cleanup())}abort(){this._cleanup()}}if(I.requestsCount=0,I.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",F);else if("function"==typeof addEventListener){addEventListener("onpagehide"in O?"pagehide":"unload",F,!1)}function F(){for(let t in I.requests)I.requests.hasOwnProperty(t)&&I.requests[t].abort()}const M=function(){const t=V({xdomain:!1});return t&&null!==t.responseType}();function V(t){const e=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!e||j))return new XMLHttpRequest}catch(t){}if(!e)try{return new(O[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}const H="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase();class K extends L{get name(){return"websocket"}doOpen(){const t=this.uri(),e=this.opts.protocols,s=H?{}:R(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(t,e,s)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=t=>this.onClose({description:"websocket connection closed",context:t}),this.ws.onmessage=t=>this.onData(t.data),this.ws.onerror=t=>this.onError("websocket error",t)}write(t){this.writable=!1;for(let e=0;e<t.length;e++){const s=t[e],n=e===t.length-1;c(s,this.supportsBinary,(t=>{try{this.doWrite(s,t)}catch(t){}n&&T((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){void 0!==this.ws&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null)}uri(){const t=this.opts.secure?"wss":"ws",e=this.query||{};return this.opts.timestampRequests&&(e[this.opts.timestampParam]=S()),this.supportsBinary||(e.b64=1),this.createUri(t,e)}}const W=O.WebSocket||O.MozWebSocket;const Y={websocket:class extends K{createSocket(t,e,s){return H?new W(t,e,s):e?new W(t,e):new W(t)}doWrite(t,e){this.ws.send(e)}},webtransport:class extends L{get name(){return"webtransport"}doOpen(){try{this._transport=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name])}catch(t){return this.emitReserved("error",t)}this._transport.closed.then((()=>{this.onClose()})).catch((t=>{this.onError("webtransport error",t)})),this._transport.ready.then((()=>{this._transport.createBidirectionalStream().then((t=>{const e=function(t,e){w||(w=new TextDecoder);const s=[];let n=0,i=-1,o=!1;return new TransformStream({transform(a,h){for(s.push(a);;){if(0===n){if(k(s)<1)break;const t=E(s,1);o=!(128&~t[0]),i=127&t[0],n=i<126?3:126===i?1:2}else if(1===n){if(k(s)<2)break;const t=E(s,2);i=new DataView(t.buffer,t.byteOffset,t.length).getUint16(0),n=3}else if(2===n){if(k(s)<8)break;const t=E(s,8),e=new DataView(t.buffer,t.byteOffset,t.length),o=e.getUint32(0);if(o>Math.pow(2,21)-1){h.enqueue(r);break}i=o*Math.pow(2,32)+e.getUint32(4),n=3}else{if(k(s)<i)break;const t=E(s,i);h.enqueue(g(o?t:w.decode(t),e)),n=0}if(0===i||i>t){h.enqueue(r);break}}}})}(Number.MAX_SAFE_INTEGER,this.socket.binaryType),s=t.readable.pipeThrough(e).getReader(),n=v();n.readable.pipeTo(t.writable),this._writer=n.writable.getWriter();const i=()=>{s.read().then((({done:t,value:e})=>{t||(this.onPacket(e),i())})).catch((t=>{}))};i();const o={type:"open"};this.query.sid&&(o.data=`{"sid":"${this.query.sid}"}`),this._writer.write(o).then((()=>this.onOpen()))}))}))}write(t){this.writable=!1;for(let e=0;e<t.length;e++){const s=t[e],n=e===t.length-1;this._writer.write(s).then((()=>{n&&T((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){var t;null===(t=this._transport)||void 0===t||t.close()}},polling:class extends U{constructor(t){super(t);const e=t&&t.forceBase64;this.supportsBinary=M&&!e}request(t={}){return Object.assign(t,{xd:this.xd},this.opts),new I(V,this.uri(),t)}}},z=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,J=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function $(t){if(t.length>8e3)throw"URI too long";const e=t,s=t.indexOf("["),n=t.indexOf("]");-1!=s&&-1!=n&&(t=t.substring(0,s)+t.substring(s,n).replace(/:/g,";")+t.substring(n,t.length));let i=z.exec(t||""),r={},o=14;for(;o--;)r[J[o]]=i[o]||"";return-1!=s&&-1!=n&&(r.source=e,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r.pathNames=function(t,e){const s=/\/{2,9}/g,n=e.replace(s,"/").split("/");"/"!=e.slice(0,1)&&0!==e.length||n.splice(0,1);"/"==e.slice(-1)&&n.splice(n.length-1,1);return n}(0,r.path),r.queryKey=function(t,e){const s={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,e,n){e&&(s[e]=n)})),s}(0,r.query),r}const Q="function"==typeof addEventListener&&"function"==typeof removeEventListener,X=[];Q&&addEventListener("offline",(()=>{X.forEach((t=>t()))}),!1);class G extends A{constructor(t,e){if(super(),this.binaryType="arraybuffer",this.writeBuffer=[],this._prevBufferLen=0,this._pingInterval=-1,this._pingTimeout=-1,this._maxPayload=-1,this._pingTimeoutTime=1/0,t&&"object"==typeof t&&(e=t,t=null),t){const s=$(t);e.hostname=s.host,e.secure="https"===s.protocol||"wss"===s.protocol,e.port=s.port,s.query&&(e.query=s.query)}else e.host&&(e.hostname=$(e.host).host);x(this,e),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this._transportsByName={},e.transports.forEach((t=>{const e=t.prototype.name;this.transports.push(e),this._transportsByName[e]=t})),this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},e),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),"string"==typeof this.opts.query&&(this.opts.query=function(t){let e={},s=t.split("&");for(let t=0,n=s.length;t<n;t++){let n=s[t].split("=");e[decodeURIComponent(n[0])]=decodeURIComponent(n[1])}return e}(this.opts.query)),Q&&(this.opts.closeOnBeforeunload&&(this._beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this._beforeunloadEventListener,!1)),"localhost"!==this.hostname&&(this._offlineEventListener=()=>{this._onClose("transport close",{description:"network connection lost"})},X.push(this._offlineEventListener))),this.opts.withCredentials&&(this._cookieJar=void 0),this._open()}createTransport(t){const e=Object.assign({},this.opts.query);e.EIO=4,e.transport=t,this.id&&(e.sid=this.id);const s=Object.assign({},this.opts,{query:e,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[t]);return new this._transportsByName[t](s)}_open(){if(0===this.transports.length)return void this.setTimeoutFn((()=>{this.emitReserved("error","No transports available")}),0);const t=this.opts.rememberUpgrade&&G.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket")?"websocket":this.transports[0];this.readyState="opening";const e=this.createTransport(t);e.open(),this.setTransport(e)}setTransport(t){this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",this._onDrain.bind(this)).on("packet",this._onPacket.bind(this)).on("error",this._onError.bind(this)).on("close",(t=>this._onClose("transport close",t)))}onOpen(){this.readyState="open",G.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush()}_onPacket(t){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState)switch(this.emitReserved("packet",t),this.emitReserved("heartbeat"),t.type){case"open":this.onHandshake(JSON.parse(t.data));break;case"ping":this._sendPacket("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this._resetPingTimeout();break;case"error":const e=new Error("server error");e.code=t.data,this._onError(e);break;case"message":this.emitReserved("data",t.data),this.emitReserved("message",t.data)}}onHandshake(t){this.emitReserved("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this._pingInterval=t.pingInterval,this._pingTimeout=t.pingTimeout,this._maxPayload=t.maxPayload,this.onOpen(),"closed"!==this.readyState&&this._resetPingTimeout()}_resetPingTimeout(){this.clearTimeoutFn(this._pingTimeoutTimer);const t=this._pingInterval+this._pingTimeout;this._pingTimeoutTime=Date.now()+t,this._pingTimeoutTimer=this.setTimeoutFn((()=>{this._onClose("ping timeout")}),t),this.opts.autoUnref&&this._pingTimeoutTimer.unref()}_onDrain(){this.writeBuffer.splice(0,this._prevBufferLen),this._prevBufferLen=0,0===this.writeBuffer.length?this.emitReserved("drain"):this.flush()}flush(){if("closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const t=this._getWritablePackets();this.transport.send(t),this._prevBufferLen=t.length,this.emitReserved("flush")}}_getWritablePackets(){if(!(this._maxPayload&&"polling"===this.transport.name&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let s=0;s<this.writeBuffer.length;s++){const n=this.writeBuffer[s].data;if(n&&(t+="string"==typeof(e=n)?function(t){let e=0,s=0;for(let n=0,i=t.length;n<i;n++)e=t.charCodeAt(n),e<128?s+=1:e<2048?s+=2:e<55296||e>=57344?s+=3:(n++,s+=4);return s}(e):Math.ceil(1.33*(e.byteLength||e.size))),s>0&&t>this._maxPayload)return this.writeBuffer.slice(0,s);t+=2}var e;return this.writeBuffer}_hasPingExpired(){if(!this._pingTimeoutTime)return!0;const t=Date.now()>this._pingTimeoutTime;return t&&(this._pingTimeoutTime=0,T((()=>{this._onClose("ping timeout")}),this.setTimeoutFn)),t}write(t,e,s){return this._sendPacket("message",t,e,s),this}send(t,e,s){return this._sendPacket("message",t,e,s),this}_sendPacket(t,e,s,n){if("function"==typeof e&&(n=e,e=void 0),"function"==typeof s&&(n=s,s=null),"closing"===this.readyState||"closed"===this.readyState)return;(s=s||{}).compress=!1!==s.compress;const i={type:t,data:e,options:s};this.emitReserved("packetCreate",i),this.writeBuffer.push(i),n&&this.once("flush",n),this.flush()}close(){const t=()=>{this._onClose("forced close"),this.transport.close()},e=()=>{this.off("upgrade",e),this.off("upgradeError",e),t()},s=()=>{this.once("upgrade",e),this.once("upgradeError",e)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(()=>{this.upgrading?s():t()})):this.upgrading?s():t()),this}_onError(t){if(G.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&"opening"===this.readyState)return this.transports.shift(),this._open();this.emitReserved("error",t),this._onClose("transport error",t)}_onClose(t,e){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){if(this.clearTimeoutFn(this._pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),Q&&(this._beforeunloadEventListener&&removeEventListener("beforeunload",this._beforeunloadEventListener,!1),this._offlineEventListener)){const t=X.indexOf(this._offlineEventListener);-1!==t&&X.splice(t,1)}this.readyState="closed",this.id=null,this.emitReserved("close",t,e),this.writeBuffer=[],this._prevBufferLen=0}}}G.protocol=4;class Z extends G{constructor(){super(...arguments),this._upgrades=[]}onOpen(){if(super.onOpen(),"open"===this.readyState&&this.opts.upgrade)for(let t=0;t<this._upgrades.length;t++)this._probe(this._upgrades[t])}_probe(t){let e=this.createTransport(t),s=!1;G.priorWebsocketSuccess=!1;const n=()=>{s||(e.send([{type:"ping",data:"probe"}]),e.once("packet",(t=>{if(!s)if("pong"===t.type&&"probe"===t.data){if(this.upgrading=!0,this.emitReserved("upgrading",e),!e)return;G.priorWebsocketSuccess="websocket"===e.name,this.transport.pause((()=>{s||"closed"!==this.readyState&&(c(),this.setTransport(e),e.send([{type:"upgrade"}]),this.emitReserved("upgrade",e),e=null,this.upgrading=!1,this.flush())}))}else{const t=new Error("probe error");t.transport=e.name,this.emitReserved("upgradeError",t)}})))};function i(){s||(s=!0,c(),e.close(),e=null)}const r=t=>{const s=new Error("probe error: "+t);s.transport=e.name,i(),this.emitReserved("upgradeError",s)};function o(){r("transport closed")}function a(){r("socket closed")}function h(t){e&&t.name!==e.name&&i()}const c=()=>{e.removeListener("open",n),e.removeListener("error",r),e.removeListener("close",o),this.off("close",a),this.off("upgrading",h)};e.once("open",n),e.once("error",r),e.once("close",o),this.once("close",a),this.once("upgrading",h),-1!==this._upgrades.indexOf("webtransport")&&"webtransport"!==t?this.setTimeoutFn((()=>{s||e.open()}),200):e.open()}onHandshake(t){this._upgrades=this._filterUpgrades(t.upgrades),super.onHandshake(t)}_filterUpgrades(t){const e=[];for(let s=0;s<t.length;s++)~this.transports.indexOf(t[s])&&e.push(t[s]);return e}}let tt=class extends Z{constructor(t,e={}){const s="object"==typeof t?t:e;(!s.transports||s.transports&&"string"==typeof s.transports[0])&&(s.transports=(s.transports||["polling","websocket","webtransport"]).map((t=>Y[t])).filter((t=>!!t))),super(t,s)}};const et="function"==typeof ArrayBuffer,st=Object.prototype.toString,nt="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===st.call(Blob),it="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===st.call(File);function rt(t){return et&&(t instanceof ArrayBuffer||(t=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t.buffer instanceof ArrayBuffer)(t))||nt&&t instanceof Blob||it&&t instanceof File}function ot(t,e){if(!t||"object"!=typeof t)return!1;if(Array.isArray(t)){for(let e=0,s=t.length;e<s;e++)if(ot(t[e]))return!0;return!1}if(rt(t))return!0;if(t.toJSON&&"function"==typeof t.toJSON&&1===arguments.length)return ot(t.toJSON(),!0);for(const e in t)if(Object.prototype.hasOwnProperty.call(t,e)&&ot(t[e]))return!0;return!1}function at(t){const e=[],s=t.data,n=t;return n.data=ht(s,e),n.attachments=e.length,{packet:n,buffers:e}}function ht(t,e){if(!t)return t;if(rt(t)){const s={_placeholder:!0,num:e.length};return e.push(t),s}if(Array.isArray(t)){const s=new Array(t.length);for(let n=0;n<t.length;n++)s[n]=ht(t[n],e);return s}if("object"==typeof t&&!(t instanceof Date)){const s={};for(const n in t)Object.prototype.hasOwnProperty.call(t,n)&&(s[n]=ht(t[n],e));return s}return t}function ct(t,e){return t.data=pt(t.data,e),delete t.attachments,t}function pt(t,e){if(!t)return t;if(t&&!0===t._placeholder){if("number"==typeof t.num&&t.num>=0&&t.num<e.length)return e[t.num];throw new Error("illegal attachments")}if(Array.isArray(t))for(let s=0;s<t.length;s++)t[s]=pt(t[s],e);else if("object"==typeof t)for(const s in t)Object.prototype.hasOwnProperty.call(t,s)&&(t[s]=pt(t[s],e));return t}const ut=["connect","connect_error","disconnect","disconnecting","newListener","removeListener"];var dt;!function(t){t[t.CONNECT=0]="CONNECT",t[t.DISCONNECT=1]="DISCONNECT",t[t.EVENT=2]="EVENT",t[t.ACK=3]="ACK",t[t.CONNECT_ERROR=4]="CONNECT_ERROR",t[t.BINARY_EVENT=5]="BINARY_EVENT",t[t.BINARY_ACK=6]="BINARY_ACK"}(dt||(dt={}));function lt(t){return"[object Object]"===Object.prototype.toString.call(t)}class ft extends A{constructor(t){super(),this.reviver=t}add(t){let e;if("string"==typeof t){if(this.reconstructor)throw new Error("got plaintext data when reconstructing a packet");e=this.decodeString(t);const s=e.type===dt.BINARY_EVENT;s||e.type===dt.BINARY_ACK?(e.type=s?dt.EVENT:dt.ACK,this.reconstructor=new yt(e),0===e.attachments&&super.emitReserved("decoded",e)):super.emitReserved("decoded",e)}else{if(!rt(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");e=this.reconstructor.takeBinaryData(t),e&&(this.reconstructor=null,super.emitReserved("decoded",e))}}decodeString(t){let e=0;const s={type:Number(t.charAt(0))};if(void 0===dt[s.type])throw new Error("unknown packet type "+s.type);if(s.type===dt.BINARY_EVENT||s.type===dt.BINARY_ACK){const n=e+1;for(;"-"!==t.charAt(++e)&&e!=t.length;);const i=t.substring(n,e);if(i!=Number(i)||"-"!==t.charAt(e))throw new Error("Illegal attachments");s.attachments=Number(i)}if("/"===t.charAt(e+1)){const n=e+1;for(;++e;){if(","===t.charAt(e))break;if(e===t.length)break}s.nsp=t.substring(n,e)}else s.nsp="/";const n=t.charAt(e+1);if(""!==n&&Number(n)==n){const n=e+1;for(;++e;){const s=t.charAt(e);if(null==s||Number(s)!=s){--e;break}if(e===t.length)break}s.id=Number(t.substring(n,e+1))}if(t.charAt(++e)){const n=this.tryParse(t.substr(e));if(!ft.isPayloadValid(s.type,n))throw new Error("invalid payload");s.data=n}return s}tryParse(t){try{return JSON.parse(t,this.reviver)}catch(t){return!1}}static isPayloadValid(t,e){switch(t){case dt.CONNECT:return lt(e);case dt.DISCONNECT:return void 0===e;case dt.CONNECT_ERROR:return"string"==typeof e||lt(e);case dt.EVENT:case dt.BINARY_EVENT:return Array.isArray(e)&&("number"==typeof e[0]||"string"==typeof e[0]&&-1===ut.indexOf(e[0]));case dt.ACK:case dt.BINARY_ACK:return Array.isArray(e)}}destroy(){this.reconstructor&&(this.reconstructor.finishedReconstruction(),this.reconstructor=null)}}class yt{constructor(t){this.packet=t,this.buffers=[],this.reconPack=t}takeBinaryData(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){const t=ct(this.reconPack,this.buffers);return this.finishedReconstruction(),t}return null}finishedReconstruction(){this.reconPack=null,this.buffers=[]}}var gt=Object.freeze({__proto__:null,Decoder:ft,Encoder:class{constructor(t){this.replacer=t}encode(t){return t.type!==dt.EVENT&&t.type!==dt.ACK||!ot(t)?[this.encodeAsString(t)]:this.encodeAsBinary({type:t.type===dt.EVENT?dt.BINARY_EVENT:dt.BINARY_ACK,nsp:t.nsp,data:t.data,id:t.id})}encodeAsString(t){let e=""+t.type;return t.type!==dt.BINARY_EVENT&&t.type!==dt.BINARY_ACK||(e+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(e+=t.nsp+","),null!=t.id&&(e+=t.id),null!=t.data&&(e+=JSON.stringify(t.data,this.replacer)),e}encodeAsBinary(t){const e=at(t),s=this.encodeAsString(e.packet),n=e.buffers;return n.unshift(s),n}},get PacketType(){return dt},protocol:5});function mt(t,e,s){return t.on(e,s),function(){t.off(e,s)}}const _t=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1});class bt extends A{constructor(t,e,s){super(),this.connected=!1,this.recovered=!1,this.receiveBuffer=[],this.sendBuffer=[],this._queue=[],this._queueSeq=0,this.ids=0,this.acks={},this.flags={},this.io=t,this.nsp=e,s&&s.auth&&(this.auth=s.auth),this._opts=Object.assign({},s),this.io._autoConnect&&this.open()}get disconnected(){return!this.connected}subEvents(){if(this.subs)return;const t=this.io;this.subs=[mt(t,"open",this.onopen.bind(this)),mt(t,"packet",this.onpacket.bind(this)),mt(t,"error",this.onerror.bind(this)),mt(t,"close",this.onclose.bind(this))]}get active(){return!!this.subs}connect(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}open(){return this.connect()}send(...t){return t.unshift("message"),this.emit.apply(this,t),this}emit(t,...e){var s,n,i;if(_t.hasOwnProperty(t))throw new Error('"'+t.toString()+'" is a reserved event name');if(e.unshift(t),this._opts.retries&&!this.flags.fromQueue&&!this.flags.volatile)return this._addToQueue(e),this;const r={type:dt.EVENT,data:e,options:{}};if(r.options.compress=!1!==this.flags.compress,"function"==typeof e[e.length-1]){const t=this.ids++,s=e.pop();this._registerAckCallback(t,s),r.id=t}const o=null===(n=null===(s=this.io.engine)||void 0===s?void 0:s.transport)||void 0===n?void 0:n.writable,a=this.connected&&!(null===(i=this.io.engine)||void 0===i?void 0:i._hasPingExpired());return this.flags.volatile&&!o||(a?(this.notifyOutgoingListeners(r),this.packet(r)):this.sendBuffer.push(r)),this.flags={},this}_registerAckCallback(t,e){var s;const n=null!==(s=this.flags.timeout)&&void 0!==s?s:this._opts.ackTimeout;if(void 0===n)return void(this.acks[t]=e);const i=this.io.setTimeoutFn((()=>{delete this.acks[t];for(let e=0;e<this.sendBuffer.length;e++)this.sendBuffer[e].id===t&&this.sendBuffer.splice(e,1);e.call(this,new Error("operation has timed out"))}),n),r=(...t)=>{this.io.clearTimeoutFn(i),e.apply(this,t)};r.withError=!0,this.acks[t]=r}emitWithAck(t,...e){return new Promise(((s,n)=>{const i=(t,e)=>t?n(t):s(e);i.withError=!0,e.push(i),this.emit(t,...e)}))}_addToQueue(t){let e;"function"==typeof t[t.length-1]&&(e=t.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:t,flags:Object.assign({fromQueue:!0},this.flags)};t.push(((t,...n)=>{if(s!==this._queue[0])return;return null!==t?s.tryCount>this._opts.retries&&(this._queue.shift(),e&&e(t)):(this._queue.shift(),e&&e(null,...n)),s.pending=!1,this._drainQueue()})),this._queue.push(s),this._drainQueue()}_drainQueue(t=!1){if(!this.connected||0===this._queue.length)return;const e=this._queue[0];e.pending&&!t||(e.pending=!0,e.tryCount++,this.flags=e.flags,this.emit.apply(this,e.args))}packet(t){t.nsp=this.nsp,this.io._packet(t)}onopen(){"function"==typeof this.auth?this.auth((t=>{this._sendConnectPacket(t)})):this._sendConnectPacket(this.auth)}_sendConnectPacket(t){this.packet({type:dt.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},t):t})}onerror(t){this.connected||this.emitReserved("connect_error",t)}onclose(t,e){this.connected=!1,delete this.id,this.emitReserved("disconnect",t,e),this._clearAcks()}_clearAcks(){Object.keys(this.acks).forEach((t=>{if(!this.sendBuffer.some((e=>String(e.id)===t))){const e=this.acks[t];delete this.acks[t],e.withError&&e.call(this,new Error("socket has been disconnected"))}}))}onpacket(t){if(t.nsp===this.nsp)switch(t.type){case dt.CONNECT:t.data&&t.data.sid?this.onconnect(t.data.sid,t.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case dt.EVENT:case dt.BINARY_EVENT:this.onevent(t);break;case dt.ACK:case dt.BINARY_ACK:this.onack(t);break;case dt.DISCONNECT:this.ondisconnect();break;case dt.CONNECT_ERROR:this.destroy();const e=new Error(t.data.message);e.data=t.data.data,this.emitReserved("connect_error",e)}}onevent(t){const e=t.data||[];null!=t.id&&e.push(this.ack(t.id)),this.connected?this.emitEvent(e):this.receiveBuffer.push(Object.freeze(e))}emitEvent(t){if(this._anyListeners&&this._anyListeners.length){const e=this._anyListeners.slice();for(const s of e)s.apply(this,t)}super.emit.apply(this,t),this._pid&&t.length&&"string"==typeof t[t.length-1]&&(this._lastOffset=t[t.length-1])}ack(t){const e=this;let s=!1;return function(...n){s||(s=!0,e.packet({type:dt.ACK,id:t,data:n}))}}onack(t){const e=this.acks[t.id];"function"==typeof e&&(delete this.acks[t.id],e.withError&&t.data.unshift(null),e.apply(this,t.data))}onconnect(t,e){this.id=t,this.recovered=e&&this._pid===e,this._pid=e,this.connected=!0,this.emitBuffered(),this.emitReserved("connect"),this._drainQueue(!0)}emitBuffered(){this.receiveBuffer.forEach((t=>this.emitEvent(t))),this.receiveBuffer=[],this.sendBuffer.forEach((t=>{this.notifyOutgoingListeners(t),this.packet(t)})),this.sendBuffer=[]}ondisconnect(){this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach((t=>t())),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&this.packet({type:dt.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(t){return this.flags.compress=t,this}get volatile(){return this.flags.volatile=!0,this}timeout(t){return this.flags.timeout=t,this}onAny(t){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(t),this}prependAny(t){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(t),this}offAny(t){if(!this._anyListeners)return this;if(t){const e=this._anyListeners;for(let s=0;s<e.length;s++)if(t===e[s])return e.splice(s,1),this}else this._anyListeners=[];return this}listenersAny(){return this._anyListeners||[]}onAnyOutgoing(t){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.push(t),this}prependAnyOutgoing(t){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.unshift(t),this}offAnyOutgoing(t){if(!this._anyOutgoingListeners)return this;if(t){const e=this._anyOutgoingListeners;for(let s=0;s<e.length;s++)if(t===e[s])return e.splice(s,1),this}else this._anyOutgoingListeners=[];return this}listenersAnyOutgoing(){return this._anyOutgoingListeners||[]}notifyOutgoingListeners(t){if(this._anyOutgoingListeners&&this._anyOutgoingListeners.length){const e=this._anyOutgoingListeners.slice();for(const s of e)s.apply(this,t.data)}}}function vt(t){t=t||{},this.ms=t.min||100,this.max=t.max||1e4,this.factor=t.factor||2,this.jitter=t.jitter>0&&t.jitter<=1?t.jitter:0,this.attempts=0}vt.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),s=Math.floor(e*this.jitter*t);t=1&Math.floor(10*e)?t+s:t-s}return 0|Math.min(t,this.max)},vt.prototype.reset=function(){this.attempts=0},vt.prototype.setMin=function(t){this.ms=t},vt.prototype.setMax=function(t){this.max=t},vt.prototype.setJitter=function(t){this.jitter=t};class wt extends A{constructor(t,e){var s;super(),this.nsps={},this.subs=[],t&&"object"==typeof t&&(e=t,t=void 0),(e=e||{}).path=e.path||"/socket.io",this.opts=e,x(this,e),this.reconnection(!1!==e.reconnection),this.reconnectionAttempts(e.reconnectionAttempts||1/0),this.reconnectionDelay(e.reconnectionDelay||1e3),this.reconnectionDelayMax(e.reconnectionDelayMax||5e3),this.randomizationFactor(null!==(s=e.randomizationFactor)&&void 0!==s?s:.5),this.backoff=new vt({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==e.timeout?2e4:e.timeout),this._readyState="closed",this.uri=t;const n=e.parser||gt;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this._autoConnect=!1!==e.autoConnect,this._autoConnect&&this.open()}reconnection(t){return arguments.length?(this._reconnection=!!t,t||(this.skipReconnect=!0),this):this._reconnection}reconnectionAttempts(t){return void 0===t?this._reconnectionAttempts:(this._reconnectionAttempts=t,this)}reconnectionDelay(t){var e;return void 0===t?this._reconnectionDelay:(this._reconnectionDelay=t,null===(e=this.backoff)||void 0===e||e.setMin(t),this)}randomizationFactor(t){var e;return void 0===t?this._randomizationFactor:(this._randomizationFactor=t,null===(e=this.backoff)||void 0===e||e.setJitter(t),this)}reconnectionDelayMax(t){var e;return void 0===t?this._reconnectionDelayMax:(this._reconnectionDelayMax=t,null===(e=this.backoff)||void 0===e||e.setMax(t),this)}timeout(t){return arguments.length?(this._timeout=t,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}open(t){if(~this._readyState.indexOf("open"))return this;this.engine=new tt(this.uri,this.opts);const e=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const n=mt(e,"open",(function(){s.onopen(),t&&t()})),i=e=>{this.cleanup(),this._readyState="closed",this.emitReserved("error",e),t?t(e):this.maybeReconnectOnOpen()},r=mt(e,"error",i);if(!1!==this._timeout){const t=this._timeout,s=this.setTimeoutFn((()=>{n(),i(new Error("timeout")),e.close()}),t);this.opts.autoUnref&&s.unref(),this.subs.push((()=>{this.clearTimeoutFn(s)}))}return this.subs.push(n),this.subs.push(r),this}connect(t){return this.open(t)}onopen(){this.cleanup(),this._readyState="open",this.emitReserved("open");const t=this.engine;this.subs.push(mt(t,"ping",this.onping.bind(this)),mt(t,"data",this.ondata.bind(this)),mt(t,"error",this.onerror.bind(this)),mt(t,"close",this.onclose.bind(this)),mt(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(t){try{this.decoder.add(t)}catch(t){this.onclose("parse error",t)}}ondecoded(t){T((()=>{this.emitReserved("packet",t)}),this.setTimeoutFn)}onerror(t){this.emitReserved("error",t)}socket(t,e){let s=this.nsps[t];return s?this._autoConnect&&!s.active&&s.connect():(s=new bt(this,t,e),this.nsps[t]=s),s}_destroy(t){const e=Object.keys(this.nsps);for(const t of e){if(this.nsps[t].active)return}this._close()}_packet(t){const e=this.encoder.encode(t);for(let s=0;s<e.length;s++)this.engine.write(e[s],t.options)}cleanup(){this.subs.forEach((t=>t())),this.subs.length=0,this.decoder.destroy()}_close(){this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close")}disconnect(){return this._close()}onclose(t,e){var s;this.cleanup(),null===(s=this.engine)||void 0===s||s.close(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",t,e),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const t=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const e=this.backoff.duration();this._reconnecting=!0;const s=this.setTimeoutFn((()=>{t.skipReconnect||(this.emitReserved("reconnect_attempt",t.backoff.attempts),t.skipReconnect||t.open((e=>{e?(t._reconnecting=!1,t.reconnect(),this.emitReserved("reconnect_error",e)):t.onreconnect()})))}),e);this.opts.autoUnref&&s.unref(),this.subs.push((()=>{this.clearTimeoutFn(s)}))}}onreconnect(){const t=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",t)}}const kt={};function Et(t,e){"object"==typeof t&&(e=t,t=void 0);const s=function(t,e="",s){let n=t;s=s||"undefined"!=typeof location&&location,null==t&&(t=s.protocol+"//"+s.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?s.protocol+t:s.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==s?s.protocol+"//"+t:"https://"+t),n=$(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";const i=-1!==n.host.indexOf(":")?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+i+":"+n.port+e,n.href=n.protocol+"://"+i+(s&&s.port===n.port?"":":"+n.port),n}(t,(e=e||{}).path||"/socket.io"),n=s.source,i=s.id,r=s.path,o=kt[i]&&r in kt[i].nsps;let a;return e.forceNew||e["force new connection"]||!1===e.multiplex||o?a=new wt(n,e):(kt[i]||(kt[i]=new wt(n,e)),a=kt[i]),s.query&&!e.query&&(e.query=s.queryKey),a.socket(s.path,e)}Object.assign(Et,{Manager:wt,Socket:bt,io:Et,connect:Et});const At="x-api-token";class Tt extends EventTarget{get connected(){var t,e;return null!==(e=null===(t=this.socket)||void 0===t?void 0:t.connected)&&void 0!==e&&e}constructor(t){super(),this.token=t,this.socket=null}close(){this.socket&&this.socket.connected&&(this.socket.removeAllListeners(),this.socket.close())}listen(t,e,s){var n,i;if(this.socket&&this.socket.connected&&(this.socket.removeAllListeners(),this.socket.close()),0===t.length)throw new Error("At least one event type must be provided");this.socket=Et(`https://realtime.jsonpad.io?${new URLSearchParams({eventTypes:t.join(","),listIds:null!==(n=null==e?void 0:e.join(","))&&void 0!==n?n:"",itemIds:null!==(i=null==s?void 0:s.join(","))&&void 0!==i?i:""})}`,{extraHeaders:{[At]:this.token}}),this.socket.on("connect",this.handleConnected.bind(this)),this.socket.on("disconnect",this.handleDisconnected.bind(this)),this.socket.on("error",this.handleError.bind(this)),this.socket.on("list-created",this.handleEvent.bind(this,"list-created")),this.socket.on("list-updated",this.handleEvent.bind(this,"list-updated")),this.socket.on("list-deleted",this.handleEvent.bind(this,"list-deleted")),this.socket.on("item-created",this.handleEvent.bind(this,"item-created")),this.socket.on("item-updated",this.handleEvent.bind(this,"item-updated")),this.socket.on("item-restored",this.handleEvent.bind(this,"item-restored")),this.socket.on("item-deleted",this.handleEvent.bind(this,"item-deleted"))}handleConnected(){this.dispatchEvent(Tt.connected)}handleDisconnected(){this.dispatchEvent(Tt.disconnected)}handleError(t){this.dispatchEvent(new CustomEvent("error",{detail:t}))}handleEvent(t,n){let i;try{i=JSON.parse(n)}catch(t){return void this.handleError("Failed to parse data")}switch(t){case"list-created":this.dispatchEvent(new s("list-created",i));break;case"list-updated":this.dispatchEvent(new s("list-updated",i));break;case"list-deleted":this.dispatchEvent(new s("list-deleted",i));break;case"item-created":this.dispatchEvent(new e("item-created",i));break;case"item-updated":this.dispatchEvent(new e("item-updated",i));break;case"item-restored":this.dispatchEvent(new e("item-restored",i));break;case"item-deleted":this.dispatchEvent(new e("item-deleted",i))}}}Tt.connected=new Event("connected"),Tt.disconnected=new Event("disconnected"),t.ItemEvent=e,t.ListEvent=s,t.default=Tt,Object.defineProperty(t,"__esModule",{value:!0})}));
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).JSONPadRealtime={})}(this,(function(t){"use strict";class e extends CustomEvent{constructor(t,e){if(e.model.data&&"string"==typeof e.model.data)try{e.model.data=JSON.parse(e.model.data)}catch(t){}super(t,{detail:e})}}class s extends CustomEvent{constructor(t,e){super(t,{detail:e})}}class n extends CustomEvent{constructor(t,e,s=null){var n,i,r,o;super("error",{detail:t}),this.code=null!==(n=null==e?void 0:e.code)&&void 0!==n?n:null,this.errorName=null!==(i=null==e?void 0:e.name)&&void 0!==i?i:null,this.max=null!==(r=null==e?void 0:e.max)&&void 0!==r?r:null,this.retryAfter=null!==(o=null==e?void 0:e.retryAfter)&&void 0!==o?o:null,this.retryIn=s}}const i=Object.create(null);i.open="0",i.close="1",i.ping="2",i.pong="3",i.message="4",i.upgrade="5",i.noop="6";const r=Object.create(null);Object.keys(i).forEach((t=>{r[i[t]]=t}));const o={type:"error",data:"parser error"},a="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),h="function"==typeof ArrayBuffer,c=t=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer instanceof ArrayBuffer,u=({type:t,data:e},s,n)=>a&&e instanceof Blob?s?n(e):p(e,n):h&&(e instanceof ArrayBuffer||c(e))?s?n(e):p(new Blob([e]),n):n(i[t]+(e||"")),p=(t,e)=>{const s=new FileReader;return s.onload=function(){const t=s.result.split(",")[1];e("b"+(t||""))},s.readAsDataURL(t)};function l(t){return t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}let d;const f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",y="undefined"==typeof Uint8Array?[]:new Uint8Array(256);for(let t=0;t<64;t++)y[f.charCodeAt(t)]=t;const m="function"==typeof ArrayBuffer,g=(t,e)=>{if("string"!=typeof t)return{type:"message",data:b(t,e)};const s=t.charAt(0);if("b"===s)return{type:"message",data:_(t.substring(1),e)};return r[s]?t.length>1?{type:r[s],data:t.substring(1)}:{type:r[s]}:o},_=(t,e)=>{if(m){const s=(t=>{let e,s,n,i,r,o=.75*t.length,a=t.length,h=0;"="===t[t.length-1]&&(o--,"="===t[t.length-2]&&o--);const c=new ArrayBuffer(o),u=new Uint8Array(c);for(e=0;e<a;e+=4)s=y[t.charCodeAt(e)],n=y[t.charCodeAt(e+1)],i=y[t.charCodeAt(e+2)],r=y[t.charCodeAt(e+3)],u[h++]=s<<2|n>>4,u[h++]=(15&n)<<4|i>>2,u[h++]=(3&i)<<6|63&r;return c})(t);return b(s,e)}return{base64:!0,data:t}},b=(t,e)=>"blob"===e?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer,v=String.fromCharCode(30);function w(){return new TransformStream({transform(t,e){!function(t,e){a&&t.data instanceof Blob?t.data.arrayBuffer().then(l).then(e):h&&(t.data instanceof ArrayBuffer||c(t.data))?e(l(t.data)):u(t,!1,(t=>{d||(d=new TextEncoder),e(d.encode(t))}))}(t,(s=>{const n=s.length;let i;if(n<126)i=new Uint8Array(1),new DataView(i.buffer).setUint8(0,n);else if(n<65536){i=new Uint8Array(3);const t=new DataView(i.buffer);t.setUint8(0,126),t.setUint16(1,n)}else{i=new Uint8Array(9);const t=new DataView(i.buffer);t.setUint8(0,127),t.setBigUint64(1,BigInt(n))}t.data&&"string"!=typeof t.data&&(i[0]|=128),e.enqueue(i),e.enqueue(s)}))}})}let k;function E(t){return t.reduce(((t,e)=>t+e.length),0)}function A(t,e){if(t[0].length===e)return t.shift();const s=new Uint8Array(e);let n=0;for(let i=0;i<e;i++)s[i]=t[0][n++],n===t[0].length&&(t.shift(),n=0);return t.length&&n<t[0].length&&(t[0]=t[0].slice(n)),s}function T(t){if(t)return function(t){for(var e in T.prototype)t[e]=T.prototype[e];return t}(t)}T.prototype.on=T.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},T.prototype.once=function(t,e){function s(){this.off(t,s),e.apply(this,arguments)}return s.fn=e,this.on(t,s),this},T.prototype.off=T.prototype.removeListener=T.prototype.removeAllListeners=T.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s,n=this._callbacks["$"+t];if(!n)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var i=0;i<n.length;i++)if((s=n[i])===e||s.fn===e){n.splice(i,1);break}return 0===n.length&&delete this._callbacks["$"+t],this},T.prototype.emit=function(t){this._callbacks=this._callbacks||{};for(var e=new Array(arguments.length-1),s=this._callbacks["$"+t],n=1;n<arguments.length;n++)e[n-1]=arguments[n];if(s){n=0;for(var i=(s=s.slice(0)).length;n<i;++n)s[n].apply(this,e)}return this},T.prototype.emitReserved=T.prototype.emit,T.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks["$"+t]||[]},T.prototype.hasListeners=function(t){return!!this.listeners(t).length};const O="function"==typeof Promise&&"function"==typeof Promise.resolve?t=>Promise.resolve().then(t):(t,e)=>e(t,0),R="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")();function C(t,...e){return e.reduce(((e,s)=>(t.hasOwnProperty(s)&&(e[s]=t[s]),e)),{})}const x=R.setTimeout,B=R.clearTimeout;function S(t,e){e.useNativeTimers?(t.setTimeoutFn=x.bind(R),t.clearTimeoutFn=B.bind(R)):(t.setTimeoutFn=R.setTimeout.bind(R),t.clearTimeoutFn=R.clearTimeout.bind(R))}function N(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}class L extends Error{constructor(t,e,s){super(t),this.description=e,this.context=s,this.type="TransportError"}}class q extends T{constructor(t){super(),this.writable=!1,S(this,t),this.opts=t,this.query=t.query,this.socket=t.socket,this.supportsBinary=!t.forceBase64}onError(t,e,s){return super.emitReserved("error",new L(t,e,s)),this}open(){return this.readyState="opening",this.doOpen(),this}close(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this}send(t){"open"===this.readyState&&this.write(t)}onOpen(){this.readyState="open",this.writable=!0,super.emitReserved("open")}onData(t){const e=g(t,this.socket.binaryType);this.onPacket(e)}onPacket(t){super.emitReserved("packet",t)}onClose(t){this.readyState="closed",super.emitReserved("close",t)}pause(t){}createUri(t,e={}){return t+"://"+this._hostname()+this._port()+this.opts.path+this._query(e)}_hostname(){const t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"}_port(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""}_query(t){const e=function(t){let e="";for(let s in t)t.hasOwnProperty(s)&&(e.length&&(e+="&"),e+=encodeURIComponent(s)+"="+encodeURIComponent(t[s]));return e}(t);return e.length?"?"+e:""}}class P extends q{constructor(){super(...arguments),this._polling=!1}get name(){return"polling"}doOpen(){this._poll()}pause(t){this.readyState="pausing";const e=()=>{this.readyState="paused",t()};if(this._polling||!this.writable){let t=0;this._polling&&(t++,this.once("pollComplete",(function(){--t||e()}))),this.writable||(t++,this.once("drain",(function(){--t||e()})))}else e()}_poll(){this._polling=!0,this.doPoll(),this.emitReserved("poll")}onData(t){((t,e)=>{const s=t.split(v),n=[];for(let t=0;t<s.length;t++){const i=g(s[t],e);if(n.push(i),"error"===i.type)break}return n})(t,this.socket.binaryType).forEach((t=>{if("opening"===this.readyState&&"open"===t.type&&this.onOpen(),"close"===t.type)return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(t)})),"closed"!==this.readyState&&(this._polling=!1,this.emitReserved("pollComplete"),"open"===this.readyState&&this._poll())}doClose(){const t=()=>{this.write([{type:"close"}])};"open"===this.readyState?t():this.once("open",t)}write(t){this.writable=!1,((t,e)=>{const s=t.length,n=new Array(s);let i=0;t.forEach(((t,r)=>{u(t,!1,(t=>{n[r]=t,++i===s&&e(n.join(v))}))}))})(t,(t=>{this.doWrite(t,(()=>{this.writable=!0,this.emitReserved("drain")}))}))}uri(){const t=this.opts.secure?"https":"http",e=this.query||{};return!1!==this.opts.timestampRequests&&(e[this.opts.timestampParam]=N()),this.supportsBinary||e.sid||(e.b64=1),this.createUri(t,e)}}let j=!1;try{j="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(t){}const D=j;function U(){}class I extends P{constructor(t){if(super(t),"undefined"!=typeof location){const e="https:"===location.protocol;let s=location.port;s||(s=e?"443":"80"),this.xd="undefined"!=typeof location&&t.hostname!==location.hostname||s!==t.port}}doWrite(t,e){const s=this.request({method:"POST",data:t});s.on("success",e),s.on("error",((t,e)=>{this.onError("xhr post error",t,e)}))}doPoll(){const t=this.request();t.on("data",this.onData.bind(this)),t.on("error",((t,e)=>{this.onError("xhr poll error",t,e)})),this.pollXhr=t}}class F extends T{constructor(t,e,s){super(),this.createRequest=t,S(this,s),this._opts=s,this._method=s.method||"GET",this._uri=e,this._data=void 0!==s.data?s.data:null,this._create()}_create(){var t;const e=C(this._opts,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");e.xdomain=!!this._opts.xd;const s=this._xhr=this.createRequest(e);try{s.open(this._method,this._uri,!0);try{if(this._opts.extraHeaders){s.setDisableHeaderCheck&&s.setDisableHeaderCheck(!0);for(let t in this._opts.extraHeaders)this._opts.extraHeaders.hasOwnProperty(t)&&s.setRequestHeader(t,this._opts.extraHeaders[t])}}catch(t){}if("POST"===this._method)try{s.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{s.setRequestHeader("Accept","*/*")}catch(t){}null===(t=this._opts.cookieJar)||void 0===t||t.addCookies(s),"withCredentials"in s&&(s.withCredentials=this._opts.withCredentials),this._opts.requestTimeout&&(s.timeout=this._opts.requestTimeout),s.onreadystatechange=()=>{var t;3===s.readyState&&(null===(t=this._opts.cookieJar)||void 0===t||t.parseCookies(s.getResponseHeader("set-cookie"))),4===s.readyState&&(200===s.status||1223===s.status?this._onLoad():this.setTimeoutFn((()=>{this._onError("number"==typeof s.status?s.status:0)}),0))},s.send(this._data)}catch(t){return void this.setTimeoutFn((()=>{this._onError(t)}),0)}"undefined"!=typeof document&&(this._index=F.requestsCount++,F.requests[this._index]=this)}_onError(t){this.emitReserved("error",t,this._xhr),this._cleanup(!0)}_cleanup(t){if(void 0!==this._xhr&&null!==this._xhr){if(this._xhr.onreadystatechange=U,t)try{this._xhr.abort()}catch(t){}"undefined"!=typeof document&&delete F.requests[this._index],this._xhr=null}}_onLoad(){const t=this._xhr.responseText;null!==t&&(this.emitReserved("data",t),this.emitReserved("success"),this._cleanup())}abort(){this._cleanup()}}if(F.requestsCount=0,F.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",M);else if("function"==typeof addEventListener){addEventListener("onpagehide"in R?"pagehide":"unload",M,!1)}function M(){for(let t in F.requests)F.requests.hasOwnProperty(t)&&F.requests[t].abort()}const V=function(){const t=H({xdomain:!1});return t&&null!==t.responseType}();function H(t){const e=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!e||D))return new XMLHttpRequest}catch(t){}if(!e)try{return new(R[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}const K="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase();class W extends q{get name(){return"websocket"}doOpen(){const t=this.uri(),e=this.opts.protocols,s=K?{}:C(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(s.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(t,e,s)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws._socket.unref(),this.onOpen()},this.ws.onclose=t=>this.onClose({description:"websocket connection closed",context:t}),this.ws.onmessage=t=>this.onData(t.data),this.ws.onerror=t=>this.onError("websocket error",t)}write(t){this.writable=!1;for(let e=0;e<t.length;e++){const s=t[e],n=e===t.length-1;u(s,this.supportsBinary,(t=>{try{this.doWrite(s,t)}catch(t){}n&&O((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){void 0!==this.ws&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null)}uri(){const t=this.opts.secure?"wss":"ws",e=this.query||{};return this.opts.timestampRequests&&(e[this.opts.timestampParam]=N()),this.supportsBinary||(e.b64=1),this.createUri(t,e)}}const Y=R.WebSocket||R.MozWebSocket;const z={websocket:class extends W{createSocket(t,e,s){return K?new Y(t,e,s):e?new Y(t,e):new Y(t)}doWrite(t,e){this.ws.send(e)}},webtransport:class extends q{get name(){return"webtransport"}doOpen(){try{this._transport=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name])}catch(t){return this.emitReserved("error",t)}this._transport.closed.then((()=>{this.onClose()})).catch((t=>{this.onError("webtransport error",t)})),this._transport.ready.then((()=>{this._transport.createBidirectionalStream().then((t=>{const e=function(t,e){k||(k=new TextDecoder);const s=[];let n=0,i=-1,r=!1;return new TransformStream({transform(a,h){for(s.push(a);;){if(0===n){if(E(s)<1)break;const t=A(s,1);r=!(128&~t[0]),i=127&t[0],n=i<126?3:126===i?1:2}else if(1===n){if(E(s)<2)break;const t=A(s,2);i=new DataView(t.buffer,t.byteOffset,t.length).getUint16(0),n=3}else if(2===n){if(E(s)<8)break;const t=A(s,8),e=new DataView(t.buffer,t.byteOffset,t.length),r=e.getUint32(0);if(r>Math.pow(2,21)-1){h.enqueue(o);break}i=r*Math.pow(2,32)+e.getUint32(4),n=3}else{if(E(s)<i)break;const t=A(s,i);h.enqueue(g(r?t:k.decode(t),e)),n=0}if(0===i||i>t){h.enqueue(o);break}}}})}(Number.MAX_SAFE_INTEGER,this.socket.binaryType),s=t.readable.pipeThrough(e).getReader(),n=w();n.readable.pipeTo(t.writable),this._writer=n.writable.getWriter();const i=()=>{s.read().then((({done:t,value:e})=>{t||(this.onPacket(e),i())})).catch((t=>{}))};i();const r={type:"open"};this.query.sid&&(r.data=`{"sid":"${this.query.sid}"}`),this._writer.write(r).then((()=>this.onOpen()))}))}))}write(t){this.writable=!1;for(let e=0;e<t.length;e++){const s=t[e],n=e===t.length-1;this._writer.write(s).then((()=>{n&&O((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){var t;null===(t=this._transport)||void 0===t||t.close()}},polling:class extends I{constructor(t){super(t);const e=t&&t.forceBase64;this.supportsBinary=V&&!e}request(t={}){return Object.assign(t,{xd:this.xd},this.opts),new F(H,this.uri(),t)}}},J=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,$=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function Q(t){if(t.length>8e3)throw"URI too long";const e=t,s=t.indexOf("["),n=t.indexOf("]");-1!=s&&-1!=n&&(t=t.substring(0,s)+t.substring(s,n).replace(/:/g,";")+t.substring(n,t.length));let i=J.exec(t||""),r={},o=14;for(;o--;)r[$[o]]=i[o]||"";return-1!=s&&-1!=n&&(r.source=e,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r.pathNames=function(t,e){const s=/\/{2,9}/g,n=e.replace(s,"/").split("/");"/"!=e.slice(0,1)&&0!==e.length||n.splice(0,1);"/"==e.slice(-1)&&n.splice(n.length-1,1);return n}(0,r.path),r.queryKey=function(t,e){const s={};return e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,e,n){e&&(s[e]=n)})),s}(0,r.query),r}const X="function"==typeof addEventListener&&"function"==typeof removeEventListener,G=[];X&&addEventListener("offline",(()=>{G.forEach((t=>t()))}),!1);class Z extends T{constructor(t,e){if(super(),this.binaryType="arraybuffer",this.writeBuffer=[],this._prevBufferLen=0,this._pingInterval=-1,this._pingTimeout=-1,this._maxPayload=-1,this._pingTimeoutTime=1/0,t&&"object"==typeof t&&(e=t,t=null),t){const s=Q(t);e.hostname=s.host,e.secure="https"===s.protocol||"wss"===s.protocol,e.port=s.port,s.query&&(e.query=s.query)}else e.host&&(e.hostname=Q(e.host).host);S(this,e),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this._transportsByName={},e.transports.forEach((t=>{const e=t.prototype.name;this.transports.push(e),this._transportsByName[e]=t})),this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},e),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),"string"==typeof this.opts.query&&(this.opts.query=function(t){let e={},s=t.split("&");for(let t=0,n=s.length;t<n;t++){let n=s[t].split("=");e[decodeURIComponent(n[0])]=decodeURIComponent(n[1])}return e}(this.opts.query)),X&&(this.opts.closeOnBeforeunload&&(this._beforeunloadEventListener=()=>{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this._beforeunloadEventListener,!1)),"localhost"!==this.hostname&&(this._offlineEventListener=()=>{this._onClose("transport close",{description:"network connection lost"})},G.push(this._offlineEventListener))),this.opts.withCredentials&&(this._cookieJar=void 0),this._open()}createTransport(t){const e=Object.assign({},this.opts.query);e.EIO=4,e.transport=t,this.id&&(e.sid=this.id);const s=Object.assign({},this.opts,{query:e,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[t]);return new this._transportsByName[t](s)}_open(){if(0===this.transports.length)return void this.setTimeoutFn((()=>{this.emitReserved("error","No transports available")}),0);const t=this.opts.rememberUpgrade&&Z.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket")?"websocket":this.transports[0];this.readyState="opening";const e=this.createTransport(t);e.open(),this.setTransport(e)}setTransport(t){this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",this._onDrain.bind(this)).on("packet",this._onPacket.bind(this)).on("error",this._onError.bind(this)).on("close",(t=>this._onClose("transport close",t)))}onOpen(){this.readyState="open",Z.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush()}_onPacket(t){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState)switch(this.emitReserved("packet",t),this.emitReserved("heartbeat"),t.type){case"open":this.onHandshake(JSON.parse(t.data));break;case"ping":this._sendPacket("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this._resetPingTimeout();break;case"error":const e=new Error("server error");e.code=t.data,this._onError(e);break;case"message":this.emitReserved("data",t.data),this.emitReserved("message",t.data)}}onHandshake(t){this.emitReserved("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this._pingInterval=t.pingInterval,this._pingTimeout=t.pingTimeout,this._maxPayload=t.maxPayload,this.onOpen(),"closed"!==this.readyState&&this._resetPingTimeout()}_resetPingTimeout(){this.clearTimeoutFn(this._pingTimeoutTimer);const t=this._pingInterval+this._pingTimeout;this._pingTimeoutTime=Date.now()+t,this._pingTimeoutTimer=this.setTimeoutFn((()=>{this._onClose("ping timeout")}),t),this.opts.autoUnref&&this._pingTimeoutTimer.unref()}_onDrain(){this.writeBuffer.splice(0,this._prevBufferLen),this._prevBufferLen=0,0===this.writeBuffer.length?this.emitReserved("drain"):this.flush()}flush(){if("closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const t=this._getWritablePackets();this.transport.send(t),this._prevBufferLen=t.length,this.emitReserved("flush")}}_getWritablePackets(){if(!(this._maxPayload&&"polling"===this.transport.name&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let s=0;s<this.writeBuffer.length;s++){const n=this.writeBuffer[s].data;if(n&&(t+="string"==typeof(e=n)?function(t){let e=0,s=0;for(let n=0,i=t.length;n<i;n++)e=t.charCodeAt(n),e<128?s+=1:e<2048?s+=2:e<55296||e>=57344?s+=3:(n++,s+=4);return s}(e):Math.ceil(1.33*(e.byteLength||e.size))),s>0&&t>this._maxPayload)return this.writeBuffer.slice(0,s);t+=2}var e;return this.writeBuffer}_hasPingExpired(){if(!this._pingTimeoutTime)return!0;const t=Date.now()>this._pingTimeoutTime;return t&&(this._pingTimeoutTime=0,O((()=>{this._onClose("ping timeout")}),this.setTimeoutFn)),t}write(t,e,s){return this._sendPacket("message",t,e,s),this}send(t,e,s){return this._sendPacket("message",t,e,s),this}_sendPacket(t,e,s,n){if("function"==typeof e&&(n=e,e=void 0),"function"==typeof s&&(n=s,s=null),"closing"===this.readyState||"closed"===this.readyState)return;(s=s||{}).compress=!1!==s.compress;const i={type:t,data:e,options:s};this.emitReserved("packetCreate",i),this.writeBuffer.push(i),n&&this.once("flush",n),this.flush()}close(){const t=()=>{this._onClose("forced close"),this.transport.close()},e=()=>{this.off("upgrade",e),this.off("upgradeError",e),t()},s=()=>{this.once("upgrade",e),this.once("upgradeError",e)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(()=>{this.upgrading?s():t()})):this.upgrading?s():t()),this}_onError(t){if(Z.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&"opening"===this.readyState)return this.transports.shift(),this._open();this.emitReserved("error",t),this._onClose("transport error",t)}_onClose(t,e){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){if(this.clearTimeoutFn(this._pingTimeoutTimer),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),X&&(this._beforeunloadEventListener&&removeEventListener("beforeunload",this._beforeunloadEventListener,!1),this._offlineEventListener)){const t=G.indexOf(this._offlineEventListener);-1!==t&&G.splice(t,1)}this.readyState="closed",this.id=null,this.emitReserved("close",t,e),this.writeBuffer=[],this._prevBufferLen=0}}}Z.protocol=4;class tt extends Z{constructor(){super(...arguments),this._upgrades=[]}onOpen(){if(super.onOpen(),"open"===this.readyState&&this.opts.upgrade)for(let t=0;t<this._upgrades.length;t++)this._probe(this._upgrades[t])}_probe(t){let e=this.createTransport(t),s=!1;Z.priorWebsocketSuccess=!1;const n=()=>{s||(e.send([{type:"ping",data:"probe"}]),e.once("packet",(t=>{if(!s)if("pong"===t.type&&"probe"===t.data){if(this.upgrading=!0,this.emitReserved("upgrading",e),!e)return;Z.priorWebsocketSuccess="websocket"===e.name,this.transport.pause((()=>{s||"closed"!==this.readyState&&(c(),this.setTransport(e),e.send([{type:"upgrade"}]),this.emitReserved("upgrade",e),e=null,this.upgrading=!1,this.flush())}))}else{const t=new Error("probe error");t.transport=e.name,this.emitReserved("upgradeError",t)}})))};function i(){s||(s=!0,c(),e.close(),e=null)}const r=t=>{const s=new Error("probe error: "+t);s.transport=e.name,i(),this.emitReserved("upgradeError",s)};function o(){r("transport closed")}function a(){r("socket closed")}function h(t){e&&t.name!==e.name&&i()}const c=()=>{e.removeListener("open",n),e.removeListener("error",r),e.removeListener("close",o),this.off("close",a),this.off("upgrading",h)};e.once("open",n),e.once("error",r),e.once("close",o),this.once("close",a),this.once("upgrading",h),-1!==this._upgrades.indexOf("webtransport")&&"webtransport"!==t?this.setTimeoutFn((()=>{s||e.open()}),200):e.open()}onHandshake(t){this._upgrades=this._filterUpgrades(t.upgrades),super.onHandshake(t)}_filterUpgrades(t){const e=[];for(let s=0;s<t.length;s++)~this.transports.indexOf(t[s])&&e.push(t[s]);return e}}let et=class extends tt{constructor(t,e={}){const s="object"==typeof t?t:e;(!s.transports||s.transports&&"string"==typeof s.transports[0])&&(s.transports=(s.transports||["polling","websocket","webtransport"]).map((t=>z[t])).filter((t=>!!t))),super(t,s)}};const st="function"==typeof ArrayBuffer,nt=Object.prototype.toString,it="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===nt.call(Blob),rt="function"==typeof File||"undefined"!=typeof File&&"[object FileConstructor]"===nt.call(File);function ot(t){return st&&(t instanceof ArrayBuffer||(t=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t.buffer instanceof ArrayBuffer)(t))||it&&t instanceof Blob||rt&&t instanceof File}function at(t,e){if(!t||"object"!=typeof t)return!1;if(Array.isArray(t)){for(let e=0,s=t.length;e<s;e++)if(at(t[e]))return!0;return!1}if(ot(t))return!0;if(t.toJSON&&"function"==typeof t.toJSON&&1===arguments.length)return at(t.toJSON(),!0);for(const e in t)if(Object.prototype.hasOwnProperty.call(t,e)&&at(t[e]))return!0;return!1}function ht(t){const e=[],s=t.data,n=t;return n.data=ct(s,e),n.attachments=e.length,{packet:n,buffers:e}}function ct(t,e){if(!t)return t;if(ot(t)){const s={_placeholder:!0,num:e.length};return e.push(t),s}if(Array.isArray(t)){const s=new Array(t.length);for(let n=0;n<t.length;n++)s[n]=ct(t[n],e);return s}if("object"==typeof t&&!(t instanceof Date)){const s={};for(const n in t)Object.prototype.hasOwnProperty.call(t,n)&&(s[n]=ct(t[n],e));return s}return t}function ut(t,e){return t.data=pt(t.data,e),delete t.attachments,t}function pt(t,e){if(!t)return t;if(t&&!0===t._placeholder){if("number"==typeof t.num&&t.num>=0&&t.num<e.length)return e[t.num];throw new Error("illegal attachments")}if(Array.isArray(t))for(let s=0;s<t.length;s++)t[s]=pt(t[s],e);else if("object"==typeof t)for(const s in t)Object.prototype.hasOwnProperty.call(t,s)&&(t[s]=pt(t[s],e));return t}const lt=["connect","connect_error","disconnect","disconnecting","newListener","removeListener"];var dt;!function(t){t[t.CONNECT=0]="CONNECT",t[t.DISCONNECT=1]="DISCONNECT",t[t.EVENT=2]="EVENT",t[t.ACK=3]="ACK",t[t.CONNECT_ERROR=4]="CONNECT_ERROR",t[t.BINARY_EVENT=5]="BINARY_EVENT",t[t.BINARY_ACK=6]="BINARY_ACK"}(dt||(dt={}));function ft(t){return"[object Object]"===Object.prototype.toString.call(t)}class yt extends T{constructor(t){super(),this.reviver=t}add(t){let e;if("string"==typeof t){if(this.reconstructor)throw new Error("got plaintext data when reconstructing a packet");e=this.decodeString(t);const s=e.type===dt.BINARY_EVENT;s||e.type===dt.BINARY_ACK?(e.type=s?dt.EVENT:dt.ACK,this.reconstructor=new mt(e),0===e.attachments&&super.emitReserved("decoded",e)):super.emitReserved("decoded",e)}else{if(!ot(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");e=this.reconstructor.takeBinaryData(t),e&&(this.reconstructor=null,super.emitReserved("decoded",e))}}decodeString(t){let e=0;const s={type:Number(t.charAt(0))};if(void 0===dt[s.type])throw new Error("unknown packet type "+s.type);if(s.type===dt.BINARY_EVENT||s.type===dt.BINARY_ACK){const n=e+1;for(;"-"!==t.charAt(++e)&&e!=t.length;);const i=t.substring(n,e);if(i!=Number(i)||"-"!==t.charAt(e))throw new Error("Illegal attachments");s.attachments=Number(i)}if("/"===t.charAt(e+1)){const n=e+1;for(;++e;){if(","===t.charAt(e))break;if(e===t.length)break}s.nsp=t.substring(n,e)}else s.nsp="/";const n=t.charAt(e+1);if(""!==n&&Number(n)==n){const n=e+1;for(;++e;){const s=t.charAt(e);if(null==s||Number(s)!=s){--e;break}if(e===t.length)break}s.id=Number(t.substring(n,e+1))}if(t.charAt(++e)){const n=this.tryParse(t.substr(e));if(!yt.isPayloadValid(s.type,n))throw new Error("invalid payload");s.data=n}return s}tryParse(t){try{return JSON.parse(t,this.reviver)}catch(t){return!1}}static isPayloadValid(t,e){switch(t){case dt.CONNECT:return ft(e);case dt.DISCONNECT:return void 0===e;case dt.CONNECT_ERROR:return"string"==typeof e||ft(e);case dt.EVENT:case dt.BINARY_EVENT:return Array.isArray(e)&&("number"==typeof e[0]||"string"==typeof e[0]&&-1===lt.indexOf(e[0]));case dt.ACK:case dt.BINARY_ACK:return Array.isArray(e)}}destroy(){this.reconstructor&&(this.reconstructor.finishedReconstruction(),this.reconstructor=null)}}class mt{constructor(t){this.packet=t,this.buffers=[],this.reconPack=t}takeBinaryData(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){const t=ut(this.reconPack,this.buffers);return this.finishedReconstruction(),t}return null}finishedReconstruction(){this.reconPack=null,this.buffers=[]}}var gt=Object.freeze({__proto__:null,Decoder:yt,Encoder:class{constructor(t){this.replacer=t}encode(t){return t.type!==dt.EVENT&&t.type!==dt.ACK||!at(t)?[this.encodeAsString(t)]:this.encodeAsBinary({type:t.type===dt.EVENT?dt.BINARY_EVENT:dt.BINARY_ACK,nsp:t.nsp,data:t.data,id:t.id})}encodeAsString(t){let e=""+t.type;return t.type!==dt.BINARY_EVENT&&t.type!==dt.BINARY_ACK||(e+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(e+=t.nsp+","),null!=t.id&&(e+=t.id),null!=t.data&&(e+=JSON.stringify(t.data,this.replacer)),e}encodeAsBinary(t){const e=ht(t),s=this.encodeAsString(e.packet),n=e.buffers;return n.unshift(s),n}},get PacketType(){return dt},protocol:5});function _t(t,e,s){return t.on(e,s),function(){t.off(e,s)}}const bt=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1});class vt extends T{constructor(t,e,s){super(),this.connected=!1,this.recovered=!1,this.receiveBuffer=[],this.sendBuffer=[],this._queue=[],this._queueSeq=0,this.ids=0,this.acks={},this.flags={},this.io=t,this.nsp=e,s&&s.auth&&(this.auth=s.auth),this._opts=Object.assign({},s),this.io._autoConnect&&this.open()}get disconnected(){return!this.connected}subEvents(){if(this.subs)return;const t=this.io;this.subs=[_t(t,"open",this.onopen.bind(this)),_t(t,"packet",this.onpacket.bind(this)),_t(t,"error",this.onerror.bind(this)),_t(t,"close",this.onclose.bind(this))]}get active(){return!!this.subs}connect(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}open(){return this.connect()}send(...t){return t.unshift("message"),this.emit.apply(this,t),this}emit(t,...e){var s,n,i;if(bt.hasOwnProperty(t))throw new Error('"'+t.toString()+'" is a reserved event name');if(e.unshift(t),this._opts.retries&&!this.flags.fromQueue&&!this.flags.volatile)return this._addToQueue(e),this;const r={type:dt.EVENT,data:e,options:{}};if(r.options.compress=!1!==this.flags.compress,"function"==typeof e[e.length-1]){const t=this.ids++,s=e.pop();this._registerAckCallback(t,s),r.id=t}const o=null===(n=null===(s=this.io.engine)||void 0===s?void 0:s.transport)||void 0===n?void 0:n.writable,a=this.connected&&!(null===(i=this.io.engine)||void 0===i?void 0:i._hasPingExpired());return this.flags.volatile&&!o||(a?(this.notifyOutgoingListeners(r),this.packet(r)):this.sendBuffer.push(r)),this.flags={},this}_registerAckCallback(t,e){var s;const n=null!==(s=this.flags.timeout)&&void 0!==s?s:this._opts.ackTimeout;if(void 0===n)return void(this.acks[t]=e);const i=this.io.setTimeoutFn((()=>{delete this.acks[t];for(let e=0;e<this.sendBuffer.length;e++)this.sendBuffer[e].id===t&&this.sendBuffer.splice(e,1);e.call(this,new Error("operation has timed out"))}),n),r=(...t)=>{this.io.clearTimeoutFn(i),e.apply(this,t)};r.withError=!0,this.acks[t]=r}emitWithAck(t,...e){return new Promise(((s,n)=>{const i=(t,e)=>t?n(t):s(e);i.withError=!0,e.push(i),this.emit(t,...e)}))}_addToQueue(t){let e;"function"==typeof t[t.length-1]&&(e=t.pop());const s={id:this._queueSeq++,tryCount:0,pending:!1,args:t,flags:Object.assign({fromQueue:!0},this.flags)};t.push(((t,...n)=>{if(s!==this._queue[0])return;return null!==t?s.tryCount>this._opts.retries&&(this._queue.shift(),e&&e(t)):(this._queue.shift(),e&&e(null,...n)),s.pending=!1,this._drainQueue()})),this._queue.push(s),this._drainQueue()}_drainQueue(t=!1){if(!this.connected||0===this._queue.length)return;const e=this._queue[0];e.pending&&!t||(e.pending=!0,e.tryCount++,this.flags=e.flags,this.emit.apply(this,e.args))}packet(t){t.nsp=this.nsp,this.io._packet(t)}onopen(){"function"==typeof this.auth?this.auth((t=>{this._sendConnectPacket(t)})):this._sendConnectPacket(this.auth)}_sendConnectPacket(t){this.packet({type:dt.CONNECT,data:this._pid?Object.assign({pid:this._pid,offset:this._lastOffset},t):t})}onerror(t){this.connected||this.emitReserved("connect_error",t)}onclose(t,e){this.connected=!1,delete this.id,this.emitReserved("disconnect",t,e),this._clearAcks()}_clearAcks(){Object.keys(this.acks).forEach((t=>{if(!this.sendBuffer.some((e=>String(e.id)===t))){const e=this.acks[t];delete this.acks[t],e.withError&&e.call(this,new Error("socket has been disconnected"))}}))}onpacket(t){if(t.nsp===this.nsp)switch(t.type){case dt.CONNECT:t.data&&t.data.sid?this.onconnect(t.data.sid,t.data.pid):this.emitReserved("connect_error",new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));break;case dt.EVENT:case dt.BINARY_EVENT:this.onevent(t);break;case dt.ACK:case dt.BINARY_ACK:this.onack(t);break;case dt.DISCONNECT:this.ondisconnect();break;case dt.CONNECT_ERROR:this.destroy();const e=new Error(t.data.message);e.data=t.data.data,this.emitReserved("connect_error",e)}}onevent(t){const e=t.data||[];null!=t.id&&e.push(this.ack(t.id)),this.connected?this.emitEvent(e):this.receiveBuffer.push(Object.freeze(e))}emitEvent(t){if(this._anyListeners&&this._anyListeners.length){const e=this._anyListeners.slice();for(const s of e)s.apply(this,t)}super.emit.apply(this,t),this._pid&&t.length&&"string"==typeof t[t.length-1]&&(this._lastOffset=t[t.length-1])}ack(t){const e=this;let s=!1;return function(...n){s||(s=!0,e.packet({type:dt.ACK,id:t,data:n}))}}onack(t){const e=this.acks[t.id];"function"==typeof e&&(delete this.acks[t.id],e.withError&&t.data.unshift(null),e.apply(this,t.data))}onconnect(t,e){this.id=t,this.recovered=e&&this._pid===e,this._pid=e,this.connected=!0,this.emitBuffered(),this.emitReserved("connect"),this._drainQueue(!0)}emitBuffered(){this.receiveBuffer.forEach((t=>this.emitEvent(t))),this.receiveBuffer=[],this.sendBuffer.forEach((t=>{this.notifyOutgoingListeners(t),this.packet(t)})),this.sendBuffer=[]}ondisconnect(){this.destroy(),this.onclose("io server disconnect")}destroy(){this.subs&&(this.subs.forEach((t=>t())),this.subs=void 0),this.io._destroy(this)}disconnect(){return this.connected&&this.packet({type:dt.DISCONNECT}),this.destroy(),this.connected&&this.onclose("io client disconnect"),this}close(){return this.disconnect()}compress(t){return this.flags.compress=t,this}get volatile(){return this.flags.volatile=!0,this}timeout(t){return this.flags.timeout=t,this}onAny(t){return this._anyListeners=this._anyListeners||[],this._anyListeners.push(t),this}prependAny(t){return this._anyListeners=this._anyListeners||[],this._anyListeners.unshift(t),this}offAny(t){if(!this._anyListeners)return this;if(t){const e=this._anyListeners;for(let s=0;s<e.length;s++)if(t===e[s])return e.splice(s,1),this}else this._anyListeners=[];return this}listenersAny(){return this._anyListeners||[]}onAnyOutgoing(t){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.push(t),this}prependAnyOutgoing(t){return this._anyOutgoingListeners=this._anyOutgoingListeners||[],this._anyOutgoingListeners.unshift(t),this}offAnyOutgoing(t){if(!this._anyOutgoingListeners)return this;if(t){const e=this._anyOutgoingListeners;for(let s=0;s<e.length;s++)if(t===e[s])return e.splice(s,1),this}else this._anyOutgoingListeners=[];return this}listenersAnyOutgoing(){return this._anyOutgoingListeners||[]}notifyOutgoingListeners(t){if(this._anyOutgoingListeners&&this._anyOutgoingListeners.length){const e=this._anyOutgoingListeners.slice();for(const s of e)s.apply(this,t.data)}}}function wt(t){t=t||{},this.ms=t.min||100,this.max=t.max||1e4,this.factor=t.factor||2,this.jitter=t.jitter>0&&t.jitter<=1?t.jitter:0,this.attempts=0}wt.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),s=Math.floor(e*this.jitter*t);t=1&Math.floor(10*e)?t+s:t-s}return 0|Math.min(t,this.max)},wt.prototype.reset=function(){this.attempts=0},wt.prototype.setMin=function(t){this.ms=t},wt.prototype.setMax=function(t){this.max=t},wt.prototype.setJitter=function(t){this.jitter=t};class kt extends T{constructor(t,e){var s;super(),this.nsps={},this.subs=[],t&&"object"==typeof t&&(e=t,t=void 0),(e=e||{}).path=e.path||"/socket.io",this.opts=e,S(this,e),this.reconnection(!1!==e.reconnection),this.reconnectionAttempts(e.reconnectionAttempts||1/0),this.reconnectionDelay(e.reconnectionDelay||1e3),this.reconnectionDelayMax(e.reconnectionDelayMax||5e3),this.randomizationFactor(null!==(s=e.randomizationFactor)&&void 0!==s?s:.5),this.backoff=new wt({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()}),this.timeout(null==e.timeout?2e4:e.timeout),this._readyState="closed",this.uri=t;const n=e.parser||gt;this.encoder=new n.Encoder,this.decoder=new n.Decoder,this._autoConnect=!1!==e.autoConnect,this._autoConnect&&this.open()}reconnection(t){return arguments.length?(this._reconnection=!!t,t||(this.skipReconnect=!0),this):this._reconnection}reconnectionAttempts(t){return void 0===t?this._reconnectionAttempts:(this._reconnectionAttempts=t,this)}reconnectionDelay(t){var e;return void 0===t?this._reconnectionDelay:(this._reconnectionDelay=t,null===(e=this.backoff)||void 0===e||e.setMin(t),this)}randomizationFactor(t){var e;return void 0===t?this._randomizationFactor:(this._randomizationFactor=t,null===(e=this.backoff)||void 0===e||e.setJitter(t),this)}reconnectionDelayMax(t){var e;return void 0===t?this._reconnectionDelayMax:(this._reconnectionDelayMax=t,null===(e=this.backoff)||void 0===e||e.setMax(t),this)}timeout(t){return arguments.length?(this._timeout=t,this):this._timeout}maybeReconnectOnOpen(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}open(t){if(~this._readyState.indexOf("open"))return this;this.engine=new et(this.uri,this.opts);const e=this.engine,s=this;this._readyState="opening",this.skipReconnect=!1;const n=_t(e,"open",(function(){s.onopen(),t&&t()})),i=e=>{this.cleanup(),this._readyState="closed",this.emitReserved("error",e),t?t(e):this.maybeReconnectOnOpen()},r=_t(e,"error",i);if(!1!==this._timeout){const t=this._timeout,s=this.setTimeoutFn((()=>{n(),i(new Error("timeout")),e.close()}),t);this.opts.autoUnref&&s.unref(),this.subs.push((()=>{this.clearTimeoutFn(s)}))}return this.subs.push(n),this.subs.push(r),this}connect(t){return this.open(t)}onopen(){this.cleanup(),this._readyState="open",this.emitReserved("open");const t=this.engine;this.subs.push(_t(t,"ping",this.onping.bind(this)),_t(t,"data",this.ondata.bind(this)),_t(t,"error",this.onerror.bind(this)),_t(t,"close",this.onclose.bind(this)),_t(this.decoder,"decoded",this.ondecoded.bind(this)))}onping(){this.emitReserved("ping")}ondata(t){try{this.decoder.add(t)}catch(t){this.onclose("parse error",t)}}ondecoded(t){O((()=>{this.emitReserved("packet",t)}),this.setTimeoutFn)}onerror(t){this.emitReserved("error",t)}socket(t,e){let s=this.nsps[t];return s?this._autoConnect&&!s.active&&s.connect():(s=new vt(this,t,e),this.nsps[t]=s),s}_destroy(t){const e=Object.keys(this.nsps);for(const t of e){if(this.nsps[t].active)return}this._close()}_packet(t){const e=this.encoder.encode(t);for(let s=0;s<e.length;s++)this.engine.write(e[s],t.options)}cleanup(){this.subs.forEach((t=>t())),this.subs.length=0,this.decoder.destroy()}_close(){this.skipReconnect=!0,this._reconnecting=!1,this.onclose("forced close")}disconnect(){return this._close()}onclose(t,e){var s;this.cleanup(),null===(s=this.engine)||void 0===s||s.close(),this.backoff.reset(),this._readyState="closed",this.emitReserved("close",t,e),this._reconnection&&!this.skipReconnect&&this.reconnect()}reconnect(){if(this._reconnecting||this.skipReconnect)return this;const t=this;if(this.backoff.attempts>=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{const e=this.backoff.duration();this._reconnecting=!0;const s=this.setTimeoutFn((()=>{t.skipReconnect||(this.emitReserved("reconnect_attempt",t.backoff.attempts),t.skipReconnect||t.open((e=>{e?(t._reconnecting=!1,t.reconnect(),this.emitReserved("reconnect_error",e)):t.onreconnect()})))}),e);this.opts.autoUnref&&s.unref(),this.subs.push((()=>{this.clearTimeoutFn(s)}))}}onreconnect(){const t=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",t)}}const Et={};function At(t,e){"object"==typeof t&&(e=t,t=void 0);const s=function(t,e="",s){let n=t;s=s||"undefined"!=typeof location&&location,null==t&&(t=s.protocol+"//"+s.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?s.protocol+t:s.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==s?s.protocol+"//"+t:"https://"+t),n=Q(t)),n.port||(/^(http|ws)$/.test(n.protocol)?n.port="80":/^(http|ws)s$/.test(n.protocol)&&(n.port="443")),n.path=n.path||"/";const i=-1!==n.host.indexOf(":")?"["+n.host+"]":n.host;return n.id=n.protocol+"://"+i+":"+n.port+e,n.href=n.protocol+"://"+i+(s&&s.port===n.port?"":":"+n.port),n}(t,(e=e||{}).path||"/socket.io"),n=s.source,i=s.id,r=s.path,o=Et[i]&&r in Et[i].nsps;let a;return e.forceNew||e["force new connection"]||!1===e.multiplex||o?a=new kt(n,e):(Et[i]||(Et[i]=new kt(n,e)),a=Et[i]),s.query&&!e.query&&(e.query=s.queryKey),a.socket(s.path,e)}Object.assign(At,{Manager:kt,Socket:vt,io:At,connect:At});const Tt="x-api-token";class Ot extends EventTarget{get connected(){var t,e;return null!==(e=null===(t=this.socket)||void 0===t?void 0:t.connected)&&void 0!==e&&e}constructor(t,e={}){super(),this.token=t,this.socket=null,this.retryTimeout=null,this.retryAttempt=0,this.options={...Ot.defaultOptions,...e}}close(){this.cancelRetry(),this.closeSocket()}listen(t,e,s){var n,i;if(this.cancelRetry(),this.closeSocket(),0===t.length)throw new Error("At least one event type must be provided");this.socket=At(`https://realtime.jsonpad.io?${new URLSearchParams({eventTypes:t.join(","),listIds:null!==(n=null==e?void 0:e.join(","))&&void 0!==n?n:"",itemIds:null!==(i=null==s?void 0:s.join(","))&&void 0!==i?i:""})}`,{extraHeaders:{[Tt]:this.token}}),this.socket.on("connect",this.handleConnected.bind(this)),this.socket.on("connect_error",this.handleConnectError.bind(this)),this.socket.on("disconnect",this.handleDisconnected.bind(this)),this.socket.on("error",this.handleError.bind(this)),this.socket.on("list-created",this.handleEvent.bind(this,"list-created")),this.socket.on("list-updated",this.handleEvent.bind(this,"list-updated")),this.socket.on("list-deleted",this.handleEvent.bind(this,"list-deleted")),this.socket.on("item-created",this.handleEvent.bind(this,"item-created")),this.socket.on("item-updated",this.handleEvent.bind(this,"item-updated")),this.socket.on("item-restored",this.handleEvent.bind(this,"item-restored")),this.socket.on("item-deleted",this.handleEvent.bind(this,"item-deleted"))}closeSocket(){this.socket&&(this.socket.removeAllListeners(),this.socket.close(),this.socket=null)}handleConnected(){this.retryAttempt=0,this.dispatchEvent(Ot.connected)}handleDisconnected(){this.dispatchEvent(Ot.disconnected)}handleConnectError(t){var e,s,i;if(null===(e=this.socket)||void 0===e?void 0:e.active)return;const r=null!==(i=null===(s=t.data)||void 0===s?void 0:s.retryAfter)&&void 0!==i?i:null,o=this.options.retry&&null!==r?this.retryDelay(r):null;this.dispatchEvent(new n(t.message,t.data,o)),null!==o&&(this.retryAttempt++,this.retryTimeout=setTimeout((()=>{var t;this.retryTimeout=null,null===(t=this.socket)||void 0===t||t.connect()}),1e3*o))}retryDelay(t){const e=Math.min(t*Math.pow(2,this.retryAttempt),300);return Math.round(e*(1+.2*Math.random()))}cancelRetry(){null!==this.retryTimeout&&(clearTimeout(this.retryTimeout),this.retryTimeout=null),this.retryAttempt=0}handleError(t){this.dispatchEvent(new n(t))}handleEvent(t,n){let i;try{i=JSON.parse(n)}catch(t){return void this.handleError("Failed to parse data")}switch(t){case"list-created":this.dispatchEvent(new s("list-created",i));break;case"list-updated":this.dispatchEvent(new s("list-updated",i));break;case"list-deleted":this.dispatchEvent(new s("list-deleted",i));break;case"item-created":this.dispatchEvent(new e("item-created",i));break;case"item-updated":this.dispatchEvent(new e("item-updated",i));break;case"item-restored":this.dispatchEvent(new e("item-restored",i));break;case"item-deleted":this.dispatchEvent(new e("item-deleted",i))}}}Ot.connected=new Event("connected"),Ot.disconnected=new Event("disconnected"),Ot.defaultOptions={retry:!0},t.ItemEvent=e,t.ListEvent=s,t.RealtimeErrorEvent=n,t.default=Ot,Object.defineProperty(t,"__esModule",{value:!0})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basementuniverse/jsonpad-realtime-sdk",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "JSONPad Realtime SDK for Node and browser",
5
5
  "author": "Gordon Larrigan <gordonlarrigan@gmail.com> (https://gordonlarrigan.com)",
6
6
  "license": "MIT",
package/src/constants.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  export const API_URL = 'https://realtime.jsonpad.io';
2
2
  export const API_TOKEN_HEADER = 'x-api-token';
3
+
4
+ // The longest we'll wait before retrying a connection the server refused, in
5
+ // seconds, and how much random jitter to add to each wait
6
+ export const MAX_RETRY_DELAY = 300;
7
+ export const RETRY_JITTER = 0.2;
@@ -1,2 +1,3 @@
1
1
  export * from './item-event';
2
2
  export * from './list-event';
3
+ export * from './realtime-error-event';
@@ -0,0 +1,54 @@
1
+ import { RealtimeErrorData } from '../types';
2
+
3
+ /**
4
+ * Dispatched as an 'error' event when the server refuses a connection, or when
5
+ * an event arrives that can't be parsed
6
+ *
7
+ * The detail is the error message, as it was before this class existed, so
8
+ * that anything already listening for 'error' keeps working
9
+ */
10
+ export class RealtimeErrorEvent extends CustomEvent<string> {
11
+ /**
12
+ * The jsonpad error code, e.g. 10015, or null if the error didn't come from
13
+ * the server
14
+ */
15
+ public readonly code: number | null;
16
+
17
+ /**
18
+ * The jsonpad error name, e.g. 'REALTIME_CONNECTION_LIMIT_EXCEEDED', or null
19
+ * if the error didn't come from the server
20
+ */
21
+ public readonly errorName: string | null;
22
+
23
+ /**
24
+ * How many concurrent connections the account's plan allows, if the
25
+ * connection was refused for holding too many of them
26
+ */
27
+ public readonly max: number | null;
28
+
29
+ /**
30
+ * How many seconds the server asked us to wait before trying again, or null
31
+ * if it didn't
32
+ */
33
+ public readonly retryAfter: number | null;
34
+
35
+ /**
36
+ * How many seconds until this client retries the connection, or null if it
37
+ * won't retry
38
+ */
39
+ public readonly retryIn: number | null;
40
+
41
+ public constructor(
42
+ message: string,
43
+ data?: RealtimeErrorData,
44
+ retryIn: number | null = null
45
+ ) {
46
+ super('error', { detail: message });
47
+
48
+ this.code = data?.code ?? null;
49
+ this.errorName = data?.name ?? null;
50
+ this.max = data?.max ?? null;
51
+ this.retryAfter = data?.retryAfter ?? null;
52
+ this.retryIn = retryIn;
53
+ }
54
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,14 @@
1
- import { ItemEvent, ListEvent } from './events';
1
+ import { ItemEvent, ListEvent, RealtimeErrorEvent } from './events';
2
2
  import { JSONPadRealtime } from './jsonpad-realtime';
3
- import { EventDetail, EventType, Item, List, MessageType } from './types';
3
+ import {
4
+ EventDetail,
5
+ EventType,
6
+ Item,
7
+ JSONPadRealtimeOptions,
8
+ List,
9
+ MessageType,
10
+ RealtimeErrorData,
11
+ } from './types';
4
12
 
5
13
  export default JSONPadRealtime;
6
14
  export {
@@ -8,7 +16,10 @@ export {
8
16
  EventType,
9
17
  Item,
10
18
  ItemEvent,
19
+ JSONPadRealtimeOptions,
11
20
  List,
12
21
  ListEvent,
13
22
  MessageType,
23
+ RealtimeErrorData,
24
+ RealtimeErrorEvent,
14
25
  };
@@ -2,14 +2,33 @@ import { io, Socket } from 'socket.io-client';
2
2
  import * as constants from './constants';
3
3
  import { ItemEvent } from './events/item-event';
4
4
  import { ListEvent } from './events/list-event';
5
- import { EventDetail, EventType, Item, List, MessageType } from './types';
5
+ import { RealtimeErrorEvent } from './events/realtime-error-event';
6
+ import {
7
+ EventDetail,
8
+ EventType,
9
+ Item,
10
+ JSONPadRealtimeOptions,
11
+ List,
12
+ MessageType,
13
+ RealtimeErrorData,
14
+ } from './types';
6
15
 
7
16
  export class JSONPadRealtime extends EventTarget {
8
17
  private static readonly connected = new Event('connected');
9
18
  private static readonly disconnected = new Event('disconnected');
10
19
 
20
+ private static readonly defaultOptions: Required<JSONPadRealtimeOptions> = {
21
+ retry: true,
22
+ };
23
+
24
+ private readonly options: Required<JSONPadRealtimeOptions>;
25
+
11
26
  private socket: Socket | null = null;
12
27
 
28
+ private retryTimeout: ReturnType<typeof setTimeout> | null = null;
29
+
30
+ private retryAttempt: number = 0;
31
+
13
32
  public get connected() {
14
33
  return this.socket?.connected ?? false;
15
34
  }
@@ -17,18 +36,21 @@ export class JSONPadRealtime extends EventTarget {
17
36
  /**
18
37
  * Create a new JSONPad realtime client instance
19
38
  */
20
- public constructor(private token: string) {
39
+ public constructor(
40
+ private token: string,
41
+ options: JSONPadRealtimeOptions = {}
42
+ ) {
21
43
  super();
44
+
45
+ this.options = { ...JSONPadRealtime.defaultOptions, ...options };
22
46
  }
23
47
 
24
48
  /**
25
49
  * Close the connection to the realtime server
26
50
  */
27
51
  public close() {
28
- if (this.socket && this.socket.connected) {
29
- this.socket.removeAllListeners();
30
- this.socket.close();
31
- }
52
+ this.cancelRetry();
53
+ this.closeSocket();
32
54
  }
33
55
 
34
56
  /**
@@ -39,10 +61,8 @@ export class JSONPadRealtime extends EventTarget {
39
61
  listIds?: string[],
40
62
  itemIds?: string[]
41
63
  ) {
42
- if (this.socket && this.socket.connected) {
43
- this.socket.removeAllListeners();
44
- this.socket.close();
45
- }
64
+ this.cancelRetry();
65
+ this.closeSocket();
46
66
 
47
67
  if (eventTypes.length === 0) {
48
68
  throw new Error('At least one event type must be provided');
@@ -62,6 +82,7 @@ export class JSONPadRealtime extends EventTarget {
62
82
  );
63
83
 
64
84
  this.socket.on('connect', this.handleConnected.bind(this));
85
+ this.socket.on('connect_error', this.handleConnectError.bind(this));
65
86
  this.socket.on('disconnect', this.handleDisconnected.bind(this));
66
87
  this.socket.on('error', this.handleError.bind(this));
67
88
  this.socket.on('list-created', this.handleEvent.bind(this, 'list-created'));
@@ -76,7 +97,23 @@ export class JSONPadRealtime extends EventTarget {
76
97
  this.socket.on('item-deleted', this.handleEvent.bind(this, 'item-deleted'));
77
98
  }
78
99
 
100
+ /**
101
+ * Close the current socket, if there is one
102
+ *
103
+ * A socket that is still connecting, or that is reconnecting in the
104
+ * background, is holding a connection slot on the server just like a
105
+ * connected one, so it gets closed too
106
+ */
107
+ private closeSocket() {
108
+ if (this.socket) {
109
+ this.socket.removeAllListeners();
110
+ this.socket.close();
111
+ this.socket = null;
112
+ }
113
+ }
114
+
79
115
  private handleConnected() {
116
+ this.retryAttempt = 0;
80
117
  this.dispatchEvent(JSONPadRealtime.connected);
81
118
  }
82
119
 
@@ -84,8 +121,62 @@ export class JSONPadRealtime extends EventTarget {
84
121
  this.dispatchEvent(JSONPadRealtime.disconnected);
85
122
  }
86
123
 
124
+ /**
125
+ * A connection attempt failed
126
+ *
127
+ * Socket.IO retries by itself when it couldn't reach the server, and leaves
128
+ * the socket inactive when the server refused the connection instead. A
129
+ * refusal for holding too many concurrent connections is worth retrying,
130
+ * since a connection the client has already lost is released once the
131
+ * server notices it has gone.
132
+ */
133
+ private handleConnectError(error: Error & { data?: RealtimeErrorData }) {
134
+ if (this.socket?.active) {
135
+ return;
136
+ }
137
+
138
+ const retryAfter = error.data?.retryAfter ?? null;
139
+ const retryIn =
140
+ this.options.retry && retryAfter !== null
141
+ ? this.retryDelay(retryAfter)
142
+ : null;
143
+
144
+ this.dispatchEvent(new RealtimeErrorEvent(error.message, error.data, retryIn));
145
+
146
+ if (retryIn !== null) {
147
+ this.retryAttempt++;
148
+ this.retryTimeout = setTimeout(() => {
149
+ this.retryTimeout = null;
150
+ this.socket?.connect();
151
+ }, retryIn * 1000);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Back off exponentially from the delay the server asked for, with a little
157
+ * jitter so that clients refused at the same time don't all come back at the
158
+ * same time
159
+ */
160
+ private retryDelay(retryAfter: number): number {
161
+ const delay = Math.min(
162
+ retryAfter * Math.pow(2, this.retryAttempt),
163
+ constants.MAX_RETRY_DELAY
164
+ );
165
+
166
+ return Math.round(delay * (1 + Math.random() * constants.RETRY_JITTER));
167
+ }
168
+
169
+ private cancelRetry() {
170
+ if (this.retryTimeout !== null) {
171
+ clearTimeout(this.retryTimeout);
172
+ this.retryTimeout = null;
173
+ }
174
+
175
+ this.retryAttempt = 0;
176
+ }
177
+
87
178
  private handleError(message: string) {
88
- this.dispatchEvent(new CustomEvent('error', { detail: message }));
179
+ this.dispatchEvent(new RealtimeErrorEvent(message));
89
180
  }
90
181
 
91
182
  private handleEvent(messageType: MessageType, data: string) {
@@ -1,5 +1,7 @@
1
1
  export * from './event-detail';
2
2
  export * from './event-type';
3
3
  export * from './item';
4
+ export * from './jsonpad-realtime-options';
4
5
  export * from './list';
5
6
  export * from './message-type';
7
+ export * from './realtime-error-data';
@@ -0,0 +1,15 @@
1
+ export type JSONPadRealtimeOptions = {
2
+ /**
3
+ * Retry a connection that the server refused because the account is already
4
+ * holding as many concurrent connections as its plan allows
5
+ *
6
+ * A connection lost to a network problem keeps its slot on the server until
7
+ * the server notices it has gone, which takes up to 45 seconds. A client
8
+ * that reconnects immediately can be refused for a limit it isn't really
9
+ * over, so retrying with a backoff gets it back on once the old connection
10
+ * has been cleaned up.
11
+ *
12
+ * Defaults to true.
13
+ */
14
+ retry?: boolean;
15
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The data attached to the error the server sends when it refuses a connection
3
+ *
4
+ * The name, code and message are the same ones the API returns in the body of
5
+ * an error response
6
+ */
7
+ export type RealtimeErrorData = {
8
+ name: string;
9
+ code: number;
10
+ message: string;
11
+
12
+ /**
13
+ * How many concurrent connections the account's plan allows, present when
14
+ * the connection was refused for holding too many of them
15
+ */
16
+ max?: number;
17
+
18
+ /**
19
+ * How many seconds to wait before trying again, present when trying again
20
+ * is worthwhile
21
+ */
22
+ retryAfter?: number;
23
+ };