/* eslint-disable */ /** * To use sails.io.js in an AMD environment (e.g. with require.js), * replace this file with the sails.io.js file from the root of: * https://github.com/balderdashy/sails.io.js * and download a standalone copy of socket.io-client from: * https://github.com/socketio/socket.io-client * then follow the instructions at: * https://github.com/balderdashy/sails.io.js#requirejsamd-usage */ // socket.io-client version 2.0.3 // https://github.com/socketio/socket.io-client !function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.io=b():a.io=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){"use strict";function d(a,b){"object"===("undefined"==typeof a?"undefined":e(a))&&(b=a,a=void 0),b=b||{};var c,d=f(a),g=d.source,k=d.id,l=d.path,m=j[k]&&l in j[k].nsps,n=b.forceNew||b["force new connection"]||!1===b.multiplex||m;return n?(i("ignoring socket cache for %s",g),c=h(g,b)):(j[k]||(i("new io instance for %s",g),j[k]=h(g,b)),c=j[k]),d.query&&!b.query&&(b.query=d.query),c.socket(d.path,b)}var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},f=c(1),g=c(7),h=c(13),i=c(3)("socket.io-client");a.exports=b=d;var j=b.managers={};b.protocol=g.protocol,b.connect=d,b.Manager=c(13),b.Socket=c(39)},function(a,b,c){(function(b){"use strict";function d(a,c){var d=a;c=c||b.location,null==a&&(a=c.protocol+"//"+c.host),"string"==typeof a&&("/"===a.charAt(0)&&(a="/"===a.charAt(1)?c.protocol+a:c.host+a),/^(https?|wss?):\/\//.test(a)||(f("protocol-less url %s",a),a="undefined"!=typeof c?c.protocol+"//"+a:"https://"+a),f("parse %s",a),d=e(a)),d.port||(/^(http|ws)$/.test(d.protocol)?d.port="80":/^(http|ws)s$/.test(d.protocol)&&(d.port="443")),d.path=d.path||"/";var g=d.host.indexOf(":")!==-1,h=g?"["+d.host+"]":d.host;return d.id=d.protocol+"://"+h+":"+d.port,d.href=d.protocol+"://"+h+(c&&c.port===d.port?"":":"+d.port),d}var e=c(2),f=c(3)("socket.io-client:url");a.exports=d}).call(b,function(){return this}())},function(a,b){var c=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,d=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];a.exports=function(a){var b=a,e=a.indexOf("["),f=a.indexOf("]");e!=-1&&f!=-1&&(a=a.substring(0,e)+a.substring(e,f).replace(/:/g,";")+a.substring(f,a.length));for(var g=c.exec(a||""),h={},i=14;i--;)h[d[i]]=g[i]||"";return e!=-1&&f!=-1&&(h.source=b,h.host=h.host.substring(1,h.host.length-1).replace(/;/g,":"),h.authority=h.authority.replace("[","").replace("]","").replace(/;/g,":"),h.ipv6uri=!0),h}},function(a,b,c){(function(d){function e(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||"undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function f(a){var c=this.useColors;if(a[0]=(c?"%c":"")+this.namespace+(c?" %c":" ")+a[0]+(c?"%c ":" ")+"+"+b.humanize(this.diff),c){var d="color: "+this.color;a.splice(1,0,d,"color: inherit");var e=0,f=0;a[0].replace(/%[a-zA-Z%]/g,function(a){"%%"!==a&&(e++,"%c"===a&&(f=e))}),a.splice(f,0,d)}}function g(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function h(a){try{null==a?b.storage.removeItem("debug"):b.storage.debug=a}catch(c){}}function i(){var a;try{a=b.storage.debug}catch(c){}return!a&&"undefined"!=typeof d&&"env"in d&&(a=d.env.DEBUG),a}function j(){try{return window.localStorage}catch(a){}}b=a.exports=c(5),b.log=g,b.formatArgs=f,b.save=h,b.load=i,b.useColors=e,b.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:j(),b.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],b.formatters.j=function(a){try{return JSON.stringify(a)}catch(b){return"[UnexpectedJSONParseError]: "+b.message}},b.enable(i())}).call(b,c(4))},function(a,b){function c(){throw new Error("setTimeout has not been defined")}function d(){throw new Error("clearTimeout has not been defined")}function e(a){if(k===setTimeout)return setTimeout(a,0);if((k===c||!k)&&setTimeout)return k=setTimeout,setTimeout(a,0);try{return k(a,0)}catch(b){try{return k.call(null,a,0)}catch(b){return k.call(this,a,0)}}}function f(a){if(l===clearTimeout)return clearTimeout(a);if((l===d||!l)&&clearTimeout)return l=clearTimeout,clearTimeout(a);try{return l(a)}catch(b){try{return l.call(null,a)}catch(b){return l.call(this,a)}}}function g(){p&&n&&(p=!1,n.length?o=n.concat(o):q=-1,o.length&&h())}function h(){if(!p){var a=e(g);p=!0;for(var b=o.length;b;){for(n=o,o=[];++q1)for(var c=1;c100)){var b=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(a);if(b){var c=parseFloat(b[1]),d=(b[2]||"ms").toLowerCase();switch(d){case"years":case"year":case"yrs":case"yr":case"y":return c*k;case"days":case"day":case"d":return c*j;case"hours":case"hour":case"hrs":case"hr":case"h":return c*i;case"minutes":case"minute":case"mins":case"min":case"m":return c*h;case"seconds":case"second":case"secs":case"sec":case"s":return c*g;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return c;default:return}}}}function d(a){return a>=j?Math.round(a/j)+"d":a>=i?Math.round(a/i)+"h":a>=h?Math.round(a/h)+"m":a>=g?Math.round(a/g)+"s":a+"ms"}function e(a){return f(a,j,"day")||f(a,i,"hour")||f(a,h,"minute")||f(a,g,"second")||a+" ms"}function f(a,b,c){if(!(a0)return c(a);if("number"===f&&isNaN(a)===!1)return b["long"]?e(a):d(a);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(a))}},function(a,b,c){function d(){}function e(a){var c=""+a.type;return b.BINARY_EVENT!==a.type&&b.BINARY_ACK!==a.type||(c+=a.attachments+"-"),a.nsp&&"/"!==a.nsp&&(c+=a.nsp+","),null!=a.id&&(c+=a.id),null!=a.data&&(c+=JSON.stringify(a.data)),l("encoded %j as %s",a,c),c}function f(a,b){function c(a){var c=o.deconstructPacket(a),d=e(c.packet),f=c.buffers;f.unshift(d),b(f)}o.removeBlobs(a,c)}function g(){this.reconstructor=null}function h(a){var c=0,d={type:Number(a.charAt(0))};if(null==b.types[d.type])return k();if(b.BINARY_EVENT===d.type||b.BINARY_ACK===d.type){for(var e="";"-"!==a.charAt(++c)&&(e+=a.charAt(c),c!=a.length););if(e!=Number(e)||"-"!==a.charAt(c))throw new Error("Illegal attachments");d.attachments=Number(e)}if("/"===a.charAt(c+1))for(d.nsp="";++c;){var f=a.charAt(c);if(","===f)break;if(d.nsp+=f,c===a.length)break}else d.nsp="/";var g=a.charAt(c+1);if(""!==g&&Number(g)==g){for(d.id="";++c;){var f=a.charAt(c);if(null==f||Number(f)!=f){--c;break}if(d.id+=a.charAt(c),c===a.length)break}d.id=Number(d.id)}return a.charAt(++c)&&(d=i(d,a.substr(c))),l("decoded %s as %j",a,d),d}function i(a,b){try{a.data=JSON.parse(b)}catch(c){return k()}return a}function j(a){this.reconPack=a,this.buffers=[]}function k(){return{type:b.ERROR,data:"parser error"}}var l=c(3)("socket.io-parser"),m=c(8),n=c(9),o=c(11),p=c(12);b.protocol=4,b.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],b.CONNECT=0,b.DISCONNECT=1,b.EVENT=2,b.ACK=3,b.ERROR=4,b.BINARY_EVENT=5,b.BINARY_ACK=6,b.Encoder=d,b.Decoder=g,d.prototype.encode=function(a,c){if(a.type!==b.EVENT&&a.type!==b.ACK||!n(a.data)||(a.type=a.type===b.EVENT?b.BINARY_EVENT:b.BINARY_ACK),l("encoding packet %j",a),b.BINARY_EVENT===a.type||b.BINARY_ACK===a.type)f(a,c);else{var d=e(a);c([d])}},m(g.prototype),g.prototype.add=function(a){var c;if("string"==typeof a)c=h(a),b.BINARY_EVENT===c.type||b.BINARY_ACK===c.type?(this.reconstructor=new j(c),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",c)):this.emit("decoded",c);else{if(!p(a)&&!a.base64)throw new Error("Unknown type: "+a);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");c=this.reconstructor.takeBinaryData(a),c&&(this.reconstructor=null,this.emit("decoded",c))}},g.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},j.prototype.takeBinaryData=function(a){if(this.buffers.push(a),this.buffers.length===this.reconPack.attachments){var b=o.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),b}return null},j.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(a,b,c){function d(a){if(a)return e(a)}function e(a){for(var b in d.prototype)a[b]=d.prototype[b];return a}a.exports=d,d.prototype.on=d.prototype.addEventListener=function(a,b){return this._callbacks=this._callbacks||{},(this._callbacks["$"+a]=this._callbacks["$"+a]||[]).push(b),this},d.prototype.once=function(a,b){function c(){this.off(a,c),b.apply(this,arguments)}return c.fn=b,this.on(a,c),this},d.prototype.off=d.prototype.removeListener=d.prototype.removeAllListeners=d.prototype.removeEventListener=function(a,b){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var c=this._callbacks["$"+a];if(!c)return this;if(1==arguments.length)return delete this._callbacks["$"+a],this;for(var d,e=0;e0&&!this.encoding){var a=this.packetBuffer.shift();this.packet(a)}},d.prototype.cleanup=function(){l("cleanup");for(var a=this.subs.length,b=0;b=this._reconnectionAttempts)l("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var b=this.backoff.duration();l("will wait %dms before reconnect attempt",b),this.reconnecting=!0;var c=setTimeout(function(){a.skipReconnect||(l("attempting reconnect"),a.emitAll("reconnect_attempt",a.backoff.attempts),a.emitAll("reconnecting",a.backoff.attempts),a.skipReconnect||a.open(function(b){b?(l("reconnect attempt error"),a.reconnecting=!1,a.reconnect(),a.emitAll("reconnect_error",b.data)):(l("reconnect success"),a.onreconnect())}))},b);this.subs.push({destroy:function(){clearTimeout(c)}})}},d.prototype.onreconnect=function(){var a=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",a)}},function(a,b,c){a.exports=c(15)},function(a,b,c){a.exports=c(16),a.exports.parser=c(23)},function(a,b,c){(function(b){function d(a,c){if(!(this instanceof d))return new d(a,c);c=c||{},a&&"object"==typeof a&&(c=a,a=null),a?(a=k(a),c.hostname=a.host,c.secure="https"===a.protocol||"wss"===a.protocol,c.port=a.port,a.query&&(c.query=a.query)):c.host&&(c.hostname=k(c.host).host),this.secure=null!=c.secure?c.secure:b.location&&"https:"===location.protocol,c.hostname&&!c.port&&(c.port=this.secure?"443":"80"),this.agent=c.agent||!1,this.hostname=c.hostname||(b.location?location.hostname:"localhost"),this.port=c.port||(b.location&&location.port?location.port:this.secure?443:80),this.query=c.query||{},"string"==typeof this.query&&(this.query=m.decode(this.query)),this.upgrade=!1!==c.upgrade,this.path=(c.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!c.forceJSONP,this.jsonp=!1!==c.jsonp,this.forceBase64=!!c.forceBase64,this.enablesXDR=!!c.enablesXDR,this.timestampParam=c.timestampParam||"t",this.timestampRequests=c.timestampRequests,this.transports=c.transports||["polling","websocket"],this.transportOptions=c.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=c.policyPort||843,this.rememberUpgrade=c.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=c.onlyBinaryUpgrades,this.perMessageDeflate=!1!==c.perMessageDeflate&&(c.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=c.pfx||null,this.key=c.key||null,this.passphrase=c.passphrase||null,this.cert=c.cert||null,this.ca=c.ca||null,this.ciphers=c.ciphers||null,this.rejectUnauthorized=void 0===c.rejectUnauthorized||c.rejectUnauthorized,this.forceNode=!!c.forceNode;var e="object"==typeof b&&b;e.global===e&&(c.extraHeaders&&Object.keys(c.extraHeaders).length>0&&(this.extraHeaders=c.extraHeaders),c.localAddress&&(this.localAddress=c.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function e(a){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}var f=c(17),g=c(8),h=c(3)("engine.io-client:socket"),i=c(37),j=c(23),k=c(2),l=c(38),m=c(31);a.exports=d,d.priorWebsocketSuccess=!1,g(d.prototype),d.protocol=j.protocol,d.Socket=d,d.Transport=c(22),d.transports=c(17),d.parser=c(23),d.prototype.createTransport=function(a){h('creating transport "%s"',a);var b=e(this.query);b.EIO=j.protocol,b.transport=a;var c=this.transportOptions[a]||{};this.id&&(b.sid=this.id);var d=new f[a]({query:b,socket:this,agent:c.agent||this.agent,hostname:c.hostname||this.hostname,port:c.port||this.port,secure:c.secure||this.secure,path:c.path||this.path,forceJSONP:c.forceJSONP||this.forceJSONP,jsonp:c.jsonp||this.jsonp,forceBase64:c.forceBase64||this.forceBase64,enablesXDR:c.enablesXDR||this.enablesXDR,timestampRequests:c.timestampRequests||this.timestampRequests,timestampParam:c.timestampParam||this.timestampParam,policyPort:c.policyPort||this.policyPort,pfx:c.pfx||this.pfx,key:c.key||this.key,passphrase:c.passphrase||this.passphrase,cert:c.cert||this.cert,ca:c.ca||this.ca,ciphers:c.ciphers||this.ciphers,rejectUnauthorized:c.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:c.perMessageDeflate||this.perMessageDeflate,extraHeaders:c.extraHeaders||this.extraHeaders,forceNode:c.forceNode||this.forceNode,localAddress:c.localAddress||this.localAddress,requestTimeout:c.requestTimeout||this.requestTimeout,protocols:c.protocols||void 0});return d},d.prototype.open=function(){var a;if(this.rememberUpgrade&&d.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)a="websocket";else{if(0===this.transports.length){var b=this;return void setTimeout(function(){b.emit("error","No transports available")},0)}a=this.transports[0]}this.readyState="opening";try{a=this.createTransport(a)}catch(c){return this.transports.shift(),void this.open()}a.open(),this.setTransport(a)},d.prototype.setTransport=function(a){h("setting transport %s",a.name);var b=this;this.transport&&(h("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=a,a.on("drain",function(){b.onDrain()}).on("packet",function(a){b.onPacket(a)}).on("error",function(a){b.onError(a)}).on("close",function(){b.onClose("transport close")})},d.prototype.probe=function(a){function b(){if(m.onlyBinaryUpgrades){var b=!this.supportsBinary&&m.transport.supportsBinary;l=l||b}l||(h('probe transport "%s" opened',a),k.send([{type:"ping",data:"probe"}]),k.once("packet",function(b){if(!l)if("pong"===b.type&&"probe"===b.data){if(h('probe transport "%s" pong',a),m.upgrading=!0,m.emit("upgrading",k),!k)return;d.priorWebsocketSuccess="websocket"===k.name,h('pausing current transport "%s"',m.transport.name),m.transport.pause(function(){l||"closed"!==m.readyState&&(h("changing transport and sending upgrade packet"),j(),m.setTransport(k),k.send([{type:"upgrade"}]),m.emit("upgrade",k),k=null,m.upgrading=!1,m.flush())})}else{h('probe transport "%s" failed',a);var c=new Error("probe error");c.transport=k.name,m.emit("upgradeError",c)}}))}function c(){l||(l=!0,j(),k.close(),k=null)}function e(b){var d=new Error("probe error: "+b);d.transport=k.name,c(),h('probe transport "%s" failed because of error: %s',a,b),m.emit("upgradeError",d)}function f(){e("transport closed")}function g(){e("socket closed")}function i(a){k&&a.name!==k.name&&(h('"%s" works - aborting "%s"',a.name,k.name),c())}function j(){k.removeListener("open",b),k.removeListener("error",e),k.removeListener("close",f),m.removeListener("close",g),m.removeListener("upgrading",i)}h('probing transport "%s"',a);var k=this.createTransport(a,{probe:1}),l=!1,m=this;d.priorWebsocketSuccess=!1,k.once("open",b),k.once("error",e),k.once("close",f),this.once("close",g),this.once("upgrading",i),k.open()},d.prototype.onOpen=function(){if(h("socket open"),this.readyState="open",d.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){h("starting upgrade probes");for(var a=0,b=this.upgrades.length;a1?{type:t[e],data:a.substring(1)}:{type:t[e]}:u}var f=new Uint8Array(a),e=f[0],g=m(a,1);return v&&"blob"===c&&(g=new v([g])),{type:t[e],data:g}},b.decodeBase64Packet=function(a,b){var c=t[a.charAt(0)];if(!j)return{type:c,data:{base64:!0,data:a.substr(1)}};var d=j.decode(a.substr(1));return"blob"===b&&v&&(d=new v([d])),{type:c,data:d}},b.encodePayload=function(a,c,d){function e(a){return a.length+":"+a}function f(a,d){b.encodePacket(a,!!g&&c,!1,function(a){d(null,e(a))})}"function"==typeof c&&(d=c,c=null);var g=l(a);return c&&g?v&&!r?b.encodePayloadAsBlob(a,d):b.encodePayloadAsArrayBuffer(a,d):a.length?void i(a,f,function(a,b){return d(b.join(""))}):d("0:")},b.decodePayload=function(a,c,d){if("string"!=typeof a)return b.decodePayloadAsBinary(a,c,d);"function"==typeof c&&(d=c,c=null);var e;if(""===a)return d(u,0,1);for(var f,g,h="",i=0,j=a.length;i0;){for(var g=new Uint8Array(e),h=0===g[0],i="",j=1;255!==g[j];j++){if(i.length>310)return d(u,0,1);i+=g[j]}e=m(e,2+i.length),i=parseInt(i);var k=m(e,0,i);if(h)try{k=String.fromCharCode.apply(null,new Uint8Array(k))}catch(l){var n=new Uint8Array(k);k="";for(var j=0;jd&&(c=d),b>=d||b>=c||0===d)return new ArrayBuffer(0);for(var e=new Uint8Array(a),f=new Uint8Array(c-b),g=b,h=0;g=55296&&b<=56319&&e65535&&(b-=65536,e+=u(b>>>10&1023|55296),b=56320|1023&b),e+=u(b);return e}function i(a,b){if(a>=55296&&a<=57343){if(b)throw Error("Lone surrogate U+"+a.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function j(a,b){return u(a>>b&63|128)}function k(a,b){if(0==(4294967168&a))return u(a);var c="";return 0==(4294965248&a)?c=u(a>>6&31|192):0==(4294901760&a)?(i(a,b)||(a=65533),c=u(a>>12&15|224),c+=j(a,6)):0==(4292870144&a)&&(c=u(a>>18&7|240),c+=j(a,12),c+=j(a,6)),c+=u(63&a|128)}function l(a,b){b=b||{};for(var c,d=!1!==b.strict,e=g(a),f=e.length,h=-1,i="";++h=s)throw Error("Invalid byte index");var a=255&r[t];if(t++,128==(192&a))return 63&a;throw Error("Invalid continuation byte")}function n(a){var b,c,d,e,f;if(t>s)throw Error("Invalid byte index");if(t==s)return!1;if(b=255&r[t],t++,0==(128&b))return b;if(192==(224&b)){if(c=m(),f=(31&b)<<6|c,f>=128)return f;throw Error("Invalid continuation byte")}if(224==(240&b)){if(c=m(),d=m(),f=(15&b)<<12|c<<6|d,f>=2048)return i(f,a)?f:65533;throw Error("Invalid continuation byte")}if(240==(248&b)&&(c=m(),d=m(),e=m(),f=(7&b)<<18|c<<12|d<<6|e,f>=65536&&f<=1114111))return f;throw Error("Invalid UTF-8 detected")}function o(a,b){b=b||{};var c=!1!==b.strict;r=g(a),s=r.length,t=0;for(var d,e=[];(d=n(c))!==!1;)e.push(d);return h(e)}var p="object"==typeof b&&b,q=("object"==typeof a&&a&&a.exports==p&&a,"object"==typeof e&&e);q.global!==q&&q.window!==q||(f=q);var r,s,t,u=String.fromCharCode,v={version:"2.1.2",encode:l,decode:o};d=function(){return v}.call(b,c,b,a),!(void 0!==d&&(a.exports=d))}(this)}).call(b,c(28)(a),function(){return this}())},function(a,b){a.exports=function(a){return a.webpackPolyfill||(a.deprecate=function(){},a.paths=[],a.children=[],a.webpackPolyfill=1),a}},function(a,b){!function(){"use strict";for(var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",c=new Uint8Array(256),d=0;d>2],f+=a[(3&d[c])<<4|d[c+1]>>4],f+=a[(15&d[c+1])<<2|d[c+2]>>6],f+=a[63&d[c+2]];return e%3===2?f=f.substring(0,f.length-1)+"=":e%3===1&&(f=f.substring(0,f.length-2)+"=="),f},b.decode=function(a){var b,d,e,f,g,h=.75*a.length,i=a.length,j=0;"="===a[a.length-1]&&(h--,"="===a[a.length-2]&&h--);var k=new ArrayBuffer(h),l=new Uint8Array(k);for(b=0;b>4,l[j++]=(15&e)<<4|f>>2,l[j++]=(3&f)<<6|63&g;return k}}()},function(a,b){(function(b){function c(a){for(var b=0;b0);return b}function d(a){var b=0;for(k=0;k';f=document.createElement(b)}catch(a){f=document.createElement("iframe"),f.name=e.iframeId,f.src="javascript:0"}f.id=e.iframeId,e.form.appendChild(f),e.iframe=f}var e=this;if(!this.form){var f,g=document.createElement("form"),h=document.createElement("textarea"),k=this.iframeId="eio_iframe_"+this.index;g.className="socketio",g.style.position="absolute",g.style.top="-1000px",g.style.left="-1000px",g.target=k,g.method="POST",g.setAttribute("accept-charset","utf-8"),h.name="d",g.appendChild(h),document.body.appendChild(g),this.form=g,this.area=h}this.form.action=this.uri(),d(),a=a.replace(j,"\\\n"),this.area.value=a.replace(i,"\\n");try{this.form.submit()}catch(l){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===e.iframe.readyState&&c()}:this.iframe.onload=c}}).call(b,function(){return this}())},function(a,b,c){(function(b){function d(a){var b=a&&a.forceBase64;b&&(this.supportsBinary=!1),this.perMessageDeflate=a.perMessageDeflate,this.usingBrowserWebSocket=l&&!a.forceNode,this.protocols=a.protocols,this.usingBrowserWebSocket||(n=e),f.call(this,a)}var e,f=c(22),g=c(23),h=c(31),i=c(32),j=c(33),k=c(3)("engine.io-client:websocket"),l=b.WebSocket||b.MozWebSocket;if("undefined"==typeof window)try{e=c(36)}catch(m){}var n=l;n||"undefined"!=typeof window||(n=e),a.exports=d,i(d,f),d.prototype.name="websocket",d.prototype.supportsBinary=!0,d.prototype.doOpen=function(){if(this.check()){var a=this.uri(),b=this.protocols,c={agent:this.agent,perMessageDeflate:this.perMessageDeflate};c.pfx=this.pfx,c.key=this.key,c.passphrase=this.passphrase,c.cert=this.cert,c.ca=this.ca,c.ciphers=this.ciphers,c.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(c.headers=this.extraHeaders),this.localAddress&&(c.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?b?new n(a,b):new n(a):new n(a,b,c)}catch(d){return this.emit("error",d)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},d.prototype.addEventListeners=function(){var a=this;this.ws.onopen=function(){a.onOpen()},this.ws.onclose=function(){a.onClose()},this.ws.onmessage=function(b){a.onData(b.data)},this.ws.onerror=function(b){a.onError("websocket error",b)}},d.prototype.write=function(a){function c(){d.emit("flush"),setTimeout(function(){d.writable=!0,d.emit("drain")},0)}var d=this;this.writable=!1;for(var e=a.length,f=0,h=e;f0&&a.jitter<=1?a.jitter:0,this.attempts=0}a.exports=c,c.prototype.duration=function(){var a=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var b=Math.random(),c=Math.floor(b*this.jitter*a);a=0==(1&Math.floor(10*b))?a-c:a+c}return 0|Math.min(a,this.max)},c.prototype.reset=function(){this.attempts=0},c.prototype.setMin=function(a){this.ms=a},c.prototype.setMax=function(a){this.max=a},c.prototype.setJitter=function(a){this.jitter=a}}])});; ////////////////////////////////////////////////////////////////////////////////////// // // // ███████╗ █████╗ ██╗██╗ ███████╗ ██╗ ██████╗ ██╗███████╗ // // ██╔════╝██╔══██╗██║██║ ██╔════╝ ██║██╔═══██╗ ██║██╔════╝ // // ███████╗███████║██║██║ ███████╗ ██║██║ ██║ ██║███████╗ // // ╚════██║██╔══██║██║██║ ╚════██║ ██║██║ ██║ ██ ██║╚════██║ // // ███████║██║ ██║██║███████╗███████║██╗██║╚██████╔╝██╗╚█████╔╝███████║ // // ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ // // // // ╦╔═╗╦ ╦╔═╗╔═╗╔═╗╦═╗╦╔═╗╔╦╗ ╔═╗╦ ╦╔═╗╔╗╔╔╦╗ ╔═╗╔╦╗╦╔═ // // ║╠═╣╚╗╔╝╠═╣╚═╗║ ╠╦╝║╠═╝ ║ ║ ║ ║║╣ ║║║ ║ ╚═╗ ║║╠╩╗ // // ╚╝╩ ╩ ╚╝ ╩ ╩╚═╝╚═╝╩╚═╩╩ ╩ ╚═╝╩═╝╩╚═╝╝╚╝ ╩ ╚═╝═╩╝╩ ╩ // // ┌─┐┌─┐┬─┐ ┌┐┌┌─┐┌┬┐┌─┐ ┬┌─┐ ┌─┐┌┐┌┌┬┐ ┌┬┐┬ ┬┌─┐ ┌┐ ┬─┐┌─┐┬ ┬┌─┐┌─┐┬─┐ // // ├┤ │ │├┬┘ ││││ │ ││├┤ │└─┐ ├─┤│││ ││ │ ├─┤├┤ ├┴┐├┬┘│ ││││└─┐├┤ ├┬┘ // // └ └─┘┴└─ ┘└┘└─┘─┴┘└─┘o└┘└─┘ ┴ ┴┘└┘─┴┘ ┴ ┴ ┴└─┘ └─┘┴└─└─┘└┴┘└─┘└─┘┴└─ // // // ////////////////////////////////////////////////////////////////////////////////////// /** * sails.io.js * v1.2.1 * ------------------------------------------------------------------------ * JavaScript Client (SDK) for communicating with Sails. * * Note that this script is completely optional, but it is handy if you're * using WebSockets from the browser to talk to your Sails server. * * For tips and documentation, visit: * http://sailsjs.com/documentation/reference/web-sockets/socket-client * ------------------------------------------------------------------------ * * This file allows you to send and receive socket.io messages to & from Sails * by simulating a REST client interface on top of socket.io. It models its API * after the $.ajax pattern from jQuery you might already be familiar with. * * So if you're switching from using AJAX to sockets, instead of: * `$.post( url, [data], [cb] )` * * You would use: * `socket.post( url, [data], [cb] )` */ (function() { // ██████╗ ██████╗ ███╗ ██╗███████╗████████╗ █████╗ ███╗ ██╗████████╗███████╗ // ██╔════╝██╔═══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗████╗ ██║╚══██╔══╝██╔════╝ // ██║ ██║ ██║██╔██╗ ██║███████╗ ██║ ███████║██╔██╗ ██║ ██║ ███████╗ // ██║ ██║ ██║██║╚██╗██║╚════██║ ██║ ██╔══██║██║╚██╗██║ ██║ ╚════██║ // ╚██████╗╚██████╔╝██║ ╚████║███████║ ██║ ██║ ██║██║ ╚████║ ██║ ███████║ // ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝ // /** * Constant containing the names of all available options * for individual sockets. * * @type {Array} */ var SOCKET_OPTIONS = [ 'useCORSRouteToGetCookie', 'url', 'multiplex', 'transports', 'query', 'path', 'headers', 'initialConnectionHeaders', 'reconnection', 'reconnectionAttempts', 'reconnectionDelay', 'reconnectionDelayMax', 'rejectUnauthorized', 'randomizationFactor', 'timeout' ]; /** * Constant containing the names of properties on `io.sails` which * may be configured using HTML attributes on the script tag which * loaded this file. * * @type {Array} * * (this is unused if loading from node.js) */ var CONFIGURABLE_VIA_HTML_ATTR = [ 'autoConnect', 'reconnection', 'environment', 'headers', 'url', 'transports', 'path' ]; /** * Constant containing the names of querystring * parameters sent when connecting any SailsSocket. * * @type {Dictionary} */ var CONNECTION_METADATA_PARAMS = { version: '__sails_io_sdk_version', platform: '__sails_io_sdk_platform', language: '__sails_io_sdk_language' }; /** * Constant containing metadata about the platform, language, and * current version of this SDK. * * @type {Dictionary} */ var SDK_INFO = { version: '1.2.1', // <-- pulled automatically from package.json, do not change! language: 'javascript', platform: (function (){ if (typeof module === 'object' && typeof module.exports !== 'undefined') { return 'node'; } else { return 'browser'; } })() }; // Build `versionString` (a querystring snippet) by // combining SDK_INFO and CONNECTION_METADATA_PARAMS. SDK_INFO.versionString = CONNECTION_METADATA_PARAMS.version + '=' + SDK_INFO.version + '&' + CONNECTION_METADATA_PARAMS.platform + '=' + SDK_INFO.platform + '&' + CONNECTION_METADATA_PARAMS.language + '=' + SDK_INFO.language; // █████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗███╗ ███╗██╗ // ██╔══██╗██╔══██╗██╔════╝██╔═══██╗██╔══██╗██╔══██╗ ██║ ██║╚══██╔══╝████╗ ████║██║ // ███████║██████╔╝███████╗██║ ██║██████╔╝██████╔╝ ███████║ ██║ ██╔████╔██║██║ // ██╔══██║██╔══██╗╚════██║██║ ██║██╔══██╗██╔══██╗ ██╔══██║ ██║ ██║╚██╔╝██║██║ // ██║ ██║██████╔╝███████║╚██████╔╝██║ ██║██████╔╝ ██║ ██║ ██║ ██║ ╚═╝ ██║███████╗ // ╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ // // █████╗ ████████╗████████╗██████╗ ██╗██████╗ ██╗ ██╗████████╗███████╗███████╗ // ██╔══██╗╚══██╔══╝╚══██╔══╝██╔══██╗██║██╔══██╗██║ ██║╚══██╔══╝██╔════╝██╔════╝ // ███████║ ██║ ██║ ██████╔╝██║██████╔╝██║ ██║ ██║ █████╗ ███████╗ // ██╔══██║ ██║ ██║ ██╔══██╗██║██╔══██╗██║ ██║ ██║ ██╔══╝ ╚════██║ // ██║ ██║ ██║ ██║ ██║ ██║██║██████╔╝╚██████╔╝ ██║ ███████╗███████║ // ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝ // // ███████╗██████╗ ██████╗ ███╗ ███╗ ██╗███████╗ ██████╗██████╗ ██╗██████╗ ████████╗██╗ // ██╔════╝██╔══██╗██╔═══██╗████╗ ████║ ██╔╝██╔════╝██╔════╝██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗ // █████╗ ██████╔╝██║ ██║██╔████╔██║ ██╔╝ ███████╗██║ ██████╔╝██║██████╔╝ ██║ ╚██╗ // ██╔══╝ ██╔══██╗██║ ██║██║╚██╔╝██║ ╚██╗ ╚════██║██║ ██╔══██╗██║██╔═══╝ ██║ ██╔╝ // ██║ ██║ ██║╚██████╔╝██║ ╚═╝ ██║ ╚██╗███████║╚██████╗██║ ██║██║██║ ██║ ██╔╝ // ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝ // // // If available, grab the DOM element for the script tag which imported this file. // (skip this if this SDK is being used outside of the DOM, i.e. in a Node process) // // This is used below to parse client-side sails.io.js configuration encoded as // HTML attributes, as well as grabbing hold of the URL from whence the SDK was fetched. var thisScriptTag = (function() { if ( typeof window !== 'object' || typeof window.document !== 'object' || typeof window.document.getElementsByTagName !== 'function' ) { return null; } // Return the URL of the last script loaded (i.e. this one) // (this must run before nextTick; see http://stackoverflow.com/a/2976714/486547) var allScriptsCurrentlyInDOM = window.document.getElementsByTagName('script'); return allScriptsCurrentlyInDOM[allScriptsCurrentlyInDOM.length - 1]; })(); // Variables to contain src URL and other script tag config (for use below). var urlThisScriptWasFetchedFrom = ''; var scriptTagConfig = {}; if (thisScriptTag) { // Save the URL that this script was fetched from. urlThisScriptWasFetchedFrom = thisScriptTag.src; // Now parse the most common client-side configuration settings // from the script tag where they may be encoded as HTML attributes. // // Any configuration which may be provided as an HTML attribute may // also be provided prefixed with `data-`. This is for folks who // need to support browsers that have issues with nonstandard // HTML attributes (or if the idea of using nonstandard HTML attributes // just creeps you out) // // If a `data-` prefixed attr is provided, it takes precedence. // (this is so that if you are already using one of these HTML // attrs for some reason, you can keep it as-is and override // it using `data-`. If you are using the `data-` prefixed version // for some other purpose... well, in that case you'll just have to // configure programmatically using `io.sails` instead.) CONFIGURABLE_VIA_HTML_ATTR.forEach(function (configKey){ scriptTagConfig[configKey] = (function (){ // Support 'data-' prefixed or normal attributes. // (prefixed versions take precedence if provided) var htmlAttrVal = thisScriptTag.getAttribute( 'data-'+configKey ); if (!htmlAttrVal) { htmlAttrVal = thisScriptTag.getAttribute( configKey ); } // The HTML attribute value should always be a string or `null`. // We'll try to parse it as JSON and use that, but worst case fall back // to the default situation of it being a string. if (typeof htmlAttrVal === 'string') { try { return JSON.parse(htmlAttrVal); } catch (e) { return htmlAttrVal; } } // If `null` was returned from getAttribute(), it means that the HTML attribute // was not specified, so we treat it as undefined (which will cause the property // to be removed below) else if (htmlAttrVal === null) { return undefined; } // Any other contingency shouldn't be possible: // - if no quotes are used in the HTML attribute, it still comes in as a string. // - if no RHS is provided for the attribute, it still comes in as "" (empty string) // (but we still handle this with an explicit error just in case--for debugging and support purposes) else throw new Error('sails.io.js :: Unexpected/invalid script tag configuration for `'+configKey+'`: `'+htmlAttrVal+'` (a `'+typeof htmlAttrVal+'`). Should be a string.'); })(); if (scriptTagConfig[configKey] === undefined){ delete scriptTagConfig[configKey]; } }); // Now that they've been parsed, do an extremely lean version of // logical type validation/coercion of provided values. ////////////////////////////////////////////////////////////////// // `autoConnect` if (typeof scriptTagConfig.autoConnect !== 'undefined') { if (scriptTagConfig.autoConnect === '') { // Special case for empty string. It means `true` (see above). scriptTagConfig.autoConnect = true; } else if (typeof scriptTagConfig.autoConnect !== 'boolean') { throw new Error('sails.io.js :: Unexpected/invalid configuration for `autoConnect` provided in script tag: `'+scriptTagConfig.autoConnect+'` (a `'+typeof scriptTagConfig.autoConnect+'`). Should be a boolean.'); } } // `environment` if (typeof scriptTagConfig.environment !== 'undefined') { if (typeof scriptTagConfig.environment !== 'string') { throw new Error('sails.io.js :: Unexpected/invalid configuration for `environment` provided in script tag: `'+scriptTagConfig.environment+'` (a `'+typeof scriptTagConfig.environment+'`). Should be a string.'); } } // `headers` if (typeof scriptTagConfig.headers !== 'undefined') { if (typeof scriptTagConfig.headers !== 'object' || Array.isArray(scriptTagConfig.headers)) { throw new Error('sails.io.js :: Unexpected/invalid configuration for `headers` provided in script tag: `'+scriptTagConfig.headers+'` (a `'+typeof scriptTagConfig.headers+'`). Should be a JSON-compatible dictionary (i.e. `{}`). Don\'t forget those double quotes (""), even on key names! Use single quotes (\'\') to wrap the HTML attribute value; e.g. `headers=\'{"X-Auth": "foo"}\'`'); } } // `url` if (typeof scriptTagConfig.url !== 'undefined') { if (typeof scriptTagConfig.url !== 'string') { throw new Error('sails.io.js :: Unexpected/invalid configuration for `url` provided in script tag: `'+scriptTagConfig.url+'` (a `'+typeof scriptTagConfig.url+'`). Should be a string.'); } } // OTHER `io.sails` options are NOT CURRENTLY SUPPORTED VIA HTML ATTRIBUTES. } // Grab a reference to the global socket.io client (if one is available). // This is used via closure below to determine which `io` to use when the // socket.io client instance (`io`) is augmented to become the Sails client // SDK instance (still `io`). var _existingGlobalSocketIO = (typeof io !== 'undefined') ? io : undefined; ////////////////////////////////////////////////////////////// ///// ///// NOW FOR BUNCHES OF: ///// - PRIVATE FUNCTION DEFINITIONS ///// - CONSTRUCTORS ///// - AND METHODS ///// ////////////////////////////////////////////////////////////// // // ███████╗ █████╗ ██╗██╗ ███████╗ ██╗ ██████╗ ██████╗██╗ ██╗███████╗███╗ ██╗████████╗ // ██╔════╝██╔══██╗██║██║ ██╔════╝ ██║██╔═══██╗ ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ // ███████╗███████║██║██║ ███████╗█████╗██║██║ ██║█████╗██║ ██║ ██║█████╗ ██╔██╗ ██║ ██║ // ╚════██║██╔══██║██║██║ ╚════██║╚════╝██║██║ ██║╚════╝██║ ██║ ██║██╔══╝ ██║╚██╗██║ ██║ // ███████║██║ ██║██║███████╗███████║ ██║╚██████╔╝ ╚██████╗███████╗██║███████╗██║ ╚████║ ██║ // ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ // /** * SailsIOClient() * * Augment the provided Socket.io client object (`io`) with methods for * talking and listening to one or more Sails backend(s). If no `io` was * provided (i.e. in a browser setting), then attempt to use the global. * * This absorbs implicit `io.sails` configuration, sets a timer for * automatically connecting a socket (if `io.sails.autoConnect` is enabled) * and returns the augmented `io`. * * Note: * The automatically-connected socket is exposed as `io.socket`. If this * socket attempts to bind event listeners or send requests before it is * connected, it will be queued up and replayed when the connection is * successfully opened. * * @param {SocketIO} io * @returns {SailsIOClient} [also called `io`] */ function SailsIOClient(_providedSocketIO) { // First, determine which `io` we're augmenting. // // Prefer the passed-in `io` instance, but fall back to the // global one if we've got it. var io; if (_providedSocketIO) { io = _providedSocketIO; } else { io = _existingGlobalSocketIO; } // (note that for readability, we deliberately do not short circuit or use the tertiary operator above) // If a socket.io client (`io`) is not available, none of this will work. if (!io) { // If node: if (SDK_INFO.platform === 'node') { throw new Error('No socket.io client available. When requiring `sails.io.js` from Node.js, a socket.io client (`io`) must be passed in; e.g.:\n```\nvar io = require(\'sails.io.js\')( require(\'socket.io-client\') )\n```\n(see https://github.com/balderdashy/sails.io.js/tree/master/test for more examples)'); } // Otherwise, this is a web browser: else { throw new Error('The Sails socket SDK depends on the socket.io client, but the socket.io global (`io`) was not available when `sails.io.js` loaded. Normally, the socket.io client code is bundled with sails.io.js, so something is a little off. Please check to be sure this version of `sails.io.js` has the minified Socket.io client at the top of the file.'); } } // If the chosen socket.io client (`io`) has ALREADY BEEN AUGMENTED by this SDK, // (i.e. if it already has a `.sails` property) then throw an error. if (io.sails) { // If node: if (SDK_INFO.platform === 'node') { throw new Error('The provided socket.io client (`io`) has already been augmented into a Sails socket SDK instance (it has `io.sails`).'); } // Otherwise, this is a web browser: else { throw new Error('The socket.io client (`io`) has already been augmented into a Sails socket SDK instance. Usually, this means you are bringing `sails.io.js` onto the page more than once.'); } } /** * A little logger for this library to use internally. * Basically just a wrapper around `console.log` with * support for feature-detection. * * @api private * @factory */ function LoggerFactory(options) { options = options || { prefix: true }; // If `console.log` is not accessible, `log` is a noop. if ( typeof console !== 'object' || typeof console.log !== 'function' || typeof console.log.bind !== 'function' ) { return function noop() {}; } return function log() { var args = Array.prototype.slice.call(arguments); // All logs are disabled when `io.sails.environment = 'production'`. if (io.sails.environment === 'production') return; // Add prefix to log messages (unless disabled) var PREFIX = ''; if (options.prefix) { args.unshift(PREFIX); } // Call wrapped logger console.log .bind(console) .apply(this, args); }; }// // Create a private logger instance var consolog = LoggerFactory(); consolog.noPrefix = LoggerFactory({ prefix: false }); /** * What is the `requestQueue`? * * The request queue is used to simplify app-level connection logic-- * i.e. so you don't have to wait for the socket to be connected * to start trying to synchronize data. * * @api private * @param {SailsSocket} socket */ function runRequestQueue (socket) { var queue = socket.requestQueue; if (!queue) return; for (var i in queue) { // Double-check that `queue[i]` will not // inadvertently discover extra properties attached to the Object // and/or Array prototype by other libraries/frameworks/tools. // (e.g. Ember does this. See https://github.com/balderdashy/sails.io.js/pull/5) var isSafeToDereference = ({}).hasOwnProperty.call(queue, i); if (isSafeToDereference) { // Get the arguments that were originally made to the "request" method var requestArgs = queue[i]; // Call the request method again in the context of the socket, with the original args socket.request.apply(socket, requestArgs); } } // Now empty the queue to remove it as a source of additional complexity. socket.requestQueue = null; } /** * Send a JSONP request. * * @param {Object} opts [optional] * @param {Function} cb * @return {XMLHttpRequest} */ function jsonp(opts, cb) { opts = opts || {}; if (typeof window === 'undefined') { // FUTURE: refactor node usage to live in here return cb(); } var scriptEl = document.createElement('script'); window._sailsIoJSConnect = function(response) { // In rare circumstances our script may have been vaporised. // Remove it, but only if it still exists // https://github.com/balderdashy/sails.io.js/issues/92 if (scriptEl && scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } cb(response); }; scriptEl.src = opts.url; document.getElementsByTagName('head')[0].appendChild(scriptEl); } // ██╗███████╗ ██████╗ ███╗ ██╗ ██╗ ██╗███████╗██████╗ ███████╗ ██████╗ ██████╗██╗ ██╗███████╗████████╗ // ██║██╔════╝██╔═══██╗████╗ ██║ ██║ ██║██╔════╝██╔══██╗██╔════╝██╔═══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ // ██║███████╗██║ ██║██╔██╗ ██║█████╗██║ █╗ ██║█████╗ ██████╔╝███████╗██║ ██║██║ █████╔╝ █████╗ ██║ // ██ ██║╚════██║██║ ██║██║╚██╗██║╚════╝██║███╗██║██╔══╝ ██╔══██╗╚════██║██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ // ╚█████╔╝███████║╚██████╔╝██║ ╚████║ ╚███╔███╔╝███████╗██████╔╝███████║╚██████╔╝╚██████╗██║ ██╗███████╗ ██║ // ╚════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══╝╚══╝ ╚══════╝╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ // // ██████╗ ███████╗███████╗██████╗ ██████╗ ███╗ ██╗███████╗███████╗ ██╗ ██╗██╗ ██╗██████╗ ██╗ // ██╔══██╗██╔════╝██╔════╝██╔══██╗██╔═══██╗████╗ ██║██╔════╝██╔════╝ ██╔╝ ██║██║ ██║██╔══██╗╚██╗ // ██████╔╝█████╗ ███████╗██████╔╝██║ ██║██╔██╗ ██║███████╗█████╗ ██║ ██║██║ █╗ ██║██████╔╝ ██║ // ██╔══██╗██╔══╝ ╚════██║██╔═══╝ ██║ ██║██║╚██╗██║╚════██║██╔══╝ ██║ ██ ██║██║███╗██║██╔══██╗ ██║ // ██║ ██║███████╗███████║██║ ╚██████╔╝██║ ╚████║███████║███████╗ ╚██╗╚█████╔╝╚███╔███╔╝██║ ██║██╔╝ // ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝ ╚═╝ ╚════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ // /** * The JWR (JSON WebSocket Response) received from a Sails server. * * @api public * @param {Object} responseCtx * => :body * => :statusCode * => :headers * * @constructor */ function JWR(responseCtx) { this.body = responseCtx.body; this.headers = responseCtx.headers || {}; this.statusCode = (typeof responseCtx.statusCode === 'undefined') ? 200 : responseCtx.statusCode; // FUTURE: Replace this typeof short-circuit with an assertion (statusCode should always be set) if (this.statusCode < 200 || this.statusCode >= 400) { // Determine the appropriate error message. var msg; if (this.statusCode === 0) { msg = 'The socket request failed.'; } else { msg = 'Server responded with a ' + this.statusCode + ' status code'; msg += ':\n```\n' + JSON.stringify(this.body, null, 2) + '\n```'; // (^^Note that we should always be able to rely on socket.io to give us // non-circular data here, so we don't have to worry about wrapping the // above in a try...catch) } // Now build and attach Error instance. this.error = new Error(msg); } } JWR.prototype.toString = function() { return '[ResponseFromSails]' + ' -- ' + 'Status: ' + this.statusCode + ' -- ' + 'Headers: ' + this.headers + ' -- ' + 'Body: ' + this.body; }; JWR.prototype.toPOJO = function() { return { body: this.body, headers: this.headers, statusCode: this.statusCode }; }; JWR.prototype.pipe = function() { // FUTURE: look at substack's stuff return new Error('Client-side streaming support not implemented yet.'); }; // ███████╗███╗ ███╗██╗████████╗███████╗██████╗ ██████╗ ███╗ ███╗ ██╗██╗ // ██╔════╝████╗ ████║██║╚══██╔══╝██╔════╝██╔══██╗██╔═══██╗████╗ ████║██╔╝╚██╗ // █████╗ ██╔████╔██║██║ ██║ █████╗ ██████╔╝██║ ██║██╔████╔██║██║ ██║ // ██╔══╝ ██║╚██╔╝██║██║ ██║ ██╔══╝ ██╔══██╗██║ ██║██║╚██╔╝██║██║ ██║ // ███████╗███████╗██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║╚██████╔╝██║ ╚═╝ ██║╚██╗██╔╝ // ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ // /** * @api private * @param {SailsSocket} socket [description] * @param {Object} requestCtx [description] */ function _emitFrom(socket, requestCtx) { if (!socket._raw) { throw new Error('Failed to emit from socket- raw SIO socket is missing.'); } // Since callback is embedded in requestCtx, // retrieve it and delete the key before continuing. var cb = requestCtx.cb; delete requestCtx.cb; // Name of the appropriate socket.io listener on the server // ( === the request method or "verb", e.g. 'get', 'post', 'put', etc. ) var sailsEndpoint = requestCtx.method; socket._raw.emit(sailsEndpoint, requestCtx, function serverResponded(responseCtx) { // Send back (emulatedHTTPBody, jsonWebSocketResponse) if (cb && !requestCtx.calledCb) { cb(responseCtx.body, new JWR(responseCtx)); // Set flag indicating that callback was called, to avoid duplicate calls. requestCtx.calledCb = true; // Remove the callback from the list. socket._responseCbs.splice(socket._responseCbs.indexOf(cb), 1); // Remove the context from the list. socket._requestCtxs.splice(socket._requestCtxs.indexOf(requestCtx), 1); } }); } // ███████╗ █████╗ ██╗██╗ ███████╗███████╗ ██████╗ ██████╗██╗ ██╗███████╗████████╗ // ██╔════╝██╔══██╗██║██║ ██╔════╝██╔════╝██╔═══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ // ███████╗███████║██║██║ ███████╗███████╗██║ ██║██║ █████╔╝ █████╗ ██║ // ╚════██║██╔══██║██║██║ ╚════██║╚════██║██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ // ███████║██║ ██║██║███████╗███████║███████║╚██████╔╝╚██████╗██║ ██╗███████╗ ██║ // ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ // /** * SailsSocket * * A wrapper for an underlying Socket instance that communicates directly * to the Socket.io server running inside of Sails. * * If no `socket` option is provied, SailsSocket will function as a mock. It will queue socket * requests and event handler bindings, replaying them when the raw underlying socket actually * connects. This is handy when we don't necessarily have the valid configuration to know * WHICH SERVER to talk to yet, etc. It is also used by `io.socket` for your convenience. * * @constructor * @api private * * ---------------------------------------------------------------------- * Note: This constructor should not be used directly. To obtain a `SailsSocket` * instance of your very own, run: * ``` * var mySocket = io.sails.connect(); * ``` * ---------------------------------------------------------------------- */ function SailsSocket (opts){ var self = this; opts = opts||{}; // Initialize private properties self._isConnecting = false; self._mightBeAboutToAutoConnect = false; // Set up connection options so that they can only be changed when socket is disconnected. var _opts = {}; SOCKET_OPTIONS.forEach(function(option) { // Okay to change global headers while socket is connected if (option == 'headers') {return;} Object.defineProperty(self, option, { get: function() { if (option == 'url') { return _opts[option] || (self._raw && self._raw.io && self._raw.io.uri); } return _opts[option]; }, set: function(value) { // Don't allow value to be changed while socket is connected if (self.isConnected() && io.sails.strict !== false && value != _opts[option]) { throw new Error('Cannot change value of `' + option + '` while socket is connected.'); } // If socket is attempting to reconnect, stop it. if (self._raw && self._raw.io && self._raw.io.reconnecting && !self._raw.io.skipReconnect) { self._raw.io.skipReconnect = true; consolog('Stopping reconnect; use .reconnect() to connect socket after changing options.'); } _opts[option] = value; } }); }); // Absorb opts into SailsSocket instance // See http://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties // for description of options SOCKET_OPTIONS.forEach(function(option) { self[option] = opts[option]; }); // Set up "eventQueue" to hold event handlers which have not been set on the actual raw socket yet. self.eventQueue = {}; // Listen for special `parseError` event sent from sockets hook on the backend // if an error occurs but a valid callback was not received from the client // (i.e. so the server had no other way to send back the error information) self.on('sails:parseError', function (err){ consolog('Sails encountered an error parsing a socket message sent from this client, and did not have access to a callback function to respond with.'); consolog('Error details:',err); }); // FUTURE: // Listen for a special private message on any connected that allows the server // to set the environment (giving us 100% certainty that we guessed right) // However, note that the `console.log`s called before and after connection // are still forced to rely on our existing heuristics (to disable, tack #production // onto the URL used to fetch this file.) }// /** * `SailsSocket.prototype._connect()` * * Begin connecting this socket to the server. * * @api private */ SailsSocket.prototype._connect = function (){ var self = this; self._isConnecting = true; // Apply `io.sails` config as defaults // (now that at least one tick has elapsed) // See http://sailsjs.com/documentation/reference/web-sockets/socket-client/sails-socket/properties // for description of options and default values SOCKET_OPTIONS.forEach(function(option) { if ('undefined' == typeof self[option]) { self[option] = io.sails[option]; } }); // Headers that will be sent with the initial request to /socket.io (Node.js only) self.extraHeaders = self.initialConnectionHeaders || {}; // For browser usage (currently works with "polling" transport only) self.transportOptions = self.transportOptions || {}; self.transports.forEach(function(transport) { self.transportOptions[transport] = self.transportOptions[transport] || {}; self.transportOptions[transport].extraHeaders = self.initialConnectionHeaders || {}; }); // Log a warning if non-Node.js platform attempts to use `initialConnectionHeaders` for anything other than `polling`. if (self.initialConnectionHeaders && SDK_INFO.platform !== 'node' && self.transports.indexOf('polling') === -1 || self.transports.length > 1) { if (typeof console === 'object' && typeof console.warn === 'function') { console.warn('When running in browser, `initialConnectionHeaders` option is only available for the `polling` transport.'); } } // Ensure URL has no trailing slash self.url = self.url ? self.url.replace(/(\/)$/, '') : undefined; // Mix the current SDK version into the query string in // the connection request to the server: if (typeof self.query === 'string') { // (If provided as a string, trim leading question mark, // just in case one was provided.) self.query = self.query.replace(/^\?/, ''); self.query += '&' + SDK_INFO.versionString; } else if (self.query && typeof self.query === 'object') { throw new Error('`query` setting does not currently support configuration as a dictionary (`{}`). Instead, it must be specified as a string like `foo=89&bar=hi`'); } else if (!self.query) { self.query = SDK_INFO.versionString; } else { throw new Error('Unexpected data type provided for `query` setting: '+self.query); } // Determine whether this is a cross-origin socket by examining the // hostname and port on the `window.location` object. If it's cross-origin, // we'll attempt to get a cookie for the domain so that a Sails session can // be established. var isXOrigin = (function (){ // If `window` doesn't exist (i.e. being used from Node.js), then // we won't bother attempting to get a cookie. If you're using sockets // from Node.js and find you need to share a session between multiple // socket connections, you'll need to make an HTTP request to the /__getcookie // endpoint of the Sails server (or any endpoint that returns a set-cookie header) // and then use the cookie value in the `initialConnectionHeaders` option to // io.sails.connect() if (typeof window === 'undefined' || typeof window.location === 'undefined') { return false; } // If `self.url` (aka "target") is falsy, then we don't need to worry about it. if (typeof self.url !== 'string') { return false; } // Get information about the "target" (`self.url`) var targetProtocol = (function (){ try { targetProtocol = self.url.match(/^([a-z]+:\/\/)/i)[1].toLowerCase(); } catch (e) {} targetProtocol = targetProtocol || 'http://'; return targetProtocol; })(); var isTargetSSL = !!self.url.match('^https'); var targetPort = (function (){ try { return self.url.match(/^[a-z]+:\/\/[^:]*:([0-9]*)/i)[1]; } catch (e){} return isTargetSSL ? '443' : '80'; })(); var targetAfterProtocol = self.url.replace(/^([a-z]+:\/\/)/i, ''); // If target protocol is different than the actual protocol, // then we'll consider this cross-origin. if (targetProtocol.replace(/[:\/]/g, '') !== window.location.protocol.replace(/[:\/]/g,'')) { return true; } // If target hostname is different than actual hostname, we'll consider this cross-origin. var hasSameHostname = targetAfterProtocol.search(window.location.hostname) === 0; if (!hasSameHostname) { return true; } // If no actual port is explicitly set on the `window.location` object, // we'll assume either 80 or 443. var isLocationSSL = window.location.protocol.match(/https/i); var locationPort = (window.location.port+'') || (isLocationSSL ? '443' : '80'); // Finally, if ports don't match, we'll consider this cross-origin. if (targetPort !== locationPort) { return true; } // Otherwise, it's the same origin. return false; })(); // Prepare to start connecting the socket (function selfInvoking (cb){ // If this is an attempt at a cross-origin or cross-port // socket connection via a browswe, send a JSONP request // first to ensure that a valid cookie is available. // This can be disabled by setting `io.sails.useCORSRouteToGetCookie` // to false. // // Otherwise, skip the stuff below. // if (!(self.useCORSRouteToGetCookie && isXOrigin)) { return cb(); } // Figure out the x-origin CORS route // (Sails provides a default) var xOriginCookieURL = self.url; if (typeof self.useCORSRouteToGetCookie === 'string') { xOriginCookieURL += self.useCORSRouteToGetCookie; } else { xOriginCookieURL += '/__getcookie'; } // Make the AJAX request (CORS) jsonp({ url: xOriginCookieURL, method: 'GET' }, cb); })(function goAheadAndActuallyConnect() { // Now that we're ready to connect, create a raw underlying Socket // using Socket.io and save it as `_raw` (this will start it connecting) self._raw = io(self.url, self); // If the low-level transport throws an error _while connecting_, then set the _isConnecting flag // to false (since we're no longer connecting with any chance of success anyway). // Also, in this case (and in dev mode only) log a helpful message. self._raw.io.engine.transport.on('error', function(err){ if (!self._isConnecting) { return; } self._isConnecting = false; // Track this timestamp for use in reconnection messages // (only relevant if reconnection is enabled.) self.connectionErrorTimestamp = (new Date()).getTime(); // Development-only message: consolog('===================================='); consolog('The socket was unable to connect.'); consolog('The server may be offline, or the'); consolog('socket may have failed authorization'); consolog('based on its origin or other factors.'); consolog('You may want to check the values of'); consolog('`sails.config.sockets.onlyAllowOrigins`'); consolog('or (more rarely) `sails.config.sockets.beforeConnect`'); consolog('in your app.'); consolog('More info: https://sailsjs.com/config/sockets'); consolog('For help: https://sailsjs.com/support'); consolog(''); consolog('Technical details:'); consolog(err); consolog('===================================='); }); // Replay event bindings from the eager socket self.replay(); /** * 'connect' event is triggered when the socket establishes a connection * successfully. */ self.on('connect', function socketConnected() { self._isConnecting = false; consolog.noPrefix( '\n' + '\n' + // ' |> ' + '\n' + // ' \\___/ '+️ // '\n'+ ' |> Now connected to '+(self.url ? self.url : 'Sails')+'.' + '\n' + '\\___/ For help, see: http://bit.ly/2q0QDpf' + '\n' + ' (using sails.io.js '+io.sails.sdk.platform+' SDK @v'+io.sails.sdk.version+')'+ '\n' + ' Connected at: '+(new Date())+'\n'+ '\n'+ '\n'+ // '\n'+ '' // ' ⚓︎ (development mode)' // 'e.g. to send a GET request to Sails via WebSockets, run:'+ '\n' + // '`io.socket.get("/foo", function serverRespondedWith (body, jwr) { console.log(body); })`'+ '\n' + ); }); self.on('disconnect', function() { // Get a timestamp of when the disconnect was detected. self.connectionLostTimestamp = (new Date()).getTime(); // Get a shallow clone of the internal array of response callbacks, in case any of the callbacks mutate it. var responseCbs = [].concat(self._responseCbs || []); // Wipe the internal array of response callbacks before executing them, in case a callback happens to add // a new request to the queue. self._responseCbs = []; // Do the same for the internal request context list. var requestCtxs = [].concat(self._requestCtxs || []); self._requestCtxs = []; // Loop through the callbacks for all in-progress requests, and call them each with an error indicating the disconnect. if (responseCbs.length) { responseCbs.forEach(function(responseCb) { responseCb(new Error('The socket disconnected before the request completed.'), { body: null, statusCode: 0, headers: {} }); }); } // If there is a list of request contexts, indicate that their callbacks have been // called and then wipe the list. This prevents errors in the edge case of a response // somehow coming back after the socket reconnects. if (requestCtxs.length) { requestCtxs.forEach(function(requestCtx) { requestCtx.calledCb = true; }); } consolog('===================================='); consolog('Socket was disconnected from Sails.'); consolog('Usually, this is due to one of the following reasons:' + '\n' + ' -> the server ' + (self.url ? self.url + ' ' : '') + 'was taken down' + '\n' + ' -> your browser lost internet connectivity'); consolog('===================================='); }); self.on('reconnecting', function(numAttempts) { consolog( '\n'+ ' Socket is trying to reconnect to '+(self.url ? self.url : 'Sails')+'...\n'+ '_-|>_- (attempt #' + numAttempts + ')'+'\n'+ '\n' ); }); self.on('reconnect', function(transport, numAttempts) { if (!self._isConnecting) { self.on('connect', runRequestQueue.bind(self, self)); } var msSinceLastOffline; var numSecsOffline; if (self.connectionLostTimestamp){ msSinceLastOffline = ((new Date()).getTime() - self.connectionLostTimestamp); numSecsOffline = (msSinceLastOffline / 1000); } else if (self.connectionErrorTimestamp) { msSinceLastOffline = ((new Date()).getTime() - self.connectionErrorTimestamp); numSecsOffline = (msSinceLastOffline / 1000); } else { msSinceLastOffline = '???'; numSecsOffline = '???'; } consolog( '\n'+ ' |> Socket reconnected successfully after'+'\n'+ '\\___/ being offline at least ' + numSecsOffline + ' seconds.'+'\n'+ '\n' ); }); // 'error' event is triggered if connection can not be established. // (usually because of a failed authorization, which is in turn // usually due to a missing or invalid cookie) self.on('error', function failedToConnect(err) { self._isConnecting = false; //////////////////////////////////////////////////////////////////////////////////// // Note: // In the future, we could provide a separate event for when a socket cannot connect // due to a failed `beforeConnect` (aka "authorization" if you're old school). // this could probably be implemented by emitting a special event from the server. //////////////////////////////////////////////////////////////////////////////////// consolog( 'Failed to connect socket (possibly due to failed `beforeConnect` on server)', 'Error:', err ); }); }); }; /** * Reconnect the underlying socket. * * @api public */ SailsSocket.prototype.reconnect = function (){ if (this._isConnecting) { throw new Error('Cannot connect- socket is already connecting'); } if (this.isConnected()) { throw new Error('Cannot connect- socket is already connected'); } return this._connect(); }; /** * Disconnect the underlying socket. * * @api public */ SailsSocket.prototype.disconnect = function (){ this._isConnecting = false; if (!this.isConnected()) { throw new Error('Cannot disconnect- socket is already disconnected'); } return this._raw.disconnect(); }; /** * isConnected * * @return {Boolean} whether the socket is connected and able to * communicate w/ the server. */ SailsSocket.prototype.isConnected = function () { if (!this._raw) { return false; } return !!this._raw.connected; }; /** * isConnecting * * @return {Boolean} whether the socket is in the process of connecting * to the server. */ SailsSocket.prototype.isConnecting = function () { return this._isConnecting; }; /** * isConnecting * * @return {Boolean} flag that is `true` after a SailsSocket instance is * initialized but before one tick of the event loop * has passed (so that it hasn't attempted to connect * yet, if autoConnect ends up being configured `true`) */ SailsSocket.prototype.mightBeAboutToAutoConnect = function() { return this._mightBeAboutToAutoConnect; }; /** * [replay description] * @return {[type]} [description] */ SailsSocket.prototype.replay = function (){ var self = this; // Pass events and a reference to the request queue // off to the self._raw for consumption for (var evName in self.eventQueue) { for (var i in self.eventQueue[evName]) { self._raw.on(evName, self.eventQueue[evName][i]); } } // Bind a one-time function to run the request queue // when the self._raw connects. if ( !self.isConnected() ) { self._raw.once('connect', runRequestQueue.bind(self, self)); } // Or run it immediately if self._raw is already connected else { runRequestQueue(self); } return self; }; /** * Chainable method to bind an event to the socket. * * @param {String} evName [event name] * @param {Function} fn [event handler function] * @return {SailsSocket} */ SailsSocket.prototype.on = function (evName, fn){ // Bind the event to the raw underlying socket if possible. if (this._raw) { this._raw.on(evName, fn); return this; } // Otherwise queue the event binding. if (!this.eventQueue[evName]) { this.eventQueue[evName] = [fn]; } else { this.eventQueue[evName].push(fn); } return this; }; /** * Chainable method to unbind an event from the socket. * * @param {String} evName [event name] * @param {Function} fn [event handler function] * @return {SailsSocket} */ SailsSocket.prototype.off = function (evName, fn){ // Bind the event to the raw underlying socket if possible. if (this._raw) { this._raw.off(evName, fn); return this; } // Otherwise queue the event binding. if (this.eventQueue[evName] && this.eventQueue[evName].indexOf(fn) > -1) { this.eventQueue[evName].splice(this.eventQueue[evName].indexOf(fn), 1); } return this; }; /** * Chainable method to unbind all events from the socket. * * @return {SailsSocket} */ SailsSocket.prototype.removeAllListeners = function (){ // Bind the event to the raw underlying socket if possible. if (this._raw) { this._raw.removeAllListeners(); return this; } // Otherwise queue the event binding. this.eventQueue = {}; return this; }; /** * Simulate a GET request to sails * e.g. * `socket.get('/user/3', Stats.populate)` * * @api public * @param {String} url :: destination URL * @param {Object} data :: parameters to send with the request [optional] * @param {Function} cb :: callback function to call when finished [optional] */ SailsSocket.prototype.get = function(url, data, cb) { // `data` is optional if (typeof data === 'function') { cb = data; data = {}; } return this.request({ method: 'get', params: data, url: url }, cb); }; /** * Simulate a POST request to sails * e.g. * `socket.post('/event', newMeeting, $spinner.hide)` * * @api public * @param {String} url :: destination URL * @param {Object} data :: parameters to send with the request [optional] * @param {Function} cb :: callback function to call when finished [optional] */ SailsSocket.prototype.post = function(url, data, cb) { // `data` is optional if (typeof data === 'function') { cb = data; data = {}; } return this.request({ method: 'post', data: data, url: url }, cb); }; /** * Simulate a PUT request to sails * e.g. * `socket.post('/event/3', changedFields, $spinner.hide)` * * @api public * @param {String} url :: destination URL * @param {Object} data :: parameters to send with the request [optional] * @param {Function} cb :: callback function to call when finished [optional] */ SailsSocket.prototype.put = function(url, data, cb) { // `data` is optional if (typeof data === 'function') { cb = data; data = {}; } return this.request({ method: 'put', params: data, url: url }, cb); }; /** * Simulate a PATCH request to sails * e.g. * `socket.patch('/event/3', changedFields, $spinner.hide)` * * @api public * @param {String} url :: destination URL * @param {Object} data :: parameters to send with the request [optional] * @param {Function} cb :: callback function to call when finished [optional] */ SailsSocket.prototype.patch = function(url, data, cb) { // `data` is optional if (typeof data === 'function') { cb = data; data = {}; } return this.request({ method: 'patch', params: data, url: url }, cb); }; /** * Simulate a DELETE request to sails * e.g. * `socket.delete('/event', $spinner.hide)` * * @api public * @param {String} url :: destination URL * @param {Object} data :: parameters to send with the request [optional] * @param {Function} cb :: callback function to call when finished [optional] */ SailsSocket.prototype['delete'] = function(url, data, cb) { // `data` is optional if (typeof data === 'function') { cb = data; data = {}; } return this.request({ method: 'delete', params: data, url: url }, cb); }; /** * Simulate an HTTP request to sails * e.g. * ``` * socket.request({ * url:'/user', * params: {}, * method: 'POST', * headers: {} * }, function (responseBody, JWR) { * // ... * }); * ``` * * @api public * @option {String} url :: destination URL * @option {Object} params :: parameters to send with the request [optional] * @option {Object} headers:: headers to send with the request [optional] * @option {Function} cb :: callback function to call when finished [optional] * @option {String} method :: HTTP request method [optional] */ SailsSocket.prototype.request = function(options, cb) { var usage = 'Usage:\n'+ 'socket.request( options, [fnToCallWhenComplete] )\n\n'+ 'options.url :: e.g. "/foo/bar"'+'\n'+ 'options.method :: e.g. "get", "post", "put", or "delete", etc.'+'\n'+ 'options.params :: e.g. { emailAddress: "mike@example.com" }'+'\n'+ 'options.headers :: e.g. { "x-my-custom-header": "some string" }'; // Old usage: // var usage = 'Usage:\n socket.'+(options.method||'request')+'('+ // ' destinationURL, [dataToSend], [fnToCallWhenComplete] )'; // Validate options and callback if (typeof cb !== 'undefined' && typeof cb !== 'function') { throw new Error('Invalid callback function!\n' + usage); } if (typeof options !== 'object' || typeof options.url !== 'string') { throw new Error('Invalid or missing URL!\n' + usage); } if (options.method && typeof options.method !== 'string') { throw new Error('Invalid `method` provided (should be a string like "post" or "put")\n' + usage); } if (options.headers && typeof options.headers !== 'object') { throw new Error('Invalid `headers` provided (should be a dictionary with string values)\n' + usage); } if (options.params && typeof options.params !== 'object') { throw new Error('Invalid `params` provided (should be a dictionary with JSON-serializable values)\n' + usage); } if (options.data && typeof options.data !== 'object') { throw new Error('Invalid `data` provided (should be a dictionary with JSON-serializable values)\n' + usage); } // Accept either `params` or `data` for backwards compatibility (but not both!) if (options.data && options.params) { throw new Error('Cannot specify both `params` and `data`! They are aliases of each other.\n' + usage); } else if (options.data) { options.params = options.data; delete options.data; } // If this socket is not connected yet, queue up this request // instead of sending it. // (so it can be replayed when the socket comes online.) if ( ! this.isConnected() ) { // If no queue array exists for this socket yet, create it. this.requestQueue = this.requestQueue || []; this.requestQueue.push([options, cb]); return; } // Otherwise, our socket is connected, so continue prepping // the request. // Default headers to an empty object options.headers = options.headers || {}; // Build a simulated request object // (and sanitize/marshal options along the way) var requestCtx = { method: (options.method || 'get').toLowerCase(), headers: options.headers, data: options.params || options.data || {}, // Remove trailing slashes and spaces to make packets smaller. url: options.url.replace(/^(.+)\/*\s*$/, '$1'), cb: cb }; // Get a reference to the callback list, or create a new one. this._responseCbs = this._responseCbs || []; // Get a reference to the request context list, or create a new one. this._requestCtxs = this._requestCtxs || []; // Add this callback to the list. If the socket disconnects, we'll call // each cb in the list with an error and reset the list. Otherwise the // cb will be removed from the list when the server responds. // Also add the request context to the list. It will be removed once // the response comes back, or if the socket disconnects. if (cb) { this._responseCbs.push(cb); this._requestCtxs.push(requestCtx); } // Merge global headers in, if there are any. if (this.headers && 'object' === typeof this.headers) { for (var header in this.headers) { if (!options.headers.hasOwnProperty(header)) { options.headers[header] = this.headers[header]; } } } // Send the request. _emitFrom(this, requestCtx); }; /** * Socket.prototype._request * * Simulate HTTP over Socket.io. * * @api private * @param {[type]} options [description] * @param {Function} cb [description] */ SailsSocket.prototype._request = function(options, cb) { throw new Error('`_request()` was a private API deprecated as of v0.11 of the sails.io.js client. Use `.request()` instead.'); }; // ██╗ ██████╗ ███████╗ █████╗ ██╗██╗ ███████╗ // ██║██╔═══██╗ ██╔════╝██╔══██╗██║██║ ██╔════╝ // ██║██║ ██║ ███████╗███████║██║██║ ███████╗ // ██║██║ ██║ ╚════██║██╔══██║██║██║ ╚════██║ // ██║╚██████╔╝██╗███████║██║ ██║██║███████╗███████║ // ╚═╝ ╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝ // // Set an `io.sails` object that may be used for configuration before the // first socket connects (i.e. to allow auto-connect behavior to be // prevented by setting `io.sails.autoConnect` in an inline script // directly after the script tag which loaded this file). // ┌─┐┌─┐┌┬┐ ┬ ┬┌─┐ ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗╔═╗ ┌─┐┌─┐┬─┐ ┬┌─┐ ┌─┐┌─┐┬┬ ┌─┐ // └─┐├┤ │ │ │├─┘ ║║║╣ ╠╣ ╠═╣║ ║║ ║ ╚═╗ ├┤ │ │├┬┘ ││ │ └─┐├─┤││ └─┐ // └─┘└─┘ ┴ └─┘┴ ═╩╝╚═╝╚ ╩ ╩╚═╝╩═╝╩ ╚═╝ └ └─┘┴└─ ┴└─┘o└─┘┴ ┴┴┴─┘└─┘ io.sails = { // Whether to automatically connect a socket and save it as `io.socket`. autoConnect: true, // Whether to automatically try to reconnect after connection is lost reconnection: false, // The route (path) to hit to get a x-origin (CORS) cookie // (or true to use the default: '/__getcookie') useCORSRouteToGetCookie: true, // The environment we're running in. // (logs are not displayed when this is set to 'production') // // Defaults to "development" unless this script was fetched from a URL // that ends in `*.min.js` or '#production', or if the conventional // `SAILS_LOCALS` global is set with an `_environment` of "production" // or "staging". (This setting may also be manually overridden.) environment: ( urlThisScriptWasFetchedFrom.match(/(\#production|\.min\.js)/g) || ( typeof window === 'object' && window && typeof window.SAILS_LOCALS === 'object' && window.SAILS_LOCALS && (window.SAILS_LOCALS._environment === 'staging' || window.SAILS_LOCALS._environment === 'production') ) )? 'production' : 'development', // The version of this sails.io.js client SDK sdk: SDK_INFO, // Transports to use when communicating with the server, in the order they will be tried transports: ['websocket'] }; // ┌─┐─┐ ┬┌┬┐┌─┐┌┐┌┌┬┐ ┬┌─┐ ┌─┐┌─┐┬┬ ┌─┐ ┌┬┐┌─┐┌─┐┌─┐┬ ┬┬ ┌┬┐┌─┐ // ├┤ ┌┴┬┘ │ ├┤ │││ ││ ││ │ └─┐├─┤││ └─┐ ││├┤ ├┤ ├─┤│ ││ │ └─┐ // └─┘┴ └─ ┴ └─┘┘└┘─┴┘ ┴└─┘o└─┘┴ ┴┴┴─┘└─┘ ─┴┘└─┘└ ┴ ┴└─┘┴─┘┴ └─┘ // ┬ ┬┬┌┬┐┬ ┬ ┌┬┐┬ ┬┌─┐ ╦ ╦╔╦╗╔╦╗╦ ╔═╗╔╦╗╔╦╗╦═╗╦╔╗ ╦ ╦╔╦╗╔═╗╔═╗ // ││││ │ ├─┤ │ ├─┤├┤ ╠═╣ ║ ║║║║ ╠═╣ ║ ║ ╠╦╝║╠╩╗║ ║ ║ ║╣ ╚═╗ // └┴┘┴ ┴ ┴ ┴ ┴ ┴ ┴└─┘ ╩ ╩ ╩ ╩ ╩╩═╝ ╩ ╩ ╩ ╩ ╩╚═╩╚═╝╚═╝ ╩ ╚═╝╚═╝ // ┌─┐┬─┐┌─┐┌┬┐ ┌┬┐┬ ┬┌─┐ ┌─┐┌─┐┬─┐┬┌─┐┌┬┐ ┌┬┐┌─┐┌─┐ // ├┤ ├┬┘│ ││││ │ ├─┤├┤ └─┐│ ├┬┘│├─┘ │ │ ├─┤│ ┬ // └ ┴└─└─┘┴ ┴ ┴ ┴ ┴└─┘ └─┘└─┘┴└─┴┴ ┴ ┴ ┴ ┴└─┘ // // Now fold in config provided as HTML attributes on the script tag: // (note that if `io.sails.*` is changed after this script, those changes // will still take precedence) CONFIGURABLE_VIA_HTML_ATTR.forEach(function (configKey){ if (typeof scriptTagConfig[configKey] !== 'undefined') { io.sails[configKey] = scriptTagConfig[configKey]; } }); ////////////////////////////////////////////////////////////////////////////// // Note that the new HTML attribute configuration style may eventually // completely replace the original approach of setting `io.sails` properties, // since the new strategy is easier to reason about. Also, it would allow us // to remove the timeout below someday. ////////////////////////////////////////////////////////////////////////////// // ┬┌─┐ ┌─┐┌─┐┬┬ ┌─┐ ╔═╗╔═╗╔╗╔╔╗╔╔═╗╔═╗╔╦╗ / \ // ││ │ └─┐├─┤││ └─┐ ║ ║ ║║║║║║║║╣ ║ ║ / / // ┴└─┘o└─┘┴ ┴┴┴─┘└─┘o╚═╝╚═╝╝╚╝╝╚╝╚═╝╚═╝ ╩ \ / /** * Add `io.sails.connect` function as a wrapper for the built-in `io()` aka `io.connect()` * method, returning a SailsSocket. This special function respects the configured io.sails * connection URL, as well as sending other identifying information (most importantly, the * current version of this SDK). * * @param {String} url [optional] * @param {Object} opts [optional] * @return {Socket} */ io.sails.connect = function(url, opts) { // Make URL optional if ('object' === typeof url) { opts = url; url = null; } // Default opts to empty object opts = opts || {}; // If explicit connection url is specified, save it to options opts.url = url || opts.url || undefined; // Instantiate and return a new SailsSocket- and try to connect immediately. var socket = new SailsSocket(opts); socket._connect(); return socket; }; // ██╗ ██████╗ ███████╗ ██████╗ ██████╗██╗ ██╗███████╗████████╗ // ██║██╔═══██╗ ██╔════╝██╔═══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ // ██║██║ ██║ ███████╗██║ ██║██║ █████╔╝ █████╗ ██║ // ██║██║ ██║ ╚════██║██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ // ██║╚██████╔╝██╗███████║╚██████╔╝╚██████╗██║ ██╗███████╗ ██║ // ╚═╝ ╚═════╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ // // io.socket // // The eager instance of Socket which will automatically try to connect // using the host that this js file was served from. // // This can be disabled or configured by setting properties on `io.sails.*` within the // first cycle of the event loop. // // Build `io.socket` so it exists // (note that this DOES NOT start the connection process) io.socket = new SailsSocket(); // // This socket is not connected yet, and has not even _started_ connecting. // // But in the mean time, this eager socket will be queue events bound by the user // before the first cycle of the event loop (using `.on()`), which will later // be rebound on the raw underlying socket. // ┌─┐┌─┐┌┬┐ ┌─┐┬ ┬┌┬┐┌─┐ ┌─┐┌─┐┌┐┌┌┐┌┌─┐┌─┐┌┬┐ ┌┬┐┬┌┬┐┌─┐┬─┐ // └─┐├┤ │ ├─┤│ │ │ │ │───│ │ │││││││├┤ │ │ │ ││││├┤ ├┬┘ // └─┘└─┘ ┴ ┴ ┴└─┘ ┴ └─┘ └─┘└─┘┘└┘┘└┘└─┘└─┘ ┴ ┴ ┴┴ ┴└─┘┴└─ // If configured to do so, start auto-connecting after the first cycle of the event loop // has completed (to allow time for this behavior to be configured/disabled // by specifying properties on `io.sails`) // Indicate that the autoConnect timer has started. io.socket._mightBeAboutToAutoConnect = true; setTimeout(function() { // Indicate that the autoConect timer fired. io.socket._mightBeAboutToAutoConnect = false; // If autoConnect is disabled, delete the eager socket (io.socket) and bail out. if (io.sails.autoConnect === false || io.sails.autoconnect === false) { delete io.socket; return; } // consolog('Eagerly auto-connecting socket to Sails... (requests will be queued in the mean-time)'); io.socket._connect(); }, 0); // // Return the `io` object. return io; } // // ///////////////////////////////////////////////////////////////////////////////// ///// ///////////////////////////////////////////////////////////////////////////////// // ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗ ███████╗██████╗ ██╗ ██╗ // ██╔════╝╚██╗██╔╝██╔══██╗██╔═══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║ ██╔╝ // █████╗ ╚███╔╝ ██████╔╝██║ ██║███████╗█████╗ ███████╗██║ ██║█████╔╝ // ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║╚════██║██╔══╝ ╚════██║██║ ██║██╔═██╗ // ███████╗██╔╝ ██╗██║ ╚██████╔╝███████║███████╗ ███████║██████╔╝██║ ██╗ // ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝ // // Add CommonJS support to allow this client SDK to be used from Node.js. if (SDK_INFO.platform === 'node') { module.exports = SailsIOClient; } // Add AMD support, registering this client SDK as an anonymous module. else if (typeof define === 'function' && define.amd) { define([], function() { return SailsIOClient; }); } else { // Otherwise, try to instantiate the client using the global `io`: SailsIOClient(); // Note: // If you are modifying this file manually to wrap an existing socket.io client // (e.g. to prevent pollution of the global namespace), you can replace the global // `io` with your own `io` instance above. } })(); ; /* eslint-enable */