river-of-ebooks/assets/dependencies/sails.io.js

1740 lines
136 KiB
JavaScript
Raw Permalink Normal View History

2018-10-16 01:45:37 +00:00
/* 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&
return new(b[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(g){}}}).call(b,function(){return this}())},function(a,b){try{a.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(c){a.exports=!1}},function(a,b,c){(function(b){function d(){}function e(a){if(i.call(this,a),this.requestTimeout=a.requestTimeout,this.extraHeaders=a.extraHeaders,b.location){var c="https:"===location.protocol,d=location.port;d||(d=c?443:80),this.xd=a.hostname!==b.location.hostname||d!==a.port,this.xs=a.secure!==c}}function f(a){this.method=a.method||"GET",this.uri=a.uri,this.xd=!!a.xd,this.xs=!!a.xs,this.async=!1!==a.async,this.data=void 0!==a.data?a.data:null,this.agent=a.agent,this.isBinary=a.isBinary,this.supportsBinary=a.supportsBinary,this.enablesXDR=a.enablesXDR,this.requestTimeout=a.requestTimeout,this.pfx=a.pfx,this.key=a.key,this.passphrase=a.passphrase,this.cert=a.cert,this.ca=a.ca,this.ciphers=a.ciphers,this.rejectUnauthorized=a.rejectUnauthorized,this.extraHeaders=a.extraHeaders,this.create()}function g(){for(var a in f.requests)f.requests.hasOwnProperty(a)&&f.requests[a].abort()}var h=c(18),i=c(21),j=c(8),k=c(32),l=c(3)("engine.io-client:polling-xhr");a.exports=e,a.exports.Request=f,k(e,i),e.prototype.supportsBinary=!0,e.prototype.request=function(a){return a=a||{},a.uri=this.uri(),a.xd=this.xd,a.xs=this.xs,a.agent=this.agent||!1,a.supportsBinary=this.supportsBinary,a.enablesXDR=this.enablesXDR,a.pfx=this.pfx,a.key=this.key,a.passphrase=this.passphrase,a.cert=this.cert,a.ca=this.ca,a.ciphers=this.ciphers,a.rejectUnauthorized=this.rejectUnauthorized,a.requestTimeout=this.requestTimeout,a.extraHeaders=this.extraHeaders,new f(a)},e.prototype.doWrite=function(a,b){var c="string"!=typeof a&&void 0!==a,d=this.request({method:"POST",data:a,isBinary:c}),e=this;d.on("success",b),d.on("error",function(a){e.onError("xhr post error",a)}),this.sendXhr=d},e.prototype.doPoll=function(){l("xhr poll");var a=this.request(),b=this;a.on("data",function(a){b.onData(a)}),a.on("error",function(a){b.onError("xhr poll error",a)}),this.pollXhr=a},j(f.prototype),f.prototype.create=function(){var a={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};a.pfx=this.pfx,a.key=this.key,a.passphrase=this.passphrase,a.cert=this.cert,a.ca=this.ca,a.ciphers=this.ciphers,a.rejectUnauthorized=this.rejectUnauthorized;var c=this.xhr=new h(a),d=this;try{l("xhr open %s: %s",this.method,this.uri),c.open(this.method,this.uri,this.async);try{if(this.extraHeaders){c.setDisableHeaderCheck&&c.setDisableHeaderCheck(!0);for(var e in this.extraHeaders)this.extraHeaders.hasOwnProperty(e)&&c.setRequestHeader(e,this.extraHeaders[e])}}catch(g){}if("POST"===this.method)try{this.isBinary?c.setRequestHeader("Content-type","application/octet-stream"):c.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(g){}try{c.setRequestHeader("Accept","*/*")}catch(g){}"withCredentials"in c&&(c.withCredentials=!0),this.requestTimeout&&(c.timeout=this.requestTimeout),this.hasXDR()?(c.onload=function(){d.onLoad()},c.onerror=function(){d.onError(c.responseText)}):c.onreadystatechange=function(){if(2===c.readyState){var a;try{a=c.getResponseHeader("Content-Type")}catch(b){}"application/octet-stream"===a&&(c.responseType="arraybuffer")}4===c.readyState&&(200===c.status||1223===c.status?d.onLoad():setTimeout(function(){d.onError(c.status)},0))},l("xhr data %s",this.data),c.send(this.data)}catch(g){return void setTimeout(function(){d.onError(g)},0)}b.document&&(this.index=f.requestsCount++,f.requests[this.index]=this)},f.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},f.prototype.onData=function(a){this.emit("data",a),this.onSuccess()},f.prototype.onError=function(a){this.emit("error",a),this.cleanup(!0)},f.prototype.cleanup=function(a){if("undefined"!=typeof this.xhr&&null!==this.xhr){if(this.hasXDR()?this.xhr.onload=this.xhr.onerror=d:this.xhr.onreadystatechange=d,a)try{this.xhr.abort()}catch(c){}b.document&&delete f.requests[this.index],this.xhr=null}},f.prototype.onLoad=function(){var a;try{var b;
//////////////////////////////////////////////////////////////////////////////////////
// //
// ███████╗ █████╗ ██╗██╗ ███████╗ ██╗ ██████╗ ██╗███████╗ //
// ██╔════╝██╔══██╗██║██║ ██╔════╝ ██║██╔═══██╗ ██║██╔════╝ //
// ███████╗███████║██║██║ ███████╗ ██║██║ ██║ ██║███████╗ //
// ╚════██║██╔══██║██║██║ ╚════██║ ██║██║ ██║ ██ ██║╚════██║ //
// ███████║██║ ██║██║███████╗███████║██╗██║╚██████╔╝██╗╚█████╔╝███████║ //
// ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ //
// //
// ╦╔═╗╦ ╦╔═╗╔═╗╔═╗╦═╗╦╔═╗╔╦╗ ╔═╗╦ ╦╔═╗╔╗╔╔╦╗ ╔═╗╔╦╗╦╔═ //
// ║╠═╣╚╗╔╝╠═╣╚═╗║ ╠╦╝║╠═╝ ║ ║ ║ ║║╣ ║║║ ║ ╚═╗ ║║╠╩╗ //
// ╚╝╩ ╩ ╚╝ ╩ ╩╚═╝╚═╝╩╚═╩╩ ╩ ╚═╝╩═╝╩╚═╝╝╚╝ ╩ ╚═╝═╩╝╩ ╩ //
// ┌─┐┌─┐┬─┐ ┌┐┌┌─┐┌┬┐┌─┐ ┬┌─┐ ┌─┐┌┐┌┌┬┐ ┌┬┐┬ ┬┌─┐ ┌┐ ┬─┐┌─┐┬ ┬┌─┐┌─┐┬─┐ //
// ├┤ │ │├┬┘ ││││ │ ││├┤ │└─┐ ├─┤│││ ││ │ ├─┤├┤ ├┴┐├┬┘│ ││││└─┐├┤ ├┬┘ //
// └ └─┘┴└─ ┘└┘└─┘─┴┘└─┘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);
};
}//</LoggerFactory>
// 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>
/**
* `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); // </setTimeout>
// Return the `io` object.
return io;
} //</SailsIOClient>
//
/////////////////////////////////////////////////////////////////////////////////
///// </bunches of private function definitions, constructors, and methods>
/////////////////////////////////////////////////////////////////////////////////
// ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗ ███████╗██████╗ ██╗ ██╗
// ██╔════╝╚██╗██╔╝██╔══██╗██╔═══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║ ██╔╝
// █████╗ ╚███╔╝ ██████╔╝██║ ██║███████╗█████╗ ███████╗██║ ██║█████╔╝
// ██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║╚════██║██╔══╝ ╚════██║██║ ██║██╔═██╗
// ███████╗██╔╝ ██╗██║ ╚██████╔╝███████║███████╗ ███████║██████╔╝██║ ██╗
// ╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝
//
// 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 */