diff --git a/game-client.js b/game-client.js index e510c23..06d963c 100644 --- a/game-client.js +++ b/game-client.js @@ -3,6 +3,11 @@ var Player = require("./player.js"); var renderer = require("./game-renderer.js"); var consts = require("./game-consts.js"); +var GRID_SIZE = consts.GRID_SIZE; +var CELL_WIDTH = consts.CELL_WIDTH; + +renderer.allowAnimation = true; + /** * Provides requestAnimationFrame in a cross browser way. * @author paulirish / http://paulirish.com/ @@ -22,102 +27,127 @@ if ( !window.requestAnimationFrame ) { })(); } +var user, socket, frame; + +//Event listeners +$(document).keydown(function(e) { + if (user.dead) + return; + var newHeading = -1; + switch (e.which) + { + case 37: newHeading = 3; break; //LEFT + case 38: newHeading = 0; break; //UP + case 39: newHeading = 1; break; //RIGHT + case 40: newHeading = 2; break; //DOWN + default: return; //exit handler for other keys. + } + + if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^ + (user.currentHeading % 2 === 0))) + { + user.heading = newHeading; + if (socket) + socket.emit("frame", { + frame: frame, + heading: newHeading + }, function(success, msg) { + if (!success) + { + //TODO: restore frames. + console.error(msg); + } + }); + } + e.preventDefault(); +}); + $(function() { - var GRID_SIZE = consts.GRID_SIZE; - var CELL_WIDTH = consts.CELL_WIDTH; - - - var canvas = $("#main-ui")[0]; - var ctx2 = canvas.getContext('2d'); - var grid = renderer.grid; - renderer.allowAnimation = true; - //Load players. - for (var p = 0; p < 9; p++) - { - //TODO: socket loading. - var pRow = getRandomInt(0, GRID_SIZE); - var pCol = getRandomInt(0, GRID_SIZE); - var sdata = { - posX: pCol * CELL_WIDTH, - posY: pRow * CELL_WIDTH, - currentHeading: getRandomInt(0, 4), - //name: ..., - num: p - }; - - renderer.addPlayer(new Player(true, grid, sdata)); - - for (var dr = -1; dr <= 1; dr++) - for (var dc = -1; dc <= 1; dc++) - if (!grid.isOutOfBounds(dr + pRow, dc + pCol)) - grid.set(dr + pRow, dc + pCol, renderer.getPlayer(p)); - } - - //Load grid. - for (var r = 0; r < grid.size; r++) - { - for (var c = 0; c < grid.size; c++) - { - //TODO: load data. - //if (Math.random() > .9) - // grid.set(r, c, players[getRandomInt(0, players.length)]); - } - } - - var frameCount = 0; - - //TODO: current player index - var user = renderer.getPlayer(0); - renderer.initUser(user); - - function update() - { - renderer.update(); - } - - //Thanks to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random - function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min)) + min; - } - - function paintLoop(ctx) - { - renderer.paint(ctx); //TODO: pre-rendering. - - //TODO: sync each loop with server. (server will give frame count.) - frameCount++; - update(); - requestAnimationFrame(paintLoop); - } - - paintLoop(ctx2); - - - //Event listeners - $(document).keydown(function(e) { - if (user.dead) - return; - var newHeading = -1; - switch (e.which) - { - case 37: newHeading = 3; break; //LEFT - case 38: newHeading = 0; break; //UP - case 39: newHeading = 1; break; //RIGHT - case 40: newHeading = 2; break; //DOWN - default: return; //exit handler for other keys. - } - - if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^ - (user.currentHeading % 2 === 0))) - { - //TODO: notify server. - user.heading = newHeading; - } - e.preventDefault(); + //Socket connection. + socket = require('socket.io-client')('http://paper-io-thekidofarcrania.c9users.io:8081'); + socket.on('connect', function(){ + console.info("Connected to server."); + socket.emit('hello', { + name: 'Test player', + type: 0, //Free-for-all + gameid: -1 //Requested game-id, or -1 for anyone. + }, function(success) { + if (success) console.info("Connected to game!"); + else console.error("Unable to connect to game."); + }); }); -}); + socket.on('game', function(data){ + //Initialize game. + //TODO: display data.gameid --- game id # + renderer.reset(); + + //Load players. + data.players.forEach(function(p) { + renderer.addPlayer(new Player(true, grid, p)); + }); + user = renderer.getPlayerFromNum(data.num); + renderer.setUser(user); + + //Load grid. + var gridData = new Uint8Array(data.grid); + for (var r = 0; r < grid.size; r++) + for (var c = 0; c < grid.size; c++) + { + var ind = gridData[r * grid.size + c] - 1; + grid.set(r, c, ind === -1 ? null : renderer.getPlayer(ind)); + } + + frame = data.frame; + }); + + socket.on('notifyFrame', function(data) { + if (data.frame - 1 !== frame) + { + console.error("Frames don't match up!"); + socket.emit('requestFrame'); //Restore data. + return; + } + + frame++; + if (data.newPlayers) + { + data.newPlayers.forEach(function(p) { + renderer.addPlayer(new Player(true, grid, p)); + }); + } + + data.moves.forEach(function(val, i) { + if (renderer.getPlayer(val) !== user) + renderer.getPlayer(i).heading = val.heading; + }); + + paintLoop(); + }); + + socket.on('disconnect', function(){ + console.info("Server has disconnected. Creating new game."); + }); + + var deadFrames = 0; + function paintLoop() + { + renderer.paint(); + if (user.dead && deadFrames === 60) //One second of frames + { + //TODO: Show welcome screen. + deadFrames = 0; + return; + } + + renderer.update(); + if (user.dead) + { + socket.disconnect(); + deadFrames++; + requestAnimationFrame(paintLoop); + } + } +}); \ No newline at end of file diff --git a/game-consts.js b/game-consts.js index f151ecc..d3e7908 100644 --- a/game-consts.js +++ b/game-consts.js @@ -10,7 +10,8 @@ var consts = { GRID_SIZE: constant(80), CELL_WIDTH: constant(40), SPEED: constant(5), - BORDER_WIDTH: constant(20) + BORDER_WIDTH: constant(20), + MAX_PLAYERS: constant(255) }; Object.defineProperties(module.exports, consts); \ No newline at end of file diff --git a/game-core.js b/game-core.js index 41325cc..4e9ac69 100644 --- a/game-core.js +++ b/game-core.js @@ -28,7 +28,9 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) if (newPlayerFrames[val.num] < ANIMATE_FRAMES) newPlayerFrames[val.num]++; else + { val.move(); + } if (val.dead) adead.push(val); diff --git a/game-renderer.js b/game-renderer.js index 2f11416..955b9e6 100644 --- a/game-renderer.js +++ b/game-renderer.js @@ -35,13 +35,12 @@ $(function () { gameHeight = canvasHeight - BAR_HEIGHT; }); -var animateGrid = new Grid(GRID_SIZE); + var allowAnimation = true; -var players = []; -var allPlayers = []; -var newPlayerFrames = []; -var playerPortion = []; -var grid = new Grid(GRID_SIZE, function(row, col, before, after) { +var animateGrid, players, allPlayers, newPlayerFrames, playerPortion, grid, + animateTo, offset, user, lagPortion, portionSpeed, zoom, kills, showedDead; + +grid = new Grid(GRID_SIZE, function(row, col, before, after) { //Keep track of areas. if (before) playerPortion[before.num]--; @@ -58,16 +57,27 @@ var grid = new Grid(GRID_SIZE, function(row, col, before, after) { }); }); -var animateTo = [0, 0]; -var offset = [0, 0]; +function init() { + animateGrid = new Grid(GRID_SIZE); + grid.reset(); + + players = []; + allPlayers = []; + newPlayerFrames = []; + playerPortion = []; + + animateTo = [0, 0]; + offset = [0, 0]; + + user = null; + lagPortion = 0; + portionSpeed = 0; + zoom = 1; + kills = 0; + showedDead = false; +} -var user; -var lagPortion = 0; -var portionSpeed = 0; -var zoom = 1; -var kills = 0; - -var showedDead = false; +init(); //Paint methods. function paintGridBorder(ctx) @@ -207,17 +217,17 @@ function paintUIBar(ctx) var barOffset; ctx.fillStyle = "white"; ctx.font = "24px Changa"; - barOffset = ctx.measureText(user.name).width + 20; - ctx.fillText(user.name, 5, CELL_WIDTH - 5); + barOffset = ctx.measureText(user ? user.name : "").width + 20; + ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); //Draw filled bar. ctx.fillStyle = "rgba(180, 180, 180, .3)"; ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); var barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * lagPortion + MIN_BAR_WIDTH); - ctx.fillStyle = user.baseColor.rgbString(); + ctx.fillStyle = user ? user.baseColor.rgbString() : ""; ctx.fillRect(barOffset, 0, barSize, CELL_WIDTH); - ctx.fillStyle = user.shadowColor.rgbString(); + ctx.fillStyle = user ? user.shadowColor.rgbString() : ""; ctx.fillRect(barOffset, CELL_WIDTH, barSize, SHADOW_OFFSET); //Percentage @@ -274,7 +284,7 @@ function paint(ctx) ctx.restore(); paintUIBar(ctx); - if (user.dead && !showedDead) + if ((!user || user.dead) && !showedDead) { showedDead = true; console.log("You died!"); @@ -302,7 +312,10 @@ function update() { } //Change area percentage - var userPortion = playerPortion[user.num] / (GRID_SIZE * GRID_SIZE); + var userPortion; + if (user) userPortion = playerPortion[user.num] / (GRID_SIZE * GRID_SIZE); + else userPortion = 0; + if (lagPortion !== userPortion) { delta = userPortion - lagPortion; @@ -327,10 +340,10 @@ function update() { console.log(val.name + " is dead"); allPlayers[val.num] = undefined; }); - + //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. //TODO: show when this player is dead - centerOnPlayer(user, animateTo); + if (user) centerOnPlayer(user, animateTo); } //Helper methods. @@ -381,6 +394,8 @@ function getBounceOffset(frame) module.exports = exports = { addPlayer: function(player) { + if (allPlayers[player.num]) + return; //Already added. allPlayers[player.num] = players[players.length] = player; newPlayerFrames[player.num] = 0; playerPortion[player.num] = 0; @@ -390,16 +405,22 @@ module.exports = exports = { getPlayer: function(ind) { return players[ind]; }, - playerNum: function() { + getPlayerFromNum: function(num) { + return allPlayers[num]; + }, + playerSize: function() { return players.length; }, - initUser: function(player) { + setUser: function(player) { user = player; centerOnPlayer(user, offset); }, incrementKill: function() { kills++; }, + reset: function() { + init(); + }, paint: paintDoubleBuff, update: update }; diff --git a/game-server.js b/game-server.js index 0957972..2afe7a2 100644 --- a/game-server.js +++ b/game-server.js @@ -1,10 +1,12 @@ -var GRID_SIZE = 80; -var CELL_WIDTH = 40; -var MAX_PLAYERS = 255; var Grid = require("./grid.js"); var Player = require("./player.js"); var core = require("./game-core.js"); +var consts = require("./game-consts.js"); + +var GRID_SIZE = consts.GRID_SIZE; +var CELL_WIDTH = consts.CELL_WIDTH; +var MAX_PLAYERS = consts.MAX_PLAYERS; function Game(id) { @@ -50,7 +52,7 @@ function Game(id) newPlayers.push(p); newPlayerFrames.push(p); nextInd++; - core.initPlayer(p); + core.initPlayer(grid, p); var splayers = players.map(function(val) {return val.serialData();}); client.emit("game", { @@ -60,11 +62,12 @@ function Game(id) "players": splayers, "grid": gridSerialData(grid, players) }); + console.log(p.name + " joined."); //TODO: limit number of requests per frame. - client.on("requestFrame", function (fn) { + client.on("requestFrame", function () { var splayers = players.map(function(val) {return val.serialData();}); - fn({ + client.emit("game", { "num": p.num, "gameid": id, "frame": frame, @@ -72,6 +75,7 @@ function Game(id) "grid": gridSerialData(grid, players) }); }); + client.on("frame", function(data, errorHan){ if (typeof data === "function") { @@ -86,8 +90,8 @@ function Game(id) errorHan(false, "No data supplied."); else if (!checkInt(data.frame, 0, Infinity)) errorHan(false, "Requires a valid non-negative frame integer."); - else if (data.frame < frame) - errorHan(false, "Late frame received."); + //else if (data.frame < frame) + // errorHan(false, "Late frame received."); else if (data.frame > frame) errorHan(false, "Invalid frame received."); else @@ -109,10 +113,11 @@ function Game(id) this.tickFrame = function() { + //TODO: notify those that drop out. var snews = newPlayers.map(function(val) {return val.serialData();}); var moves = players.map(function(val) {return {heading: val.heading};}); - var data = {moves: moves}; + var data = {frame: frame + 1, moves: moves}; if (snews.length > 0) { data.newPlayers = snews; @@ -129,7 +134,10 @@ function Game(id) { var dead = []; core.updateFrame(grid, players, newPlayerFrames, dead); - dead.forEach(function(val) { val.client.disconnect(true); }); + dead.forEach(function(val) { + console.log(val.name + " died."); + val.client.disconnect(true); + }); } } diff --git a/grid.js b/grid.js index c39f166..dc36e24 100644 --- a/grid.js +++ b/grid.js @@ -1,6 +1,7 @@ function Grid(size, changeCallback) { var grid = new Array(size); + var modified = false; var data = { grid: grid, @@ -26,8 +27,17 @@ function Grid(size, changeCallback) if (typeof changeCallback === "function") changeCallback(row, col, before, value); + modified = true; + return before; } + this.reset = function() { + if (modified) + { + grid = new Array(size); + modified = false; + } + } this.isOutOfBounds = isOutOfBounds.bind(this, data); diff --git a/player.js b/player.js index ca32383..f5650c2 100644 --- a/player.js +++ b/player.js @@ -1,6 +1,10 @@ var Stack = require("./stack.js"); var Color = require("./color.js"); var Grid = require("./grid.js"); +var consts = require("./game-consts.js"); + +var GRID_SIZE = consts.GRID_SIZE; +var CELL_WIDTH = consts.CELL_WIDTH; function defineGetter(getter) { return { @@ -24,9 +28,6 @@ function defineAccessorProperties(thisobj, data /*, names...*/) Object.defineProperties(thisobj, descript); } -var CELL_WIDTH = 40; -var GRID_SIZE = 80; - function TailMove(orientation) { this.move = 1; @@ -53,7 +54,7 @@ function Tail(player, sdata) { data.startRow = data.prevRow = sdata.startRow || 0; data.startCol = data.prevCol = sdata.startCol || 0; - sdata.forEach(function(val) { + sdata.tail.forEach(function(val) { addTail(data, val.orientation, val.move); }); } @@ -68,11 +69,11 @@ function Tail(player, sdata) //Instance methods. function serialData(data) { - return JSON.serialize({ + return { tail: data.tail, startRow: data.startRow, startCol: data.startCol - }); + }; } function setTailGrid(data, tailGrid, r, c) @@ -430,7 +431,7 @@ Player.prototype.render = function(ctx, fade) var mid = CELL_WIDTH / 2; var grd = ctx.createRadialGradient(this.posX + mid, this.posY + mid - SHADOW_OFFSET, 1, this.posX + mid, this.posY + mid - SHADOW_OFFSET, CELL_WIDTH); - grd.addColorStop(0, this.baseColor.rgbString().deriveAlpha(fade)); + grd.addColorStop(0, this.baseColor.deriveAlpha(fade).rgbString()); grd.addColorStop(1, new Color(0, 0, 1, fade).rgbString()); ctx.fillStyle = grd; ctx.fillRect(this.posX - 1, this.posY - SHADOW_OFFSET, CELL_WIDTH + 2, CELL_WIDTH); diff --git a/public/bundle.js b/public/bundle.js index 36204d4..722bb24 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -1,5 +1,189 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],3:[function(require,module,exports){ + function Color(h, s, l, a) { @@ -95,12 +279,17 @@ function hslToRgb(h, s, l){ } module.exports = Color; -},{}],2:[function(require,module,exports){ +},{}],4:[function(require,module,exports){ /* global $ */ var Player = require("./player.js"); var renderer = require("./game-renderer.js"); var consts = require("./game-consts.js"); +var GRID_SIZE = consts.GRID_SIZE; +var CELL_WIDTH = consts.CELL_WIDTH; + +renderer.allowAnimation = true; + /** * Provides requestAnimationFrame in a cross browser way. * @author paulirish / http://paulirish.com/ @@ -120,107 +309,132 @@ if ( !window.requestAnimationFrame ) { })(); } +var user, socket, frame; + +//Event listeners +$(document).keydown(function(e) { + if (user.dead) + return; + var newHeading = -1; + switch (e.which) + { + case 37: newHeading = 3; break; //LEFT + case 38: newHeading = 0; break; //UP + case 39: newHeading = 1; break; //RIGHT + case 40: newHeading = 2; break; //DOWN + default: return; //exit handler for other keys. + } + + if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^ + (user.currentHeading % 2 === 0))) + { + user.heading = newHeading; + if (socket) + socket.emit("frame", { + frame: frame, + heading: newHeading + }, function(success, msg) { + if (!success) + { + //TODO: restore frames. + console.error(msg); + } + }); + } + e.preventDefault(); +}); + $(function() { - var GRID_SIZE = consts.GRID_SIZE; - var CELL_WIDTH = consts.CELL_WIDTH; - - - var canvas = $("#main-ui")[0]; - var ctx2 = canvas.getContext('2d'); - var grid = renderer.grid; - renderer.allowAnimation = true; - //Load players. - for (var p = 0; p < 9; p++) - { - //TODO: socket loading. - var pRow = getRandomInt(0, GRID_SIZE); - var pCol = getRandomInt(0, GRID_SIZE); - var sdata = { - posX: pCol * CELL_WIDTH, - posY: pRow * CELL_WIDTH, - currentHeading: getRandomInt(0, 4), - //name: ..., - num: p - }; - - renderer.addPlayer(new Player(true, grid, sdata)); - - for (var dr = -1; dr <= 1; dr++) - for (var dc = -1; dc <= 1; dc++) - if (!grid.isOutOfBounds(dr + pRow, dc + pCol)) - grid.set(dr + pRow, dc + pCol, renderer.getPlayer(p)); - } - - //Load grid. - for (var r = 0; r < grid.size; r++) - { - for (var c = 0; c < grid.size; c++) - { - //TODO: load data. - //if (Math.random() > .9) - // grid.set(r, c, players[getRandomInt(0, players.length)]); - } - } - - var frameCount = 0; - - //TODO: current player index - var user = renderer.getPlayer(0); - renderer.initUser(user); - - function update() - { - renderer.update(); - } - - //Thanks to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random - function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min)) + min; - } - - function paintLoop(ctx) - { - renderer.paint(ctx); //TODO: pre-rendering. - - //TODO: sync each loop with server. (server will give frame count.) - frameCount++; - update(); - requestAnimationFrame(paintLoop); - } - - paintLoop(ctx2); - - - //Event listeners - $(document).keydown(function(e) { - if (user.dead) - return; - var newHeading = -1; - switch (e.which) - { - case 37: newHeading = 3; break; //LEFT - case 38: newHeading = 0; break; //UP - case 39: newHeading = 1; break; //RIGHT - case 40: newHeading = 2; break; //DOWN - default: return; //exit handler for other keys. - } - - if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^ - (user.currentHeading % 2 === 0))) - { - //TODO: notify server. - user.heading = newHeading; - } - e.preventDefault(); + //Socket connection. + socket = require('socket.io-client')('http://paper-io-thekidofarcrania.c9users.io:8081'); + socket.on('connect', function(){ + console.info("Connected to server."); + socket.emit('hello', { + name: 'Test player', + type: 0, //Free-for-all + gameid: -1 //Requested game-id, or -1 for anyone. + }, function(success) { + if (success) console.info("Connected to game!"); + else console.error("Unable to connect to game."); + }); }); -}); - -},{"./game-consts.js":3,"./game-renderer.js":5,"./player.js":7}],3:[function(require,module,exports){ + socket.on('game', function(data){ + //Initialize game. + //TODO: display data.gameid --- game id # + renderer.reset(); + + //Load players. + data.players.forEach(function(p) { + renderer.addPlayer(new Player(true, grid, p)); + }); + user = renderer.getPlayerFromNum(data.num); + renderer.setUser(user); + + //Load grid. + var gridData = new Uint8Array(data.grid); + for (var r = 0; r < grid.size; r++) + for (var c = 0; c < grid.size; c++) + { + var ind = gridData[r * grid.size + c] - 1; + grid.set(r, c, ind === -1 ? null : renderer.getPlayer(ind)); + } + + frame = data.frame; + }); + + socket.on('notifyFrame', function(data) { + if (data.frame - 1 !== frame) + { + console.error("Frames don't match up!"); + socket.emit('requestFrame'); //Restore data. + return; + } + + frame++; + if (data.newPlayers) + { + data.newPlayers.forEach(function(p) { + renderer.addPlayer(new Player(true, grid, p)); + }); + } + + data.moves.forEach(function(val, i) { + if (renderer.getPlayer(val) !== user) + renderer.getPlayer(i).heading = val.heading; + }); + + paintLoop(); + }); + + socket.on('disconnect', function(){ + console.info("Server has disconnected. Creating new game."); + }); + + var deadFrames = 0; + function paintLoop() + { + renderer.paint(); + if (user.dead && deadFrames === 60) //One second of frames + { + //TODO: Show welcome screen. + deadFrames = 0; + return; + } + + renderer.update(); + if (user.dead) + { + console.log("FF"); + socket.disconnect(); + deadFrames++; + requestAnimationFrame(paintLoop); + } + } +}); +},{"./game-consts.js":5,"./game-renderer.js":7,"./player.js":58,"socket.io-client":44}],5:[function(require,module,exports){ function constant(val) { return { @@ -233,11 +447,12 @@ var consts = { GRID_SIZE: constant(80), CELL_WIDTH: constant(40), SPEED: constant(5), - BORDER_WIDTH: constant(20) + BORDER_WIDTH: constant(20), + MAX_PLAYERS: constant(255) }; Object.defineProperties(module.exports, consts); -},{}],4:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ var ANIMATE_FRAMES = 24; var CELL_WIDTH = 40; @@ -268,7 +483,9 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) if (newPlayerFrames[val.num] < ANIMATE_FRAMES) newPlayerFrames[val.num]++; else + { val.move(); + } if (val.dead) adead.push(val); @@ -377,7 +594,7 @@ function area(player) else return Math.abs(player.posX - xDest); } -},{}],5:[function(require,module,exports){ +},{}],7:[function(require,module,exports){ /* global $ */ var Color = require("./color.js"); var Grid = require("./grid.js"); @@ -415,13 +632,12 @@ $(function () { gameHeight = canvasHeight - BAR_HEIGHT; }); -var animateGrid = new Grid(GRID_SIZE); + var allowAnimation = true; -var players = []; -var allPlayers = []; -var newPlayerFrames = []; -var playerPortion = []; -var grid = new Grid(GRID_SIZE, function(row, col, before, after) { +var animateGrid, players, allPlayers, newPlayerFrames, playerPortion, grid, + animateTo, offset, user, lagPortion, portionSpeed, zoom, kills, showedDead; + +grid = new Grid(GRID_SIZE, function(row, col, before, after) { //Keep track of areas. if (before) playerPortion[before.num]--; @@ -438,16 +654,27 @@ var grid = new Grid(GRID_SIZE, function(row, col, before, after) { }); }); -var animateTo = [0, 0]; -var offset = [0, 0]; +function init() { + animateGrid = new Grid(GRID_SIZE); + grid.reset(); + + players = []; + allPlayers = []; + newPlayerFrames = []; + playerPortion = []; + + animateTo = [0, 0]; + offset = [0, 0]; + + user = null; + lagPortion = 0; + portionSpeed = 0; + zoom = 1; + kills = 0; + showedDead = false; +} -var user; -var lagPortion = 0; -var portionSpeed = 0; -var zoom = 1; -var kills = 0; - -var showedDead = false; +init(); //Paint methods. function paintGridBorder(ctx) @@ -587,17 +814,17 @@ function paintUIBar(ctx) var barOffset; ctx.fillStyle = "white"; ctx.font = "24px Changa"; - barOffset = ctx.measureText(user.name).width + 20; - ctx.fillText(user.name, 5, CELL_WIDTH - 5); + barOffset = ctx.measureText(user ? user.name : "").width + 20; + ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); //Draw filled bar. ctx.fillStyle = "rgba(180, 180, 180, .3)"; ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); var barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * lagPortion + MIN_BAR_WIDTH); - ctx.fillStyle = user.baseColor.rgbString(); + ctx.fillStyle = user ? user.baseColor.rgbString() : ""; ctx.fillRect(barOffset, 0, barSize, CELL_WIDTH); - ctx.fillStyle = user.shadowColor.rgbString(); + ctx.fillStyle = user ? user.shadowColor.rgbString() : ""; ctx.fillRect(barOffset, CELL_WIDTH, barSize, SHADOW_OFFSET); //Percentage @@ -654,7 +881,7 @@ function paint(ctx) ctx.restore(); paintUIBar(ctx); - if (user.dead && !showedDead) + if ((!user || user.dead) && !showedDead) { showedDead = true; console.log("You died!"); @@ -682,7 +909,10 @@ function update() { } //Change area percentage - var userPortion = playerPortion[user.num] / (GRID_SIZE * GRID_SIZE); + var userPortion; + if (user) userPortion = playerPortion[user.num] / (GRID_SIZE * GRID_SIZE); + else userPortion = 0; + if (lagPortion !== userPortion) { delta = userPortion - lagPortion; @@ -707,10 +937,10 @@ function update() { console.log(val.name + " is dead"); allPlayers[val.num] = undefined; }); - + //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. //TODO: show when this player is dead - centerOnPlayer(user, animateTo); + if (user) centerOnPlayer(user, animateTo); } //Helper methods. @@ -761,6 +991,8 @@ function getBounceOffset(frame) module.exports = exports = { addPlayer: function(player) { + if (allPlayers[player.num]) + return; //Already added. allPlayers[player.num] = players[players.length] = player; newPlayerFrames[player.num] = 0; playerPortion[player.num] = 0; @@ -770,16 +1002,22 @@ module.exports = exports = { getPlayer: function(ind) { return players[ind]; }, - playerNum: function() { + getPlayerFromNum: function(num) { + return allPlayers[num]; + }, + playerSize: function() { return players.length; }, - initUser: function(player) { + setUser: function(player) { user = player; centerOnPlayer(user, offset); }, incrementKill: function() { kills++; }, + reset: function() { + init(); + }, paint: paintDoubleBuff, update: update }; @@ -795,10 +1033,11 @@ Object.defineProperties(exports, { enumerable: true } }); -},{"./color.js":1,"./game-consts.js":3,"./game-core.js":4,"./grid.js":6}],6:[function(require,module,exports){ +},{"./color.js":3,"./game-consts.js":5,"./game-core.js":6,"./grid.js":8}],8:[function(require,module,exports){ function Grid(size, changeCallback) { var grid = new Array(size); + var modified = false; var data = { grid: grid, @@ -824,8 +1063,17 @@ function Grid(size, changeCallback) if (typeof changeCallback === "function") changeCallback(row, col, before, value); + modified = true; + return before; } + this.reset = function() { + if (modified) + { + grid = new Array(size); + modified = false; + } + } this.isOutOfBounds = isOutOfBounds.bind(this, data); @@ -842,10 +1090,7788 @@ function isOutOfBounds(data, row, col) } module.exports = Grid; -},{}],7:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ +module.exports = after + +function after(count, callback, err_cb) { + var bail = false + err_cb = err_cb || noop + proxy.count = count + + return (count === 0) ? callback() : proxy + + function proxy(err, result) { + if (proxy.count <= 0) { + throw new Error('after called too many times') + } + --proxy.count + + // after first error, rest are passed to err_cb + if (err) { + bail = true + callback(err) + // future error callbacks will go to error handler + callback = err_cb + } else if (proxy.count === 0 && !bail) { + callback(null, result) + } + } +} + +function noop() {} + +},{}],10:[function(require,module,exports){ +/** + * An abstraction for slicing an arraybuffer even when + * ArrayBuffer.prototype.slice is not supported + * + * @api public + */ + +module.exports = function(arraybuffer, start, end) { + var bytes = arraybuffer.byteLength; + start = start || 0; + end = end || bytes; + + if (arraybuffer.slice) { return arraybuffer.slice(start, end); } + + if (start < 0) { start += bytes; } + if (end < 0) { end += bytes; } + if (end > bytes) { end = bytes; } + + if (start >= bytes || start >= end || bytes === 0) { + return new ArrayBuffer(0); + } + + var abv = new Uint8Array(arraybuffer); + var result = new Uint8Array(end - start); + for (var i = start, ii = 0; i < end; i++, ii++) { + result[ii] = abv[i]; + } + return result.buffer; +}; + +},{}],11:[function(require,module,exports){ + +/** + * Expose `Backoff`. + */ + +module.exports = Backoff; + +/** + * Initialize backoff timer with `opts`. + * + * - `min` initial timeout in milliseconds [100] + * - `max` max timeout [10000] + * - `jitter` [0] + * - `factor` [2] + * + * @param {Object} opts + * @api public + */ + +function Backoff(opts) { + opts = opts || {}; + this.ms = opts.min || 100; + this.max = opts.max || 10000; + this.factor = opts.factor || 2; + this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; + this.attempts = 0; +} + +/** + * Return the backoff duration. + * + * @return {Number} + * @api public + */ + +Backoff.prototype.duration = function(){ + var ms = this.ms * Math.pow(this.factor, this.attempts++); + if (this.jitter) { + var rand = Math.random(); + var deviation = Math.floor(rand * this.jitter * ms); + ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; + } + return Math.min(ms, this.max) | 0; +}; + +/** + * Reset the number of attempts. + * + * @api public + */ + +Backoff.prototype.reset = function(){ + this.attempts = 0; +}; + +/** + * Set the minimum duration + * + * @api public + */ + +Backoff.prototype.setMin = function(min){ + this.ms = min; +}; + +/** + * Set the maximum duration + * + * @api public + */ + +Backoff.prototype.setMax = function(max){ + this.max = max; +}; + +/** + * Set the jitter + * + * @api public + */ + +Backoff.prototype.setJitter = function(jitter){ + this.jitter = jitter; +}; + + +},{}],12:[function(require,module,exports){ +/* + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ +(function(){ + "use strict"; + + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // Use a lookup table to find the index. + var lookup = new Uint8Array(256); + for (var i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + exports.encode = function(arraybuffer) { + var bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64 = ""; + + for (i = 0; i < len; i+=3) { + base64 += chars[bytes[i] >> 2]; + base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + "="; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + "=="; + } + + return base64; + }; + + exports.decode = function(base64) { + var bufferLength = base64.length * 0.75, + len = base64.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + var arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i+=4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i+1)]; + encoded3 = lookup[base64.charCodeAt(i+2)]; + encoded4 = lookup[base64.charCodeAt(i+3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; + }; +})(); + +},{}],13:[function(require,module,exports){ +(function (global){ +/** + * Create a blob builder even when vendor prefixes exist + */ + +var BlobBuilder = global.BlobBuilder + || global.WebKitBlobBuilder + || global.MSBlobBuilder + || global.MozBlobBuilder; + +/** + * Check if Blob constructor is supported + */ + +var blobSupported = (function() { + try { + var a = new Blob(['hi']); + return a.size === 2; + } catch(e) { + return false; + } +})(); + +/** + * Check if Blob constructor supports ArrayBufferViews + * Fails in Safari 6, so we need to map to ArrayBuffers there. + */ + +var blobSupportsArrayBufferView = blobSupported && (function() { + try { + var b = new Blob([new Uint8Array([1,2])]); + return b.size === 2; + } catch(e) { + return false; + } +})(); + +/** + * Check if BlobBuilder is supported + */ + +var blobBuilderSupported = BlobBuilder + && BlobBuilder.prototype.append + && BlobBuilder.prototype.getBlob; + +/** + * Helper function that maps ArrayBufferViews to ArrayBuffers + * Used by BlobBuilder constructor and old browsers that didn't + * support it in the Blob constructor. + */ + +function mapArrayBufferViews(ary) { + for (var i = 0; i < ary.length; i++) { + var chunk = ary[i]; + if (chunk.buffer instanceof ArrayBuffer) { + var buf = chunk.buffer; + + // if this is a subarray, make a copy so we only + // include the subarray region from the underlying buffer + if (chunk.byteLength !== buf.byteLength) { + var copy = new Uint8Array(chunk.byteLength); + copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); + buf = copy.buffer; + } + + ary[i] = buf; + } + } +} + +function BlobBuilderConstructor(ary, options) { + options = options || {}; + + var bb = new BlobBuilder(); + mapArrayBufferViews(ary); + + for (var i = 0; i < ary.length; i++) { + bb.append(ary[i]); + } + + return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); +}; + +function BlobConstructor(ary, options) { + mapArrayBufferViews(ary); + return new Blob(ary, options || {}); +}; + +module.exports = (function() { + if (blobSupported) { + return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; + } else if (blobBuilderSupported) { + return BlobBuilderConstructor; + } else { + return undefined; + } +})(); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],14:[function(require,module,exports){ +/** + * Slice reference. + */ + +var slice = [].slice; + +/** + * Bind `obj` to `fn`. + * + * @param {Object} obj + * @param {Function|String} fn or string + * @return {Function} + * @api public + */ + +module.exports = function(obj, fn){ + if ('string' == typeof fn) fn = obj[fn]; + if ('function' != typeof fn) throw new Error('bind() requires a function'); + var args = slice.call(arguments, 2); + return function(){ + return fn.apply(obj, args.concat(slice.call(arguments))); + } +}; + +},{}],15:[function(require,module,exports){ + +/** + * Expose `Emitter`. + */ + +module.exports = Emitter; + +/** + * Initialize a new `Emitter`. + * + * @api public + */ + +function Emitter(obj) { + if (obj) return mixin(obj); +}; + +/** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + +function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; +} + +/** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.on = +Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; +}; + +/** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; +}; + +/** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.off = +Emitter.prototype.removeListener = +Emitter.prototype.removeAllListeners = +Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; +}; + +/** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + +Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; +}; + +/** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + +Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; +}; + +/** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + +Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; +}; + +},{}],16:[function(require,module,exports){ + +module.exports = function(a, b){ + var fn = function(){}; + fn.prototype = b.prototype; + a.prototype = new fn; + a.prototype.constructor = a; +}; +},{}],17:[function(require,module,exports){ + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = require('./debug'); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return ('WebkitAppearance' in document.documentElement.style) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (window.console && (console.firebug || (console.exception && console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + return JSON.stringify(v); +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs() { + var args = arguments; + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return args; + + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); + return args; +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage(){ + try { + return window.localStorage; + } catch (e) {} +} + +},{"./debug":18}],18:[function(require,module,exports){ + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = debug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = require('ms'); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + +exports.formatters = {}; + +/** + * Previously assigned color. + */ + +var prevColor = 0; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * + * @return {Number} + * @api private + */ + +function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function debug(namespace) { + + // define the `disabled` version + function disabled() { + } + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + + var self = enabled; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); + + var args = Array.prototype.slice.call(arguments); + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); + } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + enabled.enabled = true; + + var fn = exports.enabled(namespace) ? enabled : disabled; + + fn.namespace = namespace; + + return fn; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + +},{"ms":19}],19:[function(require,module,exports){ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + return options.long + ? long(val) + : short(val); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = '' + str; + if (str.length > 10000) return; + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function short(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function long(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; +} + +},{}],20:[function(require,module,exports){ + +module.exports = require('./lib/index'); + +},{"./lib/index":21}],21:[function(require,module,exports){ + +module.exports = require('./socket'); + +/** + * Exports parser + * + * @api public + * + */ +module.exports.parser = require('engine.io-parser'); + +},{"./socket":22,"engine.io-parser":33}],22:[function(require,module,exports){ +(function (global){ +/** + * Module dependencies. + */ + +var transports = require('./transports/index'); +var Emitter = require('component-emitter'); +var debug = require('debug')('engine.io-client:socket'); +var index = require('indexof'); +var parser = require('engine.io-parser'); +var parseuri = require('parseuri'); +var parsejson = require('parsejson'); +var parseqs = require('parseqs'); + +/** + * Module exports. + */ + +module.exports = Socket; + +/** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + +function Socket (uri, opts) { + if (!(this instanceof Socket)) return new Socket(uri, opts); + + opts = opts || {}; + + if (uri && 'object' === typeof uri) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.hostname = uri.host; + opts.secure = uri.protocol === 'https' || uri.protocol === 'wss'; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parseuri(opts.host).host; + } + + this.secure = null != opts.secure ? opts.secure + : (global.location && 'https:' === location.protocol); + + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = this.secure ? '443' : '80'; + } + + this.agent = opts.agent || false; + this.hostname = opts.hostname || + (global.location ? location.hostname : 'localhost'); + this.port = opts.port || (global.location && location.port + ? location.port + : (this.secure ? 443 : 80)); + this.query = opts.query || {}; + if ('string' === typeof this.query) this.query = parseqs.decode(this.query); + this.upgrade = false !== opts.upgrade; + this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; + this.forceJSONP = !!opts.forceJSONP; + this.jsonp = false !== opts.jsonp; + this.forceBase64 = !!opts.forceBase64; + this.enablesXDR = !!opts.enablesXDR; + this.timestampParam = opts.timestampParam || 't'; + this.timestampRequests = opts.timestampRequests; + this.transports = opts.transports || ['polling', 'websocket']; + this.readyState = ''; + this.writeBuffer = []; + this.prevBufferLen = 0; + this.policyPort = opts.policyPort || 843; + this.rememberUpgrade = opts.rememberUpgrade || false; + this.binaryType = null; + this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; + this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; + + if (true === this.perMessageDeflate) this.perMessageDeflate = {}; + if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { + this.perMessageDeflate.threshold = 1024; + } + + // SSL options for Node.js client + this.pfx = opts.pfx || null; + this.key = opts.key || null; + this.passphrase = opts.passphrase || null; + this.cert = opts.cert || null; + this.ca = opts.ca || null; + this.ciphers = opts.ciphers || null; + this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; + this.forceNode = !!opts.forceNode; + + // other options for Node.js client + var freeGlobal = typeof global === 'object' && global; + if (freeGlobal.global === freeGlobal) { + if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { + this.extraHeaders = opts.extraHeaders; + } + + if (opts.localAddress) { + this.localAddress = opts.localAddress; + } + } + + // set on handshake + this.id = null; + this.upgrades = null; + this.pingInterval = null; + this.pingTimeout = null; + + // set on heartbeat + this.pingIntervalTimer = null; + this.pingTimeoutTimer = null; + + this.open(); +} + +Socket.priorWebsocketSuccess = false; + +/** + * Mix in `Emitter`. + */ + +Emitter(Socket.prototype); + +/** + * Protocol version. + * + * @api public + */ + +Socket.protocol = parser.protocol; // this is an int + +/** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + +Socket.Socket = Socket; +Socket.Transport = require('./transport'); +Socket.transports = require('./transports/index'); +Socket.parser = require('engine.io-parser'); + +/** + * 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); + + // append engine.io protocol identifier + query.EIO = parser.protocol; + + // transport name + query.transport = name; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + agent: this.agent, + hostname: this.hostname, + port: this.port, + secure: this.secure, + path: this.path, + query: query, + forceJSONP: this.forceJSONP, + jsonp: this.jsonp, + forceBase64: this.forceBase64, + enablesXDR: this.enablesXDR, + timestampRequests: this.timestampRequests, + timestampParam: this.timestampParam, + policyPort: this.policyPort, + socket: this, + pfx: this.pfx, + key: this.key, + passphrase: this.passphrase, + cert: this.cert, + ca: this.ca, + ciphers: this.ciphers, + rejectUnauthorized: this.rejectUnauthorized, + perMessageDeflate: this.perMessageDeflate, + extraHeaders: this.extraHeaders, + forceNode: this.forceNode, + localAddress: this.localAddress + }); + + 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 () { + var transport; + if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) { + transport = 'websocket'; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit('error', 'No transports available'); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = 'opening'; + + // Retry with the next transport if the transport is disabled (jsonp: false) + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); +}; + +/** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + +Socket.prototype.setTransport = function (transport) { + debug('setting transport %s', transport.name); + var self = this; + + if (this.transport) { + debug('clearing existing transport %s', this.transport.name); + this.transport.removeAllListeners(); + } + + // set up transport + this.transport = transport; + + // set up transport listeners + transport + .on('drain', function () { + self.onDrain(); + }) + .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 }); + var failed = false; + var self = this; + + Socket.priorWebsocketSuccess = false; + + function onTransportOpen () { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + if (failed) return; + + debug('probe transport "%s" opened', name); + transport.send([{ type: 'ping', data: 'probe' }]); + transport.once('packet', function (msg) { + if (failed) return; + if ('pong' === msg.type && 'probe' === msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit('upgrading', transport); + if (!transport) return; + Socket.priorWebsocketSuccess = 'websocket' === transport.name; + + debug('pausing current transport "%s"', self.transport.name); + self.transport.pause(function () { + if (failed) return; + if ('closed' === self.readyState) return; + debug('changing transport and sending upgrade packet'); + + cleanup(); + + self.setTransport(transport); + transport.send([{ type: 'upgrade' }]); + self.emit('upgrade', transport); + 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('upgradeError', err); + } + }); + } + + function freezeTransport () { + if (failed) return; + + // Any callback called by transport should be ignored since now + failed = true; + + cleanup(); + + transport.close(); + transport = null; + } + + // Handle any error that happens while probing + function onerror (err) { + var error = new Error('probe error: ' + err); + error.transport = transport.name; + + freezeTransport(); + + debug('probe transport "%s" failed because of error: %s', name, err); + + self.emit('upgradeError', error); + } + + function onTransportClose () { + onerror('transport closed'); + } + + // When the socket is closed while we're probing + function onclose () { + onerror('socket closed'); + } + + // When the socket is upgraded while we're probing + function onupgrade (to) { + if (transport && to.name !== transport.name) { + debug('"%s" works - aborting "%s"', to.name, transport.name); + freezeTransport(); + } + } + + // Remove all listeners on the transport and on self + function cleanup () { + transport.removeListener('open', onTransportOpen); + transport.removeListener('error', onerror); + transport.removeListener('close', onTransportClose); + self.removeListener('close', onclose); + self.removeListener('upgrading', onupgrade); + } + + transport.once('open', onTransportOpen); + transport.once('error', onerror); + transport.once('close', onTransportClose); + + this.once('close', onclose); + this.once('upgrading', onupgrade); + + transport.open(); +}; + +/** + * Called when connection is deemed open. + * + * @api public + */ + +Socket.prototype.onOpen = function () { + debug('socket open'); + this.readyState = 'open'; + Socket.priorWebsocketSuccess = 'websocket' === this.transport.name; + this.emit('open'); + this.flush(); + + // we check for `readyState` in case an `open` + // listener already closed the socket + if ('open' === this.readyState && 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 || + 'closing' === this.readyState) { + debug('socket receive: type "%s", data "%s"', packet.type, packet.data); + + this.emit('packet', packet); + + // Socket is live - any packet counts + this.emit('heartbeat'); + + switch (packet.type) { + case 'open': + this.onHandshake(parsejson(packet.data)); + break; + + case 'pong': + this.setPing(); + this.emit('pong'); + break; + + case 'error': + var err = new Error('server error'); + err.code = packet.data; + this.onError(err); + break; + + case 'message': + this.emit('data', packet.data); + this.emit('message', packet.data); + 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 = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); + // In case open handler closes socket + if ('closed' === this.readyState) return; + this.setPing(); + + // Prolong liveness of socket on heartbeat + this.removeListener('heartbeat', this.onHeartbeat); + this.on('heartbeat', this.onHeartbeat); +}; + +/** + * Resets ping timeout. + * + * @api private + */ + +Socket.prototype.onHeartbeat = function (timeout) { + clearTimeout(this.pingTimeoutTimer); + var self = this; + self.pingTimeoutTimer = setTimeout(function () { + if ('closed' === self.readyState) return; + self.onClose('ping timeout'); + }, timeout || (self.pingInterval + self.pingTimeout)); +}; + +/** + * Pings server every `this.pingInterval` and expects response + * within `this.pingTimeout` or closes connection. + * + * @api private + */ + +Socket.prototype.setPing = function () { + var self = this; + clearTimeout(self.pingIntervalTimer); + self.pingIntervalTimer = setTimeout(function () { + debug('writing ping packet - expecting pong within %sms', self.pingTimeout); + self.ping(); + self.onHeartbeat(self.pingTimeout); + }, self.pingInterval); +}; + +/** +* Sends a ping packet. +* +* @api private +*/ + +Socket.prototype.ping = function () { + var self = this; + this.sendPacket('ping', function () { + self.emit('ping'); + }); +}; + +/** + * Called on `drain` event + * + * @api private + */ + +Socket.prototype.onDrain = function () { + this.writeBuffer.splice(0, this.prevBufferLen); + + // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit('drain'); + } else { + this.flush(); + } +}; + +/** + * 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); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = this.writeBuffer.length; + this.emit('flush'); + } +}; + +/** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + +Socket.prototype.write = +Socket.prototype.send = function (msg, options, fn) { + this.sendPacket('message', msg, options, fn); + return this; +}; + +/** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + +Socket.prototype.sendPacket = function (type, data, options, fn) { + if ('function' === typeof data) { + fn = data; + data = undefined; + } + + if ('function' === typeof options) { + fn = options; + options = null; + } + + if ('closing' === this.readyState || 'closed' === this.readyState) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + + var packet = { + type: type, + data: data, + options: options + }; + this.emit('packetCreate', packet); + this.writeBuffer.push(packet); + if (fn) this.once('flush', fn); + this.flush(); +}; + +/** + * Closes the connection. + * + * @api private + */ + +Socket.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.readyState = 'closing'; + + var self = this; + + if (this.writeBuffer.length) { + this.once('drain', function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close () { + self.onClose('forced close'); + debug('socket closing - telling transport to close'); + self.transport.close(); + } + + function cleanupAndClose () { + self.removeListener('upgrade', cleanupAndClose); + self.removeListener('upgradeError', cleanupAndClose); + close(); + } + + function waitForUpgrade () { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once('upgrade', cleanupAndClose); + self.once('upgradeError', cleanupAndClose); + } + + return this; +}; + +/** + * Called upon transport error + * + * @api private + */ + +Socket.prototype.onError = function (err) { + debug('socket error %j', err); + Socket.priorWebsocketSuccess = false; + this.emit('error', err); + this.onClose('transport error', err); +}; + +/** + * Called upon transport close. + * + * @api private + */ + +Socket.prototype.onClose = function (reason, desc) { + if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) { + debug('socket close with reason: "%s"', reason); + var self = this; + + // clear timers + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); + + // stop event from firing again for transport + this.transport.removeAllListeners('close'); + + // ensure transport won't stay open + this.transport.close(); + + // ignore further transport communication + this.transport.removeAllListeners(); + + // set ready state + this.readyState = 'closed'; + + // clear session id + this.id = null; + + // emit close event + this.emit('close', reason, desc); + + // clean buffers after, so users can still + // grab the buffers on `close` event + self.writeBuffer = []; + self.prevBufferLen = 0; + } +}; + +/** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + +Socket.prototype.filterUpgrades = function (upgrades) { + var filteredUpgrades = []; + for (var i = 0, j = upgrades.length; i < j; i++) { + if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; +}; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./transport":23,"./transports/index":24,"component-emitter":30,"debug":31,"engine.io-parser":33,"indexof":37,"parsejson":41,"parseqs":42,"parseuri":43}],23:[function(require,module,exports){ +/** + * Module dependencies. + */ + +var parser = require('engine.io-parser'); +var Emitter = require('component-emitter'); + +/** + * Module exports. + */ + +module.exports = Transport; + +/** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + +function Transport (opts) { + this.path = opts.path; + this.hostname = opts.hostname; + this.port = opts.port; + this.secure = opts.secure; + this.query = opts.query; + this.timestampParam = opts.timestampParam; + this.timestampRequests = opts.timestampRequests; + this.readyState = ''; + this.agent = opts.agent || false; + this.socket = opts.socket; + this.enablesXDR = opts.enablesXDR; + + // SSL options for Node.js client + this.pfx = opts.pfx; + this.key = opts.key; + this.passphrase = opts.passphrase; + this.cert = opts.cert; + this.ca = opts.ca; + this.ciphers = opts.ciphers; + this.rejectUnauthorized = opts.rejectUnauthorized; + this.forceNode = opts.forceNode; + + // other options for Node.js client + this.extraHeaders = opts.extraHeaders; + this.localAddress = opts.localAddress; +} + +/** + * Mix in `Emitter`. + */ + +Emitter(Transport.prototype); + +/** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + +Transport.prototype.onError = function (msg, desc) { + var err = new Error(msg); + err.type = 'TransportError'; + err.description = desc; + this.emit('error', err); + return this; +}; + +/** + * Opens the transport. + * + * @api public + */ + +Transport.prototype.open = function () { + if ('closed' === this.readyState || '' === this.readyState) { + this.readyState = 'opening'; + this.doOpen(); + } + + return this; +}; + +/** + * Closes the transport. + * + * @api private + */ + +Transport.prototype.close = function () { + if ('opening' === this.readyState || 'open' === this.readyState) { + this.doClose(); + this.onClose(); + } + + return this; +}; + +/** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + +Transport.prototype.send = function (packets) { + if ('open' === this.readyState) { + this.write(packets); + } else { + throw new Error('Transport not open'); + } +}; + +/** + * Called upon open + * + * @api private + */ + +Transport.prototype.onOpen = function () { + this.readyState = 'open'; + this.writable = true; + this.emit('open'); +}; + +/** + * Called with data. + * + * @param {String} data + * @api private + */ + +Transport.prototype.onData = function (data) { + var packet = parser.decodePacket(data, this.socket.binaryType); + this.onPacket(packet); +}; + +/** + * Called with a decoded packet. + */ + +Transport.prototype.onPacket = function (packet) { + this.emit('packet', packet); +}; + +/** + * Called upon close. + * + * @api private + */ + +Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); +}; + +},{"component-emitter":30,"engine.io-parser":33}],24:[function(require,module,exports){ +(function (global){ +/** + * Module dependencies + */ + +var XMLHttpRequest = require('xmlhttprequest-ssl'); +var XHR = require('./polling-xhr'); +var JSONP = require('./polling-jsonp'); +var websocket = require('./websocket'); + +/** + * Export transports. + */ + +exports.polling = polling; +exports.websocket = websocket; + +/** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + +function polling (opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (global.location) { + var isSSL = 'https:' === location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./polling-jsonp":25,"./polling-xhr":26,"./websocket":28,"xmlhttprequest-ssl":29}],25:[function(require,module,exports){ +(function (global){ + +/** + * Module requirements. + */ + +var Polling = require('./polling'); +var inherit = require('component-inherit'); + +/** + * Module exports. + */ + +module.exports = JSONPPolling; + +/** + * Cached regular expressions. + */ + +var rNewline = /\n/g; +var rEscapedNewline = /\\n/g; + +/** + * Global JSONP callbacks. + */ + +var callbacks; + +/** + * Noop. + */ + +function empty () { } + +/** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + +function JSONPPolling (opts) { + Polling.call(this, opts); + + this.query = this.query || {}; + + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + if (!global.___eio) global.___eio = []; + callbacks = global.___eio; + } + + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (global.document && global.addEventListener) { + global.addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); + } +} + +/** + * Inherits from Polling. + */ + +inherit(JSONPPolling, Polling); + +/* + * JSONP only supports binary as base64 encoded strings + */ + +JSONPPolling.prototype.supportsBinary = false; + +/** + * Closes the socket. + * + * @api private + */ + +JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + Polling.prototype.doClose.call(this); +}; + +/** + * Starts a poll cycle. + * + * @api private + */ + +JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + script.onerror = function (e) { + self.onError('jsonp poll error', e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + this.script = script; + + var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } +}; + +/** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + +JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); + } + + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '