<commit_before>/*---------------------------------------------------------------------------
  Copyright 2013 Microsoft Corporation.

  This is free software; you can redistribute it and/or modify it under the
  terms of the Apache License, Version 2.0. A copy of the License can be
  found in the file "license.txt" at the root of this distribution.
---------------------------------------------------------------------------*/

define(["../scripts/promise","../scripts/util"], function(Promise,Util) {

var appKey      = "3vj9witefc2z44w";
var root        = "dropbox";
var redirectUri = "https://www.madoko.net/redirect/dropbox";
//var redirectUri = "https://madoko.cloudapp.net/redirect/dropbox";
var contentUrl  = "https://api-content.dropbox.com/1/files/" + root + "/";
var pushUrl     = "https://api-content.dropbox.com/1/files_put/" + root + "/";
var metadataUrl = "https://api.dropbox.com/1/metadata/" + root + "/";
var fileopsUrl  = "https://api.dropbox.com/1/fileops/";
var accountUrl  = "https://api.dropbox.com/1/account/";
var appRoot     = "";

var _access_token = null;
var _username = "";

function getAccessToken() {
  if (!_access_token) {
    _access_token = Util.getCookie("auth_dropbox");
  }
  return _access_token;
}

/* ----------------------------------------------
   Login
---------------------------------------------- */

function login(dontForce) {
  if (getAccessToken()) return Promise.resolved();
  if (dontForce) return Promise.rejected( new Error("dropbox: not logged in") );
  var url = "https://www.dropbox.com/1/oauth2/authorize"
  var params = {
    response_type: "token",
    client_id: appKey,
    redirect_uri:  redirectUri,
    state: Util.generateOAuthState(),
  };
  return Util.openModalPopup(url,params,"oauth",600,600).then( function() {
    if (!getAccessToken()) throw new Error("dropbox login failed");
    return getUserName();
  });
}

function getUserName() {
  if (_username) return Promise.resolved(_username);
  return Util.requestGET( accountUrl + "info", { access_token: getAccessToken() } ).then( function(info) {
    if (typeof info === "string") info = JSON.parse(info);
    _username = info ? info.display_name : "";
    return _username;
  });
}

function logout() {
  Util.setCookie("auth_dropbox","",0);
  _access_token = null;
  _username = "";
}

function chooseOneFile() {
  return new Promise( function(cont) {
    window.Dropbox.choose( {
      success: function(files) {
        if (!files || !files.length || files.length !== 1) cont(new Error("Can only select a single file to open"));
        cont(null, files[0].link );
      },
      cancel: function() {
        cont( new Error("dropbox dialog was canceled") );
      },
      linkType: "direct",
      multiselect: false,
      extensions: [".mdk",".md",".mkdn",".markdown"],
    });
  });
}

function loginAndChooseOneFile() {
  Util.setCookie("dropbox-next","choose",60);
  return login().then( function() {
    Util.setCookie("dropbox-next","",0);
    var err  = Util.getCookie("dropbox-error");
    if (err) {
      throw new Error( decodeURIComponent(err) );
    }
    var link = Util.getCookie("dropbox-choose");
    if (!link) {
      throw new Error( "dropbox: could not read file selection -- try again" );
    }
    return decodeURIComponent(link);
  });
}

/* ----------------------------------------------
  Basic file API
---------------------------------------------- */

function pullFile(fname,binary) {
  var opts = { url: contentUrl + fname, binary: binary };
  return Util.requestGET( opts, { access_token: getAccessToken() });
}

function fileInfo(fname) {
  var url = metadataUrl + fname;
  return Util.requestGET( url, { access_token: getAccessToken() }).then( function(info) {
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
}

function folderInfo(fname) {
  var url = metadataUrl + fname;
  return Util.requestGET( url, { access_token: getAccessToken(), list: true }).then( function(info) {
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
}

function pushFile(fname,content) {
  var url = pushUrl + fname;
  return Util.requestPOST( url, { access_token: getAccessToken() }, content ).then( function(info) {
    if (!info) throw new Error("dropbox: could not push file: " + fname);
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
  //, function(err) {
  //  throw new Error("dropbox: could not push file: " + fname + ": " + err.toString());
  //});
}

function createFolder( dirname ) {
  var url = fileopsUrl + "create_folder";
  return Util.requestPOST( url, {
    access_token: getAccessToken(),
    root: root,
    path: dirname,
  }).then( function(info) {
    return true; // freshly created
  }, function(err) {
    if (err.httpCode === 403) return false;
    throw err;
  });
}

/* ----------------------------------------------
   Main entry points
---------------------------------------------- */

function openFile() {
  // carefully login so we avoid popup blockers but can still use dropbox's file picker
  var choose = (getAccessToken() ? chooseOneFile : loginAndChooseOneFile);
  return choose().then( function(fileLink) {
    var cap = new RegExp("^https://" + ".*?/view/[^\\/]+/" + appRoot + "(.*)$").exec(fileLink);
    if (!cap) throw (new Error("Can only select files in the " + appRoot + " folder"));
    return cap[1];
  }).then( function(fname) {
    return { remote: new Dropbox(Util.dirname(fname)), docName: Util.basename(fname) };
  });
}

function createAt( folder ) {
  return login().then( function() {
    return new Dropbox(folder);
  });
}

/* ----------------------------------------------
   Remote interface
---------------------------------------------- */

function unpersist(obj) {
  return new Dropbox(obj.folder);
}

function type() {
  return "dropbox";
}

function logo() {
  return "icon-dropbox.png";
}

var Dropbox = (function() {

  function Dropbox( folder ) {
    var self = this;
    self.folder = folder || "";
  }

  Dropbox.prototype.createNewAt = function(folder) {
    return createAt(folder);
  }

  Dropbox.prototype.type = function() {
    return type();
  }

  Dropbox.prototype.logo = function() {
    return logo();
  }

  Dropbox.prototype.getFolder = function() {
    var self = this;
    return self.folder;
  }

  Dropbox.prototype.persist = function() {
    var self = this;
    return { folder: self.folder };
  }

  Dropbox.prototype.fullPath = function(fname) {
    var self = this;
    return Util.combine(self.folder,fname);
  }

  Dropbox.prototype.connect = function(dontForce) {
    return login(dontForce);
  }

  Dropbox.prototype.createSubFolder = function(dirname) {
    var self = this;
    var folder = self.fullPath(dirname);
    return createFolder(folder).then( function(created) {
      return { folder: folder, created: created };
    });
  }

  Dropbox.prototype.pushFile = function( fpath, content ) {
    var self = this;
    return pushFile( self.fullPath(fpath), content ).then( function(info) {
      return new Date(info.modified);
    });
  }

  Dropbox.prototype.pullFile = function( fpath, binary ) {
    var self = this;
    return self.getRemoteTime(fpath).then( function(date) {
      if (!date) return Promise.rejected("file not found: " + fpath);
      return pullFile( self.fullPath(fpath), binary ).then( function(content,req) {
        var file = {
          path: fpath,
          content: content,
          createdTime: date,
        };
        return file;
      });
    });
  }

  Dropbox.prototype.getRemoteTime = function( fpath ) {
    var self = this;
    return fileInfo( self.fullPath(fpath) ).then( function(info) {
      return (info ? new Date(info.modified) : null);
    }, function(err) {
      return null;
    });
  }

  Dropbox.prototype.listing = function( fpath ) {
    var self = this;
    return folderInfo( self.fullPath(fpath) ).then( function(info) {
      return (info ? info.contents : []).map( function(item) {
        item.type = item.is_dir ? "folder" : "file";
        item.isShared = item.is_dir && item.icon==="folder_user";
        return item;
      });
    });
  }

  Dropbox.prototype.connected = function() {
    return (getAccessToken() != null);
  }

  Dropbox.prototype.login = function() {
    return login();
  }

  Dropbox.prototype.logout = function() {
    return logout();
  }

  Dropbox.prototype.getUserName = function() {
    var self = this;
    if (!self.connected()) return Promise.resolved(null);
    return getUserName();
  }

  return Dropbox;
})();

return {
  openFile: openFile,
  createAt: createAt,
  login: login,
  logout: logout,
  unpersist: unpersist,
  type: type,
  logo: logo,
  Dropbox: Dropbox,
}

});<commit_msg>Fix dropbox issue where deleted files would have a modified date<commit_after>/*---------------------------------------------------------------------------
  Copyright 2013 Microsoft Corporation.

  This is free software; you can redistribute it and/or modify it under the
  terms of the Apache License, Version 2.0. A copy of the License can be
  found in the file "license.txt" at the root of this distribution.
---------------------------------------------------------------------------*/

define(["../scripts/promise","../scripts/util"], function(Promise,Util) {

var appKey      = "3vj9witefc2z44w";
var root        = "dropbox";
var redirectUri = "https://www.madoko.net/redirect/dropbox";
//var redirectUri = "https://madoko.cloudapp.net/redirect/dropbox";
var contentUrl  = "https://api-content.dropbox.com/1/files/" + root + "/";
var pushUrl     = "https://api-content.dropbox.com/1/files_put/" + root + "/";
var metadataUrl = "https://api.dropbox.com/1/metadata/" + root + "/";
var fileopsUrl  = "https://api.dropbox.com/1/fileops/";
var accountUrl  = "https://api.dropbox.com/1/account/";
var appRoot     = "";

var _access_token = null;
var _username = "";

function getAccessToken() {
  if (!_access_token) {
    _access_token = Util.getCookie("auth_dropbox");
  }
  return _access_token;
}

/* ----------------------------------------------
   Login
---------------------------------------------- */

function login(dontForce) {
  if (getAccessToken()) return Promise.resolved();
  if (dontForce) return Promise.rejected( new Error("dropbox: not logged in") );
  var url = "https://www.dropbox.com/1/oauth2/authorize"
  var params = {
    response_type: "token",
    client_id: appKey,
    redirect_uri:  redirectUri,
    state: Util.generateOAuthState(),
  };
  return Util.openModalPopup(url,params,"oauth",600,600).then( function() {
    if (!getAccessToken()) throw new Error("dropbox login failed");
    return getUserName();
  });
}

function getUserName() {
  if (_username) return Promise.resolved(_username);
  return Util.requestGET( accountUrl + "info", { access_token: getAccessToken() } ).then( function(info) {
    if (typeof info === "string") info = JSON.parse(info);
    _username = info ? info.display_name : "";
    return _username;
  });
}

function logout() {
  Util.setCookie("auth_dropbox","",0);
  _access_token = null;
  _username = "";
}

function chooseOneFile() {
  return new Promise( function(cont) {
    window.Dropbox.choose( {
      success: function(files) {
        if (!files || !files.length || files.length !== 1) cont(new Error("Can only select a single file to open"));
        cont(null, files[0].link );
      },
      cancel: function() {
        cont( new Error("dropbox dialog was canceled") );
      },
      linkType: "direct",
      multiselect: false,
      extensions: [".mdk",".md",".mkdn",".markdown"],
    });
  });
}

function loginAndChooseOneFile() {
  Util.setCookie("dropbox-next","choose",60);
  return login().then( function() {
    Util.setCookie("dropbox-next","",0);
    var err  = Util.getCookie("dropbox-error");
    if (err) {
      throw new Error( decodeURIComponent(err) );
    }
    var link = Util.getCookie("dropbox-choose");
    if (!link) {
      throw new Error( "dropbox: could not read file selection -- try again" );
    }
    return decodeURIComponent(link);
  });
}

/* ----------------------------------------------
  Basic file API
---------------------------------------------- */

function pullFile(fname,binary) {
  var opts = { url: contentUrl + fname, binary: binary };
  return Util.requestGET( opts, { access_token: getAccessToken() });
}

function fileInfo(fname) {
  var url = metadataUrl + fname;
  return Util.requestGET( url, { access_token: getAccessToken() }).then( function(info) {
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
}

function folderInfo(fname) {
  var url = metadataUrl + fname;
  return Util.requestGET( url, { access_token: getAccessToken(), list: true }).then( function(info) {
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
}

function pushFile(fname,content) {
  var url = pushUrl + fname;
  return Util.requestPOST( url, { access_token: getAccessToken() }, content ).then( function(info) {
    if (!info) throw new Error("dropbox: could not push file: " + fname);
    return (typeof info === "string" ? JSON.parse(info) : info);
  });
  //, function(err) {
  //  throw new Error("dropbox: could not push file: " + fname + ": " + err.toString());
  //});
}

function createFolder( dirname ) {
  var url = fileopsUrl + "create_folder";
  return Util.requestPOST( url, {
    access_token: getAccessToken(),
    root: root,
    path: dirname,
  }).then( function(info) {
    return true; // freshly created
  }, function(err) {
    if (err.httpCode === 403) return false;
    throw err;
  });
}

/* ----------------------------------------------
   Main entry points
---------------------------------------------- */

function openFile() {
  // carefully login so we avoid popup blockers but can still use dropbox's file picker
  var choose = (getAccessToken() ? chooseOneFile : loginAndChooseOneFile);
  return choose().then( function(fileLink) {
    var cap = new RegExp("^https://" + ".*?/view/[^\\/]+/" + appRoot + "(.*)$").exec(fileLink);
    if (!cap) throw (new Error("Can only select files in the " + appRoot + " folder"));
    return cap[1];
  }).then( function(fname) {
    return { remote: new Dropbox(Util.dirname(fname)), docName: Util.basename(fname) };
  });
}

function createAt( folder ) {
  return login().then( function() {
    return new Dropbox(folder);
  });
}

/* ----------------------------------------------
   Remote interface
---------------------------------------------- */

function unpersist(obj) {
  return new Dropbox(obj.folder);
}

function type() {
  return "dropbox";
}

function logo() {
  return "icon-dropbox.png";
}

var Dropbox = (function() {

  function Dropbox( folder ) {
    var self = this;
    self.folder = folder || "";
  }

  Dropbox.prototype.createNewAt = function(folder) {
    return createAt(folder);
  }

  Dropbox.prototype.type = function() {
    return type();
  }

  Dropbox.prototype.logo = function() {
    return logo();
  }

  Dropbox.prototype.getFolder = function() {
    var self = this;
    return self.folder;
  }

  Dropbox.prototype.persist = function() {
    var self = this;
    return { folder: self.folder };
  }

  Dropbox.prototype.fullPath = function(fname) {
    var self = this;
    return Util.combine(self.folder,fname);
  }

  Dropbox.prototype.connect = function(dontForce) {
    return login(dontForce);
  }

  Dropbox.prototype.createSubFolder = function(dirname) {
    var self = this;
    var folder = self.fullPath(dirname);
    return createFolder(folder).then( function(created) {
      return { folder: folder, created: created };
    });
  }

  Dropbox.prototype.pushFile = function( fpath, content ) {
    var self = this;
    return pushFile( self.fullPath(fpath), content ).then( function(info) {
      return new Date(info.modified);
    });
  }

  Dropbox.prototype.pullFile = function( fpath, binary ) {
    var self = this;
    return self.getRemoteTime(fpath).then( function(date) {
      if (!date) return Promise.rejected("file not found: " + fpath);
      return pullFile( self.fullPath(fpath), binary ).then( function(content,req) {
        var file = {
          path: fpath,
          content: content,
          createdTime: date,
        };
        return file;
      });
    });
  }

  Dropbox.prototype.getRemoteTime = function( fpath ) {
    var self = this;
    return fileInfo( self.fullPath(fpath) ).then( function(info) {
      return (info && !info.is_deleted ? new Date(info.modified) : null);
    }, function(err) {
      return null;
    });
  }

  Dropbox.prototype.listing = function( fpath ) {
    var self = this;
    return folderInfo( self.fullPath(fpath) ).then( function(info) {
      return (info ? info.contents : []).map( function(item) {
        item.type = item.is_dir ? "folder" : "file";
        item.isShared = item.is_dir && item.icon==="folder_user";
        return item;
      });
    });
  }

  Dropbox.prototype.connected = function() {
    return (getAccessToken() != null);
  }

  Dropbox.prototype.login = function() {
    return login();
  }

  Dropbox.prototype.logout = function() {
    return logout();
  }

  Dropbox.prototype.getUserName = function() {
    var self = this;
    if (!self.connected()) return Promise.resolved(null);
    return getUserName();
  }

  return Dropbox;
})();

return {
  openFile: openFile,
  createAt: createAt,
  login: login,
  logout: logout,
  unpersist: unpersist,
  type: type,
  logo: logo,
  Dropbox: Dropbox,
}

});<|endoftext|>


<commit_before>"use strict";

var NodeBB = require('./nodebb'),

	async = require('async'),

	Shouts = {};

Shouts.addShout = function(fromuid, content, callback) {
	NodeBB.db.incrObjectField('global', 'nextSid', function(err, sid) {
		if (err) return callback(err);

		var shout = {
			sid: sid,
			content: content,
			timestamp: Date.now(),
			fromuid: fromuid,
			deleted: '0'
		};

		async.parallel([
			async.apply(NodeBB.db.setObject, 'shout:' + sid, shout),
			async.apply(NodeBB.db.listAppend, 'shouts', sid)
		], function(err) {
			if (err) return callback(err);

			getShouts([sid], callback);
		});
	});
};

Shouts.getPlainShouts = function(sids, callback) {
	var keys = sids.map(function(sid) {
		return 'shout:' + sid;
	});

	NodeBB.db.getObjects(keys, callback);
};

Shouts.getShouts = function(start, end, callback) {
	NodeBB.db.getListRange('shouts', start, end, function(err, sids) {
		if (err) return callback(err);

		if (!Array.isArray(sids) || !sids.length) {
			return callback(null, []);
		}

		getShouts(sids, callback);
	});
};

function getShouts(sids, callback) {
	var keys = sids.map(function(sid) {
		return 'shout:' + sid;
	}), userData, uids,
		userFields = ['uid', 'username', 'userslug', 'picture', 'status'];

	NodeBB.db.getObjects(keys, function(err, shouts) {
		if (err) return callback(err);

		// Get a list of unique uids of the users of non-deleted shouts
		uids = shouts.map(function(s) {
			return parseInt(s.deleted, 10) !== 1 ? parseInt(s.fromuid, 10) : null;
		}).filter(function(u, index, self) {
			return u === null ? false : self.indexOf(u) === index;
		});

		NodeBB.User.getMultipleUserFields(uids, userFields, function(err, usersData) {
			if (err) return callback(err);

			async.map(shouts, function(shout, next) {
				if (parseInt(shout.deleted, 10) === 1) return next();

				userData = usersData[uids.indexOf(parseInt(shout.fromuid, 10))];

				Shouts.parse(shout.content, userData, function(err, s) {
					shout.user = s.user;
					shout.content = s.content;

					next(null, shout);
				});
			}, callback);
		});
	});
}

Shouts.parse = function(raw, userData, callback) {
	async.parallel({
		parsed: async.apply(NodeBB.Plugins.fireHook, 'filter:parse.raw', raw),
		isAdmin: async.apply(NodeBB.User.isAdministrator, userData.uid),
		status: function(next) {
			next(null, NodeBB.SocketIndex.isUserOnline(userData.uid) ? (userData.status || 'online') : 'offline');
		}
	}, function(err, result) {
		if (err) {
			callback(null, {
				user: userData,
				content: raw
			});
		}

		userData.status = result.status;
		userData.isAdmin = result.isAdmin;

		callback(null, {
			user: userData,
			content: result.parsed
		});
	});
};

Shouts.removeShout = function(sid, uid, callback) {
	async.parallel({
		isAdmin: async.apply(NodeBB.User.isAdministrator, uid),
		fromUid: async.apply(NodeBB.db.getObjectField, 'shout:' + sid, 'fromuid')
	}, function(err, result) {
		if (err) return callback(err);

		if (result.isAdmin || parseInt(result.fromUid, 10) === parseInt(uid, 10)) {
			NodeBB.db.setObjectField('shout:' + sid, 'deleted', '1', function (err, result) {
				if (err) return callback(err);

				return callback(null, true);
			});
		} else {
			return callback(new Error('not-authorized'));
		}
	});
};

Shouts.editShout = function(sid, msg, uid, callback) {
	async.parallel({
		isAdmin: async.apply(NodeBB.User.isAdministrator, uid),
		fromUid: async.apply(NodeBB.db.getObjectField, 'shout:' + sid, 'fromuid')
	}, function(err, result) {
		if (err) return callback(err);

		if (result.isAdmin || parseInt(result.fromUid, 10) === parseInt(uid, 10)) {
			NodeBB.db.setObjectField('shout: ' + sid, 'content', msg, function(err, result) {
				if (err) return callback(err);

				getShouts([sid], callback);
			});
		} else {
			return callback(new Error('not-authorized'));
		}
	});
};

Shouts.pruneDeleted = function(uid, callback) {
	NodeBB.User.isAdministrator(uid, function(err, isAdmin) {
		if (!isAdmin) return callback(new Error('not-authorized'));

		NodeBB.db.getListRange('shouts', 0, -1, function(err, sids) {
			if (err || !sids || !sids.length) return callback(err);

			var keys = sids.map(function(sid) {
				return 'shout:' + sid;
			});

			NodeBB.db.getObjectsFields(keys, ['sid', 'deleted'], function(err, items) {
				async.map(items, function(item, next) {
					if (parseInt(item.deleted, 10) === 0) {
						return next();
					}

					NodeBB.db.listRemoveAll('shouts', item.sid);
					next(null, item.sid);
				}, function(err, sids) {
					var keys = sids.map(function(sid) {
						return 'shout:' + sid;
					});

					NodeBB.db.deleteAll(keys, function(err, result) {
						if (err) return callback(err);

						callback(null, true);
					});
				});
			});
		});
	});
};

Shouts.removeAll = function(uid, callback) {
	NodeBB.User.isAdministrator(uid, function(err, isAdmin) {
		if (!isAdmin) return callback(new Error('not-authorized'));

		NodeBB.db.getListRange('shouts', 0, -1, function (err, sids) {
			if (err || !sids || !sids.length) return callback(err);

			var keys = sids.map(function (sid) {
				return 'shout:' + sid;
			});

			async.parallel([
				async.apply(NodeBB.db.deleteAll, keys),
				async.apply(NodeBB.db.delete, 'shouts'),
				async.apply(NodeBB.db.setObjectField, 'global', 'nextSid', 0)
			], function(err, result) {
				if (err) return callback(err);

				callback(null, true);
			});
		});
	});
};

module.exports = Shouts;<commit_msg>Fix editing of shouts<commit_after>"use strict";

var NodeBB = require('./nodebb'),

	async = require('async'),

	Shouts = {};

Shouts.addShout = function(fromuid, content, callback) {
	NodeBB.db.incrObjectField('global', 'nextSid', function(err, sid) {
		if (err) return callback(err);

		var shout = {
			sid: sid,
			content: content,
			timestamp: Date.now(),
			fromuid: fromuid,
			deleted: '0'
		};

		async.parallel([
			async.apply(NodeBB.db.setObject, 'shout:' + sid, shout),
			async.apply(NodeBB.db.listAppend, 'shouts', sid)
		], function(err) {
			if (err) return callback(err);

			getShouts([sid], callback);
		});
	});
};

Shouts.getPlainShouts = function(sids, callback) {
	var keys = sids.map(function(sid) {
		return 'shout:' + sid;
	});

	NodeBB.db.getObjects(keys, callback);
};

Shouts.getShouts = function(start, end, callback) {
	NodeBB.db.getListRange('shouts', start, end, function(err, sids) {
		if (err) return callback(err);

		if (!Array.isArray(sids) || !sids.length) {
			return callback(null, []);
		}

		getShouts(sids, callback);
	});
};

function getShouts(sids, callback) {
	var keys = sids.map(function(sid) {
		return 'shout:' + sid;
	}), userData, uids,
		userFields = ['uid', 'username', 'userslug', 'picture', 'status'];

	NodeBB.db.getObjects(keys, function(err, shouts) {
		if (err) return callback(err);

		// Get a list of unique uids of the users of non-deleted shouts
		uids = shouts.map(function(s) {
			return parseInt(s.deleted, 10) !== 1 ? parseInt(s.fromuid, 10) : null;
		}).filter(function(u, index, self) {
			return u === null ? false : self.indexOf(u) === index;
		});

		NodeBB.User.getMultipleUserFields(uids, userFields, function(err, usersData) {
			if (err) return callback(err);

			async.map(shouts, function(shout, next) {
				if (parseInt(shout.deleted, 10) === 1) return next();

				userData = usersData[uids.indexOf(parseInt(shout.fromuid, 10))];

				Shouts.parse(shout.content, userData, function(err, s) {
					shout.user = s.user;
					shout.content = s.content;

					next(null, shout);
				});
			}, callback);
		});
	});
}

Shouts.parse = function(raw, userData, callback) {
	async.parallel({
		parsed: async.apply(NodeBB.Plugins.fireHook, 'filter:parse.raw', raw),
		isAdmin: async.apply(NodeBB.User.isAdministrator, userData.uid),
		status: function(next) {
			next(null, NodeBB.SocketIndex.isUserOnline(userData.uid) ? (userData.status || 'online') : 'offline');
		}
	}, function(err, result) {
		if (err) {
			callback(null, {
				user: userData,
				content: raw
			});
		}

		userData.status = result.status;
		userData.isAdmin = result.isAdmin;

		callback(null, {
			user: userData,
			content: result.parsed
		});
	});
};

Shouts.removeShout = function(sid, uid, callback) {
	async.parallel({
		isAdmin: async.apply(NodeBB.User.isAdministrator, uid),
		fromUid: async.apply(NodeBB.db.getObjectField, 'shout:' + sid, 'fromuid')
	}, function(err, result) {
		if (err) return callback(err);

		if (result.isAdmin || parseInt(result.fromUid, 10) === parseInt(uid, 10)) {
			NodeBB.db.setObjectField('shout:' + sid, 'deleted', '1', function (err, result) {
				if (err) return callback(err);

				return callback(null, true);
			});
		} else {
			return callback(new Error('not-authorized'));
		}
	});
};

Shouts.editShout = function(sid, msg, uid, callback) {
	async.parallel({
		isAdmin: async.apply(NodeBB.User.isAdministrator, uid),
		fromUid: async.apply(NodeBB.db.getObjectField, 'shout:' + sid, 'fromuid')
	}, function(err, result) {
		if (err) return callback(err);

		if (result.isAdmin || parseInt(result.fromUid, 10) === parseInt(uid, 10)) {
			NodeBB.db.setObjectField('shout:' + sid, 'content', msg, function(err, result) {
				if (err) return callback(err);

				getShouts([sid], callback);
			});
		} else {
			return callback(new Error('not-authorized'));
		}
	});
};

Shouts.pruneDeleted = function(uid, callback) {
	NodeBB.User.isAdministrator(uid, function(err, isAdmin) {
		if (!isAdmin) return callback(new Error('not-authorized'));

		NodeBB.db.getListRange('shouts', 0, -1, function(err, sids) {
			if (err || !sids || !sids.length) return callback(err);

			var keys = sids.map(function(sid) {
				return 'shout:' + sid;
			});

			NodeBB.db.getObjectsFields(keys, ['sid', 'deleted'], function(err, items) {
				async.map(items, function(item, next) {
					if (parseInt(item.deleted, 10) === 0) {
						return next();
					}

					NodeBB.db.listRemoveAll('shouts', item.sid);
					next(null, item.sid);
				}, function(err, sids) {
					var keys = sids.map(function(sid) {
						return 'shout:' + sid;
					});

					NodeBB.db.deleteAll(keys, function(err, result) {
						if (err) return callback(err);

						callback(null, true);
					});
				});
			});
		});
	});
};

Shouts.removeAll = function(uid, callback) {
	NodeBB.User.isAdministrator(uid, function(err, isAdmin) {
		if (!isAdmin) return callback(new Error('not-authorized'));

		NodeBB.db.getListRange('shouts', 0, -1, function (err, sids) {
			if (err || !sids || !sids.length) return callback(err);

			var keys = sids.map(function (sid) {
				return 'shout:' + sid;
			});

			async.parallel([
				async.apply(NodeBB.db.deleteAll, keys),
				async.apply(NodeBB.db.delete, 'shouts'),
				async.apply(NodeBB.db.setObjectField, 'global', 'nextSid', 0)
			], function(err, result) {
				if (err) return callback(err);

				callback(null, true);
			});
		});
	});
};

module.exports = Shouts;<|endoftext|>


<commit_before>import defaultOptions from './default-options';
import Slide from './slide';
import { EventTarget } from './utils';
import installModules from './modules';

const ensureSlideId = (slideElement, index) => {
    if (!slideElement.id) {
        slideElement.id = index + 1;
    }
};

class Shower extends EventTarget {
    constructor(options) {
        super();

        this._mode = 'list';
        this._isStarted = false;
        this.options = { ...defaultOptions, ...options };
    }

    /**
     * @param {object} options
     */
    configure(options) {
        Object.assign(this.options, options);
    }

    start() {
        if (this._isStarted) return;

        const { containerSelector } = this.options;
        this.container = document.querySelector(containerSelector);
        if (!this.container) {
            throw new Error(`Shower container with selector '${containerSelector}' not found.`);
        }

        this._isStarted = true;
        this._initSlides();

        // maintains invariant: active slide always exists in `full` mode
        this.addEventListener('modechange', () => {
            if (this.isFullMode && !this.activeSlide) {
                this.first();
            }
        });

        installModules(this);
        this.dispatchEvent(new Event('start'));
    }

    _initSlides() {
        const slideElements = [
            ...this.container.querySelectorAll(this.options.slideSelector),
        ].filter((slideElement) => !slideElement.hidden);

        slideElements.forEach(ensureSlideId);
        this.slides = slideElements.map((slideElement) => {
            const slide = new Slide(slideElement, this.options);

            slide.addEventListener('activate', () => {
                this._changeActiveSlide(slide);
            });

            slide.element.addEventListener('click', () => {
                if (this.isListMode) {
                    this.enterFullMode();
                    slide.activate();
                }
            });

            return slide;
        });
    }

    _changeMode(mode) {
        if (mode === this._mode) return;

        this._mode = mode;
    }

    _changeActiveSlide(next) {
        const prev = this.slides.find((slide) => {
            return slide.isActive && slide !== next;
        });

        if (prev) {
            prev.deactivate();
        }

        const event = new CustomEvent('slidechange', {
            detail: { prev },
        });

        this.dispatchEvent(event);
    }

    get isFullMode() {
        return this._mode === 'full';
    }

    get isListMode() {
        return this._mode === 'list';
    }

    get activeSlide() {
        return this.slides.find((slide) => slide.isActive);
    }

    get activeSlideIndex() {
        return this.slides.findIndex((slide) => slide.isActive);
    }

    /**
     * Slide fills the maximum area.
     */
    enterFullMode() {
        this._changeMode('full');
    }

    /**
     * Shower returns into list mode.
     */
    exitFullMode() {
        this._changeMode('list');
    }

    /**
     * @param {number} index
     */
    goTo(index) {
        const slide = this.slides[index];
        if (slide) {
            slide.activate();
        }
    }

    /**
     * @param {number} delta
     */
    go(delta) {
        this.goTo(this.activeSlideIndex + delta);
    }

    /**
     * @param {boolean=} isForce
     */
    prev(isForce) {
        const prev = new Event('prev', { cancelable: !isForce });
        if (this.dispatchEvent(prev)) {
            this.go(-1);
        }
    }

    /**
     * @param {boolean=} isForce
     */
    next(isForce) {
        const next = new Event('next', { cancelable: !isForce });
        if (this.dispatchEvent(next)) {
            this.go(1);
        }
    }

    first() {
        this.goTo(0);
    }

    last() {
        this.goTo(this.slides.length - 1);
    }
}

export default Shower;
<commit_msg>Don't emit unless during initialization of Shower<commit_after>import defaultOptions from './default-options';
import Slide from './slide';
import { EventTarget } from './utils';
import installModules from './modules';

const ensureSlideId = (slideElement, index) => {
    if (!slideElement.id) {
        slideElement.id = index + 1;
    }
};

class Shower extends EventTarget {
    constructor(options) {
        super();

        this._mode = 'list';
        this._isStarted = false;
        this.options = { ...defaultOptions, ...options };
    }

    /**
     * @param {object} options
     */
    configure(options) {
        Object.assign(this.options, options);
    }

    start() {
        if (this._isStarted) return;

        const { containerSelector } = this.options;
        this.container = document.querySelector(containerSelector);
        if (!this.container) {
            throw new Error(`Shower container with selector '${containerSelector}' not found.`);
        }

        this._initSlides();

        // maintains invariant: active slide always exists in `full` mode
        this.addEventListener('modechange', () => {
            if (this.isFullMode && !this.activeSlide) {
                this.first();
            }
        });

        installModules(this);

        this._isStarted = true;
        this.dispatchEvent(new Event('start'));
    }

    _initSlides() {
        const slideElements = [
            ...this.container.querySelectorAll(this.options.slideSelector),
        ].filter((slideElement) => !slideElement.hidden);

        slideElements.forEach(ensureSlideId);
        this.slides = slideElements.map((slideElement) => {
            const slide = new Slide(slideElement, this.options);

            slide.addEventListener('activate', () => {
                this._changeActiveSlide(slide);
            });

            slide.element.addEventListener('click', () => {
                if (this.isListMode) {
                    this.enterFullMode();
                    slide.activate();
                }
            });

            return slide;
        });
    }

    _changeMode(mode) {
        if (mode === this._mode) return;

        this._mode = mode;
        if (this._isStarted) {
            this.dispatchEvent(new Event('modechange'));
        }
    }

    _changeActiveSlide(next) {
        const prev = this.slides.find((slide) => {
            return slide.isActive && slide !== next;
        });

        if (prev) {
            prev.deactivate();
        }

        if (!this._isStarted) return;

        const event = new CustomEvent('slidechange', {
            detail: { prev },
        });

        this.dispatchEvent(event);
    }

    get isFullMode() {
        return this._mode === 'full';
    }

    get isListMode() {
        return this._mode === 'list';
    }

    get activeSlide() {
        return this.slides.find((slide) => slide.isActive);
    }

    get activeSlideIndex() {
        return this.slides.findIndex((slide) => slide.isActive);
    }

    /**
     * Slide fills the maximum area.
     */
    enterFullMode() {
        this._changeMode('full');
    }

    /**
     * Shower returns into list mode.
     */
    exitFullMode() {
        this._changeMode('list');
    }

    /**
     * @param {number} index
     */
    goTo(index) {
        const slide = this.slides[index];
        if (slide) {
            slide.activate();
        }
    }

    /**
     * @param {number} delta
     */
    go(delta) {
        this.goTo(this.activeSlideIndex + delta);
    }

    /**
     * @param {boolean=} isForce
     */
    prev(isForce) {
        const prev = new Event('prev', { cancelable: !isForce });
        if (this.dispatchEvent(prev)) {
            this.go(-1);
        }
    }

    /**
     * @param {boolean=} isForce
     */
    next(isForce) {
        const next = new Event('next', { cancelable: !isForce });
        if (this.dispatchEvent(next)) {
            this.go(1);
        }
    }

    first() {
        this.goTo(0);
    }

    last() {
        this.goTo(this.slides.length - 1);
    }
}

export default Shower;
<|endoftext|>


<commit_before>'use strict';

const https = require('https');
const Rx = require('rx');
const Telegram = require('node-telegram-bot');
const config = require('./config');
const BotRepository = require('./botRepository');

const BOT_NAME = 'SioDailyBot';

const rxHttpsGet = (url) => {
  return Rx.Observable.create(obs => {
    https.get(url, (res) => {
      obs.onNext(res);
      obs.onCompleted();
    }).on('error', (e) => {
      obs.onError(e);
    });
  });
};

class SioBot {

  constructor(commands) {
    this.bot = new Telegram(config.telegram);
    this.repository = new BotRepository();

    this.rxSendMessage = Rx.Observable.fromNodeCallback(this.bot.sendMessage, this.bot);
    this.rxSendPhoto = Rx.Observable.fromNodeCallback(this.bot.sendPhoto, this.bot);

    let events = Rx.Observable.fromEvent(this.bot, 'message');

    let newChats = events.filter(SioBot.isChatExisting);
    let oldChats = events.filter(event => !SioBot.isChatExisting(event));

    newChats.doOnNext(this.addChat, this).subscribe();
    oldChats.doOnNext(this.delChat, this).subscribe();

    Rx.Observable
      .from(Object.keys(commands))
      .select(c => new RegExp('(?:/)(' + c + ')(?:@' + BOT_NAME + '){0,1}(?:(?:\\s)+(.*))*'))
      .selectMany(c => {
        return events
          .filter(e => e && e.text)
          .select(event => {
            let matches = event.text.match(c);
            if (matches) {
              return {
                command: matches[1],
                args: matches[2],
                event: event
              }
            }
          });
      })
      .filter(c => c)
      .doOnNext(c => {
        console.log('Calling command %s with param %s', c.command, c.args);
        return commands[c.command](c.event, c.args);
      })
      .subscribe();

    this.bot.start();
  };

  static isChatExisting(event) {
    return (!event.left_chat_participant || event.left_chat_participant.username !== BOT_NAME);
  };

  addChat(event) {
    this.repository.addChat(event.chat.id);
  };

  delChat(event) {
    this.repository.delChat(event.chat.id);
  };

  getAllChats() {
    return this.repository.getAllChats();
  };

  sendImage(chatId, imageLink, caption) {
    return rxHttpsGet(imageLink)
      .selectMany(stream => {
        console.log('Sending image "%s" to chat %s...', caption, chatId);
        return this.rxSendPhoto({
          chat_id: chatId,
          caption: caption,
          files: {
            filename: caption + '.jpg',
            stream: stream,
            contentType: 'image/jpg'
          }
        });
      });
  };

  sendImageAll(imageLink, title, url) {
    return this.getAllChats()
      .selectMany(chatId => {
        return this.sendImage(chatId, imageLink, title, url);
      });
  };

  sendText(chatId, text) {
    return this.rxSendMessage({
      chat_id: chatId,
      text: text,
      parse_mode: 'Markdown'
    });
  };

  sendTextAll(text) {
    return this.getAllChats()
      .selectMany(chatId => {
        return this.sendText(chatId, text);
      });
  };

}

module.exports = SioBot;<commit_msg>perf(sioBot): retry polling after failing for duplicate token<commit_after>'use strict';

const https = require('https');
const Rx = require('rx');
const Telegram = require('node-telegram-bot');
const config = require('./config');
const BotRepository = require('./botRepository');

const BOT_NAME = 'SioDailyBot';

const rxHttpsGet = (url) => {
  return Rx.Observable.create(obs => {
    https.get(url, (res) => {
      obs.onNext(res);
      obs.onCompleted();
    }).on('error', (e) => {
      obs.onError(e);
    });
  });
};

class SioBot {

  constructor(commands) {
    this.bot = new Telegram(config.telegram);
    this.repository = new BotRepository();

    this.rxSendMessage = Rx.Observable.fromNodeCallback(this.bot.sendMessage, this.bot);
    this.rxSendPhoto = Rx.Observable.fromNodeCallback(this.bot.sendPhoto, this.bot);

    this.startPolling(commands);
  }

  startPolling(commands) {
    console.log('Trying to start polling Telegram...');

    let events = Rx.Observable.fromEvent(this.bot, 'message');
    let newChats = events.filter(SioBot.isChatExisting);
    let oldChats = events.filter(event => !SioBot.isChatExisting(event));

    let newChatsSubscriber = newChats.doOnNext(this.addChat, this).subscribe();
    let oldChatsSubscriber = oldChats.doOnNext(this.delChat, this).subscribe();

    let commandsSubscriber = Rx.Observable
      .from(Object.keys(commands))
      .select(c => new RegExp('(?:/)(' + c + ')(?:@' + BOT_NAME + '){0,1}(?:(?:\\s)+(.*))*'))
      .selectMany(c => {
        return events
          .filter(e => e && e.text)
          .select(event => {
            let matches = event.text.match(c);
            if (matches) {
              return {
                command: matches[1],
                args: matches[2],
                event: event
              }
            }
          });
      })
      .filter(c => c)
      .doOnNext(c => {
        console.log('Calling command %s with param %s', c.command, c.args);
        return commands[c.command](c.event, c.args);
      })
      .subscribe();

    this.bot.start();

    let errors = Rx.Observable.fromEvent(this.bot, 'error');
    let errorsSubscriber = errors
      .filter(err => err.message === 'Duplicate token.')
      .subscribe(err => {
        newChatsSubscriber.dispose();
        oldChatsSubscriber.dispose();
        commandsSubscriber.dispose();
        errorsSubscriber.dispose();
        console.error('There is another bot instance polling Telegram, retrying in 5000ms');
        setTimeout(() => {
          this.bot.stop();
          this.startPolling(commands);
        }, 5000);
      });
  }

  static isChatExisting(event) {
    return (!event.left_chat_participant || event.left_chat_participant.username !== BOT_NAME);
  };

  addChat(event) {
    this.repository.addChat(event.chat.id);
  };

  delChat(event) {
    this.repository.delChat(event.chat.id);
  };

  getAllChats() {
    return this.repository.getAllChats();
  };

  sendImage(chatId, imageLink, caption) {
    return rxHttpsGet(imageLink)
      .selectMany(stream => {
        console.log('Sending image "%s" to chat %s...', caption, chatId);
        return this.rxSendPhoto({
          chat_id: chatId,
          caption: caption,
          files: {
            filename: caption + '.jpg',
            stream: stream,
            contentType: 'image/jpg'
          }
        });
      });
  };

  sendImageAll(imageLink, title, url) {
    return this.getAllChats()
      .selectMany(chatId => {
        return this.sendImage(chatId, imageLink, title, url);
      });
  };

  sendText(chatId, text) {
    return this.rxSendMessage({
      chat_id: chatId,
      text: text,
      parse_mode: 'Markdown'
    });
  };

  sendTextAll(text) {
    return this.getAllChats()
      .selectMany(chatId => {
        return this.sendText(chatId, text);
      });
  };

}

module.exports = SioBot;<|endoftext|>


<commit_before>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2013 Sebastian Werner
==================================================================================================
*/

"use strict";

(function(document, undefined)
{
  var hasPointerEvents = document.onpointerdown !== undefined || document.onmspointerdown !== undefined;
  var hasTouchEvents = document.ontouchstart !== undefined;
  var hasMouseEvents = !hasPointerEvents && !hasTouchEvents;
  var hasMouseEnterLeaveEvents = document.onmouseenter || document.onmouseleave;

  var special = {
    tap : core.bom.TapEvent
  }

  /** {=Map} Map of all event types supported */
  var supportedPointerEvents = core.Array.toKeys([
    "down", "up", "move", "over", "out", "cancel", "enter", "leave"
  ], true);

  if (jasy.Env.isSet("debug"))
  {
    var checkSignature = function(target, type, callback, context, capture)
    {
      if (target == null) {
        throw new Error("Invalid targrt");
      }

      core.Assert.isType(type, "String");

      if (!supportedPointerEvents[type]) {
        throw new Error("Unsupported event: " + type);
      }

      core.Assert.isType(callback, "Function");

      if (context != null) {
        core.Assert.isType(context, "String");
      }

      if (capture != null) {
        core.Assert.isType(capture, "Boolean");
      }
    };
  }

  /**
   * Pointer Events API for dealing with down/up/move events.
   *
   * Still work in progress.
   */
  core.Module("core.bom.PointerEvent",
  {
    has : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.remove(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Check for listener existance
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      return !!target[eventId];
    },

    add : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.add(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Lookup entry
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      var listeners = core.bom.event.Util.create(target, eventId);

      // Full event name to fire
      var pointerType = "pointer" + type;

      if (hasTouchEvents)
      {
        var db = target["$$ev-touch"];
        if (db)
        {
          console.log("Reuse existing event handlers...");
          db.push({type : type, callback : callback});
          return;
        }

        // Initialize
        db = target["$$ev-touch"] = [
          {type : type, callback : callback}
        ];

        // Primary
        var primaryIdentifier = null;

        // Touch Identifier:
        // An identification number for each touch point. When a touch point becomes active, it must be assigned an
        // identifier that is distinct from any other active touch point. While the touch point remains active, all
        // events that refer to it must assign it the same identifier.

        listeners.touchstart = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          var all = nativeEvent.touches;

          if (changed.length == 1 && all.length == 1) {
            primaryIdentifier = changed[0].identifier;
          }

          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointerdown: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointerdown");
            eventObject.isPrimary = point.identifier == primaryIdentifier;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "down") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();
          }
        };

        listeners.touchmove = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointermove: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointermove");
            eventObject.isPrimary = point.identifier == primaryIdentifier;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "move") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();
          }
        };

        listeners.touchend = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointerup: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointerup");
            eventObject.isPrimary = point.identifier == primaryIdentifier;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "up") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();

            // Reset primary
            if (point.identifier == primaryIdentifier) {
              primaryIdentifier = null;
            }
          }
        };

        listeners.touchcancel = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointercancel: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointercancel");
            eventObject.isPrimary = point.identifier == primaryIdentifier;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "cancel") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();

            // Reset primary
            if (point.identifier == primaryIdentifier) {
              primaryIdentifier = null;
            }
          }
        };

      }

      // Touch support
      if (false && hasTouchEvents)
      {
        if (type == "down") {
          var nativeType = "touchstart";
        } else if (type == "move") {
          var nativeType = "touchmove";
        } else if (type == "up") {
          var nativeType = "touchend";
        }

        listeners[nativeType] = function(nativeEvent)
        {
          var touches = nativeEvent.touches;
          var changedTouches = nativeEvent.changedTouches;

          for (var i=0, l=changedTouches.length; i<l; i++)
          {
            var touchPoint = changedTouches[i];
            var eventObject = core.bom.event.type.Pointer.obtain(touchPoint, pointerType);

            // Maybe that solution is too easy... let's see
            eventObject.isPrimary = touches.length < 2;

            callback(eventObject);
            eventObject.release();
          }
        };
      }

      // Mouse support
      else if (hasMouseEvents)
      {
        var nativeType = "mouse" + type;
        if (hasMouseEnterLeaveEvents && (nativeType == "mouseenter" || nativeType == "mouseleave")) {
          nativeType = nativeType == "mouseenter" ? "mouseover" : "mouseout";
        }

        listeners[nativeType] = function(nativeEvent)
        {
          var eventObject = core.bom.event.type.Pointer.obtain(nativeEvent, pointerType);
          callback(eventObject);
          eventObject.release();
        };
      }

      // Registering all native listeners
      core.bom.event.Util.addNative(target, eventId, capture);
    },

    remove : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.remove(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Unregistering all native listeners
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      core.bom.event.Util.removeNative(target, eventId, capture);
    }
  });

})(document);

<commit_msg>Tweaked target storage/restorage<commit_after>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2013 Sebastian Werner
==================================================================================================
*/

"use strict";

(function(document, undefined)
{
  var hasPointerEvents = document.onpointerdown !== undefined || document.onmspointerdown !== undefined;
  var hasTouchEvents = document.ontouchstart !== undefined;
  var hasMouseEvents = !hasPointerEvents && !hasTouchEvents;
  var hasMouseEnterLeaveEvents = document.onmouseenter || document.onmouseleave;

  var special = {
    tap : core.bom.TapEvent
  }

  /** {=Map} Map of all event types supported */
  var supportedPointerEvents = core.Array.toKeys([
    "down", "up", "move", "over", "out", "cancel", "enter", "leave"
  ], true);

  if (jasy.Env.isSet("debug"))
  {
    var checkSignature = function(target, type, callback, context, capture)
    {
      if (target == null) {
        throw new Error("Invalid targrt");
      }

      core.Assert.isType(type, "String");

      if (!supportedPointerEvents[type]) {
        throw new Error("Unsupported event: " + type);
      }

      core.Assert.isType(callback, "Function");

      if (context != null) {
        core.Assert.isType(context, "String");
      }

      if (capture != null) {
        core.Assert.isType(capture, "Boolean");
      }
    };
  }

  /**
   * Pointer Events API for dealing with down/up/move events.
   *
   * Still work in progress.
   */
  core.Module("core.bom.PointerEvent",
  {
    has : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.remove(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Check for listener existance
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      return !!target[eventId];
    },

    add : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.add(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Lookup entry
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      var listeners = core.bom.event.Util.create(target, eventId);

      // Full event name to fire
      var pointerType = "pointer" + type;

      if (hasTouchEvents)
      {
        var db = target["$$ev-touch"];
        if (db)
        {
          console.log("Reuse existing event handlers...");
          db.push({type : type, callback : callback});
          return;
        }

        // Initialize
        db = target["$$ev-touch"] = [
          {type : type, callback : callback}
        ];

        // Primary
        var primaryIdentifier = null;

        var previousTargets = {};

        // Touch Identifier:
        // An identification number for each touch point. When a touch point becomes active, it must be assigned an
        // identifier that is distinct from any other active touch point. While the touch point remains active, all
        // events that refer to it must assign it the same identifier.

        listeners.touchstart = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          var all = nativeEvent.touches;

          if (changed.length == 1 && all.length == 1) {
            primaryIdentifier = changed[0].identifier;
          }

          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];
            var currentTarget = previousTargets[point.identifier] = point.target;

            console.log("Fire pointerdown: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointerdown");
            eventObject.isPrimary = point.identifier == primaryIdentifier;
            eventObject.currentTarget = currentTarget;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "down") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();
          }
        };

        listeners.touchmove = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointermove: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointermove");
            eventObject.isPrimary = point.identifier == primaryIdentifier;
            eventObject.target = eventObject.currentTarget = previousTargets[point.identifier];

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "move") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();
          }
        };

        listeners.touchend = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointerup: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointerup");
            eventObject.isPrimary = point.identifier == primaryIdentifier;
            eventObject.target = eventObject.currentTarget = previousTargets[point.identifier];

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "up") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();

            // Reset primary
            if (point.identifier == primaryIdentifier) {
              primaryIdentifier = null;
            }
          }
        };

        listeners.touchcancel = function(nativeEvent)
        {
          var changed = nativeEvent.changedTouches;
          for (var i=0, il=changed.length; i<il; i++)
          {
            var point = changed[i];

            console.log("Fire pointercancel: primary=" + (point.identifier == primaryIdentifier));

            var eventObject = core.bom.event.type.Pointer.obtain(point, "pointercancel");
            eventObject.isPrimary = point.identifier == primaryIdentifier;

            for (var j=0, jl=db.length; j<jl; j++)
            {
              var entry = db[j];
              if (entry.type == "cancel") {
                entry.callback(eventObject);
              }
            }

            eventObject.release();

            // Reset primary
            if (point.identifier == primaryIdentifier) {
              primaryIdentifier = null;
            }
          }
        };

      }

      // Touch support
      if (false && hasTouchEvents)
      {
        if (type == "down") {
          var nativeType = "touchstart";
        } else if (type == "move") {
          var nativeType = "touchmove";
        } else if (type == "up") {
          var nativeType = "touchend";
        }

        listeners[nativeType] = function(nativeEvent)
        {
          var touches = nativeEvent.touches;
          var changedTouches = nativeEvent.changedTouches;

          for (var i=0, l=changedTouches.length; i<l; i++)
          {
            var touchPoint = changedTouches[i];
            var eventObject = core.bom.event.type.Pointer.obtain(touchPoint, pointerType);

            // Maybe that solution is too easy... let's see
            eventObject.isPrimary = touches.length < 2;

            callback(eventObject);
            eventObject.release();
          }
        };
      }

      // Mouse support
      else if (hasMouseEvents)
      {
        var nativeType = "mouse" + type;
        if (hasMouseEnterLeaveEvents && (nativeType == "mouseenter" || nativeType == "mouseleave")) {
          nativeType = nativeType == "mouseenter" ? "mouseover" : "mouseout";
        }

        listeners[nativeType] = function(nativeEvent)
        {
          var eventObject = core.bom.event.type.Pointer.obtain(nativeEvent, pointerType);
          callback(eventObject);
          eventObject.release();
        };
      }

      // Registering all native listeners
      core.bom.event.Util.addNative(target, eventId, capture);
    },

    remove : function(target, type, callback, context, capture)
    {
      var specialHandler = special[type];
      if (specialHandler) {
        return specialHandler.remove(target, type, callback, context, capture);
      }

      // Input parameter validation
      if (jasy.Env.isSet("debug")) {
        checkSignature(target, type, callback, context, capture);
      }

      // Hard-wire context to function, re-use existing bound functions
      if (context) {
        callback = core.Function.bind(callback, context);
      }

      // Unregistering all native listeners
      var eventId = core.bom.event.Util.getId(type, callback, capture);
      core.bom.event.Util.removeNative(target, eventId, capture);
    }
  });

})(document);

