0&&(u=n[0]._offset);for(var c,h=0,f=0,l=0,p=e.length;l=65536?(i-=65536,r+=String.fromCharCode(55296+(i>>>10),56320+(1023&i))):r+=String.fromCharCode(i)}else r+=String.fromCharCode((15&a)<<12|(63&t.getUint8(++o))<<6|(63&t.getUint8(++o))<<0);else r+=String.fromCharCode((31&a)<<6|63&t.getUint8(++o));else r+=String.fromCharCode(a)}return r}(this._view,this._offset,t);return this._offset+=t,e},Et.prototype._bin=function(t){var e=this._buffer.slice(this._offset,this._offset+t);return this._offset+=t,e},Et.prototype._parse=function(){var t,e=this._view.getUint8(this._offset++),n=0,r=0,i=0,o=0;if(e<192)return e<128?e:e<144?this._map(15&e):e<160?this._array(15&e):this._str(31&e);if(e>223)return-1*(255-e+1);switch(e){case 192:return null;case 194:return!1;case 195:return!0;case 196:return n=this._view.getUint8(this._offset),this._offset+=1,this._bin(n);case 197:return n=this._view.getUint16(this._offset),this._offset+=2,this._bin(n);case 198:return n=this._view.getUint32(this._offset),this._offset+=4,this._bin(n);case 199:return n=this._view.getUint8(this._offset),r=this._view.getInt8(this._offset+1),this._offset+=2,[r,this._bin(n)];case 200:return n=this._view.getUint16(this._offset),r=this._view.getInt8(this._offset+2),this._offset+=3,[r,this._bin(n)];case 201:return n=this._view.getUint32(this._offset),r=this._view.getInt8(this._offset+4),this._offset+=5,[r,this._bin(n)];case 202:return t=this._view.getFloat32(this._offset),this._offset+=4,t;case 203:return t=this._view.getFloat64(this._offset),this._offset+=8,t;case 204:return t=this._view.getUint8(this._offset),this._offset+=1,t;case 205:return t=this._view.getUint16(this._offset),this._offset+=2,t;case 206:return t=this._view.getUint32(this._offset),this._offset+=4,t;case 207:return i=this._view.getUint32(this._offset)*Math.pow(2,32),o=this._view.getUint32(this._offset+4),this._offset+=8,i+o;case 208:return t=this._view.getInt8(this._offset),this._offset+=1,t;case 209:return t=this._view.getInt16(this._offset),this._offset+=2,t;case 210:return t=this._view.getInt32(this._offset),this._offset+=4,t;case 211:return i=this._view.getInt32(this._offset)*Math.pow(2,32),o=this._view.getUint32(this._offset+4),this._offset+=8,i+o;case 212:return r=this._view.getInt8(this._offset),this._offset+=1,0===r?void(this._offset+=1):[r,this._bin(1)];case 213:return r=this._view.getInt8(this._offset),this._offset+=1,[r,this._bin(2)];case 214:return r=this._view.getInt8(this._offset),this._offset+=1,[r,this._bin(4)];case 215:return r=this._view.getInt8(this._offset),this._offset+=1,0===r?(i=this._view.getInt32(this._offset)*Math.pow(2,32),o=this._view.getUint32(this._offset+4),this._offset+=8,new Date(i+o)):[r,this._bin(8)];case 216:return r=this._view.getInt8(this._offset),this._offset+=1,[r,this._bin(16)];case 217:return n=this._view.getUint8(this._offset),this._offset+=1,this._str(n);case 218:return n=this._view.getUint16(this._offset),this._offset+=2,this._str(n);case 219:return n=this._view.getUint32(this._offset),this._offset+=4,this._str(n);case 220:return n=this._view.getUint16(this._offset),this._offset+=2,this._array(n);case 221:return n=this._view.getUint32(this._offset),this._offset+=4,this._array(n);case 222:return n=this._view.getUint16(this._offset),this._offset+=2,this._map(n);case 223:return n=this._view.getUint32(this._offset),this._offset+=4,this._map(n)}throw new Error("Could not parse")};var Tt=function(t){var e=new Et(t),n=e._parse();if(e._offset!==t.byteLength)throw new Error(t.byteLength-e._offset+" trailing bytes");return n};bt.encode=Ot,bt.decode=Tt;var Ct,At={exports:{}};!function(t){function e(t){if(t)return function(t){for(var n in e.prototype)t[n]=e.prototype[n];return t}(t)}t.exports=e,e.prototype.on=e.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},e.prototype.once=function(t,e){function n(){this.off(t,n),e.apply(this,arguments)}return n.fn=e,this.on(t,n),this},e.prototype.off=e.prototype.removeListener=e.prototype.removeAllListeners=e.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n,r=this._callbacks["$"+t];if(!r)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var i=0;i=Ut.CONNECT&&t.type<=Ut.CONNECT_ERROR))throw new Error("invalid packet type");if(!Bt(t.nsp))throw new Error("invalid namespace");if(!function(t){switch(t.type){case Ut.CONNECT:return void 0===t.data||qt(t.data);case Ut.DISCONNECT:return void 0===t.data;case Ut.CONNECT_ERROR:return Bt(t.data)||qt(t.data);default:return Array.isArray(t.data)}}(t))throw new Error("invalid payload");if(!(void 0===t.id||xt(t.id)))throw new Error("invalid packet id")},jt.prototype.destroy=function(){};var Dt=_t.Encoder=Pt,Nt=_t.Decoder=jt,Mt=t({__proto__:null,default:_t,protocol:Lt,get PacketType(){return Ct},Encoder:Dt,Decoder:Nt},[_t]);function It(t,e,n){return t.on(e,n),function(){t.off(e,n)}}var Ft=Object.freeze({connect:1,connect_error:1,disconnect:1,disconnecting:1,newListener:1,removeListener:1}),Ht=function(t){s(r,t);var e=p(r);function r(t,i,s){var a;return n(this,r),(a=e.call(this)).connected=!1,a.recovered=!1,a.receiveBuffer=[],a.sendBuffer=[],a._queue=[],a._queueSeq=0,a.ids=0,a.acks={},a.flags={},a.io=t,a.nsp=i,s&&s.auth&&(a.auth=s.auth),a._opts=o({},s),a.io._autoConnect&&a.open(),a}return i(r,[{key:"disconnected",get:function(){return!this.connected}},{key:"subEvents",value:function(){if(!this.subs){var t=this.io;this.subs=[It(t,"open",this.onopen.bind(this)),It(t,"packet",this.onpacket.bind(this)),It(t,"error",this.onerror.bind(this)),It(t,"close",this.onclose.bind(this))]}}},{key:"active",get:function(){return!!this.subs}},{key:"connect",value:function(){return this.connected||(this.subEvents(),this.io._reconnecting||this.io.open(),"open"===this.io._readyState&&this.onopen()),this}},{key:"open",value:function(){return this.connect()}},{key:"send",value:function(){for(var t=arguments.length,e=new Array(t),n=0;n1?e-1:0),r=1;r1?n-1:0),i=1;in._opts.retries&&(n._queue.shift(),e&&e(t));else if(n._queue.shift(),e){for(var i=arguments.length,o=new Array(i>1?i-1:0),s=1;s0&&void 0!==arguments[0]&&arguments[0];if(this.connected&&0!==this._queue.length){var e=this._queue[0];e.pending&&!t||(e.pending=!0,e.tryCount++,this.flags=e.flags,this.emit.apply(this,e.args))}}},{key:"packet",value:function(t){t.nsp=this.nsp,this.io._packet(t)}},{key:"onopen",value:function(){var t=this;"function"==typeof this.auth?this.auth((function(e){t._sendConnectPacket(e)})):this._sendConnectPacket(this.auth)}},{key:"_sendConnectPacket",value:function(t){this.packet({type:Ct.CONNECT,data:this._pid?o({pid:this._pid,offset:this._lastOffset},t):t})}},{key:"onerror",value:function(t){this.connected||this.emitReserved("connect_error",t)}},{key:"onclose",value:function(t,e){this.connected=!1,delete this.id,this.emitReserved("disconnect",t,e)}},{key:"onpacket",value:function(t){if(t.nsp===this.nsp)switch(t.type){case Ct.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 Ct.EVENT:case Ct.BINARY_EVENT:this.onevent(t);break;case Ct.ACK:case Ct.BINARY_ACK:this.onack(t);break;case Ct.DISCONNECT:this.ondisconnect();break;case Ct.CONNECT_ERROR:this.destroy();var e=new Error(t.data.message);e.data=t.data.data,this.emitReserved("connect_error",e)}}},{key:"onevent",value:function(t){var e=t.data||[];null!=t.id&&e.push(this.ack(t.id)),this.connected?this.emitEvent(e):this.receiveBuffer.push(Object.freeze(e))}},{key:"emitEvent",value:function(t){if(this._anyListeners&&this._anyListeners.length){var e,n=v(this._anyListeners.slice());try{for(n.s();!(e=n.n()).done;){e.value.apply(this,t)}}catch(t){n.e(t)}finally{n.f()}}d(a(r.prototype),"emit",this).apply(this,t),this._pid&&t.length&&"string"==typeof t[t.length-1]&&(this._lastOffset=t[t.length-1])}},{key:"ack",value:function(t){var e=this,n=!1;return function(){if(!n){n=!0;for(var r=arguments.length,i=new Array(r),o=0;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}$t.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),n=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-n:t+n}return 0|Math.min(t,this.max)},$t.prototype.reset=function(){this.attempts=0},$t.prototype.setMin=function(t){this.ms=t},$t.prototype.setMax=function(t){this.max=t},$t.prototype.setJitter=function(t){this.jitter=t};var Vt=function(t){s(o,t);var r=p(o);function o(t,i){var s,a;n(this,o),(s=r.call(this)).nsps={},s.subs=[],t&&"object"===e(t)&&(i=t,t=void 0),(i=i||{}).path=i.path||"/socket.io",s.opts=i,V(l(s),i),s.reconnection(!1!==i.reconnection),s.reconnectionAttempts(i.reconnectionAttempts||1/0),s.reconnectionDelay(i.reconnectionDelay||1e3),s.reconnectionDelayMax(i.reconnectionDelayMax||5e3),s.randomizationFactor(null!==(a=i.randomizationFactor)&&void 0!==a?a:.5),s.backoff=new $t({min:s.reconnectionDelay(),max:s.reconnectionDelayMax(),jitter:s.randomizationFactor()}),s.timeout(null==i.timeout?2e4:i.timeout),s._readyState="closed",s.uri=t;var u=i.parser||Mt;return s.encoder=new u.Encoder,s.decoder=new u.Decoder,s._autoConnect=!1!==i.autoConnect,s._autoConnect&&s.open(),s}return i(o,[{key:"reconnection",value:function(t){return arguments.length?(this._reconnection=!!t,this):this._reconnection}},{key:"reconnectionAttempts",value:function(t){return void 0===t?this._reconnectionAttempts:(this._reconnectionAttempts=t,this)}},{key:"reconnectionDelay",value:function(t){var e;return void 0===t?this._reconnectionDelay:(this._reconnectionDelay=t,null===(e=this.backoff)||void 0===e||e.setMin(t),this)}},{key:"randomizationFactor",value:function(t){var e;return void 0===t?this._randomizationFactor:(this._randomizationFactor=t,null===(e=this.backoff)||void 0===e||e.setJitter(t),this)}},{key:"reconnectionDelayMax",value:function(t){var e;return void 0===t?this._reconnectionDelayMax:(this._reconnectionDelayMax=t,null===(e=this.backoff)||void 0===e||e.setMax(t),this)}},{key:"timeout",value:function(t){return arguments.length?(this._timeout=t,this):this._timeout}},{key:"maybeReconnectOnOpen",value:function(){!this._reconnecting&&this._reconnection&&0===this.backoff.attempts&&this.reconnect()}},{key:"open",value:function(t){var e=this;if(~this._readyState.indexOf("open"))return this;this.engine=new mt(this.uri,this.opts);var n=this.engine,r=this;this._readyState="opening",this.skipReconnect=!1;var i=It(n,"open",(function(){r.onopen(),t&&t()})),o=function(n){e.cleanup(),e._readyState="closed",e.emitReserved("error",n),t?t(n):e.maybeReconnectOnOpen()},s=It(n,"error",o);if(!1!==this._timeout){var a=this._timeout,u=this.setTimeoutFn((function(){i(),o(new Error("timeout")),n.close()}),a);this.opts.autoUnref&&u.unref(),this.subs.push((function(){e.clearTimeoutFn(u)}))}return this.subs.push(i),this.subs.push(s),this}},{key:"connect",value:function(t){return this.open(t)}},{key:"onopen",value:function(){this.cleanup(),this._readyState="open",this.emitReserved("open");var t=this.engine;this.subs.push(It(t,"ping",this.onping.bind(this)),It(t,"data",this.ondata.bind(this)),It(t,"error",this.onerror.bind(this)),It(t,"close",this.onclose.bind(this)),It(this.decoder,"decoded",this.ondecoded.bind(this)))}},{key:"onping",value:function(){this.emitReserved("ping")}},{key:"ondata",value:function(t){try{this.decoder.add(t)}catch(t){this.onclose("parse error",t)}}},{key:"ondecoded",value:function(t){var e=this;ct((function(){e.emitReserved("packet",t)}),this.setTimeoutFn)}},{key:"onerror",value:function(t){this.emitReserved("error",t)}},{key:"socket",value:function(t,e){var n=this.nsps[t];return n?this._autoConnect&&!n.active&&n.connect():(n=new Ht(this,t,e),this.nsps[t]=n),n}},{key:"_destroy",value:function(t){for(var e=0,n=Object.keys(this.nsps);e=this._reconnectionAttempts)this.backoff.reset(),this.emitReserved("reconnect_failed"),this._reconnecting=!1;else{var n=this.backoff.duration();this._reconnecting=!0;var r=this.setTimeoutFn((function(){e.skipReconnect||(t.emitReserved("reconnect_attempt",e.backoff.attempts),e.skipReconnect||e.open((function(n){n?(e._reconnecting=!1,e.reconnect(),t.emitReserved("reconnect_error",n)):e.onreconnect()})))}),n);this.opts.autoUnref&&r.unref(),this.subs.push((function(){t.clearTimeoutFn(r)}))}}},{key:"onreconnect",value:function(){var t=this.backoff.attempts;this._reconnecting=!1,this.backoff.reset(),this.emitReserved("reconnect",t)}}]),o}(M),Wt={};function zt(t,n){"object"===e(t)&&(n=t,t=void 0);var r,i=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",n=arguments.length>2?arguments[2]:void 0,r=t;n=n||"undefined"!=typeof location&&location,null==t&&(t=n.protocol+"//"+n.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?n.protocol+t:n.host+t),/^(https?|wss?):\/\//.test(t)||(t=void 0!==n?n.protocol+"//"+t:"https://"+t),r=gt(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var i=-1!==r.host.indexOf(":")?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+i+":"+r.port+e,r.href=r.protocol+"://"+i+(n&&n.port===r.port?"":":"+r.port),r}(t,(n=n||{}).path||"/socket.io"),o=i.source,s=i.id,a=i.path,u=Wt[s]&&a in Wt[s].nsps;return n.forceNew||n["force new connection"]||!1===n.multiplex||u?r=new Vt(o,n):(Wt[s]||(Wt[s]=new Vt(o,n)),r=Wt[s]),i.query&&!n.query&&(n.query=i.queryKey),r.socket(i.path,n)}return o(zt,{Manager:Vt,Socket:Ht,io:zt,connect:zt}),zt}));
+//# sourceMappingURL=socket.io.msgpack.min.js.map
diff --git a/packages/puter-js/src/lib/socket.io/socket.io.msgpack.min.js.map b/packages/puter-js/src/lib/socket.io/socket.io.msgpack.min.js.map
new file mode 100644
index 000000000..0244c60ca
--- /dev/null
+++ b/packages/puter-js/src/lib/socket.io/socket.io.msgpack.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"socket.io.msgpack.min.js","sources":["../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/commons.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/encodePacket.browser.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/contrib/base64-arraybuffer.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/index.js","../node_modules/engine.io-client/node_modules/engine.io-parser/build/esm/decodePacket.browser.js","../node_modules/@socket.io/component-emitter/index.mjs","../node_modules/engine.io-client/build/esm/globalThis.browser.js","../node_modules/engine.io-client/build/esm/util.js","../node_modules/engine.io-client/build/esm/transport.js","../node_modules/engine.io-client/build/esm/contrib/yeast.js","../node_modules/engine.io-client/build/esm/contrib/parseqs.js","../node_modules/engine.io-client/build/esm/contrib/has-cors.js","../node_modules/engine.io-client/build/esm/transports/xmlhttprequest.browser.js","../node_modules/engine.io-client/build/esm/transports/polling.js","../node_modules/engine.io-client/build/esm/transports/websocket-constructor.browser.js","../node_modules/engine.io-client/build/esm/transports/websocket.js","../node_modules/engine.io-client/build/esm/transports/webtransport.js","../node_modules/engine.io-client/build/esm/transports/index.js","../node_modules/engine.io-client/build/esm/contrib/parseuri.js","../node_modules/engine.io-client/build/esm/socket.js","../node_modules/engine.io-client/build/esm/index.js","../node_modules/notepack.io/browser/encode.js","../node_modules/notepack.io/browser/decode.js","../node_modules/notepack.io/lib/index.js","../node_modules/component-emitter/index.js","../node_modules/socket.io-msgpack-parser/index.js","../build/esm/on.js","../build/esm/socket.js","../build/esm/contrib/backo2.js","../build/esm/manager.js","../build/esm/index.js","../build/esm/url.js"],"sourcesContent":["const PACKET_TYPES = Object.create(null); // no Map = no polyfill\nPACKET_TYPES[\"open\"] = \"0\";\nPACKET_TYPES[\"close\"] = \"1\";\nPACKET_TYPES[\"ping\"] = \"2\";\nPACKET_TYPES[\"pong\"] = \"3\";\nPACKET_TYPES[\"message\"] = \"4\";\nPACKET_TYPES[\"upgrade\"] = \"5\";\nPACKET_TYPES[\"noop\"] = \"6\";\nconst PACKET_TYPES_REVERSE = Object.create(null);\nObject.keys(PACKET_TYPES).forEach(key => {\n PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key;\n});\nconst ERROR_PACKET = { type: \"error\", data: \"parser error\" };\nexport { PACKET_TYPES, PACKET_TYPES_REVERSE, ERROR_PACKET };\n","import { PACKET_TYPES } from \"./commons.js\";\nconst withNativeBlob = typeof Blob === \"function\" ||\n (typeof Blob !== \"undefined\" &&\n Object.prototype.toString.call(Blob) === \"[object BlobConstructor]\");\nconst withNativeArrayBuffer = typeof ArrayBuffer === \"function\";\n// ArrayBuffer.isView method is not defined in IE10\nconst isView = obj => {\n return typeof ArrayBuffer.isView === \"function\"\n ? ArrayBuffer.isView(obj)\n : obj && obj.buffer instanceof ArrayBuffer;\n};\nconst encodePacket = ({ type, data }, supportsBinary, callback) => {\n if (withNativeBlob && data instanceof Blob) {\n if (supportsBinary) {\n return callback(data);\n }\n else {\n return encodeBlobAsBase64(data, callback);\n }\n }\n else if (withNativeArrayBuffer &&\n (data instanceof ArrayBuffer || isView(data))) {\n if (supportsBinary) {\n return callback(data);\n }\n else {\n return encodeBlobAsBase64(new Blob([data]), callback);\n }\n }\n // plain string\n return callback(PACKET_TYPES[type] + (data || \"\"));\n};\nconst encodeBlobAsBase64 = (data, callback) => {\n const fileReader = new FileReader();\n fileReader.onload = function () {\n const content = fileReader.result.split(\",\")[1];\n callback(\"b\" + (content || \"\"));\n };\n return fileReader.readAsDataURL(data);\n};\nfunction toArray(data) {\n if (data instanceof Uint8Array) {\n return data;\n }\n else if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n else {\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n }\n}\nlet TEXT_ENCODER;\nexport function encodePacketToBinary(packet, callback) {\n if (withNativeBlob && packet.data instanceof Blob) {\n return packet.data\n .arrayBuffer()\n .then(toArray)\n .then(callback);\n }\n else if (withNativeArrayBuffer &&\n (packet.data instanceof ArrayBuffer || isView(packet.data))) {\n return callback(toArray(packet.data));\n }\n encodePacket(packet, false, encoded => {\n if (!TEXT_ENCODER) {\n TEXT_ENCODER = new TextEncoder();\n }\n callback(TEXT_ENCODER.encode(encoded));\n });\n}\nexport { encodePacket };\n","// imported from https://github.com/socketio/base64-arraybuffer\nconst chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n// Use a lookup table to find the index.\nconst lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);\nfor (let i = 0; i < chars.length; i++) {\n lookup[chars.charCodeAt(i)] = i;\n}\nexport const encode = (arraybuffer) => {\n let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';\n for (i = 0; i < len; i += 3) {\n base64 += chars[bytes[i] >> 2];\n base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];\n base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];\n base64 += chars[bytes[i + 2] & 63];\n }\n if (len % 3 === 2) {\n base64 = base64.substring(0, base64.length - 1) + '=';\n }\n else if (len % 3 === 1) {\n base64 = base64.substring(0, base64.length - 2) + '==';\n }\n return base64;\n};\nexport const decode = (base64) => {\n let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;\n if (base64[base64.length - 1] === '=') {\n bufferLength--;\n if (base64[base64.length - 2] === '=') {\n bufferLength--;\n }\n }\n const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);\n for (i = 0; i < len; i += 4) {\n encoded1 = lookup[base64.charCodeAt(i)];\n encoded2 = lookup[base64.charCodeAt(i + 1)];\n encoded3 = lookup[base64.charCodeAt(i + 2)];\n encoded4 = lookup[base64.charCodeAt(i + 3)];\n bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);\n bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);\n bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);\n }\n return arraybuffer;\n};\n","import { encodePacket, encodePacketToBinary } from \"./encodePacket.js\";\nimport { decodePacket } from \"./decodePacket.js\";\nimport { ERROR_PACKET } from \"./commons.js\";\nconst SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text\nconst encodePayload = (packets, callback) => {\n // some packets may be added to the array while encoding, so the initial length must be saved\n const length = packets.length;\n const encodedPackets = new Array(length);\n let count = 0;\n packets.forEach((packet, i) => {\n // force base64 encoding for binary packets\n encodePacket(packet, false, encodedPacket => {\n encodedPackets[i] = encodedPacket;\n if (++count === length) {\n callback(encodedPackets.join(SEPARATOR));\n }\n });\n });\n};\nconst decodePayload = (encodedPayload, binaryType) => {\n const encodedPackets = encodedPayload.split(SEPARATOR);\n const packets = [];\n for (let i = 0; i < encodedPackets.length; i++) {\n const decodedPacket = decodePacket(encodedPackets[i], binaryType);\n packets.push(decodedPacket);\n if (decodedPacket.type === \"error\") {\n break;\n }\n }\n return packets;\n};\nexport function createPacketEncoderStream() {\n return new TransformStream({\n transform(packet, controller) {\n encodePacketToBinary(packet, encodedPacket => {\n const payloadLength = encodedPacket.length;\n let header;\n // inspired by the WebSocket format: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#decoding_payload_length\n if (payloadLength < 126) {\n header = new Uint8Array(1);\n new DataView(header.buffer).setUint8(0, payloadLength);\n }\n else if (payloadLength < 65536) {\n header = new Uint8Array(3);\n const view = new DataView(header.buffer);\n view.setUint8(0, 126);\n view.setUint16(1, payloadLength);\n }\n else {\n header = new Uint8Array(9);\n const view = new DataView(header.buffer);\n view.setUint8(0, 127);\n view.setBigUint64(1, BigInt(payloadLength));\n }\n // first bit indicates whether the payload is plain text (0) or binary (1)\n if (packet.data && typeof packet.data !== \"string\") {\n header[0] |= 0x80;\n }\n controller.enqueue(header);\n controller.enqueue(encodedPacket);\n });\n }\n });\n}\nlet TEXT_DECODER;\nfunction totalLength(chunks) {\n return chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n}\nfunction concatChunks(chunks, size) {\n if (chunks[0].length === size) {\n return chunks.shift();\n }\n const buffer = new Uint8Array(size);\n let j = 0;\n for (let i = 0; i < size; i++) {\n buffer[i] = chunks[0][j++];\n if (j === chunks[0].length) {\n chunks.shift();\n j = 0;\n }\n }\n if (chunks.length && j < chunks[0].length) {\n chunks[0] = chunks[0].slice(j);\n }\n return buffer;\n}\nexport function createPacketDecoderStream(maxPayload, binaryType) {\n if (!TEXT_DECODER) {\n TEXT_DECODER = new TextDecoder();\n }\n const chunks = [];\n let state = 0 /* READ_HEADER */;\n let expectedLength = -1;\n let isBinary = false;\n return new TransformStream({\n transform(chunk, controller) {\n chunks.push(chunk);\n while (true) {\n if (state === 0 /* READ_HEADER */) {\n if (totalLength(chunks) < 1) {\n break;\n }\n const header = concatChunks(chunks, 1);\n isBinary = (header[0] & 0x80) === 0x80;\n expectedLength = header[0] & 0x7f;\n if (expectedLength < 126) {\n state = 3 /* READ_PAYLOAD */;\n }\n else if (expectedLength === 126) {\n state = 1 /* READ_EXTENDED_LENGTH_16 */;\n }\n else {\n state = 2 /* READ_EXTENDED_LENGTH_64 */;\n }\n }\n else if (state === 1 /* READ_EXTENDED_LENGTH_16 */) {\n if (totalLength(chunks) < 2) {\n break;\n }\n const headerArray = concatChunks(chunks, 2);\n expectedLength = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length).getUint16(0);\n state = 3 /* READ_PAYLOAD */;\n }\n else if (state === 2 /* READ_EXTENDED_LENGTH_64 */) {\n if (totalLength(chunks) < 8) {\n break;\n }\n const headerArray = concatChunks(chunks, 8);\n const view = new DataView(headerArray.buffer, headerArray.byteOffset, headerArray.length);\n const n = view.getUint32(0);\n if (n > Math.pow(2, 53 - 32) - 1) {\n // the maximum safe integer in JavaScript is 2^53 - 1\n controller.enqueue(ERROR_PACKET);\n break;\n }\n expectedLength = n * Math.pow(2, 32) + view.getUint32(4);\n state = 3 /* READ_PAYLOAD */;\n }\n else {\n if (totalLength(chunks) < expectedLength) {\n break;\n }\n const data = concatChunks(chunks, expectedLength);\n controller.enqueue(decodePacket(isBinary ? data : TEXT_DECODER.decode(data), binaryType));\n state = 0 /* READ_HEADER */;\n }\n if (expectedLength === 0 || expectedLength > maxPayload) {\n controller.enqueue(ERROR_PACKET);\n break;\n }\n }\n }\n });\n}\nexport const protocol = 4;\nexport { encodePacket, encodePayload, decodePacket, decodePayload };\n","import { ERROR_PACKET, PACKET_TYPES_REVERSE } from \"./commons.js\";\nimport { decode } from \"./contrib/base64-arraybuffer.js\";\nconst withNativeArrayBuffer = typeof ArrayBuffer === \"function\";\nexport const decodePacket = (encodedPacket, binaryType) => {\n if (typeof encodedPacket !== \"string\") {\n return {\n type: \"message\",\n data: mapBinary(encodedPacket, binaryType)\n };\n }\n const type = encodedPacket.charAt(0);\n if (type === \"b\") {\n return {\n type: \"message\",\n data: decodeBase64Packet(encodedPacket.substring(1), binaryType)\n };\n }\n const packetType = PACKET_TYPES_REVERSE[type];\n if (!packetType) {\n return ERROR_PACKET;\n }\n return encodedPacket.length > 1\n ? {\n type: PACKET_TYPES_REVERSE[type],\n data: encodedPacket.substring(1)\n }\n : {\n type: PACKET_TYPES_REVERSE[type]\n };\n};\nconst decodeBase64Packet = (data, binaryType) => {\n if (withNativeArrayBuffer) {\n const decoded = decode(data);\n return mapBinary(decoded, binaryType);\n }\n else {\n return { base64: true, data }; // fallback for old browsers\n }\n};\nconst mapBinary = (data, binaryType) => {\n switch (binaryType) {\n case \"blob\":\n if (data instanceof Blob) {\n // from WebSocket + binaryType \"blob\"\n return data;\n }\n else {\n // from HTTP long-polling or WebTransport\n return new Blob([data]);\n }\n case \"arraybuffer\":\n default:\n if (data instanceof ArrayBuffer) {\n // from HTTP long-polling (base64) or WebSocket + binaryType \"arraybuffer\"\n return data;\n }\n else {\n // from WebTransport (Uint8Array)\n return data.buffer;\n }\n }\n};\n","/**\n * Initialize a new `Emitter`.\n *\n * @api public\n */\n\nexport function Emitter(obj) {\n if (obj) return mixin(obj);\n}\n\n/**\n * Mixin the emitter properties.\n *\n * @param {Object} obj\n * @return {Object}\n * @api private\n */\n\nfunction mixin(obj) {\n for (var key in Emitter.prototype) {\n obj[key] = Emitter.prototype[key];\n }\n return obj;\n}\n\n/**\n * Listen on the given `event` with `fn`.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.on =\nEmitter.prototype.addEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n (this._callbacks['$' + event] = this._callbacks['$' + event] || [])\n .push(fn);\n return this;\n};\n\n/**\n * Adds an `event` listener that will be invoked a single\n * time then automatically removed.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.once = function(event, fn){\n function on() {\n this.off(event, on);\n fn.apply(this, arguments);\n }\n\n on.fn = fn;\n this.on(event, on);\n return this;\n};\n\n/**\n * Remove the given callback for `event` or all\n * registered callbacks.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n */\n\nEmitter.prototype.off =\nEmitter.prototype.removeListener =\nEmitter.prototype.removeAllListeners =\nEmitter.prototype.removeEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n\n // all\n if (0 == arguments.length) {\n this._callbacks = {};\n return this;\n }\n\n // specific event\n var callbacks = this._callbacks['$' + event];\n if (!callbacks) return this;\n\n // remove all handlers\n if (1 == arguments.length) {\n delete this._callbacks['$' + event];\n return this;\n }\n\n // remove specific handler\n var cb;\n for (var i = 0; i < callbacks.length; i++) {\n cb = callbacks[i];\n if (cb === fn || cb.fn === fn) {\n callbacks.splice(i, 1);\n break;\n }\n }\n\n // Remove event specific arrays for event types that no\n // one is subscribed for to avoid memory leak.\n if (callbacks.length === 0) {\n delete this._callbacks['$' + event];\n }\n\n return this;\n};\n\n/**\n * Emit `event` with the given args.\n *\n * @param {String} event\n * @param {Mixed} ...\n * @return {Emitter}\n */\n\nEmitter.prototype.emit = function(event){\n this._callbacks = this._callbacks || {};\n\n var args = new Array(arguments.length - 1)\n , callbacks = this._callbacks['$' + event];\n\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n\n if (callbacks) {\n callbacks = callbacks.slice(0);\n for (var i = 0, len = callbacks.length; i < len; ++i) {\n callbacks[i].apply(this, args);\n }\n }\n\n return this;\n};\n\n// alias used for reserved events (protected method)\nEmitter.prototype.emitReserved = Emitter.prototype.emit;\n\n/**\n * Return array of callbacks for `event`.\n *\n * @param {String} event\n * @return {Array}\n * @api public\n */\n\nEmitter.prototype.listeners = function(event){\n this._callbacks = this._callbacks || {};\n return this._callbacks['$' + event] || [];\n};\n\n/**\n * Check if this emitter has `event` handlers.\n *\n * @param {String} event\n * @return {Boolean}\n * @api public\n */\n\nEmitter.prototype.hasListeners = function(event){\n return !! this.listeners(event).length;\n};\n","export const globalThisShim = (() => {\n if (typeof self !== \"undefined\") {\n return self;\n }\n else if (typeof window !== \"undefined\") {\n return window;\n }\n else {\n return Function(\"return this\")();\n }\n})();\n","import { globalThisShim as globalThis } from \"./globalThis.js\";\nexport function pick(obj, ...attr) {\n return attr.reduce((acc, k) => {\n if (obj.hasOwnProperty(k)) {\n acc[k] = obj[k];\n }\n return acc;\n }, {});\n}\n// Keep a reference to the real timeout functions so they can be used when overridden\nconst NATIVE_SET_TIMEOUT = globalThis.setTimeout;\nconst NATIVE_CLEAR_TIMEOUT = globalThis.clearTimeout;\nexport function installTimerFunctions(obj, opts) {\n if (opts.useNativeTimers) {\n obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis);\n obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis);\n }\n else {\n obj.setTimeoutFn = globalThis.setTimeout.bind(globalThis);\n obj.clearTimeoutFn = globalThis.clearTimeout.bind(globalThis);\n }\n}\n// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)\nconst BASE64_OVERHEAD = 1.33;\n// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9\nexport function byteLength(obj) {\n if (typeof obj === \"string\") {\n return utf8Length(obj);\n }\n // arraybuffer or blob\n return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);\n}\nfunction utf8Length(str) {\n let c = 0, length = 0;\n for (let i = 0, l = str.length; i < l; i++) {\n c = str.charCodeAt(i);\n if (c < 0x80) {\n length += 1;\n }\n else if (c < 0x800) {\n length += 2;\n }\n else if (c < 0xd800 || c >= 0xe000) {\n length += 3;\n }\n else {\n i++;\n length += 4;\n }\n }\n return length;\n}\n","import { decodePacket } from \"engine.io-parser\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { installTimerFunctions } from \"./util.js\";\nimport { encode } from \"./contrib/parseqs.js\";\nclass TransportError extends Error {\n constructor(reason, description, context) {\n super(reason);\n this.description = description;\n this.context = context;\n this.type = \"TransportError\";\n }\n}\nexport class Transport extends Emitter {\n /**\n * Transport abstract constructor.\n *\n * @param {Object} opts - options\n * @protected\n */\n constructor(opts) {\n super();\n this.writable = false;\n installTimerFunctions(this, opts);\n this.opts = opts;\n this.query = opts.query;\n this.socket = opts.socket;\n }\n /**\n * Emits an error.\n *\n * @param {String} reason\n * @param description\n * @param context - the error context\n * @return {Transport} for chaining\n * @protected\n */\n onError(reason, description, context) {\n super.emitReserved(\"error\", new TransportError(reason, description, context));\n return this;\n }\n /**\n * Opens the transport.\n */\n open() {\n this.readyState = \"opening\";\n this.doOpen();\n return this;\n }\n /**\n * Closes the transport.\n */\n close() {\n if (this.readyState === \"opening\" || this.readyState === \"open\") {\n this.doClose();\n this.onClose();\n }\n return this;\n }\n /**\n * Sends multiple packets.\n *\n * @param {Array} packets\n */\n send(packets) {\n if (this.readyState === \"open\") {\n this.write(packets);\n }\n else {\n // this might happen if the transport was silently closed in the beforeunload event handler\n }\n }\n /**\n * Called upon open\n *\n * @protected\n */\n onOpen() {\n this.readyState = \"open\";\n this.writable = true;\n super.emitReserved(\"open\");\n }\n /**\n * Called with data.\n *\n * @param {String} data\n * @protected\n */\n onData(data) {\n const packet = decodePacket(data, this.socket.binaryType);\n this.onPacket(packet);\n }\n /**\n * Called with a decoded packet.\n *\n * @protected\n */\n onPacket(packet) {\n super.emitReserved(\"packet\", packet);\n }\n /**\n * Called upon close.\n *\n * @protected\n */\n onClose(details) {\n this.readyState = \"closed\";\n super.emitReserved(\"close\", details);\n }\n /**\n * Pauses the transport, in order not to lose packets during an upgrade.\n *\n * @param onPause\n */\n pause(onPause) { }\n createUri(schema, query = {}) {\n return (schema +\n \"://\" +\n this._hostname() +\n this._port() +\n this.opts.path +\n this._query(query));\n }\n _hostname() {\n const hostname = this.opts.hostname;\n return hostname.indexOf(\":\") === -1 ? hostname : \"[\" + hostname + \"]\";\n }\n _port() {\n if (this.opts.port &&\n ((this.opts.secure && Number(this.opts.port !== 443)) ||\n (!this.opts.secure && Number(this.opts.port) !== 80))) {\n return \":\" + this.opts.port;\n }\n else {\n return \"\";\n }\n }\n _query(query) {\n const encodedQuery = encode(query);\n return encodedQuery.length ? \"?\" + encodedQuery : \"\";\n }\n}\n","// imported from https://github.com/unshiftio/yeast\n'use strict';\nconst alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split(''), length = 64, map = {};\nlet seed = 0, i = 0, prev;\n/**\n * Return a string representing the specified number.\n *\n * @param {Number} num The number to convert.\n * @returns {String} The string representation of the number.\n * @api public\n */\nexport function encode(num) {\n let encoded = '';\n do {\n encoded = alphabet[num % length] + encoded;\n num = Math.floor(num / length);\n } while (num > 0);\n return encoded;\n}\n/**\n * Return the integer value specified by the given string.\n *\n * @param {String} str The string to convert.\n * @returns {Number} The integer value represented by the string.\n * @api public\n */\nexport function decode(str) {\n let decoded = 0;\n for (i = 0; i < str.length; i++) {\n decoded = decoded * length + map[str.charAt(i)];\n }\n return decoded;\n}\n/**\n * Yeast: A tiny growing id generator.\n *\n * @returns {String} A unique id.\n * @api public\n */\nexport function yeast() {\n const now = encode(+new Date());\n if (now !== prev)\n return seed = 0, prev = now;\n return now + '.' + encode(seed++);\n}\n//\n// Map each character to its index.\n//\nfor (; i < length; i++)\n map[alphabet[i]] = i;\n","// imported from https://github.com/galkn/querystring\n/**\n * Compiles a querystring\n * Returns string representation of the object\n *\n * @param {Object}\n * @api private\n */\nexport function encode(obj) {\n let str = '';\n for (let i in obj) {\n if (obj.hasOwnProperty(i)) {\n if (str.length)\n str += '&';\n str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);\n }\n }\n return str;\n}\n/**\n * Parses a simple querystring into an object\n *\n * @param {String} qs\n * @api private\n */\nexport function decode(qs) {\n let qry = {};\n let pairs = qs.split('&');\n for (let i = 0, l = pairs.length; i < l; i++) {\n let pair = pairs[i].split('=');\n qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n return qry;\n}\n","// imported from https://github.com/component/has-cors\nlet value = false;\ntry {\n value = typeof XMLHttpRequest !== 'undefined' &&\n 'withCredentials' in new XMLHttpRequest();\n}\ncatch (err) {\n // if XMLHttp support is disabled in IE then it will throw\n // when trying to create\n}\nexport const hasCORS = value;\n","// browser shim for xmlhttprequest module\nimport { hasCORS } from \"../contrib/has-cors.js\";\nimport { globalThisShim as globalThis } from \"../globalThis.js\";\nexport function XHR(opts) {\n const xdomain = opts.xdomain;\n // XMLHttpRequest can be disabled on IE\n try {\n if (\"undefined\" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {\n return new XMLHttpRequest();\n }\n }\n catch (e) { }\n if (!xdomain) {\n try {\n return new globalThis[[\"Active\"].concat(\"Object\").join(\"X\")](\"Microsoft.XMLHTTP\");\n }\n catch (e) { }\n }\n}\nexport function createCookieJar() { }\n","import { Transport } from \"../transport.js\";\nimport { yeast } from \"../contrib/yeast.js\";\nimport { encodePayload, decodePayload } from \"engine.io-parser\";\nimport { createCookieJar, XHR as XMLHttpRequest, } from \"./xmlhttprequest.js\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { installTimerFunctions, pick } from \"../util.js\";\nimport { globalThisShim as globalThis } from \"../globalThis.js\";\nfunction empty() { }\nconst hasXHR2 = (function () {\n const xhr = new XMLHttpRequest({\n xdomain: false,\n });\n return null != xhr.responseType;\n})();\nexport class Polling extends Transport {\n /**\n * XHR Polling constructor.\n *\n * @param {Object} opts\n * @package\n */\n constructor(opts) {\n super(opts);\n this.polling = false;\n if (typeof location !== \"undefined\") {\n const isSSL = \"https:\" === location.protocol;\n let port = location.port;\n // some user agents have empty `location.port`\n if (!port) {\n port = isSSL ? \"443\" : \"80\";\n }\n this.xd =\n (typeof location !== \"undefined\" &&\n opts.hostname !== location.hostname) ||\n port !== opts.port;\n }\n /**\n * XHR supports binary\n */\n const forceBase64 = opts && opts.forceBase64;\n this.supportsBinary = hasXHR2 && !forceBase64;\n if (this.opts.withCredentials) {\n this.cookieJar = createCookieJar();\n }\n }\n get name() {\n return \"polling\";\n }\n /**\n * Opens the socket (triggers polling). We write a PING message to determine\n * when the transport is open.\n *\n * @protected\n */\n doOpen() {\n this.poll();\n }\n /**\n * Pauses polling.\n *\n * @param {Function} onPause - callback upon buffers are flushed and transport is paused\n * @package\n */\n pause(onPause) {\n this.readyState = \"pausing\";\n const pause = () => {\n this.readyState = \"paused\";\n onPause();\n };\n if (this.polling || !this.writable) {\n let total = 0;\n if (this.polling) {\n total++;\n this.once(\"pollComplete\", function () {\n --total || pause();\n });\n }\n if (!this.writable) {\n total++;\n this.once(\"drain\", function () {\n --total || pause();\n });\n }\n }\n else {\n pause();\n }\n }\n /**\n * Starts polling cycle.\n *\n * @private\n */\n poll() {\n this.polling = true;\n this.doPoll();\n this.emitReserved(\"poll\");\n }\n /**\n * Overloads onData to detect payloads.\n *\n * @protected\n */\n onData(data) {\n const callback = (packet) => {\n // if its the first message we consider the transport open\n if (\"opening\" === this.readyState && packet.type === \"open\") {\n this.onOpen();\n }\n // if its a close packet, we close the ongoing requests\n if (\"close\" === packet.type) {\n this.onClose({ description: \"transport closed by the server\" });\n return false;\n }\n // otherwise bypass onData and handle the message\n this.onPacket(packet);\n };\n // decode payload\n decodePayload(data, this.socket.binaryType).forEach(callback);\n // if an event did not trigger closing\n if (\"closed\" !== this.readyState) {\n // if we got data we're not polling\n this.polling = false;\n this.emitReserved(\"pollComplete\");\n if (\"open\" === this.readyState) {\n this.poll();\n }\n else {\n }\n }\n }\n /**\n * For polling, send a close packet.\n *\n * @protected\n */\n doClose() {\n const close = () => {\n this.write([{ type: \"close\" }]);\n };\n if (\"open\" === this.readyState) {\n close();\n }\n else {\n // in case we're trying to close while\n // handshaking is in progress (GH-164)\n this.once(\"open\", close);\n }\n }\n /**\n * Writes a packets payload.\n *\n * @param {Array} packets - data packets\n * @protected\n */\n write(packets) {\n this.writable = false;\n encodePayload(packets, (data) => {\n this.doWrite(data, () => {\n this.writable = true;\n this.emitReserved(\"drain\");\n });\n });\n }\n /**\n * Generates uri for connection.\n *\n * @private\n */\n uri() {\n const schema = this.opts.secure ? \"https\" : \"http\";\n const query = this.query || {};\n // cache busting is forced\n if (false !== this.opts.timestampRequests) {\n query[this.opts.timestampParam] = yeast();\n }\n if (!this.supportsBinary && !query.sid) {\n query.b64 = 1;\n }\n return this.createUri(schema, query);\n }\n /**\n * Creates a request.\n *\n * @param {String} method\n * @private\n */\n request(opts = {}) {\n Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);\n return new Request(this.uri(), opts);\n }\n /**\n * Sends data.\n *\n * @param {String} data to send.\n * @param {Function} called upon flush.\n * @private\n */\n doWrite(data, fn) {\n const req = this.request({\n method: \"POST\",\n data: data,\n });\n req.on(\"success\", fn);\n req.on(\"error\", (xhrStatus, context) => {\n this.onError(\"xhr post error\", xhrStatus, context);\n });\n }\n /**\n * Starts a poll cycle.\n *\n * @private\n */\n doPoll() {\n const req = this.request();\n req.on(\"data\", this.onData.bind(this));\n req.on(\"error\", (xhrStatus, context) => {\n this.onError(\"xhr poll error\", xhrStatus, context);\n });\n this.pollXhr = req;\n }\n}\nexport class Request extends Emitter {\n /**\n * Request constructor\n *\n * @param {Object} options\n * @package\n */\n constructor(uri, opts) {\n super();\n installTimerFunctions(this, opts);\n this.opts = opts;\n this.method = opts.method || \"GET\";\n this.uri = uri;\n this.data = undefined !== opts.data ? opts.data : null;\n this.create();\n }\n /**\n * Creates the XHR object and sends the request.\n *\n * @private\n */\n create() {\n var _a;\n const opts = pick(this.opts, \"agent\", \"pfx\", \"key\", \"passphrase\", \"cert\", \"ca\", \"ciphers\", \"rejectUnauthorized\", \"autoUnref\");\n opts.xdomain = !!this.opts.xd;\n const xhr = (this.xhr = new XMLHttpRequest(opts));\n try {\n xhr.open(this.method, this.uri, true);\n try {\n if (this.opts.extraHeaders) {\n xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);\n for (let i in this.opts.extraHeaders) {\n if (this.opts.extraHeaders.hasOwnProperty(i)) {\n xhr.setRequestHeader(i, this.opts.extraHeaders[i]);\n }\n }\n }\n }\n catch (e) { }\n if (\"POST\" === this.method) {\n try {\n xhr.setRequestHeader(\"Content-type\", \"text/plain;charset=UTF-8\");\n }\n catch (e) { }\n }\n try {\n xhr.setRequestHeader(\"Accept\", \"*/*\");\n }\n catch (e) { }\n (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.addCookies(xhr);\n // ie6 check\n if (\"withCredentials\" in xhr) {\n xhr.withCredentials = this.opts.withCredentials;\n }\n if (this.opts.requestTimeout) {\n xhr.timeout = this.opts.requestTimeout;\n }\n xhr.onreadystatechange = () => {\n var _a;\n if (xhr.readyState === 3) {\n (_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.parseCookies(xhr);\n }\n if (4 !== xhr.readyState)\n return;\n if (200 === xhr.status || 1223 === xhr.status) {\n this.onLoad();\n }\n else {\n // make sure the `error` event handler that's user-set\n // does not throw in the same tick and gets caught here\n this.setTimeoutFn(() => {\n this.onError(typeof xhr.status === \"number\" ? xhr.status : 0);\n }, 0);\n }\n };\n xhr.send(this.data);\n }\n catch (e) {\n // Need to defer since .create() is called directly from the constructor\n // and thus the 'error' event can only be only bound *after* this exception\n // occurs. Therefore, also, we cannot throw here at all.\n this.setTimeoutFn(() => {\n this.onError(e);\n }, 0);\n return;\n }\n if (typeof document !== \"undefined\") {\n this.index = Request.requestsCount++;\n Request.requests[this.index] = this;\n }\n }\n /**\n * Called upon error.\n *\n * @private\n */\n onError(err) {\n this.emitReserved(\"error\", err, this.xhr);\n this.cleanup(true);\n }\n /**\n * Cleans up house.\n *\n * @private\n */\n cleanup(fromError) {\n if (\"undefined\" === typeof this.xhr || null === this.xhr) {\n return;\n }\n this.xhr.onreadystatechange = empty;\n if (fromError) {\n try {\n this.xhr.abort();\n }\n catch (e) { }\n }\n if (typeof document !== \"undefined\") {\n delete Request.requests[this.index];\n }\n this.xhr = null;\n }\n /**\n * Called upon load.\n *\n * @private\n */\n onLoad() {\n const data = this.xhr.responseText;\n if (data !== null) {\n this.emitReserved(\"data\", data);\n this.emitReserved(\"success\");\n this.cleanup();\n }\n }\n /**\n * Aborts the request.\n *\n * @package\n */\n abort() {\n this.cleanup();\n }\n}\nRequest.requestsCount = 0;\nRequest.requests = {};\n/**\n * Aborts pending requests when unloading the window. This is needed to prevent\n * memory leaks (e.g. when using IE) and to ensure that no spurious error is\n * emitted.\n */\nif (typeof document !== \"undefined\") {\n // @ts-ignore\n if (typeof attachEvent === \"function\") {\n // @ts-ignore\n attachEvent(\"onunload\", unloadHandler);\n }\n else if (typeof addEventListener === \"function\") {\n const terminationEvent = \"onpagehide\" in globalThis ? \"pagehide\" : \"unload\";\n addEventListener(terminationEvent, unloadHandler, false);\n }\n}\nfunction unloadHandler() {\n for (let i in Request.requests) {\n if (Request.requests.hasOwnProperty(i)) {\n Request.requests[i].abort();\n }\n }\n}\n","import { globalThisShim as globalThis } from \"../globalThis.js\";\nexport const nextTick = (() => {\n const isPromiseAvailable = typeof Promise === \"function\" && typeof Promise.resolve === \"function\";\n if (isPromiseAvailable) {\n return (cb) => Promise.resolve().then(cb);\n }\n else {\n return (cb, setTimeoutFn) => setTimeoutFn(cb, 0);\n }\n})();\nexport const WebSocket = globalThis.WebSocket || globalThis.MozWebSocket;\nexport const usingBrowserWebSocket = true;\nexport const defaultBinaryType = \"arraybuffer\";\n","import { Transport } from \"../transport.js\";\nimport { yeast } from \"../contrib/yeast.js\";\nimport { pick } from \"../util.js\";\nimport { nextTick, usingBrowserWebSocket, WebSocket, } from \"./websocket-constructor.js\";\nimport { encodePacket } from \"engine.io-parser\";\n// detect ReactNative environment\nconst isReactNative = typeof navigator !== \"undefined\" &&\n typeof navigator.product === \"string\" &&\n navigator.product.toLowerCase() === \"reactnative\";\nexport class WS extends Transport {\n /**\n * WebSocket transport constructor.\n *\n * @param {Object} opts - connection options\n * @protected\n */\n constructor(opts) {\n super(opts);\n this.supportsBinary = !opts.forceBase64;\n }\n get name() {\n return \"websocket\";\n }\n doOpen() {\n if (!this.check()) {\n // let probe timeout\n return;\n }\n const uri = this.uri();\n const protocols = this.opts.protocols;\n // React Native only supports the 'headers' option, and will print a warning if anything else is passed\n const opts = isReactNative\n ? {}\n : pick(this.opts, \"agent\", \"perMessageDeflate\", \"pfx\", \"key\", \"passphrase\", \"cert\", \"ca\", \"ciphers\", \"rejectUnauthorized\", \"localAddress\", \"protocolVersion\", \"origin\", \"maxPayload\", \"family\", \"checkServerIdentity\");\n if (this.opts.extraHeaders) {\n opts.headers = this.opts.extraHeaders;\n }\n try {\n this.ws =\n usingBrowserWebSocket && !isReactNative\n ? protocols\n ? new WebSocket(uri, protocols)\n : new WebSocket(uri)\n : new WebSocket(uri, protocols, opts);\n }\n catch (err) {\n return this.emitReserved(\"error\", err);\n }\n this.ws.binaryType = this.socket.binaryType;\n this.addEventListeners();\n }\n /**\n * Adds event listeners to the socket\n *\n * @private\n */\n addEventListeners() {\n this.ws.onopen = () => {\n if (this.opts.autoUnref) {\n this.ws._socket.unref();\n }\n this.onOpen();\n };\n this.ws.onclose = (closeEvent) => this.onClose({\n description: \"websocket connection closed\",\n context: closeEvent,\n });\n this.ws.onmessage = (ev) => this.onData(ev.data);\n this.ws.onerror = (e) => this.onError(\"websocket error\", e);\n }\n write(packets) {\n this.writable = false;\n // encodePacket efficient as it uses WS framing\n // no need for encodePayload\n for (let i = 0; i < packets.length; i++) {\n const packet = packets[i];\n const lastPacket = i === packets.length - 1;\n encodePacket(packet, this.supportsBinary, (data) => {\n // always create a new object (GH-437)\n const opts = {};\n if (!usingBrowserWebSocket) {\n if (packet.options) {\n opts.compress = packet.options.compress;\n }\n if (this.opts.perMessageDeflate) {\n const len = \n // @ts-ignore\n \"string\" === typeof data ? Buffer.byteLength(data) : data.length;\n if (len < this.opts.perMessageDeflate.threshold) {\n opts.compress = false;\n }\n }\n }\n // Sometimes the websocket has already been closed but the browser didn't\n // have a chance of informing us about it yet, in that case send will\n // throw an error\n try {\n if (usingBrowserWebSocket) {\n // TypeError is thrown when passing the second argument on Safari\n this.ws.send(data);\n }\n else {\n this.ws.send(data, opts);\n }\n }\n catch (e) {\n }\n if (lastPacket) {\n // fake drain\n // defer to next tick to allow Socket to clear writeBuffer\n nextTick(() => {\n this.writable = true;\n this.emitReserved(\"drain\");\n }, this.setTimeoutFn);\n }\n });\n }\n }\n doClose() {\n if (typeof this.ws !== \"undefined\") {\n this.ws.close();\n this.ws = null;\n }\n }\n /**\n * Generates uri for connection.\n *\n * @private\n */\n uri() {\n const schema = this.opts.secure ? \"wss\" : \"ws\";\n const query = this.query || {};\n // append timestamp to URI\n if (this.opts.timestampRequests) {\n query[this.opts.timestampParam] = yeast();\n }\n // communicate binary support capabilities\n if (!this.supportsBinary) {\n query.b64 = 1;\n }\n return this.createUri(schema, query);\n }\n /**\n * Feature detection for WebSocket.\n *\n * @return {Boolean} whether this transport is available.\n * @private\n */\n check() {\n return !!WebSocket;\n }\n}\n","import { Transport } from \"../transport.js\";\nimport { nextTick } from \"./websocket-constructor.js\";\nimport { createPacketDecoderStream, createPacketEncoderStream, } from \"engine.io-parser\";\nexport class WT extends Transport {\n get name() {\n return \"webtransport\";\n }\n doOpen() {\n // @ts-ignore\n if (typeof WebTransport !== \"function\") {\n return;\n }\n // @ts-ignore\n this.transport = new WebTransport(this.createUri(\"https\"), this.opts.transportOptions[this.name]);\n this.transport.closed\n .then(() => {\n this.onClose();\n })\n .catch((err) => {\n this.onError(\"webtransport error\", err);\n });\n // note: we could have used async/await, but that would require some additional polyfills\n this.transport.ready.then(() => {\n this.transport.createBidirectionalStream().then((stream) => {\n const decoderStream = createPacketDecoderStream(Number.MAX_SAFE_INTEGER, this.socket.binaryType);\n const reader = stream.readable.pipeThrough(decoderStream).getReader();\n const encoderStream = createPacketEncoderStream();\n encoderStream.readable.pipeTo(stream.writable);\n this.writer = encoderStream.writable.getWriter();\n const read = () => {\n reader\n .read()\n .then(({ done, value }) => {\n if (done) {\n return;\n }\n this.onPacket(value);\n read();\n })\n .catch((err) => {\n });\n };\n read();\n const packet = { type: \"open\" };\n if (this.query.sid) {\n packet.data = `{\"sid\":\"${this.query.sid}\"}`;\n }\n this.writer.write(packet).then(() => this.onOpen());\n });\n });\n }\n write(packets) {\n this.writable = false;\n for (let i = 0; i < packets.length; i++) {\n const packet = packets[i];\n const lastPacket = i === packets.length - 1;\n this.writer.write(packet).then(() => {\n if (lastPacket) {\n nextTick(() => {\n this.writable = true;\n this.emitReserved(\"drain\");\n }, this.setTimeoutFn);\n }\n });\n }\n }\n doClose() {\n var _a;\n (_a = this.transport) === null || _a === void 0 ? void 0 : _a.close();\n }\n}\n","import { Polling } from \"./polling.js\";\nimport { WS } from \"./websocket.js\";\nimport { WT } from \"./webtransport.js\";\nexport const transports = {\n websocket: WS,\n webtransport: WT,\n polling: Polling,\n};\n","// imported from https://github.com/galkn/parseuri\n/**\n * Parses a URI\n *\n * Note: we could also have used the built-in URL object, but it isn't supported on all platforms.\n *\n * See:\n * - https://developer.mozilla.org/en-US/docs/Web/API/URL\n * - https://caniuse.com/url\n * - https://www.rfc-editor.org/rfc/rfc3986#appendix-B\n *\n * History of the parse() method:\n * - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c\n * - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3\n * - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242\n *\n * @author Steven Levithan (MIT license)\n * @api private\n */\nconst re = /^(?:(?![^:@\\/?#]+:[^:@\\/]*@)(http|https|ws|wss):\\/\\/)?((?:(([^:@\\/?#]*)(?::([^:@\\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\\/?#]*)(?::(\\d*))?)(((\\/(?:[^?#](?![^?#\\/]*\\.[^?#\\/.]+(?:[?#]|$)))*\\/?)?([^?#\\/]*))(?:\\?([^#]*))?(?:#(.*))?)/;\nconst parts = [\n 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'\n];\nexport function parse(str) {\n const src = str, b = str.indexOf('['), e = str.indexOf(']');\n if (b != -1 && e != -1) {\n str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);\n }\n let m = re.exec(str || ''), uri = {}, i = 14;\n while (i--) {\n uri[parts[i]] = m[i] || '';\n }\n if (b != -1 && e != -1) {\n uri.source = src;\n uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');\n uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');\n uri.ipv6uri = true;\n }\n uri.pathNames = pathNames(uri, uri['path']);\n uri.queryKey = queryKey(uri, uri['query']);\n return uri;\n}\nfunction pathNames(obj, path) {\n const regx = /\\/{2,9}/g, names = path.replace(regx, \"/\").split(\"/\");\n if (path.slice(0, 1) == '/' || path.length === 0) {\n names.splice(0, 1);\n }\n if (path.slice(-1) == '/') {\n names.splice(names.length - 1, 1);\n }\n return names;\n}\nfunction queryKey(uri, query) {\n const data = {};\n query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {\n if ($1) {\n data[$1] = $2;\n }\n });\n return data;\n}\n","import { transports } from \"./transports/index.js\";\nimport { installTimerFunctions, byteLength } from \"./util.js\";\nimport { decode } from \"./contrib/parseqs.js\";\nimport { parse } from \"./contrib/parseuri.js\";\nimport { Emitter } from \"@socket.io/component-emitter\";\nimport { protocol } from \"engine.io-parser\";\nimport { defaultBinaryType } from \"./transports/websocket-constructor.js\";\nexport class Socket extends Emitter {\n /**\n * Socket constructor.\n *\n * @param {String|Object} uri - uri or options\n * @param {Object} opts - options\n */\n constructor(uri, opts = {}) {\n super();\n this.binaryType = defaultBinaryType;\n this.writeBuffer = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = null;\n }\n if (uri) {\n uri = parse(uri);\n opts.hostname = uri.host;\n opts.secure = uri.protocol === \"https\" || uri.protocol === \"wss\";\n opts.port = uri.port;\n if (uri.query)\n opts.query = uri.query;\n }\n else if (opts.host) {\n opts.hostname = parse(opts.host).host;\n }\n installTimerFunctions(this, opts);\n this.secure =\n null != opts.secure\n ? opts.secure\n : typeof location !== \"undefined\" && \"https:\" === location.protocol;\n if (opts.hostname && !opts.port) {\n // if no port is specified manually, use the protocol default\n opts.port = this.secure ? \"443\" : \"80\";\n }\n this.hostname =\n opts.hostname ||\n (typeof location !== \"undefined\" ? location.hostname : \"localhost\");\n this.port =\n opts.port ||\n (typeof location !== \"undefined\" && location.port\n ? location.port\n : this.secure\n ? \"443\"\n : \"80\");\n this.transports = opts.transports || [\n \"polling\",\n \"websocket\",\n \"webtransport\",\n ];\n this.writeBuffer = [];\n this.prevBufferLen = 0;\n this.opts = Object.assign({\n path: \"/engine.io\",\n agent: false,\n withCredentials: false,\n upgrade: true,\n timestampParam: \"t\",\n rememberUpgrade: false,\n addTrailingSlash: true,\n rejectUnauthorized: true,\n perMessageDeflate: {\n threshold: 1024,\n },\n transportOptions: {},\n closeOnBeforeunload: false,\n }, opts);\n this.opts.path =\n this.opts.path.replace(/\\/$/, \"\") +\n (this.opts.addTrailingSlash ? \"/\" : \"\");\n if (typeof this.opts.query === \"string\") {\n this.opts.query = decode(this.opts.query);\n }\n // set on handshake\n this.id = null;\n this.upgrades = null;\n this.pingInterval = null;\n this.pingTimeout = null;\n // set on heartbeat\n this.pingTimeoutTimer = null;\n if (typeof addEventListener === \"function\") {\n if (this.opts.closeOnBeforeunload) {\n // Firefox closes the connection when the \"beforeunload\" event is emitted but not Chrome. This event listener\n // ensures every browser behaves the same (no \"disconnect\" event at the Socket.IO level when the page is\n // closed/reloaded)\n this.beforeunloadEventListener = () => {\n if (this.transport) {\n // silently close the transport\n this.transport.removeAllListeners();\n this.transport.close();\n }\n };\n addEventListener(\"beforeunload\", this.beforeunloadEventListener, false);\n }\n if (this.hostname !== \"localhost\") {\n this.offlineEventListener = () => {\n this.onClose(\"transport close\", {\n description: \"network connection lost\",\n });\n };\n addEventListener(\"offline\", this.offlineEventListener, false);\n }\n }\n this.open();\n }\n /**\n * Creates transport of the given type.\n *\n * @param {String} name - transport name\n * @return {Transport}\n * @private\n */\n createTransport(name) {\n const query = Object.assign({}, this.opts.query);\n // append engine.io protocol identifier\n query.EIO = protocol;\n // transport name\n query.transport = name;\n // session id if we already have one\n if (this.id)\n query.sid = this.id;\n const opts = Object.assign({}, this.opts, {\n query,\n socket: this,\n hostname: this.hostname,\n secure: this.secure,\n port: this.port,\n }, this.opts.transportOptions[name]);\n return new transports[name](opts);\n }\n /**\n * Initializes transport to use and starts probe.\n *\n * @private\n */\n open() {\n let transport;\n if (this.opts.rememberUpgrade &&\n Socket.priorWebsocketSuccess &&\n this.transports.indexOf(\"websocket\") !== -1) {\n transport = \"websocket\";\n }\n else if (0 === this.transports.length) {\n // Emit error on next tick so it can be listened to\n this.setTimeoutFn(() => {\n this.emitReserved(\"error\", \"No transports available\");\n }, 0);\n return;\n }\n else {\n transport = this.transports[0];\n }\n this.readyState = \"opening\";\n // Retry with the next transport if the transport is disabled (jsonp: false)\n try {\n transport = this.createTransport(transport);\n }\n catch (e) {\n this.transports.shift();\n this.open();\n return;\n }\n transport.open();\n this.setTransport(transport);\n }\n /**\n * Sets the current transport. Disables the existing one (if any).\n *\n * @private\n */\n setTransport(transport) {\n if (this.transport) {\n this.transport.removeAllListeners();\n }\n // set up transport\n this.transport = transport;\n // set up transport listeners\n transport\n .on(\"drain\", this.onDrain.bind(this))\n .on(\"packet\", this.onPacket.bind(this))\n .on(\"error\", this.onError.bind(this))\n .on(\"close\", (reason) => this.onClose(\"transport close\", reason));\n }\n /**\n * Probes a transport.\n *\n * @param {String} name - transport name\n * @private\n */\n probe(name) {\n let transport = this.createTransport(name);\n let failed = false;\n Socket.priorWebsocketSuccess = false;\n const onTransportOpen = () => {\n if (failed)\n return;\n transport.send([{ type: \"ping\", data: \"probe\" }]);\n transport.once(\"packet\", (msg) => {\n if (failed)\n return;\n if (\"pong\" === msg.type && \"probe\" === msg.data) {\n this.upgrading = true;\n this.emitReserved(\"upgrading\", transport);\n if (!transport)\n return;\n Socket.priorWebsocketSuccess = \"websocket\" === transport.name;\n this.transport.pause(() => {\n if (failed)\n return;\n if (\"closed\" === this.readyState)\n return;\n cleanup();\n this.setTransport(transport);\n transport.send([{ type: \"upgrade\" }]);\n this.emitReserved(\"upgrade\", transport);\n transport = null;\n this.upgrading = false;\n this.flush();\n });\n }\n else {\n const err = new Error(\"probe error\");\n // @ts-ignore\n err.transport = transport.name;\n this.emitReserved(\"upgradeError\", err);\n }\n });\n };\n function freezeTransport() {\n if (failed)\n return;\n // Any callback called by transport should be ignored since now\n failed = true;\n cleanup();\n transport.close();\n transport = null;\n }\n // Handle any error that happens while probing\n const onerror = (err) => {\n const error = new Error(\"probe error: \" + err);\n // @ts-ignore\n error.transport = transport.name;\n freezeTransport();\n this.emitReserved(\"upgradeError\", error);\n };\n function onTransportClose() {\n onerror(\"transport closed\");\n }\n // When the socket is closed while we're probing\n function onclose() {\n onerror(\"socket closed\");\n }\n // When the socket is upgraded while we're probing\n function onupgrade(to) {\n if (transport && to.name !== transport.name) {\n freezeTransport();\n }\n }\n // Remove all listeners on the transport and on self\n const cleanup = () => {\n transport.removeListener(\"open\", onTransportOpen);\n transport.removeListener(\"error\", onerror);\n transport.removeListener(\"close\", onTransportClose);\n this.off(\"close\", onclose);\n this.off(\"upgrading\", onupgrade);\n };\n transport.once(\"open\", onTransportOpen);\n transport.once(\"error\", onerror);\n transport.once(\"close\", onTransportClose);\n this.once(\"close\", onclose);\n this.once(\"upgrading\", onupgrade);\n if (this.upgrades.indexOf(\"webtransport\") !== -1 &&\n name !== \"webtransport\") {\n // favor WebTransport\n this.setTimeoutFn(() => {\n if (!failed) {\n transport.open();\n }\n }, 200);\n }\n else {\n transport.open();\n }\n }\n /**\n * Called when connection is deemed open.\n *\n * @private\n */\n onOpen() {\n this.readyState = \"open\";\n Socket.priorWebsocketSuccess = \"websocket\" === this.transport.name;\n this.emitReserved(\"open\");\n this.flush();\n // we check for `readyState` in case an `open`\n // listener already closed the socket\n if (\"open\" === this.readyState && this.opts.upgrade) {\n let i = 0;\n const l = this.upgrades.length;\n for (; i < l; i++) {\n this.probe(this.upgrades[i]);\n }\n }\n }\n /**\n * Handles a packet.\n *\n * @private\n */\n onPacket(packet) {\n if (\"opening\" === this.readyState ||\n \"open\" === this.readyState ||\n \"closing\" === this.readyState) {\n this.emitReserved(\"packet\", packet);\n // Socket is live - any packet counts\n this.emitReserved(\"heartbeat\");\n this.resetPingTimeout();\n switch (packet.type) {\n case \"open\":\n this.onHandshake(JSON.parse(packet.data));\n break;\n case \"ping\":\n this.sendPacket(\"pong\");\n this.emitReserved(\"ping\");\n this.emitReserved(\"pong\");\n break;\n case \"error\":\n const err = new Error(\"server error\");\n // @ts-ignore\n err.code = packet.data;\n this.onError(err);\n break;\n case \"message\":\n this.emitReserved(\"data\", packet.data);\n this.emitReserved(\"message\", packet.data);\n break;\n }\n }\n else {\n }\n }\n /**\n * Called upon handshake completion.\n *\n * @param {Object} data - handshake obj\n * @private\n */\n onHandshake(data) {\n this.emitReserved(\"handshake\", data);\n this.id = data.sid;\n this.transport.query.sid = data.sid;\n this.upgrades = this.filterUpgrades(data.upgrades);\n this.pingInterval = data.pingInterval;\n this.pingTimeout = data.pingTimeout;\n this.maxPayload = data.maxPayload;\n this.onOpen();\n // In case open handler closes socket\n if (\"closed\" === this.readyState)\n return;\n this.resetPingTimeout();\n }\n /**\n * Sets and resets ping timeout timer based on server pings.\n *\n * @private\n */\n resetPingTimeout() {\n this.clearTimeoutFn(this.pingTimeoutTimer);\n this.pingTimeoutTimer = this.setTimeoutFn(() => {\n this.onClose(\"ping timeout\");\n }, this.pingInterval + this.pingTimeout);\n if (this.opts.autoUnref) {\n this.pingTimeoutTimer.unref();\n }\n }\n /**\n * Called on `drain` event\n *\n * @private\n */\n onDrain() {\n this.writeBuffer.splice(0, this.prevBufferLen);\n // setting prevBufferLen = 0 is very important\n // for example, when upgrading, upgrade packet is sent over,\n // and a nonzero prevBufferLen could cause problems on `drain`\n this.prevBufferLen = 0;\n if (0 === this.writeBuffer.length) {\n this.emitReserved(\"drain\");\n }\n else {\n this.flush();\n }\n }\n /**\n * Flush write buffers.\n *\n * @private\n */\n flush() {\n if (\"closed\" !== this.readyState &&\n this.transport.writable &&\n !this.upgrading &&\n this.writeBuffer.length) {\n const packets = this.getWritablePackets();\n this.transport.send(packets);\n // keep track of current length of writeBuffer\n // splice writeBuffer and callbackBuffer on `drain`\n this.prevBufferLen = packets.length;\n this.emitReserved(\"flush\");\n }\n }\n /**\n * Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP\n * long-polling)\n *\n * @private\n */\n getWritablePackets() {\n const shouldCheckPayloadSize = this.maxPayload &&\n this.transport.name === \"polling\" &&\n this.writeBuffer.length > 1;\n if (!shouldCheckPayloadSize) {\n return this.writeBuffer;\n }\n let payloadSize = 1; // first packet type\n for (let i = 0; i < this.writeBuffer.length; i++) {\n const data = this.writeBuffer[i].data;\n if (data) {\n payloadSize += byteLength(data);\n }\n if (i > 0 && payloadSize > this.maxPayload) {\n return this.writeBuffer.slice(0, i);\n }\n payloadSize += 2; // separator + packet type\n }\n return this.writeBuffer;\n }\n /**\n * Sends a message.\n *\n * @param {String} msg - message.\n * @param {Object} options.\n * @param {Function} callback function.\n * @return {Socket} for chaining.\n */\n write(msg, options, fn) {\n this.sendPacket(\"message\", msg, options, fn);\n return this;\n }\n send(msg, options, fn) {\n this.sendPacket(\"message\", msg, options, fn);\n return this;\n }\n /**\n * Sends a packet.\n *\n * @param {String} type: packet type.\n * @param {String} data.\n * @param {Object} options.\n * @param {Function} fn - callback function.\n * @private\n */\n sendPacket(type, data, options, fn) {\n if (\"function\" === typeof data) {\n fn = data;\n data = undefined;\n }\n if (\"function\" === typeof options) {\n fn = options;\n options = null;\n }\n if (\"closing\" === this.readyState || \"closed\" === this.readyState) {\n return;\n }\n options = options || {};\n options.compress = false !== options.compress;\n const packet = {\n type: type,\n data: data,\n options: options,\n };\n this.emitReserved(\"packetCreate\", packet);\n this.writeBuffer.push(packet);\n if (fn)\n this.once(\"flush\", fn);\n this.flush();\n }\n /**\n * Closes the connection.\n */\n close() {\n const close = () => {\n this.onClose(\"forced close\");\n this.transport.close();\n };\n const cleanupAndClose = () => {\n this.off(\"upgrade\", cleanupAndClose);\n this.off(\"upgradeError\", cleanupAndClose);\n close();\n };\n const waitForUpgrade = () => {\n // wait for upgrade to finish since we can't send packets while pausing a transport\n this.once(\"upgrade\", cleanupAndClose);\n this.once(\"upgradeError\", cleanupAndClose);\n };\n if (\"opening\" === this.readyState || \"open\" === this.readyState) {\n this.readyState = \"closing\";\n if (this.writeBuffer.length) {\n this.once(\"drain\", () => {\n if (this.upgrading) {\n waitForUpgrade();\n }\n else {\n close();\n }\n });\n }\n else if (this.upgrading) {\n waitForUpgrade();\n }\n else {\n close();\n }\n }\n return this;\n }\n /**\n * Called upon transport error\n *\n * @private\n */\n onError(err) {\n Socket.priorWebsocketSuccess = false;\n this.emitReserved(\"error\", err);\n this.onClose(\"transport error\", err);\n }\n /**\n * Called upon transport close.\n *\n * @private\n */\n onClose(reason, description) {\n if (\"opening\" === this.readyState ||\n \"open\" === this.readyState ||\n \"closing\" === this.readyState) {\n // clear timers\n this.clearTimeoutFn(this.pingTimeoutTimer);\n // stop event from firing again for transport\n this.transport.removeAllListeners(\"close\");\n // ensure transport won't stay open\n this.transport.close();\n // ignore further transport communication\n this.transport.removeAllListeners();\n if (typeof removeEventListener === \"function\") {\n removeEventListener(\"beforeunload\", this.beforeunloadEventListener, false);\n removeEventListener(\"offline\", this.offlineEventListener, false);\n }\n // set ready state\n this.readyState = \"closed\";\n // clear session id\n this.id = null;\n // emit close event\n this.emitReserved(\"close\", reason, description);\n // clean buffers after, so users can still\n // grab the buffers on `close` event\n this.writeBuffer = [];\n this.prevBufferLen = 0;\n }\n }\n /**\n * Filters upgrades, returning only those matching client transports.\n *\n * @param {Array} upgrades - server upgrades\n * @private\n */\n filterUpgrades(upgrades) {\n const filteredUpgrades = [];\n let i = 0;\n const j = upgrades.length;\n for (; i < j; i++) {\n if (~this.transports.indexOf(upgrades[i]))\n filteredUpgrades.push(upgrades[i]);\n }\n return filteredUpgrades;\n }\n}\nSocket.protocol = protocol;\n","import { Socket } from \"./socket.js\";\nexport { Socket };\nexport const protocol = Socket.protocol;\nexport { Transport } from \"./transport.js\";\nexport { transports } from \"./transports/index.js\";\nexport { installTimerFunctions } from \"./util.js\";\nexport { parse } from \"./contrib/parseuri.js\";\nexport { nextTick } from \"./transports/websocket-constructor.js\";\n","'use strict';\n\nfunction utf8Write(view, offset, str) {\n var c = 0;\n for (var i = 0, l = str.length; i < l; i++) {\n c = str.charCodeAt(i);\n if (c < 0x80) {\n view.setUint8(offset++, c);\n }\n else if (c < 0x800) {\n view.setUint8(offset++, 0xc0 | (c >> 6));\n view.setUint8(offset++, 0x80 | (c & 0x3f));\n }\n else if (c < 0xd800 || c >= 0xe000) {\n view.setUint8(offset++, 0xe0 | (c >> 12));\n view.setUint8(offset++, 0x80 | (c >> 6) & 0x3f);\n view.setUint8(offset++, 0x80 | (c & 0x3f));\n }\n else {\n i++;\n c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));\n view.setUint8(offset++, 0xf0 | (c >> 18));\n view.setUint8(offset++, 0x80 | (c >> 12) & 0x3f);\n view.setUint8(offset++, 0x80 | (c >> 6) & 0x3f);\n view.setUint8(offset++, 0x80 | (c & 0x3f));\n }\n }\n}\n\nfunction utf8Length(str) {\n var c = 0, length = 0;\n for (var i = 0, l = str.length; i < l; i++) {\n c = str.charCodeAt(i);\n if (c < 0x80) {\n length += 1;\n }\n else if (c < 0x800) {\n length += 2;\n }\n else if (c < 0xd800 || c >= 0xe000) {\n length += 3;\n }\n else {\n i++;\n length += 4;\n }\n }\n return length;\n}\n\nfunction _encode(bytes, defers, value) {\n var type = typeof value, i = 0, l = 0, hi = 0, lo = 0, length = 0, size = 0;\n\n if (type === 'string') {\n length = utf8Length(value);\n\n // fixstr\n if (length < 0x20) {\n bytes.push(length | 0xa0);\n size = 1;\n }\n // str 8\n else if (length < 0x100) {\n bytes.push(0xd9, length);\n size = 2;\n }\n // str 16\n else if (length < 0x10000) {\n bytes.push(0xda, length >> 8, length);\n size = 3;\n }\n // str 32\n else if (length < 0x100000000) {\n bytes.push(0xdb, length >> 24, length >> 16, length >> 8, length);\n size = 5;\n } else {\n throw new Error('String too long');\n }\n defers.push({ _str: value, _length: length, _offset: bytes.length });\n return size + length;\n }\n if (type === 'number') {\n // TODO: encode to float 32?\n\n // float 64\n if (Math.floor(value) !== value || !isFinite(value)) {\n bytes.push(0xcb);\n defers.push({ _float: value, _length: 8, _offset: bytes.length });\n return 9;\n }\n\n if (value >= 0) {\n // positive fixnum\n if (value < 0x80) {\n bytes.push(value);\n return 1;\n }\n // uint 8\n if (value < 0x100) {\n bytes.push(0xcc, value);\n return 2;\n }\n // uint 16\n if (value < 0x10000) {\n bytes.push(0xcd, value >> 8, value);\n return 3;\n }\n // uint 32\n if (value < 0x100000000) {\n bytes.push(0xce, value >> 24, value >> 16, value >> 8, value);\n return 5;\n }\n // uint 64\n hi = (value / Math.pow(2, 32)) >> 0;\n lo = value >>> 0;\n bytes.push(0xcf, hi >> 24, hi >> 16, hi >> 8, hi, lo >> 24, lo >> 16, lo >> 8, lo);\n return 9;\n } else {\n // negative fixnum\n if (value >= -0x20) {\n bytes.push(value);\n return 1;\n }\n // int 8\n if (value >= -0x80) {\n bytes.push(0xd0, value);\n return 2;\n }\n // int 16\n if (value >= -0x8000) {\n bytes.push(0xd1, value >> 8, value);\n return 3;\n }\n // int 32\n if (value >= -0x80000000) {\n bytes.push(0xd2, value >> 24, value >> 16, value >> 8, value);\n return 5;\n }\n // int 64\n hi = Math.floor(value / Math.pow(2, 32));\n lo = value >>> 0;\n bytes.push(0xd3, hi >> 24, hi >> 16, hi >> 8, hi, lo >> 24, lo >> 16, lo >> 8, lo);\n return 9;\n }\n }\n if (type === 'object') {\n // nil\n if (value === null) {\n bytes.push(0xc0);\n return 1;\n }\n\n if (Array.isArray(value)) {\n length = value.length;\n\n // fixarray\n if (length < 0x10) {\n bytes.push(length | 0x90);\n size = 1;\n }\n // array 16\n else if (length < 0x10000) {\n bytes.push(0xdc, length >> 8, length);\n size = 3;\n }\n // array 32\n else if (length < 0x100000000) {\n bytes.push(0xdd, length >> 24, length >> 16, length >> 8, length);\n size = 5;\n } else {\n throw new Error('Array too large');\n }\n for (i = 0; i < length; i++) {\n size += _encode(bytes, defers, value[i]);\n }\n return size;\n }\n\n // fixext 8 / Date\n if (value instanceof Date) {\n var time = value.getTime();\n hi = Math.floor(time / Math.pow(2, 32));\n lo = time >>> 0;\n bytes.push(0xd7, 0, hi >> 24, hi >> 16, hi >> 8, hi, lo >> 24, lo >> 16, lo >> 8, lo);\n return 10;\n }\n\n if (value instanceof ArrayBuffer) {\n length = value.byteLength;\n\n // bin 8\n if (length < 0x100) {\n bytes.push(0xc4, length);\n size = 2;\n } else\n // bin 16\n if (length < 0x10000) {\n bytes.push(0xc5, length >> 8, length);\n size = 3;\n } else\n // bin 32\n if (length < 0x100000000) {\n bytes.push(0xc6, length >> 24, length >> 16, length >> 8, length);\n size = 5;\n } else {\n throw new Error('Buffer too large');\n }\n defers.push({ _bin: value, _length: length, _offset: bytes.length });\n return size + length;\n }\n\n if (typeof value.toJSON === 'function') {\n return _encode(bytes, defers, value.toJSON());\n }\n\n var keys = [], key = '';\n\n var allKeys = Object.keys(value);\n for (i = 0, l = allKeys.length; i < l; i++) {\n key = allKeys[i];\n if (typeof value[key] !== 'function') {\n keys.push(key);\n }\n }\n length = keys.length;\n\n // fixmap\n if (length < 0x10) {\n bytes.push(length | 0x80);\n size = 1;\n }\n // map 16\n else if (length < 0x10000) {\n bytes.push(0xde, length >> 8, length);\n size = 3;\n }\n // map 32\n else if (length < 0x100000000) {\n bytes.push(0xdf, length >> 24, length >> 16, length >> 8, length);\n size = 5;\n } else {\n throw new Error('Object too large');\n }\n\n for (i = 0; i < length; i++) {\n key = keys[i];\n size += _encode(bytes, defers, key);\n size += _encode(bytes, defers, value[key]);\n }\n return size;\n }\n // false/true\n if (type === 'boolean') {\n bytes.push(value ? 0xc3 : 0xc2);\n return 1;\n }\n // fixext 1 / undefined\n if (type === 'undefined') {\n bytes.push(0xd4, 0, 0);\n return 3;\n }\n throw new Error('Could not encode');\n}\n\nfunction encode(value) {\n var bytes = [];\n var defers = [];\n var size = _encode(bytes, defers, value);\n var buf = new ArrayBuffer(size);\n var view = new DataView(buf);\n\n var deferIndex = 0;\n var deferWritten = 0;\n var nextOffset = -1;\n if (defers.length > 0) {\n nextOffset = defers[0]._offset;\n }\n\n var defer, deferLength = 0, offset = 0;\n for (var i = 0, l = bytes.length; i < l; i++) {\n view.setUint8(deferWritten + i, bytes[i]);\n if (i + 1 !== nextOffset) { continue; }\n defer = defers[deferIndex];\n deferLength = defer._length;\n offset = deferWritten + nextOffset;\n if (defer._bin) {\n var bin = new Uint8Array(defer._bin);\n for (var j = 0; j < deferLength; j++) {\n view.setUint8(offset + j, bin[j]);\n }\n } else if (defer._str) {\n utf8Write(view, offset, defer._str);\n } else if (defer._float !== undefined) {\n view.setFloat64(offset, defer._float);\n }\n deferIndex++;\n deferWritten += deferLength;\n if (defers[deferIndex]) {\n nextOffset = defers[deferIndex]._offset;\n }\n }\n return buf;\n}\n\nmodule.exports = encode;\n","'use strict';\n\nfunction Decoder(buffer) {\n this._offset = 0;\n if (buffer instanceof ArrayBuffer) {\n this._buffer = buffer;\n this._view = new DataView(this._buffer);\n } else if (ArrayBuffer.isView(buffer)) {\n this._buffer = buffer.buffer;\n this._view = new DataView(this._buffer, buffer.byteOffset, buffer.byteLength);\n } else {\n throw new Error('Invalid argument');\n }\n}\n\nfunction utf8Read(view, offset, length) {\n var string = '', chr = 0;\n for (var i = offset, end = offset + length; i < end; i++) {\n var byte = view.getUint8(i);\n if ((byte & 0x80) === 0x00) {\n string += String.fromCharCode(byte);\n continue;\n }\n if ((byte & 0xe0) === 0xc0) {\n string += String.fromCharCode(\n ((byte & 0x1f) << 6) |\n (view.getUint8(++i) & 0x3f)\n );\n continue;\n }\n if ((byte & 0xf0) === 0xe0) {\n string += String.fromCharCode(\n ((byte & 0x0f) << 12) |\n ((view.getUint8(++i) & 0x3f) << 6) |\n ((view.getUint8(++i) & 0x3f) << 0)\n );\n continue;\n }\n if ((byte & 0xf8) === 0xf0) {\n chr = ((byte & 0x07) << 18) |\n ((view.getUint8(++i) & 0x3f) << 12) |\n ((view.getUint8(++i) & 0x3f) << 6) |\n ((view.getUint8(++i) & 0x3f) << 0);\n if (chr >= 0x010000) { // surrogate pair\n chr -= 0x010000;\n string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);\n } else {\n string += String.fromCharCode(chr);\n }\n continue;\n }\n throw new Error('Invalid byte ' + byte.toString(16));\n }\n return string;\n}\n\nDecoder.prototype._array = function (length) {\n var value = new Array(length);\n for (var i = 0; i < length; i++) {\n value[i] = this._parse();\n }\n return value;\n};\n\nDecoder.prototype._map = function (length) {\n var key = '', value = {};\n for (var i = 0; i < length; i++) {\n key = this._parse();\n value[key] = this._parse();\n }\n return value;\n};\n\nDecoder.prototype._str = function (length) {\n var value = utf8Read(this._view, this._offset, length);\n this._offset += length;\n return value;\n};\n\nDecoder.prototype._bin = function (length) {\n var value = this._buffer.slice(this._offset, this._offset + length);\n this._offset += length;\n return value;\n};\n\nDecoder.prototype._parse = function () {\n var prefix = this._view.getUint8(this._offset++);\n var value, length = 0, type = 0, hi = 0, lo = 0;\n\n if (prefix < 0xc0) {\n // positive fixint\n if (prefix < 0x80) {\n return prefix;\n }\n // fixmap\n if (prefix < 0x90) {\n return this._map(prefix & 0x0f);\n }\n // fixarray\n if (prefix < 0xa0) {\n return this._array(prefix & 0x0f);\n }\n // fixstr\n return this._str(prefix & 0x1f);\n }\n\n // negative fixint\n if (prefix > 0xdf) {\n return (0xff - prefix + 1) * -1;\n }\n\n switch (prefix) {\n // nil\n case 0xc0:\n return null;\n // false\n case 0xc2:\n return false;\n // true\n case 0xc3:\n return true;\n\n // bin\n case 0xc4:\n length = this._view.getUint8(this._offset);\n this._offset += 1;\n return this._bin(length);\n case 0xc5:\n length = this._view.getUint16(this._offset);\n this._offset += 2;\n return this._bin(length);\n case 0xc6:\n length = this._view.getUint32(this._offset);\n this._offset += 4;\n return this._bin(length);\n\n // ext\n case 0xc7:\n length = this._view.getUint8(this._offset);\n type = this._view.getInt8(this._offset + 1);\n this._offset += 2;\n return [type, this._bin(length)];\n case 0xc8:\n length = this._view.getUint16(this._offset);\n type = this._view.getInt8(this._offset + 2);\n this._offset += 3;\n return [type, this._bin(length)];\n case 0xc9:\n length = this._view.getUint32(this._offset);\n type = this._view.getInt8(this._offset + 4);\n this._offset += 5;\n return [type, this._bin(length)];\n\n // float\n case 0xca:\n value = this._view.getFloat32(this._offset);\n this._offset += 4;\n return value;\n case 0xcb:\n value = this._view.getFloat64(this._offset);\n this._offset += 8;\n return value;\n\n // uint\n case 0xcc:\n value = this._view.getUint8(this._offset);\n this._offset += 1;\n return value;\n case 0xcd:\n value = this._view.getUint16(this._offset);\n this._offset += 2;\n return value;\n case 0xce:\n value = this._view.getUint32(this._offset);\n this._offset += 4;\n return value;\n case 0xcf:\n hi = this._view.getUint32(this._offset) * Math.pow(2, 32);\n lo = this._view.getUint32(this._offset + 4);\n this._offset += 8;\n return hi + lo;\n\n // int\n case 0xd0:\n value = this._view.getInt8(this._offset);\n this._offset += 1;\n return value;\n case 0xd1:\n value = this._view.getInt16(this._offset);\n this._offset += 2;\n return value;\n case 0xd2:\n value = this._view.getInt32(this._offset);\n this._offset += 4;\n return value;\n case 0xd3:\n hi = this._view.getInt32(this._offset) * Math.pow(2, 32);\n lo = this._view.getUint32(this._offset + 4);\n this._offset += 8;\n return hi + lo;\n\n // fixext\n case 0xd4:\n type = this._view.getInt8(this._offset);\n this._offset += 1;\n if (type === 0x00) {\n this._offset += 1;\n return void 0;\n }\n return [type, this._bin(1)];\n case 0xd5:\n type = this._view.getInt8(this._offset);\n this._offset += 1;\n return [type, this._bin(2)];\n case 0xd6:\n type = this._view.getInt8(this._offset);\n this._offset += 1;\n return [type, this._bin(4)];\n case 0xd7:\n type = this._view.getInt8(this._offset);\n this._offset += 1;\n if (type === 0x00) {\n hi = this._view.getInt32(this._offset) * Math.pow(2, 32);\n lo = this._view.getUint32(this._offset + 4);\n this._offset += 8;\n return new Date(hi + lo);\n }\n return [type, this._bin(8)];\n case 0xd8:\n type = this._view.getInt8(this._offset);\n this._offset += 1;\n return [type, this._bin(16)];\n\n // str\n case 0xd9:\n length = this._view.getUint8(this._offset);\n this._offset += 1;\n return this._str(length);\n case 0xda:\n length = this._view.getUint16(this._offset);\n this._offset += 2;\n return this._str(length);\n case 0xdb:\n length = this._view.getUint32(this._offset);\n this._offset += 4;\n return this._str(length);\n\n // array\n case 0xdc:\n length = this._view.getUint16(this._offset);\n this._offset += 2;\n return this._array(length);\n case 0xdd:\n length = this._view.getUint32(this._offset);\n this._offset += 4;\n return this._array(length);\n\n // map\n case 0xde:\n length = this._view.getUint16(this._offset);\n this._offset += 2;\n return this._map(length);\n case 0xdf:\n length = this._view.getUint32(this._offset);\n this._offset += 4;\n return this._map(length);\n }\n\n throw new Error('Could not parse');\n};\n\nfunction decode(buffer) {\n var decoder = new Decoder(buffer);\n var value = decoder._parse();\n if (decoder._offset !== buffer.byteLength) {\n throw new Error((buffer.byteLength - decoder._offset) + ' trailing bytes');\n }\n return value;\n}\n\nmodule.exports = decode;\n","exports.encode = require('./encode');\nexports.decode = require('./decode');\n","\r\n/**\r\n * Expose `Emitter`.\r\n */\r\n\r\nif (typeof module !== 'undefined') {\r\n module.exports = Emitter;\r\n}\r\n\r\n/**\r\n * Initialize a new `Emitter`.\r\n *\r\n * @api public\r\n */\r\n\r\nfunction Emitter(obj) {\r\n if (obj) return mixin(obj);\r\n};\r\n\r\n/**\r\n * Mixin the emitter properties.\r\n *\r\n * @param {Object} obj\r\n * @return {Object}\r\n * @api private\r\n */\r\n\r\nfunction mixin(obj) {\r\n for (var key in Emitter.prototype) {\r\n obj[key] = Emitter.prototype[key];\r\n }\r\n return obj;\r\n}\r\n\r\n/**\r\n * Listen on the given `event` with `fn`.\r\n *\r\n * @param {String} event\r\n * @param {Function} fn\r\n * @return {Emitter}\r\n * @api public\r\n */\r\n\r\nEmitter.prototype.on =\r\nEmitter.prototype.addEventListener = function(event, fn){\r\n this._callbacks = this._callbacks || {};\r\n (this._callbacks['$' + event] = this._callbacks['$' + event] || [])\r\n .push(fn);\r\n return this;\r\n};\r\n\r\n/**\r\n * Adds an `event` listener that will be invoked a single\r\n * time then automatically removed.\r\n *\r\n * @param {String} event\r\n * @param {Function} fn\r\n * @return {Emitter}\r\n * @api public\r\n */\r\n\r\nEmitter.prototype.once = function(event, fn){\r\n function on() {\r\n this.off(event, on);\r\n fn.apply(this, arguments);\r\n }\r\n\r\n on.fn = fn;\r\n this.on(event, on);\r\n return this;\r\n};\r\n\r\n/**\r\n * Remove the given callback for `event` or all\r\n * registered callbacks.\r\n *\r\n * @param {String} event\r\n * @param {Function} fn\r\n * @return {Emitter}\r\n * @api public\r\n */\r\n\r\nEmitter.prototype.off =\r\nEmitter.prototype.removeListener =\r\nEmitter.prototype.removeAllListeners =\r\nEmitter.prototype.removeEventListener = function(event, fn){\r\n this._callbacks = this._callbacks || {};\r\n\r\n // all\r\n if (0 == arguments.length) {\r\n this._callbacks = {};\r\n return this;\r\n }\r\n\r\n // specific event\r\n var callbacks = this._callbacks['$' + event];\r\n if (!callbacks) return this;\r\n\r\n // remove all handlers\r\n if (1 == arguments.length) {\r\n delete this._callbacks['$' + event];\r\n return this;\r\n }\r\n\r\n // remove specific handler\r\n var cb;\r\n for (var i = 0; i < callbacks.length; i++) {\r\n cb = callbacks[i];\r\n if (cb === fn || cb.fn === fn) {\r\n callbacks.splice(i, 1);\r\n break;\r\n }\r\n }\r\n\r\n // Remove event specific arrays for event types that no\r\n // one is subscribed for to avoid memory leak.\r\n if (callbacks.length === 0) {\r\n delete this._callbacks['$' + event];\r\n }\r\n\r\n return this;\r\n};\r\n\r\n/**\r\n * Emit `event` with the given args.\r\n *\r\n * @param {String} event\r\n * @param {Mixed} ...\r\n * @return {Emitter}\r\n */\r\n\r\nEmitter.prototype.emit = function(event){\r\n this._callbacks = this._callbacks || {};\r\n\r\n var args = new Array(arguments.length - 1)\r\n , callbacks = this._callbacks['$' + event];\r\n\r\n for (var i = 1; i < arguments.length; i++) {\r\n args[i - 1] = arguments[i];\r\n }\r\n\r\n if (callbacks) {\r\n callbacks = callbacks.slice(0);\r\n for (var i = 0, len = callbacks.length; i < len; ++i) {\r\n callbacks[i].apply(this, args);\r\n }\r\n }\r\n\r\n return this;\r\n};\r\n\r\n/**\r\n * Return array of callbacks for `event`.\r\n *\r\n * @param {String} event\r\n * @return {Array}\r\n * @api public\r\n */\r\n\r\nEmitter.prototype.listeners = function(event){\r\n this._callbacks = this._callbacks || {};\r\n return this._callbacks['$' + event] || [];\r\n};\r\n\r\n/**\r\n * Check if this emitter has `event` handlers.\r\n *\r\n * @param {String} event\r\n * @return {Boolean}\r\n * @api public\r\n */\r\n\r\nEmitter.prototype.hasListeners = function(event){\r\n return !! this.listeners(event).length;\r\n};\r\n","var msgpack = require(\"notepack.io\");\nvar Emitter = require(\"component-emitter\");\n\nexports.protocol = 5;\n\n/**\n * Packet types (see https://github.com/socketio/socket.io-protocol)\n */\n\nvar PacketType = (exports.PacketType = {\n CONNECT: 0,\n DISCONNECT: 1,\n EVENT: 2,\n ACK: 3,\n CONNECT_ERROR: 4,\n});\n\nvar isInteger =\n Number.isInteger ||\n function (value) {\n return (\n typeof value === \"number\" &&\n isFinite(value) &&\n Math.floor(value) === value\n );\n };\n\nvar isString = function (value) {\n return typeof value === \"string\";\n};\n\nvar isObject = function (value) {\n return Object.prototype.toString.call(value) === \"[object Object]\";\n};\n\nfunction Encoder() {}\n\nEncoder.prototype.encode = function (packet) {\n return [msgpack.encode(packet)];\n};\n\nfunction Decoder() {}\n\nEmitter(Decoder.prototype);\n\nDecoder.prototype.add = function (obj) {\n var decoded = msgpack.decode(obj);\n this.checkPacket(decoded);\n this.emit(\"decoded\", decoded);\n};\n\nfunction isDataValid(decoded) {\n switch (decoded.type) {\n case PacketType.CONNECT:\n return decoded.data === undefined || isObject(decoded.data);\n case PacketType.DISCONNECT:\n return decoded.data === undefined;\n case PacketType.CONNECT_ERROR:\n return isString(decoded.data) || isObject(decoded.data);\n default:\n return Array.isArray(decoded.data);\n }\n}\n\nDecoder.prototype.checkPacket = function (decoded) {\n var isTypeValid =\n isInteger(decoded.type) &&\n decoded.type >= PacketType.CONNECT &&\n decoded.type <= PacketType.CONNECT_ERROR;\n if (!isTypeValid) {\n throw new Error(\"invalid packet type\");\n }\n\n if (!isString(decoded.nsp)) {\n throw new Error(\"invalid namespace\");\n }\n\n if (!isDataValid(decoded)) {\n throw new Error(\"invalid payload\");\n }\n\n var isAckValid = decoded.id === undefined || isInteger(decoded.id);\n if (!isAckValid) {\n throw new Error(\"invalid packet id\");\n }\n};\n\nDecoder.prototype.destroy = function () {};\n\nexports.Encoder = Encoder;\nexports.Decoder = Decoder;\n","export function on(obj, ev, fn) {\n obj.on(ev, fn);\n return function subDestroy() {\n obj.off(ev, fn);\n };\n}\n","import { PacketType } from \"socket.io-parser\";\nimport { on } from \"./on.js\";\nimport { Emitter, } from \"@socket.io/component-emitter\";\n/**\n * Internal events.\n * These events can't be emitted by the user.\n */\nconst RESERVED_EVENTS = Object.freeze({\n connect: 1,\n connect_error: 1,\n disconnect: 1,\n disconnecting: 1,\n // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener\n newListener: 1,\n removeListener: 1,\n});\n/**\n * A Socket is the fundamental class for interacting with the server.\n *\n * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(\"connected\");\n * });\n *\n * // send an event to the server\n * socket.emit(\"foo\", \"bar\");\n *\n * socket.on(\"foobar\", () => {\n * // an event was received from the server\n * });\n *\n * // upon disconnection\n * socket.on(\"disconnect\", (reason) => {\n * console.log(`disconnected due to ${reason}`);\n * });\n */\nexport class Socket extends Emitter {\n /**\n * `Socket` constructor.\n */\n constructor(io, nsp, opts) {\n super();\n /**\n * Whether the socket is currently connected to the server.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.connected); // true\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.connected); // false\n * });\n */\n this.connected = false;\n /**\n * Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will\n * be transmitted by the server.\n */\n this.recovered = false;\n /**\n * Buffer for packets received before the CONNECT packet\n */\n this.receiveBuffer = [];\n /**\n * Buffer for packets that will be sent once the socket is connected\n */\n this.sendBuffer = [];\n /**\n * The queue of packets to be sent with retry in case of failure.\n *\n * Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.\n * @private\n */\n this._queue = [];\n /**\n * A sequence to generate the ID of the {@link QueuedPacket}.\n * @private\n */\n this._queueSeq = 0;\n this.ids = 0;\n this.acks = {};\n this.flags = {};\n this.io = io;\n this.nsp = nsp;\n if (opts && opts.auth) {\n this.auth = opts.auth;\n }\n this._opts = Object.assign({}, opts);\n if (this.io._autoConnect)\n this.open();\n }\n /**\n * Whether the socket is currently disconnected\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"connect\", () => {\n * console.log(socket.disconnected); // false\n * });\n *\n * socket.on(\"disconnect\", () => {\n * console.log(socket.disconnected); // true\n * });\n */\n get disconnected() {\n return !this.connected;\n }\n /**\n * Subscribe to open, close and packet events\n *\n * @private\n */\n subEvents() {\n if (this.subs)\n return;\n const io = this.io;\n this.subs = [\n on(io, \"open\", this.onopen.bind(this)),\n on(io, \"packet\", this.onpacket.bind(this)),\n on(io, \"error\", this.onerror.bind(this)),\n on(io, \"close\", this.onclose.bind(this)),\n ];\n }\n /**\n * Whether the Socket will try to reconnect when its Manager connects or reconnects.\n *\n * @example\n * const socket = io();\n *\n * console.log(socket.active); // true\n *\n * socket.on(\"disconnect\", (reason) => {\n * if (reason === \"io server disconnect\") {\n * // the disconnection was initiated by the server, you need to manually reconnect\n * console.log(socket.active); // false\n * }\n * // else the socket will automatically try to reconnect\n * console.log(socket.active); // true\n * });\n */\n get active() {\n return !!this.subs;\n }\n /**\n * \"Opens\" the socket.\n *\n * @example\n * const socket = io({\n * autoConnect: false\n * });\n *\n * socket.connect();\n */\n connect() {\n if (this.connected)\n return this;\n this.subEvents();\n if (!this.io[\"_reconnecting\"])\n this.io.open(); // ensure open\n if (\"open\" === this.io._readyState)\n this.onopen();\n return this;\n }\n /**\n * Alias for {@link connect()}.\n */\n open() {\n return this.connect();\n }\n /**\n * Sends a `message` event.\n *\n * This method mimics the WebSocket.send() method.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send\n *\n * @example\n * socket.send(\"hello\");\n *\n * // this is equivalent to\n * socket.emit(\"message\", \"hello\");\n *\n * @return self\n */\n send(...args) {\n args.unshift(\"message\");\n this.emit.apply(this, args);\n return this;\n }\n /**\n * Override `emit`.\n * If the event is in `events`, it's emitted normally.\n *\n * @example\n * socket.emit(\"hello\", \"world\");\n *\n * // all serializable datastructures are supported (no need to call JSON.stringify)\n * socket.emit(\"hello\", 1, \"2\", { 3: [\"4\"], 5: Uint8Array.from([6]) });\n *\n * // with an acknowledgement from the server\n * socket.emit(\"hello\", \"world\", (val) => {\n * // ...\n * });\n *\n * @return self\n */\n emit(ev, ...args) {\n if (RESERVED_EVENTS.hasOwnProperty(ev)) {\n throw new Error('\"' + ev.toString() + '\" is a reserved event name');\n }\n args.unshift(ev);\n if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {\n this._addToQueue(args);\n return this;\n }\n const packet = {\n type: PacketType.EVENT,\n data: args,\n };\n packet.options = {};\n packet.options.compress = this.flags.compress !== false;\n // event ack callback\n if (\"function\" === typeof args[args.length - 1]) {\n const id = this.ids++;\n const ack = args.pop();\n this._registerAckCallback(id, ack);\n packet.id = id;\n }\n const isTransportWritable = this.io.engine &&\n this.io.engine.transport &&\n this.io.engine.transport.writable;\n const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);\n if (discardPacket) {\n }\n else if (this.connected) {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n }\n else {\n this.sendBuffer.push(packet);\n }\n this.flags = {};\n return this;\n }\n /**\n * @private\n */\n _registerAckCallback(id, ack) {\n var _a;\n const timeout = (_a = this.flags.timeout) !== null && _a !== void 0 ? _a : this._opts.ackTimeout;\n if (timeout === undefined) {\n this.acks[id] = ack;\n return;\n }\n // @ts-ignore\n const timer = this.io.setTimeoutFn(() => {\n delete this.acks[id];\n for (let i = 0; i < this.sendBuffer.length; i++) {\n if (this.sendBuffer[i].id === id) {\n this.sendBuffer.splice(i, 1);\n }\n }\n ack.call(this, new Error(\"operation has timed out\"));\n }, timeout);\n this.acks[id] = (...args) => {\n // @ts-ignore\n this.io.clearTimeoutFn(timer);\n ack.apply(this, [null, ...args]);\n };\n }\n /**\n * Emits an event and waits for an acknowledgement\n *\n * @example\n * // without timeout\n * const response = await socket.emitWithAck(\"hello\", \"world\");\n *\n * // with a specific timeout\n * try {\n * const response = await socket.timeout(1000).emitWithAck(\"hello\", \"world\");\n * } catch (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n *\n * @return a Promise that will be fulfilled when the server acknowledges the event\n */\n emitWithAck(ev, ...args) {\n // the timeout flag is optional\n const withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;\n return new Promise((resolve, reject) => {\n args.push((arg1, arg2) => {\n if (withErr) {\n return arg1 ? reject(arg1) : resolve(arg2);\n }\n else {\n return resolve(arg1);\n }\n });\n this.emit(ev, ...args);\n });\n }\n /**\n * Add the packet to the queue.\n * @param args\n * @private\n */\n _addToQueue(args) {\n let ack;\n if (typeof args[args.length - 1] === \"function\") {\n ack = args.pop();\n }\n const packet = {\n id: this._queueSeq++,\n tryCount: 0,\n pending: false,\n args,\n flags: Object.assign({ fromQueue: true }, this.flags),\n };\n args.push((err, ...responseArgs) => {\n if (packet !== this._queue[0]) {\n // the packet has already been acknowledged\n return;\n }\n const hasError = err !== null;\n if (hasError) {\n if (packet.tryCount > this._opts.retries) {\n this._queue.shift();\n if (ack) {\n ack(err);\n }\n }\n }\n else {\n this._queue.shift();\n if (ack) {\n ack(null, ...responseArgs);\n }\n }\n packet.pending = false;\n return this._drainQueue();\n });\n this._queue.push(packet);\n this._drainQueue();\n }\n /**\n * Send the first packet of the queue, and wait for an acknowledgement from the server.\n * @param force - whether to resend a packet that has not been acknowledged yet\n *\n * @private\n */\n _drainQueue(force = false) {\n if (!this.connected || this._queue.length === 0) {\n return;\n }\n const packet = this._queue[0];\n if (packet.pending && !force) {\n return;\n }\n packet.pending = true;\n packet.tryCount++;\n this.flags = packet.flags;\n this.emit.apply(this, packet.args);\n }\n /**\n * Sends a packet.\n *\n * @param packet\n * @private\n */\n packet(packet) {\n packet.nsp = this.nsp;\n this.io._packet(packet);\n }\n /**\n * Called upon engine `open`.\n *\n * @private\n */\n onopen() {\n if (typeof this.auth == \"function\") {\n this.auth((data) => {\n this._sendConnectPacket(data);\n });\n }\n else {\n this._sendConnectPacket(this.auth);\n }\n }\n /**\n * Sends a CONNECT packet to initiate the Socket.IO session.\n *\n * @param data\n * @private\n */\n _sendConnectPacket(data) {\n this.packet({\n type: PacketType.CONNECT,\n data: this._pid\n ? Object.assign({ pid: this._pid, offset: this._lastOffset }, data)\n : data,\n });\n }\n /**\n * Called upon engine or manager `error`.\n *\n * @param err\n * @private\n */\n onerror(err) {\n if (!this.connected) {\n this.emitReserved(\"connect_error\", err);\n }\n }\n /**\n * Called upon engine `close`.\n *\n * @param reason\n * @param description\n * @private\n */\n onclose(reason, description) {\n this.connected = false;\n delete this.id;\n this.emitReserved(\"disconnect\", reason, description);\n }\n /**\n * Called with socket packet.\n *\n * @param packet\n * @private\n */\n onpacket(packet) {\n const sameNamespace = packet.nsp === this.nsp;\n if (!sameNamespace)\n return;\n switch (packet.type) {\n case PacketType.CONNECT:\n if (packet.data && packet.data.sid) {\n this.onconnect(packet.data.sid, packet.data.pid);\n }\n else {\n 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/)\"));\n }\n break;\n case PacketType.EVENT:\n case PacketType.BINARY_EVENT:\n this.onevent(packet);\n break;\n case PacketType.ACK:\n case PacketType.BINARY_ACK:\n this.onack(packet);\n break;\n case PacketType.DISCONNECT:\n this.ondisconnect();\n break;\n case PacketType.CONNECT_ERROR:\n this.destroy();\n const err = new Error(packet.data.message);\n // @ts-ignore\n err.data = packet.data.data;\n this.emitReserved(\"connect_error\", err);\n break;\n }\n }\n /**\n * Called upon a server event.\n *\n * @param packet\n * @private\n */\n onevent(packet) {\n const args = packet.data || [];\n if (null != packet.id) {\n args.push(this.ack(packet.id));\n }\n if (this.connected) {\n this.emitEvent(args);\n }\n else {\n this.receiveBuffer.push(Object.freeze(args));\n }\n }\n emitEvent(args) {\n if (this._anyListeners && this._anyListeners.length) {\n const listeners = this._anyListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, args);\n }\n }\n super.emit.apply(this, args);\n if (this._pid && args.length && typeof args[args.length - 1] === \"string\") {\n this._lastOffset = args[args.length - 1];\n }\n }\n /**\n * Produces an ack callback to emit with an event.\n *\n * @private\n */\n ack(id) {\n const self = this;\n let sent = false;\n return function (...args) {\n // prevent double callbacks\n if (sent)\n return;\n sent = true;\n self.packet({\n type: PacketType.ACK,\n id: id,\n data: args,\n });\n };\n }\n /**\n * Called upon a server acknowlegement.\n *\n * @param packet\n * @private\n */\n onack(packet) {\n const ack = this.acks[packet.id];\n if (\"function\" === typeof ack) {\n ack.apply(this, packet.data);\n delete this.acks[packet.id];\n }\n else {\n }\n }\n /**\n * Called upon server connect.\n *\n * @private\n */\n onconnect(id, pid) {\n this.id = id;\n this.recovered = pid && this._pid === pid;\n this._pid = pid; // defined only if connection state recovery is enabled\n this.connected = true;\n this.emitBuffered();\n this.emitReserved(\"connect\");\n this._drainQueue(true);\n }\n /**\n * Emit buffered events (received and emitted).\n *\n * @private\n */\n emitBuffered() {\n this.receiveBuffer.forEach((args) => this.emitEvent(args));\n this.receiveBuffer = [];\n this.sendBuffer.forEach((packet) => {\n this.notifyOutgoingListeners(packet);\n this.packet(packet);\n });\n this.sendBuffer = [];\n }\n /**\n * Called upon server disconnect.\n *\n * @private\n */\n ondisconnect() {\n this.destroy();\n this.onclose(\"io server disconnect\");\n }\n /**\n * Called upon forced client/server side disconnections,\n * this method ensures the manager stops tracking us and\n * that reconnections don't get triggered for this.\n *\n * @private\n */\n destroy() {\n if (this.subs) {\n // clean subscriptions to avoid reconnections\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs = undefined;\n }\n this.io[\"_destroy\"](this);\n }\n /**\n * Disconnects the socket manually. In that case, the socket will not try to reconnect.\n *\n * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.\n *\n * @example\n * const socket = io();\n *\n * socket.on(\"disconnect\", (reason) => {\n * // console.log(reason); prints \"io client disconnect\"\n * });\n *\n * socket.disconnect();\n *\n * @return self\n */\n disconnect() {\n if (this.connected) {\n this.packet({ type: PacketType.DISCONNECT });\n }\n // remove socket from pool\n this.destroy();\n if (this.connected) {\n // fire events\n this.onclose(\"io client disconnect\");\n }\n return this;\n }\n /**\n * Alias for {@link disconnect()}.\n *\n * @return self\n */\n close() {\n return this.disconnect();\n }\n /**\n * Sets the compress flag.\n *\n * @example\n * socket.compress(false).emit(\"hello\");\n *\n * @param compress - if `true`, compresses the sending data\n * @return self\n */\n compress(compress) {\n this.flags.compress = compress;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not\n * ready to send messages.\n *\n * @example\n * socket.volatile.emit(\"hello\"); // the server may or may not receive it\n *\n * @returns self\n */\n get volatile() {\n this.flags.volatile = true;\n return this;\n }\n /**\n * Sets a modifier for a subsequent event emission that the callback will be called with an error when the\n * given number of milliseconds have elapsed without an acknowledgement from the server:\n *\n * @example\n * socket.timeout(5000).emit(\"my-event\", (err) => {\n * if (err) {\n * // the server did not acknowledge the event in the given delay\n * }\n * });\n *\n * @returns self\n */\n timeout(timeout) {\n this.flags.timeout = timeout;\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * @example\n * socket.onAny((event, ...args) => {\n * console.log(`got ${event}`);\n * });\n *\n * @param listener\n */\n onAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * @example\n * socket.prependAny((event, ...args) => {\n * console.log(`got event ${event}`);\n * });\n *\n * @param listener\n */\n prependAny(listener) {\n this._anyListeners = this._anyListeners || [];\n this._anyListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`got event ${event}`);\n * }\n *\n * socket.onAny(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAny(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAny();\n *\n * @param listener\n */\n offAny(listener) {\n if (!this._anyListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAny() {\n return this._anyListeners || [];\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.onAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n onAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.push(listener);\n return this;\n }\n /**\n * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the\n * callback. The listener is added to the beginning of the listeners array.\n *\n * Note: acknowledgements sent to the server are not included.\n *\n * @example\n * socket.prependAnyOutgoing((event, ...args) => {\n * console.log(`sent event ${event}`);\n * });\n *\n * @param listener\n */\n prependAnyOutgoing(listener) {\n this._anyOutgoingListeners = this._anyOutgoingListeners || [];\n this._anyOutgoingListeners.unshift(listener);\n return this;\n }\n /**\n * Removes the listener that will be fired when any event is emitted.\n *\n * @example\n * const catchAllListener = (event, ...args) => {\n * console.log(`sent event ${event}`);\n * }\n *\n * socket.onAnyOutgoing(catchAllListener);\n *\n * // remove a specific listener\n * socket.offAnyOutgoing(catchAllListener);\n *\n * // or remove all listeners\n * socket.offAnyOutgoing();\n *\n * @param [listener] - the catch-all listener (optional)\n */\n offAnyOutgoing(listener) {\n if (!this._anyOutgoingListeners) {\n return this;\n }\n if (listener) {\n const listeners = this._anyOutgoingListeners;\n for (let i = 0; i < listeners.length; i++) {\n if (listener === listeners[i]) {\n listeners.splice(i, 1);\n return this;\n }\n }\n }\n else {\n this._anyOutgoingListeners = [];\n }\n return this;\n }\n /**\n * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,\n * e.g. to remove listeners.\n */\n listenersAnyOutgoing() {\n return this._anyOutgoingListeners || [];\n }\n /**\n * Notify the listeners for each packet sent\n *\n * @param packet\n *\n * @private\n */\n notifyOutgoingListeners(packet) {\n if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {\n const listeners = this._anyOutgoingListeners.slice();\n for (const listener of listeners) {\n listener.apply(this, packet.data);\n }\n }\n }\n}\n","/**\n * Initialize backoff timer with `opts`.\n *\n * - `min` initial timeout in milliseconds [100]\n * - `max` max timeout [10000]\n * - `jitter` [0]\n * - `factor` [2]\n *\n * @param {Object} opts\n * @api public\n */\nexport function Backoff(opts) {\n opts = opts || {};\n this.ms = opts.min || 100;\n this.max = opts.max || 10000;\n this.factor = opts.factor || 2;\n this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;\n this.attempts = 0;\n}\n/**\n * Return the backoff duration.\n *\n * @return {Number}\n * @api public\n */\nBackoff.prototype.duration = function () {\n var ms = this.ms * Math.pow(this.factor, this.attempts++);\n if (this.jitter) {\n var rand = Math.random();\n var deviation = Math.floor(rand * this.jitter * ms);\n ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;\n }\n return Math.min(ms, this.max) | 0;\n};\n/**\n * Reset the number of attempts.\n *\n * @api public\n */\nBackoff.prototype.reset = function () {\n this.attempts = 0;\n};\n/**\n * Set the minimum duration\n *\n * @api public\n */\nBackoff.prototype.setMin = function (min) {\n this.ms = min;\n};\n/**\n * Set the maximum duration\n *\n * @api public\n */\nBackoff.prototype.setMax = function (max) {\n this.max = max;\n};\n/**\n * Set the jitter\n *\n * @api public\n */\nBackoff.prototype.setJitter = function (jitter) {\n this.jitter = jitter;\n};\n","import { Socket as Engine, installTimerFunctions, nextTick, } from \"engine.io-client\";\nimport { Socket } from \"./socket.js\";\nimport * as parser from \"socket.io-parser\";\nimport { on } from \"./on.js\";\nimport { Backoff } from \"./contrib/backo2.js\";\nimport { Emitter, } from \"@socket.io/component-emitter\";\nexport class Manager extends Emitter {\n constructor(uri, opts) {\n var _a;\n super();\n this.nsps = {};\n this.subs = [];\n if (uri && \"object\" === typeof uri) {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n opts.path = opts.path || \"/socket.io\";\n this.opts = opts;\n installTimerFunctions(this, opts);\n this.reconnection(opts.reconnection !== false);\n this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);\n this.reconnectionDelay(opts.reconnectionDelay || 1000);\n this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);\n this.randomizationFactor((_a = opts.randomizationFactor) !== null && _a !== void 0 ? _a : 0.5);\n this.backoff = new Backoff({\n min: this.reconnectionDelay(),\n max: this.reconnectionDelayMax(),\n jitter: this.randomizationFactor(),\n });\n this.timeout(null == opts.timeout ? 20000 : opts.timeout);\n this._readyState = \"closed\";\n this.uri = uri;\n const _parser = opts.parser || parser;\n this.encoder = new _parser.Encoder();\n this.decoder = new _parser.Decoder();\n this._autoConnect = opts.autoConnect !== false;\n if (this._autoConnect)\n this.open();\n }\n reconnection(v) {\n if (!arguments.length)\n return this._reconnection;\n this._reconnection = !!v;\n return this;\n }\n reconnectionAttempts(v) {\n if (v === undefined)\n return this._reconnectionAttempts;\n this._reconnectionAttempts = v;\n return this;\n }\n reconnectionDelay(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelay;\n this._reconnectionDelay = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMin(v);\n return this;\n }\n randomizationFactor(v) {\n var _a;\n if (v === undefined)\n return this._randomizationFactor;\n this._randomizationFactor = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setJitter(v);\n return this;\n }\n reconnectionDelayMax(v) {\n var _a;\n if (v === undefined)\n return this._reconnectionDelayMax;\n this._reconnectionDelayMax = v;\n (_a = this.backoff) === null || _a === void 0 ? void 0 : _a.setMax(v);\n return this;\n }\n timeout(v) {\n if (!arguments.length)\n return this._timeout;\n this._timeout = v;\n return this;\n }\n /**\n * Starts trying to reconnect if reconnection is enabled and we have not\n * started reconnecting yet\n *\n * @private\n */\n maybeReconnectOnOpen() {\n // Only try to reconnect if it's the first time we're connecting\n if (!this._reconnecting &&\n this._reconnection &&\n this.backoff.attempts === 0) {\n // keeps reconnection from firing twice for the same reconnection loop\n this.reconnect();\n }\n }\n /**\n * Sets the current transport `socket`.\n *\n * @param {Function} fn - optional, callback\n * @return self\n * @public\n */\n open(fn) {\n if (~this._readyState.indexOf(\"open\"))\n return this;\n this.engine = new Engine(this.uri, this.opts);\n const socket = this.engine;\n const self = this;\n this._readyState = \"opening\";\n this.skipReconnect = false;\n // emit `open`\n const openSubDestroy = on(socket, \"open\", function () {\n self.onopen();\n fn && fn();\n });\n const onError = (err) => {\n this.cleanup();\n this._readyState = \"closed\";\n this.emitReserved(\"error\", err);\n if (fn) {\n fn(err);\n }\n else {\n // Only do this if there is no fn to handle the error\n this.maybeReconnectOnOpen();\n }\n };\n // emit `error`\n const errorSub = on(socket, \"error\", onError);\n if (false !== this._timeout) {\n const timeout = this._timeout;\n // set timer\n const timer = this.setTimeoutFn(() => {\n openSubDestroy();\n onError(new Error(\"timeout\"));\n socket.close();\n }, timeout);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(() => {\n this.clearTimeoutFn(timer);\n });\n }\n this.subs.push(openSubDestroy);\n this.subs.push(errorSub);\n return this;\n }\n /**\n * Alias for open()\n *\n * @return self\n * @public\n */\n connect(fn) {\n return this.open(fn);\n }\n /**\n * Called upon transport open.\n *\n * @private\n */\n onopen() {\n // clear old subs\n this.cleanup();\n // mark as open\n this._readyState = \"open\";\n this.emitReserved(\"open\");\n // add new subs\n const socket = this.engine;\n this.subs.push(on(socket, \"ping\", this.onping.bind(this)), on(socket, \"data\", this.ondata.bind(this)), on(socket, \"error\", this.onerror.bind(this)), on(socket, \"close\", this.onclose.bind(this)), on(this.decoder, \"decoded\", this.ondecoded.bind(this)));\n }\n /**\n * Called upon a ping.\n *\n * @private\n */\n onping() {\n this.emitReserved(\"ping\");\n }\n /**\n * Called with data.\n *\n * @private\n */\n ondata(data) {\n try {\n this.decoder.add(data);\n }\n catch (e) {\n this.onclose(\"parse error\", e);\n }\n }\n /**\n * Called when parser fully decodes a packet.\n *\n * @private\n */\n ondecoded(packet) {\n // the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a \"parse error\"\n nextTick(() => {\n this.emitReserved(\"packet\", packet);\n }, this.setTimeoutFn);\n }\n /**\n * Called upon socket error.\n *\n * @private\n */\n onerror(err) {\n this.emitReserved(\"error\", err);\n }\n /**\n * Creates a new socket for the given `nsp`.\n *\n * @return {Socket}\n * @public\n */\n socket(nsp, opts) {\n let socket = this.nsps[nsp];\n if (!socket) {\n socket = new Socket(this, nsp, opts);\n this.nsps[nsp] = socket;\n }\n else if (this._autoConnect && !socket.active) {\n socket.connect();\n }\n return socket;\n }\n /**\n * Called upon a socket close.\n *\n * @param socket\n * @private\n */\n _destroy(socket) {\n const nsps = Object.keys(this.nsps);\n for (const nsp of nsps) {\n const socket = this.nsps[nsp];\n if (socket.active) {\n return;\n }\n }\n this._close();\n }\n /**\n * Writes a packet.\n *\n * @param packet\n * @private\n */\n _packet(packet) {\n const encodedPackets = this.encoder.encode(packet);\n for (let i = 0; i < encodedPackets.length; i++) {\n this.engine.write(encodedPackets[i], packet.options);\n }\n }\n /**\n * Clean up transport subscriptions and packet buffer.\n *\n * @private\n */\n cleanup() {\n this.subs.forEach((subDestroy) => subDestroy());\n this.subs.length = 0;\n this.decoder.destroy();\n }\n /**\n * Close the current socket.\n *\n * @private\n */\n _close() {\n this.skipReconnect = true;\n this._reconnecting = false;\n this.onclose(\"forced close\");\n if (this.engine)\n this.engine.close();\n }\n /**\n * Alias for close()\n *\n * @private\n */\n disconnect() {\n return this._close();\n }\n /**\n * Called upon engine close.\n *\n * @private\n */\n onclose(reason, description) {\n this.cleanup();\n this.backoff.reset();\n this._readyState = \"closed\";\n this.emitReserved(\"close\", reason, description);\n if (this._reconnection && !this.skipReconnect) {\n this.reconnect();\n }\n }\n /**\n * Attempt a reconnection.\n *\n * @private\n */\n reconnect() {\n if (this._reconnecting || this.skipReconnect)\n return this;\n const self = this;\n if (this.backoff.attempts >= this._reconnectionAttempts) {\n this.backoff.reset();\n this.emitReserved(\"reconnect_failed\");\n this._reconnecting = false;\n }\n else {\n const delay = this.backoff.duration();\n this._reconnecting = true;\n const timer = this.setTimeoutFn(() => {\n if (self.skipReconnect)\n return;\n this.emitReserved(\"reconnect_attempt\", self.backoff.attempts);\n // check again for the case socket closed in above events\n if (self.skipReconnect)\n return;\n self.open((err) => {\n if (err) {\n self._reconnecting = false;\n self.reconnect();\n this.emitReserved(\"reconnect_error\", err);\n }\n else {\n self.onreconnect();\n }\n });\n }, delay);\n if (this.opts.autoUnref) {\n timer.unref();\n }\n this.subs.push(() => {\n this.clearTimeoutFn(timer);\n });\n }\n }\n /**\n * Called upon successful reconnect.\n *\n * @private\n */\n onreconnect() {\n const attempt = this.backoff.attempts;\n this._reconnecting = false;\n this.backoff.reset();\n this.emitReserved(\"reconnect\", attempt);\n }\n}\n","import { url } from \"./url.js\";\nimport { Manager } from \"./manager.js\";\nimport { Socket } from \"./socket.js\";\n/**\n * Managers cache.\n */\nconst cache = {};\nfunction lookup(uri, opts) {\n if (typeof uri === \"object\") {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n const parsed = url(uri, opts.path || \"/socket.io\");\n const source = parsed.source;\n const id = parsed.id;\n const path = parsed.path;\n const sameNamespace = cache[id] && path in cache[id][\"nsps\"];\n const newConnection = opts.forceNew ||\n opts[\"force new connection\"] ||\n false === opts.multiplex ||\n sameNamespace;\n let io;\n if (newConnection) {\n io = new Manager(source, opts);\n }\n else {\n if (!cache[id]) {\n cache[id] = new Manager(source, opts);\n }\n io = cache[id];\n }\n if (parsed.query && !opts.query) {\n opts.query = parsed.queryKey;\n }\n return io.socket(parsed.path, opts);\n}\n// so that \"lookup\" can be used both as a function (e.g. `io(...)`) and as a\n// namespace (e.g. `io.connect(...)`), for backward compatibility\nObject.assign(lookup, {\n Manager,\n Socket,\n io: lookup,\n connect: lookup,\n});\n/**\n * Protocol version.\n *\n * @public\n */\nexport { protocol } from \"socket.io-parser\";\n/**\n * Expose constructors for standalone build.\n *\n * @public\n */\nexport { Manager, Socket, lookup as io, lookup as connect, lookup as default, };\n","import { parse } from \"engine.io-client\";\n/**\n * URL parser.\n *\n * @param uri - url\n * @param path - the request path of the connection\n * @param loc - An object meant to mimic window.location.\n * Defaults to window.location.\n * @public\n */\nexport function url(uri, path = \"\", loc) {\n let obj = uri;\n // default to window.location\n loc = loc || (typeof location !== \"undefined\" && location);\n if (null == uri)\n uri = loc.protocol + \"//\" + loc.host;\n // relative path support\n if (typeof uri === \"string\") {\n if (\"/\" === uri.charAt(0)) {\n if (\"/\" === uri.charAt(1)) {\n uri = loc.protocol + uri;\n }\n else {\n uri = loc.host + uri;\n }\n }\n if (!/^(https?|wss?):\\/\\//.test(uri)) {\n if (\"undefined\" !== typeof loc) {\n uri = loc.protocol + \"//\" + uri;\n }\n else {\n uri = \"https://\" + uri;\n }\n }\n // parse\n obj = parse(uri);\n }\n // make sure we treat `localhost:80` and `localhost` equally\n if (!obj.port) {\n if (/^(http|ws)$/.test(obj.protocol)) {\n obj.port = \"80\";\n }\n else if (/^(http|ws)s$/.test(obj.protocol)) {\n obj.port = \"443\";\n }\n }\n obj.path = obj.path || \"/\";\n const ipv6 = obj.host.indexOf(\":\") !== -1;\n const host = ipv6 ? \"[\" + obj.host + \"]\" : obj.host;\n // define unique id\n obj.id = obj.protocol + \"://\" + host + \":\" + obj.port + path;\n // define href\n obj.href =\n obj.protocol +\n \"://\" +\n host +\n (loc && loc.port === obj.port ? \"\" : \":\" + obj.port);\n return obj;\n}\n"],"names":["PACKET_TYPES","Object","create","PACKET_TYPES_REVERSE","keys","forEach","key","TEXT_ENCODER","ERROR_PACKET","type","data","withNativeBlob","Blob","prototype","toString","call","withNativeArrayBuffer","ArrayBuffer","isView","obj","buffer","encodePacket","_ref","supportsBinary","callback","encodeBlobAsBase64","fileReader","FileReader","onload","content","result","split","readAsDataURL","toArray","Uint8Array","byteOffset","byteLength","chars","lookup","i","charCodeAt","TEXT_DECODER","decodePacket","encodedPacket","binaryType","mapBinary","charAt","decodeBase64Packet","substring","length","decoded","base64","encoded1","encoded2","encoded3","encoded4","bufferLength","len","p","arraybuffer","bytes","decode","SEPARATOR","String","fromCharCode","createPacketEncoderStream","TransformStream","transform","packet","controller","arrayBuffer","then","encoded","TextEncoder","encode","encodePacketToBinary","header","payloadLength","DataView","setUint8","view","setUint16","setBigUint64","BigInt","enqueue","totalLength","chunks","reduce","acc","chunk","concatChunks","size","shift","j","slice","Emitter","mixin","on","addEventListener","event","fn","this","_callbacks","push","Emitter$1","once","off","apply","arguments","removeListener","removeAllListeners","removeEventListener","cb","callbacks","splice","emit","args","Array","emitReserved","listeners","hasListeners","globalThisShim","self","window","Function","pick","_len","attr","_key","k","hasOwnProperty","NATIVE_SET_TIMEOUT","globalThis","setTimeout","NATIVE_CLEAR_TIMEOUT","clearTimeout","installTimerFunctions","opts","useNativeTimers","setTimeoutFn","bind","clearTimeoutFn","prev","TransportError","_Error","_inherits","_super","_createSuper","reason","description","context","_this","_classCallCheck","_createClass","_wrapNativeSuper","Error","Transport","_Emitter","_super2","_this2","writable","_assertThisInitialized","query","socket","value","_get","_getPrototypeOf","readyState","doOpen","doClose","onClose","packets","write","onPacket","details","onPause","schema","undefined","_hostname","_port","path","_query","hostname","indexOf","port","secure","Number","encodedQuery","str","encodeURIComponent","alphabet","map","seed","num","Math","floor","yeast","now","Date","XMLHttpRequest","err","hasCORS","XHR","xdomain","e","concat","join","empty","hasXHR2","responseType","Polling","_Transport","polling","location","isSSL","protocol","xd","forceBase64","withCredentials","cookieJar","createCookieJar","get","poll","pause","total","doPoll","_this3","encodedPayload","encodedPackets","decodedPacket","decodePayload","onOpen","_this4","close","_this5","count","encodePayload","doWrite","timestampRequests","timestampParam","sid","b64","createUri","_extends","Request","uri","_this6","req","request","method","xhrStatus","onError","_this7","onData","pollXhr","_this8","_a","_this9","xhr","open","extraHeaders","setDisableHeaderCheck","setRequestHeader","addCookies","requestTimeout","timeout","onreadystatechange","parseCookies","status","onLoad","send","document","index","requestsCount","requests","cleanup","fromError","abort","responseText","attachEvent","unloadHandler","nextTick","Promise","resolve","WebSocket","MozWebSocket","isReactNative","navigator","product","toLowerCase","WS","check","protocols","headers","ws","addEventListeners","onopen","autoUnref","_socket","unref","onclose","closeEvent","onmessage","ev","onerror","_loop","lastPacket","WT","WebTransport","transport","transportOptions","name","closed","ready","createBidirectionalStream","stream","decoderStream","maxPayload","TextDecoder","state","expectedLength","isBinary","headerArray","getUint16","n","getUint32","pow","createPacketDecoderStream","MAX_SAFE_INTEGER","reader","readable","pipeThrough","getReader","encoderStream","pipeTo","writer","getWriter","read","done","transports","websocket","webtransport","re","parts","parse","src","b","replace","m","exec","source","host","authority","ipv6uri","pathNames","regx","names","queryKey","$0","$1","$2","Socket","writeBuffer","_typeof","prevBufferLen","agent","upgrade","rememberUpgrade","addTrailingSlash","rejectUnauthorized","perMessageDeflate","threshold","closeOnBeforeunload","qs","qry","pairs","l","pair","decodeURIComponent","id","upgrades","pingInterval","pingTimeout","pingTimeoutTimer","beforeunloadEventListener","offlineEventListener","EIO","priorWebsocketSuccess","createTransport","setTransport","onDrain","failed","onTransportOpen","msg","upgrading","flush","freezeTransport","error","onTransportClose","onupgrade","to","probe","resetPingTimeout","onHandshake","JSON","sendPacket","code","filterUpgrades","getWritablePackets","payloadSize","c","utf8Length","ceil","options","compress","cleanupAndClose","waitForUpgrade","filteredUpgrades","Socket$1","utf8Write","offset","_encode","defers","hi","lo","_str","_length","_offset","isFinite","_float","isArray","time","getTime","_bin","toJSON","allKeys","encode_1","buf","deferIndex","deferWritten","nextOffset","defer","deferLength","bin","setFloat64","Decoder","_buffer","_view","_array","_parse","_map","string","chr","end","byte","getUint8","utf8Read","prefix","getInt8","getFloat32","getFloat64","getInt16","getInt32","decode_1","decoder","lib","require$$0","require$$1","module","exports","msgpack","socket_ioMsgpackParser","PacketType","PacketType_1","CONNECT","DISCONNECT","EVENT","ACK","CONNECT_ERROR","isInteger","isString","isObject","Encoder","add","checkPacket","nsp","isDataValid","destroy","Encoder_1","Decoder_1","RESERVED_EVENTS","freeze","connect","connect_error","disconnect","disconnecting","newListener","io","connected","recovered","receiveBuffer","sendBuffer","_queue","_queueSeq","ids","acks","flags","auth","_opts","_autoConnect","subs","onpacket","subEvents","_readyState","unshift","_len2","_key2","retries","fromQueue","_addToQueue","ack","pop","_registerAckCallback","isTransportWritable","engine","notifyOutgoingListeners","ackTimeout","timer","_len3","_key3","_len4","_key4","withErr","reject","arg1","arg2","tryCount","pending","_len5","responseArgs","_key5","_drainQueue","force","_packet","_sendConnectPacket","_pid","pid","_lastOffset","onconnect","BINARY_EVENT","onevent","BINARY_ACK","onack","ondisconnect","message","emitEvent","_anyListeners","_step","_iterator","_createForOfIteratorHelper","s","f","sent","_len6","_key6","emitBuffered","subDestroy","listener","_anyOutgoingListeners","_step2","_iterator2","Backoff","ms","min","max","factor","jitter","attempts","duration","rand","random","deviation","reset","setMin","setMax","setJitter","Manager","nsps","reconnection","reconnectionAttempts","Infinity","reconnectionDelay","reconnectionDelayMax","randomizationFactor","backoff","_parser","parser","encoder","autoConnect","v","_reconnection","_reconnectionAttempts","_reconnectionDelay","_randomizationFactor","_reconnectionDelayMax","_timeout","_reconnecting","reconnect","Engine","skipReconnect","openSubDestroy","maybeReconnectOnOpen","errorSub","onping","ondata","ondecoded","active","_i","_nsps","_close","delay","onreconnect","attempt","cache","parsed","loc","test","href","url","sameNamespace","forceNew","multiplex"],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/puter-js/src/lib/utils.js b/packages/puter-js/src/lib/utils.js
new file mode 100644
index 000000000..6706b75b3
--- /dev/null
+++ b/packages/puter-js/src/lib/utils.js
@@ -0,0 +1,436 @@
+/**
+ * Parses a given response text into a JSON object. If the parsing fails due to invalid JSON format,
+ * the original response text is returned.
+ *
+ * @param {string} responseText - The response text to be parsed into JSON. It is expected to be a valid JSON string.
+ * @returns {Object|string} The parsed JSON object if the responseText is valid JSON, otherwise returns the original responseText.
+ * @example
+ * // returns { key: "value" }
+ * parseResponse('{"key": "value"}');
+ *
+ * @example
+ * // returns "Invalid JSON"
+ * parseResponse('Invalid JSON');
+ */
+async function parseResponse(target) {
+ if ( target.responseType === 'blob' ) {
+ // Get content type of the blob
+ const contentType = target.getResponseHeader('content-type');
+ if ( contentType.startsWith('application/json') ) {
+ // If the blob is JSON, parse it
+ const text = await target.response.text();
+ try {
+ return JSON.parse(text);
+ } catch (error) {
+ return text;
+ }
+ }else if ( contentType.startsWith('application/octet-stream') ) {
+ // If the blob is an octet stream, return the blob
+ return target.response;
+ }
+
+ // Otherwise return an ojbect
+ return {
+ success: true,
+ result: target.response,
+ };
+ }
+ const responseText = target.responseText;
+ try {
+ return JSON.parse(responseText);
+ } catch (error) {
+ return responseText;
+ }
+}
+
+/**
+ * A function that generates a UUID (Universally Unique Identifier) using the version 4 format,
+ * which are random UUIDs. It uses the cryptographic number generator available in modern browsers.
+ *
+ * The generated UUID is a 36 character string (32 alphanumeric characters separated by 4 hyphens).
+ * It follows the pattern: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where x is any hexadecimal digit
+ * and y is one of 8, 9, A, or B.
+ *
+ * @returns {string} Returns a new UUID v4 string.
+ *
+ * @example
+ *
+ * let id = this.#uuidv4(); // Generate a new UUID
+ *
+ */
+function uuidv4(){
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ );
+}
+
+
+/**
+ * Initializes and returns an XMLHttpRequest object configured for a specific API endpoint, method, and headers.
+ *
+ * @param {string} endpoint - The API endpoint to which the request will be sent. This is appended to the API origin URL.
+ * @param {string} APIOrigin - The origin URL of the API. This is prepended to the endpoint.
+ * @param {string} authToken - The authorization token used for accessing the API. This is included in the request headers.
+ * @param {string} [method='post'] - The HTTP method to be used for the request. Defaults to 'post' if not specified.
+ * @param {string} [contentType='application/json;charset=UTF-8'] - The content type of the request. Defaults to
+ * 'application/json;charset=UTF-8' if not specified.
+ *
+ * @returns {XMLHttpRequest} The initialized XMLHttpRequest object.
+ */
+function initXhr(endpoint, APIOrigin, authToken, method= "post", contentType = "application/json;charset=UTF-8", responseType = undefined) {
+ const xhr = new XMLHttpRequest();
+ xhr.open(method, APIOrigin + endpoint, true);
+ xhr.setRequestHeader("Authorization", "Bearer " + authToken);
+ xhr.setRequestHeader("Content-Type", contentType);
+ xhr.responseType = responseType;
+ return xhr;
+}
+
+/**
+ * Handles an HTTP response by invoking appropriate callback functions and resolving or rejecting a promise.
+ *
+ * @param {Function} success_cb - An optional callback function for successful responses. It should take a response object
+ * as its only argument.
+ * @param {Function} error_cb - An optional callback function for error handling. It should take an error object
+ * as its only argument.
+ * @param {Function} resolve_func - A function used to resolve a promise. It should take a response object
+ * as its only argument.
+ * @param {Function} reject_func - A function used to reject a promise. It should take an error object
+ * as its only argument.
+ * @param {Object} response - The HTTP response object from the request. Expected to have 'status' and 'responseText'
+ * properties.
+ *
+ * @returns {void} The function does not return a value but will either resolve or reject a promise based on the
+ * response status.
+ */
+async function handle_resp(success_cb, error_cb, resolve_func, reject_func, response){
+ const resp = await parseResponse(response);
+ // error - unauthorized
+ if(response.status === 401){
+ // if error callback is provided, call it
+ if(error_cb && typeof error_cb === 'function')
+ error_cb({status: 401, message: 'Unauthorized'})
+ // reject promise
+ return reject_func({status: 401, message: 'Unauthorized'})
+ }
+ // error - other
+ else if(response.status !== 200){
+ // if error callback is provided, call it
+ if(error_cb && typeof error_cb === 'function')
+ error_cb(resp)
+ // reject promise
+ return reject_func(resp)
+ }
+ // success
+ else{
+ // This is a driver error
+ if(resp.success === false && resp.error?.code === 'permission_denied'){
+ let perm = await puter.ui.requestPermission({permission: 'driver:puter-image-generation:generate'});
+ // try sending again if permission was granted
+ if(perm.granted){
+ // todo repeat request
+ }
+ }
+ // if success callback is provided, call it
+ if(success_cb && typeof success_cb === 'function')
+ success_cb(resp);
+ // resolve with success
+ return resolve_func(resp);
+ }
+}
+
+/**
+ * Handles an error by invoking a specified error callback and then rejecting a promise.
+ *
+ * @param {Function} error_cb - An optional callback function that is called if it's provided.
+ * This function should take an error object as its only argument.
+ * @param {Function} reject_func - A function used to reject a promise. It should take an error object
+ * as its only argument.
+ * @param {Object} error - The error object that is passed to both the error callback and the reject function.
+ *
+ * @returns {void} The function does not return a value but will call the reject function with the error.
+ */
+function handle_error(error_cb, reject_func, error){
+ // if error callback is provided, call it
+ if(error_cb && typeof error_cb === 'function')
+ error_cb(error)
+ // reject promise
+ return reject_func(error)
+}
+
+function setupXhrEventHandlers(xhr, success_cb, error_cb, resolve_func, reject_func) {
+ // load: success or error
+ xhr.addEventListener('load', function(e){
+ return handle_resp(success_cb, error_cb, resolve_func, reject_func, this, xhr);
+ });
+
+ // error
+ xhr.addEventListener('error', function(e){
+ return handle_error(error_cb, reject_func, this);
+ })
+}
+
+const NOOP = () => {};
+class Valid {
+ static callback (cb) {
+ return (cb && typeof cb === 'function') ? cb : undefined;
+ }
+}
+
+/**
+ * Makes the hybrid promise/callback function for a particular driver method
+ * @param {string[]} arg_defs - argument names (for now; definitions eventually)
+ * @param {string} driverInterface - name of the interface
+ * @param {string} driverMethod - name of the method
+ */
+function make_driver_method(arg_defs, driverInterface, driverMethod, settings = {}) {
+ return async function (...args) {
+ let driverArgs = {};
+ let options = {};
+
+ // Check if the first argument is an object and use it as named parameters
+ if (args.length === 1 && typeof args[0] === 'object' && !Array.isArray(args[0])) {
+ driverArgs = { ...args[0] };
+ options = {
+ success: driverArgs.success,
+ error: driverArgs.error,
+ };
+ // Remove callback functions from driverArgs if they exist
+ delete driverArgs.success;
+ delete driverArgs.error;
+ } else {
+ // Handle as individual arguments
+ arg_defs.forEach((argName, index) => {
+ driverArgs[argName] = args[index];
+ });
+ options = {
+ success: args[arg_defs.length],
+ error: args[arg_defs.length + 1],
+ };
+ }
+
+ // preprocess
+ if(settings.preprocess && typeof settings.preprocess === 'function'){
+ driverArgs = settings.preprocess(driverArgs);
+ }
+
+ return await driverCall(options, driverInterface, driverMethod, driverArgs, settings);
+ };
+}
+
+async function driverCall (options, driverInterface, driverMethod, driverArgs, settings) {
+ const tp = new TeePromise();
+
+ driverCall_(
+ options,
+ tp.resolve.bind(tp),
+ tp.reject.bind(tp),
+ driverInterface,
+ driverMethod,
+ driverArgs,
+ undefined,
+ undefined,
+ settings,
+ );
+
+ return await tp;
+}
+
+// This function encapsulates the logic for sending a driver call request
+async function driverCall_(
+ options = {},
+ resolve_func, reject_func,
+ driverInterface, driverMethod, driverArgs,
+ method,
+ contentType = 'application/json;charset=UTF-8',
+ settings = {},
+) {
+ // If there is no authToken and the environment is web, try authenticating with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ return reject_func({
+ error: {
+ code: 'auth_canceled', message: 'Authentication canceled'
+ }
+ })
+ }
+ }
+
+ const success_cb = Valid.callback(options.success) ?? NOOP;
+ const error_cb = Valid.callback(options.error) ?? NOOP;
+ // create xhr object
+ const xhr = initXhr('/drivers/call', puter.APIOrigin, puter.authToken, 'POST', contentType);
+
+ if ( settings.responseType ) {
+ xhr.responseType = settings.responseType;
+ }
+
+ // load: success or error
+ xhr.addEventListener('load', async function(response){
+ const resp = await parseResponse(response.target);
+ // HTTP Error - unauthorized
+ if(response.status === 401 || resp?.code === "token_auth_failed"){
+ if(resp?.code === "token_auth_failed" && puter.env === 'web'){
+ try{
+ puter.resetAuthToken();
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ return reject_func({
+ error: {
+ code: 'auth_canceled', message: 'Authentication canceled'
+ }
+ })
+ }
+ }
+ // if error callback is provided, call it
+ if(error_cb && typeof error_cb === 'function')
+ error_cb({status: 401, message: 'Unauthorized'})
+ // reject promise
+ return reject_func({status: 401, message: 'Unauthorized'})
+ }
+ // HTTP Error - other
+ else if(response.status && response.status !== 200){
+ // if error callback is provided, call it
+ error_cb(resp)
+ // reject promise
+ return reject_func(resp)
+ }
+ // HTTP Success
+ else{
+ // Driver Error: permission denied
+ if(resp.success === false && resp.error?.code === 'permission_denied'){
+ let perm = await puter.ui.requestPermission({permission: 'driver:' + driverInterface + ':' + driverMethod});
+ // try sending again if permission was granted
+ if(perm.granted){
+ // repeat request with permission granted
+ return driverCall_(options, resolve_func, reject_func, driverInterface, driverMethod, driverArgs, method, contentType, settings);
+ }else{
+ // if error callback is provided, call it
+ error_cb(resp)
+ // reject promise
+ return reject_func(resp)
+ }
+ }
+ // Driver Error: other
+ else if(resp.success === false){
+ // if error callback is provided, call it
+ error_cb(resp)
+ // reject promise
+ return reject_func(resp)
+ }
+
+ let result = resp.result !== undefined ? resp.result : resp;
+ if ( settings.transform ) {
+ result = await settings.transform(result);
+ }
+
+ // Success: if callback is provided, call it
+ if(resolve_func.success)
+ success_cb(result);
+ // Success: resolve with the result
+ return resolve_func(result);
+ }
+ });
+
+ // error
+ xhr.addEventListener('error', function(e){
+ return handle_error(error_cb, reject_func, this);
+ })
+
+ // send request
+ xhr.send(JSON.stringify({
+ interface: driverInterface,
+ test_mode: settings?.test_mode,
+ method: driverMethod,
+ args: driverArgs,
+ }));
+}
+
+class TeePromise {
+ static STATUS_PENDING = {};
+ static STATUS_RUNNING = {};
+ static STATUS_DONE = {};
+ constructor () {
+ this.status_ = this.constructor.STATUS_PENDING;
+ this.donePromise = new Promise((resolve, reject) => {
+ this.doneResolve = resolve;
+ this.doneReject = reject;
+ });
+ }
+ get status () {
+ return this.status_;
+ }
+ set status (status) {
+ this.status_ = status;
+ if ( status === this.constructor.STATUS_DONE ) {
+ this.doneResolve();
+ }
+ }
+ resolve (value) {
+ this.status_ = this.constructor.STATUS_DONE;
+ this.doneResolve(value);
+ }
+ awaitDone () {
+ return this.donePromise;
+ }
+ then (fn, rfn) {
+ return this.donePromise.then(fn, rfn);
+ }
+
+ reject (err) {
+ this.status_ = this.constructor.STATUS_DONE;
+ this.doneReject(err);
+ }
+
+ /**
+ * @deprecated use then() instead
+ */
+ onComplete(fn) {
+ return this.then(fn);
+ }
+}
+
+async function blob_to_url (blob) {
+ const tp = new TeePromise();
+ const reader = new FileReader();
+ reader.onloadend = () => tp.resolve(reader.result);
+ reader.readAsDataURL(blob);
+ return await tp;
+}
+
+function blobToDataUri(blob) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = function(event) {
+ resolve(event.target.result);
+ };
+ reader.onerror = function(error) {
+ reject(error);
+ };
+ reader.readAsDataURL(blob);
+ });
+}
+
+function arrayBufferToDataUri(arrayBuffer) {
+ return new Promise((resolve, reject) => {
+ const blob = new Blob([arrayBuffer]);
+ const reader = new FileReader();
+ reader.onload = function(event) {
+ resolve(event.target.result);
+ };
+ reader.onerror = function(error) {
+ reject(error);
+ };
+ reader.readAsDataURL(blob);
+ });
+}
+
+export {parseResponse, uuidv4, handle_resp, handle_error, initXhr, setupXhrEventHandlers, driverCall,
+ TeePromise,
+ make_driver_method,
+ blob_to_url,
+ arrayBufferToDataUri,
+ blobToDataUri,
+};
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/AI.js b/packages/puter-js/src/modules/AI.js
new file mode 100644
index 000000000..346325107
--- /dev/null
+++ b/packages/puter-js/src/modules/AI.js
@@ -0,0 +1,253 @@
+import * as utils from '../lib/utils.js'
+
+class AI{
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token and resets the socket connection with the updated token, if applicable.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [AI]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [AI]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ img2txt = async (...args) => {
+ let MAX_INPUT_SIZE = 10 * 1024 * 1024;
+ let options = {};
+ let testMode = false;
+
+ // Check that the argument is not undefined or null
+ if(!args){
+ throw({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ // if argument is string transform it to the object that the API expects
+ if (typeof args[0] === 'string' || args[0] instanceof Blob) {
+ options.source = args[0];
+ }
+
+ // if input is a blob, transform it to a data URI
+ if (args[0].source instanceof Blob) {
+ options.source = await utils.blobToDataUri(args[0].source);
+ }
+
+ // check input size
+ if (options.source.length > this.MAX_INPUT_SIZE) {
+ throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
+ }
+
+ // determine if test mode is enabled
+ if (typeof args[1] === 'boolean' && args[1] === true ||
+ typeof args[2] === 'boolean' && args[2] === true ||
+ typeof args[3] === 'boolean' && args[3] === true) {
+ testMode = true;
+ }
+
+ console.log(args, options);
+ return await utils.make_driver_method(['source'], 'puter-ocr', 'recognize', {
+ test_mode: testMode ?? false,
+ transform: async (result) => {
+ let str = '';
+ for (let i = 0; i < result?.blocks?.length; i++) {
+ if("text/textract:LINE" === result.blocks[i].type)
+ str += result.blocks[i].text + "\n";
+ }
+ return str;
+ }
+ }).call(this, options);
+ }
+
+ txt2speech = async (...args) => {
+ let MAX_INPUT_SIZE = 3000;
+ let options = {};
+ let testMode = false;
+
+ if(!args){
+ throw({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ // if argument is string transform it to the object that the API expects
+ if (typeof args[0] === 'string') {
+ options = { text: args[0] };
+ }
+
+ // if second argument is string, it's the language
+ if (args[1] && typeof args[1] === 'string') {
+ options.language = args[1];
+ }
+
+ // check input size
+ if (options.text.length > this.MAX_INPUT_SIZE) {
+ throw { message: 'Input size cannot be larger than ' + MAX_INPUT_SIZE, code: 'input_too_large' };
+ }
+
+ // determine if test mode is enabled
+ if (typeof args[1] === 'boolean' && args[1] === true ||
+ typeof args[2] === 'boolean' && args[2] === true ||
+ typeof args[3] === 'boolean' && args[3] === true) {
+ testMode = true;
+ }
+
+ return await utils.make_driver_method(['source'], 'puter-tts', 'synthesize', {
+ responseType: 'blob',
+ test_mode: testMode ?? false,
+ transform: async (result) => {
+ const url = await utils.blob_to_url(result);
+ const audio = new Audio(url);
+ audio.toString = () => url;
+ audio.valueOf = () => url;
+ return audio;
+ }
+ }).call(this, options);
+ }
+
+
+ // accepts either a string or an array of message objects
+ // if string, it's treated as the prompt which is a shorthand for { messages: [{ content: prompt }] }
+ // if object, it's treated as the full argument object that the API expects
+ chat = async (...args) => {
+ let options = {};
+ let testMode = false;
+
+ // Check that the argument is not undefined or null
+ if(!args){
+ throw({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ // ai.chat(prompt)
+ // ai.chat(prompt, testMode)
+ if (typeof args[0] === 'string' && (!args[1] || typeof args[1] === 'boolean')) {
+ options = { messages: [{ content: args[0] }] };
+ }
+ // ai.chat(prompt, imageURL/File)
+ // ai.chat(prompt, imageURL/File, testMode)
+ else if (typeof args[0] === 'string' && (typeof args[1] === 'string' || args[1] instanceof File)) {
+ // if imageURL is a File, transform it to a data URI
+ if(args[1] instanceof File){
+ args[1] = await utils.blobToDataUri(args[1]);
+ }
+
+ // parse args[1] as an image_url object
+ options = {
+ vision: true,
+ messages: [
+ {
+ content: [
+ args[0],
+ {
+ image_url: {
+ url: args[1]
+ }
+ }
+ ],
+ }
+ ]
+ };
+ }
+ // chat(prompt, [imageURLs])
+ else if (typeof args[0] === 'string' && Array.isArray(args[1])) {
+ // parse args[1] as an array of image_url objects
+ for (let i = 0; i < args[1].length; i++) {
+ args[1][i] = { image_url: { url: args[1][i] } };
+ }
+ options = {
+ vision: true,
+ messages: [
+ {
+ content: [
+ args[0],
+ ...args[1]
+ ],
+ }
+ ]
+ };
+ }
+ // chat([messages])
+ else if (Array.isArray(args[0])) {
+ options = { messages: args[0] };
+ }
+
+ // determine if testMode is enabled
+ if (typeof args[1] === 'boolean' && args[1] === true ||
+ typeof args[2] === 'boolean' && args[2] === true ||
+ typeof args[3] === 'boolean' && args[3] === true) {
+ testMode = true;
+ }
+
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['messages'], 'puter-chat-completion', 'complete', {
+ test_mode: testMode ?? false,
+ transform: async (result) => {
+ result.toString = () => {
+ return result.message?.content;
+ };
+
+ result.valueOf = () => {
+ return result.message?.content;
+ }
+
+ return result;
+ }
+ }).call(this, options);
+ }
+
+ txt2img = async (...args) => {
+ let options = {};
+ let testMode = false;
+
+ if(!args){
+ throw({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ // if argument is string transform it to the object that the API expects
+ if (typeof args[0] === 'string') {
+ options = { prompt: args[0] };
+ }
+
+ // if second argument is string, it's the `testMode`
+ if (typeof args[1] === 'boolean' && args[1] === true) {
+ testMode = true;
+ }
+
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['prompt'], 'puter-image-generation', 'generate', {
+ responseType: 'blob',
+ test_mode: testMode ?? false,
+ transform: async blob => {
+ let img = new Image();
+ img.src = await utils.blob_to_url(blob);
+ img.toString = () => img.src;
+ img.valueOf = () => img.src;
+ return img;
+ }
+ }).call(this, options);
+ }
+}
+
+export default AI;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/Apps.js b/packages/puter-js/src/modules/Apps.js
new file mode 100644
index 000000000..c36d1be51
--- /dev/null
+++ b/packages/puter-js/src/modules/Apps.js
@@ -0,0 +1,158 @@
+import * as utils from '../lib/utils.js'
+
+class Apps{
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [Apps]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [Apps]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ list = async (...args) => {
+ let options = {};
+
+ options = { "predicate": ['user-can-edit'] };
+
+ return utils.make_driver_method(['uid'], 'puter-apps', 'select').call(this, options);
+ }
+
+ create = async (...args) => {
+ let options = {};
+ // * allows for: puter.apps.new('example-app') *
+ if (typeof args[0] === 'string') {
+ let indexURL = args[1];
+ let title = args[2] ?? args[0];
+
+ options = { object: { name: args[0], index_url: indexURL, title: title}};
+ }else if (typeof args[0] === 'object' && args[0] !== null) {
+ let options_raw = args[0];
+ options = {
+ object: {
+ name: options_raw.name,
+ index_url: options_raw.indexURL,
+ title: options_raw.title,
+ description: options_raw.description,
+ icon: options_raw.icon,
+ maximize_on_start: options_raw.maximizeOnStart,
+ filetype_associations: options_raw.filetypeAssociations,
+ }
+ };
+ }
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['object'], 'puter-apps', 'create').call(this, options);
+ }
+
+ update = async(...args) => {
+ let options = {};
+
+ // if there is one string argument, assume it is the app name
+ // * allows for: puter.apps.update('example-app') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ let object_raw = args[1];
+ let object = {
+ name: object_raw.name,
+ index_url: object_raw.indexURL,
+ title: object_raw.title,
+ description: object_raw.description,
+ icon: object_raw.icon,
+ maximize_on_start: object_raw.maximizeOnStart,
+ filetype_associations: object_raw.filetypeAssociations,
+ };
+
+ options = { id: { name: args[0]}, object: object};
+ }
+
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['object'], 'puter-apps', 'update').call(this, options);
+ }
+
+ get = async(...args) => {
+ let options = {};
+ // if there is one string argument, assume it is the app name
+ // * allows for: puter.apps.get('example-app') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ options = { id: {name: args[0]}};
+ }
+ return utils.make_driver_method(['uid'], 'puter-apps', 'read').call(this, options);
+ }
+
+ delete = async(...args) => {
+ let options = {};
+ // if there is one string argument, assume it is the app name
+ // * allows for: puter.apps.get('example-app') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ options = { id: {name: args[0]}};
+ }
+ return utils.make_driver_method(['uid'], 'puter-apps', 'delete').call(this, options);
+ }
+
+ getDeveloperProfile = function(...args){
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ success: args[0],
+ error: args[1],
+ };
+ }
+
+ return new Promise((resolve, reject) => {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ success: args[0],
+ error: args[1],
+ };
+ }
+
+ return new Promise((resolve, reject) => {
+ const xhr = utils.initXhr('/get-dev-profile', puter.APIOrigin, puter.authToken, 'get');
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+ });
+ }
+}
+
+export default Apps;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/Auth.js b/packages/puter-js/src/modules/Auth.js
new file mode 100644
index 000000000..7c877bbe2
--- /dev/null
+++ b/packages/puter-js/src/modules/Auth.js
@@ -0,0 +1,115 @@
+import * as utils from '../lib/utils.js'
+
+class Auth{
+ // Used to generate a unique message id for each message sent to the host environment
+ // we start from 1 because 0 is falsy and we want to avoid that for the message id
+ #messageID = 1;
+
+
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [Auth]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [Auth]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ signIn = () =>{
+ return new Promise((resolve, reject) => {
+ let msg_id = this.#messageID++;
+
+ let w = 600;
+ let h = 600;
+ let title = 'Puter';
+ var left = (screen.width/2)-(w/2);
+ var top = (screen.height/2)-(h/2);
+ window.open(puter.defaultGUIOrigin + '/action/sign-in?embedded_in_popup=true&msg_id=' + msg_id,
+ title,
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
+
+ window.addEventListener('message', function(e){
+ if(e.data.msg_id == msg_id){
+ // remove redundant attributes
+ delete e.data.msg_id;
+ delete e.data.msg;
+
+ if(e.data.success){
+ // set the auth token
+ puter.setAuthToken(e.data.token);
+
+ resolve(e.data);
+ }else
+ reject(e.data);
+
+ // delete the listener
+ window.removeEventListener('message', this);
+ }
+ });
+ });
+ }
+
+ isSignedIn = () =>{
+ if(puter.authToken)
+ return true;
+ else
+ return false;
+ }
+
+ getUser = function(...args){
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ success: args[0],
+ error: args[1],
+ };
+ }
+
+ return new Promise((resolve, reject) => {
+ const xhr = utils.initXhr('/whoami', puter.APIOrigin, puter.authToken, 'get');
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+ }
+
+ signOut = () =>{
+ puter.resetAuthToken();
+ }
+}
+
+export default Auth
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FSItem.js b/packages/puter-js/src/modules/FSItem.js
new file mode 100644
index 000000000..0df2ec5f3
--- /dev/null
+++ b/packages/puter-js/src/modules/FSItem.js
@@ -0,0 +1,98 @@
+import path from "../lib/path.js"
+
+class FSItem{
+ constructor(options){
+ this.readURL = options.readURL ?? options.read_url;
+ this.writeURL = options.writeURL ?? options.write_url;
+ this.metadataURL = options.metadataURL ?? options.metadata_url;
+ this.name = options.name ?? options.fsentry_name;
+ this.uid = options.uid ?? options.uuid ?? options.fsentry_uid ?? options.fsentry_id ?? options.fsentry_uuid ?? options.id;
+ this.id = this.uid;
+ this.uuid = this.uid;
+ this.path = options.path ?? options.fsentry_path;
+ this.size = options.size ?? options.fsentry_size;
+ this.accessed = options.accessed ?? options.fsentry_accessed;
+ this.modified = options.modified ?? options.fsentry_modified;
+ this.created = options.created ?? options.fsentry_created;
+ this.isDirectory = (options.isDirectory || options.is_dir || options.fsentry_is_dir) ? true : false;
+ }
+
+ write = async function(data){
+ return puter.fs.write(
+ this.path,
+ new File([data], this.name),
+ {
+ overwrite: true,
+ dedupeName: false,
+ },
+ );
+ }
+
+ // Watches for changes to the item, and calls the callback function
+ // with the new data when a change is detected.
+ watch = function(callback){
+ // todo - implement
+ }
+
+ open = function(callback){
+ // todo - implement
+ }
+
+ // Set wallpaper
+ setAsWallpaper = function(options, callback){
+ // todo - implement
+ }
+
+ rename = function(new_name){
+ return puter.fs.rename(this.uid, new_name);
+ }
+
+ move = function(dest_path, overwrite=false, new_name){
+ return puter.fs.move(this.path, dest_path, overwrite, new_name);
+ }
+
+ copy = function(destination_directory, auto_rename=false, overwrite=false){
+ return puter.fs.copy(this.path, destination_directory, auto_rename, overwrite);
+ }
+
+ delete = function(){
+ return puter.fs.delete(this.path);
+ }
+
+ versions = async function(){
+ // todo - implement
+ }
+
+ trash = function(){
+ // todo make sure puter allows for moving to trash by default
+ // todo implement trashing
+ }
+
+ mkdir = async function(name, auto_rename = false){
+ // Don't proceed if this is not a directory, throw error
+ if(!this.isDirectory)
+ throw new Error('mkdir() can only be called on a directory');
+
+ // mkdir
+ return puter.fs.mkdir(path.join(this.path, name));
+ }
+
+ metadata = async function(){
+ // todo - implement
+ }
+
+ readdir = async function(){
+ // Don't proceed if this is not a directory, throw error
+ if(!this.isDirectory)
+ throw new Error('readdir() can only be called on a directory');
+
+ // readdir
+ return puter.fs.readdir(this.path);
+ }
+
+ read = async function(){
+ return puter.fs.read(this.path);
+ }
+}
+
+export default FSItem;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/index.js b/packages/puter-js/src/modules/FileSystem/index.js
new file mode 100644
index 000000000..e39805cc8
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/index.js
@@ -0,0 +1,133 @@
+import io from '../../lib/socket.io/socket.io.esm.min.js';
+
+// Operations
+import readdir from "./operations/readdir.js";
+import stat from "./operations/stat.js";
+import space from "./operations/space.js";
+import mkdir from "./operations/mkdir.js";
+import copy from "./operations/copy.js";
+import rename from "./operations/rename.js";
+import upload from "./operations/upload.js";
+import read from "./operations/read.js";
+import move from "./operations/move.js";
+import write from "./operations/write.js";
+import sign from "./operations/sign.js";
+// Why is this called deleteFSEntry instead of just delete? because delete is
+// a reserved keyword in javascript
+import deleteFSEntry from "./operations/deleteFSEntry.js";
+
+class FileSystem{
+
+ readdir = readdir;
+ stat = stat;
+ space = space;
+ mkdir = mkdir;
+ copy = copy;
+ rename = rename;
+ upload = upload;
+ read = read;
+ // Why is this called deleteFSEntry instead of just delete? because delete is
+ // a reserved keyword in javascript.
+ delete = deleteFSEntry;
+ move = move;
+ write = write;
+ sign = sign;
+
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ * and connects to the socket.
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ // Connect socket.
+ this.initializeSocket();
+ }
+
+ /**
+ * Initializes the socket connection to the server using the current API origin.
+ * If a socket connection already exists, it disconnects it before creating a new one.
+ * Sets up various event listeners on the socket to handle different socket events like
+ * connect, disconnect, reconnect, reconnect_attempt, reconnect_error, reconnect_failed, and error.
+ *
+ * @memberof FileSystem
+ * @returns {void}
+ */
+ initializeSocket() {
+ if (this.socket) {
+ this.socket.disconnect();
+ }
+
+ this.socket = io(this.APIOrigin, {
+ query: {
+ auth_token: this.authToken,
+ }
+ });
+
+ this.bindSocketEvents();
+ }
+
+ bindSocketEvents() {
+ this.socket.on('connect', () => {
+ console.log('FileSystem Socket: Connected', this.socket.id);
+ });
+
+ this.socket.on('disconnect', () => {
+ console.log('FileSystem Socket: Disconnected');
+ });
+
+ this.socket.on('reconnect', (attempt) => {
+ console.log('FileSystem Socket: Reconnected', this.socket.id);
+ });
+
+ this.socket.on('reconnect_attempt', (attempt) => {
+ console.log('FileSystem Socket: Reconnection Attemps', attempt);
+ });
+
+ this.socket.on('reconnect_error', (error) => {
+ console.log('FileSystem Socket: Reconnection Error', error);
+ });
+
+ this.socket.on('reconnect_failed', () => {
+ console.log('FileSystem Socket: Reconnection Failed');
+ });
+
+ this.socket.on('error', (error) => {
+ console.error('FileSystem Socket Error:', error);
+ });
+ }
+
+ /**
+ * Sets a new authentication token and resets the socket connection with the updated token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [FileSystem]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ // reset socket
+ this.initializeSocket();
+ }
+
+ /**
+ * Sets the API origin and resets the socket connection with the updated API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [Apps]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ // reset socket
+ this.initializeSocket();
+ }
+}
+
+export default FileSystem;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/copy.js b/packages/puter-js/src/modules/FileSystem/operations/copy.js
new file mode 100644
index 000000000..4e33162a0
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/copy.js
@@ -0,0 +1,61 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const copy = function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ source: args[0],
+ destination: args[1],
+ overwrite: args[2]?.overwrite,
+ new_name: args[2]?.newName || args[2]?.new_name,
+ create_missing_parents: args[2]?.createMissingParents || args[2]?.create_missing_parents,
+ new_metadata: args[2]?.newMetadata || args[2]?.new_metadata,
+ original_client_socket_id: args[2]?.excludeSocketID || args[2]?.original_client_socket_id,
+ success: args[3],
+ error: args[4],
+ // Add more if needed...
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // convert paths to absolute path
+ options.source = getAbsolutePathForApp(options.source);
+ options.destination = getAbsolutePathForApp(options.destination);
+
+ // create xhr object
+ const xhr = utils.initXhr('/copy', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send(JSON.stringify({
+ original_client_socket_id: this.socket.id,
+ socket_id: this.socket.id,
+ source: options.source,
+ destination: options.destination,
+ overwrite: options.overwrite,
+ new_name: (options.new_name || options.newName),
+ // if user is copying an item to where its source is, change the name so there is no conflict
+ dedupe_name: (options.dedupe_name || options.dedupeName),
+ }));
+ })
+}
+
+export default copy;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/deleteFSEntry.js b/packages/puter-js/src/modules/FileSystem/operations/deleteFSEntry.js
new file mode 100644
index 000000000..0b8644b65
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/deleteFSEntry.js
@@ -0,0 +1,59 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+// why is this called deleteFSEntry instead of just delete?
+// because delete is a reserved keyword in javascript
+const deleteFSEntry = async function(...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ }
+ // Otherwise, we assume separate arguments are provided
+ else {
+ options = {
+ paths: args[0],
+ recursive: args[1]?.recursive ?? true,
+ descendantsOnly: args[1]?.descendantsOnly ?? false,
+ };
+ }
+
+ // If paths is a string, convert to array
+ // this is to make it easier for the user to provide a single path without having to wrap it in an array
+ let paths = options.paths;
+ if(typeof paths === 'string')
+ paths = [paths];
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/delete', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ // convert paths to absolute paths
+ paths = paths.map((path) => {
+ return getAbsolutePathForApp(path);
+ })
+
+ xhr.send(JSON.stringify({
+ paths: paths,
+ descendants_only: (options.descendants_only || options.descendantsOnly) ?? false,
+ recursive: options.recursive ?? true,
+ }));
+ })
+}
+
+export default deleteFSEntry;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/mkdir.js b/packages/puter-js/src/modules/FileSystem/operations/mkdir.js
new file mode 100644
index 000000000..c256ab654
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/mkdir.js
@@ -0,0 +1,59 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+import path from "../../../lib/path.js"
+
+const mkdir = function (...args) {
+ let options = {};
+
+ // If first argument is a string and the second is an object, or if the first is an object
+ if ((typeof args[0] === 'string' && typeof args[1] === 'object' && !(args[1] instanceof Function)) || (typeof args[0] === 'object' && args[0] !== null)) {
+ // If it's a string followed by an object, it means path then options
+ if (typeof args[0] === 'string') {
+ options.path = args[0];
+ // Merge the options
+ Object.assign(options, args[1]);
+ options.success = args[2];
+ options.error = args[3];
+ } else {
+ options = args[0];
+ }
+ } else if (typeof args[0] === 'string') {
+ // it means it's a path then functions (success and optionally error)
+ options.path = args[0];
+ options.success = args[1];
+ options.error = args[2];
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/mkdir', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ options.path = getAbsolutePathForApp(options.path);
+
+ xhr.send(JSON.stringify({
+ parent: path.dirname(options.path),
+ path: path.basename(options.path),
+ overwrite: options.overwrite ?? false,
+ dedupe_name: (options.rename || options.dedupeName) ?? false,
+ shortcut_to: options.shortcutTo,
+ original_client_socket_id: this.socket.id,
+ create_missing_parents: (options.recursive || options.createMissingParents) ?? false,
+ }));
+ })
+}
+
+export default mkdir;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/move.js b/packages/puter-js/src/modules/FileSystem/operations/move.js
new file mode 100644
index 000000000..eb87c245b
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/move.js
@@ -0,0 +1,57 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const move = function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ source: args[0],
+ destination: args[1],
+ overwrite: args[2]?.overwrite,
+ new_name: args[2]?.newName || args[2]?.new_name,
+ create_missing_parents: args[2]?.createMissingParents || args[2]?.create_missing_parents,
+ new_metadata: args[2]?.newMetadata || args[2]?.new_metadata,
+ original_client_socket_id: args[2]?.excludeSocketID || args[2]?.original_client_socket_id,
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // convert source and destination to absolute path
+ options.source = getAbsolutePathForApp(options.source);
+ options.destination = getAbsolutePathForApp(options.destination);
+
+ // create xhr object
+ const xhr = utils.initXhr('/move', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send(JSON.stringify({
+ source: options.source,
+ destination: options.destination,
+ overwrite: options.overwrite,
+ new_name: (options.new_name || options.newName),
+ create_missing_parents: (options.create_missing_parents || options.createMissingParents),
+ new_metadata: (options.new_metadata || options.newMetadata),
+ original_client_socket_id: options.excludeSocketID,
+ }));
+ })
+}
+
+export default move;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/read.js b/packages/puter-js/src/modules/FileSystem/operations/read.js
new file mode 100644
index 000000000..971bc59d8
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/read.js
@@ -0,0 +1,44 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const read = function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ path: typeof args[0] === 'string' ? args[0] : (typeof args[0] === 'object' && args[0] !== null ? args[0].path : args[0]),
+ success: args[1],
+ error: args[2],
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // convert path to absolute path
+ options.path = getAbsolutePathForApp(options.path);
+
+ // create xhr object
+ const xhr = utils.initXhr('/read?file=' + encodeURIComponent(options.path), this.APIOrigin, this.authToken, 'get', "application/json;charset=UTF-8", 'blob');
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+}
+
+export default read;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/readdir.js b/packages/puter-js/src/modules/FileSystem/operations/readdir.js
new file mode 100644
index 000000000..fe2f2e145
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/readdir.js
@@ -0,0 +1,46 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const readdir = async function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ path: args[0],
+ success: args[1],
+ error: args[2],
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // path is required
+ if(!options.path){
+ throw new Error({ code: 'NO_PATH', message: 'No path provided.' });
+ }
+
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/readdir', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send(JSON.stringify({path: getAbsolutePathForApp(options.path)}));
+ })
+}
+
+export default readdir;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/rename.js b/packages/puter-js/src/modules/FileSystem/operations/rename.js
new file mode 100644
index 000000000..c670e482a
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/rename.js
@@ -0,0 +1,56 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const rename = function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ path: args[0],
+ new_name: args[1],
+ success: args[2],
+ error: args[3],
+ // Add more if needed...
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/rename', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ let dataToSend = {
+ original_client_socket_id: options.excludeSocketID || options.original_client_socket_id,
+ new_name: options.new_name || options.newName,
+ };
+
+ if (options.uid !== undefined) {
+ dataToSend.uid = options.uid;
+ } else if (options.path !== undefined) {
+ // If dirPath is not provided or it's not starting with a slash, it means it's a relative path
+ // in that case, we need to prepend the app's root directory to it
+ dataToSend.path = getAbsolutePathForApp(options.path);
+ }
+
+ xhr.send(JSON.stringify(dataToSend));
+ })
+}
+
+export default rename;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/sign.js b/packages/puter-js/src/modules/FileSystem/operations/sign.js
new file mode 100644
index 000000000..c2d5efb50
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/sign.js
@@ -0,0 +1,103 @@
+
+import * as utils from '../../../lib/utils.js';
+
+/**
+ * Signs a file system entry or entries and optionally calls a provided callback with the result.
+ * If a single item is passed, it is converted into an array.
+ * Sends a POST request to the server to sign the items.
+ *
+ * @param {(Object|Object[])} items - The file system entry or entries to be signed. Can be a single object or an array of objects.
+ * @param {function} [callback] - Optional callback function to be invoked with the result of the signing.
+ * @returns {(Object|Object[])} If a single item was passed, returns a single object. If multiple items were passed, returns an array of objects.
+ * @throws {Error} If the AJAX request fails.
+ * @async
+ */
+const sign = function(...args){
+ let options;
+
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ app_uid: args[0],
+ items: args[1],
+ success: args[2],
+ error: args[3],
+ // Add more if needed...
+ };
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+
+ let items = options.items;
+
+ // if only a single item is passed, convert it to array
+ // so that the code below can work with arrays
+ if(!Array.isArray(items)){
+ items = [items]
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/sign', this.APIOrigin, this.authToken);
+
+ // response
+ xhr.addEventListener('load', async function(e){
+ const resp = await utils.parseResponse(this);
+ // error
+ if(this.status !== 200){
+ // if error callback is provided, call it
+ if(options.error && typeof options.error === 'function')
+ options.error(resp)
+ // reject promise
+ return reject(resp)
+ }
+ // success
+ else{
+ let res = resp;
+ let result;
+ let token = res.token;
+ // if only a single item was passed, return a single object
+ if(items.length == 1){
+ result = {...(res.signatures[0])};
+ }
+ // if multiple items were passed, return an array of objects
+ else{
+ let obj=[];
+ for(let i=0; i {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/df', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+}
+
+export default space;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/stat.js b/packages/puter-js/src/modules/FileSystem/operations/stat.js
new file mode 100644
index 000000000..28f9d6498
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/stat.js
@@ -0,0 +1,57 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+
+const stat = async function (...args) {
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ path: args[0],
+ options: typeof args[1] === 'object' ? args[1] : {},
+ success: typeof args[1] === 'object' ? args[2] : args[1],
+ error: typeof args[1] === 'object' ? args[3] : args[2],
+ // Add more if needed...
+ };
+ }
+
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject('Authentication failed.');
+ }
+ }
+
+ // create xhr object
+ const xhr = utils.initXhr('/stat', this.APIOrigin, this.authToken);
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ let dataToSend = {};
+ if (options.uid !== undefined) {
+ dataToSend.uid = options.uid;
+ } else if (options.path !== undefined) {
+ // If dirPath is not provided or it's not starting with a slash, it means it's a relative path
+ // in that case, we need to prepend the app's root directory to it
+ dataToSend.path = getAbsolutePathForApp(options.path);
+ }
+
+ dataToSend.return_subdomains = options.returnSubdomains;
+ dataToSend.return_permissions = options.returnPermissions;
+ dataToSend.return_versions = options.returnVersions;
+ dataToSend.return_size = options.returnSize;
+
+ xhr.send(JSON.stringify(dataToSend));
+ })
+}
+
+export default stat;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/FileSystem/operations/upload.js b/packages/puter-js/src/modules/FileSystem/operations/upload.js
new file mode 100644
index 000000000..8aab5d6c9
--- /dev/null
+++ b/packages/puter-js/src/modules/FileSystem/operations/upload.js
@@ -0,0 +1,419 @@
+import * as utils from '../../../lib/utils.js';
+import getAbsolutePathForApp from '../utils/getAbsolutePathForApp.js';
+import path from "../../../lib/path.js"
+
+const upload = async function(items, dirPath, options = {}){
+ return new Promise(async (resolve, reject) => {
+ // If auth token is not provided and we are in the web environment,
+ // try to authenticate with Puter
+ if(!puter.authToken && puter.env === 'web'){
+ try{
+ await puter.ui.authenticateWithPuter();
+ }catch(e){
+ // if authentication fails, throw an error
+ reject(e);
+ }
+ }
+
+ // xhr object to be used for the upload
+ let xhr = new XMLHttpRequest();
+
+ // Can not write to root
+ if(dirPath === '/'){
+ // if error callback is provided, call it
+ if(options.error && typeof options.error === 'function')
+ options.error('Can not upload to root directory.');
+ return reject('Can not upload to root directory.');
+ }
+
+ // If dirPath is not provided or it's not starting with a slash, it means it's a relative path
+ // in that case, we need to prepend the app's root directory to it
+ dirPath = getAbsolutePathForApp(dirPath);
+
+ // Generate a unique ID for this upload operation
+ // This will be used to uniquely identify this operation and its progress
+ // across servers and clients
+ const operation_id = utils.uuidv4();
+
+ // Call 'init' callback if provided
+ // init is basically a hook that allows the user to get the operation ID and the XMLHttpRequest object
+ if(options.init && typeof options.init === 'function'){
+ options.init(operation_id, xhr);
+ }
+
+ // keeps track of the amount of data uploaded to the server
+ let bytes_uploaded_to_server = 0;
+ // keeps track of the amount of data uploaded to the cloud
+ let bytes_uploaded_to_cloud = 0;
+
+ // This will hold the normalized entries to be uploaded
+ // Since 'items' could be a DataTransferItemList, FileList, File, or an array of any of these,
+ // we need to normalize it into an array of consistently formatted objects which will be held in 'entries'
+ let entries;
+
+ // will hold the total size of the upload
+ let total_size = 0;
+ let file_count = 0;
+
+ let seemsToBeParsedDataTransferItems = false;
+ if(Array.isArray(items) && items.length > 0){
+ for(let i=0; i {
+ if ( entry_a.isDirectory && ! entry_b.isDirectory ) return -1;
+ if ( ! entry_a.isDirectory && entry_b.isDirectory ) return 1;
+ if ( entry_a.isDirectory && entry_b.isDirectory ) return 0;
+
+ return entry_a.size - entry_b.size;
+ });
+ }
+ // FileList/File
+ else if(items instanceof File || items[0] instanceof File || items instanceof FileList || items[0] instanceof FileList){
+ if(!Array.isArray(items))
+ entries = items instanceof FileList ? Array.from(items) : [items];
+ else
+ entries = items;
+
+ // Sort entries by size ascending
+ entries.sort((entry_a, entry_b) => {
+ return entry_a.size - entry_b.size;
+ })
+ // add FullPath property to each entry
+ for(let i=0; i {
+ if(msg.operation_id === operation_id){
+ bytes_uploaded_to_cloud += msg.loaded_diff
+ }
+ }
+
+ // Handle upload progress events from server
+ this.socket.on('upload.progress', progress_handler);
+
+ // keeps track of the amount of data uploaded to the server
+ let previous_chunk_uploaded = null;
+
+ // open request to server
+ xhr.open("post",(this.APIOrigin +'/batch'), true);
+ // set auth header
+ xhr.setRequestHeader("Authorization", "Bearer " + this.authToken);
+
+ // -----------------------------------------------
+ // Upload progress: client -> server
+ // -----------------------------------------------
+ xhr.upload.addEventListener('progress', function(e){
+ // update operation tracker
+ let chunk_uploaded;
+ if(previous_chunk_uploaded === null){
+ chunk_uploaded = e.loaded;
+ previous_chunk_uploaded = 0;
+ }else{
+ chunk_uploaded = e.loaded - previous_chunk_uploaded;
+ }
+ previous_chunk_uploaded += chunk_uploaded;
+ bytes_uploaded_to_server += chunk_uploaded;
+
+ // overall operation progress
+ let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
+ op_progress = op_progress > 100 ? 100 : op_progress;
+
+ // progress callback function
+ if(options.progress && typeof options.progress === 'function')
+ options.progress(operation_id, op_progress);
+ })
+
+ // -----------------------------------------------
+ // Upload progress: server -> cloud
+ // the following code will check the progress of the upload every 100ms
+ // -----------------------------------------------
+ let cloud_progress_check_interval = setInterval(function() {
+ // operation progress
+ let op_progress = ((bytes_uploaded_to_cloud + bytes_uploaded_to_server)/total_size * 100).toFixed(2);
+
+ op_progress = op_progress > 100 ? 100 : op_progress;
+ if(options.progress && typeof options.progress === 'function')
+ options.progress(operation_id, op_progress);
+ }, 100);
+
+ // -----------------------------------------------
+ // onabort
+ // -----------------------------------------------
+ xhr.onabort = ()=>{
+ // stop the cloud upload progress tracker
+ clearInterval(cloud_progress_check_interval);
+ // remove progress handler
+ this.socket.off('upload.progress', progress_handler);
+ // if an 'abort' callback is provided, call it
+ if(options.abort && typeof options.abort === 'function')
+ options.abort(operation_id);
+ }
+
+ // -----------------------------------------------
+ // on success/error
+ // -----------------------------------------------
+ xhr.onreadystatechange = async (e)=>{
+ if (xhr.readyState === 4) {
+ const resp = await utils.parseResponse(xhr);
+ // Error
+ if((xhr.status >= 400 && xhr.status < 600) || (options.strict && xhr.status === 218)) {
+ // stop the cloud upload progress tracker
+ clearInterval(cloud_progress_check_interval);
+
+ // remove progress handler
+ this.socket.off('upload.progress', progress_handler);
+
+ // If this is a 'strict' upload (i.e. status code is 218), we need to find out which operation failed
+ // and call the error callback with that operation.
+ if(options.strict && xhr.status === 218){
+ // find the operation that failed
+ let failed_operation;
+ for(let i=0; i{
+ // if we are in the gui environment, return the relative path as is
+ if(puter.env === 'gui')
+ return relativePath;
+
+ // if no relative path is provided, use the current working directory
+ if(!relativePath)
+ relativePath = '.';
+
+ // If relativePath is not provided, or it's not starting with a slash or tilde,
+ // it means it's a relative path. In that case, prepend the app's root directory.
+ if (!relativePath || (!relativePath.startsWith('/') && !relativePath.startsWith('~') && puter.appID)) {
+ relativePath = path.join('~/AppData', puter.appID, relativePath);
+ }
+
+ return relativePath;
+}
+
+export default getAbsolutePathForApp;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/Hosting.js b/packages/puter-js/src/modules/Hosting.js
new file mode 100644
index 000000000..fa2234164
--- /dev/null
+++ b/packages/puter-js/src/modules/Hosting.js
@@ -0,0 +1,136 @@
+import * as utils from '../lib/utils.js';
+import getAbsolutePathForApp from './FileSystem/utils/getAbsolutePathForApp.js';
+
+class Hosting{
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [Router]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [Apps]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ // todo document the `Subdomain` object.
+ list = utils.make_driver_method([], 'puter-subdomains', 'select');
+
+ create = async (...args) => {
+ let options = {};
+ // * allows for: puter.hosting.new('example-subdomain') *
+ if (typeof args[0] === 'string' && args.length === 1) {
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
+ // and use it as the subdomain. This is to make development easier.
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
+ args[0] = args[0].split('.')[0];
+ }
+
+ options = { object: { subdomain: args[0] }};
+ }
+ // if there are two string arguments, assume they are the subdomain and the target directory
+ // * allows for: puter.hosting.new('example-subdomain', '/path/to/target') *
+ else if (Array.isArray(args) && args.length === 2 && typeof args[0] === 'string') {
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
+ // and use it as the subdomain. This is to make development easier.
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
+ args[0] = args[0].split('.')[0];
+ }
+
+ // if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
+ if(args[1]){
+ args[1] = getAbsolutePathForApp(args[1]);
+ }
+
+ options = { object: { subdomain: args[0], root_dir: args[1] }};
+ }
+ // allows for: puter.hosting.new({ subdomain: 'subdomain' })
+ else if (typeof args[0] === 'object') {
+ options = { object: args[0] };
+ }
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['object'], 'puter-subdomains', 'create').call(this, options);
+ }
+
+ update = async(...args) => {
+ let options = {};
+ // If there are two string arguments, assume they are the subdomain and the target directory
+ // * allows for: puter.hosting.update('example-subdomain', '/path/to/target') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
+ // and use it as the subdomain. This is to make development easier.
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
+ args[0] = args[0].split('.')[0];
+ }
+
+ // if the target directory is not an absolute path, make it an absolute path relative to the app's root directory
+ if(args[1]){
+ args[1] = getAbsolutePathForApp(args[1]);
+ }
+
+ options = { id: {subdomain: args[0]}, object: { root_dir: args[1] ?? null }};
+ }
+
+ // Call the original chat.complete method
+ return await utils.make_driver_method(['object'], 'puter-subdomains', 'update').call(this, options);
+ }
+
+ get = async(...args) => {
+ let options = {};
+ // if there is one string argument, assume it is the subdomain
+ // * allows for: puter.hosting.get('example-subdomain') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
+ // and use it as the subdomain. This is to make development easier.
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
+ args[0] = args[0].split('.')[0];
+ }
+
+ options = { id: {subdomain: args[0]}};
+ }
+ return utils.make_driver_method(['uid'], 'puter-subdomains', 'read').call(this, options);
+ }
+
+ delete = async(...args) => {
+ let options = {};
+ // if there is one string argument, assume it is the subdomain
+ // * allows for: puter.hosting.get('example-subdomain') *
+ if (Array.isArray(args) && typeof args[0] === 'string') {
+ // if subdomain is in the format of a `subdomain.puter.site` or `subdomain.puter.com`, extract the subdomain
+ // and use it as the subdomain. This is to make development easier.
+ if (args[0].match(/^[a-z0-9]+\.puter\.(site|com)$/)) {
+ args[0] = args[0].split('.')[0];
+ }
+
+ options = { id: {subdomain: args[0]}};
+ }
+ return utils.make_driver_method(['uid'], 'puter-subdomains', 'delete').call(this, options);
+ }
+}
+
+export default Hosting;
diff --git a/packages/puter-js/src/modules/KV.js b/packages/puter-js/src/modules/KV.js
new file mode 100644
index 000000000..c04056065
--- /dev/null
+++ b/packages/puter-js/src/modules/KV.js
@@ -0,0 +1,205 @@
+import * as utils from '../lib/utils.js'
+
+class KV{
+ MAX_KEY_SIZE = 1024;
+ MAX_VALUE_SIZE = 400 * 1024;
+
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [KV]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [KV]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ /**
+ * Resolves to 'true' on success, or rejects with an error on failure
+ *
+ * `key` cannot be undefined or null.
+ * `key` size cannot be larger than 1mb.
+ * `value` size cannot be larger than 10mb.
+ */
+ set = utils.make_driver_method(['key', 'value'], 'puter-kvstore', 'set',{
+ preprocess: (args)=>{
+ // key cannot be undefined or null
+ if(args.key === undefined || args.key === null){
+ throw { message: 'Key cannot be undefined', code: 'key_undefined'};
+ }
+ // key size cannot be larger than MAX_KEY_SIZE
+ if(args.key.length > this.MAX_KEY_SIZE){
+ throw {message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'};
+ }
+ // value size cannot be larger than MAX_VALUE_SIZE
+ if(args.value && args.value.length > this.MAX_VALUE_SIZE){
+ throw {message: 'Value size cannot be larger than ' + this.MAX_VALUE_SIZE, code: 'value_too_large'};
+ }
+ return args;
+ }
+ })
+
+ /**
+ * Resolves to the value if the key exists, or `undefined` if the key does not exist. Rejects with an error on failure.
+ */
+ get = utils.make_driver_method(['key'], 'puter-kvstore', 'get', {
+ preprocess: (args)=>{
+ // key size cannot be larger than MAX_KEY_SIZE
+ if(args.key.length > this.MAX_KEY_SIZE){
+ throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
+ }
+
+ return args;
+ },
+ transform: (res)=>{
+ return res;
+ }
+ })
+
+ incr = async(...args) => {
+ let options = {};
+
+ // arguments are required
+ if(!args || args.length === 0){
+ throw ({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ options.key = args[0];
+ options.amount = args[1] ?? 1;
+
+ // key size cannot be larger than MAX_KEY_SIZE
+ if(options.key.length > this.MAX_KEY_SIZE){
+ throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
+ }
+
+ return utils.make_driver_method(['key'], 'puter-kvstore', 'incr').call(this, options);
+ }
+
+ decr = async(...args) => {
+ let options = {};
+
+ // arguments are required
+ if(!args || args.length === 0){
+ throw ({message: 'Arguments are required', code: 'arguments_required'});
+ }
+
+ options.key = args[0];
+ options.amount = args[1] ?? 1;
+
+ // key size cannot be larger than MAX_KEY_SIZE
+ if(options.key.length > this.MAX_KEY_SIZE){
+ throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
+ }
+
+ return utils.make_driver_method(['key'], 'puter-kvstore', 'decr').call(this, options);
+ }
+
+ // resolves to 'true' on success, or rejects with an error on failure
+ // will still resolve to 'true' if the key does not exist
+ del = utils.make_driver_method(['key'], 'puter-kvstore', 'del', {
+ preprocess: (args)=>{
+ // key size cannot be larger than this.MAX_KEY_SIZE
+ if(args.key.length > this.MAX_KEY_SIZE){
+ throw ({message: 'Key size cannot be larger than ' + this.MAX_KEY_SIZE, code: 'key_too_large'});
+ }
+
+ return args;
+ }
+ });
+
+ list = async(...args) => {
+ let options = {};
+ let pattern;
+ let returnValues = false;
+
+ // list(true) or list(pattern, true) will return the key-value pairs
+ if((args && args.length === 1 && args[0] === true) || (args && args.length === 2 && args[1] === true)){
+ options = {};
+ returnValues = true;
+ }
+ // return only the keys, default behavior
+ else{
+ options = { as: 'keys'};
+ }
+
+ // list(pattern)
+ // list(pattern, true)
+ if((args && args.length === 1 && typeof args[0] === 'string') || (args && args.length === 2 && typeof args[0] === 'string' && args[1] === true)){
+ pattern = args[0];
+ }
+
+ return utils.make_driver_method([], 'puter-kvstore', 'list', {
+ transform: (res)=>{
+ // glob pattern was provided
+ if(pattern){
+ // consider both the key and the value
+ if(!returnValues) {
+ let keys = res.filter((key)=>{
+ return globMatch(pattern, key);
+ });
+ return keys;
+ }else{
+ let keys = res.filter((key_value_pair)=>{
+ return globMatch(pattern, key_value_pair.key);
+ });
+ return keys;
+ }
+ }
+
+ return res;
+ }
+ }).call(this, options);
+ }
+
+ // resolve to 'true' on success, or rejects with an error on failure
+ // will still resolve to 'true' if there are no keys
+ flush = utils.make_driver_method([], 'puter-kvstore', 'flush')
+
+ // clear is an alias for flush
+ clear = this.flush;
+}
+
+
+function globMatch(pattern, str) {
+ const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
+ let regexPattern = escapeRegExp(pattern)
+ .replace(/\\\*/g, '.*') // Replace * with .*
+ .replace(/\\\?/g, '.') // Replace ? with .
+ .replace(/\\\[/g, '[') // Replace [ with [
+ .replace(/\\\]/g, ']') // Replace ] with ]
+ .replace(/\\\^/g, '^'); // Replace ^ with ^
+
+ let re = new RegExp('^' + regexPattern + '$');
+ return re.test(str);
+}
+
+
+
+export default KV
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/OS.js b/packages/puter-js/src/modules/OS.js
new file mode 100644
index 000000000..a436e58a3
--- /dev/null
+++ b/packages/puter-js/src/modules/OS.js
@@ -0,0 +1,90 @@
+import * as utils from '../lib/utils.js'
+
+class OS{
+ /**
+ * Creates a new instance with the given authentication token, API origin, and app ID,
+ *
+ * @class
+ * @param {string} authToken - Token used to authenticate the user.
+ * @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
+ * @param {string} appID - ID of the app to use.
+ */
+ constructor (authToken, APIOrigin, appID) {
+ this.authToken = authToken;
+ this.APIOrigin = APIOrigin;
+ this.appID = appID;
+ }
+
+ /**
+ * Sets a new authentication token.
+ *
+ * @param {string} authToken - The new authentication token.
+ * @memberof [OS]
+ * @returns {void}
+ */
+ setAuthToken (authToken) {
+ this.authToken = authToken;
+ }
+
+ /**
+ * Sets the API origin.
+ *
+ * @param {string} APIOrigin - The new API origin.
+ * @memberof [Apps]
+ * @returns {void}
+ */
+ setAPIOrigin (APIOrigin) {
+ this.APIOrigin = APIOrigin;
+ }
+
+ user = function(...args){
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ success: args[0],
+ error: args[1],
+ };
+ }
+
+ return new Promise((resolve, reject) => {
+ const xhr = utils.initXhr('/whoami', this.APIOrigin, this.authToken, 'get');
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+ }
+
+ version = function(...args){
+ let options;
+
+ // If first argument is an object, it's the options
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = args[0];
+ } else {
+ // Otherwise, we assume separate arguments are provided
+ options = {
+ success: args[0],
+ error: args[1],
+ // Add more if needed...
+ };
+ }
+
+ return new Promise((resolve, reject) => {
+ const xhr = utils.initXhr('/version', this.APIOrigin, this.authToken, 'get');
+
+ // set up event handlers for load and error events
+ utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
+
+ xhr.send();
+ })
+ }
+}
+
+export default OS
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/PuterDialog.js b/packages/puter-js/src/modules/PuterDialog.js
new file mode 100644
index 000000000..8be194176
--- /dev/null
+++ b/packages/puter-js/src/modules/PuterDialog.js
@@ -0,0 +1,373 @@
+class PuterDialog extends HTMLElement {
+ constructor(resolve, reject) {
+ super();
+ this.attachShadow({ mode: 'open' });
+
+ this.reject = reject;
+ this.resolve = resolve;
+
+ // Dialog
+ this.shadowRoot.innerHTML = `
+
+ `;
+
+ // Event listener for the 'message' event
+ this.messageListener = async (event) => {
+ if (event.data.msg === 'puter.token') {
+ this.close();
+ // Set the authToken property
+ puter.setAuthToken(event.data.token);
+ // update appID
+ puter.setAppID(event.data.app_uid);
+ // Remove the event listener to avoid memory leaks
+ window.removeEventListener('message', this.messageListener);
+
+ puter.puterAuthState.authGranted = true;
+ // Resolve the promise
+ this.resolve();
+
+ // Call onAuth callback
+ if(puter.onAuth && typeof puter.onAuth === 'function'){
+ puter.getUser().then((user) => {
+ puter.onAuth(user)
+ });
+ }
+
+ puter.puterAuthState.isPromptOpen = false;
+ // Resolve or reject any waiting promises.
+ if (puter.puterAuthState.resolver) {
+ if (puter.puterAuthState.authGranted) {
+ puter.puterAuthState.resolver.resolve();
+ } else {
+ puter.puterAuthState.resolver.reject();
+ }
+ puter.puterAuthState.resolver = null;
+ };
+ }
+ };
+
+ }
+
+ // Optional: Handle dialog cancellation as rejection
+ cancelListener = () => {
+ this.close();
+ window.removeEventListener('message', this.messageListener);
+ puter.puterAuthState.authGranted = false;
+ puter.puterAuthState.isPromptOpen = false;
+
+ // Reject the promise with an error message indicating user cancellation.
+ // This ensures that the calling code's catch block will be triggered.
+ this.reject(new Error('User cancelled the authentication'));
+
+ // If there's a resolver set, use it to reject the waiting promise as well.
+ if (puter.puterAuthState.resolver) {
+ puter.puterAuthState.resolver.reject(new Error('User cancelled the authentication'));
+ puter.puterAuthState.resolver = null;
+ }
+ };
+
+ connectedCallback() {
+ // Add event listener to the button
+ this.shadowRoot.querySelector('#launch-auth-popup').addEventListener('click', ()=>{
+ let w = 600;
+ let h = 400;
+ let title = 'Puter';
+ var left = (screen.width/2)-(w/2);
+ var top = (screen.height/2)-(h/2);
+ window.open(puter.defaultGUIOrigin + '/?embedded_in_popup=true&request_auth=true',
+ title,
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
+ })
+
+ // Add the event listener to the window object
+ window.addEventListener('message', this.messageListener);
+
+ // Add event listeners for cancel and close buttons
+ this.shadowRoot.querySelector('#launch-auth-popup-cancel').addEventListener('click', this.cancelListener);
+ this.shadowRoot.querySelector('.close-btn').addEventListener('click',this.cancelListener);
+ }
+
+ open() {
+ this.shadowRoot.querySelector('dialog').showModal();
+ }
+
+ close() {
+ this.shadowRoot.querySelector('dialog').close();
+ }
+}
+customElements.define('puter-dialog', PuterDialog);
+
+module.exports = PuterDialog;
\ No newline at end of file
diff --git a/packages/puter-js/src/modules/UI.js b/packages/puter-js/src/modules/UI.js
new file mode 100644
index 000000000..849d4cad8
--- /dev/null
+++ b/packages/puter-js/src/modules/UI.js
@@ -0,0 +1,1019 @@
+import FSItem from './FSItem.js';
+import PuterDialog from './PuterDialog.js';
+import EventListener from '../lib/EventListener.js';
+
+// AppConnection provides an API for interacting with another app.
+// It's returned by UI methods, and cannot be constructed directly by user code.
+// For basic usage:
+// - postMessage(message) Send a message to the target app
+// - on('message', callback) Listen to messages from the target app
+class AppConnection extends EventListener {
+ // targetOrigin for postMessage() calls to Puter
+ #puterOrigin = '*';
+
+ // Whether the target app is open
+ #isOpen;
+
+ constructor(messageTarget, appInstanceID, targetAppInstanceID) {
+ super([
+ 'message', // The target sent us something with postMessage()
+ 'close', // The target app was closed
+ ]);
+ this.messageTarget = messageTarget;
+ this.appInstanceID = appInstanceID;
+ this.targetAppInstanceID = targetAppInstanceID;
+ this.#isOpen = true;
+
+ // TODO: Set this.#puterOrigin to the puter origin
+
+ window.addEventListener('message', event => {
+ if (event.data.msg === 'messageToApp') {
+ if (event.data.appInstanceID !== this.targetAppInstanceID) {
+ // Message is from a different AppConnection; ignore it.
+ return;
+ }
+ if (event.data.targetAppInstanceID !== this.appInstanceID) {
+ console.error(`AppConnection received message intended for wrong app! appInstanceID=${this.appInstanceID}, target=${event.data.targetAppInstanceID}`);
+ return;
+ }
+ this.emit('message', event.data.contents);
+ return;
+ }
+
+ if (event.data.msg === 'appClosed') {
+ if (event.data.appInstanceID !== this.targetAppInstanceID) {
+ // Message is from a different AppConnection; ignore it.
+ return;
+ }
+
+ this.#isOpen = false;
+ this.emit('close', {
+ appInstanceID: this.targetAppInstanceID,
+ });
+ }
+ });
+ }
+
+ postMessage(message) {
+ if (!this.#isOpen) {
+ console.warn('Trying to post message on a closed AppConnection');
+ return;
+ }
+
+ this.messageTarget.postMessage({
+ msg: 'messageToApp',
+ appInstanceID: this.appInstanceID,
+ targetAppInstanceID: this.targetAppInstanceID,
+ targetAppOrigin: '*', // TODO: Specify this somehow
+ contents: message,
+ }, this.#puterOrigin);
+ }
+
+ // Attempt to close the target application
+ close() {
+ if (!this.#isOpen) {
+ console.warn('Trying to close an app on a closed AppConnection');
+ return;
+ }
+
+ this.messageTarget.postMessage({
+ msg: 'closeApp',
+ appInstanceID: this.appInstanceID,
+ targetAppInstanceID: this.targetAppInstanceID,
+ }, this.#puterOrigin);
+ }
+}
+
+class UI extends EventListener {
+ // Used to generate a unique message id for each message sent to the host environment
+ // we start from 1 because 0 is falsy and we want to avoid that for the message id
+ #messageID = 1;
+
+ // Holds the callback functions for the various events
+ // that are triggered when a watched item has changed.
+ itemWatchCallbackFunctions = [];
+
+ // Holds the unique app instance ID that is provided by the host environment
+ appInstanceID;
+
+ // Holds the unique app instance ID for the parent (if any), which is provided by the host environment
+ parentInstanceID;
+
+ // If we have a parent app, holds an AppConnection to it
+ #parentAppConnection = null;
+
+ // Holds the callback functions for the various events
+ // that can be triggered by the host environment's messages.
+ #callbackFunctions = [];
+
+ // onWindowClose() is executed right before the window is closed. Users can override this function
+ // to perform a variety of tasks right before window is closed. Users can override this function.
+ #onWindowClose;
+
+ // When an item is opened by this app in any way onItemsOpened() is executed. Users can override this function.
+ #onItemsOpened;
+
+ #onLaunchedWithItems;
+
+ // List of events that can be listened to.
+ #eventNames;
+
+ // The most recent value that we received for a given broadcast, by name.
+ #lastBroadcastValue = new Map(); // name -> data
+
+ // Replaces boilerplate for most methods: posts a message to the GUI with a unique ID, and sets a callback for it.
+ #postMessageWithCallback = function(name, resolve, args = {}) {
+ const msg_id = this.#messageID++;
+ this.messageTarget?.postMessage({
+ msg: name,
+ env: this.env,
+ appInstanceID: this.appInstanceID,
+ uuid: msg_id,
+ ...args,
+ }, '*');
+ //register callback
+ this.#callbackFunctions[msg_id] = resolve;
+ }
+
+ constructor (appInstanceID, parentInstanceID, appID, env) {
+ const eventNames = [
+ 'localeChanged',
+ 'themeChanged',
+ ];
+ super(eventNames);
+ this.#eventNames = eventNames;
+ this.appInstanceID = appInstanceID;
+ this.parentInstanceID = parentInstanceID;
+ this.appID = appID;
+ this.env = env;
+
+ if(this.env === 'app'){
+ this.messageTarget = window.parent;
+ }
+ else if(this.env === 'gui'){
+ return;
+ }
+
+ if (this.parentInstanceID) {
+ this.#parentAppConnection = new AppConnection(this.messageTarget, this.appInstanceID, this.parentInstanceID);
+ }
+
+ // Tell the host environment that this app is using the Puter SDK and is ready to receive messages,
+ // this will allow the OS to send custom messages to the app
+ this.messageTarget?.postMessage({
+ msg: "READY",
+ appInstanceID: this.appInstanceID,
+ }, '*');
+
+ // When this app's window is focused send a message to the host environment
+ window.addEventListener('focus', (e) => {
+ this.messageTarget?.postMessage({
+ msg: "windowFocused",
+ appInstanceID: this.appInstanceID,
+ }, '*');
+ });
+
+ // Bind the message event listener to the window
+ let lastDraggedOverElement = null;
+ window.addEventListener('message', async (e) => {
+ // `error`
+ if(e.data.error){
+ throw e.data.error;
+ }
+ // `focus` event
+ else if(e.data.msg && e.data.msg === 'focus'){
+ window.focus();
+ }
+ // `click` event
+ else if(e.data.msg && e.data.msg === 'click'){
+ // Get the element that was clicked on and click it
+ const clicked_el = document.elementFromPoint(e.data.x, e.data.y);
+ if(clicked_el !== null)
+ clicked_el.click();
+ }
+ // `dragover` event based on the `drag` event from the host environment
+ else if(e.data.msg && e.data.msg === 'drag'){
+ // Get the element being dragged over
+ const draggedOverElement = document.elementFromPoint(e.data.x, e.data.y);
+ if(draggedOverElement !== lastDraggedOverElement){
+ // If the last element exists and is different from the current, dispatch a dragleave on it
+ if(lastDraggedOverElement){
+ const dragLeaveEvent = new Event('dragleave', {
+ bubbles: true,
+ cancelable: true,
+ clientX: e.data.x,
+ clientY: e.data.y
+ });
+ lastDraggedOverElement.dispatchEvent(dragLeaveEvent);
+ }
+ // If the current element exists and is different from the last, dispatch dragenter on it
+ if(draggedOverElement){
+ const dragEnterEvent = new Event('dragenter', {
+ bubbles: true,
+ cancelable: true,
+ clientX: e.data.x,
+ clientY: e.data.y
+ });
+ draggedOverElement.dispatchEvent(dragEnterEvent);
+ }
+
+ // Update the lastDraggedOverElement
+ lastDraggedOverElement = draggedOverElement;
+ }
+ }
+ // `drop` event
+ else if(e.data.msg && e.data.msg === 'drop'){
+ if(lastDraggedOverElement){
+ const dropEvent = new CustomEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ detail: {
+ clientX: e.data.x,
+ clientY: e.data.y,
+ items: e.data.items
+ }
+ });
+ lastDraggedOverElement.dispatchEvent(dropEvent);
+
+ // Reset the lastDraggedOverElement
+ lastDraggedOverElement = null;
+ }
+ }
+ // windowWillClose
+ else if(e.data.msg === 'windowWillClose'){
+ // If the user has not overridden onWindowClose() then send a message back to the host environment
+ // to let it know that it is ok to close the window.
+ if(this.#onWindowClose === undefined){
+ this.messageTarget?.postMessage({
+ msg: true,
+ appInstanceID: this.appInstanceID,
+ original_msg_id: e.data.msg_id,
+ }, '*');
+ }
+ // If the user has overridden onWindowClose() then send a message back to the host environment
+ // to let it know that it is NOT ok to close the window. Then execute onWindowClose() and the user will
+ // have to manually close the window.
+ else{
+ this.messageTarget?.postMessage({
+ msg: false,
+ appInstanceID: this.appInstanceID,
+ original_msg_id: e.data.msg_id,
+ }, '*');
+ this.#onWindowClose();
+ }
+ }
+ // itemsOpened
+ else if(e.data.msg === 'itemsOpened'){
+ // If the user has not overridden onItemsOpened() then only send a message back to the host environment
+ if(this.#onItemsOpened === undefined){
+ this.messageTarget?.postMessage({
+ msg: true,
+ appInstanceID: this.appInstanceID,
+ original_msg_id: e.data.msg_id,
+ }, '*');
+ }
+ // If the user has overridden onItemsOpened() then send a message back to the host environment
+ // and execute onItemsOpened()
+ else{
+ this.messageTarget?.postMessage({
+ msg: false,
+ appInstanceID: this.appInstanceID,
+ original_msg_id: e.data.msg_id,
+ }, '*');
+
+ let items = [];
+ if(e.data.items.length > 0){
+ for (let index = 0; index < e.data.items.length; index++)
+ items.push(new FSItem(e.data.items[index]))
+ }
+ this.#onItemsOpened(items);
+ }
+ }
+ // getAppDataSucceeded
+ else if(e.data.msg === 'getAppDataSucceeded'){
+ let appDataItem = new FSItem(e.data.item);
+ if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
+ this.#callbackFunctions[e.data.original_msg_id](appDataItem);
+ }
+ }
+ // readAppDataFileSucceeded
+ else if(e.data.msg === 'readAppDataFileSucceeded'){
+ let appDataItem = new FSItem(e.data.item);
+ if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
+ this.#callbackFunctions[e.data.original_msg_id](appDataItem);
+ }
+ }
+ // readAppDataFileFailed
+ else if(e.data.msg === 'readAppDataFileFailed'){
+ if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
+ this.#callbackFunctions[e.data.original_msg_id](null);
+ }
+ }
+ // Determine if this is a response to a previous message and if so, is there
+ // a callback function for this message? if answer is yes to both then execute the callback
+ else if(e.data.original_msg_id && this.#callbackFunctions[e.data.original_msg_id]){
+ if(e.data.msg === 'fileOpenPicked'){
+ // 1 item returned
+ if(e.data.items.length === 1){
+ this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.items[0]));
+ }
+ // multiple items returned
+ else if(e.data.items.length > 1){
+ // multiple items returned
+ let items = [];
+ for (let index = 0; index < e.data.items.length; index++)
+ items.push(new FSItem(e.data.items[index]))
+ this.#callbackFunctions[e.data.original_msg_id](items);
+ }
+ }
+ else if(e.data.msg === 'directoryPicked'){
+ // 1 item returned
+ if(e.data.items.length === 1){
+ this.#callbackFunctions[e.data.original_msg_id](new FSItem({
+ uid: e.data.items[0].uid,
+ name: e.data.items[0].fsentry_name,
+ path: e.data.items[0].path,
+ readURL: e.data.items[0].read_url,
+ writeURL: e.data.items[0].write_url,
+ metadataURL: e.data.items[0].metadata_url,
+ isDirectory: true,
+ size: e.data.items[0].fsentry_size,
+ accessed: e.data.items[0].fsentry_accessed,
+ modified: e.data.items[0].fsentry_modified,
+ created: e.data.items[0].fsentry_created,
+ }));
+ }
+ // multiple items returned
+ else if(e.data.items.length > 1){
+ // multiple items returned
+ let items = [];
+ for (let index = 0; index < e.data.items.length; index++)
+ items.push(new FSItem(e.data.items[index]))
+ this.#callbackFunctions[e.data.original_msg_id](items);
+ }
+ }
+ else if(e.data.msg === 'colorPicked'){
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](e.data.color);
+ }
+ else if(e.data.msg === 'fontPicked'){
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](e.data.font);
+ }
+ else if(e.data.msg === 'alertResponded'){
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](e.data.response);
+ }
+ else if(e.data.msg === 'promptResponded'){
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](e.data.response);
+ }
+ else if(e.data.msg === "fileSaved"){
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](new FSItem(e.data.saved_file));
+ }
+ else if (e.data.msg === 'childAppLaunched') {
+ // execute callback with a new AppConnection to the child
+ const connection = new AppConnection(this.messageTarget, this.appInstanceID, e.data.child_instance_id);
+ this.#callbackFunctions[e.data.original_msg_id](connection);
+ }
+ else{
+ // execute callback
+ this.#callbackFunctions[e.data.original_msg_id](e.data);
+ }
+
+ //remove this callback function since it won't be needed again
+ delete this.#callbackFunctions[e.data.original_msg_id];
+ }
+ // Item Watch response
+ else if(e.data.msg === "itemChanged" && e.data.data && e.data.data.uid){
+ //excute callback
+ if(itemWatchCallbackFunctions[e.data.data.uid] && typeof itemWatchCallbackFunctions[e.data.data.uid] === 'function')
+ itemWatchCallbackFunctions[e.data.data.uid](e.data.data);
+ }
+ // Broadcasts
+ else if (e.data.msg === 'broadcast') {
+ const { name, data } = e.data;
+ if (!this.#eventNames.includes(name)) {
+ return;
+ }
+ this.emit(name, data);
+ this.#lastBroadcastValue.set(name, data);
+ }
+ });
+ }
+
+ onWindowClose = function(callback) {
+ this.#onWindowClose = callback;
+ }
+
+ onItemsOpened = function(callback) {
+ // DEPRECATED - this is also called when items are dropped on the app, which in new versions should be handled
+ // with the 'drop' event.
+ // Check if a file was opened with this app, i.e. check URL parameters of window/iframe
+ // Even though the file has been opened when the app is launched, we need to wait for the onItemsOpened callback to be set
+ // before we can call it. This is why we need to check the URL parameters here.
+ // This should also be done only the very first time the callback is set (hence the if(!this.#onItemsOpened) check) since
+ // the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
+ if(!this.#onItemsOpened){
+ let URLParams = new URLSearchParams(window.location.search);
+ if(URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url')){
+ let fpath = URLParams.get('puter.item.path');
+ fpath = `~/` + fpath.split('/').slice(2).join('/');
+ callback([new FSItem({
+ name: URLParams.get('puter.item.name'),
+ path: fpath,
+ uid: URLParams.get('puter.item.uid'),
+ readURL: URLParams.get('puter.item.read_url'),
+ writeURL: URLParams.get('puter.item.write_url'),
+ metadataURL: URLParams.get('puter.item.metadata_url'),
+ size: URLParams.get('puter.item.size'),
+ accessed: URLParams.get('puter.item.accessed'),
+ modified: URLParams.get('puter.item.modified'),
+ created: URLParams.get('puter.item.created'),
+ })]);
+ }
+ }
+
+ this.#onItemsOpened = callback;
+ }
+
+ onLaunchedWithItems = function(callback) {
+ // Check if a file was opened with this app, i.e. check URL parameters of window/iframe
+ // Even though the file has been opened when the app is launched, we need to wait for the onLaunchedWithItems callback to be set
+ // before we can call it. This is why we need to check the URL parameters here.
+ // This should also be done only the very first time the callback is set (hence the if(!this.#onLaunchedWithItems) check) since
+ // the URL parameters will be checked every time the callback is set which can cause problems if the callback is set multiple times.
+ if(!this.#onLaunchedWithItems){
+ let URLParams = new URLSearchParams(window.location.search);
+ if(URLParams.has('puter.item.name') && URLParams.has('puter.item.uid') && URLParams.has('puter.item.read_url')){
+ let fpath = URLParams.get('puter.item.path');
+ fpath = `~/` + fpath.split('/').slice(2).join('/');
+ callback([new FSItem({
+ name: URLParams.get('puter.item.name'),
+ path: fpath,
+ uid: URLParams.get('puter.item.uid'),
+ readURL: URLParams.get('puter.item.read_url'),
+ writeURL: URLParams.get('puter.item.write_url'),
+ metadataURL: URLParams.get('puter.item.metadata_url'),
+ size: URLParams.get('puter.item.size'),
+ accessed: URLParams.get('puter.item.accessed'),
+ modified: URLParams.get('puter.item.modified'),
+ created: URLParams.get('puter.item.created'),
+ })]);
+ }
+ }
+
+ this.#onLaunchedWithItems = callback;
+ }
+
+ alert = function(message, buttons, options, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('ALERT', resolve, { message, buttons, options });
+ })
+ }
+
+ prompt = function(message, placeholder, options, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('PROMPT', resolve, { message, placeholder, options });
+ })
+ }
+
+ showDirectoryPicker = function(options, callback){
+ return new Promise((resolve) => {
+ const msg_id = this.#messageID++;
+ if(this.env === 'app'){
+ this.messageTarget?.postMessage({
+ msg: "showDirectoryPicker",
+ appInstanceID: this.appInstanceID,
+ uuid: msg_id,
+ options: options,
+ env: this.env,
+ }, '*');
+ }else{
+ let w = 700;
+ let h = 400;
+ let title = 'Puter: Open Directory';
+ var left = (screen.width/2)-(w/2);
+ var top = (screen.height/2)-(h/2);
+ window.open(`${puter.defaultGUIOrigin}/action/show-directory-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options)}`,
+ title,
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
+ }
+
+ //register callback
+ this.#callbackFunctions[msg_id] = resolve;
+ })
+ }
+
+ showOpenFilePicker = function(options, callback){
+ return new Promise((resolve) => {
+ const msg_id = this.#messageID++;
+
+ if(this.env === 'app'){
+ this.messageTarget?.postMessage({
+ msg: "showOpenFilePicker",
+ appInstanceID: this.appInstanceID,
+ uuid: msg_id,
+ options: options ?? {},
+ env: this.env,
+ }, '*');
+ }else{
+ let w = 700;
+ let h = 400;
+ let title = 'Puter: Open File';
+ var left = (screen.width/2)-(w/2);
+ var top = (screen.height/2)-(h/2);
+ window.open(`${puter.defaultGUIOrigin}/action/show-open-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&options=${JSON.stringify(options ?? {})}`,
+ title,
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
+ }
+ //register callback
+ this.#callbackFunctions[msg_id] = resolve;
+ })
+ }
+
+ showFontPicker = function(options){
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('showFontPicker', resolve, { options: options ?? {} });
+ })
+ }
+
+ showColorPicker = function(options){
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('showColorPicker', resolve, { options: options ?? {} });
+ })
+ }
+
+ showSaveFilePicker = function(content, suggestedName){
+ return new Promise((resolve) => {
+ const msg_id = this.#messageID++;
+ const url = (Object.prototype.toString.call(content) === '[object URL]' ? content : undefined);
+
+ if(this.env === 'app'){
+ this.messageTarget?.postMessage({
+ msg: "showSaveFilePicker",
+ appInstanceID: this.appInstanceID,
+ content: url ? undefined : content,
+ url: url ? url.toString() : undefined,
+ suggestedName: suggestedName ?? '',
+ env: this.env,
+ uuid: msg_id
+ }, '*');
+ }else{
+ window.addEventListener('message', async (e) => {
+ if(e.data?.msg === "sendMeFileData"){
+ // Send the blob URL to the host environment
+ e.source.postMessage({
+ msg: "showSaveFilePickerPopup",
+ content: url ? undefined : content,
+ url: url ? url.toString() : undefined,
+ suggestedName: suggestedName ?? '',
+ env: this.env,
+ uuid: msg_id
+ }, '*');
+
+ // remove the event listener
+ window.removeEventListener('message', this);
+ }
+ });
+ // Create a Blob from your binary data
+ let blob = new Blob([content], {type: 'application/octet-stream'});
+
+ // Create an object URL for the Blob
+ let objectUrl = URL.createObjectURL(blob);
+
+ let w = 700;
+ let h = 400;
+ let title = 'Puter: Save File';
+ var left = (screen.width/2)-(w/2);
+ var top = (screen.height/2)-(h/2);
+ window.open(`${puter.defaultGUIOrigin}/action/show-save-file-picker?embedded_in_popup=true&msg_id=${msg_id}&appInstanceID=${this.appInstanceID}&env=${this.env}&blobUrl=${encodeURIComponent(objectUrl)}`,
+ title,
+ 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
+ }
+ //register callback
+ this.#callbackFunctions[msg_id] = resolve;
+ })
+ }
+
+ setWindowTitle = function(title, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('setWindowTitle', resolve, { new_title: title });
+ })
+ }
+
+ setWindowWidth = function(width, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('setWindowWidth', resolve, { width });
+ })
+ }
+
+ setWindowHeight = function(height, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('setWindowHeight', resolve, { height });
+ })
+ }
+
+ setWindowSize = function(width, height, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('setWindowSize', resolve, { width, height });
+ })
+ }
+
+ setWindowPosition = function(x, y, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('setWindowPosition', resolve, { x, y });
+ })
+ }
+
+ /**
+ * Asynchronously extracts entries from DataTransferItems, like files and directories.
+ *
+ * @private
+ * @function
+ * @async
+ * @param {DataTransferItemList} dataTransferItems - List of data transfer items from a drag-and-drop operation.
+ * @param {Object} [options={}] - Optional settings.
+ * @param {boolean} [options.raw=false] - Determines if the file path should be processed.
+ * @returns {Promise>} - A promise that resolves to an array of File or Entry objects.
+ * @throws {Error} - Throws an error if there's an EncodingError and provides information about how to solve it.
+ *
+ * @example
+ * const items = event.dataTransfer.items;
+ * const entries = await getEntriesFromDataTransferItems(items, { raw: false });
+ */
+ getEntriesFromDataTransferItems = async function(dataTransferItems, options = { raw: false }) {
+ const checkErr = (err) => {
+ if (this.getEntriesFromDataTransferItems.didShowInfo) return
+ if (err.name !== 'EncodingError') return
+ this.getEntriesFromDataTransferItems.didShowInfo = true
+ const infoMsg = `${err.name} occured within datatransfer-files-promise module\n`
+ + `Error message: "${err.message}"\n`
+ + 'Try serving html over http if currently you are running it from the filesystem.'
+ console.warn(infoMsg)
+ }
+
+ const readFile = (entry, path = '') => {
+ return new Promise((resolve, reject) => {
+ entry.file(file => {
+ if (!options.raw) file.filepath = path + file.name // save full path
+ resolve(file)
+ }, (err) => {
+ checkErr(err)
+ reject(err)
+ })
+ })
+ }
+
+ const dirReadEntries = (dirReader, path) => {
+ return new Promise((resolve, reject) => {
+ dirReader.readEntries(async entries => {
+ let files = []
+ for (let entry of entries) {
+ const itemFiles = await getFilesFromEntry(entry, path)
+ files = files.concat(itemFiles)
+ }
+ resolve(files)
+ }, (err) => {
+ checkErr(err)
+ reject(err)
+ })
+ })
+ }
+
+ const readDir = async (entry, path) => {
+ const dirReader = entry.createReader()
+ const newPath = path + entry.name + '/'
+ let files = []
+ let newFiles
+ do {
+ newFiles = await dirReadEntries(dirReader, newPath)
+ files = files.concat(newFiles)
+ } while (newFiles.length > 0)
+ return files
+ }
+
+ const getFilesFromEntry = async (entry, path = '') => {
+ if(entry === null)
+ return;
+ else if (entry.isFile) {
+ const file = await readFile(entry, path)
+ return [file]
+ }
+ else if (entry.isDirectory) {
+ const files = await readDir(entry, path)
+ files.push(entry)
+ return files
+ }
+ }
+
+ let files = []
+ let entries = []
+
+ // Pull out all entries before reading them
+ for (let i = 0, ii = dataTransferItems.length; i < ii; i++) {
+ entries.push(dataTransferItems[i].webkitGetAsEntry())
+ }
+
+ // Recursively read through all entries
+ for (let entry of entries) {
+ const newFiles = await getFilesFromEntry(entry)
+ files = files.concat(newFiles)
+ }
+
+ return files
+ }
+
+ authenticateWithPuter = function() {
+ if(this.env !== 'web'){
+ return;
+ }
+
+ // if authToken is already present, resolve immediately
+ if(this.authToken){
+ return new Promise((resolve) => {
+ resolve();
+ })
+ }
+
+ // If a prompt is already open, return a promise that resolves based on the existing prompt's result.
+ if (puter.puterAuthState.isPromptOpen) {
+ return new Promise((resolve, reject) => {
+ puter.puterAuthState.resolver = { resolve, reject };
+ });
+ }
+
+ // Show the permission prompt and set the state.
+ puter.puterAuthState.isPromptOpen = true;
+ puter.puterAuthState.authGranted = null;
+
+ return new Promise((resolve, reject) => {
+ if (!puter.authToken) {
+ const puterDialog = new PuterDialog(resolve, reject);
+ document.body.appendChild(puterDialog);
+ puterDialog.open();
+ } else {
+ // If authToken is already present, resolve immediately
+ resolve();
+ }
+ });
+ }
+
+ // Returns a Promise
+ launchApp = function(appName, args, callback) {
+ return new Promise((resolve) => {
+ // if appName is an object and args is not set, then appName is actually args
+ if (typeof appName === 'object' && !args) {
+ args = appName;
+ appName = undefined;
+ }
+
+ this.#postMessageWithCallback('launchApp', resolve, { app_name: appName, args });
+ })
+ }
+
+ parentApp() {
+ return this.#parentAppConnection;
+ }
+
+ createWindow = function (options, callback) {
+ return new Promise((resolve) => {
+ this.#postMessageWithCallback('createWindow', resolve, { options: options ?? {} });
+ })
+ }
+
+ // Menubar
+ menubar = function(){
+ // Remove previous style tag
+ document.querySelectorAll('style.puter-stylesheet').forEach(function(el) {
+ el.remove();
+ })
+
+ // Add new style tag
+ const style = document.createElement('style');
+ style.classList.add('puter-stylesheet');
+ style.innerHTML = `
+ .--puter-menubar {
+ border-bottom: 1px solid #e9e9e9;
+ background-color: #fbf9f9;
+ padding-top: 3px;
+ padding-bottom: 2px;
+ display: inline-block;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ height: 31px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 13px;
+ z-index: 9999;
+ }
+
+ .--puter-menubar, .--puter-menubar * {
+ user-select: none;
+ -webkit-user-select: none;
+ cursor: default;
+ }
+
+ .--puter-menubar .dropdown-item-divider>hr {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ border-bottom: none;
+ border-top: 1px solid #00000033;
+ }
+
+ .--puter-menubar>li {
+ display: inline-block;
+ padding: 10px 5px;
+ }
+
+ .--puter-menubar>li>ul {
+ display: none;
+ z-index: 999999999999;
+ list-style: none;
+ background-color: rgb(233, 233, 233);
+ width: 200px;
+ border: 1px solid #e4ebf3de;
+ box-shadow: 0px 0px 5px #00000066;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ color: #333;
+ border-radius: 4px;
+ padding: 2px;
+ min-width: 200px;
+ margin-top: 5px;
+ position: absolute;
+ }
+
+ .--puter-menubar .menubar-item {
+ display: block;
+ line-height: 24px;
+ margin-top: -7px;
+ text-align: center;
+ border-radius: 3px;
+ padding: 0 5px;
+ }
+
+ .--puter-menubar .menubar-item-open {
+ background-color: rgb(216, 216, 216);
+ }
+
+ .--puter-menubar .dropdown-item {
+ padding: 5px;
+ padding: 5px 30px;
+ list-style-type: none;
+ user-select: none;
+ font-size: 13px;
+ }
+
+ .--puter-menubar .dropdown-item-icon, .--puter-menubar .dropdown-item-icon-active {
+ pointer-events: none;
+ width: 18px;
+ height: 18px;
+ margin-left: -23px;
+ margin-bottom: -4px;
+ margin-right: 5px;
+ }
+ .--puter-menubar .dropdown-item-disabled .dropdown-item-icon{
+ display: inline-block !important;
+ }
+ .--puter-menubar .dropdown-item-disabled .dropdown-item-icon-active{
+ display: none !important;
+ }
+ .--puter-menubar .dropdown-item-icon-active {
+ display:none;
+ }
+ .--puter-menubar .dropdown-item:hover .dropdown-item-icon{
+ display: none;
+ }
+ .--puter-menubar .dropdown-item:hover .dropdown-item-icon-active{
+ display: inline-block;
+ }
+ .--puter-menubar .dropdown-item-hide-icon .dropdown-item-icon, .--puter-menubar .dropdown-item-hide-icon .dropdown-item-icon-active{
+ display: none !important;
+ }
+ .--puter-menubar .dropdown-item a {
+ color: #333;
+ text-decoration: none;
+ }
+
+ .--puter-menubar .dropdown-item:hover, .--puter-menubar .dropdown-item:hover a {
+ background-color: rgb(59 134 226);
+ color: white;
+ border-radius: 4px;
+ }
+
+ .--puter-menubar .dropdown-item-disabled, .--puter-menubar .dropdown-item-disabled:hover {
+ opacity: 0.5;
+ background-color: transparent;
+ color: initial;
+ cursor: initial;
+ pointer-events: none;
+ }
+
+ .--puter-menubar .menubar * {
+ user-select: none;
+ }
+ `;
+ let head = document.head || document.getElementsByTagName('head')[0];
+ head.appendChild(style);
+
+ document.addEventListener('click', function(e){
+ // Don't hide if clicking on disabled item
+ if(e.target.classList.contains('dropdown-item-disabled'))
+ return false;
+ // Hide open menus
+ if(!(e.target).classList.contains('menubar-item')){
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
+ el.classList.remove('menubar-item-open');
+ })
+
+ document.querySelectorAll('.dropdown').forEach(el => el.style.display = "none");
+ }
+ });
+
+ // When focus is gone from this window, hide open menus
+ window.addEventListener('blur', function(e){
+ document.querySelectorAll('.dropdown').forEach(function(el) {
+ el.style.display = "none";
+ })
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(el => el.classList.remove('menubar-item-open'));
+ });
+
+ // Returns the siblings of the element
+ const siblings = function (e) {
+ const siblings = [];
+
+ // if no parent, return empty list
+ if(!e.parentNode) {
+ return siblings;
+ }
+
+ // first child of the parent node
+ let sibling = e.parentNode.firstChild;
+
+ // get all other siblings
+ while (sibling) {
+ if (sibling.nodeType === 1 && sibling !== e) {
+ siblings.push(sibling);
+ }
+ sibling = sibling.nextSibling;
+ }
+ return siblings;
+ };
+
+ // Open dropdown
+ document.querySelectorAll('.menubar-item').forEach(el => el.addEventListener('mousedown', function(e){
+ // Hide all other menus
+ document.querySelectorAll('.dropdown').forEach(function(el) {
+ el.style.display = 'none';
+ });
+
+ // Remove open class from all menus, except this menu that was just clicked
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
+ if(el != e.target)
+ el.classList.remove('menubar-item-open');
+ });
+
+ // If menu is already open, close it
+ if(this.classList.contains('menubar-item-open')){
+ document.querySelectorAll('.menubar-item.menubar-item-open').forEach(function(el) {
+ el.classList.remove('menubar-item-open');
+ });
+ }
+
+ // If menu is not open, open it
+ else if(!e.target.classList.contains('dropdown-item')){
+ this.classList.add('menubar-item-open')
+
+ // show all sibling
+ siblings(this).forEach(function(el) {
+ el.style.display = 'block';
+ });
+ }
+
+ }));
+
+ // If a menu is open, and you hover over another menu, open that menu
+ document.querySelectorAll('.--puter-menubar .menubar-item').forEach(el => el.addEventListener('mouseover', function(e){
+ const open_menus = document.querySelectorAll('.menubar-item.menubar-item-open');
+ if(open_menus.length > 0 && open_menus[0] !== e.target){
+ e.target.dispatchEvent(new Event('mousedown'));
+ }
+ }))
+ }
+
+ on(eventName, callback) {
+ super.on(eventName, callback);
+ // If we already received a broadcast for this event, run the callback immediately
+ if (this.#eventNames.includes(eventName) && this.#lastBroadcastValue.has(eventName)) {
+ callback(this.#lastBroadcastValue.get(eventName));
+ }
+ }
+}
+
+export default UI
\ No newline at end of file
diff --git a/packages/puter-js/test/fs.test.js b/packages/puter-js/test/fs.test.js
new file mode 100644
index 000000000..1f2e4bc11
--- /dev/null
+++ b/packages/puter-js/test/fs.test.js
@@ -0,0 +1,619 @@
+naughtyStrings = [
+ "文件.txt", // Chinese characters
+ "файл.txt", // Cyrillic characters
+ "ファイル.txt", // Japanese characters
+ "파일.txt", // Korean characters
+ "ملف.txt", // Arabic characters
+ "फ़ाइल.txt", // Hindi characters
+ "archivo.txt", // Spanish characters
+ "fichier.txt", // French characters
+ "αρχείο.txt", // Greek characters
+ "datei.txt", // German characters
+ "fil.txt", // Swedish characters
+ "קובץ.txt", // Hebrew characters
+ "文件名.txt", // Chinese characters
+ "файлы.txt", // Russian characters
+ "फ़ाइलें.txt", // Hindi characters
+ "📄_emoji.txt", // Emoji
+ "file name with spaces.txt",
+ "file-name-with-dashes.txt",
+ "file_name_with_underscores.txt",
+ "file.name.with.periods.txt",
+ "file,name,with,commas.txt",
+ "file;name;with;semicolons.txt",
+ "file(name)with(parentheses).txt",
+ "file[name]with[brackets].txt",
+ "file{name}with{braces}.txt",
+ "file!name!with!exclamations!.txt",
+ "file@name@with@ats.txt",
+ "file#name#with#hashes#.txt",
+ "file$name$with$dollars$.txt",
+ "file%name%with%percentages%.txt",
+ "file^name^with^carats^.txt",
+ "file&name&with&s&.txt",
+ "file*name*with*asterisks*.txt",
+ "file_name_with_long_name_exceeding_255_characters_abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.txt",
+ "file👍name👍with👍thumbs👍up.txt",
+ "file😂name😂with😂emojis😂.txt",
+ "file🌍name🌍with🌍globe🌍emojis🌍.txt",
+ "file🔥name🔥with🔥fire🔥emoji🔥.txt",
+ "file🎉name🎉with🎉party🎉popper🎉emoji🎉.txt",
+ "file💼name💼with💼briefcase💼emoji💼.txt",
+ "file🍔name🍔with🍔burger🍔emoji🍔.txt",
+ "file🚀name🚀with🚀rocket🚀emoji🚀.txt",
+ "file👽name👽with👽alien👽emoji👽.txt",
+ "file🌈name🌈with🌈rainbow🌈emoji🌈.txt",
+ "file🍆name🍆with🍆eggplant🍆emoji🍆.txt",
+ "file🍑name🍑with🍑peach🍑emoji🍑.txt",
+ "invisible\u200Bname.txt", // Invisible Unicode character (Zero Width Space)
+ "invisible\u200Cname.txt", // Invisible Unicode character (Zero Width Non-Joiner)
+ "invisible\u200Dname.txt", // Invisible Unicode character (Zero Width Joiner)
+ "invisible\uFEFFname.txt", // Invisible Unicode character (Zero Width No-Break Space)
+ "invisible\u180Ename.txt", // Invisible Unicode character (Mongolian Vowel Separator)
+ "hash#tag.txt",
+ "percent%20encoded.txt",
+ "plus+sign.txt",
+ "ampersand&symbol.txt",
+ "at@symbol.txt",
+ "parentheses(1).txt",
+ "brackets[1].txt",
+ "curly{braces}.txt",
+ "angle.txt",
+ "exclamation!point.txt",
+ "question?mark.txt",
+ "colon:separated.txt",
+ "semicolon;separated.txt",
+ "single'quote.txt",
+ "double\"quote.txt",
+ "backtick`char.txt",
+ "tilde~sign.txt",
+ "underscore_character.txt",
+ "hyphen-character.txt",
+ "equal=sign.txt",
+ "plus+sign.txt",
+ "asterisk*char.txt",
+ "caret^char.txt",
+ "percent%sign.txt",
+ "dollar$sign.txt",
+ "pound#sign.txt",
+ "at@sign.txt",
+ "exclamation!mark.txt",
+ "question?mark.txt",
+ "backslash\\char.txt",
+ "pipe|char.txt",
+ "colon:char.txt",
+ "semicolon;char.txt",
+ "quote'char.txt",
+ "double\"quote.txt",
+ "backtick`char.txt",
+ "braces{char}.txt",
+ "brackets[char].txt",
+ "parentheses(char).txt",
+ "angle.txt",
+ "ellipsis….txt",
+ "accentué.txt",
+ "ümlaut.txt",
+ "tildeñ.txt",
+ "çedilla.txt",
+ "špecial.txt",
+ "russianЯ.txt",
+ "chinese中文.txt",
+ "arabicعربى.txt",
+ "hebrewעברית.txt",
+ "japanese日本語.txt",
+ "korean한국어.txt",
+ "vietnameseTiếng Việt.txt",
+
+]
+
+window.fsTests = [
+ testFSWrite = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.write(randName, 'testValue');
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWrite passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ throw("testFSWrite failed to delete file:", error);
+ }
+ } catch (error) {
+ console.log(error);
+ throw("testFSWrite failed:", error);
+ }
+ },
+ testFSRead = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await (await puter.fs.read(randName)).text();
+ assert(result === 'testValue', "Failed to read from file");
+ pass("testFSRead passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSRead failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSRead failed:", error);
+ }
+ },
+ testFSWriteWithoutData = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.write(randName);
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteWithoutData passed");
+ if(randName !== result.name) {
+ fail(`testFSWriteWithoutData failed: Names do not match ${randName} ${result.name}`);
+ }
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteWithoutData failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSWriteWithoutData failed:", error);
+ }
+ },
+ testFSReadWithoutData = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName);
+ const result = await (await puter.fs.read(randName)).text();
+ assert(result === '', "Failed to read from file");
+ pass("testFSReadWithoutData passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSReadWithoutData failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSReadWithoutData failed:", error);
+ }
+ },
+ testFSWriteToExistingFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.write(randName, 'updatedValue');
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteToExistingFile passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteToExistingFile failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSWriteToExistingFile failed:", error);
+ }
+ },
+ testFSWriteToExistingFileWithoutOverwriteAndDedupe = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.write(randName, 'updatedValue', { overwrite: false, dedupeName: false });
+ assert(!result.uid, "Failed to write to file");
+ fail("testFSWriteToExistingFileWithoutOverwriteAndDedupe failed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteToExistingFileWithoutOverwriteAndDedupe failed to delete file:", error);
+ }
+ } catch (error) {
+ pass("testFSWriteToExistingFileWithoutOverwriteAndDedupe passed");
+ }
+
+ },
+ testFSWriteToExistingFileWithoutOverwriteButWithDedupe = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.write(randName, 'updatedValue', { overwrite: false, dedupeName: true });
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteToExistingFileWithoutOverwriteButWithDedupe passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteToExistingFileWithoutOverwriteButWithDedupe failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSWriteToExistingFileWithoutOverwriteButWithDedupe failed:", error);
+ }
+ },
+ testFSWriteToExistingFileWithOverwriteButWithoutDedupe = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.write(randName, 'updatedValue', { overwrite: true, dedupeName: false });
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteToExistingFileWithOverwriteButWithoutDedupe passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteToExistingFileWithOverwriteButWithoutDedupe failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSWriteToExistingFileWithOverwriteButWithoutDedupe failed:", error);
+ }
+ },
+ // create a directory
+ testFSCreateDir = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.mkdir(randName);
+ assert(result.uid, "Failed to create directory");
+ pass("testFSCreateDir passed");
+ } catch (error) {
+ fail("testFSCreateDir failed:", error);
+ }
+ },
+
+ // write a number of files to a directory and list them
+ testFSReadDir = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.mkdir(randName);
+ await puter.fs.write(randName + '/file1', 'testValue');
+ await puter.fs.write(randName + '/file2', 'testValue');
+ await puter.fs.write(randName + '/file3', 'testValue');
+ const result = await puter.fs.readdir(randName);
+ assert(result.length === 3, "Failed to read directory");
+ pass("testFSReadDir passed");
+ } catch (error) {
+ fail("testFSReadDir failed:", error);
+ }
+ },
+
+ // create a file then delete it
+ testFSDelete = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.delete(randName);
+ assert(!result.uid, "Failed to delete file");
+ pass("testFSDelete passed");
+ } catch (error) {
+ fail("testFSDelete failed:", error);
+ }
+ },
+
+ // create a directory, write a number of files to it, then delete it
+ testFSDeleteDir = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.mkdir(randName);
+ await puter.fs.write(randName + '/file1', 'testValue');
+ await puter.fs.write(randName + '/file2', 'testValue');
+ await puter.fs.write(randName + '/file3', 'testValue');
+ const result = await puter.fs.delete(randName);
+ assert(!result.uid, "Failed to delete directory");
+ pass("testFSDeleteDir passed");
+ } catch (error) {
+ fail("testFSDeleteDir failed:", error);
+ }
+ },
+
+ // attempt to delete a non-existent file
+ testFSDeleteNonExistentFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.delete(randName);
+ assert(!result.uid, "Failed to delete non-existent file");
+ pass("testFSDeleteNonExistentFile passed");
+ } catch (error) {
+ if(error.code !== "subject_does_not_exist")
+ fail("testFSDeleteNonExistentFile failed:", error);
+ else
+ pass("testFSDeleteNonExistentFile passed");
+ }
+ },
+
+ // attempt to access a non-existent file
+ testFSReadNonExistentFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.read(randName);
+ fail("testFSReadNonExistentFile failed");
+ } catch (error) {
+ if(error.code !== "subject_does_not_exist")
+ fail("testFSReadNonExistentFile failed:", error);
+ else
+ pass("testFSReadNonExistentFile passed");
+ }
+ },
+
+ testFSWriteWithSpecialCharacters = async ()=>{
+ let randName
+ try {
+ randName = 'testFileWithSpecialCharacte rs!@#$%^&*()_+{}|:"<>?`~'
+ const result = await puter.fs.write(randName, 'testValue', { specialCharacters: true });
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteWithSpecialCharacters passed");
+ } catch (error) {
+ fail("testFSWriteWithSpecialCharacters failed:", error);
+ }
+
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSWriteWithSpecialCharacters failed to delete file:", error);
+ }
+ },
+
+ testFSReadWithSpecialCharacters = async ()=>{
+ try {
+ let randName = 'testFileWithSpecialCharacte rs!@#$%^&*()_+{}|:"<>?`~'
+ await puter.fs.write(randName, 'testValue');
+ const result = await (await puter.fs.read(randName)).text();
+ assert(result === 'testValue', "Failed to read from file");
+ pass("testFSReadWithSpecialCharacters passed");
+ } catch (error) {
+ fail("testFSReadWithSpecialCharacters failed:", error);
+ }
+ },
+
+ testFSWriteLargeFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ const result = await puter.fs.write(randName, 'testValue'.repeat(100000));
+ assert(result.uid, "Failed to write to file");
+ pass("testFSWriteLargeFile passed");
+ } catch (error) {
+ fail("testFSWriteLargeFile failed:", error);
+ }
+ },
+
+ testFSReadLargeFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue'.repeat(100000));
+ const result = await (await puter.fs.read(randName)).text();
+ assert(result === 'testValue'.repeat(100000), "Failed to read from file");
+ pass("testFSReadLargeFile passed");
+ } catch (error) {
+ fail("testFSReadLargeFile failed:", error);
+ }
+ },
+
+ testFSRenameFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ let randName2 = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ const result = await puter.fs.rename(randName, randName2);
+ assert(result.name, "Failed to rename file");
+ pass("testFSRenameFile passed");
+ // check that the old file is gone
+ try {
+ await puter.fs.read(randName);
+ fail("testFSRenameFile failed to delete old file");
+ } catch (error) {
+ if(error.code !== "subject_does_not_exist")
+ fail("testFSRenameFile failed to delete old file:", error);
+ else
+ pass("testFSRenameFile passed");
+ }
+ } catch (error) {
+ fail("testFSRenameFile failed:", error);
+ }
+ },
+
+ testFSMoveFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ let randName2 = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ await puter.fs.mkdir(randName2);
+ let result = await puter.fs.move(randName, randName2);
+ assert(result.moved, "Failed to move file");
+ // check that the old file is gone
+ try {
+ await puter.fs.read(randName);
+ fail("testFSMoveFile failed to delete old file");
+ } catch (error) {
+ if(error.code !== "subject_does_not_exist")
+ fail("testFSMoveFile failed to delete old file:", error);
+ else
+ pass("testFSMoveFile passed");
+ }
+ } catch (error) {
+ fail("testFSMoveFile failed:", error);
+ }
+ },
+
+ testFSCopyFile = async ()=>{
+ try {
+ let randName = puter.randName();
+ let randName2 = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ await puter.fs.mkdir(randName2);
+ let result = await puter.fs.copy(randName, randName2);
+ assert(Array.isArray(result) && result[0].uid, "Failed to copy file");
+ // check that the old file is still there
+ try {
+ await puter.fs.read(randName);
+ pass("testFSCopyFile passed");
+ } catch (error) {
+ fail("testFSCopyFile failed to keep old file:", error);
+ }
+ } catch (error) {
+ fail("testFSCopyFile failed:", error);
+ }
+ },
+
+ // copy a file to a directory with newName option
+ testFSCopyFileWithNewName = async ()=>{
+ try {
+ let randName = puter.randName();
+ let randName2 = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ await puter.fs.mkdir(randName2);
+ let result = await puter.fs.copy(randName, randName2, { newName: 'newName' });
+ assert(Array.isArray(result) && result[0].uid, "Failed to copy file");
+ // check file name
+ assert(result[0].name === 'newName', "Failed to copy file with new name");
+ // check that the old file is still there
+ try {
+ await puter.fs.read(randName);
+ pass("testFSCopyFileWithNewName passed");
+ } catch (error) {
+ fail("testFSCopyFileWithNewName failed to keep old file:", error);
+ }
+ } catch (error) {
+ fail("testFSCopyFileWithNewName failed:", error);
+ }
+ },
+
+ testFSStat = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.write(randName, 'testValue');
+ let result = await puter.fs.stat(randName);
+ assert(result.uid, "Failed to stat file");
+ pass("testFSStat passed");
+ } catch (error) {
+ fail("testFSStat failed:", error);
+ }
+ },
+
+ testFSStatDir = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.mkdir(randName);
+ let result = await puter.fs.stat(randName);
+ assert(result.uid, "Failed to stat directory");
+ pass("testFSStatDir passed");
+ } catch (error) {
+ fail("testFSStatDir failed:", error);
+ }
+ },
+
+ testFSStatNonExistent = async ()=>{
+ try {
+ let randName = puter.randName();
+ let result = await puter.fs.stat(randName);
+ fail("testFSStatNonExistent failed");
+ } catch (error) {
+ if(error.code !== "subject_does_not_exist")
+ fail("testFSStatNonExistent failed:", error);
+ else
+ pass("testFSStatNonExistent passed");
+ }
+ },
+
+ // create a directory, write a number of files to it, then delete it
+ testFSDeleteDirWithFiles = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.mkdir(randName);
+ await puter.fs.write(randName + '/file1', 'testValue');
+ await puter.fs.write(randName + '/file2', 'testValue');
+ await puter.fs.write(randName + '/file3', 'testValue');
+ const result = await puter.fs.delete(randName, { recursive: true });
+ assert(!result.uid, "Failed to delete directory");
+ pass("testFSDeleteDirWithFiles passed");
+ } catch (error) {
+ fail("testFSDeleteDirWithFiles failed:", error);
+ }
+ },
+ // check if stat on a directory returns name, path, is_dir
+ testFSStatDirReturnsAttrs = async ()=>{
+ try {
+ let randName = puter.randName();
+ await puter.fs.mkdir(randName);
+ let result = await puter.fs.stat(randName);
+ assert(result.name && typeof result.name === 'string', "Failed to stat directory (name)");
+ assert(result.path && typeof result.path === 'string', "Failed to stat directory (path)");
+ assert(result.immutable !== undefined, "Failed to stat directory (immutable)");
+ assert(result.metadata !== undefined, "Failed to stat directory (metadata)");
+ assert(result.modified !== undefined, "Failed to stat directory (modified)");
+ assert(result.created !== undefined, "Failed to stat directory (created)");
+ assert(result.accessed !== undefined, "Failed to stat directory (accessed)");
+ assert(result.size !== undefined, "Failed to stat directory (size)");
+ assert(result.layout !== undefined, "Failed to stat directory (layout)");
+ assert(result.owner !== undefined && typeof result.owner === 'object', "Failed to stat directory (owner)");
+ assert(result.dirname !== undefined && typeof result.dirname === 'string', "Failed to stat directory (dirname)");
+ assert(result.parent_id !== undefined && typeof result.parent_id === 'string', "Failed to stat directory (parent_id)");
+ // todo this will fail for now until is_dir is turned into boolean
+ assert(result.is_dir !== undefined && typeof result.is_dir === 'boolean' && result.is_dir === true, "Failed to stat directory (is_dir)");
+ assert(result.is_empty !== undefined && typeof result.is_empty === 'boolean', "Failed to stat directory (is_empty)");
+ pass("testFSStatDirReturnsAttrs passed");
+ } catch (error) {
+ throw("testFSStatDirReturnsAttrs failed:", error);
+ }
+ },
+
+ // test read() with the object returned by write()
+ testFSReadWithWriteResult = async ()=>{
+ try {
+ let randName = puter.randName();
+ let writeResult = await puter.fs.write(randName, 'testValue');
+ let result = await (await puter.fs.read(writeResult)).text();
+ assert(result === 'testValue', "Failed to read from file");
+ pass("testFSReadWithWriteResult passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSReadWithWriteResult failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSReadWithWriteResult failed:", error);
+ }
+ },
+
+ // test stat() with the object returned by write()
+ testFSStatWithWriteResult = async ()=>{
+ try {
+ let randName = puter.randName();
+ let writeResult = await puter.fs.write(randName, 'testValue');
+ let result = await puter.fs.stat(writeResult);
+ assert(result.uid, "Failed to stat file");
+ pass("testFSStatWithWriteResult passed");
+ // delete the file
+ try {
+ await puter.fs.delete(randName);
+ } catch (error) {
+ fail("testFSStatWithWriteResult failed to delete file:", error);
+ }
+ } catch (error) {
+ fail("testFSStatWithWriteResult failed:", error);
+ }
+ },
+
+ // test creating files with names from naughtyStrings
+ testFSWriteWithNaughtyStrings = async ()=>{
+ try {
+ let randName = puter.randName();
+ for(let i = 0; i < naughtyStrings.length; i++) {
+ let filename = randName + naughtyStrings[i];
+ console.log(filename);
+ let result = await puter.fs.write(filename, 'testValue');
+ assert(result.uid, "Failed to write to file");
+ // check name
+ assert(result.name === filename, "Failed to write to file with naughty name: " + filename);
+ // delete the file
+ try {
+ await puter.fs.delete(filename);
+ } catch (error) {
+ fail("testFSWriteWithNaughtyStrings failed to delete file: " + filename, error);
+ }
+ }
+ pass("testFSWriteWithNaughtyStrings passed");
+ } catch (error) {
+ console.log(error);
+ fail("testFSWriteWithNaughtyStrings failed:", error);
+ }
+ },
+];
\ No newline at end of file
diff --git a/packages/puter-js/test/kv.test.js b/packages/puter-js/test/kv.test.js
new file mode 100644
index 000000000..5dad4b4a4
--- /dev/null
+++ b/packages/puter-js/test/kv.test.js
@@ -0,0 +1,417 @@
+window.kvTests = [
+ testSetKeyWithValue = async function() {
+ try {
+ const result = await puter.kv.set('testKey', 'testValue');
+ assert(result === true, "Failed to set key with value");
+ pass("testSetKeyWithValue passed");
+ } catch (error) {
+ fail("testSetKeyWithValue failed:", error);
+ }
+ },
+
+ testUpdateKey = async function() {
+ try {
+ await puter.kv.set('updateKey', 'initialValue');
+ const result = await puter.kv.set('updateKey', 'updatedValue');
+ assert(result === true, "Failed to update existing key");
+ pass("testUpdateKey passed");
+ } catch (error) {
+ fail("testUpdateKey failed:", error);
+ }
+ },
+
+ testKeySizeLimit = async function() {
+ try {
+ const largeKey = 'a'.repeat(1025); // 1 KB + 1 byte
+ await puter.kv.set(largeKey, 'value');
+ fail("testKeySizeLimit failed: No error thrown for large key");
+ } catch (error) {
+ pass("testKeySizeLimit passed:", error.message);
+ }
+ },
+
+ testInvalidParameters = async function() {
+ try {
+ await puter.kv.set(undefined, 'value');
+ fail("testInvalidParameters failed: No error thrown for undefined key");
+ } catch (error) {
+ pass("testInvalidParameters passed:", error.message);
+ }
+ },
+
+ // testEmptyKey should fail
+ testEmptyKey = async function() {
+ try {
+ await puter.kv.set('', 'value');
+ fail("testEmptyKey failed: No error thrown for empty key");
+ } catch (error) {
+ pass("testEmptyKey passed:", error.message);
+ }
+ },
+
+
+ testSetNullValue = async function() {
+ try {
+ const result = await puter.kv.set('nullValueKey', null);
+ assert(result === true, "Failed to set null value");
+ pass("testSetNullValue passed");
+ } catch (error) {
+ fail("testSetNullValue failed:", error);
+ }
+ },
+
+ testSetObjectValue = async function() {
+ try {
+ const result = await puter.kv.set('objectKey', { a: 1 });
+ assert(result === true, "Failed to set object as value");
+ pass("testSetObjectValue passed");
+ } catch (error) {
+ fail("testSetObjectValue failed:", error);
+ }
+ },
+
+ testSetKeyWithSpecialCharacters = async function() {
+ try {
+ const result = await puter.kv.set('special@Key#', 'value');
+ assert(result === true, "Failed to set key with special characters");
+ pass("testSetKeyWithSpecialCharacters passed");
+ } catch (error) {
+ fail("testSetKeyWithSpecialCharacters failed:", error);
+ }
+ },
+
+ testSetLargeValue = async function() {
+ try {
+ const largeValue = 'a'.repeat(10000); // 10 KB
+ const result = await puter.kv.set('largeValueKey', largeValue);
+ assert(result === true, "Failed to set large value");
+ pass("testSetLargeValue passed");
+ } catch (error) {
+ fail("testSetLargeValue failed:", error);
+ }
+ },
+
+ testSetBooleanValue = async function() {
+ try {
+ const result = await puter.kv.set('booleanKey', true);
+ assert(result === true, "Failed to set boolean value");
+ pass("testSetBooleanValue passed");
+ } catch (error) {
+ fail("testSetBooleanValue failed:", error);
+ }
+ },
+
+ testSetNumericKey = async function() {
+ try {
+ const result = await puter.kv.set(123, 'value');
+ assert(result === true, "Failed to set numeric key");
+ pass("testSetNumericKey passed");
+ } catch (error) {
+ fail("testSetNumericKey failed:", error);
+ }
+ },
+
+ testSetConcurrentKeys = async function() {
+ try {
+ const promises = [puter.kv.set('key1', 'value1'), puter.kv.set('key2', 'value2')];
+ const results = await Promise.all(promises);
+ assert(results.every(result => result === true), "Failed to set concurrent keys");
+ pass("testSetConcurrentKeys passed");
+ } catch (error) {
+ fail("testSetConcurrentKeys failed:", error);
+ }
+ },
+
+ testSetValueAndRetrieve = async function() {
+ try {
+ await puter.kv.set('retrieveKey', 'testValue');
+ const value = await puter.kv.get('retrieveKey');
+ assert(value === 'testValue', "Failed to retrieve correct value");
+ pass("testSetValueAndRetrieve passed");
+ } catch (error) {
+ fail("testSetValueAndRetrieve failed:", error);
+ }
+ },
+
+ testUpdateValueAndRetrieve = async function() {
+ try {
+ await puter.kv.set('updateKey', 'initialValue');
+ await puter.kv.set('updateKey', 'updatedValue');
+ const value = await puter.kv.get('updateKey');
+ assert(value === 'updatedValue', "Failed to retrieve updated value");
+ pass("testUpdateValueAndRetrieve passed");
+ } catch (error) {
+ fail("testUpdateValueAndRetrieve failed:", error);
+ }
+ },
+
+ testSetNumericValueAndRetrieve = async function() {
+ try {
+ await puter.kv.set('numericKey', 123);
+ const value = await puter.kv.get('numericKey');
+ assert(value === 123, "Failed to retrieve numeric value");
+ pass("testSetNumericValueAndRetrieve passed");
+ } catch (error) {
+ fail("testSetNumericValueAndRetrieve failed:", error);
+ }
+ },
+
+ testSetBooleanValueAndRetrieve = async function() {
+ try {
+ await puter.kv.set('booleanKey', true);
+ const value = await puter.kv.get('booleanKey');
+ assert(value === true, "Failed to retrieve boolean value");
+ pass("testSetBooleanValueAndRetrieve passed");
+ } catch (error) {
+ fail("testSetBooleanValueAndRetrieve failed:", error);
+ }
+ },
+
+
+ testSetAndDeleteKey = async function() {
+ try {
+ await puter.kv.set('deleteKey', 'value');
+ const result = await puter.kv.del('deleteKey');
+ assert(result === true, "Failed to delete key");
+ pass("testSetAndDeleteKey passed");
+ } catch (error) {
+ fail("testSetAndDeleteKey failed:", error);
+ }
+ },
+
+ // if key does not exist, get() should return null
+ testGetNonexistentKey = async function() {
+ try {
+ const value = await puter.kv.get('nonexistentKey_102mk');
+ assert(value === null, "Failed to return `null` for nonexistent key");
+ pass("testGetNonexistentKey passed");
+ } catch (error) {
+ fail("testGetNonexistentKey failed:", error);
+ }
+ },
+
+ // string key and object value
+ testSetObjectValue = async function() {
+ try {
+ const result = await puter.kv.set('objectKey', { a: 1 });
+ assert(result === true, "Failed to set object as value");
+ const value = await puter.kv.get('objectKey');
+ assert(value.a === 1, "Failed to retrieve object value");
+ pass("testSetObjectValue passed");
+ } catch (error) {
+ fail("testSetObjectValue failed:", error);
+ }
+ },
+
+ // string key and array value
+ testSetArrayValue = async function() {
+ try {
+ const result = await puter.kv.set('arrayKey', [1, 2, 3]);
+ assert(result === true, "Failed to set array as value");
+ const value = await puter.kv.get('arrayKey');
+ assert(value[0] === 1, "Failed to retrieve array value");
+ pass("testSetArrayValue passed");
+ } catch (error) {
+ fail("testSetArrayValue failed:", error);
+ }
+ },
+
+ testSetKeyWithSpecialCharactersAndRetrieve = async function() {
+ try {
+ await puter.kv.set('special@Key#', 'value');
+ const value = await puter.kv.get('special@Key#');
+ assert(value === 'value', "Failed to retrieve value for key with special characters");
+ pass("testSetKeyWithSpecialCharactersAndRetrieve passed");
+ } catch (error) {
+ fail("testSetKeyWithSpecialCharactersAndRetrieve failed:", error);
+ }
+ },
+
+ testConcurrentSetOperations = async function() {
+ try {
+ const promises = [puter.kv.set('key1', 'value1'), puter.kv.set('key2', 'value2')];
+ const results = await Promise.all(promises);
+ assert(results.every(result => result === true), "Failed to set concurrent keys");
+ pass("testConcurrentSetOperations passed");
+ } catch (error) {
+ fail("testConcurrentSetOperations failed:", error);
+ }
+ },
+
+ //test flush: create a bunch of keys, flush, then check if they exist
+ testFlush = async function() {
+ try {
+ const keys = [];
+ for(let i = 0; i < 10; i++){
+ keys.push('key' + i);
+ }
+ await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
+ await puter.kv.flush();
+ const results = await Promise.all(keys.map(key => puter.kv.get(key)));
+ assert(results.every(result => result === null), "Failed to flush keys");
+ pass("testFlush passed");
+ } catch (error) {
+ fail("testFlush failed:", error);
+ }
+ },
+
+ // incr
+ testIncr = async function() {
+ try {
+ const result = await puter.kv.incr('incrKey');
+ assert(result === 1, "Failed to increment key");
+ pass("testIncr passed");
+ } catch (error) {
+ fail("testIncr failed:", error);
+ }
+ },
+
+ // decr
+ testDecr = async function() {
+ try {
+ const result = await puter.kv.decr('decrKey');
+ assert(result === -1, "Failed to decrement key");
+ pass("testDecr passed");
+ } catch (error) {
+ fail("testDecr failed:", error);
+ }
+ },
+
+ // incr existing key
+ testIncrExistingKey = async function() {
+ try {
+ await puter.kv.set('incrKey', 1);
+ const result = await puter.kv.incr('incrKey');
+ assert(result === 2, "Failed to increment existing key");
+ pass("testIncrExistingKey passed");
+ } catch (error) {
+ fail("testIncrExistingKey failed:", error);
+ }
+ },
+
+ // decr existing key
+ testIncrExistingKey = async function() {
+ try {
+ await puter.kv.set('decrKey', 2);
+ const result = await puter.kv.decr('decrKey');
+ assert(result === 1, "Failed to decrement existing key");
+ pass("testDecrExistingKey passed");
+ } catch (error) {
+ fail("testDecrExistingKey failed:", error);
+ }
+ },
+
+ // incr by amount
+ testIncrByAmount = async function() {
+ try {
+ await puter.kv.set('incrKey', 1);
+ const result = await puter.kv.incr('incrKey', 5);
+ assert(result === 6, "Failed to increment key by amount");
+ pass("testIncrByAmount passed");
+ } catch (error) {
+ fail("testIncrByAmount failed:", error);
+ }
+ },
+
+ // decr by amount
+ testDecrByAmount = async function() {
+ try {
+ await puter.kv.set('decrKey', 10);
+ const result = await puter.kv.decr('decrKey', 5);
+ assert(result === 5, "Failed to decrement key by amount");
+ pass("testDecrByAmount passed");
+ } catch (error) {
+ fail("testDecrByAmount failed:", error);
+ }
+ },
+
+ // incr by amount existing key
+ testIncrByAmountExistingKey = async function() {
+ try {
+ await puter.kv.set('incrKey', 1);
+ const result = await puter.kv.incr('incrKey', 5);
+ assert(result === 6, "Failed to increment existing key by amount");
+ pass("testIncrByAmountExistingKey passed");
+ } catch (error) {
+ fail("testIncrByAmountExistingKey failed:", error);
+ }
+ },
+
+ // decr by amount existing key
+ testDecrByAmountExistingKey= async function() {
+ try {
+ await puter.kv.set('decrKey', 10);
+ const result = await puter.kv.decr('decrKey', 5);
+ assert(result === 5, "Failed to decrement existing key by amount");
+ pass("testDecrByAmountExistingKey passed");
+ } catch (error) {
+ fail("testDecrByAmountExistingKey failed:", error);
+ }
+ },
+
+ // incr by negative amount
+ testIncrByNegativeAmount = async function() {
+ try {
+ await puter.kv.set('incrKey', 1);
+ const result = await puter.kv.incr('incrKey', -5);
+ assert(result === -4, "Failed to increment key by negative amount");
+ pass("testIncrByNegativeAmount passed");
+ } catch (error) {
+ fail("testIncrByNegativeAmount failed:", error);
+ }
+ },
+
+ // decr by negative amount
+ testDecrByNegativeAmount = async function() {
+ try {
+ await puter.kv.set('decrKey', 10);
+ const result = await puter.kv.decr('decrKey', -5);
+ assert(result === 15, "Failed to decrement key by negative amount");
+ pass("testDecrByNegativeAmount passed");
+ } catch (error) {
+ fail("testDecrByNegativeAmount failed:", error);
+ }
+ },
+
+ // list keys
+ testListKeys = async function() {
+ try {
+ const keys = [];
+ // flush first
+ await puter.kv.flush();
+ // create 10 keys
+ for(let i = 0; i < 10; i++){
+ keys.push('key' + i);
+ }
+ // set all keys
+ await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
+ // list keys
+ const result = await puter.kv.list();
+ assert(result.length === 10, "Failed to list keys");
+ pass("testListKeys passed");
+ } catch (error) {
+ fail("testListKeys failed:", error);
+ }
+ },
+
+ // list keys using glob
+ testListKeysGlob = async function() {
+ try {
+ const keys = [];
+ // flush first
+ await puter.kv.flush();
+ // create 10 keys
+ for(let i = 0; i < 10; i++){
+ keys.push('key' + i);
+ }
+ // set all keys
+ await Promise.all(keys.map(key => puter.kv.set(key, 'value')));
+ // list keys
+ const result = await puter.kv.list('k*');
+ assert(result.length === 10, "Failed to list keys using glob");
+ pass("testListKeysGlob passed");
+ } catch (error) {
+ fail("testListKeysGlob failed:", error);
+ }
+ },
+]
diff --git a/packages/puter-js/test/run.html b/packages/puter-js/test/run.html
new file mode 100644
index 000000000..3b01c34e2
--- /dev/null
+++ b/packages/puter-js/test/run.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/run-selfhosted.js b/run-selfhosted.js
index 3c63230c6..c2ddc1137 100644
--- a/run-selfhosted.js
+++ b/run-selfhosted.js
@@ -93,10 +93,18 @@ const early_init_errors = [
}
];
+const newstuff = {
+ // Nullish coalescing operator
+ nco: (...a) => a.reduce((acc, val) => acc == undefined ? val : acc),
+ // Optional chaining
+ oc: (obj, ...keys) => keys.reduce((acc, key) => acc ? acc[key] : undefined, obj),
+ oc_call: (maybe_fn, ...args) => maybe_fn ? maybe_fn(...args) : undefined,
+};1
+
const _print_error_help = (error_help) => {
const lines = [];
- lines.push(error_help.title ?? error_help.text);
- for ( const note of (error_help.notes ?? []) ) {
+ lines.push(nco(error_help.title, error_help.text));
+ for ( const note of (nco(error_help.notes, [])) ) {
lines.push(`📝 ${note}`)
}
if ( error_help.suggestions ) {
@@ -120,7 +128,7 @@ const _print_error_help = (error_help) => {
await main();
} catch (e) {
for ( const error_help of early_init_errors ) {
- if ( e?.message?.includes(error_help.text) ) {
+ if ( oc_call(oc(e, message, includes), error_help.text) ) {
_print_error_help(error_help);
break;
}
diff --git a/src/IPC.js b/src/IPC.js
index de771e447..f8aad6602 100644
--- a/src/IPC.js
+++ b/src/IPC.js
@@ -74,11 +74,15 @@ window.addEventListener('message', async (event) => {
return;
}
- const iframe_for_app_instance = (instanceID) => {
- return $(`.window[data-element_uuid="${instanceID}"]`).find('.window-app-iframe').get(0)
+ const window_for_app_instance = (instance_id) => {
+ return $(`.window[data-element_uuid="${instance_id}"]`).get(0);
};
- const $el_parent_window = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`);
+ const iframe_for_app_instance = (instance_id) => {
+ return $(window_for_app_instance(instance_id)).find('.window-app-iframe').get(0);
+ };
+
+ const $el_parent_window = $(window_for_app_instance(event.data.appInstanceID));
const parent_window_id = $el_parent_window.attr('data-id');
const $el_parent_disable_mask = $el_parent_window.find('.window-disable-mask');
const target_iframe = iframe_for_app_instance(event.data.appInstanceID);
@@ -105,6 +109,9 @@ window.addEventListener('message', async (event) => {
}, '*');
delete window.child_launch_callbacks[event.data.appInstanceID];
}
+
+ // Send any saved broadcasts to the new app
+ globalThis.services.get('broadcast').sendSavedBroadcastsTo(event.data.appInstanceID);
}
//-------------------------------------------------
// windowFocused
@@ -354,7 +361,7 @@ window.addEventListener('message', async (event) => {
// setWindowTitle
//--------------------------------------------------------
else if(event.data.msg === 'setWindowTitle' && event.data.new_title !== undefined){
- const el_window = $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).get(0);
+ const el_window = window_for_app_instance(event.data.appInstanceID);
// set window title
$(el_window).find(`.window-head-title`).html(html_encode(event.data.new_title));
// send confirmation to requester window
@@ -1096,11 +1103,48 @@ window.addEventListener('message', async (event) => {
contents,
}, targetAppOrigin);
}
+ //--------------------------------------------------------
+ // closeApp
+ //--------------------------------------------------------
+ else if (event.data.msg === 'closeApp') {
+ const { appInstanceID, targetAppInstanceID } = event.data;
+
+ const target_window = window_for_app_instance(targetAppInstanceID);
+ if (!target_window) {
+ console.warn(`Failed to close non-existent app ${targetAppInstanceID}`);
+ return;
+ }
+
+ // Check permissions
+ const allowed = await (async () => {
+ // Parents can close their children
+ if (target_window.dataset['parent_instance_id'] === appInstanceID) {
+ console.log(`⚠️ Allowing app ${appInstanceID} to close child app ${targetAppInstanceID}`);
+ return true;
+ }
+
+ // God-mode apps can close anything
+ const app_info = await get_apps(app_name);
+ if (app_info.godmode === 1) {
+ console.log(`⚠️ Allowing GODMODE app ${appInstanceID} to close app ${targetAppInstanceID}`);
+ return true;
+ }
+
+ // TODO: What other situations should we allow?
+ return false;
+ })();
+
+ if (allowed) {
+ $(target_window).close();
+ } else {
+ console.warn(`⚠️ App ${appInstanceID} is not permitted to close app ${targetAppInstanceID}`);
+ }
+ }
//--------------------------------------------------------
// exit
//--------------------------------------------------------
else if(event.data.msg === 'exit'){
- $(`.window[data-element_uuid="${event.data.appInstanceID}"]`).close({bypass_iframe_messaging: true});
+ $(window_for_app_instance(event.data.appInstanceID)).close({bypass_iframe_messaging: true});
}
});
\ No newline at end of file
diff --git a/src/UI/Settings/UIWindowSettings.js b/src/UI/Settings/UIWindowSettings.js
index 2dc15fa1c..6806da352 100644
--- a/src/UI/Settings/UIWindowSettings.js
+++ b/src/UI/Settings/UIWindowSettings.js
@@ -26,6 +26,7 @@ import changeLanguage from "../../i18n/i18nChangeLanguage.js"
import UIWindowConfirmUserDeletion from './UIWindowConfirmUserDeletion.js';
import UITabAbout from './UITabAbout.js';
import UIWindowThemeDialog from '../UIWindowThemeDialog.js';
+import UIWindowManageSessions from '../UIWindowManageSessions.js';
async function UIWindowSettings(options){
return new Promise(async (resolve) => {
@@ -60,10 +61,12 @@ async function UIWindowSettings(options){
used of
+ ( ${i18n('storage_puter_used')})
`
h += ``;
@@ -111,6 +114,14 @@ async function UIWindowSettings(options){
h += ``;
h += ``;
+ // session manager
+ h += ``;
+ h += `
${i18n('sessions')}`;
+ h += `
`;
+ h += ``;
+ h += `
`;
+ h += `
`;
+
h += ``;
// Personalization
@@ -269,10 +280,26 @@ async function UIWindowSettings(options){
let usage_percentage = (res.used / res.capacity * 100).toFixed(0);
usage_percentage = usage_percentage > 100 ? 100 : usage_percentage;
- $('#storage-used').html(byte_format(res.used));
+ let general_used = res.used;
+
+ let host_usage_percentage = 0;
+ if ( res.host_used ) {
+ $('#storage-puter-used').html(byte_format(res.used));
+ $('#storage-puter-used-w').show();
+
+ general_used = res.host_used;
+ host_usage_percentage = ((res.host_used - res.used) / res.capacity * 100).toFixed(0);
+ }
+
+ $('#storage-used').html(byte_format(general_used));
$('#storage-capacity').html(byte_format(res.capacity));
- $('#storage-used-percent').html(usage_percentage + '%');
+ $('#storage-used-percent').html(
+ usage_percentage + '%' +
+ (host_usage_percentage > 0
+ ? ' / ' + host_usage_percentage + '%' : '')
+ );
$('#storage-bar').css('width', usage_percentage + '%');
+ $('#storage-bar-host').css('width', host_usage_percentage + '%');
if (usage_percentage >= 100) {
$('#storage-bar').css({
'border-top-right-radius': '3px',
@@ -332,6 +359,10 @@ async function UIWindowSettings(options){
UIWindowThemeDialog();
})
+ $(el_window).find('.manage-sessions').on('click', function (e) {
+ UIWindowManageSessions();
+ })
+
$(el_window).on('click', '.settings-sidebar-item', function(){
const $this = $(this);
const settings = $this.attr('data-settings');
diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js
index a922e4033..3134c23e7 100644
--- a/src/UI/UIWindow.js
+++ b/src/UI/UIWindow.js
@@ -185,6 +185,7 @@ async function UIWindow(options) {
data-uid ="${options.uid}"
data-element_uuid="${options.element_uuid}"
data-parent_uuid="${options.parent_uuid}"
+ ${options.parent_instance_id ? `data-parent_instance_id="${options.parent_instance_id}"` : ''}
data-id ="${win_id}"
data-iframe_msg_uid ="${options.iframe_msg_uid}"
data-is_dir ="${options.is_dir}"
@@ -2745,8 +2746,9 @@ $.fn.close = async function(options) {
options = options || {};
$(this).each(async function() {
const el_iframe = $(this).find('.window-app-iframe');
+ const app_uses_sdk = el_iframe.length > 0 && el_iframe.attr('data-appUsesSDK') === 'true';
// tell child app that this window is about to close, get its response
- if(el_iframe.length > 0 && el_iframe.attr('data-appUsesSDK') === 'true'){
+ if(app_uses_sdk){
if(!options.bypass_iframe_messaging){
const resp = await sendWindowWillCloseMsg(el_iframe.get(0));
if(!resp.msg){
@@ -2819,6 +2821,30 @@ $.fn.close = async function(options) {
}
// close child windows
$(`.window[data-parent_uuid="${window_uuid}"]`).close();
+
+ // notify other apps that we're closing
+ if (app_uses_sdk) {
+ // notify parent app, if we have one, that we're closing
+ const parent_id = this.dataset['parent_instance_id'];
+ const parent = $(`.window[data-element_uuid="${parent_id}"] .window-app-iframe`).get(0);
+ if (parent) {
+ parent.contentWindow.postMessage({
+ msg: 'appClosed',
+ appInstanceID: window_uuid,
+ }, '*');
+ }
+
+ // notify child apps, if we have them, that we're closing
+ const children = $(`.window[data-parent_instance_id="${window_uuid}"] .window-app-iframe`);
+ children.each((_, child) => {
+ child.contentWindow.postMessage({
+ msg: 'appClosed',
+ appInstanceID: window_uuid,
+ }, '*');
+ });
+ // TODO: Once other AppConnections exist, those will need notifying too.
+ }
+
// remove backdrop
$(this).closest('.window-backdrop').remove();
// remove DOM element
diff --git a/src/UI/UIWindowManageSessions.js b/src/UI/UIWindowManageSessions.js
new file mode 100644
index 000000000..1debb5da9
--- /dev/null
+++ b/src/UI/UIWindowManageSessions.js
@@ -0,0 +1,152 @@
+import UIAlert from "./UIAlert.js";
+import UIWindow from "./UIWindow.js";
+
+const UIWindowManageSessions = async function UIWindowManageSessions () {
+ const services = globalThis.services;
+
+ const w = await UIWindow({
+ title: i18n('ui_manage_sessions'),
+ icon: null,
+ uid: null,
+ is_dir: false,
+ message: 'message',
+ // body_icon: options.body_icon,
+ // backdrop: options.backdrop ?? false,
+ is_droppable: false,
+ has_head: true,
+ selectable_body: false,
+ draggable_body: true,
+ allow_context_menu: false,
+ window_class: 'window-session-manager',
+ dominant: true,
+ body_content: '',
+ // width: 600,
+ // parent_uuid: options.parent_uuid,
+ // ...options.window_options,
+ });
+
+ const SessionWidget = ({ session }) => {
+ const el = document.createElement('div');
+ el.classList.add('session-widget');
+ el.dataset.uuid = session.uuid;
+ // '' +
+ // JSON.stringify(session, null, 2) +
+ // '
';
+
+ const el_uuid = document.createElement('div');
+ el_uuid.textContent = session.uuid;
+ el.appendChild(el_uuid);
+ el_uuid.classList.add('session-widget-uuid');
+
+ const el_meta = document.createElement('div');
+ el_meta.classList.add('session-widget-meta');
+ for ( const key in session.meta ) {
+ const el_entry = document.createElement('div');
+ el_entry.classList.add('session-widget-meta-entry');
+
+ const el_key = document.createElement('div');
+ el_key.textContent = key;
+ el_key.classList.add('session-widget-meta-key');
+ el_entry.appendChild(el_key);
+
+ const el_value = document.createElement('div');
+ el_value.textContent = session.meta[key];
+ el_value.classList.add('session-widget-meta-value');
+ el_entry.appendChild(el_value);
+
+ el_meta.appendChild(el_entry);
+ }
+ el.appendChild(el_meta);
+
+ const el_actions = document.createElement('div');
+ el_actions.classList.add('session-widget-actions');
+
+ const el_btn_revoke = document.createElement('button');
+ el_btn_revoke.textContent = i18n('ui_revoke');
+ el_btn_revoke.classList.add('button', 'button-danger');
+ el_btn_revoke.addEventListener('click', async () => {
+ try{
+ const alert_resp = await UIAlert({
+ message: i18n('confirm_session_revoke'),
+ buttons:[
+ {
+ label: i18n('yes'),
+ value: 'yes',
+ type: 'primary',
+ },
+ {
+ label: i18n('cancel')
+ },
+ ]
+ });
+
+ if ( alert_resp !== 'yes' ) {
+ return;
+ }
+
+ const resp = await fetch(`${api_origin}/auth/revoke-session`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${puter.authToken}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ uuid: session.uuid,
+ }),
+ });
+ if ( resp.ok ) {
+ el.remove();
+ return;
+ }
+ UIAlert({ message: await resp.text() }).appendTo(w_body);
+ } catch ( e ) {
+ UIAlert({ message: e.toString() }).appendTo(w_body);
+ }
+ });
+ el_actions.appendChild(el_btn_revoke);
+ el.appendChild(el_actions);
+
+ return {
+ appendTo (parent) {
+ parent.appendChild(el);
+ return this;
+ }
+ };
+ };
+
+ const reload_sessions = async () => {
+ const resp = await fetch(`${api_origin}/auth/list-sessions`, {
+ headers: {
+ Authorization: `Bearer ${puter.authToken}`,
+ },
+ method: 'GET',
+ });
+
+ const sessions = await resp.json();
+
+ for ( const el of w_body.querySelectorAll('.session-widget') ) {
+ if ( !sessions.find(s => s.uuid === el.dataset.uuid) ) {
+ el.remove();
+ }
+ }
+
+ for ( const session of sessions ) {
+ if ( w.querySelector(`.session-widget[data-uuid="${session.uuid}"]`) ) {
+ continue;
+ }
+ SessionWidget({ session }).appendTo(w_body);
+ }
+ };
+
+ const w_body = w.querySelector('.window-body');
+
+ w_body.classList.add('session-manager-list');
+
+ reload_sessions();
+ const interval = setInterval(reload_sessions, 8000);
+ w.on_close = () => {
+ clearInterval(interval);
+ }
+};
+
+export default UIWindowManageSessions;
diff --git a/src/css/style.css b/src/css/style.css
index a09816a68..17694e5b8 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -3538,6 +3538,7 @@ label {
}
#storage-bar {
+ float: left;
height: 15px;
background-color: #dbe3ef;
border-top-left-radius: 3px;
@@ -3545,12 +3546,20 @@ label {
width: 0;
}
+#storage-bar-host {
+ float: left;
+ height: 15px;
+ background-color: #dbe3ef9c;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ width: 0;
+}
+
#storage-used-percent {
position: absolute;
- left: calc(50% - 20px);
text-align: center;
display: inline-block;
- width: 40px;
+ width: 100%;
font-size: 13px;
}
@@ -3710,3 +3719,61 @@ label {
background: #04AA6D;
cursor: pointer;
}
+
+.session-manager-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px;
+ box-sizing: border-box;
+ height: 100% !important;
+}
+
+.session-widget {
+ display: flex;
+ flex-direction: column;
+ padding: 10px;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ gap: 4px;
+}
+
+.session-widget-uuid {
+ font-size: 12px;
+ font-weight: 600;
+ color: #9c185b;
+}
+
+.session-widget-meta {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-height: 100px;
+ overflow-y: scroll;
+}
+
+.session-widget-meta-entry {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.session-widget-meta-key {
+ font-size: 12px;
+ color: #666;
+ flex-basis: 40%;
+ flex-shrink: 0;
+}
+
+.session-widget-meta-value {
+ font-size: 12px;
+ color: #666;
+ flex-grow: 1;
+}
+
+.session-widget-actions {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ justify-content: flex-end;
+}
\ No newline at end of file
diff --git a/src/helpers.js b/src/helpers.js
index a8486d11e..f3a88a9bc 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -722,7 +722,14 @@ window.mutate_user_preferences = function(user_preferences_delta) {
window.update_user_preferences = function(user_preferences) {
window.user_preferences = user_preferences;
localStorage.setItem('user_preferences', JSON.stringify(user_preferences));
- window.locale = user_preferences.language ?? 'en';
+ const language = user_preferences.language ?? 'en';
+ window.locale = language;
+
+ // Broadcast locale change to apps
+ const broadcastService = globalThis.services.get('broadcast');
+ broadcastService.sendBroadcast('localeChanged', {
+ language: language,
+ }, { sendToNewAppInstances: true });
}
window.sendWindowWillCloseMsg = function(iframe_element) {
@@ -1853,6 +1860,10 @@ window.launch_app = async (options)=>{
let icon, title, file_signature;
const window_options = options.window_options ?? {};
+ if (options.parent_instance_id) {
+ window_options.parent_instance_id = options.parent_instance_id;
+ }
+
// try to get 3rd-party app info
let app_info = options.app_obj ?? await get_apps(options.name);
diff --git a/src/i18n/translations/de.js b/src/i18n/translations/de.js
index 4e899e158..639d4b765 100644
--- a/src/i18n/translations/de.js
+++ b/src/i18n/translations/de.js
@@ -21,9 +21,9 @@ const de = {
english_name: "German",
code: "de",
dictionary: {
- access_granted_to: "Erlaubt zugriff auf",
+ access_granted_to: "Zugriff erlaubt für",
add_existing_account: "Bestehenden Account hinzufügen",
- all_fields_required: 'Alle felder müssen ausgefüllt werden.',
+ all_fields_required: 'Alle Felder müssen ausgefüllt werden.',
apply: "Anwenden",
ascending: 'Aufsteigend',
background: "Hintergrund",
@@ -34,7 +34,7 @@ const de = {
change_language: "Sprache ändern",
change_password: "Passwort ändern",
change_username: "Benutzername ändern",
- close_all_windows: "Alle fenster schließen",
+ close_all_windows: "Alle Fenster schließen",
color: 'Farbe',
confirm_account_for_free_referral_storage_c2a: 'Erstellen Sie ein Konto und bestätigen Sie Ihre E-Mail-Adresse, um 1 GB kostenlosen Speicherplatz zu erhalten. Auch Ihr Freund erhält 1 GB kostenlosen Speicherplatz.',
confirm_new_password: "Neues Passwort bestätigen",
@@ -43,7 +43,7 @@ const de = {
continue: "Weiter",
copy: 'Kopieren',
copy_link: "Link kopieren",
- copying: "Kopiert",
+ copying: "Kopiere",
cover: 'Abdecken',
create_account: "Account erstellen",
create_free_account: "Kostenlosen Account erstellen",
@@ -52,19 +52,19 @@ const de = {
cut: 'Ausschneiden',
date_modified: 'Zuletzt geändert',
delete: 'Entfernen',
- delete_permanently: "Permanent entfernen",
+ delete_permanently: "Dauerhaft entfernen",
deploy_as_app: 'Als App bereitstellen',
- descending: 'Herabsteigend',
- desktop_background_fit: "Fit", //idk translation
- dir_published_as_website: `%strong% Wurde veröffentlicht zu:`,
- disassociate_dir: "Verzeichniss trennen",
+ descending: 'Absteigend',
+ desktop_background_fit: "Passend",
+ dir_published_as_website: `%strong% wurde veröffentlicht zu:`,
+ disassociate_dir: "Verzeichnis trennen",
download: 'Herunterladen',
downloading: "Lädt herunter",
- email: "Email",
- email_or_username: "Email oder Benutzername",
+ email: "E-Mail",
+ email_or_username: "E-Mail oder Benutzername",
empty_trash: 'Papierkorb leeren',
empty_trash_confirmation: `Sind Sie sicher, dass Sie die Elemente im Papierkorb dauerhaft löschen möchten?`,
- emptying_trash: 'Leert Papierkorb…',
+ emptying_trash: 'Papierkorb wird geleert…',
feedback: "Feedback",
feedback_c2a: "Bitte verwenden Sie das untenstehende Formular, um uns Ihr Feedback, Ihre Kommentare und Fehlerberichte zu senden.",
feedback_sent_confirmation: "Danke, dass Sie uns kontaktiert haben. Wenn mit Ihrem Konto eine E-Mail-Adresse verknüpft ist, erhalten Sie so schnell wie möglich eine Rückmeldung von uns.",
@@ -83,15 +83,15 @@ const de = {
log_in: "Einloggen",
log_out: 'Ausloggen',
move: 'Verschieben',
- moving: "Verschiebt",
+ moving: "Verschiebe",
my_websites: "Meine Webseiten",
name: 'Name',
name_cannot_be_empty: 'Name kann nicht leer sein.',
name_cannot_contain_double_period: "Der Name kann nicht '..' sein.",
name_cannot_contain_period: "Der Name kann nicht '.' sein.",
name_cannot_contain_slash: "Der Name kann nicht '/' enthalten.",
- name_must_be_string: "Name kann nur eine Zeichenfolge sein.",
- name_too_long: `Der Name kann nicht länger als %% Zeichen sein.`,
+ name_must_be_string: "Name darf nur eine Zeichenfolge sein.",
+ name_too_long: `Der Name darf nicht länger als %% Zeichen sein.`,
new: 'Neu',
new_folder: 'Neuer Ordner',
new_password: "Neues Passwort",
@@ -100,25 +100,25 @@ const de = {
no_websites_published: "Sie haben noch keine Webseite veröffentlicht",
ok: 'OK',
open: "Öffnen",
- open_in_new_tab: "Im neuen Tab öffnen",
- open_in_new_window: "Im neuen Fenster öffnen",
+ open_in_new_tab: "In neuem Tab öffnen",
+ open_in_new_window: "In neuem Fenster öffnen",
open_with: "Öffnen mit",
password: "Passwort",
password_changed: "Passwort geändert.",
- passwords_do_not_match: '`Neues Passwoer` und `Neues Passwort bestätigen` stimmen nicht überein.',
+ passwords_do_not_match: 'Die Passwörter in `Neues Passwort` und `Neues Passwort bestätigen` stimmen nicht überein.',
paste: 'Einfügen',
paste_into_folder: "In Ordner einfügen",
pick_name_for_website: "Wählen Sie einen Namen für Ihre Webseite:",
picture: "Bild",
powered_by_puter_js: `Betrieben von {{link=docs}}Puter.js{{/link}}`,
- preparing: "Bereitet vor...",
- preparing_for_upload: "Bereitet für das Hochladen vor...",
+ preparing: "Bereite vor…",
+ preparing_for_upload: "Bereite für das Hochladen vor…",
properties: "Einstellungen",
publish: "Veröffentlichen",
publish_as_website: 'Als Webseite veröffentlichen',
recent: "Kürzlich",
recover_password: "Passwort wiederherstellen",
- refer_friends_c2a: "Erhalten Sie 1 GB für jeden Freund, der ein Account bei Puter erstellt und bestätigt. Ihr Freund bekommt auch 1 GB!",
+ refer_friends_c2a: "Erhalten Sie 1 GB für jeden Freund, der einen Account bei Puter erstellt und bestätigt. Ihr Freund bekommt auch 1 GB!",
refer_friends_social_media_c2a: `Erhalten Sie 1 GB kostenlosen Speicherplatz auf Puter.com!`,
refresh: 'Neu laden',
release_address_confirmation: `Sind Sie sicher, dass Sie diese Adresse freigeben möchten?`,
@@ -127,9 +127,9 @@ const de = {
repeat: 'Wiederholen',
resend_confirmation_code: "Bestätigungscode erneut senden",
restore: "Wiederherstellen",
- save_account_to_get_copy_link: "Bitte erstellen Sie ein Acccount, um fortzufahren.",
- save_account_to_publish: 'Bitte erstellen Sie ein Acccount, um fortzufahren.',
- save_session_c2a: 'Erstellen Sie ein Konto, um Ihre aktuelle Sitzung zu speichern und den Verlust Ihrer Arbeit zu vermeiden.',
+ save_account_to_get_copy_link: "Bitte erstellen Sie einen Acccount, um fortzufahren.",
+ save_account_to_publish: 'Bitte erstellen Sie einen Acccount, um fortzufahren.',
+ save_session_c2a: 'Erstellen Sie ein Konto, um Ihre aktuelle Sitzung zu speichern und den Verlust Ihrer Arbeit zu verhindern.',
scan_qr_c2a: 'Scannen Sie den folgenden Code, um sich von anderen Geräten aus bei dieser Sitzung anzumelden',
select: "Auswählen",
select_color: 'Farbe auswählen…',
@@ -140,18 +140,19 @@ const de = {
share_to: "Teilen mit",
show_all_windows: "Alle Fenster zeigen",
show_hidden: 'Zeige versteckte',
- sign_in_with_puter: "Einloggen mit Puter",
- sign_up: "Einloggen",
- signing_in: "Loggt ein…",
+ sign_in_with_puter: "Anmelden mit Puter",
+ sign_up: "Anmelden",
+ signing_in: "Melde an…",
size: 'Größe',
- sort_by: 'Sotieren nach',
+ sort_by: 'Sortieren nach',
start: 'Start',
- taking_longer_than_usual: 'Dauert etwas länger als gewöhnlich. Bitte warten...',
+ storage_puter_used: 'Verwendet von Puter',
+ taking_longer_than_usual: 'Es dauert etwas länger als gewöhnlich. Bitte warten…',
text_document: 'Textdokument',
tos_fineprint: `Indem Sie auf „Kostenloses Konto erstellen“ klicken, stimmen Sie den {{link=terms}}Nutzungsbedingungen{{/link}} und der {{link=privacy}}Datenschutzerklärung{{/link}} von Puter zu.`,
trash: 'Papierkorb',
type: 'Typ',
- undo: 'Zurück',
+ undo: 'Rückgängig',
unzip: "Entpacken",
upload: 'Hochladen',
upload_here: 'Hier hochladen',
@@ -160,7 +161,7 @@ const de = {
versions: "Versionen",
yes_release_it: 'Ja, veröffentlichen',
you_have_been_referred_to_puter_by_a_friend: "Sie wurden von einem Freund an Puter verwiesen!",
- zip: "Zip",
+ zip: "Zippen",
}
};
diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js
index 3fed31897..8e2c5c3a3 100644
--- a/src/i18n/translations/en.js
+++ b/src/i18n/translations/en.js
@@ -51,6 +51,7 @@ const en = {
confirm_new_password: "Confirm New Password",
confirm_delete_user: "Are you sure you want to delete your account? All your files and data will be permanently deleted. This action cannot be undone.",
confirm_delete_user_title: "Delete Account?",
+ confirm_session_revoke: "Are you sure you want to revoke this session?",
contact_us: "Contact Us",
contain: 'Contain',
continue: "Continue",
@@ -112,6 +113,7 @@ const en = {
log_in: "Log In",
log_into_another_account_anyway: 'Log into another account anyway',
log_out: 'Log Out',
+ manage_sessions: "Manage Sessions",
move: 'Move',
moving: "Moving",
my_websites: "My Websites",
@@ -179,6 +181,7 @@ const en = {
select: "Select",
selected: 'selected',
select_color: 'Select color…',
+ sessions: "Sessions",
send: "Send",
send_password_recovery_email: "Send Password Recovery Email",
session_saved: "Thank you for creating an account. This session has been saved.",
@@ -196,6 +199,7 @@ const en = {
start: 'Start',
status: "Status",
storage_usage: "Storage Usage",
+ storage_puter_used: 'used by Puter',
taking_longer_than_usual: 'Taking a little longer than usual. Please wait...',
terms: "Terms",
text_document: 'Text document',
@@ -205,6 +209,8 @@ const en = {
type: 'Type',
type_confirm_to_delete_account: "Type 'confirm' to delete your account.",
ui_colors: "UI Colors",
+ ui_manage_sessions: "Session Manager",
+ ui_revoke: "Revoke",
undo: 'Undo',
unlimited: 'Unlimited',
unzip: "Unzip",
diff --git a/src/i18n/translations/fa.js b/src/i18n/translations/fa.js
index d0db2ae45..1aecc6d17 100644
--- a/src/i18n/translations/fa.js
+++ b/src/i18n/translations/fa.js
@@ -147,6 +147,7 @@ const fa = {
size: 'اندازه',
sort_by: 'مرتب سازی بر اساس',
start: 'شروع',
+ storage_puter_used: 'استفاده شده توسط Puter',
taking_longer_than_usual: 'کمی بیشتر از معمول طول می کشد. لطفا صبر کنید...',
text_document: 'سند متنی',
tos_fineprint: `با کلیک بر روی 'ایجاد حساب کاربری رایگان' شما با {{link=terms}}شرایط خدمات{{/link}} و {{link=privacy}}سیاست حفظ حریم خصوصی{{/link}} Puter موافقت می کنید.`,
diff --git a/src/i18n/translations/ko.js b/src/i18n/translations/ko.js
index e02299282..5954e0947 100644
--- a/src/i18n/translations/ko.js
+++ b/src/i18n/translations/ko.js
@@ -21,7 +21,9 @@ const ko = {
english_name: "Korean",
code: "ko",
dictionary: {
+ about: "정보",
access_granted_to: "접근 권한 부여",
+ account: "계정",
add_existing_account: "기존 계정 추가",
all_fields_required: '모든 필드는 필수입니다.',
apply: "적용",
@@ -34,6 +36,10 @@ const ko = {
change_language: "언어 변경",
change_password: "비밀번호 변경",
change_username: "사용자 이름 변경",
+ clock: '시계',
+ clock_visible_hide: '숨기기 - 항상 숨김',
+ clock_visible_show: '표시 - 항상 표시',
+ clock_visible_auto: '자동 - 숨김',
close_all_windows: "모든 창 닫기",
color: '색상',
confirm_account_for_free_referral_storage_c2a: '계정을 생성하고 이메일 주소를 확인하여 1GB의 무료 저장 공간을 받으십시오. 친구도 1GB의 무료 저장 공간을 받게 됩니다.',
@@ -52,6 +58,7 @@ const ko = {
cut: '잘라내기',
date_modified: '수정한 날짜',
delete: '삭제',
+ delete_account: "계정 삭제",
delete_permanently: "영구 삭제",
deploy_as_app: '앱으로 배포',
descending: '내림차순',
@@ -80,6 +87,7 @@ const ko = {
items_in_trash_cannot_be_renamed: `이 항목은 휴지통에 있기 때문에 이름을 바꿀 수 없습니다. 이 항목의 이름을 바꾸려면 먼저 휴지통에서 끌어내십시오.`,
jpeg_image: 'JPEG 이미지',
keep_in_taskbar: '작업 표시줄에 유지',
+ language: "언어",
log_in: "로그인",
log_out: '로그아웃',
move: '이동',
@@ -136,6 +144,7 @@ const ko = {
send: "보내기",
send_password_recovery_email: "비밀번호 복구 이메일 보내기",
session_saved: "계정을 생성해 주셔서 감사합니다. 이 세션이 저장되었습니다.",
+ settings: "설정",
set_new_password: "새 비밀번호 설정",
share_to: "공유",
show_all_windows: "모든 창 표시",
@@ -146,6 +155,8 @@ const ko = {
size: '크기',
sort_by: '정렬 기준',
start: '시작',
+ storage_usage: "저장 공간 사용량",
+ storage_puter_used: 'Puter에서 사용 중',
taking_longer_than_usual: '보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...',
text_document: '텍스트 문서',
tos_fineprint: `무료 계정 생성을 클릭하면 Puter의 {{link=terms}}서비스 약관{{/link}}과 {{link=privacy}}개인정보 보호정책{{/link}}에 동의하는 것입니다.`,
@@ -157,7 +168,9 @@ const ko = {
upload_here: '여기에 업로드',
username: "사용자 이름",
username_changed: '사용자 이름이 성공적으로 업데이트되었습니다.',
+ usage: "사용량",
versions: "버전",
+ visibility: '가시성',
yes_release_it: '예, 해제합니다',
you_have_been_referred_to_puter_by_a_friend: "친구가 Puter로 추천했습니다!",
zip: "압축",
diff --git a/src/i18n/translations/translations.js b/src/i18n/translations/translations.js
index d53c0f7b0..ccecb8c67 100644
--- a/src/i18n/translations/translations.js
+++ b/src/i18n/translations/translations.js
@@ -39,6 +39,7 @@ import sv from './sv.js';
import th from './th.js';
import ur from './ur.js';
import zh from './zh.js';
+import zhtw from './zhtw.js';
import emoji from './emoji.js';
@@ -66,6 +67,7 @@ export default {
sv,
th,
zh,
+ zhtw,
ur,
emoji,
};
diff --git a/src/i18n/translations/zhtw.js b/src/i18n/translations/zhtw.js
new file mode 100644
index 000000000..755fab818
--- /dev/null
+++ b/src/i18n/translations/zhtw.js
@@ -0,0 +1,226 @@
+/**
+ * Copyright (C) 2024 Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+const zhtw = {
+ name: "繁體中文",
+ english_name: "Traditional Chinese",
+ code: "zhtw",
+ dictionary: {
+ about: "關於",
+ account: "帳號",
+ access_granted_to: "已授予存取權限給",
+ add_existing_account: "新增現有帳號",
+ all_fields_required: '所有欄位都必須填寫。',
+ apply: "套用",
+ ascending: '升序',
+ auto_arrange: '自動排列',
+ background: "背景",
+ browse: "瀏覽",
+ cancel: '取消',
+ center: '中心',
+ change_desktop_background: '更改桌面背景…',
+ change_email: "更改電子郵件",
+ change_language: "更改語言",
+ change_password: "更改密碼",
+ change_ui_colors: "更改 UI 顏色",
+ change_username: "更改使用者名稱",
+ close_all_windows: "關閉所有視窗",
+ close_all_windows_and_log_out: '關閉視窗並登出',
+ change_always_open_with: "您是否希望總是用此程式開啟這類型的檔案",
+ color: '顏色',
+ hue: '色調',
+ confirm_account_for_free_referral_storage_c2a: '建立帳號並確認您的電子郵件地址以獲得 1 GB 的免費儲存空間。您的朋友也將獲得 1 GB 的免費儲存空間。',
+ confirm_delete_multiple_items: '您確定要永久刪除這些項目嗎?',
+ confirm_delete_single_item: '您確定要永久刪除此項目嗎?',
+ confirm_open_apps_log_out: '您有開啟的應用程式。您確定要登出嗎?',
+ confirm_new_password: "確認新密碼",
+ confirm_delete_user: "您確定要刪除您的帳號嗎?您的所有檔案和資料將被永久刪除。此操作無法復原。",
+ confirm_delete_user_title: "刪除帳號?",
+ contact_us: "聯絡我們",
+ contain: '包含',
+ continue: "繼續",
+ copy: '複製',
+ copy_link: "複製連結",
+ copying: "正在複製",
+ cover: '覆蓋',
+ create_account: "建立帳號",
+ create_free_account: "建立免費帳號",
+ create_shortcut: "建立捷徑",
+ credits: "鳴謝",
+ current_password: "目前的密碼",
+ cut: '剪下',
+ clock: "時鐘",
+ clock_visible_hide: '隱藏 - 總是隱藏',
+ clock_visible_show: '顯示 - 總是顯示',
+ clock_visible_auto: '自動 - 預設,僅在全螢幕模式下可見。',
+ date_modified: '修改日期',
+ delete: '刪除',
+ delete_account: "刪除帳號",
+ delete_permanently: "永久刪除",
+ deploy_as_app: '部署為應用程式',
+ descending: '降序',
+ desktop_background_fit: "適應",
+ developers: "開發者",
+ dir_published_as_website: `%strong% 已發布到:`,
+ disassociate_dir: "取消關聯目錄",
+ download: '下載',
+ download_file: '下載檔案',
+ downloading: "正在下載",
+ email: "電子郵件",
+ email_or_username: "電子郵件或使用者名稱",
+ empty_trash: '清空垃圾桶',
+ empty_trash_confirmation: `您確定要永久刪除垃圾桶中的項目嗎?`,
+ emptying_trash: '正在清空垃圾桶…',
+ enter_password_to_confirm_delete_user: "輸入您的密碼以確認刪除帳號",
+ feedback: "回饋",
+ feedback_c2a: "請使用下面的表單向我們傳送您的回饋、評論和錯誤報告。",
+ feedback_sent_confirmation: "感謝您聯絡我們。如果您的帳號有關聯的電子郵件,我們將盡快回覆您。",
+ fit: "適應",
+ forgot_pass_c2a: "忘記密碼?",
+ from: "從",
+ general: "一般",
+ get_a_copy_of_on_puter: `在 Puter.com 上取得 '%%' 的副本!`,
+ get_copy_link: '取得複製連結',
+ hide_all_windows: "隱藏所有視窗",
+ html_document: 'HTML 文件',
+ image: '圖片',
+ incorrect_password: "密碼錯誤",
+ invite_link: "邀請連結",
+ item: '項目',
+ items_in_trash_cannot_be_renamed: `此項目無法重新命名,因為它在垃圾桶中。要重新命名此項目,請先將其從垃圾桶中拖出。`,
+ jpeg_image: 'JPEG 圖片',
+ keep_in_taskbar: '保留在工作列',
+ language: "語言",
+ license: "許可證",
+ lightness: '亮度',
+ loading: '載入中',
+ log_in: "登入",
+ log_into_another_account_anyway: '無論如何都要登入另一個帳號',
+ log_out: '登出',
+ move: '移動',
+ moving: "正在移動",
+ my_websites: "我的網站",
+ name: '名稱',
+ name_cannot_be_empty: '名稱不能為空。',
+ name_cannot_contain_double_period: "名稱不能為 '..' 字元。",
+ name_cannot_contain_period: "名稱不能為 '.' 字元。",
+ name_cannot_contain_slash: "名稱不能包含 '/' 字元。",
+ name_must_be_string: "名稱只能是字串。",
+ name_too_long: `名稱不能超過 %% 個字元。`,
+ new: '新建',
+ new_folder: '新資料夾',
+ new_password: "新密碼",
+ new_username: "新使用者名稱",
+ no: '否',
+ no_dir_associated_with_site: '此地址沒有關聯的目錄。',
+ no_websites_published: "您尚未發布任何網站。",
+ ok: '確定',
+ open: "開啟",
+ open_in_new_tab: "在新標籤頁中開啟",
+ open_in_new_window: "在新視窗中開啟",
+ open_with: "開啟方式",
+ oss_code_and_content: "開源軟體和內容",
+ password: "密碼",
+ password_changed: "密碼已更改。",
+ passwords_do_not_match: '`新密碼` 和 `確認新密碼` 不符。',
+ paste: '貼上',
+ paste_into_folder: "貼入資料夾",
+ personalization: "個人化",
+ pick_name_for_website: "為您的網站選擇一個名稱:",
+ picture: "圖片",
+ plural_suffix: '個',
+ powered_by_puter_js: `由 {{link=docs}}Puter.js{{/link}} 提供技術支援`,
+ preparing: "準備中...",
+ preparing_for_upload: "準備上傳...",
+ privacy: "隱私",
+ proceed_to_login: '繼續登入',
+ proceed_with_account_deletion: "繼續刪除帳號",
+ properties: "屬性",
+ publish: "發布",
+ publish_as_website: '發布為網站',
+ puter_description: `Puter 是一個以隱私為首的個人雲端,將您的所有檔案、應用程式和遊戲保留在一個
+ 安全的地方,隨時隨地都可以存取。`,
+ recent: "最近",
+ recover_password: "找回密碼",
+ refer_friends_c2a: "每推薦一位朋友在 Puter 上建立並確認帳號,您就可以獲得 1 GB 的儲存空間。您的朋友也會獲得 1 GB 的儲存空間!",
+ refer_friends_social_media_c2a: `在 Puter.com 上獲得 1 GB 的免費儲存空間!`,
+ refresh: '重新整理',
+ release_address_confirmation: `您確定要釋放此地址嗎?`,
+ remove_from_taskbar:'從工作列移除',
+ rename: '重新命名',
+ repeat: '重複',
+ replace: '取代',
+ replace_all: '全部取代',
+ resend_confirmation_code: "重新傳送確認碼",
+ reset_colors: "重設顏色",
+ restore: "還原",
+ saturation: '飽和度',
+ save_account: '儲存帳號',
+ save_account_to_get_copy_link: "請建立帳號以繼續。",
+ save_account_to_publish: '請建立帳號以繼續。',
+ save_session: '儲存工作階段',
+ save_session_c2a: '建立帳號以儲存您目前的工作階段,避免遺失您的工作。',
+ scan_qr_c2a: '掃描下方的 QR Code,從其他裝置登入此工作階段',
+ select: "選擇",
+ selected: '已選擇',
+ select_color: '選擇顏色…',
+ send: "傳送",
+ send_password_recovery_email: "傳送密碼恢復電子郵件",
+ session_saved: "感謝您建立帳號。此工作階段已儲存。",
+ settings: "設定",
+ set_new_password: "設定新密碼",
+ share_to: "分享到",
+ show_all_windows: "顯示所有視窗",
+ show_hidden: '顯示隱藏項目',
+ sign_in_with_puter: "使用 Puter 登入",
+ sign_up: "註冊",
+ signing_in: "正在登入…",
+ size: '大小',
+ skip: '跳過',
+ sort_by: '排序方式',
+ start: '開始',
+ status: "狀態",
+ storage_usage: "儲存空間使用情況",
+ taking_longer_than_usual: '花費的時間比預期的還長。請稍候...',
+ terms: "條款",
+ text_document: '文字文件',
+ tos_fineprint: `點選 '建立免費帳號' 即表示您同意 Puter 的 {{link=terms}}服務條款{{/link}} 和 {{link=privacy}}隱私政策{{/link}}。`,
+ transparency: "透明度",
+ trash: '垃圾桶',
+ type: '類型',
+ type_confirm_to_delete_account: "輸入 'confirm' 以刪除您的帳號。",
+ ui_colors: "UI 顏色",
+ undo: '復原',
+ unlimited: '無限',
+ unzip: "解壓縮",
+ upload: '上傳',
+ upload_here: '在此上傳',
+ usage: '使用情況',
+ username: "使用者名稱",
+ username_changed: '使用者名稱已成功更新。',
+ versions: "版本",
+ visibility: '可視性',
+ yes: '是',
+ yes_release_it: '是的,釋放它',
+ you_have_been_referred_to_puter_by_a_friend: "您的朋友推薦您使用 Puter!",
+ zip: "壓縮",
+ storage_puter_used: "由 Puter 使用",
+ }
+};
+
+export default zhtw;
diff --git a/src/index.js b/src/index.js
index 25d136175..6bb56651c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -53,11 +53,9 @@ window.gui = async function(options){
window.max_item_name_length = options.max_item_name_length ?? 500;
window.require_email_verification_to_publish_website = options.require_email_verification_to_publish_website ?? true;
- // Add Puter.JS
- await loadScript('https://js.puter.com/v2/');
-
// DEV: Load the initgui.js file if we are in development mode
if(!window.gui_env || window.gui_env === "dev"){
+ await loadScript('/sdk/puter.dev.js');
await loadScript('/initgui.js', {isModule: true});
}
@@ -65,6 +63,7 @@ window.gui = async function(options){
// note: the order of the bundles is important
// note: Build script will prepend `window.gui_env="prod"` to the top of the file
else if(gui_env === "prod"){
+ await loadScript('https://js.puter.com/v2/');
// Load the minified bundles
await loadCSS('/dist/bundle.min.css');
await loadScript('/dist/bundle.min.js');
diff --git a/src/initgui.js b/src/initgui.js
index b5d2afb37..1cc6ac4a3 100644
--- a/src/initgui.js
+++ b/src/initgui.js
@@ -36,6 +36,7 @@ import PuterDialog from './UI/PuterDialog.js';
import determine_active_container_parent from './helpers/determine_active_container_parent.js';
import { ThemeService } from './services/ThemeService.js';
import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js';
+import { BroadcastService } from './services/BroadcastService.js';
const launch_services = async function () {
const services_l_ = [];
@@ -49,6 +50,7 @@ const launch_services = async function () {
services_m_[name] = instance;
}
+ register('broadcast', new BroadcastService());
register('theme', new ThemeService());
for (const [_, instance] of services_l_) {
@@ -364,7 +366,7 @@ window.initgui = async function(){
}
while(!is_verified)
}
- update_auth_data(window.auth_token, whoami);
+ update_auth_data(whoami.token || window.auth_token, whoami);
// -------------------------------------------------------------------------------------
// Load desktop, only if we're not embedded in a popup
diff --git a/src/services/BroadcastService.js b/src/services/BroadcastService.js
new file mode 100644
index 000000000..e0e6a470d
--- /dev/null
+++ b/src/services/BroadcastService.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright (C) 2024 Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { Service } from "../definitions.js";
+
+export class BroadcastService extends Service {
+ // After a new app is launched, it will receive these broadcasts
+ #broadcastsToSendToNewAppInstances = new Map(); // name -> data
+
+ async _init() {
+ // Nothing
+ }
+
+ // Send a 'broadcast' message to all open apps, with the given name and data.
+ // If sendToNewAppInstances is true, the message will be saved, and sent to any apps that are launched later.
+ // A new saved broadcast will replace an earlier one with the same name.
+ sendBroadcast(name, data, { sendToNewAppInstances = false } = {}) {
+ $('.window-app-iframe[data-appUsesSDK=true]').each((_, iframe) => {
+ iframe.contentWindow.postMessage({
+ msg: 'broadcast',
+ name: name,
+ data: data,
+ }, '*');
+ });
+
+ if (sendToNewAppInstances) {
+ this.#broadcastsToSendToNewAppInstances.set(name, data);
+ }
+ }
+
+ // Send all saved broadcast messages to the given app instance.
+ sendSavedBroadcastsTo(appInstanceID) {
+ const iframe = $(`.window[data-element_uuid="${appInstanceID}"] .window-app-iframe[data-appUsesSDK=true]`).get(0);
+ if (!iframe) {
+ console.error(`Attempted to send saved broadcasts to app instance ${appInstanceID}, which is not using the Puter SDK`);
+ return;
+ }
+ for (const [name, data] of this.#broadcastsToSendToNewAppInstances) {
+ iframe.contentWindow.postMessage({
+ msg: 'broadcast',
+ name: name,
+ data: data,
+ }, '*');
+ }
+ }
+}
diff --git a/src/services/ThemeService.js b/src/services/ThemeService.js
index 8a6e54f77..210386382 100644
--- a/src/services/ThemeService.js
+++ b/src/services/ThemeService.js
@@ -32,7 +32,11 @@ const default_values = {
};
export class ThemeService extends Service {
+ #broadcastService;
+
async _init () {
+ this.#broadcastService = globalThis.services.get('broadcast');
+
this.state = {
sat: 41.18,
hue: 210,
@@ -116,6 +120,16 @@ export class ThemeService extends Service {
this.root.style.setProperty('--primary-lightness', s.lig + '%');
this.root.style.setProperty('--primary-alpha', s.alpha);
this.root.style.setProperty('--primary-color', s.light_text ? 'white' : '#373e44');
+
+ // TODO: Should we debounce this to reduce traffic?
+ this.#broadcastService.sendBroadcast('themeChanged', {
+ palette: {
+ primaryHue: s.hue,
+ primarySaturation: s.sat + '%',
+ primaryLightness: s.lig + '%',
+ primaryAlpha: s.alpha,
+ },
+ }, { sendToNewAppInstances: true });
}
save_ () {