├── .gitignore ├── .tern-project ├── Gruntfile.coffee ├── README.mkd ├── fidget.sh ├── images └── vim-fiddle.gif ├── node_server.js ├── package.json ├── plugin └── fidget.vim ├── template ├── index.html ├── jsFiddle.html ├── jsFiddle.js ├── main.css ├── main.js ├── socket.io.js └── socket_client.js └── testCurl.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | node_modules/ 3 | *.swp 4 | *.pyc 5 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser", 4 | "jquery" 5 | ], 6 | "plugins": { 7 | "node": { 8 | "baseURL": "./", 9 | "paths":{} 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | grunt.initConfig({ 3 | watch: 4 | reTest: 5 | files: ['*.sh'] 6 | tasks: ['shell:testCurl'] 7 | browserTest: 8 | files: ['**/*client.js'] 9 | options: 10 | livereload: true 11 | nodemon: 12 | dev: 13 | script: 'node_server.js' 14 | options: 15 | args: ['/Users/mohitaggarwal/Documents/coolProjects/vim-fiddle'] 16 | shell: 17 | testCurl: 18 | options: 19 | stdout: false 20 | stderr: false 21 | command: 'sh testCurl.sh' 22 | 23 | concurrent: 24 | options: 25 | logConcurrentOutput: true 26 | mainTask: ['nodemon', 'watch'] 27 | 28 | }) 29 | 30 | grunt.loadNpmTasks 'grunt-nodemon' 31 | grunt.loadNpmTasks 'grunt-contrib-watch' 32 | grunt.loadNpmTasks 'grunt-shell' 33 | grunt.loadNpmTasks 'grunt-concurrent' 34 | 35 | grunt.registerTask 'default', ['concurrent'] 36 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # vim-fidget - JsFiddle for Vim 2 | ## What?? 3 | This is a plugin (with a very creative name!) that emulates JsFiddle in vim and lets you post it to actual jsfiddle.com so that it can be shared. 4 | ![SCREENCAST](/images/vim-fiddle.gif?raw=true "VIM-Fidget") 5 | 6 | ## Why?? 7 | I am a web developer and the whole concept of trying out small chunks of programs makes JsFiddle really cool **BUT...** 8 | 9 | **WHERE IS MY VIM....?** this plugin answers that (it also does *live update* say whatt) 10 | 11 | ## Installation 12 | 13 | - Manual installation: 14 | 1. Copy the files to your `.vim` directory 15 | 2. then run `npm install` in the repo 16 | - [Pathogen](https://github.com/tpope/vim-pathogen) 17 | - `cd ~/.vim/bundle && git clone git://github.com/mohitleo9/vim-fidget.git && npm install` 18 | - [Vundle](https://github.com/gmarik/vundle) 19 | 1. Add `Bundle 'mohitleo9/vim-fidget'` to .vimrc 20 | 2. Run `:BundleInstall` 21 | 3. Then `go to the bundle/vim-fidget && run npm install` 22 | - [NeoBundle](https://github.com/Shougo/neobundle.vim) 23 | 1. Add 24 | 25 | NeoBundle 'mohitleo9/vim-fidget',{ 26 | \ 'build' : { 27 | \ 'unix' : 'npm install', 28 | \ 'mac' : 'npm install', 29 | \ }, 30 | \} 31 | to .vimrc 32 | 2. Run `:NeoBundleInstall` 33 | 34 | ## How to use ?? 35 | It adds two commands 36 | 37 | `VimFidget` : create a fidget and open a browser window for live preview 38 | 39 | `VimFidgetBrowse` : upload the fidget to jsfiddle 40 | Also the server dies when you quit index.html buffer 41 | 42 | # Amending the default templates 43 | You can change the template files in bundle/vim-fidget/template to include 44 | stuff that you frequently use. For example, if you work locally and would like a base Fidget that includes jQuery, Modernizr and a meta tag for correct display of HTML on mobile devices you could: 45 | 46 | - download jQuery and Modernizr to your template folder 47 | - amend the `index.html` file as follows: 48 | 49 | ````html 50 | 51 | 52 | 53 | 54 | Vim Fidget 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 |
66 | 67 | 68 | 69 | ```` 70 | 71 | You can also alter the default CSS (main.css) and JS (main.js) in the same manner. 72 | 73 | ## Credits: 74 | - I took a lot of ideas from (and some code!) [vim-fiddle](https://github.com/mharju/vim-fiddle) 75 | - Also for using nodeJs and other stuff I looked(and took some code) at [suan/vim-instant-markdown](https://github.com/suan/vim-instant-markdown) 76 | 77 | ## That's it for today Folks!!! 78 | -------------------------------------------------------------------------------- /fidget.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$FIDGETDIR" == "" ]; then 4 | FIDGETDIR=/tmp 5 | fi 6 | 7 | if [ "$1" == "" ]; then 8 | FIDGET=$FIDGETDIR/`python -c "import random; print hex(int(str(random.random())[2:10]))[2:]"` 9 | else 10 | FIDGET=$FIDGETDIR/$1 11 | fi 12 | 13 | echo "Creating fiddle to $FIDGET" 14 | # set -e makes sure that we exit on failure 15 | (set -e; 16 | # get the actual file path avoiding symlinks 17 | SOURCE="${BASH_SOURCE[0]}" 18 | while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink 19 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 20 | SOURCE="$(readlink "$SOURCE")" 21 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located 22 | done 23 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 24 | 25 | echo $DIR 26 | mkdir $FIDGET; 27 | cp $DIR/template/* $FIDGET; 28 | echo $FIDGET 29 | node $DIR/node_server.js $FIDGET >/dev/null 2>/dev/null & 30 | ) 31 | -------------------------------------------------------------------------------- /images/vim-fiddle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohitleo9/vim-fidget/4309192ab420b46f9c0c7a9feaee367497e90561/images/vim-fiddle.gif -------------------------------------------------------------------------------- /node_server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var server = require('http').createServer(httpHandler), 4 | spawn = require('child_process').spawn, 5 | fs = require('fs'), 6 | exec = require('child_process').exec, 7 | io = require('socket.io').listen(server), 8 | querystring = require('querystring'), 9 | url = require('url'), 10 | send = require('send'), 11 | server, 12 | socket; 13 | 14 | server.listen(8092); 15 | var assetsLocation = process.argv[2]; 16 | 17 | function httpHandler(req, res) { 18 | switch(req.method){ 19 | case 'GET': 20 | path = url.parse(req.url).pathname; 21 | if (path.indexOf('read/') > -1){ 22 | // this means stream the contents of the file 23 | fileName = path.substring(path.indexOf('read/') + 'read/'.length); 24 | buf = fs.readFileSync(assetsLocation + "/" + fileName); 25 | res.write(buf.toString()); 26 | res.end(); 27 | return; 28 | } 29 | // nodejs automatically provide the current assetsLocation path 30 | if (path === null || path === '/') { 31 | res.write(assetsLocation); 32 | res.end(); 33 | return; 34 | } 35 | send(req, path, {root: assetsLocation}) 36 | .pipe(res); 37 | return; 38 | case 'POST': 39 | var postData = ''; 40 | req.on('data', function(chunck) { 41 | postData += chunck.toString(); 42 | }); 43 | req.on('end', function () { 44 | postData = querystring.parse(postData); 45 | 46 | if(postData.action === 'cssReload'){ 47 | io.emit('cssReload', postData.name); 48 | } 49 | 50 | if(postData.action === 'reload'){ 51 | io.emit('reload', 'reload'); 52 | } 53 | }); 54 | 55 | res.write('ok'); 56 | res.end(); 57 | return; 58 | 59 | case 'PUT': 60 | if (process.platform.toLowerCase().indexOf('darwin') >= 0){ 61 | spawn('open', ['http://localhost:8092' + req.url]); 62 | } 63 | else { // assume unix/linux 64 | spawn('xdg-open', ['http://localhost:8092' + req.url]); 65 | } 66 | res.write('ok'); 67 | res.end(); 68 | return; 69 | 70 | case 'DELETE': 71 | io.emit('die', 'die'); 72 | process.exit(1); 73 | return; 74 | 75 | case 'READ': 76 | return; 77 | } 78 | } 79 | 80 | io.on('connection', function(socket) { 81 | console.log("user connected"); 82 | }); 83 | 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim-fidget", 3 | "version": "0.0.2", 4 | "main": "node_server.js", 5 | "bin": { 6 | "vim-fidget": "./fidget.sh" 7 | }, 8 | "preferGlobal": "true", 9 | "dependencies": { 10 | "socket.io": "^1.0.6", 11 | "send": "^0.8.3" 12 | }, 13 | "devDependencies": { 14 | "grunt": "^0.4.5", 15 | "grunt-cli": "^0.1.13", 16 | "grunt-concurrent": "^0.5.0", 17 | "grunt-contrib-watch": "^0.6.1", 18 | "grunt-nodemon": "^0.3.0", 19 | "grunt-shell": "^0.7.0", 20 | "send": "^0.8.3", 21 | "socket.io": "^1.0.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugin/fidget.vim: -------------------------------------------------------------------------------- 1 | if (exists("g:vim_fidget_loaded") && g:vim_fidget_loaded) 2 | finish 3 | endif 4 | let g:vim_fidget_loaded = 1 5 | " # Configuration 6 | 7 | function! s:reloadCss() 8 | call system("curl -d 'action=cssReload&name=main.css' http://localhost:8092/ &>/dev/null &") 9 | endfu 10 | 11 | 12 | function! s:reload() 13 | call system("curl -d 'action=reload' http://localhost:8092/ &>/dev/null &") 14 | endfu 15 | " for some reason putting this inside a function does not work 16 | let s:path = expand(':p:h:h') 17 | " This is necessary for stuff to take place :) 18 | let s:fidget_path = s:path.'/fidget.sh' 19 | function! s:startDaemon() 20 | silent call system(s:fidget_path) 21 | " this is to wait for the server to startup 22 | while empty(system("curl -s localhost:8092")) 23 | exe 'sleep 100m' 24 | endwhile 25 | let g:fidget_files_path = system("curl -s localhost:8092") 26 | endfu 27 | 28 | function! s:killDaemon() 29 | call system("curl -s -X DELETE http://localhost:8092/ &>/dev/null") 30 | endfu 31 | 32 | 33 | function! s:openBrowser() 34 | call system("curl -s -X PUT http://localhost:8092/index.html &>/dev/null &") 35 | endfu 36 | 37 | function! s:createJsFiddle() 38 | call system("curl -s -X PUT http://localhost:8092/jsFiddle.html &>/dev/null &") 39 | endfunction 40 | 41 | fu! s:cleanUp() 42 | call s:killDaemon() 43 | au! fidget-start_commands * 44 | endfu 45 | 46 | 47 | fu! s:start_vim_fidget() 48 | " load the files 49 | call s:startDaemon() 50 | tabnew 51 | exe 'e '.g:fidget_files_path.'/index.html' 52 | exe 'split '.g:fidget_files_path.'/main.js' 53 | exe 'vsplit '.g:fidget_files_path.'/main.css' 54 | " # Define the autocmds " 55 | aug fidget-start_commands 56 | au! 57 | au BufWritePost *main.css call s:reloadCss() 58 | au BufWritePost *main.js call s:reload() 59 | au BufWritePost *index.html call s:reload() 60 | au BufDelete,BufUnload,BufUnload *index.html call s:cleanUp() 61 | au BufDelete,BufUnload,BufUnload *main.css call s:cleanUp() 62 | au BufDelete,BufUnload,BufUnload,BufWipeout *main.js call s:cleanUp() 63 | au VimLeavePre * call s:cleanUp() 64 | aug END 65 | call s:openBrowser() 66 | endfu 67 | 68 | command! -nargs=0 VimFidget call s:start_vim_fidget() 69 | command! -nargs=0 VimFidgetBrowse call s:createJsFiddle() 70 | command! -nargs=0 VimFidgetKillServer call s:cleanUp() 71 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
asdf asdf ;;;;asdf asdf asdf
10 |
asdf asdf ;;;;asdf asdf asdf
11 |
asdf asdf ;;;;asdf asdf asdf
12 |
asdf asdf ;;;;asdf asdf asdf
13 |
asdf asdf ;;;;asdf asdf asdf
14 |
asdf asdf ;;;;asdf asdf asdf
15 |
asdf asdf ;;;;asdf asdf asdf
16 |
asdf asdf ;;;;asdf asdf asdf
17 | 18 | 19 | -------------------------------------------------------------------------------- /template/jsFiddle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 0 && !this.encoding) { 444 | var pack = this.packetBuffer.shift(); 445 | this.packet(pack); 446 | } 447 | }; 448 | 449 | /** 450 | * Clean up transport subscriptions and packet buffer. 451 | * 452 | * @api private 453 | */ 454 | 455 | Manager.prototype.cleanup = function(){ 456 | var sub; 457 | while (sub = this.subs.shift()) sub.destroy(); 458 | 459 | this.packetBuffer = []; 460 | this.encoding = false; 461 | 462 | this.decoder.destroy(); 463 | }; 464 | 465 | /** 466 | * Close the current socket. 467 | * 468 | * @api private 469 | */ 470 | 471 | Manager.prototype.close = 472 | Manager.prototype.disconnect = function(){ 473 | this.skipReconnect = true; 474 | this.engine.close(); 475 | }; 476 | 477 | /** 478 | * Called upon engine close. 479 | * 480 | * @api private 481 | */ 482 | 483 | Manager.prototype.onclose = function(reason){ 484 | debug('close'); 485 | this.cleanup(); 486 | this.readyState = 'closed'; 487 | this.emit('close', reason); 488 | if (this._reconnection && !this.skipReconnect) { 489 | this.reconnect(); 490 | } 491 | }; 492 | 493 | /** 494 | * Attempt a reconnection. 495 | * 496 | * @api private 497 | */ 498 | 499 | Manager.prototype.reconnect = function(){ 500 | if (this.reconnecting) return this; 501 | 502 | var self = this; 503 | this.attempts++; 504 | 505 | if (this.attempts > this._reconnectionAttempts) { 506 | debug('reconnect failed'); 507 | this.emitAll('reconnect_failed'); 508 | this.reconnecting = false; 509 | } else { 510 | var delay = this.attempts * this.reconnectionDelay(); 511 | delay = Math.min(delay, this.reconnectionDelayMax()); 512 | debug('will wait %dms before reconnect attempt', delay); 513 | 514 | this.reconnecting = true; 515 | var timer = setTimeout(function(){ 516 | debug('attempting reconnect'); 517 | self.emitAll('reconnect_attempt', self.attempts); 518 | self.emitAll('reconnecting', self.attempts); 519 | self.open(function(err){ 520 | if (err) { 521 | debug('reconnect attempt error'); 522 | self.reconnecting = false; 523 | self.reconnect(); 524 | self.emitAll('reconnect_error', err.data); 525 | } else { 526 | debug('reconnect success'); 527 | self.onreconnect(); 528 | } 529 | }); 530 | }, delay); 531 | 532 | this.subs.push({ 533 | destroy: function(){ 534 | clearTimeout(timer); 535 | } 536 | }); 537 | } 538 | }; 539 | 540 | /** 541 | * Called upon successful reconnect. 542 | * 543 | * @api private 544 | */ 545 | 546 | Manager.prototype.onreconnect = function(){ 547 | var attempt = this.attempts; 548 | this.attempts = 0; 549 | this.reconnecting = false; 550 | this.emitAll('reconnect', attempt); 551 | }; 552 | 553 | },{"./on":4,"./socket":5,"./url":6,"component-bind":7,"component-emitter":8,"debug":9,"engine.io-client":11,"object-component":37,"socket.io-parser":40}],4:[function(require,module,exports){ 554 | 555 | /** 556 | * Module exports. 557 | */ 558 | 559 | module.exports = on; 560 | 561 | /** 562 | * Helper for subscriptions. 563 | * 564 | * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` 565 | * @param {String} event name 566 | * @param {Function} callback 567 | * @api public 568 | */ 569 | 570 | function on(obj, ev, fn) { 571 | obj.on(ev, fn); 572 | return { 573 | destroy: function(){ 574 | obj.removeListener(ev, fn); 575 | } 576 | }; 577 | } 578 | 579 | },{}],5:[function(require,module,exports){ 580 | 581 | /** 582 | * Module dependencies. 583 | */ 584 | 585 | var parser = require('socket.io-parser'); 586 | var Emitter = require('component-emitter'); 587 | var toArray = require('to-array'); 588 | var on = require('./on'); 589 | var bind = require('component-bind'); 590 | var debug = require('debug')('socket.io-client:socket'); 591 | var hasBin = require('has-binary-data'); 592 | var indexOf = require('indexof'); 593 | 594 | /** 595 | * Module exports. 596 | */ 597 | 598 | module.exports = exports = Socket; 599 | 600 | /** 601 | * Internal events (blacklisted). 602 | * These events can't be emitted by the user. 603 | * 604 | * @api private 605 | */ 606 | 607 | var events = { 608 | connect: 1, 609 | connect_error: 1, 610 | connect_timeout: 1, 611 | disconnect: 1, 612 | error: 1, 613 | reconnect: 1, 614 | reconnect_attempt: 1, 615 | reconnect_failed: 1, 616 | reconnect_error: 1, 617 | reconnecting: 1 618 | }; 619 | 620 | /** 621 | * Shortcut to `Emitter#emit`. 622 | */ 623 | 624 | var emit = Emitter.prototype.emit; 625 | 626 | /** 627 | * `Socket` constructor. 628 | * 629 | * @api public 630 | */ 631 | 632 | function Socket(io, nsp){ 633 | this.io = io; 634 | this.nsp = nsp; 635 | this.json = this; // compat 636 | this.ids = 0; 637 | this.acks = {}; 638 | this.open(); 639 | this.receiveBuffer = []; 640 | this.sendBuffer = []; 641 | this.connected = false; 642 | this.disconnected = true; 643 | this.subEvents(); 644 | } 645 | 646 | /** 647 | * Mix in `Emitter`. 648 | */ 649 | 650 | Emitter(Socket.prototype); 651 | 652 | /** 653 | * Subscribe to open, close and packet events 654 | * 655 | * @api private 656 | */ 657 | 658 | Socket.prototype.subEvents = function() { 659 | var io = this.io; 660 | this.subs = [ 661 | on(io, 'open', bind(this, 'onopen')), 662 | on(io, 'packet', bind(this, 'onpacket')), 663 | on(io, 'close', bind(this, 'onclose')) 664 | ]; 665 | }; 666 | 667 | /** 668 | * Called upon engine `open`. 669 | * 670 | * @api private 671 | */ 672 | 673 | Socket.prototype.open = 674 | Socket.prototype.connect = function(){ 675 | if (this.connected) return this; 676 | 677 | this.io.open(); // ensure open 678 | if ('open' == this.io.readyState) this.onopen(); 679 | return this; 680 | }; 681 | 682 | /** 683 | * Sends a `message` event. 684 | * 685 | * @return {Socket} self 686 | * @api public 687 | */ 688 | 689 | Socket.prototype.send = function(){ 690 | var args = toArray(arguments); 691 | args.unshift('message'); 692 | this.emit.apply(this, args); 693 | return this; 694 | }; 695 | 696 | /** 697 | * Override `emit`. 698 | * If the event is in `events`, it's emitted normally. 699 | * 700 | * @param {String} event name 701 | * @return {Socket} self 702 | * @api public 703 | */ 704 | 705 | Socket.prototype.emit = function(ev){ 706 | if (events.hasOwnProperty(ev)) { 707 | emit.apply(this, arguments); 708 | return this; 709 | } 710 | 711 | var args = toArray(arguments); 712 | var parserType = parser.EVENT; // default 713 | if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary 714 | var packet = { type: parserType, data: args }; 715 | 716 | // event ack callback 717 | if ('function' == typeof args[args.length - 1]) { 718 | debug('emitting packet with ack id %d', this.ids); 719 | this.acks[this.ids] = args.pop(); 720 | packet.id = this.ids++; 721 | } 722 | 723 | if (this.connected) { 724 | this.packet(packet); 725 | } else { 726 | this.sendBuffer.push(packet); 727 | } 728 | 729 | return this; 730 | }; 731 | 732 | /** 733 | * Sends a packet. 734 | * 735 | * @param {Object} packet 736 | * @api private 737 | */ 738 | 739 | Socket.prototype.packet = function(packet){ 740 | packet.nsp = this.nsp; 741 | this.io.packet(packet); 742 | }; 743 | 744 | /** 745 | * "Opens" the socket. 746 | * 747 | * @api private 748 | */ 749 | 750 | Socket.prototype.onopen = function(){ 751 | debug('transport is open - connecting'); 752 | 753 | // write connect packet if necessary 754 | if ('/' != this.nsp) { 755 | this.packet({ type: parser.CONNECT }); 756 | } 757 | }; 758 | 759 | /** 760 | * Called upon engine `close`. 761 | * 762 | * @param {String} reason 763 | * @api private 764 | */ 765 | 766 | Socket.prototype.onclose = function(reason){ 767 | debug('close (%s)', reason); 768 | this.connected = false; 769 | this.disconnected = true; 770 | this.emit('disconnect', reason); 771 | }; 772 | 773 | /** 774 | * Called with socket packet. 775 | * 776 | * @param {Object} packet 777 | * @api private 778 | */ 779 | 780 | Socket.prototype.onpacket = function(packet){ 781 | if (packet.nsp != this.nsp) return; 782 | 783 | switch (packet.type) { 784 | case parser.CONNECT: 785 | this.onconnect(); 786 | break; 787 | 788 | case parser.EVENT: 789 | this.onevent(packet); 790 | break; 791 | 792 | case parser.BINARY_EVENT: 793 | this.onevent(packet); 794 | break; 795 | 796 | case parser.ACK: 797 | this.onack(packet); 798 | break; 799 | 800 | case parser.BINARY_ACK: 801 | this.onack(packet); 802 | break; 803 | 804 | case parser.DISCONNECT: 805 | this.ondisconnect(); 806 | break; 807 | 808 | case parser.ERROR: 809 | this.emit('error', packet.data); 810 | break; 811 | } 812 | }; 813 | 814 | /** 815 | * Called upon a server event. 816 | * 817 | * @param {Object} packet 818 | * @api private 819 | */ 820 | 821 | Socket.prototype.onevent = function(packet){ 822 | var args = packet.data || []; 823 | debug('emitting event %j', args); 824 | 825 | if (null != packet.id) { 826 | debug('attaching ack callback to event'); 827 | args.push(this.ack(packet.id)); 828 | } 829 | 830 | if (this.connected) { 831 | emit.apply(this, args); 832 | } else { 833 | this.receiveBuffer.push(args); 834 | } 835 | }; 836 | 837 | /** 838 | * Produces an ack callback to emit with an event. 839 | * 840 | * @api private 841 | */ 842 | 843 | Socket.prototype.ack = function(id){ 844 | var self = this; 845 | var sent = false; 846 | return function(){ 847 | // prevent double callbacks 848 | if (sent) return; 849 | sent = true; 850 | var args = toArray(arguments); 851 | debug('sending ack %j', args); 852 | 853 | var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; 854 | self.packet({ 855 | type: type, 856 | id: id, 857 | data: args 858 | }); 859 | }; 860 | }; 861 | 862 | /** 863 | * Called upon a server acknowlegement. 864 | * 865 | * @param {Object} packet 866 | * @api private 867 | */ 868 | 869 | Socket.prototype.onack = function(packet){ 870 | debug('calling ack %s with %j', packet.id, packet.data); 871 | var fn = this.acks[packet.id]; 872 | fn.apply(this, packet.data); 873 | delete this.acks[packet.id]; 874 | }; 875 | 876 | /** 877 | * Called upon server connect. 878 | * 879 | * @api private 880 | */ 881 | 882 | Socket.prototype.onconnect = function(){ 883 | this.connected = true; 884 | this.disconnected = false; 885 | this.emit('connect'); 886 | this.emitBuffered(); 887 | }; 888 | 889 | /** 890 | * Emit buffered events (received and emitted). 891 | * 892 | * @api private 893 | */ 894 | 895 | Socket.prototype.emitBuffered = function(){ 896 | var i; 897 | for (i = 0; i < this.receiveBuffer.length; i++) { 898 | emit.apply(this, this.receiveBuffer[i]); 899 | } 900 | this.receiveBuffer = []; 901 | 902 | for (i = 0; i < this.sendBuffer.length; i++) { 903 | this.packet(this.sendBuffer[i]); 904 | } 905 | this.sendBuffer = []; 906 | }; 907 | 908 | /** 909 | * Called upon server disconnect. 910 | * 911 | * @api private 912 | */ 913 | 914 | Socket.prototype.ondisconnect = function(){ 915 | debug('server disconnect (%s)', this.nsp); 916 | this.destroy(); 917 | this.onclose('io server disconnect'); 918 | }; 919 | 920 | /** 921 | * Called upon forced client/server side disconnections, 922 | * this method ensures the manager stops tracking us and 923 | * that reconnections don't get triggered for this. 924 | * 925 | * @api private. 926 | */ 927 | 928 | Socket.prototype.destroy = function(){ 929 | // clean subscriptions to avoid reconnections 930 | for (var i = 0; i < this.subs.length; i++) { 931 | this.subs[i].destroy(); 932 | } 933 | 934 | this.io.destroy(this); 935 | }; 936 | 937 | /** 938 | * Disconnects the socket manually. 939 | * 940 | * @return {Socket} self 941 | * @api public 942 | */ 943 | 944 | Socket.prototype.close = 945 | Socket.prototype.disconnect = function(){ 946 | if (!this.connected) return this; 947 | 948 | debug('performing disconnect (%s)', this.nsp); 949 | this.packet({ type: parser.DISCONNECT }); 950 | 951 | // remove socket from pool 952 | this.destroy(); 953 | 954 | // fire events 955 | this.onclose('io client disconnect'); 956 | return this; 957 | }; 958 | 959 | },{"./on":4,"component-bind":7,"component-emitter":8,"debug":9,"has-binary-data":32,"indexof":36,"socket.io-parser":40,"to-array":43}],6:[function(require,module,exports){ 960 | var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}; 961 | /** 962 | * Module dependencies. 963 | */ 964 | 965 | var parseuri = require('parseuri'); 966 | var debug = require('debug')('socket.io-client:url'); 967 | 968 | /** 969 | * Module exports. 970 | */ 971 | 972 | module.exports = url; 973 | 974 | /** 975 | * URL parser. 976 | * 977 | * @param {String} url 978 | * @param {Object} An object meant to mimic window.location. 979 | * Defaults to window.location. 980 | * @api public 981 | */ 982 | 983 | function url(uri, loc){ 984 | var obj = uri; 985 | 986 | // default to window.location 987 | var loc = loc || global.location; 988 | if (null == uri) uri = loc.protocol + '//' + loc.hostname; 989 | 990 | // relative path support 991 | if ('string' == typeof uri) { 992 | if ('/' == uri.charAt(0)) { 993 | if ('undefined' != typeof loc) { 994 | uri = loc.hostname + uri; 995 | } 996 | } 997 | 998 | if (!/^(https?|wss?):\/\//.test(uri)) { 999 | debug('protocol-less url %s', uri); 1000 | if ('undefined' != typeof loc) { 1001 | uri = loc.protocol + '//' + uri; 1002 | } else { 1003 | uri = 'https://' + uri; 1004 | } 1005 | } 1006 | 1007 | // parse 1008 | debug('parse %s', uri); 1009 | obj = parseuri(uri); 1010 | } 1011 | 1012 | // make sure we treat `localhost:80` and `localhost` equally 1013 | if (!obj.port) { 1014 | if (/^(http|ws)$/.test(obj.protocol)) { 1015 | obj.port = '80'; 1016 | } 1017 | else if (/^(http|ws)s$/.test(obj.protocol)) { 1018 | obj.port = '443'; 1019 | } 1020 | } 1021 | 1022 | obj.path = obj.path || '/'; 1023 | 1024 | // define unique id 1025 | obj.id = obj.protocol + '://' + obj.host + ':' + obj.port; 1026 | // define href 1027 | obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); 1028 | 1029 | return obj; 1030 | } 1031 | 1032 | },{"debug":9,"parseuri":38}],7:[function(require,module,exports){ 1033 | /** 1034 | * Slice reference. 1035 | */ 1036 | 1037 | var slice = [].slice; 1038 | 1039 | /** 1040 | * Bind `obj` to `fn`. 1041 | * 1042 | * @param {Object} obj 1043 | * @param {Function|String} fn or string 1044 | * @return {Function} 1045 | * @api public 1046 | */ 1047 | 1048 | module.exports = function(obj, fn){ 1049 | if ('string' == typeof fn) fn = obj[fn]; 1050 | if ('function' != typeof fn) throw new Error('bind() requires a function'); 1051 | var args = slice.call(arguments, 2); 1052 | return function(){ 1053 | return fn.apply(obj, args.concat(slice.call(arguments))); 1054 | } 1055 | }; 1056 | 1057 | },{}],8:[function(require,module,exports){ 1058 | 1059 | /** 1060 | * Expose `Emitter`. 1061 | */ 1062 | 1063 | module.exports = Emitter; 1064 | 1065 | /** 1066 | * Initialize a new `Emitter`. 1067 | * 1068 | * @api public 1069 | */ 1070 | 1071 | function Emitter(obj) { 1072 | if (obj) return mixin(obj); 1073 | }; 1074 | 1075 | /** 1076 | * Mixin the emitter properties. 1077 | * 1078 | * @param {Object} obj 1079 | * @return {Object} 1080 | * @api private 1081 | */ 1082 | 1083 | function mixin(obj) { 1084 | for (var key in Emitter.prototype) { 1085 | obj[key] = Emitter.prototype[key]; 1086 | } 1087 | return obj; 1088 | } 1089 | 1090 | /** 1091 | * Listen on the given `event` with `fn`. 1092 | * 1093 | * @param {String} event 1094 | * @param {Function} fn 1095 | * @return {Emitter} 1096 | * @api public 1097 | */ 1098 | 1099 | Emitter.prototype.on = 1100 | Emitter.prototype.addEventListener = function(event, fn){ 1101 | this._callbacks = this._callbacks || {}; 1102 | (this._callbacks[event] = this._callbacks[event] || []) 1103 | .push(fn); 1104 | return this; 1105 | }; 1106 | 1107 | /** 1108 | * Adds an `event` listener that will be invoked a single 1109 | * time then automatically removed. 1110 | * 1111 | * @param {String} event 1112 | * @param {Function} fn 1113 | * @return {Emitter} 1114 | * @api public 1115 | */ 1116 | 1117 | Emitter.prototype.once = function(event, fn){ 1118 | var self = this; 1119 | this._callbacks = this._callbacks || {}; 1120 | 1121 | function on() { 1122 | self.off(event, on); 1123 | fn.apply(this, arguments); 1124 | } 1125 | 1126 | on.fn = fn; 1127 | this.on(event, on); 1128 | return this; 1129 | }; 1130 | 1131 | /** 1132 | * Remove the given callback for `event` or all 1133 | * registered callbacks. 1134 | * 1135 | * @param {String} event 1136 | * @param {Function} fn 1137 | * @return {Emitter} 1138 | * @api public 1139 | */ 1140 | 1141 | Emitter.prototype.off = 1142 | Emitter.prototype.removeListener = 1143 | Emitter.prototype.removeAllListeners = 1144 | Emitter.prototype.removeEventListener = function(event, fn){ 1145 | this._callbacks = this._callbacks || {}; 1146 | 1147 | // all 1148 | if (0 == arguments.length) { 1149 | this._callbacks = {}; 1150 | return this; 1151 | } 1152 | 1153 | // specific event 1154 | var callbacks = this._callbacks[event]; 1155 | if (!callbacks) return this; 1156 | 1157 | // remove all handlers 1158 | if (1 == arguments.length) { 1159 | delete this._callbacks[event]; 1160 | return this; 1161 | } 1162 | 1163 | // remove specific handler 1164 | var cb; 1165 | for (var i = 0; i < callbacks.length; i++) { 1166 | cb = callbacks[i]; 1167 | if (cb === fn || cb.fn === fn) { 1168 | callbacks.splice(i, 1); 1169 | break; 1170 | } 1171 | } 1172 | return this; 1173 | }; 1174 | 1175 | /** 1176 | * Emit `event` with the given args. 1177 | * 1178 | * @param {String} event 1179 | * @param {Mixed} ... 1180 | * @return {Emitter} 1181 | */ 1182 | 1183 | Emitter.prototype.emit = function(event){ 1184 | this._callbacks = this._callbacks || {}; 1185 | var args = [].slice.call(arguments, 1) 1186 | , callbacks = this._callbacks[event]; 1187 | 1188 | if (callbacks) { 1189 | callbacks = callbacks.slice(0); 1190 | for (var i = 0, len = callbacks.length; i < len; ++i) { 1191 | callbacks[i].apply(this, args); 1192 | } 1193 | } 1194 | 1195 | return this; 1196 | }; 1197 | 1198 | /** 1199 | * Return array of callbacks for `event`. 1200 | * 1201 | * @param {String} event 1202 | * @return {Array} 1203 | * @api public 1204 | */ 1205 | 1206 | Emitter.prototype.listeners = function(event){ 1207 | this._callbacks = this._callbacks || {}; 1208 | return this._callbacks[event] || []; 1209 | }; 1210 | 1211 | /** 1212 | * Check if this emitter has `event` handlers. 1213 | * 1214 | * @param {String} event 1215 | * @return {Boolean} 1216 | * @api public 1217 | */ 1218 | 1219 | Emitter.prototype.hasListeners = function(event){ 1220 | return !! this.listeners(event).length; 1221 | }; 1222 | 1223 | },{}],9:[function(require,module,exports){ 1224 | 1225 | /** 1226 | * Expose `debug()` as the module. 1227 | */ 1228 | 1229 | module.exports = debug; 1230 | 1231 | /** 1232 | * Create a debugger with the given `name`. 1233 | * 1234 | * @param {String} name 1235 | * @return {Type} 1236 | * @api public 1237 | */ 1238 | 1239 | function debug(name) { 1240 | if (!debug.enabled(name)) return function(){}; 1241 | 1242 | return function(fmt){ 1243 | fmt = coerce(fmt); 1244 | 1245 | var curr = new Date; 1246 | var ms = curr - (debug[name] || curr); 1247 | debug[name] = curr; 1248 | 1249 | fmt = name 1250 | + ' ' 1251 | + fmt 1252 | + ' +' + debug.humanize(ms); 1253 | 1254 | // This hackery is required for IE8 1255 | // where `console.log` doesn't have 'apply' 1256 | window.console 1257 | && console.log 1258 | && Function.prototype.apply.call(console.log, console, arguments); 1259 | } 1260 | } 1261 | 1262 | /** 1263 | * The currently active debug mode names. 1264 | */ 1265 | 1266 | debug.names = []; 1267 | debug.skips = []; 1268 | 1269 | /** 1270 | * Enables a debug mode by name. This can include modes 1271 | * separated by a colon and wildcards. 1272 | * 1273 | * @param {String} name 1274 | * @api public 1275 | */ 1276 | 1277 | debug.enable = function(name) { 1278 | try { 1279 | localStorage.debug = name; 1280 | } catch(e){} 1281 | 1282 | var split = (name || '').split(/[\s,]+/) 1283 | , len = split.length; 1284 | 1285 | for (var i = 0; i < len; i++) { 1286 | name = split[i].replace('*', '.*?'); 1287 | if (name[0] === '-') { 1288 | debug.skips.push(new RegExp('^' + name.substr(1) + '$')); 1289 | } 1290 | else { 1291 | debug.names.push(new RegExp('^' + name + '$')); 1292 | } 1293 | } 1294 | }; 1295 | 1296 | /** 1297 | * Disable debug output. 1298 | * 1299 | * @api public 1300 | */ 1301 | 1302 | debug.disable = function(){ 1303 | debug.enable(''); 1304 | }; 1305 | 1306 | /** 1307 | * Humanize the given `ms`. 1308 | * 1309 | * @param {Number} m 1310 | * @return {String} 1311 | * @api private 1312 | */ 1313 | 1314 | debug.humanize = function(ms) { 1315 | var sec = 1000 1316 | , min = 60 * 1000 1317 | , hour = 60 * min; 1318 | 1319 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; 1320 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; 1321 | if (ms >= sec) return (ms / sec | 0) + 's'; 1322 | return ms + 'ms'; 1323 | }; 1324 | 1325 | /** 1326 | * Returns true if the given mode name is enabled, false otherwise. 1327 | * 1328 | * @param {String} name 1329 | * @return {Boolean} 1330 | * @api public 1331 | */ 1332 | 1333 | debug.enabled = function(name) { 1334 | for (var i = 0, len = debug.skips.length; i < len; i++) { 1335 | if (debug.skips[i].test(name)) { 1336 | return false; 1337 | } 1338 | } 1339 | for (var i = 0, len = debug.names.length; i < len; i++) { 1340 | if (debug.names[i].test(name)) { 1341 | return true; 1342 | } 1343 | } 1344 | return false; 1345 | }; 1346 | 1347 | /** 1348 | * Coerce `val`. 1349 | */ 1350 | 1351 | function coerce(val) { 1352 | if (val instanceof Error) return val.stack || val.message; 1353 | return val; 1354 | } 1355 | 1356 | // persist 1357 | 1358 | try { 1359 | if (window.localStorage) debug.enable(localStorage.debug); 1360 | } catch(e){} 1361 | 1362 | },{}],10:[function(require,module,exports){ 1363 | 1364 | /** 1365 | * Module dependencies. 1366 | */ 1367 | 1368 | var index = require('indexof'); 1369 | 1370 | /** 1371 | * Expose `Emitter`. 1372 | */ 1373 | 1374 | module.exports = Emitter; 1375 | 1376 | /** 1377 | * Initialize a new `Emitter`. 1378 | * 1379 | * @api public 1380 | */ 1381 | 1382 | function Emitter(obj) { 1383 | if (obj) return mixin(obj); 1384 | }; 1385 | 1386 | /** 1387 | * Mixin the emitter properties. 1388 | * 1389 | * @param {Object} obj 1390 | * @return {Object} 1391 | * @api private 1392 | */ 1393 | 1394 | function mixin(obj) { 1395 | for (var key in Emitter.prototype) { 1396 | obj[key] = Emitter.prototype[key]; 1397 | } 1398 | return obj; 1399 | } 1400 | 1401 | /** 1402 | * Listen on the given `event` with `fn`. 1403 | * 1404 | * @param {String} event 1405 | * @param {Function} fn 1406 | * @return {Emitter} 1407 | * @api public 1408 | */ 1409 | 1410 | Emitter.prototype.on = function(event, fn){ 1411 | this._callbacks = this._callbacks || {}; 1412 | (this._callbacks[event] = this._callbacks[event] || []) 1413 | .push(fn); 1414 | return this; 1415 | }; 1416 | 1417 | /** 1418 | * Adds an `event` listener that will be invoked a single 1419 | * time then automatically removed. 1420 | * 1421 | * @param {String} event 1422 | * @param {Function} fn 1423 | * @return {Emitter} 1424 | * @api public 1425 | */ 1426 | 1427 | Emitter.prototype.once = function(event, fn){ 1428 | var self = this; 1429 | this._callbacks = this._callbacks || {}; 1430 | 1431 | function on() { 1432 | self.off(event, on); 1433 | fn.apply(this, arguments); 1434 | } 1435 | 1436 | fn._off = on; 1437 | this.on(event, on); 1438 | return this; 1439 | }; 1440 | 1441 | /** 1442 | * Remove the given callback for `event` or all 1443 | * registered callbacks. 1444 | * 1445 | * @param {String} event 1446 | * @param {Function} fn 1447 | * @return {Emitter} 1448 | * @api public 1449 | */ 1450 | 1451 | Emitter.prototype.off = 1452 | Emitter.prototype.removeListener = 1453 | Emitter.prototype.removeAllListeners = function(event, fn){ 1454 | this._callbacks = this._callbacks || {}; 1455 | 1456 | // all 1457 | if (0 == arguments.length) { 1458 | this._callbacks = {}; 1459 | return this; 1460 | } 1461 | 1462 | // specific event 1463 | var callbacks = this._callbacks[event]; 1464 | if (!callbacks) return this; 1465 | 1466 | // remove all handlers 1467 | if (1 == arguments.length) { 1468 | delete this._callbacks[event]; 1469 | return this; 1470 | } 1471 | 1472 | // remove specific handler 1473 | var i = index(callbacks, fn._off || fn); 1474 | if (~i) callbacks.splice(i, 1); 1475 | return this; 1476 | }; 1477 | 1478 | /** 1479 | * Emit `event` with the given args. 1480 | * 1481 | * @param {String} event 1482 | * @param {Mixed} ... 1483 | * @return {Emitter} 1484 | */ 1485 | 1486 | Emitter.prototype.emit = function(event){ 1487 | this._callbacks = this._callbacks || {}; 1488 | var args = [].slice.call(arguments, 1) 1489 | , callbacks = this._callbacks[event]; 1490 | 1491 | if (callbacks) { 1492 | callbacks = callbacks.slice(0); 1493 | for (var i = 0, len = callbacks.length; i < len; ++i) { 1494 | callbacks[i].apply(this, args); 1495 | } 1496 | } 1497 | 1498 | return this; 1499 | }; 1500 | 1501 | /** 1502 | * Return array of callbacks for `event`. 1503 | * 1504 | * @param {String} event 1505 | * @return {Array} 1506 | * @api public 1507 | */ 1508 | 1509 | Emitter.prototype.listeners = function(event){ 1510 | this._callbacks = this._callbacks || {}; 1511 | return this._callbacks[event] || []; 1512 | }; 1513 | 1514 | /** 1515 | * Check if this emitter has `event` handlers. 1516 | * 1517 | * @param {String} event 1518 | * @return {Boolean} 1519 | * @api public 1520 | */ 1521 | 1522 | Emitter.prototype.hasListeners = function(event){ 1523 | return !! this.listeners(event).length; 1524 | }; 1525 | 1526 | },{"indexof":36}],11:[function(require,module,exports){ 1527 | 1528 | module.exports = require('./lib/'); 1529 | 1530 | },{"./lib/":12}],12:[function(require,module,exports){ 1531 | 1532 | module.exports = require('./socket'); 1533 | 1534 | /** 1535 | * Exports parser 1536 | * 1537 | * @api public 1538 | * 1539 | */ 1540 | module.exports.parser = require('engine.io-parser'); 1541 | 1542 | },{"./socket":13,"engine.io-parser":22}],13:[function(require,module,exports){ 1543 | var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/** 1544 | * Module dependencies. 1545 | */ 1546 | 1547 | var transports = require('./transports'); 1548 | var Emitter = require('component-emitter'); 1549 | var debug = require('debug')('engine.io-client:socket'); 1550 | var index = require('indexof'); 1551 | var parser = require('engine.io-parser'); 1552 | var parseuri = require('parseuri'); 1553 | var parsejson = require('parsejson'); 1554 | var parseqs = require('parseqs'); 1555 | 1556 | /** 1557 | * Module exports. 1558 | */ 1559 | 1560 | module.exports = Socket; 1561 | 1562 | /** 1563 | * Noop function. 1564 | * 1565 | * @api private 1566 | */ 1567 | 1568 | function noop(){} 1569 | 1570 | /** 1571 | * Socket constructor. 1572 | * 1573 | * @param {String|Object} uri or options 1574 | * @param {Object} options 1575 | * @api public 1576 | */ 1577 | 1578 | function Socket(uri, opts){ 1579 | if (!(this instanceof Socket)) return new Socket(uri, opts); 1580 | 1581 | opts = opts || {}; 1582 | 1583 | if (uri && 'object' == typeof uri) { 1584 | opts = uri; 1585 | uri = null; 1586 | } 1587 | 1588 | if (uri) { 1589 | uri = parseuri(uri); 1590 | opts.host = uri.host; 1591 | opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; 1592 | opts.port = uri.port; 1593 | if (uri.query) opts.query = uri.query; 1594 | } 1595 | 1596 | this.secure = null != opts.secure ? opts.secure : 1597 | (global.location && 'https:' == location.protocol); 1598 | 1599 | if (opts.host) { 1600 | var pieces = opts.host.split(':'); 1601 | opts.hostname = pieces.shift(); 1602 | if (pieces.length) opts.port = pieces.pop(); 1603 | } 1604 | 1605 | this.agent = opts.agent || false; 1606 | this.hostname = opts.hostname || 1607 | (global.location ? location.hostname : 'localhost'); 1608 | this.port = opts.port || (global.location && location.port ? 1609 | location.port : 1610 | (this.secure ? 443 : 80)); 1611 | this.query = opts.query || {}; 1612 | if ('string' == typeof this.query) this.query = parseqs.decode(this.query); 1613 | this.upgrade = false !== opts.upgrade; 1614 | this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; 1615 | this.forceJSONP = !!opts.forceJSONP; 1616 | this.forceBase64 = !!opts.forceBase64; 1617 | this.timestampParam = opts.timestampParam || 't'; 1618 | this.timestampRequests = opts.timestampRequests; 1619 | this.transports = opts.transports || ['polling', 'websocket']; 1620 | this.readyState = ''; 1621 | this.writeBuffer = []; 1622 | this.callbackBuffer = []; 1623 | this.policyPort = opts.policyPort || 843; 1624 | this.rememberUpgrade = opts.rememberUpgrade || false; 1625 | this.open(); 1626 | this.binaryType = null; 1627 | this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; 1628 | } 1629 | 1630 | Socket.priorWebsocketSuccess = false; 1631 | 1632 | /** 1633 | * Mix in `Emitter`. 1634 | */ 1635 | 1636 | Emitter(Socket.prototype); 1637 | 1638 | /** 1639 | * Protocol version. 1640 | * 1641 | * @api public 1642 | */ 1643 | 1644 | Socket.protocol = parser.protocol; // this is an int 1645 | 1646 | /** 1647 | * Expose deps for legacy compatibility 1648 | * and standalone browser access. 1649 | */ 1650 | 1651 | Socket.Socket = Socket; 1652 | Socket.Transport = require('./transport'); 1653 | Socket.transports = require('./transports'); 1654 | Socket.parser = require('engine.io-parser'); 1655 | 1656 | /** 1657 | * Creates transport of the given type. 1658 | * 1659 | * @param {String} transport name 1660 | * @return {Transport} 1661 | * @api private 1662 | */ 1663 | 1664 | Socket.prototype.createTransport = function (name) { 1665 | debug('creating transport "%s"', name); 1666 | var query = clone(this.query); 1667 | 1668 | // append engine.io protocol identifier 1669 | query.EIO = parser.protocol; 1670 | 1671 | // transport name 1672 | query.transport = name; 1673 | 1674 | // session id if we already have one 1675 | if (this.id) query.sid = this.id; 1676 | 1677 | var transport = new transports[name]({ 1678 | agent: this.agent, 1679 | hostname: this.hostname, 1680 | port: this.port, 1681 | secure: this.secure, 1682 | path: this.path, 1683 | query: query, 1684 | forceJSONP: this.forceJSONP, 1685 | forceBase64: this.forceBase64, 1686 | timestampRequests: this.timestampRequests, 1687 | timestampParam: this.timestampParam, 1688 | policyPort: this.policyPort, 1689 | socket: this 1690 | }); 1691 | 1692 | return transport; 1693 | }; 1694 | 1695 | function clone (obj) { 1696 | var o = {}; 1697 | for (var i in obj) { 1698 | if (obj.hasOwnProperty(i)) { 1699 | o[i] = obj[i]; 1700 | } 1701 | } 1702 | return o; 1703 | } 1704 | 1705 | /** 1706 | * Initializes transport to use and starts probe. 1707 | * 1708 | * @api private 1709 | */ 1710 | Socket.prototype.open = function () { 1711 | var transport; 1712 | if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { 1713 | transport = 'websocket'; 1714 | } else { 1715 | transport = this.transports[0]; 1716 | } 1717 | this.readyState = 'opening'; 1718 | var transport = this.createTransport(transport); 1719 | transport.open(); 1720 | this.setTransport(transport); 1721 | }; 1722 | 1723 | /** 1724 | * Sets the current transport. Disables the existing one (if any). 1725 | * 1726 | * @api private 1727 | */ 1728 | 1729 | Socket.prototype.setTransport = function(transport){ 1730 | debug('setting transport %s', transport.name); 1731 | var self = this; 1732 | 1733 | if (this.transport) { 1734 | debug('clearing existing transport %s', this.transport.name); 1735 | this.transport.removeAllListeners(); 1736 | } 1737 | 1738 | // set up transport 1739 | this.transport = transport; 1740 | 1741 | // set up transport listeners 1742 | transport 1743 | .on('drain', function(){ 1744 | self.onDrain(); 1745 | }) 1746 | .on('packet', function(packet){ 1747 | self.onPacket(packet); 1748 | }) 1749 | .on('error', function(e){ 1750 | self.onError(e); 1751 | }) 1752 | .on('close', function(){ 1753 | self.onClose('transport close'); 1754 | }); 1755 | }; 1756 | 1757 | /** 1758 | * Probes a transport. 1759 | * 1760 | * @param {String} transport name 1761 | * @api private 1762 | */ 1763 | 1764 | Socket.prototype.probe = function (name) { 1765 | debug('probing transport "%s"', name); 1766 | var transport = this.createTransport(name, { probe: 1 }) 1767 | , failed = false 1768 | , self = this; 1769 | 1770 | Socket.priorWebsocketSuccess = false; 1771 | 1772 | function onTransportOpen(){ 1773 | if (self.onlyBinaryUpgrades) { 1774 | var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; 1775 | failed = failed || upgradeLosesBinary; 1776 | } 1777 | if (failed) return; 1778 | 1779 | debug('probe transport "%s" opened', name); 1780 | transport.send([{ type: 'ping', data: 'probe' }]); 1781 | transport.once('packet', function (msg) { 1782 | if (failed) return; 1783 | if ('pong' == msg.type && 'probe' == msg.data) { 1784 | debug('probe transport "%s" pong', name); 1785 | self.upgrading = true; 1786 | self.emit('upgrading', transport); 1787 | Socket.priorWebsocketSuccess = 'websocket' == transport.name; 1788 | 1789 | debug('pausing current transport "%s"', self.transport.name); 1790 | self.transport.pause(function () { 1791 | if (failed) return; 1792 | if ('closed' == self.readyState || 'closing' == self.readyState) { 1793 | return; 1794 | } 1795 | debug('changing transport and sending upgrade packet'); 1796 | 1797 | cleanup(); 1798 | 1799 | self.setTransport(transport); 1800 | transport.send([{ type: 'upgrade' }]); 1801 | self.emit('upgrade', transport); 1802 | transport = null; 1803 | self.upgrading = false; 1804 | self.flush(); 1805 | }); 1806 | } else { 1807 | debug('probe transport "%s" failed', name); 1808 | var err = new Error('probe error'); 1809 | err.transport = transport.name; 1810 | self.emit('upgradeError', err); 1811 | } 1812 | }); 1813 | } 1814 | 1815 | function freezeTransport() { 1816 | if (failed) return; 1817 | 1818 | // Any callback called by transport should be ignored since now 1819 | failed = true; 1820 | 1821 | cleanup(); 1822 | 1823 | transport.close(); 1824 | transport = null; 1825 | } 1826 | 1827 | //Handle any error that happens while probing 1828 | function onerror(err) { 1829 | var error = new Error('probe error: ' + err); 1830 | error.transport = transport.name; 1831 | 1832 | freezeTransport(); 1833 | 1834 | debug('probe transport "%s" failed because of error: %s', name, err); 1835 | 1836 | self.emit('upgradeError', error); 1837 | } 1838 | 1839 | function onTransportClose(){ 1840 | onerror("transport closed"); 1841 | } 1842 | 1843 | //When the socket is closed while we're probing 1844 | function onclose(){ 1845 | onerror("socket closed"); 1846 | } 1847 | 1848 | //When the socket is upgraded while we're probing 1849 | function onupgrade(to){ 1850 | if (transport && to.name != transport.name) { 1851 | debug('"%s" works - aborting "%s"', to.name, transport.name); 1852 | freezeTransport(); 1853 | } 1854 | } 1855 | 1856 | //Remove all listeners on the transport and on self 1857 | function cleanup(){ 1858 | transport.removeListener('open', onTransportOpen); 1859 | transport.removeListener('error', onerror); 1860 | transport.removeListener('close', onTransportClose); 1861 | self.removeListener('close', onclose); 1862 | self.removeListener('upgrading', onupgrade); 1863 | } 1864 | 1865 | transport.once('open', onTransportOpen); 1866 | transport.once('error', onerror); 1867 | transport.once('close', onTransportClose); 1868 | 1869 | this.once('close', onclose); 1870 | this.once('upgrading', onupgrade); 1871 | 1872 | transport.open(); 1873 | 1874 | }; 1875 | 1876 | /** 1877 | * Called when connection is deemed open. 1878 | * 1879 | * @api public 1880 | */ 1881 | 1882 | Socket.prototype.onOpen = function () { 1883 | debug('socket open'); 1884 | this.readyState = 'open'; 1885 | Socket.priorWebsocketSuccess = 'websocket' == this.transport.name; 1886 | this.emit('open'); 1887 | this.flush(); 1888 | 1889 | // we check for `readyState` in case an `open` 1890 | // listener already closed the socket 1891 | if ('open' == this.readyState && this.upgrade && this.transport.pause) { 1892 | debug('starting upgrade probes'); 1893 | for (var i = 0, l = this.upgrades.length; i < l; i++) { 1894 | this.probe(this.upgrades[i]); 1895 | } 1896 | } 1897 | }; 1898 | 1899 | /** 1900 | * Handles a packet. 1901 | * 1902 | * @api private 1903 | */ 1904 | 1905 | Socket.prototype.onPacket = function (packet) { 1906 | if ('opening' == this.readyState || 'open' == this.readyState) { 1907 | debug('socket receive: type "%s", data "%s"', packet.type, packet.data); 1908 | 1909 | this.emit('packet', packet); 1910 | 1911 | // Socket is live - any packet counts 1912 | this.emit('heartbeat'); 1913 | 1914 | switch (packet.type) { 1915 | case 'open': 1916 | this.onHandshake(parsejson(packet.data)); 1917 | break; 1918 | 1919 | case 'pong': 1920 | this.setPing(); 1921 | break; 1922 | 1923 | case 'error': 1924 | var err = new Error('server error'); 1925 | err.code = packet.data; 1926 | this.emit('error', err); 1927 | break; 1928 | 1929 | case 'message': 1930 | this.emit('data', packet.data); 1931 | this.emit('message', packet.data); 1932 | break; 1933 | } 1934 | } else { 1935 | debug('packet received with socket readyState "%s"', this.readyState); 1936 | } 1937 | }; 1938 | 1939 | /** 1940 | * Called upon handshake completion. 1941 | * 1942 | * @param {Object} handshake obj 1943 | * @api private 1944 | */ 1945 | 1946 | Socket.prototype.onHandshake = function (data) { 1947 | this.emit('handshake', data); 1948 | this.id = data.sid; 1949 | this.transport.query.sid = data.sid; 1950 | this.upgrades = this.filterUpgrades(data.upgrades); 1951 | this.pingInterval = data.pingInterval; 1952 | this.pingTimeout = data.pingTimeout; 1953 | this.onOpen(); 1954 | // In case open handler closes socket 1955 | if ('closed' == this.readyState) return; 1956 | this.setPing(); 1957 | 1958 | // Prolong liveness of socket on heartbeat 1959 | this.removeListener('heartbeat', this.onHeartbeat); 1960 | this.on('heartbeat', this.onHeartbeat); 1961 | }; 1962 | 1963 | /** 1964 | * Resets ping timeout. 1965 | * 1966 | * @api private 1967 | */ 1968 | 1969 | Socket.prototype.onHeartbeat = function (timeout) { 1970 | clearTimeout(this.pingTimeoutTimer); 1971 | var self = this; 1972 | self.pingTimeoutTimer = setTimeout(function () { 1973 | if ('closed' == self.readyState) return; 1974 | self.onClose('ping timeout'); 1975 | }, timeout || (self.pingInterval + self.pingTimeout)); 1976 | }; 1977 | 1978 | /** 1979 | * Pings server every `this.pingInterval` and expects response 1980 | * within `this.pingTimeout` or closes connection. 1981 | * 1982 | * @api private 1983 | */ 1984 | 1985 | Socket.prototype.setPing = function () { 1986 | var self = this; 1987 | clearTimeout(self.pingIntervalTimer); 1988 | self.pingIntervalTimer = setTimeout(function () { 1989 | debug('writing ping packet - expecting pong within %sms', self.pingTimeout); 1990 | self.ping(); 1991 | self.onHeartbeat(self.pingTimeout); 1992 | }, self.pingInterval); 1993 | }; 1994 | 1995 | /** 1996 | * Sends a ping packet. 1997 | * 1998 | * @api public 1999 | */ 2000 | 2001 | Socket.prototype.ping = function () { 2002 | this.sendPacket('ping'); 2003 | }; 2004 | 2005 | /** 2006 | * Called on `drain` event 2007 | * 2008 | * @api private 2009 | */ 2010 | 2011 | Socket.prototype.onDrain = function() { 2012 | for (var i = 0; i < this.prevBufferLen; i++) { 2013 | if (this.callbackBuffer[i]) { 2014 | this.callbackBuffer[i](); 2015 | } 2016 | } 2017 | 2018 | this.writeBuffer.splice(0, this.prevBufferLen); 2019 | this.callbackBuffer.splice(0, this.prevBufferLen); 2020 | 2021 | // setting prevBufferLen = 0 is very important 2022 | // for example, when upgrading, upgrade packet is sent over, 2023 | // and a nonzero prevBufferLen could cause problems on `drain` 2024 | this.prevBufferLen = 0; 2025 | 2026 | if (this.writeBuffer.length == 0) { 2027 | this.emit('drain'); 2028 | } else { 2029 | this.flush(); 2030 | } 2031 | }; 2032 | 2033 | /** 2034 | * Flush write buffers. 2035 | * 2036 | * @api private 2037 | */ 2038 | 2039 | Socket.prototype.flush = function () { 2040 | if ('closed' != this.readyState && this.transport.writable && 2041 | !this.upgrading && this.writeBuffer.length) { 2042 | debug('flushing %d packets in socket', this.writeBuffer.length); 2043 | this.transport.send(this.writeBuffer); 2044 | // keep track of current length of writeBuffer 2045 | // splice writeBuffer and callbackBuffer on `drain` 2046 | this.prevBufferLen = this.writeBuffer.length; 2047 | this.emit('flush'); 2048 | } 2049 | }; 2050 | 2051 | /** 2052 | * Sends a message. 2053 | * 2054 | * @param {String} message. 2055 | * @param {Function} callback function. 2056 | * @return {Socket} for chaining. 2057 | * @api public 2058 | */ 2059 | 2060 | Socket.prototype.write = 2061 | Socket.prototype.send = function (msg, fn) { 2062 | this.sendPacket('message', msg, fn); 2063 | return this; 2064 | }; 2065 | 2066 | /** 2067 | * Sends a packet. 2068 | * 2069 | * @param {String} packet type. 2070 | * @param {String} data. 2071 | * @param {Function} callback function. 2072 | * @api private 2073 | */ 2074 | 2075 | Socket.prototype.sendPacket = function (type, data, fn) { 2076 | var packet = { type: type, data: data }; 2077 | this.emit('packetCreate', packet); 2078 | this.writeBuffer.push(packet); 2079 | this.callbackBuffer.push(fn); 2080 | this.flush(); 2081 | }; 2082 | 2083 | /** 2084 | * Closes the connection. 2085 | * 2086 | * @api private 2087 | */ 2088 | 2089 | Socket.prototype.close = function () { 2090 | if ('opening' == this.readyState || 'open' == this.readyState) { 2091 | this.onClose('forced close'); 2092 | debug('socket closing - telling transport to close'); 2093 | this.transport.close(); 2094 | } 2095 | 2096 | return this; 2097 | }; 2098 | 2099 | /** 2100 | * Called upon transport error 2101 | * 2102 | * @api private 2103 | */ 2104 | 2105 | Socket.prototype.onError = function (err) { 2106 | debug('socket error %j', err); 2107 | Socket.priorWebsocketSuccess = false; 2108 | this.emit('error', err); 2109 | this.onClose('transport error', err); 2110 | }; 2111 | 2112 | /** 2113 | * Called upon transport close. 2114 | * 2115 | * @api private 2116 | */ 2117 | 2118 | Socket.prototype.onClose = function (reason, desc) { 2119 | if ('opening' == this.readyState || 'open' == this.readyState) { 2120 | debug('socket close with reason: "%s"', reason); 2121 | var self = this; 2122 | 2123 | // clear timers 2124 | clearTimeout(this.pingIntervalTimer); 2125 | clearTimeout(this.pingTimeoutTimer); 2126 | 2127 | // clean buffers in next tick, so developers can still 2128 | // grab the buffers on `close` event 2129 | setTimeout(function() { 2130 | self.writeBuffer = []; 2131 | self.callbackBuffer = []; 2132 | self.prevBufferLen = 0; 2133 | }, 0); 2134 | 2135 | // stop event from firing again for transport 2136 | this.transport.removeAllListeners('close'); 2137 | 2138 | // ensure transport won't stay open 2139 | this.transport.close(); 2140 | 2141 | // ignore further transport communication 2142 | this.transport.removeAllListeners(); 2143 | 2144 | // set ready state 2145 | this.readyState = 'closed'; 2146 | 2147 | // clear session id 2148 | this.id = null; 2149 | 2150 | // emit close event 2151 | this.emit('close', reason, desc); 2152 | } 2153 | }; 2154 | 2155 | /** 2156 | * Filters upgrades, returning only those matching client transports. 2157 | * 2158 | * @param {Array} server upgrades 2159 | * @api private 2160 | * 2161 | */ 2162 | 2163 | Socket.prototype.filterUpgrades = function (upgrades) { 2164 | var filteredUpgrades = []; 2165 | for (var i = 0, j = upgrades.length; i