<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */

var util = require('./util')
  , transports = require('./transports')
  , debug = require('debug')('engine-client:socket')
  , EventEmitter = require('./event-emitter')

/**
 * Module exports.
 */

module.exports = Socket;

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts = arguments[1] || {};
    opts.host = uri.host;
    opts.secure = uri.scheme == 'https' || uri.scheme == 'wss';
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};
  this.secure = opts.secure || false;
  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.query = opts.query || {};
  this.query.uid = rnd();
  this.upgrade = false !== opts.upgrade;
  this.resource = opts.resource || 'default';
  this.path = (opts.path || '/engine.io').replace(/\/$/, '');
  this.path += '/' + this.resource + '/';
  this.forceJSONP = !!opts.forceJSONP;
  this.timestampParam = opts.timestampParam || 't';
  this.timestampRequests = !!opts.timestampRequests;
  this.flashPath = opts.flashPath || '';
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];
  this.policyPort = opts.policyPort || 843;
  this.open();
};

/**
 * Inherits from EventEmitter.
 */

util.inherits(Socket, EventEmitter);

/**
 * Creates transport of the given type.
 *
 * @param {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  debug('creating transport "%s"', name);
  var query = clone(this.query)
  query.transport = name;

  if (this.id) {
    query.sid = this.id;
  }

  var transport = new transports[name]({
      host: this.host
    , port: this.port
    , secure: this.secure
    , path: this.path
    , query: query
    , forceJSONP: this.forceJSONP
    , timestampRequests: this.timestampRequests
    , timestampParam: this.timestampParam
    , flashPath: this.flashPath
    , policyPort: this.policyPort
  });

  return transport;
};

function clone (obj) {
  var o = {};
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = obj[i];
    }
  }
  return o;
}

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  this.readyState = 'opening';
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  this.setTransport(transport);
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  if (this.transport) {
    debug('clearing existing transport');
    this.transport.removeAllListeners();
  }

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('drain', function () {
      self.flush();
    })
    .on('packet', function (packet) {
      self.onPacket(packet);
    })
    .on('error', function (e) {
      self.onError(e);
    })
    .on('close', function () {
      self.onClose('transport close');
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  debug('probing transport "%s"', name);
  var transport = this.createTransport(name, { probe: 1 })
    , self = this

  transport.once('open', function () {
    debug('probe transport "%s" opened', name);
    transport.send([{ type: 'ping', data: 'probe' }]);
    transport.once('packet', function (msg) {
      if ('pong' == msg.type && 'probe' == msg.data) {
        debug('probe transport "%s" pong', name);
        self.upgrading = true;
        self.emit('upgrading', transport);

        debug('pausing current transport "%s"', self.transport.name);
        self.transport.pause(function () {
          if ('closed' == self.readyState || 'closing' == self.readyState) return;
          debug('changing transport and sending upgrade packet');
          self.emit('upgrade', transport);
          self.setTransport(transport);
          transport.send([{ type: 'upgrade' }]);
          transport = null;
          self.upgrading = false;
          self.flush();
        });
      } else {
        debug('probe transport "%s" failed', name);
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  this.once('close', function () {
    if (transport) {
      debug('socket closed prematurely - aborting probe');
      transport.close();
      transport = null;
    }
  });

  this.once('upgrading', function (to) {
    if (transport && to.name != transport.name) {
      debug('"%s" works - aborting "%s"', to.name, transport.name);
      transport.close();
      transport = null;
    }
  });
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  debug('socket open');
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
  this.flush();

  if (this.upgrade && this.transport.pause) {
    debug('starting upgrade probes');
    for (var i = 0, l = this.upgrades.length; i < l; i++) {
      this.probe(this.upgrades[i]);
    }
  }
};

/**
 * Handles a packet.
 *
 * @api private
 */

Socket.prototype.onPacket = function (packet) {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
    switch (packet.type) {
      case 'open':
        this.onHandshake(util.parseJSON(packet.data));
        break;

      case 'ping':
        this.sendPacket('pong');
        this.setPingTimeout();
        break;

      case 'pong':
        this.setPingTimeout(this.pingInterval);
        break;

      case 'error':
        var err = new Error('server error');
        err.code = packet.data;
        this.emit('error', err);
        break;

      case 'message':
        this.emit('message', packet.data);
        var event = { data: packet.data };
        event.toString = function () {
          return packet.data;
        }
        this.onmessage && this.onmessage.call(this, event);
        break;
    }
  } else {
    debug('packet received with socket readyState "%s"', this.readyState);
  }
};

/**
 * Called upon handshake completion.
 *
 * @param {Object} handshake obj
 * @api private
 */

Socket.prototype.onHandshake = function (data) {
  this.emit('handshake', data);
  this.id = data.sid;
  this.transport.query.sid = data.sid;
  this.upgrades = data.upgrades;
  this.pingInterval = data.pingInterval;
  this.pingTimeout = data.pingTimeout;
  this.onOpen();
  this.setPingTimeout();
};

/**
 * Clears and sets a ping timeout based on the expected ping interval.
 *
 * @api private
 */

Socket.prototype.setPingTimeout = function (duration) {
  clearTimeout(this.pingTimeoutTimer);
  var self = this;
  this.pingTimeoutTimer = setTimeout(function () {
    self.onClose('ping timeout');
  }, duration || this.pingTimeout);
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if ('closed' != this.readyState && this.transport.writable
    && !this.upgrading && this.writeBuffer.length) {
    debug('flushing %d packets in socket', this.writeBuffer.length);
    this.transport.send(this.writeBuffer);
    this.writeBuffer = [];
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.sendPacket('message', msg);
  return this;
};

/**
 * Sends a packet.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.sendPacket = function (type, data) {
  var packet = { type: type, data: data };
  this.writeBuffer.push(packet);
  this.flush();
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    this.onClose('forced close');
    debug('socket closing - telling transport to close');
    this.transport.close();
  }

  return this;
};

/**
 * Called upon transport error
 *
 * @api private
 */

Socket.prototype.onError = function (err) {
  this.emit('error', err);
  this.onClose('transport error', err);
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function (reason, desc) {
  if ('closed' != this.readyState) {
    debug('socket close with reason: "%s"', reason);
    this.readyState = 'closed';
    this.emit('close', reason, desc);
    this.onclose && this.onclose.call(this);
  }
};

/**
 * Generates a random uid.
 *
 * @api private
 */

function rnd () {
  return String(Math.random()).substr(5) + String(Math.random()).substr(5);
}
<commit_msg>fix initial timeout<commit_after>/**
 * Module dependencies.
 */

var util = require('./util')
  , transports = require('./transports')
  , debug = require('debug')('engine-client:socket')
  , EventEmitter = require('./event-emitter')

/**
 * Module exports.
 */

module.exports = Socket;

/**
 * Socket constructor.
 *
 * @param {Object} options
 * @api public
 */

function Socket (opts) {
  if ('string' == typeof opts) {
    var uri = util.parseUri(opts);
    opts = arguments[1] || {};
    opts.host = uri.host;
    opts.secure = uri.scheme == 'https' || uri.scheme == 'wss';
    opts.port = uri.port || (opts.secure ? 443 : 80);
  }

  opts = opts || {};
  this.secure = opts.secure || false;
  this.host = opts.host || opts.hostname || 'localhost';
  this.port = opts.port || 80;
  this.query = opts.query || {};
  this.query.uid = rnd();
  this.upgrade = false !== opts.upgrade;
  this.resource = opts.resource || 'default';
  this.path = (opts.path || '/engine.io').replace(/\/$/, '');
  this.path += '/' + this.resource + '/';
  this.forceJSONP = !!opts.forceJSONP;
  this.timestampParam = opts.timestampParam || 't';
  this.timestampRequests = !!opts.timestampRequests;
  this.flashPath = opts.flashPath || '';
  this.transports = opts.transports || ['polling', 'websocket', 'flashsocket'];
  this.readyState = '';
  this.writeBuffer = [];
  this.policyPort = opts.policyPort || 843;
  this.open();
};

/**
 * Inherits from EventEmitter.
 */

util.inherits(Socket, EventEmitter);

/**
 * Creates transport of the given type.
 *
 * @param {String} transport name
 * @return {Transport}
 * @api private
 */

Socket.prototype.createTransport = function (name) {
  debug('creating transport "%s"', name);
  var query = clone(this.query)
  query.transport = name;

  if (this.id) {
    query.sid = this.id;
  }

  var transport = new transports[name]({
      host: this.host
    , port: this.port
    , secure: this.secure
    , path: this.path
    , query: query
    , forceJSONP: this.forceJSONP
    , timestampRequests: this.timestampRequests
    , timestampParam: this.timestampParam
    , flashPath: this.flashPath
    , policyPort: this.policyPort
  });

  return transport;
};

function clone (obj) {
  var o = {};
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = obj[i];
    }
  }
  return o;
}

/**
 * Initializes transport to use and starts probe.
 *
 * @api private
 */

Socket.prototype.open = function () {
  this.readyState = 'opening';
  var transport = this.createTransport(this.transports[0]);
  transport.open();
  this.setTransport(transport);
};

/**
 * Sets the current transport. Disables the existing one (if any).
 *
 * @api private
 */

Socket.prototype.setTransport = function (transport) {
  var self = this;

  if (this.transport) {
    debug('clearing existing transport');
    this.transport.removeAllListeners();
  }

  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
    .on('drain', function () {
      self.flush();
    })
    .on('packet', function (packet) {
      self.onPacket(packet);
    })
    .on('error', function (e) {
      self.onError(e);
    })
    .on('close', function () {
      self.onClose('transport close');
    })
};

/**
 * Probes a transport.
 *
 * @param {String} transport name
 * @api private
 */

Socket.prototype.probe = function (name) {
  debug('probing transport "%s"', name);
  var transport = this.createTransport(name, { probe: 1 })
    , self = this

  transport.once('open', function () {
    debug('probe transport "%s" opened', name);
    transport.send([{ type: 'ping', data: 'probe' }]);
    transport.once('packet', function (msg) {
      if ('pong' == msg.type && 'probe' == msg.data) {
        debug('probe transport "%s" pong', name);
        self.upgrading = true;
        self.emit('upgrading', transport);

        debug('pausing current transport "%s"', self.transport.name);
        self.transport.pause(function () {
          if ('closed' == self.readyState || 'closing' == self.readyState) return;
          debug('changing transport and sending upgrade packet');
          self.emit('upgrade', transport);
          self.setTransport(transport);
          transport.send([{ type: 'upgrade' }]);
          transport = null;
          self.upgrading = false;
          self.flush();
        });
      } else {
        debug('probe transport "%s" failed', name);
        var err = new Error('probe error');
        err.transport = transport.name;
        self.emit('error', err);
      }
    });
  });

  transport.open();

  this.once('close', function () {
    if (transport) {
      debug('socket closed prematurely - aborting probe');
      transport.close();
      transport = null;
    }
  });

  this.once('upgrading', function (to) {
    if (transport && to.name != transport.name) {
      debug('"%s" works - aborting "%s"', to.name, transport.name);
      transport.close();
      transport = null;
    }
  });
};

/**
 * Called when connection is deemed open.
 *
 * @api public
 */

Socket.prototype.onOpen = function () {
  debug('socket open');
  this.readyState = 'open';
  this.emit('open');
  this.onopen && this.onopen.call(this);
  this.flush();

  if (this.upgrade && this.transport.pause) {
    debug('starting upgrade probes');
    for (var i = 0, l = this.upgrades.length; i < l; i++) {
      this.probe(this.upgrades[i]);
    }
  }
};

/**
 * Handles a packet.
 *
 * @api private
 */

Socket.prototype.onPacket = function (packet) {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
    switch (packet.type) {
      case 'open':
        this.onHandshake(util.parseJSON(packet.data));
        break;

      case 'ping':
        this.sendPacket('pong');
        this.setPingTimeout();
        break;

      case 'pong':
        this.setPingTimeout(this.pingInterval);
        break;

      case 'error':
        var err = new Error('server error');
        err.code = packet.data;
        this.emit('error', err);
        break;

      case 'message':
        this.emit('message', packet.data);
        var event = { data: packet.data };
        event.toString = function () {
          return packet.data;
        }
        this.onmessage && this.onmessage.call(this, event);
        break;
    }
  } else {
    debug('packet received with socket readyState "%s"', this.readyState);
  }
};

/**
 * Called upon handshake completion.
 *
 * @param {Object} handshake obj
 * @api private
 */

Socket.prototype.onHandshake = function (data) {
  this.emit('handshake', data);
  this.id = data.sid;
  this.transport.query.sid = data.sid;
  this.upgrades = data.upgrades;
  this.pingInterval = data.pingInterval;
  this.pingTimeout = data.pingTimeout;
  this.onOpen();
  this.setPingTimeout(data.pingInterval + data.pingTimeout);
};

/**
 * Clears and sets a ping timeout based on the expected ping interval.
 *
 * @api private
 */

Socket.prototype.setPingTimeout = function (duration) {
  clearTimeout(this.pingTimeoutTimer);
  var self = this;
  this.pingTimeoutTimer = setTimeout(function () {
    self.onClose('ping timeout');
  }, duration || this.pingTimeout);
};

/**
 * Flush write buffers.
 *
 * @api private
 */

Socket.prototype.flush = function () {
  if ('closed' != this.readyState && this.transport.writable
    && !this.upgrading && this.writeBuffer.length) {
    debug('flushing %d packets in socket', this.writeBuffer.length);
    this.transport.send(this.writeBuffer);
    this.writeBuffer = [];
  }
};

/**
 * Sends a message.
 *
 * @param {String} message.
 * @return {Socket} for chaining.
 * @api public
 */

Socket.prototype.send = function (msg) {
  this.sendPacket('message', msg);
  return this;
};

/**
 * Sends a packet.
 *
 * @param {String} packet type.
 * @param {String} data.
 * @api private
 */

Socket.prototype.sendPacket = function (type, data) {
  var packet = { type: type, data: data };
  this.writeBuffer.push(packet);
  this.flush();
};

/**
 * Closes the connection.
 *
 * @api private
 */

Socket.prototype.close = function () {
  if ('opening' == this.readyState || 'open' == this.readyState) {
    this.onClose('forced close');
    debug('socket closing - telling transport to close');
    this.transport.close();
  }

  return this;
};

/**
 * Called upon transport error
 *
 * @api private
 */

Socket.prototype.onError = function (err) {
  this.emit('error', err);
  this.onClose('transport error', err);
};

/**
 * Called upon transport close.
 *
 * @api private
 */

Socket.prototype.onClose = function (reason, desc) {
  if ('closed' != this.readyState) {
    debug('socket close with reason: "%s"', reason);
    this.readyState = 'closed';
    this.emit('close', reason, desc);
    this.onclose && this.onclose.call(this);
  }
};

/**
 * Generates a random uid.
 *
 * @api private
 */

function rnd () {
  return String(Math.random()).substr(5) + String(Math.random()).substr(5);
}
<|endoftext|>