├── .editorconfig ├── .gitignore ├── .gitmodules ├── .jshintignore ├── .travis.build ├── .travis.yml ├── CHANGELOG.markdown ├── LICENCE ├── Makefile ├── README.markdown ├── bower.json ├── demos └── light │ ├── app.js │ ├── index.html │ └── stylesheet.css ├── dist ├── node │ └── pusher.js ├── react-native │ └── pusher.js ├── web │ ├── json2.js │ ├── json2.min.js │ ├── pusher.js │ ├── pusher.min.js │ ├── sockjs.js │ └── sockjs.min.js └── worker │ ├── pusher.worker.js │ └── pusher.worker.min.js ├── jshint-config.json ├── node.js ├── package.json ├── react-native.js ├── spec ├── config │ ├── jasmine-node │ │ ├── config.js │ │ ├── webpack.integration.js │ │ └── webpack.unit.js │ └── karma │ │ ├── available_browsers.json │ │ ├── config.ci.js │ │ ├── config.common.js │ │ ├── config.integration.js │ │ ├── config.unit.js │ │ ├── config.worker.js │ │ ├── integration.js │ │ └── unit.js └── javascripts │ ├── helpers │ ├── mocks.js │ ├── node │ │ └── integration.js │ └── web │ │ └── integration.js │ ├── integration │ ├── core │ │ ├── cluster_config_spec.js │ │ ├── falling_back_spec.js │ │ ├── pusher_spec.js │ │ ├── timeout_configuration_spec.js │ │ └── transport_lists_spec.js │ ├── index.node.js │ ├── index.web.js │ ├── index.worker.js │ └── web │ │ └── dom │ │ ├── jsonp_spec.js │ │ └── script_request_spec.js │ ├── node_modules │ └── pusher_integration.ts │ ├── polyfills │ └── index.js │ └── unit │ ├── core │ ├── channels │ │ ├── channel_spec.js │ │ ├── channels_spec.js │ │ ├── presence_channel_spec.js │ │ └── private_channel_spec.js │ ├── connection │ │ ├── connection_manager_spec.js │ │ ├── connection_spec.js │ │ ├── handshake_spec.js │ │ └── protocol_spec.js │ ├── defaults_spec.js │ ├── events_dispatcher_spec.js │ ├── http │ │ ├── http_polling_socket_spec.js │ │ ├── http_request_spec.js │ │ ├── http_socket_spec.js │ │ ├── http_streaming_socket_spec.js │ │ └── http_xhr_request_spec.js │ ├── logger_spec.js │ ├── pusher_authorizer_spec.js │ ├── pusher_spec.js │ ├── strategies │ │ ├── best_connected_ever_strategy_spec.js │ │ ├── cached_strategy_spec.js │ │ ├── delayed_strategy_spec.js │ │ ├── first_connected_strategy_spec.js │ │ ├── if_strategy_spec.js │ │ ├── sequential_strategy_spec.js │ │ ├── strategy_builder_spec.js │ │ └── transport_strategy_spec.js │ ├── timeline │ │ └── timeline_spec.js │ ├── transports │ │ ├── assistant_to_the_transport_manager_spec.js │ │ ├── hosts_and_ports_spec.js │ │ ├── transport_connection_spec.js │ │ └── transport_manager_spec.js │ └── utils │ │ ├── periodic_timer_spec.js │ │ └── timers_spec.js │ ├── index.node.js │ ├── index.web.js │ ├── index.worker.js │ ├── isomorphic │ └── transports │ │ ├── hosts_and_ports_spec.js │ │ └── transports_spec.js │ ├── node │ └── timeline_sender_spec.js │ ├── web │ ├── dom │ │ ├── dependency_loader_spec.js │ │ ├── jsonp_request_spec.js │ │ └── script_receiver_factory_spec.js │ ├── http │ │ └── http_xdomain_request_spec.js │ ├── pusher_authorizer_spec.js │ ├── timeline │ │ └── timeline_sender_spec.js │ └── transports │ │ ├── hosts_and_ports_spec.js │ │ └── transports_spec.js │ └── worker │ ├── pusher_authorizer_spec.js │ └── timeline_sender_spec.js ├── src ├── core │ ├── auth │ │ ├── auth_transports.ts │ │ ├── options.ts │ │ └── pusher_authorizer.ts │ ├── base64.ts │ ├── channels │ │ ├── channel.ts │ │ ├── channel_table.ts │ │ ├── channels.ts │ │ ├── members.ts │ │ ├── presence_channel.ts │ │ └── private_channel.ts │ ├── client.ts │ ├── config.ts │ ├── connection │ │ ├── callbacks.ts │ │ ├── connection.ts │ │ ├── connection_manager.ts │ │ ├── connection_manager_options.ts │ │ ├── handshake │ │ │ ├── handshake_payload.ts │ │ │ └── index.ts │ │ └── protocol │ │ │ ├── action.ts │ │ │ ├── message.ts │ │ │ └── protocol.ts │ ├── defaults.ts │ ├── errors.ts │ ├── events │ │ ├── callback.ts │ │ ├── callback_registry.ts │ │ ├── callback_table.ts │ │ └── dispatcher.ts │ ├── http │ │ ├── ajax.ts │ │ ├── http_factory.ts │ │ ├── http_polling_socket.ts │ │ ├── http_request.ts │ │ ├── http_socket.ts │ │ ├── http_streaming_socket.ts │ │ ├── request_hooks.ts │ │ ├── socket_hooks.ts │ │ ├── state.ts │ │ └── url_location.ts │ ├── index.ts │ ├── logger.ts │ ├── options.ts │ ├── pusher-licence.js │ ├── pusher.ts │ ├── reachability.ts │ ├── socket.ts │ ├── strategies │ │ ├── best_connected_ever_strategy.ts │ │ ├── cached_strategy.ts │ │ ├── delayed_strategy.ts │ │ ├── first_connected_strategy.ts │ │ ├── if_strategy.ts │ │ ├── sequential_strategy.ts │ │ ├── strategy.ts │ │ ├── strategy_builder.ts │ │ ├── strategy_options.ts │ │ ├── strategy_runner.ts │ │ └── transport_strategy.ts │ ├── timeline │ │ ├── level.ts │ │ ├── timeline.ts │ │ ├── timeline_sender.ts │ │ └── timeline_transport.ts │ ├── transports │ │ ├── assistant_to_the_transport_manager.ts │ │ ├── ping_delay_options.ts │ │ ├── transport.ts │ │ ├── transport_connection.ts │ │ ├── transport_connection_options.ts │ │ ├── transport_hooks.ts │ │ ├── transport_manager.ts │ │ ├── transports_table.ts │ │ ├── url_scheme.ts │ │ └── url_schemes.ts │ ├── util.ts │ └── utils │ │ ├── collections.ts │ │ ├── factory.ts │ │ └── timers │ │ ├── abstract_timer.ts │ │ ├── index.ts │ │ ├── scheduling.ts │ │ └── timed_callback.ts ├── d.ts │ ├── faye-websocket │ │ └── faye-websocket.d.ts │ ├── fetch │ │ ├── fetch.d.ts │ │ └── promise.d.ts │ ├── module │ │ └── module.d.ts │ ├── react-native │ │ └── react-native.d.ts │ ├── window │ │ ├── events.d.ts │ │ ├── sockjs.d.ts │ │ ├── websocket.d.ts │ │ └── xmlhttprequest.d.ts │ └── xmlhttprequest │ │ └── xmlhttprequest.d.ts └── runtimes │ ├── interface.ts │ ├── isomorphic │ ├── auth │ │ └── xhr_auth.ts │ ├── default_strategy.ts │ ├── http │ │ ├── http.ts │ │ └── http_xhr_request.ts │ ├── runtime.ts │ ├── timeline │ │ └── xhr_timeline.ts │ └── transports │ │ ├── transport_connection_initializer.ts │ │ └── transports.ts │ ├── node │ ├── net_info.ts │ └── runtime.ts │ ├── react-native │ ├── net_info.ts │ └── runtime.ts │ ├── web │ ├── auth │ │ └── jsonp_auth.ts │ ├── browser.ts │ ├── default_strategy.ts │ ├── dom │ │ ├── dependencies.ts │ │ ├── dependency_loader.ts │ │ ├── json2.js │ │ ├── jsonp_request.ts │ │ ├── script_receiver.ts │ │ ├── script_receiver_factory.ts │ │ └── script_request.ts │ ├── http │ │ ├── http.ts │ │ └── http_xdomain_request.ts │ ├── net_info.ts │ ├── runtime.ts │ ├── timeline │ │ └── jsonp_timeline.ts │ └── transports │ │ ├── transport_connection_initializer.ts │ │ └── transports.ts │ └── worker │ ├── auth │ └── fetch_auth.ts │ ├── net_info.ts │ ├── runtime.ts │ └── timeline │ └── fetch_timeline.ts ├── tsconfig.json └── webpack ├── config.min.js ├── config.node.js ├── config.react-native.js ├── config.shared.js ├── config.web.js ├── config.worker.js ├── dev.server.js └── hosting_config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.js] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rvmrc 2 | .bundle 3 | *.DS_Store 4 | *.swp 5 | coverage 6 | node_modules 7 | lib/ 8 | tmp/ 9 | dist/*/lib/ 10 | 11 | !/dist/* 12 | !/src/node_modules 13 | !spec/javascripts/node_modules 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sockjs-client"] 2 | path = src/runtimes/web/dom/sockjs 3 | url = git://github.com/pusher/sockjs-client.git 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | src/base64.js 2 | -------------------------------------------------------------------------------- /.travis.build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_BRANCH" == "master" ]; then 4 | export CI='full' ; 5 | else 6 | export CI='basic' ; 7 | fi 8 | 9 | make web_unit && make web_integration 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "5.1" 3 | before_script: 4 | - npm install 5 | script: 6 | - ./.travis.build 7 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Pusher JavaScript Library 2 | http://pusher.com/ 3 | 4 | Copyright 2014, Pusher 5 | Released under the MIT licence. 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | build_all: web react-native node worker 4 | 5 | sockjs: 6 | pushd src/runtimes/web/dom/sockjs && \ 7 | npm install && \ 8 | make && \ 9 | popd 10 | 11 | json2: 12 | cp src/runtimes/web/dom/json2.js dist/web 13 | node_modules/.bin/uglifyjs dist/web/json2.js -o dist/web/json2.min.js 14 | 15 | web: 16 | echo "Browser Release:" 17 | make sockjs 18 | cp src/runtimes/web/dom/sockjs/sockjs.js dist/web 19 | node_modules/webpack/bin/webpack.js --config=webpack/config.web.js 20 | echo "Browser Minified Release:" 21 | cp src/runtimes/web/dom/sockjs/sockjs.min.js dist/web 22 | MINIFY=web node_modules/webpack/bin/webpack.js --config=webpack/config.min.js 23 | 24 | react-native: 25 | echo "React Native Release:" 26 | node_modules/webpack/bin/webpack.js --config=webpack/config.react-native.js 27 | 28 | node: 29 | echo "NodeJS Release": 30 | node_modules/webpack/bin/webpack.js --config=webpack/config.node.js 31 | 32 | worker: 33 | echo "Web Worker Release:" 34 | node_modules/webpack/bin/webpack.js --config=webpack/config.worker.js 35 | echo "Web Worker Minified Release:" 36 | MINIFY=worker node_modules/webpack/bin/webpack.js --config=webpack/config.min.js 37 | 38 | web_unit: 39 | node_modules/karma/bin/karma start spec/config/karma/unit.js 40 | 41 | web_integration: 42 | node_modules/karma/bin/karma start spec/config/karma/integration.js 43 | 44 | worker_unit: 45 | WORKER=true node_modules/karma/bin/karma start spec/config/karma/unit.js 46 | 47 | worker_integration: 48 | WORKER=true node_modules/karma/bin/karma start spec/config/karma/integration.js 49 | 50 | node_unit: 51 | node_modules/webpack/bin/webpack.js --config=spec/config/jasmine-node/webpack.unit.js && \ 52 | node spec/config/jasmine-node/config.js ./tmp/node_unit 53 | 54 | node_integration: 55 | node_modules/webpack/bin/webpack.js --config=spec/config/jasmine-node/webpack.integration.js && \ 56 | node spec/config/jasmine-node/config.js ./tmp/node_integration 57 | 58 | serve: 59 | node webpack/dev.server.js 60 | 61 | .PHONY: build_all 62 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # This library has been merged into [PusherJS/master](https://github.com/pusher/pusher-js) 🎉🎉🎉 2 | 3 | Now you can use our official library to do awesome realtime stuff on ReactNative, Workers and NodeJS. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher-websocket-iso", 3 | "location": "git://github.com/pusher-community/pusher-websocket-js-iso.git", 4 | "description": "EXPERIMENTAL isomorphic Pusher WebSocket client", 5 | "keywords": [ 6 | "pusher", 7 | "client", 8 | "websocket", 9 | "http", 10 | "fallback", 11 | "isomorphic", 12 | "events", 13 | "pubsub" 14 | ], 15 | "licence": "MIT", 16 | "authors": ["Pusher "], 17 | "main": "./dist/web/pusher.js", 18 | "ignore": ["**/*", "!/dist/*", "!LICENCE", "!README.markdown", "!bower.json", "!CHANGELOG.markdown"] 19 | } 20 | -------------------------------------------------------------------------------- /demos/light/app.js: -------------------------------------------------------------------------------- 1 | $().ready(function(){ 2 | var socket = new Pusher('0eb73a235dd03b3df71a'); 3 | var nextConnectionAttempt = null; 4 | 5 | socket.connection.bind('connecting_in', function(data) { 6 | updateLight('amber'); 7 | updateControls('connect'); 8 | if(data > 0) 9 | { 10 | nextConnectionAttempt = new Date().getTime() + data; 11 | handleAmberDelay(nextConnectionAttempt); 12 | } 13 | }); 14 | 15 | socket.connection.bind('connecting', function() { 16 | updateLight('amber'); 17 | updateControls('connect'); 18 | }); 19 | 20 | socket.connection.bind('connected', function(data) { 21 | resetAmber(); 22 | updateLight('green'); 23 | updateControls('disconnect'); 24 | }); 25 | 26 | socket.connection.bind('disconnected', function(data) { 27 | updateLight('red'); 28 | updateControls('connect'); 29 | }); 30 | 31 | $('#connect').click(function() { 32 | socket.connection.connect(); 33 | }); 34 | 35 | $('#disconnect').click(function() { 36 | socket.connection.disconnect(); 37 | }); 38 | }); 39 | 40 | var lights = ['red', 'amber', 'green']; 41 | function updateLight(light) { 42 | for(var i = 0; i < lights.length; i++) 43 | { 44 | if(lights[i] !== light) 45 | { 46 | $("#" + lights[i] + 'on').hide(); 47 | $("#" + lights[i] + 'off').show(); 48 | } 49 | else 50 | { 51 | $("#" + lights[i] + 'on').show(); 52 | $("#" + lights[i] + 'off').hide(); 53 | } 54 | } 55 | }; 56 | 57 | var amberDelayTimer = null; 58 | function handleAmberDelay(nextConnectionAttempt) { 59 | clearTimeout(amberDelayTimer); 60 | updateAndSetTimer(nextConnectionAttempt); 61 | }; 62 | 63 | function updateAndSetTimer(nextConnectionAttempt) { 64 | amberDelayTimer = setTimeout(function() { 65 | var countdown = Math.round((nextConnectionAttempt - new Date().getTime()) / 1000); 66 | if(countdown >= 0) // can take a bit longer if using Flash 67 | $("#amberon").html(countdown); 68 | updateAndSetTimer(nextConnectionAttempt); 69 | }, 999); 70 | }; 71 | 72 | function resetAmber() { 73 | clearTimeout(amberDelayTimer); 74 | $("#amberon").html(""); 75 | }; 76 | 77 | var controls = ['connect', 'disconnect']; 78 | function updateControls(addControl) { 79 | for(var i = 0; i < controls.length; i++) 80 | if(controls[i] !== addControl) 81 | $("#" + controls[i]).hide(); 82 | else 83 | $("#" + controls[i]).show(); 84 | }; -------------------------------------------------------------------------------- /demos/light/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pusher Connection Status 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 |
23 |
24 |

Pusher Connection Status

25 |
26 |
27 |

28 |

29 | 30 |

31 |

32 | 33 |

34 |

35 | 36 |

37 |

38 | 39 |
40 | Connect 41 | 42 |
43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demos/light/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #EEE; 3 | color: #333; 4 | font-family: "Helvetica", Helvetica, Arial, sans-serif; 5 | font-size: 100%; } 6 | 7 | a { color:black; } 8 | a:visited { color:black; } 9 | 10 | .connect_disconnect { 11 | text-align: center; 12 | } 13 | 14 | .light_container { 15 | margin-left: auto; 16 | margin-right: auto; 17 | width: 40px; 18 | margin-bottom:50px; 19 | background-color:black; 20 | padding:5px 15px 5px 15px; 21 | -moz-border-radius: 10px; 22 | -webkit-border-radius: 10px; 23 | } 24 | 25 | .redon { 26 | display: block; 27 | width: 40px; 28 | height: 40px; 29 | background: #FF0000; 30 | -moz-border-radius: 20px; 31 | -webkit-border-radius: 20px; 32 | } 33 | 34 | .redoff { 35 | display: block; 36 | width: 40px; 37 | height: 40px; 38 | background: #440000; 39 | -moz-border-radius: 20px; 40 | -webkit-border-radius: 20px; 41 | } 42 | 43 | .amberon { 44 | display: block; 45 | width: 40px; 46 | height: 40px; 47 | background: #FFC800; 48 | -moz-border-radius: 20px; 49 | -webkit-border-radius: 20px; 50 | text-align:center; 51 | vertical-align: bottom; 52 | font-size:34px; 53 | color:#FFFF00; 54 | } 55 | 56 | .amberoff { 57 | display: block; 58 | width: 40px; 59 | height: 40px; 60 | background: #443800; 61 | -moz-border-radius: 20px; 62 | -webkit-border-radius: 20px; 63 | } 64 | 65 | .greenon { 66 | display: block; 67 | width: 40px; 68 | height: 40px; 69 | background: #00FF00; 70 | -moz-border-radius: 20px; 71 | -webkit-border-radius: 20px; 72 | } 73 | 74 | .greenoff { 75 | display: block; 76 | width: 40px; 77 | height: 40px; 78 | background: #004400; 79 | -moz-border-radius: 20px; 80 | -webkit-border-radius: 20px; 81 | } 82 | 83 | #container { 84 | position: relative; 85 | width: 500px; 86 | margin: 0 auto 40px; 87 | background: white; 88 | border: 1px solid #d0d0d0; 89 | -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; 90 | -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; 91 | -o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; 92 | box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; 93 | margin-top: 50px; 94 | 95 | padding: 0 0 6px 0; 96 | } 97 | #container header h2 { 98 | font-size: 2.4em; 99 | font-weight: normal; 100 | text-align: center; 101 | padding: 0.4em 0 0.7em; 102 | margin: 0; 103 | line-height: 1em; } 104 | 105 | footer { 106 | border-top: 1px solid #d0d0d0; 107 | padding-top: 6px; 108 | width: 500px; 109 | margin: 0 auto 2em; 110 | color: #777; 111 | text-shadow: rgba(255, 255, 255, 0.8) 0 1px 1px; 112 | text-align: center; 113 | font-size: 80%; 114 | } 115 | 116 | footer a { 117 | color: #777; 118 | text-decoration: underline; } 119 | footer a:visited { color: #777; } 120 | footer a:hover, footer a:focus { 121 | color: #777; 122 | text-decoration: none; } -------------------------------------------------------------------------------- /dist/web/json2.min.js: -------------------------------------------------------------------------------- 1 | if(typeof JSON!=="object"){JSON={}}(function(){"use strict";function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i 0; 36 | }, "JSONP to respond", 5000); 37 | runs(function() { 38 | expect(callback.calls.length).toEqual(1); 39 | expect(callback).toHaveBeenCalledWith(null, { 40 | "session": "2289545", 41 | "features": ["ws", "sockjs"], 42 | "version": "1.13.0", 43 | "t0": "1355850357911", 44 | "timeline": [ 45 | { t: 0, e: 2 }, 46 | { t: 2, e: 2, type: "ws" }, 47 | { t: 1001, e: 4, type: "ws" }, 48 | { t: 1001, e: 0, type: "sockjs" }, 49 | { t: 2000, e: 5 }, 50 | { t: 2158, e: 1, type: "sockjs" }, 51 | { t: 2378, e: 2, type: "sockjs" }, 52 | { t: 3892, e: 3, type: "sockjs" }, 53 | { t: 3892, e: 3 } 54 | ] 55 | }); 56 | request.cleanup() 57 | }); 58 | }); 59 | 60 | it("should call back without a result on 404 response", function() { 61 | var callback = jasmine.createSpy(); 62 | var receiver = Pusher.ScriptReceivers.create(callback); 63 | var url = Integration.API_URL + "/jsonp/404"; 64 | var request; 65 | 66 | runs(function() { 67 | request = new JSONPRequest(url, {}); 68 | request.send(receiver); 69 | }); 70 | waitsFor(function() { 71 | return callback.calls.length > 0; 72 | }, "JSONP to respond", 5000); 73 | runs(function() { 74 | expect(callback.calls.length).toEqual(1); 75 | expect(callback.calls[0].args[1]).toBe(undefined); 76 | request.cleanup(); 77 | }); 78 | }); 79 | 80 | it("should call back without a result on 500 response", function() { 81 | var callback = jasmine.createSpy(); 82 | var receiver = Pusher.ScriptReceivers.create(callback); 83 | var url = Integration.API_URL + "/jsonp/500"; 84 | var request; 85 | 86 | runs(function() { 87 | request = new JSONPRequest(url, {}); 88 | request.send(receiver); 89 | }); 90 | waitsFor(function() { 91 | return callback.calls.length > 0; 92 | }, "JSONP to respond", 5000); 93 | runs(function() { 94 | expect(callback.calls.length).toEqual(1); 95 | expect(callback.calls[0].args[1]).toBe(undefined); 96 | request.cleanup(); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /spec/javascripts/integration/web/dom/script_request_spec.js: -------------------------------------------------------------------------------- 1 | var Pusher = require('pusher_integration'); 2 | window.Pusher = Pusher; 3 | 4 | var Integration = require('integration'); 5 | var ScriptRequest = require('dom/script_request').default; 6 | 7 | 8 | Integration.describe("ScriptRequest", function() { 9 | var callback, receiver; 10 | var query, url; 11 | var request; 12 | var ScriptReceivers; 13 | 14 | beforeEach(function() { 15 | callback = jasmine.createSpy(); 16 | receiver = Pusher.Integration.ScriptReceivers.create(callback); 17 | query = "receiver=" + receiver.name + "¶m=test"; 18 | url = Integration.API_URL + "/v2/script_request/echo?" + query; 19 | request = new ScriptRequest(url); 20 | }); 21 | 22 | afterEach(function(){ 23 | request.cleanup(); 24 | }); 25 | 26 | it("should send a request and receive a correct response", function() { 27 | runs(function() { 28 | request.send(receiver); 29 | }); 30 | waitsFor(function() { 31 | return callback.calls.length > 0; 32 | }, "endpoint to respond", 5000); 33 | runs(function() { 34 | expect(callback.calls.length).toEqual(1); 35 | expect(callback).toHaveBeenCalledWith(null, { param: "test" }); 36 | }); 37 | }); 38 | 39 | it("should allow cleaning up", function() { 40 | var callback = jasmine.createSpy(); 41 | var receiver = Pusher.Integration.ScriptReceivers.create(callback); 42 | var query = "receiver=" + receiver.name; 43 | var url = Integration.API_URL + "/v2/script_request/echo?" + query; 44 | 45 | var request = new ScriptRequest(url); 46 | 47 | runs(function() { 48 | expect(document.getElementById(receiver.id)).toBe(null); 49 | expect(document.getElementById(receiver.id + "_error")).toBe(null); 50 | request.send(receiver); 51 | // we don't test for the _error tag, because it's Opera-specific 52 | expect(document.getElementById(receiver.id)).not.toBe(null); 53 | }); 54 | waitsFor(function() { 55 | return callback.calls.length > 0; 56 | }, "endpoint to respond", 5000); 57 | runs(function() { 58 | expect(document.getElementById(receiver.id)).not.toBe(null); 59 | request.cleanup(); 60 | expect(document.getElementById(receiver.id)).toBe(null); 61 | expect(document.getElementById(receiver.id + "_error")).toBe(null); 62 | }); 63 | }); 64 | 65 | it("should call back without result on a 404 response", function() { 66 | var url = Integration.API_URL + "/jsonp/404/" + receiver.number; 67 | var request = new ScriptRequest(url); 68 | 69 | runs(function() { 70 | request.send(receiver); 71 | }); 72 | waitsFor(function() { 73 | return callback.calls.length > 0; 74 | }, "endpoint to respond", 5000); 75 | runs(function() { 76 | expect(callback.calls.length).toEqual(1); 77 | expect(callback.calls[0].args[1]).toBe(undefined); 78 | request.cleanup(); 79 | }); 80 | }); 81 | 82 | it("should call back without result on a 500 response", function() { 83 | var url = Integration.API_URL + "/jsonp/500/" + receiver.number; 84 | var request = new ScriptRequest(url); 85 | 86 | runs(function() { 87 | request.send(receiver); 88 | }); 89 | waitsFor(function() { 90 | return callback.calls.length > 0; 91 | }, "endpoint to respond", 5000); 92 | runs(function() { 93 | expect(callback.calls.length).toEqual(1); 94 | expect(callback.calls[0].args[1]).toBe(undefined); 95 | request.cleanup(); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /spec/javascripts/node_modules/pusher_integration.ts: -------------------------------------------------------------------------------- 1 | import Pusher from 'core/pusher'; 2 | import {ScriptReceiverFactory} from 'dom/script_receiver_factory'; 3 | 4 | class PusherIntegration extends Pusher { 5 | 6 | static Integration : any = { 7 | ScriptReceivers: new ScriptReceiverFactory( 8 | "_pusher_integration_script_receivers", 9 | "Pusher.Integration.ScriptReceivers" 10 | )} 11 | 12 | } 13 | 14 | module.exports = PusherIntegration; 15 | -------------------------------------------------------------------------------- /spec/javascripts/polyfills/index.js: -------------------------------------------------------------------------------- 1 | // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys 2 | if (!Object.keys) { 3 | Object.keys = (function() { 4 | 'use strict'; 5 | var hasOwnProperty = Object.prototype.hasOwnProperty, 6 | hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'), 7 | dontEnums = [ 8 | 'toString', 9 | 'toLocaleString', 10 | 'valueOf', 11 | 'hasOwnProperty', 12 | 'isPrototypeOf', 13 | 'propertyIsEnumerable', 14 | 'constructor' 15 | ], 16 | dontEnumsLength = dontEnums.length; 17 | 18 | return function(obj) { 19 | if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { 20 | throw new TypeError('Object.keys called on non-object'); 21 | } 22 | 23 | var result = [], prop, i; 24 | 25 | for (prop in obj) { 26 | if (hasOwnProperty.call(obj, prop)) { 27 | result.push(prop); 28 | } 29 | } 30 | 31 | if (hasDontEnumBug) { 32 | for (i = 0; i < dontEnumsLength; i++) { 33 | if (hasOwnProperty.call(obj, dontEnums[i])) { 34 | result.push(dontEnums[i]); 35 | } 36 | } 37 | } 38 | return result; 39 | }; 40 | }()); 41 | } 42 | 43 | // Production steps of ECMA-262, Edition 5, 15.4.4.18 44 | // Reference: http://es5.github.io/#x15.4.4.18 45 | if (!Array.prototype.forEach) { 46 | 47 | Array.prototype.forEach = function(callback, thisArg) { 48 | 49 | var T, k; 50 | 51 | if (this == null) { 52 | throw new TypeError(' this is null or not defined'); 53 | } 54 | 55 | // 1. Let O be the result of calling toObject() passing the 56 | // |this| value as the argument. 57 | var O = Object(this); 58 | 59 | // 2. Let lenValue be the result of calling the Get() internal 60 | // method of O with the argument "length". 61 | // 3. Let len be toUint32(lenValue). 62 | var len = O.length >>> 0; 63 | 64 | // 4. If isCallable(callback) is false, throw a TypeError 65 | // exception. // See: http://es5.github.com/#x9.11 66 | if (typeof callback !== "function") { 67 | throw new TypeError(callback + ' is not a function'); 68 | } 69 | 70 | // 5. If thisArg was supplied, let T be thisArg; else let 71 | // T be undefined. 72 | if (arguments.length > 1) { 73 | T = thisArg; 74 | } 75 | 76 | // 6. Let k be 0 77 | k = 0; 78 | 79 | // 7. Repeat, while k < len 80 | while (k < len) { 81 | 82 | var kValue; 83 | 84 | // a. Let Pk be ToString(k). 85 | // This is implicit for LHS operands of the in operator 86 | // b. Let kPresent be the result of calling the HasProperty 87 | // internal method of O with argument Pk. 88 | // This step can be combined with c 89 | // c. If kPresent is true, then 90 | if (k in O) { 91 | 92 | // i. Let kValue be the result of calling the Get internal 93 | // method of O with argument Pk. 94 | kValue = O[k]; 95 | 96 | // ii. Call the Call internal method of callback with T as 97 | // the this value and argument list containing kValue, k, and O. 98 | callback.call(T, kValue, k, O); 99 | } 100 | // d. Increase k by 1. 101 | k++; 102 | } 103 | // 8. return undefined 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/channels/channels_spec.js: -------------------------------------------------------------------------------- 1 | var Channels = require('core/channels/channels').default; 2 | var Channel = require('core/channels/channel').default; 3 | var PrivateChannel = require('core/channels/private_channel').default; 4 | var PresenceChannel = require('core/channels/presence_channel').default; 5 | var Factory = require('core/utils/factory').default; 6 | var Mocks = require("mocks"); 7 | 8 | describe("Channels", function() { 9 | var channels; 10 | 11 | beforeEach(function() { 12 | channels = new Channels(Factory); 13 | }); 14 | 15 | describe("#add", function() { 16 | it("should create two different channels for different names", function() { 17 | var channel1 = channels.add("test1", {}); 18 | var channel2 = channels.add("test2", {}); 19 | 20 | expect(channel1).not.toEqual(channel2); 21 | }); 22 | 23 | it("should create a channel only once", function() { 24 | var channel = channels.add("test", {}); 25 | 26 | expect(channels.add("test", {})).toEqual(channel); 27 | }); 28 | 29 | it("should create a regular channel when name doesn't have known prefix", function() { 30 | expect(channels.add("test")).toEqual(jasmine.any(Channel)); 31 | }); 32 | 33 | it("should create a private channel when name starts with 'private-'", function() { 34 | expect(channels.add("private-test")).toEqual(jasmine.any(PrivateChannel)); 35 | }); 36 | 37 | it("should create a presence channel when name starts with 'presence-'", function() { 38 | expect(channels.add("presence-test")).toEqual(jasmine.any(PresenceChannel)); 39 | }); 40 | }); 41 | 42 | describe("#find", function() { 43 | it("should return previously inserted channels", function() { 44 | var channel1 = channels.add("test1", {}); 45 | var channel2 = channels.add("test2", {}); 46 | 47 | expect(channels.find("test1")).toEqual(channel1); 48 | expect(channels.find("test2")).toEqual(channel2); 49 | }); 50 | 51 | it("should return undefined if channel doesn't exist", function() { 52 | expect(channels.find("idontexist")).toBe(undefined); 53 | }); 54 | }); 55 | 56 | describe("#remove", function() { 57 | it("should remove previously inserted channel", function() { 58 | var channel1 = channels.add("test1", {}); 59 | var channel2 = channels.add("test2", {}); 60 | 61 | channels.remove("test1"); 62 | 63 | expect(channels.find("test1")).toBe(undefined); 64 | expect(channels.find("test2")).toEqual(channel2); 65 | }); 66 | }); 67 | 68 | describe("#disconnect", function() { 69 | it("should call disconnect on all channels", function() { 70 | var channel1 = channels.add("test1", {}); 71 | var channel2 = channels.add("test2", {}); 72 | 73 | spyOn(channel1, "disconnect"); 74 | spyOn(channel2, "disconnect"); 75 | channels.disconnect(); 76 | 77 | expect(channel1.disconnect).toHaveBeenCalled(); 78 | expect(channel2.disconnect).toHaveBeenCalled(); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/defaults_spec.js: -------------------------------------------------------------------------------- 1 | var WSTransport = require('runtime').default.Transports.ws; 2 | var StrategyBuilder = require('core/strategies/strategy_builder'); 3 | var Runtime = require('runtime').default; 4 | var DefaultConfig = require('core/config'); 5 | 6 | describe("Default", function() { 7 | describe("strategy", function() { 8 | function buildTest(ws) { 9 | it("should be supported when ws=" + ws, function() { 10 | if (ws) { 11 | spyOn(WSTransport, "isSupported").andReturn(true); 12 | } 13 | var strategy = StrategyBuilder.build( 14 | Runtime.getDefaultStrategy(DefaultConfig.getGlobalConfig()) 15 | ); 16 | expect(strategy.isSupported()).toBe(true); 17 | }); 18 | } 19 | 20 | for (var ws = 0; ws <= 1; ws++) { 21 | buildTest(ws); 22 | } 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/http/http_polling_socket_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require('mocks'); 2 | var HTTPFactory = require('runtime').default.HTTPFactory; 3 | 4 | describe("HTTP.createPollingSocket", function() { 5 | var hooks; 6 | var url; 7 | var socket; 8 | 9 | beforeEach(function() { 10 | spyOn(HTTPFactory, "createSocket").andCallFake(function(h, u) { 11 | socket = Mocks.getHTTPSocket(); 12 | hooks = h; 13 | url = u; 14 | return socket; 15 | }); 16 | }); 17 | 18 | it("should pass the correct url", function() { 19 | HTTPFactory.createPollingSocket("http://example.org/xyz"); 20 | expect(url).toEqual("http://example.org/xyz"); 21 | }); 22 | 23 | describe("hooks", function() { 24 | beforeEach(function() { 25 | HTTPFactory.createPollingSocket("http://example.com"); 26 | }); 27 | 28 | it("#getReceiveURL should generate a correct streaming URL", function() { 29 | var url = { base: "foo/bar", queryString: "?foo=bar" }; 30 | var session = "012/asdf"; 31 | expect(hooks.getReceiveURL(url, session)).toEqual( 32 | "foo/bar/012/asdf/xhr?foo=bar" 33 | ); 34 | }); 35 | 36 | it("#onHeartbeat should not send anything", function() { 37 | hooks.onHeartbeat(socket); 38 | expect(socket.sendRaw).not.toHaveBeenCalled(); 39 | }); 40 | 41 | it("#sendHeartbeat should send an '[]' frame", function() { 42 | hooks.sendHeartbeat(socket); 43 | expect(socket.sendRaw).toHaveBeenCalledWith("[]"); 44 | expect(socket.sendRaw.calls.length).toEqual(1); 45 | }); 46 | 47 | it("#onFinished with status 200 should reconnect the socket", function() { 48 | hooks.onFinished(socket, 200); 49 | expect(socket.reconnect.calls.length).toEqual(1); 50 | }); 51 | 52 | it("#onFinished with non-200 status should close the socket", function() { 53 | hooks.onFinished(socket, 500); 54 | expect(socket.onClose).toHaveBeenCalledWith( 55 | 1006, "Connection interrupted (500)", false 56 | ); 57 | expect(socket.onClose.calls.length).toEqual(1); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/http/http_streaming_socket_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require('mocks'); 2 | var HTTPFactory = require('runtime').default.HTTPFactory; 3 | 4 | describe("HTTP.getStreamingSocket", function() { 5 | var hooks; 6 | var url; 7 | var socket; 8 | 9 | beforeEach(function() { 10 | spyOn(HTTPFactory, "createSocket").andCallFake(function(h, u) { 11 | socket = Mocks.getHTTPSocket(); 12 | hooks = h; 13 | url = u; 14 | return socket; 15 | }); 16 | }); 17 | 18 | it("should pass the correct url", function() { 19 | HTTPFactory.createStreamingSocket("http://example.org/xyz"); 20 | expect(url).toEqual("http://example.org/xyz"); 21 | }); 22 | 23 | describe("hooks", function() { 24 | beforeEach(function() { 25 | HTTPFactory.createStreamingSocket("http://example.com"); 26 | }); 27 | 28 | it("#getReceiveURL should generate a correct streaming URL", function() { 29 | var url = { base: "foo/bar", queryString: "?foo=bar" }; 30 | var session = "012/asdf"; 31 | expect(hooks.getReceiveURL(url, session)).toEqual( 32 | "foo/bar/012/asdf/xhr_streaming?foo=bar" 33 | ); 34 | }); 35 | 36 | it("#onHeartbeat should send an '[]' frame", function() { 37 | hooks.onHeartbeat(socket); 38 | expect(socket.sendRaw).toHaveBeenCalledWith("[]"); 39 | expect(socket.sendRaw.calls.length).toEqual(1); 40 | }); 41 | 42 | it("#sendHeartbeat should send an '[]' frame", function() { 43 | hooks.sendHeartbeat(socket); 44 | expect(socket.sendRaw).toHaveBeenCalledWith("[]"); 45 | expect(socket.sendRaw.calls.length).toEqual(1); 46 | }); 47 | 48 | it("#onFinished should close the socket", function() { 49 | hooks.onFinished(socket, 200); 50 | expect(socket.onClose).toHaveBeenCalledWith( 51 | 1006, "Connection interrupted (200)", false 52 | ); 53 | expect(socket.onClose.calls.length).toEqual(1); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/logger_spec.js: -------------------------------------------------------------------------------- 1 | var Pusher = require('core/pusher').default; 2 | var Logger = require('core/logger').default; 3 | var global = Function("return this")(); 4 | 5 | describe("Pusher.logToConsole", function() { 6 | 7 | var _nativeConsoleLog; 8 | var _consoleLogCalls; 9 | 10 | beforeEach(function() { 11 | _consoleLogCalls = []; 12 | 13 | _nativeConsoleLog = global.console.log; 14 | global.console.log = function() { 15 | _consoleLogCalls.push(arguments); 16 | }; 17 | }); 18 | 19 | afterEach(function() { 20 | global.console.log = _nativeConsoleLog; 21 | }); 22 | 23 | it("should be disabled by default", function() { 24 | expect(Pusher.logToConsole).toEqual(false); 25 | }); 26 | 27 | it("should not log to the console if set to false", function() { 28 | Logger.warn("test", "this is a test"); 29 | 30 | expect(_consoleLogCalls.length).toEqual(0); 31 | }); 32 | 33 | it("should log to the console if set to true", function() { 34 | Pusher.logToConsole = true; 35 | Logger.warn("test", "this is a test"); 36 | 37 | expect(_consoleLogCalls.length).toBeGreaterThan(0); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/strategies/delayed_strategy_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require("mocks"); 2 | var DelayedStrategy = require('core/strategies/delayed_strategy').default; 3 | 4 | describe("DelayedStrategy", function() { 5 | beforeEach(function() { 6 | this.substrategy = Mocks.getStrategy(true); 7 | this.strategy = new DelayedStrategy(this.substrategy, { delay: 0 }); 8 | this.callback = jasmine.createSpy(); 9 | 10 | jasmine.Clock.useMock(); 11 | }); 12 | 13 | describe("after calling isSupported", function() { 14 | it("should return true if substrategy is supported", function() { 15 | var substrategy = Mocks.getStrategy(true); 16 | var strategy = new DelayedStrategy(substrategy, {}); 17 | expect(strategy.isSupported()).toBe(true); 18 | }); 19 | 20 | it("should return false if substrategy is not supported", function() { 21 | var substrategy = Mocks.getStrategy(false); 22 | var strategy = new DelayedStrategy(substrategy, {}); 23 | expect(strategy.isSupported()).toBe(false); 24 | }); 25 | }); 26 | 27 | describe("on connect", function() { 28 | it("should connect to substrategy after a delay", function() { 29 | var strategy = new DelayedStrategy(this.substrategy, { 30 | delay: 100 31 | }); 32 | 33 | strategy.connect(0, this.callback); 34 | 35 | expect(this.substrategy.connect).not.toHaveBeenCalled(); 36 | jasmine.Clock.tick(99); 37 | expect(this.substrategy.connect).not.toHaveBeenCalled(); 38 | jasmine.Clock.tick(1); 39 | expect(this.substrategy.connect).toHaveBeenCalled(); 40 | 41 | var handshake = {}; 42 | this.substrategy._callback(null, handshake); 43 | 44 | expect(this.callback).toHaveBeenCalledWith(null, handshake); 45 | }); 46 | 47 | it("should pass an error when substrategy fails", function() { 48 | this.strategy.connect(0, this.callback); 49 | jasmine.Clock.tick(0); 50 | this.substrategy._callback(true); 51 | 52 | expect(this.callback).toHaveBeenCalledWith(true); 53 | }); 54 | }); 55 | 56 | describe("on abort", function() { 57 | it("should abort substrategy when connecting", function() { 58 | var runner = this.strategy.connect(0); 59 | jasmine.Clock.tick(0); 60 | runner.abort(); 61 | expect(this.substrategy._abort).toHaveBeenCalled(); 62 | }); 63 | 64 | it("should clear the timer and not abort substrategy when waiting", function() { 65 | var run = this.strategy.connect(0); 66 | expect(this.substrategy.connect).not.toHaveBeenCalled(); 67 | run.abort(); 68 | jasmine.Clock.tick(10000); 69 | expect(this.substrategy._abort).not.toHaveBeenCalled(); 70 | expect(this.substrategy.connect).not.toHaveBeenCalled(); 71 | }); 72 | }); 73 | 74 | describe("on forceMinPriority", function() { 75 | it("should force the priority while waiting", function() { 76 | var runner = this.strategy.connect(0, this.callback); 77 | runner.forceMinPriority(5); 78 | jasmine.Clock.tick(0); 79 | expect(this.substrategy.connect) 80 | .toHaveBeenCalledWith(5, jasmine.any(Function)); 81 | }); 82 | 83 | it("should force the priority while connecting", function() { 84 | var runner = this.strategy.connect(0, this.callback); 85 | jasmine.Clock.tick(0); 86 | runner.forceMinPriority(5); 87 | expect(this.substrategy._forceMinPriority).toHaveBeenCalledWith(5); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/strategies/first_connected_strategy_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require("mocks"); 2 | var FirstConnectedStrategy = require('core/strategies/first_connected_strategy').default; 3 | 4 | describe("FirstConnectedStrategy", function() { 5 | var substrategy; 6 | var callback; 7 | var strategy; 8 | 9 | beforeEach(function() { 10 | substrategy = Mocks.getStrategy(true); 11 | strategy = new FirstConnectedStrategy(substrategy); 12 | state = {}; 13 | 14 | callback = jasmine.createSpy(); 15 | }); 16 | 17 | describe("after calling isSupported", function() { 18 | it("should return true when the substrategy is supported", function() { 19 | var substrategy = Mocks.getStrategy(true); 20 | var strategy = new FirstConnectedStrategy(substrategy); 21 | expect(strategy.isSupported()).toBe(true); 22 | }); 23 | 24 | it("should return false when the substrategy is not supported", function() { 25 | var substrategy = Mocks.getStrategy(false); 26 | var strategy = new FirstConnectedStrategy(substrategy); 27 | expect(strategy.isSupported()).toBe(false); 28 | }); 29 | }); 30 | 31 | describe("on connect", function() { 32 | it("should succeed on first connection and abort the substrategy", function() { 33 | strategy.connect(0, callback); 34 | 35 | expect(substrategy.connect) 36 | .toHaveBeenCalledWith(0, jasmine.any(Function)); 37 | 38 | var handshake = {}; 39 | substrategy._callback(null, handshake); 40 | 41 | expect(callback).toHaveBeenCalledWith(null, handshake); 42 | expect(substrategy._abort).toHaveBeenCalled(); 43 | }); 44 | 45 | it("should pass an error when the substrategy fails", function() { 46 | strategy.connect(0, callback); 47 | 48 | substrategy._callback(true); 49 | expect(callback).toHaveBeenCalledWith(true, undefined); 50 | }); 51 | }); 52 | 53 | describe("on abort", function() { 54 | it("should abort the substrategy", function() { 55 | var runner = strategy.connect(0); 56 | runner.abort(); 57 | expect(substrategy._abort).toHaveBeenCalled(); 58 | }); 59 | }); 60 | 61 | describe("on forceMinPriority", function() { 62 | it("should force the priority on the substrategy", function() { 63 | var runner = strategy.connect(0, this.callback); 64 | runner.forceMinPriority(5); 65 | expect(substrategy._forceMinPriority).toHaveBeenCalledWith(5); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/transports/hosts_and_ports_spec.js: -------------------------------------------------------------------------------- 1 | var TestEnv = require('testenv'); 2 | var Pusher = require('core/pusher').default; 3 | var NetInfo = require('net_info').NetInfo; 4 | var Mocks = require('mocks'); 5 | var Defaults = require('core/defaults').default; 6 | var version = Defaults.VERSION; 7 | var cdn_http = Defaults.cdn_http; 8 | var cdn_https = Defaults.cdn_https; 9 | var dependency_suffix = Defaults.dependency_suffix; 10 | var Runtime = require('runtime').default; 11 | 12 | describe("Host/Port Configuration", function() { 13 | var transport; 14 | var pusher; 15 | var Transports; 16 | 17 | beforeEach(function() { 18 | spyOn(Runtime, 'getNetwork').andCallFake(function(){ 19 | var network = new NetInfo(); 20 | network.isOnline = jasmine.createSpy("isOnline") 21 | .andReturn(true); 22 | return network; 23 | }); 24 | spyOn(Runtime, "getLocalStorage").andReturn({}); 25 | Transports = Runtime.Transports; 26 | }); 27 | 28 | afterEach(function() { 29 | pusher.disconnect(); 30 | }); 31 | 32 | describe("WebSockets", function() { 33 | var _WebSocket; 34 | 35 | beforeEach(function() { 36 | spyOn(Runtime, 'createWebSocket').andReturn(Mocks.getTransport()); 37 | 38 | spyOn(Transports.ws.hooks, "isInitialized").andReturn(true); 39 | spyOn(Transports.ws, "isSupported").andReturn(true); 40 | spyOn(Transports.xhr_streaming, "isSupported").andReturn(false); 41 | spyOn(Transports.xhr_polling, "isSupported").andReturn(false); 42 | 43 | if (TestEnv == "web") { 44 | spyOn(Transports.xdr_streaming, "isSupported").andReturn(false); 45 | spyOn(Transports.xdr_polling, "isSupported").andReturn(false); 46 | } 47 | }); 48 | 49 | it("should connect to ws://ws.pusherapp.com:80 by default", function() { 50 | pusher = new Pusher("foobar"); 51 | pusher.connect(); 52 | 53 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 54 | "ws://ws.pusherapp.com:80/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 55 | ); 56 | }); 57 | 58 | it("should connect to wss://ws.pusherapp.com:443 by default when encrypted", function() { 59 | pusher = new Pusher("foobar", { encrypted: true }); 60 | pusher.connect(); 61 | 62 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 63 | "wss://ws.pusherapp.com:443/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 64 | ); 65 | }); 66 | 67 | it("should connect using wsHost and wsPort when specified in options", function() { 68 | pusher = new Pusher("foobar", { wsHost: "example.com", wsPort: 1999 }); 69 | pusher.connect(); 70 | 71 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 72 | "ws://example.com:1999/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 73 | ); 74 | }); 75 | 76 | it("should connect using wsHost and wssPort when specified in options and encrypted", function() { 77 | pusher = new Pusher("foobar", { wsHost: "example.org", wssPort: 4444, encrypted: true }); 78 | pusher.connect(); 79 | 80 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 81 | "wss://example.org:4444/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 82 | ); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/transports/transport_manager_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require("mocks"); 2 | var Factory = require('core/utils/factory').default; 3 | var TransportManager = require('core/transports/transport_manager').default; 4 | var AssistantToTheTransportManager = require('core/transports/assistant_to_the_transport_manager').default; 5 | 6 | describe("TransportManager", function() { 7 | it("should create an assistant for a transport class", function() { 8 | var transportClass = Mocks.getTransportClass(true); 9 | var assistant = Mocks.getAssistantToTheTransportManager(); 10 | var manager = new TransportManager({ 11 | minPingDelay: 1111, 12 | maxPingDelay: 2222 13 | }); 14 | 15 | spyOn(Factory, 'createAssistantToTheTransportManager').andReturn(assistant); 16 | 17 | expect(manager.getAssistant(transportClass)).toBe(assistant); 18 | expect(Factory.createAssistantToTheTransportManager).toHaveBeenCalledWith( 19 | manager, transportClass, { minPingDelay: 1111, maxPingDelay: 2222 } 20 | ); 21 | }); 22 | 23 | describe("with initial two lives", function() { 24 | var manager; 25 | 26 | beforeEach(function() { 27 | manager = new TransportManager({ lives: 2 }); 28 | }); 29 | 30 | it("should be alive in the beginning", function() { 31 | expect(manager.isAlive()).toBe(true); 32 | }); 33 | 34 | it("should be alive after losing one life", function() { 35 | manager.reportDeath(); 36 | expect(manager.isAlive()).toBe(true); 37 | }); 38 | 39 | it("should be dead after losing both lives", function() { 40 | manager.reportDeath(); 41 | manager.reportDeath(); 42 | expect(manager.isAlive()).toBe(false); 43 | }); 44 | }); 45 | 46 | describe("with unlimited number of lives", function() { 47 | var manager; 48 | 49 | beforeEach(function() { 50 | manager = new TransportManager(); 51 | }); 52 | 53 | it("should be alive in the beginning", function() { 54 | expect(manager.isAlive()).toBe(true); 55 | }); 56 | 57 | it("should be alive after losing lots of lives", function() { 58 | for (var i = 0; i < 666; i++) { 59 | manager.reportDeath(); 60 | } 61 | expect(manager.isAlive()).toBe(true); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /spec/javascripts/unit/core/utils/periodic_timer_spec.js: -------------------------------------------------------------------------------- 1 | var Timers = require('core/utils/timers'); 2 | var PeriodicTimer = Timers.PeriodicTimer; 3 | var global = require('runtime').default.getGlobal(); 4 | 5 | describe("PeriodicTimer", function() { 6 | var callback; 7 | var timer; 8 | 9 | beforeEach(function() { 10 | jasmine.Clock.useMock(); 11 | 12 | callback = jasmine.createSpy("callback"); 13 | timer = new PeriodicTimer(123, callback); 14 | }); 15 | 16 | afterEach(function() { 17 | timer.ensureAborted(); 18 | }); 19 | 20 | it("should keep executing the callback with the specified interval", function() { 21 | expect(callback.calls.length).toEqual(0); 22 | jasmine.Clock.tick(122); 23 | expect(callback.calls.length).toEqual(0); 24 | jasmine.Clock.tick(1); 25 | expect(callback.calls.length).toEqual(1); 26 | 27 | expect(callback.calls.length).toEqual(1); 28 | jasmine.Clock.tick(122); 29 | expect(callback.calls.length).toEqual(1); 30 | jasmine.Clock.tick(1); 31 | expect(callback.calls.length).toEqual(2); 32 | }); 33 | 34 | describe("#isRunning", function() { 35 | it("should return true before first execution", function() { 36 | jasmine.Clock.tick(122); 37 | expect(timer.isRunning()).toBe(true); 38 | }); 39 | 40 | it("should return true after execution", function() { 41 | jasmine.Clock.tick(123); 42 | expect(timer.isRunning()).toBe(true); 43 | }); 44 | 45 | it("should return false after aborting", function() { 46 | timer.ensureAborted(); 47 | expect(timer.isRunning()).toBe(false); 48 | }); 49 | }); 50 | 51 | describe("#ensureAborted", function() { 52 | it("should abort the timer before execution", function() { 53 | timer.ensureAborted(); 54 | jasmine.Clock.tick(1000000); 55 | expect(callback).not.toHaveBeenCalled(); 56 | }); 57 | 58 | it("should play nice after first execution", function() { 59 | jasmine.Clock.tick(1000); 60 | timer.ensureAborted(); 61 | }); 62 | 63 | it("should stop callback from being called even if clearInterval is broken", function() { 64 | // IE has some edge-case with clearInterval not working, let's simulate it 65 | spyOn(global, "clearInterval"); 66 | timer.ensureAborted(); 67 | jasmine.Clock.tick(1000); 68 | expect(callback).not.toHaveBeenCalled(); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /spec/javascripts/unit/index.node.js: -------------------------------------------------------------------------------- 1 | var sharedTestsContext = require.context("./core", true, /_spec$/); 2 | sharedTestsContext.keys().forEach(sharedTestsContext); 3 | 4 | var nodeTestsContext = require.context("./isomorphic", true, /_spec$/); 5 | nodeTestsContext.keys().forEach(nodeTestsContext); 6 | 7 | var nodeTestsContext = require.context("./node", true, /_spec$/); 8 | nodeTestsContext.keys().forEach(nodeTestsContext) 9 | -------------------------------------------------------------------------------- /spec/javascripts/unit/index.web.js: -------------------------------------------------------------------------------- 1 | // the webpack context API uses Object.keys and Array.prototype.forEach 2 | // both of which are unavailable in old versions of IE. 3 | require('../polyfills'); 4 | 5 | var sharedTestsContext = require.context("./core", true, /_spec$/); 6 | sharedTestsContext.keys().forEach(sharedTestsContext); 7 | 8 | var nodeTestsContext = require.context("./web", true, /_spec$/); 9 | nodeTestsContext.keys().forEach(nodeTestsContext); 10 | -------------------------------------------------------------------------------- /spec/javascripts/unit/index.worker.js: -------------------------------------------------------------------------------- 1 | var sharedTestsContext = require.context("./core", true, /_spec$/); 2 | sharedTestsContext.keys().forEach(sharedTestsContext); 3 | 4 | var nodeTestsContext = require.context("./isomorphic", true, /_spec$/); 5 | nodeTestsContext.keys().forEach(nodeTestsContext); 6 | 7 | var nodeTestsContext = require.context("./worker", true, /_spec$/); 8 | nodeTestsContext.keys().forEach(nodeTestsContext); 9 | -------------------------------------------------------------------------------- /spec/javascripts/unit/isomorphic/transports/hosts_and_ports_spec.js: -------------------------------------------------------------------------------- 1 | var TestEnv = require('testenv'); 2 | var Pusher = require('core/pusher').default; 3 | var NetInfo = require('net_info').NetInfo; 4 | var Mocks = require('mocks'); 5 | var Defaults = require('core/defaults').default; 6 | var version = Defaults.VERSION; 7 | var cdn_http = Defaults.cdn_http; 8 | var cdn_https = Defaults.cdn_https; 9 | var dependency_suffix = Defaults.dependency_suffix; 10 | var Runtime = require('runtime').default; 11 | 12 | describe("Host/Port Configuration", function() { 13 | var transport; 14 | var pusher; 15 | var Transports; 16 | 17 | beforeEach(function() { 18 | spyOn(Runtime, 'getNetwork').andCallFake(function(){ 19 | var network = new NetInfo(); 20 | network.isOnline = jasmine.createSpy("isOnline") 21 | .andReturn(true); 22 | return network; 23 | }); 24 | spyOn(Runtime, "getLocalStorage").andReturn({}); 25 | }); 26 | 27 | afterEach(function() { 28 | pusher.disconnect(); 29 | }); 30 | 31 | describe("WebSockets", function() { 32 | var _WebSocket; 33 | 34 | beforeEach(function() { 35 | spyOn(Runtime, 'createWebSocket').andReturn(Mocks.getTransport()); 36 | 37 | var Transports = Runtime.Transports; 38 | 39 | spyOn(Transports.ws, "isSupported").andReturn(true); 40 | spyOn(Transports.xhr_streaming, "isSupported").andReturn(false); 41 | spyOn(Transports.xhr_polling, "isSupported").andReturn(false); 42 | }); 43 | 44 | it("should connect to ws://ws.pusherapp.com:80 by default", function() { 45 | pusher = new Pusher("foobar"); 46 | pusher.connect(); 47 | 48 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 49 | "ws://ws.pusherapp.com:80/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 50 | ); 51 | }); 52 | 53 | it("should connect to wss://ws.pusherapp.com:443 by default when encrypted", function() { 54 | pusher = new Pusher("foobar", { encrypted: true }); 55 | pusher.connect(); 56 | 57 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 58 | "wss://ws.pusherapp.com:443/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 59 | ); 60 | }); 61 | 62 | it("should connect using wsHost and wsPort when specified in options", function() { 63 | pusher = new Pusher("foobar", { wsHost: "example.com", wsPort: 1999 }); 64 | pusher.connect(); 65 | 66 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 67 | "ws://example.com:1999/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 68 | ); 69 | }); 70 | 71 | it("should connect using wsHost and wssPort when specified in options and encrypted", function() { 72 | pusher = new Pusher("foobar", { wsHost: "example.org", wssPort: 4444, encrypted: true }); 73 | pusher.connect(); 74 | 75 | expect(Runtime.createWebSocket).toHaveBeenCalledWith( 76 | "wss://example.org:4444/app/foobar?protocol=7&client=js&version="+version+"&flash=false" 77 | ); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /spec/javascripts/unit/node/timeline_sender_spec.js: -------------------------------------------------------------------------------- 1 | var Mocks = require('mocks'); 2 | var TimelineSender = require('core/timeline/timeline_sender').default; 3 | var Runtime = require('runtime').default; 4 | 5 | describe("TimelineSender", function() { 6 | var timeline, onSend, sender; 7 | 8 | beforeEach(function(){ 9 | timeline = Mocks.getTimeline(); 10 | timeline.isEmpty.andReturn(false); 11 | timeline.send.andCallFake(function(sendXHR, callback) { 12 | sendXHR({ events: [1, 2, 3]}, callback); 13 | }); 14 | 15 | onSend = jasmine.createSpy("onSend"); 16 | sender = new TimelineSender(timeline, { 17 | host: "example.com", 18 | path: "/timeline" 19 | }); 20 | }); 21 | 22 | describe("XHR", function(){ 23 | var xhrRequest; 24 | 25 | beforeEach(function() { 26 | spyOn(Runtime, "createXHR").andCallFake(function() { 27 | xhrRequest = Mocks.getXHR(); 28 | return xhrRequest; 29 | }); 30 | }); 31 | 32 | describe("on construction", function() { 33 | it("should expose options", function() { 34 | sender = new TimelineSender(timeline, { 35 | host: "localhost", 36 | port: 666 37 | }); 38 | 39 | expect(sender.options).toEqual({ 40 | host: "localhost", 41 | port: 666 42 | }); 43 | }); 44 | }); 45 | 46 | describe("on send", function() { 47 | it("should send a non-empty timeline", function() { 48 | sender.send(false, onSend); 49 | 50 | expect(Runtime.createXHR.calls.length).toEqual(1); 51 | var encodedParams = 'WzEsMiwzXQ%3D%3D'; 52 | 53 | expect(xhrRequest.open).toHaveBeenCalledWith( 54 | "GET", 55 | 'http://example.com/timeline/2?events=WzEsMiwzXQ%3D%3D', 56 | true); 57 | 58 | expect(xhrRequest.send).toHaveBeenCalled(); 59 | }); 60 | 61 | it("should send secure XHR requests when encrypted", function() { 62 | var sender = new TimelineSender(timeline, { 63 | encrypted: true, 64 | host: "example.com", 65 | path: "/timeline" 66 | }); 67 | sender.send(true, onSend); 68 | 69 | expect(Runtime.createXHR.calls.length).toEqual(1); 70 | expect(xhrRequest.open).toHaveBeenCalledWith( 71 | "GET", 72 | 'https://example.com/timeline/2?events=WzEsMiwzXQ%3D%3D', 73 | true); 74 | }); 75 | 76 | it("should not send an empty timeline", function() { 77 | timeline.isEmpty.andReturn(true); 78 | sender.send(false, onSend); 79 | expect(Runtime.createXHR).not.toHaveBeenCalled(); 80 | }); 81 | 82 | it("should use returned hostname for subsequent requests", function() { 83 | sender.send(false); 84 | xhrRequest.readyState = 4; 85 | xhrRequest.status = 200; 86 | xhrRequest.responseText = JSON.stringify({host: "returned.example.com"}); 87 | xhrRequest.onreadystatechange(); 88 | 89 | sender.send(false); 90 | expect(xhrRequest.open).toHaveBeenCalledWith( 91 | 'GET', 92 | 'http://returned.example.com/timeline/2?events=WzEsMiwzXQ%3D%3D', 93 | true 94 | ); 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /spec/javascripts/unit/web/dom/script_receiver_factory_spec.js: -------------------------------------------------------------------------------- 1 | var ScriptReceiverFactory = require('dom/script_receiver_factory').ScriptReceiverFactory; 2 | 3 | describe("ScriptReceiverFactory", function() { 4 | var receivers; 5 | var callback1, callback2; 6 | var receiver1, receiver2; 7 | 8 | beforeEach(function() { 9 | receivers = new ScriptReceiverFactory("test_prefix", "Recv"); 10 | callback1 = jasmine.createSpy(); 11 | callback2 = jasmine.createSpy(); 12 | receiver1 = receivers.create(callback1); 13 | receiver2 = receivers.create(callback2); 14 | }); 15 | 16 | describe("#create", function() { 17 | it("should set sequential ids with the supplied prefix", function() { 18 | expect(receiver1.id).toEqual("test_prefix1"); 19 | expect(receiver2.id).toEqual("test_prefix2"); 20 | }); 21 | 22 | it("should set correct names", function() { 23 | expect(receiver1.name).toEqual("Recv[1]"); 24 | expect(receiver2.name).toEqual("Recv[2]"); 25 | }); 26 | 27 | it("should set correct callbacks", function() { 28 | expect(callback1).not.toHaveBeenCalled(); 29 | expect(callback2).not.toHaveBeenCalled(); 30 | 31 | receiver1.callback(); 32 | expect(callback1).toHaveBeenCalled(); 33 | expect(callback2).not.toHaveBeenCalled(); 34 | 35 | receiver2.callback(); 36 | expect(callback1).toHaveBeenCalled(); 37 | expect(callback2).toHaveBeenCalled(); 38 | }); 39 | 40 | it("should bind correct callbacks to the factory", function() { 41 | expect(callback1).not.toHaveBeenCalled(); 42 | expect(callback2).not.toHaveBeenCalled(); 43 | 44 | receivers[1](); 45 | expect(callback1).toHaveBeenCalled(); 46 | expect(callback2).not.toHaveBeenCalled(); 47 | 48 | receivers[2](); 49 | expect(callback1).toHaveBeenCalled(); 50 | expect(callback2).toHaveBeenCalled(); 51 | }); 52 | }); 53 | 54 | describe("#remove", function() { 55 | it("should unbind correct callbacks from the factory", function() { 56 | expect(receivers[1]).not.toEqual(undefined); 57 | expect(receivers[2]).not.toEqual(undefined); 58 | 59 | receivers.remove(receiver1); 60 | expect(receivers[1]).toEqual(undefined); 61 | expect(receivers[2]).not.toEqual(undefined); 62 | 63 | receivers.remove(receiver2); 64 | expect(receivers[1]).toEqual(undefined); 65 | expect(receivers[2]).toEqual(undefined); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /spec/javascripts/unit/web/pusher_authorizer_spec.js: -------------------------------------------------------------------------------- 1 | var Authorizer = require('core/auth/pusher_authorizer').default; 2 | var Logger = require('core/logger'); 3 | var Mocks = require('mocks'); 4 | var Util = require('core/util').default; 5 | var Factory = require('core/utils/factory').default; 6 | var Logger = require('core/logger').default; 7 | var Runtime = require('runtime').default; 8 | 9 | describe("JSONP Authorizer", function() { 10 | it("should raise a warning if headers are passed", function() { 11 | var headers = { "foo": "bar", "n": 42 }; 12 | var authorizer = new Authorizer( 13 | { name: "chan" }, 14 | { authTransport: "jsonp", 15 | auth: { 16 | headers: headers 17 | } 18 | } 19 | ); 20 | 21 | var document = Mocks.getDocument(); 22 | var script = Mocks.getDocumentElement(); 23 | var documentElement = Mocks.getDocumentElement(); 24 | 25 | document.createElement.andReturn(script); 26 | document.getElementsByTagName.andReturn([]); 27 | document.documentElement = documentElement; 28 | spyOn(Runtime, "getDocument").andReturn(document); 29 | 30 | spyOn(Logger, "warn"); 31 | authorizer.authorize("1.23", function() {}); 32 | 33 | expect(Logger.warn).toHaveBeenCalledWith( 34 | "Warn", 35 | "To send headers with the auth request, you must use AJAX, rather than JSONP." 36 | ); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /spec/javascripts/unit/worker/pusher_authorizer_spec.js: -------------------------------------------------------------------------------- 1 | var Authorizer = require('core/auth/pusher_authorizer').default; 2 | var fetchAuth = require('worker/auth/fetch_auth').default; 3 | var Runtime = require('runtime').default; 4 | var fetchMock = require('fetch-mock'); 5 | 6 | var endpoint = 'http://example.org/pusher/auth'; 7 | 8 | describe("Fetch Authorizer", function(){ 9 | 10 | beforeEach(function(){ 11 | Authorizer.authorizers = {ajax: fetchAuth} 12 | }); 13 | 14 | afterEach(function(){ 15 | fetchMock.restore(); 16 | }); 17 | 18 | it("should pass headers in the request", function(){ 19 | fetchMock 20 | .mock(endpoint, {body: {hello: "world"}}); 21 | 22 | var headers = { "foo": "bar", "n": 42 }; 23 | var authorizer = new Authorizer( 24 | { name: "chan" }, 25 | { authTransport: "ajax", 26 | authEndpoint: endpoint, 27 | auth: { 28 | headers: headers 29 | } 30 | } 31 | ); 32 | 33 | authorizer.authorize("1.23", function() {}); 34 | 35 | var lastCall = fetchMock.lastCall(endpoint)[0]; 36 | var sentHeaders = lastCall.headers; 37 | expect(sentHeaders.get("Content-Type")).toEqual("application/x-www-form-urlencoded"); 38 | expect(sentHeaders.get("foo")).toEqual("bar"); 39 | expect(sentHeaders.get("n")).toEqual('42'); 40 | }); 41 | 42 | it("should pass params in the query string", function(){ 43 | fetchMock 44 | .mock(endpoint, {body: {hello: "world"}}); 45 | 46 | var params = { "a": 1, "b": 2 }; 47 | var authorizer = new Authorizer( 48 | { name: "chan" }, 49 | { authTransport: "ajax", 50 | authEndpoint: endpoint, 51 | auth: { 52 | params: params 53 | } 54 | } 55 | ); 56 | authorizer.authorize("1.23", function() {}).then(function(){ 57 | var lastCall = fetchMock.lastCall(endpoint)[0]; 58 | console.log(lastCall); 59 | expect(lastCall.body).toEqual("socket_id=1.23&channel_name=chan&a=1&b=2"); 60 | }); 61 | }); 62 | 63 | it("should call back with the auth result on success", function(){ 64 | var data = { foo: "bar", number: 1}; 65 | var dataJSON = JSON.stringify(data); 66 | 67 | fetchMock.mock(endpoint, { 68 | body: dataJSON 69 | }) 70 | 71 | var authorizer = new Authorizer( 72 | { name: "chan" }, 73 | { 74 | authTransport: "ajax", 75 | authEndpoint: endpoint 76 | } 77 | ); 78 | var callback = jasmine.createSpy("callback"); 79 | authorizer.authorize("1.23", callback).then(function(){ 80 | expect(callback.calls.length).toEqual(1); 81 | expect(callback).toHaveBeenCalledWith(false, data); 82 | }); 83 | }); 84 | 85 | it("should call back with an error if JSON is invalid", function(){ 86 | var authorizer = new Authorizer( 87 | { name: "chan" }, 88 | { 89 | authTransport: "ajax", 90 | authEndpoint: endpoint 91 | } 92 | ); 93 | 94 | var invalidJSON = 'INVALID { "something": "something"}'; 95 | fetchMock.mock(endpoint, { 96 | body: invalidJSON 97 | }) 98 | 99 | var callback = jasmine.createSpy("callback"); 100 | 101 | authorizer.authorize("1.23", callback).then(function(){ 102 | expect(callback.calls.length).toEqual(1); 103 | expect(callback).toHaveBeenCalledWith( 104 | true, 105 | "JSON returned from webapp was invalid, yet status code was 200. " + 106 | "Data was: " + 107 | invalidJSON 108 | ); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /spec/javascripts/unit/worker/timeline_sender_spec.js: -------------------------------------------------------------------------------- 1 | var fetchMock = require('fetch-mock'); 2 | var Mocks = require('mocks'); 3 | var TimelineSender = require('core/timeline/timeline_sender').default; 4 | var Runtime = require('runtime').default; 5 | var fetchTimeline = require('worker/timeline/fetch_timeline').default; 6 | 7 | describe("fetch", function(){ 8 | var encodedParams, timeline, onSend, sender; 9 | 10 | beforeEach(function(){ 11 | timeline = Mocks.getTimeline(); 12 | timeline.isEmpty.andReturn(false); 13 | timeline.send.andCallFake(function(sendXHR, callback) { 14 | sendXHR({ events: [1, 2, 3]}, callback); 15 | }); 16 | 17 | onSend = jasmine.createSpy("onSend"); 18 | sender = new TimelineSender(timeline, { 19 | host: "example.com", 20 | path: "/timeline" 21 | }); 22 | 23 | Runtime.TimelineTransport = fetchTimeline; 24 | encodedParams = 'WzEsMiwzXQ%3D%3D'; 25 | }); 26 | 27 | afterEach(function(){ 28 | fetchMock.restore(); 29 | }); 30 | 31 | describe("on send", function(){ 32 | it ("should send a non-empty timeline", function(){ 33 | var matcher = /example\.com/; 34 | 35 | fetchMock.mock(matcher, 200); 36 | sender.send(false, onSend); 37 | 38 | var lastCall = fetchMock.lastCall(matcher)[0]; 39 | expect(lastCall).toEqual('http://example.com/timeline/2?events=' + encodedParams); 40 | }); 41 | }); 42 | 43 | it("should send secure requests when encrypted", function(){ 44 | var matcher = /example\.com/; 45 | 46 | var sender = new TimelineSender(timeline, { 47 | encrypted: true, 48 | host: "example.com", 49 | path: "/timeline" 50 | }); 51 | 52 | fetchMock.mock(matcher, 200); 53 | sender.send(true, onSend); 54 | 55 | var lastCall = fetchMock.lastCall(matcher)[0]; 56 | expect(lastCall).toEqual('https://example.com/timeline/2?events=' + encodedParams); 57 | }); 58 | 59 | it("should use returned hostname for subsequent requests", function(done) { 60 | var matcher = /example\.com/; 61 | 62 | fetchMock.mock(matcher, { 63 | status: 200, 64 | body: JSON.stringify({host: "returned.example.com"}) 65 | }); 66 | 67 | sender.send(false); 68 | 69 | setTimeout(function(){ 70 | sender.send(false) 71 | var lastCall = fetchMock.lastCall(matcher)[0]; 72 | expect(lastCall).toEqual('http://returned.example.com/timeline/2?events=' + encodedParams); 73 | done(); 74 | }, 10); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/core/auth/auth_transports.ts: -------------------------------------------------------------------------------- 1 | import AbstractRuntime from 'runtimes/interface'; 2 | 3 | interface AuthTransport { 4 | (context : AbstractRuntime, socketId : string, callback : Function) : void 5 | } 6 | 7 | interface AuthTransports { 8 | [index : string] : AuthTransport; 9 | } 10 | 11 | 12 | export {AuthTransport, AuthTransports}; 13 | -------------------------------------------------------------------------------- /src/core/auth/options.ts: -------------------------------------------------------------------------------- 1 | export interface AuthOptions { 2 | params: any; 3 | headers : any; 4 | } 5 | 6 | export interface AuthorizerOptions { 7 | authTransport: "ajax" | "jsonp"; 8 | auth: AuthOptions; 9 | } 10 | -------------------------------------------------------------------------------- /src/core/auth/pusher_authorizer.ts: -------------------------------------------------------------------------------- 1 | import Logger from '../logger'; 2 | import Channel from '../channels/channel'; 3 | import Factory from '../utils/factory'; 4 | import Runtime from 'runtime'; 5 | import {AuthTransports} from './auth_transports'; 6 | import {AuthOptions, AuthorizerOptions} from './options'; 7 | 8 | export default class Authorizer { 9 | static authorizers : AuthTransports; 10 | 11 | channel: Channel; 12 | type: string; 13 | options: AuthorizerOptions; 14 | authOptions: AuthOptions; 15 | 16 | constructor(channel : Channel, options : AuthorizerOptions) { 17 | this.channel = channel; 18 | 19 | let {authTransport} = options; 20 | 21 | if (typeof Runtime.getAuthorizers()[authTransport] === "undefined") { 22 | throw `'${authTransport}' is not a recognized auth transport` 23 | } 24 | 25 | this.type = authTransport; 26 | this.options = options; 27 | this.authOptions = (options || {}).auth || {} 28 | } 29 | 30 | composeQuery(socketId : string) : string { 31 | var query = 'socket_id=' + encodeURIComponent(socketId) + 32 | '&channel_name=' + encodeURIComponent(this.channel.name); 33 | 34 | for(var i in this.authOptions.params) { 35 | query += "&" + encodeURIComponent(i) + "=" + encodeURIComponent(this.authOptions.params[i]); 36 | } 37 | 38 | return query; 39 | } 40 | 41 | authorize(socketId : string, callback : Function) : any { 42 | Authorizer.authorizers = Authorizer.authorizers || Runtime.getAuthorizers(); 43 | return Authorizer.authorizers[this.type].call(this, Runtime, socketId, callback); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/base64.ts: -------------------------------------------------------------------------------- 1 | const global = Function("return this")(); 2 | 3 | export default function encode (s : any) : string { 4 | return btoa(utob(s)); 5 | } 6 | 7 | var fromCharCode = String.fromCharCode; 8 | 9 | var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 10 | var b64tab = {}; 11 | 12 | for (var i = 0, l = b64chars.length; i < l; i++) { 13 | b64tab[b64chars.charAt(i)] = i; 14 | } 15 | 16 | var cb_utob = function(c) { 17 | var cc = c.charCodeAt(0); 18 | return cc < 0x80 ? c 19 | : cc < 0x800 ? fromCharCode(0xc0 | (cc >>> 6)) + 20 | fromCharCode(0x80 | (cc & 0x3f)) 21 | : fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + 22 | fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + 23 | fromCharCode(0x80 | ( cc & 0x3f)); 24 | }; 25 | 26 | var utob = function(u) { 27 | return u.replace(/[^\x00-\x7F]/g, cb_utob); 28 | }; 29 | 30 | var cb_encode = function(ccc) { 31 | var padlen = [0, 2, 1][ccc.length % 3]; 32 | var ord = ccc.charCodeAt(0) << 16 33 | | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) 34 | | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)); 35 | var chars = [ 36 | b64chars.charAt( ord >>> 18), 37 | b64chars.charAt((ord >>> 12) & 63), 38 | padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), 39 | padlen >= 1 ? '=' : b64chars.charAt(ord & 63) 40 | ]; 41 | return chars.join(''); 42 | }; 43 | 44 | var btoa = global.btoa || function(b) { 45 | return b.replace(/[\s\S]{1,3}/g, cb_encode); 46 | }; 47 | -------------------------------------------------------------------------------- /src/core/channels/channel.ts: -------------------------------------------------------------------------------- 1 | import {default as EventsDispatcher} from '../events/dispatcher'; 2 | import * as Errors from '../errors'; 3 | import Logger from '../logger'; 4 | import Pusher from '../pusher'; 5 | 6 | /** Provides base public channel interface with an event emitter. 7 | * 8 | * Emits: 9 | * - pusher:subscription_succeeded - after subscribing successfully 10 | * - other non-internal events 11 | * 12 | * @param {String} name 13 | * @param {Pusher} pusher 14 | */ 15 | export default class Channel extends EventsDispatcher { 16 | name: string; 17 | pusher: Pusher; 18 | subscribed: boolean; 19 | 20 | constructor(name : string, pusher: Pusher) { 21 | super(function(event, data){ 22 | Logger.debug('No callbacks on ' + name + ' for ' + event); 23 | }); 24 | 25 | this.name = name; 26 | this.pusher = pusher; 27 | this.subscribed = false; 28 | } 29 | 30 | /** Skips authorization, since public channels don't require it. 31 | * 32 | * @param {Function} callback 33 | */ 34 | authorize(socketId : string, callback : Function) { 35 | return callback(false, {}); 36 | } 37 | 38 | /** Triggers an event */ 39 | trigger(event : string, data : any) { 40 | if (event.indexOf("client-") !== 0) { 41 | throw new Errors.BadEventName( 42 | "Event '" + event + "' does not start with 'client-'" 43 | ); 44 | } 45 | return this.pusher.send_event(event, data, this.name); 46 | } 47 | 48 | /** Signals disconnection to the channel. For internal use only. */ 49 | disconnect() { 50 | this.subscribed = false; 51 | } 52 | 53 | /** Handles an event. For internal use only. 54 | * 55 | * @param {String} event 56 | * @param {*} data 57 | */ 58 | handleEvent(event : string, data : any) { 59 | if (event.indexOf("pusher_internal:") === 0) { 60 | if (event === "pusher_internal:subscription_succeeded") { 61 | this.subscribed = true; 62 | this.emit("pusher:subscription_succeeded", data); 63 | } 64 | } else { 65 | this.emit(event, data); 66 | } 67 | } 68 | 69 | /** Sends a subscription request. For internal use only. */ 70 | subscribe() { 71 | this.authorize(this.pusher.connection.socket_id, (error, data)=> { 72 | if (error) { 73 | this.handleEvent('pusher:subscription_error', data); 74 | } else { 75 | this.pusher.send_event('pusher:subscribe', { 76 | auth: data.auth, 77 | channel_data: data.channel_data, 78 | channel: this.name 79 | }); 80 | } 81 | }); 82 | } 83 | 84 | /** Sends an unsubscription request. For internal use only. */ 85 | unsubscribe() { 86 | this.pusher.send_event('pusher:unsubscribe', { 87 | channel: this.name 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/core/channels/channel_table.ts: -------------------------------------------------------------------------------- 1 | import Channel from "./channel"; 2 | 3 | interface ChannelTable { 4 | [index : string] : Channel; 5 | } 6 | 7 | export default ChannelTable; 8 | -------------------------------------------------------------------------------- /src/core/channels/channels.ts: -------------------------------------------------------------------------------- 1 | import Channel from "./channel"; 2 | import * as Collections from '../utils/collections'; 3 | import ChannelTable from './channel_table'; 4 | import Factory from '../utils/factory'; 5 | import Pusher from '../pusher'; 6 | 7 | /** Handles a channel map. */ 8 | export default class Channels { 9 | channels: ChannelTable; 10 | 11 | constructor() { 12 | this.channels = {}; 13 | } 14 | 15 | /** Creates or retrieves an existing channel by its name. 16 | * 17 | * @param {String} name 18 | * @param {Pusher} pusher 19 | * @return {Channel} 20 | */ 21 | add(name : string, pusher : Pusher) { 22 | if (!this.channels[name]) { 23 | this.channels[name] = createChannel(name, pusher); 24 | } 25 | return this.channels[name]; 26 | } 27 | 28 | /** Returns a list of all channels 29 | * 30 | * @return {Array} 31 | */ 32 | all() : Channel[] { 33 | return Collections.values(this.channels); 34 | } 35 | 36 | /** Finds a channel by its name. 37 | * 38 | * @param {String} name 39 | * @return {Channel} channel or null if it doesn't exist 40 | */ 41 | find(name: string) { 42 | return this.channels[name]; 43 | } 44 | 45 | /** Removes a channel from the map. 46 | * 47 | * @param {String} name 48 | */ 49 | remove(name : string) { 50 | var channel = this.channels[name]; 51 | delete this.channels[name]; 52 | return channel; 53 | } 54 | 55 | /** Proxies disconnection signal to all channels. */ 56 | disconnect() { 57 | Collections.objectApply(this.channels, function(channel) { 58 | channel.disconnect(); 59 | }); 60 | } 61 | } 62 | 63 | function createChannel(name : string, pusher : Pusher) : Channel { 64 | if (name.indexOf('private-') === 0) { 65 | return Factory.createPrivateChannel(name, pusher); 66 | } else if (name.indexOf('presence-') === 0) { 67 | return Factory.createPresenceChannel(name, pusher); 68 | } else { 69 | return Factory.createChannel(name, pusher); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/core/channels/members.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from '../utils/collections'; 2 | 3 | /** Represents a collection of members of a presence channel. */ 4 | export default class Members { 5 | 6 | members: any; 7 | count: number; 8 | myID: any; 9 | me: any; 10 | 11 | constructor(){ 12 | this.reset(); 13 | } 14 | 15 | /** Returns member's info for given id. 16 | * 17 | * Resulting object containts two fields - id and info. 18 | * 19 | * @param {Number} id 20 | * @return {Object} member's info or null 21 | */ 22 | get(id : string) : any { 23 | if (Object.prototype.hasOwnProperty.call(this.members, id)) { 24 | return { 25 | id: id, 26 | info: this.members[id] 27 | }; 28 | } else { 29 | return null; 30 | } 31 | } 32 | 33 | /** Calls back for each member in unspecified order. 34 | * 35 | * @param {Function} callback 36 | */ 37 | each(callback : Function) { 38 | Collections.objectApply(this.members, (member, id)=> { 39 | callback(this.get(id)); 40 | }); 41 | } 42 | 43 | /** Updates the id for connected member. For internal use only. */ 44 | setMyID(id : string) { 45 | this.myID = id; 46 | } 47 | 48 | /** Handles subscription data. For internal use only. */ 49 | onSubscription(subscriptionData : any) { 50 | this.members = subscriptionData.presence.hash; 51 | this.count = subscriptionData.presence.count; 52 | this.me = this.get(this.myID); 53 | } 54 | 55 | /** Adds a new member to the collection. For internal use only. */ 56 | addMember(memberData : any) { 57 | if (this.get(memberData.user_id) === null) { 58 | this.count++; 59 | } 60 | this.members[memberData.user_id] = memberData.user_info; 61 | return this.get(memberData.user_id); 62 | } 63 | 64 | /** Adds a member from the collection. For internal use only. */ 65 | removeMember(memberData : any) { 66 | var member = this.get(memberData.user_id); 67 | if (member) { 68 | delete this.members[memberData.user_id]; 69 | this.count--; 70 | } 71 | return member; 72 | } 73 | 74 | /** Resets the collection to the initial state. For internal use only. */ 75 | reset() { 76 | this.members = {}; 77 | this.count = 0; 78 | this.myID = null; 79 | this.me = null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/core/channels/presence_channel.ts: -------------------------------------------------------------------------------- 1 | import PrivateChannel from './private_channel'; 2 | import Logger from '../logger'; 3 | import Members from './members'; 4 | import Pusher from '../pusher'; 5 | 6 | export default class PresenceChannel extends PrivateChannel { 7 | members: Members; 8 | 9 | /** Adds presence channel functionality to private channels. 10 | * 11 | * @param {String} name 12 | * @param {Pusher} pusher 13 | */ 14 | constructor(name : string, pusher : Pusher) { 15 | super(name, pusher); 16 | this.members = new Members(); 17 | } 18 | 19 | /** Authenticates the connection as a member of the channel. 20 | * 21 | * @param {String} socketId 22 | * @param {Function} callback 23 | */ 24 | authorize(socketId : string, callback : Function) { 25 | super.authorize(socketId, (error, authData) => { 26 | if (!error) { 27 | if (authData.channel_data === undefined) { 28 | Logger.warn( 29 | "Invalid auth response for channel '" + 30 | this.name + 31 | "', expected 'channel_data' field" 32 | ); 33 | callback("Invalid auth response"); 34 | return; 35 | } 36 | var channelData = JSON.parse(authData.channel_data); 37 | this.members.setMyID(channelData.user_id); 38 | } 39 | callback(error, authData); 40 | }); 41 | } 42 | 43 | /** Handles presence and subscription events. For internal use only. 44 | * 45 | * @param {String} event 46 | * @param {*} data 47 | */ 48 | handleEvent(event : string, data : any) { 49 | switch (event) { 50 | case "pusher_internal:subscription_succeeded": 51 | this.members.onSubscription(data); 52 | this.subscribed = true; 53 | this.emit("pusher:subscription_succeeded", this.members); 54 | break; 55 | case "pusher_internal:member_added": 56 | var addedMember = this.members.addMember(data); 57 | this.emit('pusher:member_added', addedMember); 58 | break; 59 | case "pusher_internal:member_removed": 60 | var removedMember = this.members.removeMember(data); 61 | if (removedMember) { 62 | this.emit('pusher:member_removed', removedMember); 63 | } 64 | break; 65 | default: 66 | PrivateChannel.prototype.handleEvent.call(this, event, data); 67 | } 68 | } 69 | 70 | /** Resets the channel state, including members map. For internal use only. */ 71 | disconnect() { 72 | this.members.reset(); 73 | super.disconnect(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/core/channels/private_channel.ts: -------------------------------------------------------------------------------- 1 | import Factory from "../utils/factory"; 2 | import Channel from './channel'; 3 | 4 | /** Extends public channels to provide private channel interface. 5 | * 6 | * @param {String} name 7 | * @param {Pusher} pusher 8 | */ 9 | export default class PrivateChannel extends Channel { 10 | 11 | /** Authorizes the connection to use the channel. 12 | * 13 | * @param {String} socketId 14 | * @param {Function} callback 15 | */ 16 | authorize(socketId : string, callback : Function) { 17 | var authorizer = Factory.createAuthorizer(this, this.pusher.config); 18 | return authorizer.authorize(socketId, callback); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/core/client.ts: -------------------------------------------------------------------------------- 1 | import ConnectionManager from './connection/connection_manager'; 2 | import {AuthOptions} from './auth/options'; 3 | 4 | interface PusherOptions { 5 | cluster: string; 6 | disableStats: boolean; 7 | statsHost: string; 8 | activity_timeout: number; 9 | pong_timeout: number; 10 | unavailable_timeout: number; 11 | encrypted: boolean; 12 | timelineParams: any; 13 | authTransport: "ajax" | "jsonp"; 14 | auth: AuthOptions; 15 | } 16 | 17 | export default PusherOptions; 18 | -------------------------------------------------------------------------------- /src/core/config.ts: -------------------------------------------------------------------------------- 1 | import Defaults from './defaults'; 2 | 3 | export var getGlobalConfig = function() { 4 | return { 5 | wsHost: Defaults.host, 6 | wsPort: Defaults.ws_port, 7 | wssPort: Defaults.wss_port, 8 | httpHost: Defaults.sockjs_host, 9 | httpPort: Defaults.sockjs_http_port, 10 | httpsPort: Defaults.sockjs_https_port, 11 | httpPath: Defaults.sockjs_path, 12 | statsHost: Defaults.stats_host, 13 | authEndpoint: Defaults.channel_auth_endpoint, 14 | authTransport: Defaults.channel_auth_transport, 15 | // TODO make this consistent with other options in next major version 16 | activity_timeout: Defaults.activity_timeout, 17 | pong_timeout: Defaults.pong_timeout, 18 | unavailable_timeout: Defaults.unavailable_timeout 19 | }; 20 | }; 21 | 22 | export var getClusterConfig = function(clusterName) { 23 | return { 24 | wsHost: "ws-" + clusterName + ".pusher.com", 25 | httpHost: "sockjs-" + clusterName + ".pusher.com" 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/core/connection/callbacks.ts: -------------------------------------------------------------------------------- 1 | import HandshakePayload from './handshake/handshake_payload'; 2 | 3 | export interface ErrorCallbacks { 4 | ssl_only: (result: HandshakePayload) => void; 5 | refused: (result: HandshakePayload) => void; 6 | backoff: (result: HandshakePayload) => void; 7 | retry: (result: HandshakePayload) => void; 8 | } 9 | 10 | export interface HandshakeCallbacks { 11 | connected: (handshake: HandshakePayload) => void; 12 | } 13 | 14 | export interface ConnectionCallbacks { 15 | message: (message: any) => void; 16 | ping: () => void; 17 | activity: () => void; 18 | error: (error : any) => void; 19 | closed: () => void; 20 | } 21 | -------------------------------------------------------------------------------- /src/core/connection/connection_manager_options.ts: -------------------------------------------------------------------------------- 1 | import Timeline from '../timeline/timeline'; 2 | import Strategy from '../strategies/strategy'; 3 | import StategyOptions from '../strategies/strategy_options'; 4 | 5 | interface ConnectionManagerOptions { 6 | timeline: Timeline; 7 | getStrategy: (StrategyOptions) => Strategy; 8 | unavailableTimeout: number; 9 | pongTimeout: number; 10 | activityTimeout: number; 11 | } 12 | 13 | export default ConnectionManagerOptions; 14 | -------------------------------------------------------------------------------- /src/core/connection/handshake/handshake_payload.ts: -------------------------------------------------------------------------------- 1 | import TransportConnection from "../../transports/transport_connection"; 2 | import Action from "../protocol/action"; 3 | import Connection from "../connection"; 4 | 5 | interface HandshakePayload { 6 | transport: TransportConnection; 7 | action: Action; 8 | connection?: Connection; 9 | activityTimeout?: number; 10 | error: any; 11 | } 12 | 13 | export default HandshakePayload; 14 | -------------------------------------------------------------------------------- /src/core/connection/handshake/index.ts: -------------------------------------------------------------------------------- 1 | import Util from '../../util'; 2 | import * as Collections from '../../utils/collections'; 3 | import * as Protocol from '../protocol/protocol'; 4 | import Connection from '../connection'; 5 | import TransportConnection from "../../transports/transport_connection"; 6 | import HandshakePayload from './handshake_payload'; 7 | 8 | /** 9 | * Handles Pusher protocol handshakes for transports. 10 | * 11 | * Calls back with a result object after handshake is completed. Results 12 | * always have two fields: 13 | * - action - string describing action to be taken after the handshake 14 | * - transport - the transport object passed to the constructor 15 | * 16 | * Different actions can set different additional properties on the result. 17 | * In the case of 'connected' action, there will be a 'connection' property 18 | * containing a Connection object for the transport. Other actions should 19 | * carry an 'error' property. 20 | * 21 | * @param {AbstractTransport} transport 22 | * @param {Function} callback 23 | */ 24 | export default class Handshake { 25 | transport: TransportConnection; 26 | callback: (HandshakePayload)=>void; 27 | onMessage: Function; 28 | onClosed: Function; 29 | 30 | constructor(transport : TransportConnection, callback : (HandshakePayload)=>void) { 31 | this.transport = transport; 32 | this.callback = callback; 33 | this.bindListeners(); 34 | } 35 | 36 | close() { 37 | this.unbindListeners(); 38 | this.transport.close(); 39 | } 40 | 41 | private bindListeners() { 42 | this.onMessage = (m)=> { 43 | this.unbindListeners(); 44 | 45 | var result; 46 | try { 47 | result = Protocol.processHandshake(m); 48 | } catch (e) { 49 | this.finish("error", { error: e }); 50 | this.transport.close(); 51 | return; 52 | } 53 | 54 | if (result.action === "connected") { 55 | this.finish("connected", { 56 | connection: new Connection(result.id, this.transport), 57 | activityTimeout: result.activityTimeout 58 | }); 59 | } else { 60 | this.finish(result.action, { error: result.error }); 61 | this.transport.close(); 62 | } 63 | }; 64 | 65 | this.onClosed = (closeEvent) => { 66 | this.unbindListeners(); 67 | 68 | var action = Protocol.getCloseAction(closeEvent) || "backoff"; 69 | var error = Protocol.getCloseError(closeEvent); 70 | this.finish(action, { error: error }); 71 | }; 72 | 73 | this.transport.bind("message", this.onMessage); 74 | this.transport.bind("closed", this.onClosed); 75 | } 76 | 77 | private unbindListeners() { 78 | this.transport.unbind("message", this.onMessage); 79 | this.transport.unbind("closed", this.onClosed); 80 | } 81 | 82 | private finish(action : string, params : any) { 83 | this.callback( 84 | Collections.extend({ transport: this.transport, action: action }, params) 85 | ); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/core/connection/protocol/action.ts: -------------------------------------------------------------------------------- 1 | interface Action { 2 | action: string; 3 | id?: string; 4 | activityTimeout?: number; 5 | error?: any; 6 | } 7 | 8 | export default Action; 9 | -------------------------------------------------------------------------------- /src/core/connection/protocol/message.ts: -------------------------------------------------------------------------------- 1 | interface Message { 2 | event: string; 3 | channel?: string; 4 | data?: any; 5 | } 6 | 7 | export default Message; 8 | -------------------------------------------------------------------------------- /src/core/defaults.ts: -------------------------------------------------------------------------------- 1 | export interface DefaultConfig { 2 | VERSION: string; 3 | PROTOCOL: number; 4 | host: string; 5 | ws_port: number; 6 | wss_port: number; 7 | sockjs_host: string; 8 | sockjs_http_port: number; 9 | sockjs_https_port: number; 10 | sockjs_path: string; 11 | stats_host: string; 12 | channel_auth_endpoint: string; 13 | channel_auth_transport: string; 14 | activity_timeout: number; 15 | pong_timeout: number; 16 | unavailable_timeout: number; 17 | 18 | cdn_http?: string; 19 | cdn_https?: string; 20 | dependency_suffix?: string; 21 | } 22 | 23 | var Defaults : DefaultConfig = { 24 | VERSION: "", 25 | PROTOCOL: 7, 26 | 27 | // DEPRECATED: WS connection parameters 28 | host: 'ws.pusherapp.com', 29 | ws_port: 80, 30 | wss_port: 443, 31 | // DEPRECATED: SockJS fallback parameters 32 | sockjs_host: 'sockjs.pusher.com', 33 | sockjs_http_port: 80, 34 | sockjs_https_port: 443, 35 | sockjs_path: "/pusher", 36 | // DEPRECATED: Stats 37 | stats_host: 'stats.pusher.com', 38 | // DEPRECATED: Other settings 39 | channel_auth_endpoint: '/pusher/auth', 40 | channel_auth_transport: 'ajax', 41 | activity_timeout: 120000, 42 | pong_timeout: 30000, 43 | unavailable_timeout: 10000, 44 | 45 | // CDN configuration 46 | cdn_http: '', 47 | cdn_https: '', 48 | dependency_suffix: '' 49 | } 50 | 51 | export default Defaults; 52 | -------------------------------------------------------------------------------- /src/core/errors.ts: -------------------------------------------------------------------------------- 1 | /** Error classes used throughout the library. */ 2 | export class BadEventName extends Error {} 3 | export class RequestTimedOut extends Error {} 4 | export class TransportPriorityTooLow extends Error {} 5 | export class TransportClosed extends Error {} 6 | export class UnsupportedTransport extends Error {} 7 | export class UnsupportedStrategy extends Error {} 8 | -------------------------------------------------------------------------------- /src/core/events/callback.ts: -------------------------------------------------------------------------------- 1 | interface Callback { 2 | fn: Function; 3 | context: any; 4 | } 5 | 6 | export default Callback; 7 | -------------------------------------------------------------------------------- /src/core/events/callback_registry.ts: -------------------------------------------------------------------------------- 1 | import Callback from "./callback"; 2 | import * as Collections from '../utils/collections'; 3 | import CallbackTable from "./callback_table"; 4 | 5 | export default class CallbackRegistry { 6 | _callbacks: CallbackTable; 7 | 8 | constructor() { 9 | this._callbacks = {}; 10 | } 11 | 12 | get(name : string) : Callback[] { 13 | return this._callbacks[prefix(name)]; 14 | } 15 | 16 | add(name : string, callback : Function, context : any) { 17 | var prefixedEventName = prefix(name); 18 | this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || []; 19 | this._callbacks[prefixedEventName].push({ 20 | fn: callback, 21 | context: context 22 | }); 23 | } 24 | 25 | remove(name?: string, callback?: Function, context?: any) { 26 | if (!name && !callback && !context) { 27 | this._callbacks = {}; 28 | return; 29 | } 30 | 31 | var names = name ? [prefix(name)] : Collections.keys(this._callbacks); 32 | 33 | if (callback || context) { 34 | this.removeCallback(names, callback, context); 35 | } else { 36 | this.removeAllCallbacks(names); 37 | } 38 | } 39 | 40 | private removeCallback(names : string[], callback : Function, context : any) { 41 | Collections.apply(names, function(name) { 42 | this._callbacks[name] = Collections.filter( 43 | this._callbacks[name] || [], 44 | function(binding) { 45 | return (callback && callback !== binding.fn) || 46 | (context && context !== binding.context); 47 | } 48 | ); 49 | if (this._callbacks[name].length === 0) { 50 | delete this._callbacks[name]; 51 | } 52 | }, this); 53 | } 54 | 55 | private removeAllCallbacks(names : string[]) { 56 | Collections.apply(names, function(name) { 57 | delete this._callbacks[name]; 58 | }, this); 59 | } 60 | } 61 | 62 | function prefix(name : string) : string { 63 | return "_" + name; 64 | } 65 | -------------------------------------------------------------------------------- /src/core/events/callback_table.ts: -------------------------------------------------------------------------------- 1 | import Callback from "./callback"; 2 | 3 | interface CallbackTable { 4 | [index : string] : Callback[]; 5 | } 6 | 7 | export default CallbackTable; 8 | -------------------------------------------------------------------------------- /src/core/events/dispatcher.ts: -------------------------------------------------------------------------------- 1 | import CallbackRegistry from './callback_registry'; 2 | import Callback from './callback'; 3 | const global = Function("return this")(); 4 | 5 | /** Manages callback bindings and event emitting. 6 | * 7 | * @param Function failThrough called when no listeners are bound to an event 8 | */ 9 | export default class Dispatcher { 10 | callbacks: CallbackRegistry; 11 | global_callbacks: Function[]; 12 | failThrough: Function; 13 | 14 | constructor(failThrough?: Function) { 15 | this.callbacks = new CallbackRegistry(); 16 | this.global_callbacks = []; 17 | this.failThrough = failThrough; 18 | } 19 | 20 | bind(eventName : string, callback : Function, context?: any) { 21 | this.callbacks.add(eventName, callback, context); 22 | return this; 23 | } 24 | 25 | bind_all(callback : Function) { 26 | this.global_callbacks.push(callback); 27 | return this; 28 | } 29 | 30 | unbind(eventName : string, callback : Function, context?: any) { 31 | this.callbacks.remove(eventName, callback, context); 32 | return this; 33 | } 34 | 35 | unbind_all(eventName?: string, callback?: Function) { 36 | this.callbacks.remove(eventName, callback); 37 | return this; 38 | } 39 | 40 | emit(eventName : string, data?: any) : Dispatcher { 41 | var i; 42 | 43 | for (i = 0; i < this.global_callbacks.length; i++) { 44 | this.global_callbacks[i](eventName, data); 45 | } 46 | 47 | var callbacks = this.callbacks.get(eventName); 48 | if (callbacks && callbacks.length > 0) { 49 | for (i = 0; i < callbacks.length; i++) { 50 | callbacks[i].fn.call(callbacks[i].context || global, data); 51 | } 52 | } else if (this.failThrough) { 53 | this.failThrough(eventName, data); 54 | } 55 | 56 | return this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/core/http/ajax.ts: -------------------------------------------------------------------------------- 1 | interface Ajax { 2 | open(method : string, url : string, async?: boolean, user?: string, password?: string) : void; 3 | send(payload?: any) : void; 4 | setRequestHeader(key : string, value : string) : void; 5 | onreadystatechange : Function; 6 | readyState : number; 7 | responseText: string; 8 | status: number; 9 | withCredentials?: boolean; 10 | 11 | ontimeout: Function; 12 | onerror: Function; 13 | onprogress: Function; 14 | onload: Function; 15 | abort: Function; 16 | } 17 | 18 | export default Ajax; 19 | -------------------------------------------------------------------------------- /src/core/http/http_factory.ts: -------------------------------------------------------------------------------- 1 | import HTTPSocket from "./http_socket"; 2 | import SocketHooks from "./socket_hooks"; 3 | import HTTPRequest from "./http_request"; 4 | import RequestHooks from "./request_hooks"; 5 | import Ajax from './ajax'; 6 | 7 | interface HTTPFactory { 8 | createStreamingSocket(url : string) : HTTPSocket; 9 | createPollingSocket(url : string) : HTTPSocket; 10 | createSocket(hooks : SocketHooks, url : string) : HTTPSocket; 11 | createXHR(method : string, url : string) : HTTPRequest; 12 | createXDR?(method : string, url : string) : HTTPRequest; 13 | createRequest(hooks : RequestHooks, method : string, url : string) : HTTPRequest; 14 | } 15 | 16 | export default HTTPFactory; 17 | -------------------------------------------------------------------------------- /src/core/http/http_polling_socket.ts: -------------------------------------------------------------------------------- 1 | import SocketHooks from "./socket_hooks"; 2 | import URLLocation from "./url_location"; 3 | import HTTPSocket from "./http_socket"; 4 | 5 | var hooks : SocketHooks = { 6 | getReceiveURL: function(url : URLLocation, session : string) : string { 7 | return url.base + "/" + session + "/xhr" + url.queryString; 8 | }, 9 | onHeartbeat: function() { 10 | // next HTTP request will reset server's activity timer 11 | }, 12 | sendHeartbeat: function(socket) { 13 | socket.sendRaw("[]"); 14 | }, 15 | onFinished: function(socket, status) { 16 | if (status === 200) { 17 | socket.reconnect(); 18 | } else { 19 | socket.onClose(1006, "Connection interrupted (" + status + ")", false); 20 | } 21 | } 22 | }; 23 | 24 | export default hooks; 25 | -------------------------------------------------------------------------------- /src/core/http/http_request.ts: -------------------------------------------------------------------------------- 1 | import Runtime from "runtime"; 2 | import RequestHooks from "./request_hooks"; 3 | import Ajax from "./ajax"; 4 | import {default as EventsDispatcher} from "../events/dispatcher"; 5 | 6 | const MAX_BUFFER_LENGTH = 256*1024; 7 | 8 | export default class HTTPRequest extends EventsDispatcher { 9 | hooks: RequestHooks; 10 | method: string; 11 | url: string; 12 | position: number; 13 | xhr: Ajax; 14 | unloader: Function; 15 | 16 | constructor(hooks : RequestHooks, method : string, url : string) { 17 | super(); 18 | this.hooks = hooks; 19 | this.method = method; 20 | this.url = url; 21 | } 22 | 23 | start(payload?: any) { 24 | this.position = 0; 25 | this.xhr = this.hooks.getRequest(this); 26 | 27 | this.unloader = ()=> { 28 | this.close(); 29 | }; 30 | Runtime.addUnloadListener(this.unloader); 31 | 32 | this.xhr.open(this.method, this.url, true); 33 | 34 | if (this.xhr.setRequestHeader) { 35 | this.xhr.setRequestHeader("Content-Type", "application/json"); // ReactNative doesn't set this header by default. 36 | } 37 | this.xhr.send(payload); 38 | } 39 | 40 | close() { 41 | if (this.unloader) { 42 | Runtime.removeUnloadListener(this.unloader); 43 | this.unloader = null; 44 | } 45 | if (this.xhr) { 46 | this.hooks.abortRequest(this.xhr); 47 | this.xhr = null; 48 | } 49 | } 50 | 51 | onChunk(status : number, data : any) { 52 | while (true) { 53 | var chunk = this.advanceBuffer(data); 54 | if (chunk) { 55 | this.emit("chunk", { status: status, data: chunk }); 56 | } else { 57 | break; 58 | } 59 | } 60 | if (this.isBufferTooLong(data)) { 61 | this.emit("buffer_too_long"); 62 | } 63 | } 64 | 65 | private advanceBuffer(buffer : any[]) : any { 66 | var unreadData = buffer.slice(this.position); 67 | var endOfLinePosition = unreadData.indexOf("\n"); 68 | 69 | if (endOfLinePosition !== -1) { 70 | this.position += endOfLinePosition + 1; 71 | return unreadData.slice(0, endOfLinePosition); 72 | } else { 73 | // chunk is not finished yet, don't move the buffer pointer 74 | return null; 75 | } 76 | } 77 | 78 | private isBufferTooLong(buffer : any) : boolean { 79 | return this.position === buffer.length && buffer.length > MAX_BUFFER_LENGTH; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/core/http/http_streaming_socket.ts: -------------------------------------------------------------------------------- 1 | import SocketHooks from "./socket_hooks"; 2 | import HTTPSocket from "./http_socket"; 3 | 4 | var hooks : SocketHooks = { 5 | getReceiveURL: function(url, session) { 6 | return url.base + "/" + session + "/xhr_streaming" + url.queryString; 7 | }, 8 | onHeartbeat: function(socket) { 9 | socket.sendRaw("[]"); 10 | }, 11 | sendHeartbeat: function(socket) { 12 | socket.sendRaw("[]"); 13 | }, 14 | onFinished: function(socket, status) { 15 | socket.onClose(1006, "Connection interrupted (" + status + ")", false); 16 | } 17 | }; 18 | 19 | export default hooks; 20 | -------------------------------------------------------------------------------- /src/core/http/request_hooks.ts: -------------------------------------------------------------------------------- 1 | import HTTPRequest from "./http_request"; 2 | import Ajax from "./ajax"; 3 | 4 | interface RequestHooks { 5 | getRequest(HTTPRequest) : Ajax; 6 | abortRequest(HTTPRequest) : void; 7 | } 8 | 9 | export default RequestHooks; 10 | -------------------------------------------------------------------------------- /src/core/http/socket_hooks.ts: -------------------------------------------------------------------------------- 1 | import Socket from "../socket"; 2 | import URLLocation from "./url_location"; 3 | 4 | interface SocketHooks { 5 | getReceiveURL(url : URLLocation, session : string) : string; 6 | onHeartbeat(Socket) : void; 7 | sendHeartbeat(Socket) : void; 8 | onFinished(Socket, Status) : void; 9 | } 10 | 11 | export default SocketHooks; 12 | -------------------------------------------------------------------------------- /src/core/http/state.ts: -------------------------------------------------------------------------------- 1 | enum State { 2 | CONNECTING = 0, 3 | OPEN = 1, 4 | CLOSED = 3 5 | } 6 | 7 | export default State; 8 | -------------------------------------------------------------------------------- /src/core/http/url_location.ts: -------------------------------------------------------------------------------- 1 | interface URLLocation { 2 | base: string; 3 | queryString: string; 4 | } 5 | 6 | export default URLLocation; 7 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | import Pusher from './pusher'; 2 | 3 | // required so we don't have to do require('pusher').default etc. 4 | module.exports = Pusher; 5 | -------------------------------------------------------------------------------- /src/core/logger.ts: -------------------------------------------------------------------------------- 1 | import {stringify} from './utils/collections'; 2 | import Pusher from './pusher'; 3 | 4 | const Logger = { 5 | debug(...args : any[]) { 6 | if (!Pusher.log) { 7 | return 8 | } 9 | Pusher.log(stringify.apply(this, arguments)); 10 | }, 11 | warn(...args : any[]) { 12 | var message = stringify.apply(this, arguments); 13 | const global = Function("return this")(); 14 | if (global.console) { 15 | if (global.console.warn) { 16 | global.console.warn(message); 17 | } else if (global.console.log) { 18 | global.console.log(message); 19 | } 20 | } 21 | if (Pusher.log) { 22 | Pusher.log(message); 23 | } 24 | } 25 | } 26 | 27 | export default Logger; 28 | -------------------------------------------------------------------------------- /src/core/options.ts: -------------------------------------------------------------------------------- 1 | import ConnectionManager from './connection/connection_manager'; 2 | import {AuthOptions} from './auth/options'; 3 | 4 | interface PusherOptions { 5 | cluster: string; 6 | disableStats: boolean; 7 | statsHost: string; 8 | activity_timeout: number; 9 | pong_timeout: number; 10 | unavailable_timeout: number; 11 | encrypted: boolean; 12 | timelineParams: any; 13 | authTransport: "ajax" | "jsonp"; 14 | auth: AuthOptions; 15 | } 16 | 17 | export default PusherOptions; 18 | -------------------------------------------------------------------------------- /src/core/pusher-licence.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Pusher JavaScript Library v 3 | * http://pusher.com/ 4 | * 5 | * Copyright 2016, Pusher 6 | * Released under the MIT licence. 7 | */ 8 | -------------------------------------------------------------------------------- /src/core/reachability.ts: -------------------------------------------------------------------------------- 1 | import {default as EventsDispatcher} from './events/dispatcher'; 2 | 3 | interface Reachability extends EventsDispatcher { 4 | isOnline(): boolean; 5 | } 6 | 7 | export default Reachability; 8 | -------------------------------------------------------------------------------- /src/core/socket.ts: -------------------------------------------------------------------------------- 1 | interface Socket { 2 | send(payload : any) : void; 3 | ping?() : void; 4 | close(code?: any, reason?:any); 5 | sendRaw?(payload : any) : boolean; 6 | 7 | onopen?(evt?: any) : void; 8 | onerror?(error : any) : void; 9 | onclose?(closeEvent : any) : void; 10 | onmessage?(message : any) : void; 11 | onactivity?() : void; 12 | } 13 | 14 | export default Socket; 15 | -------------------------------------------------------------------------------- /src/core/strategies/best_connected_ever_strategy.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from '../utils/collections'; 2 | import Util from '../util'; 3 | import Strategy from './strategy'; 4 | 5 | /** Launches all substrategies and emits prioritized connected transports. 6 | * 7 | * @param {Array} strategies 8 | */ 9 | export default class BestConnectedEverStrategy implements Strategy { 10 | strategies: Strategy[]; 11 | 12 | constructor(strategies : Strategy[]) { 13 | this.strategies = strategies; 14 | } 15 | 16 | isSupported() : boolean { 17 | return Collections.any(this.strategies, Util.method("isSupported")); 18 | } 19 | 20 | connect(minPriority : number, callback : Function) { 21 | return connect(this.strategies, minPriority, function(i, runners) { 22 | return function(error, handshake) { 23 | runners[i].error = error; 24 | if (error) { 25 | if (allRunnersFailed(runners)) { 26 | callback(true); 27 | } 28 | return; 29 | } 30 | Collections.apply(runners, function(runner) { 31 | runner.forceMinPriority(handshake.transport.priority); 32 | }); 33 | callback(null, handshake); 34 | }; 35 | }); 36 | } 37 | } 38 | 39 | /** Connects to all strategies in parallel. 40 | * 41 | * Callback builder should be a function that takes two arguments: index 42 | * and a list of runners. It should return another function that will be 43 | * passed to the substrategy with given index. Runners can be aborted using 44 | * abortRunner(s) functions from this class. 45 | * 46 | * @param {Array} strategies 47 | * @param {Function} callbackBuilder 48 | * @return {Object} strategy runner 49 | */ 50 | function connect(strategies : Strategy[], minPriority : number, callbackBuilder : Function) { 51 | var runners = Collections.map(strategies, function(strategy, i, _, rs) { 52 | return strategy.connect(minPriority, callbackBuilder(i, rs)); 53 | }); 54 | return { 55 | abort: function() { 56 | Collections.apply(runners, abortRunner); 57 | }, 58 | forceMinPriority: function(p) { 59 | Collections.apply(runners, function(runner) { 60 | runner.forceMinPriority(p); 61 | }); 62 | } 63 | }; 64 | } 65 | 66 | function allRunnersFailed(runners) : boolean { 67 | return Collections.all(runners, function(runner) { 68 | return Boolean(runner.error); 69 | }); 70 | } 71 | 72 | function abortRunner(runner) { 73 | if (!runner.error && !runner.aborted) { 74 | runner.abort(); 75 | runner.aborted = true; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/core/strategies/delayed_strategy.ts: -------------------------------------------------------------------------------- 1 | import {OneOffTimer as Timer} from '../utils/timers'; 2 | import Strategy from './strategy'; 3 | import StrategyOptions from './strategy_options'; 4 | 5 | /** Runs substrategy after specified delay. 6 | * 7 | * Options: 8 | * - delay - time in miliseconds to delay the substrategy attempt 9 | * 10 | * @param {Strategy} strategy 11 | * @param {Object} options 12 | */ 13 | export default class DelayedStrategy implements Strategy { 14 | strategy: Strategy; 15 | options: {delay: number}; 16 | 17 | constructor(strategy : Strategy, {delay : number}) { 18 | this.strategy = strategy; 19 | this.options = {delay : number}; 20 | } 21 | 22 | isSupported() : boolean { 23 | return this.strategy.isSupported(); 24 | } 25 | 26 | connect(minPriority : number, callback : Function) { 27 | var strategy = this.strategy; 28 | var runner; 29 | var timer = new Timer(this.options.delay, function() { 30 | runner = strategy.connect(minPriority, callback); 31 | }); 32 | 33 | return { 34 | abort: function() { 35 | timer.ensureAborted(); 36 | if (runner) { 37 | runner.abort(); 38 | } 39 | }, 40 | forceMinPriority: function(p) { 41 | minPriority = p; 42 | if (runner) { 43 | runner.forceMinPriority(p); 44 | } 45 | } 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/strategies/first_connected_strategy.ts: -------------------------------------------------------------------------------- 1 | import Strategy from "./strategy"; 2 | import StrategyRunner from "./strategy_runner"; 3 | 4 | /** Launches the substrategy and terminates on the first open connection. 5 | * 6 | * @param {Strategy} strategy 7 | */ 8 | export default class FirstConnectedStrategy implements Strategy { 9 | strategy : Strategy; 10 | 11 | constructor(strategy : Strategy) { 12 | this.strategy = strategy; 13 | } 14 | 15 | isSupported() : boolean { 16 | return this.strategy.isSupported(); 17 | } 18 | 19 | connect(minPriority : number, callback : Function) : StrategyRunner { 20 | var runner = this.strategy.connect( 21 | minPriority, 22 | function(error, handshake) { 23 | if (handshake) { 24 | runner.abort(); 25 | } 26 | callback(error, handshake); 27 | } 28 | ); 29 | return runner; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/core/strategies/if_strategy.ts: -------------------------------------------------------------------------------- 1 | import Strategy from "./strategy"; 2 | import StrategyRunner from "./strategy_runner"; 3 | 4 | /** Proxies method calls to one of substrategies basing on the test function. 5 | * 6 | * @param {Function} test 7 | * @param {Strategy} trueBranch strategy used when test returns true 8 | * @param {Strategy} falseBranch strategy used when test returns false 9 | */ 10 | export default class IfStrategy implements Strategy { 11 | test: () => boolean; 12 | trueBranch : Strategy; 13 | falseBranch : Strategy; 14 | 15 | constructor(test : ()=>boolean, trueBranch : Strategy, falseBranch : Strategy) { 16 | this.test = test; 17 | this.trueBranch = trueBranch; 18 | this.falseBranch = falseBranch; 19 | } 20 | 21 | isSupported() : boolean { 22 | var branch = this.test() ? this.trueBranch : this.falseBranch; 23 | return branch.isSupported(); 24 | } 25 | 26 | connect(minPriority : number, callback : Function) : StrategyRunner { 27 | var branch = this.test() ? this.trueBranch : this.falseBranch; 28 | return branch.connect(minPriority, callback); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/strategies/sequential_strategy.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from '../utils/collections'; 2 | import Util from '../util'; 3 | import {OneOffTimer as Timer} from '../utils/timers'; 4 | import Strategy from "./strategy"; 5 | import StrategyOptions from "./strategy_options"; 6 | 7 | /** Loops through strategies with optional timeouts. 8 | * 9 | * Options: 10 | * - loop - whether it should loop through the substrategy list 11 | * - timeout - initial timeout for a single substrategy 12 | * - timeoutLimit - maximum timeout 13 | * 14 | * @param {Strategy[]} strategies 15 | * @param {Object} options 16 | */ 17 | export default class SequentialStrategy implements Strategy { 18 | 19 | strategies: Strategy[]; 20 | loop: boolean; 21 | failFast: boolean; 22 | timeout: number; 23 | timeoutLimit: number; 24 | 25 | constructor(strategies : Strategy[], options : StrategyOptions) { 26 | this.strategies = strategies; 27 | this.loop = Boolean(options.loop); 28 | this.failFast = Boolean(options.failFast); 29 | this.timeout = options.timeout; 30 | this.timeoutLimit = options.timeoutLimit; 31 | } 32 | 33 | isSupported() : boolean { 34 | return Collections.any(this.strategies, Util.method("isSupported")); 35 | } 36 | 37 | connect(minPriority : number, callback : Function) { 38 | var strategies = this.strategies; 39 | var current = 0; 40 | var timeout = this.timeout; 41 | var runner = null; 42 | 43 | var tryNextStrategy = (error, handshake)=> { 44 | if (handshake) { 45 | callback(null, handshake); 46 | } else { 47 | current = current + 1; 48 | if (this.loop) { 49 | current = current % strategies.length; 50 | } 51 | 52 | if (current < strategies.length) { 53 | if (timeout) { 54 | timeout = timeout * 2; 55 | if (this.timeoutLimit) { 56 | timeout = Math.min(timeout, this.timeoutLimit); 57 | } 58 | } 59 | runner = this.tryStrategy( 60 | strategies[current], 61 | minPriority, 62 | { timeout, failFast: this.failFast }, 63 | tryNextStrategy 64 | ); 65 | } else { 66 | callback(true); 67 | } 68 | } 69 | }; 70 | 71 | runner = this.tryStrategy( 72 | strategies[current], 73 | minPriority, 74 | { timeout: timeout, failFast: this.failFast }, 75 | tryNextStrategy 76 | ); 77 | 78 | return { 79 | abort: function() { 80 | runner.abort(); 81 | }, 82 | forceMinPriority: function(p) { 83 | minPriority = p; 84 | if (runner) { 85 | runner.forceMinPriority(p); 86 | } 87 | } 88 | }; 89 | } 90 | 91 | private tryStrategy(strategy : Strategy, minPriority : number, options : StrategyOptions, callback : Function) { 92 | var timer = null; 93 | var runner = null; 94 | 95 | if (options.timeout > 0) { 96 | timer = new Timer(options.timeout, function() { 97 | runner.abort(); 98 | callback(true); 99 | }); 100 | } 101 | 102 | runner = strategy.connect(minPriority, function(error, handshake) { 103 | if (error && timer && timer.isRunning() && !options.failFast) { 104 | // advance to the next strategy after the timeout 105 | return; 106 | } 107 | if (timer) { 108 | timer.ensureAborted(); 109 | } 110 | callback(error, handshake); 111 | }); 112 | 113 | return { 114 | abort: function() { 115 | if (timer) { 116 | timer.ensureAborted(); 117 | } 118 | runner.abort(); 119 | }, 120 | forceMinPriority: function(p) { 121 | runner.forceMinPriority(p); 122 | } 123 | }; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/core/strategies/strategy.ts: -------------------------------------------------------------------------------- 1 | import StrategyRunner from "./strategy_runner"; 2 | 3 | interface Strategy { 4 | isSupported() : boolean; 5 | connect(minPriority: number, callback: Function) : StrategyRunner; 6 | } 7 | 8 | export default Strategy; 9 | -------------------------------------------------------------------------------- /src/core/strategies/strategy_options.ts: -------------------------------------------------------------------------------- 1 | interface StrategyOptions { 2 | ttl?: number; 3 | timeline?: any; 4 | encrypted?: boolean; 5 | ignoreNullOrigin?: boolean; 6 | loop?: boolean; 7 | failFast?: boolean; 8 | timeout?: number; 9 | timeoutLimit?: number; 10 | key?: string; 11 | } 12 | 13 | export default StrategyOptions; 14 | -------------------------------------------------------------------------------- /src/core/strategies/strategy_runner.ts: -------------------------------------------------------------------------------- 1 | interface StrategyRunner { 2 | forceMinPriority: (number)=> void; 3 | abort: ()=> void; 4 | } 5 | 6 | export default StrategyRunner; 7 | -------------------------------------------------------------------------------- /src/core/timeline/level.ts: -------------------------------------------------------------------------------- 1 | enum TimelineLevel { 2 | ERROR = 3, 3 | INFO = 6, 4 | DEBUG = 7 5 | } 6 | 7 | export default TimelineLevel; 8 | -------------------------------------------------------------------------------- /src/core/timeline/timeline.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from "../utils/collections"; 2 | import Util from "../util"; 3 | import {default as Level} from "./level"; 4 | 5 | export interface TimelineOptions { 6 | level?: Level; 7 | limit?: number; 8 | version?: string; 9 | cluster?: string; 10 | features?: string[]; 11 | params?: any; 12 | } 13 | 14 | export default class Timeline { 15 | key: string; 16 | session: number; 17 | events: any[]; 18 | options: TimelineOptions; 19 | sent: number; 20 | uniqueID: number; 21 | 22 | constructor(key : string, session : number, options : TimelineOptions) { 23 | this.key = key; 24 | this.session = session; 25 | this.events = []; 26 | this.options = options || {}; 27 | this.sent = 0; 28 | this.uniqueID = 0; 29 | } 30 | 31 | log(level, event) { 32 | if (level <= this.options.level) { 33 | this.events.push( 34 | Collections.extend({}, event, { timestamp: Util.now() }) 35 | ); 36 | if (this.options.limit && this.events.length > this.options.limit) { 37 | this.events.shift(); 38 | } 39 | } 40 | } 41 | 42 | error(event) { 43 | this.log(Level.ERROR, event); 44 | } 45 | 46 | info(event) { 47 | this.log(Level.INFO, event); 48 | } 49 | 50 | debug(event) { 51 | this.log(Level.DEBUG, event); 52 | } 53 | 54 | isEmpty() { 55 | return this.events.length === 0; 56 | } 57 | 58 | send(sendfn, callback) { 59 | var data = Collections.extend({ 60 | session: this.session, 61 | bundle: this.sent + 1, 62 | key: this.key, 63 | lib: "js", 64 | version: this.options.version, 65 | cluster: this.options.cluster, 66 | features: this.options.features, 67 | timeline: this.events 68 | }, this.options.params); 69 | 70 | this.events = []; 71 | sendfn(data, (error, result)=> { 72 | if (!error) { 73 | this.sent++; 74 | } 75 | if (callback) { 76 | callback(error, result); 77 | } 78 | }); 79 | 80 | return true; 81 | } 82 | 83 | generateUniqueID() : number { 84 | this.uniqueID++; 85 | return this.uniqueID; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/core/timeline/timeline_sender.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from "../utils/collections"; 2 | import Util from "../util"; 3 | import base64encode from "../base64"; 4 | import Timeline from "./timeline"; 5 | import Runtime from 'runtime'; 6 | 7 | export interface TimelineSenderOptions { 8 | host?: string; 9 | port?: number; 10 | path?: string; 11 | } 12 | 13 | export default class TimelineSender { 14 | timeline: Timeline; 15 | options : TimelineSenderOptions; 16 | host: string; 17 | 18 | constructor(timeline: Timeline, options : TimelineSenderOptions) { 19 | this.timeline = timeline; 20 | this.options = options || {}; 21 | } 22 | 23 | send(encrypted : boolean, callback?: Function) { 24 | if (this.timeline.isEmpty()) { 25 | return; 26 | } 27 | 28 | this.timeline.send(Runtime.TimelineTransport.getAgent(this, encrypted), callback); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/timeline/timeline_transport.ts: -------------------------------------------------------------------------------- 1 | import TimelineSender from "core/timeline/timeline_sender"; 2 | 3 | interface TimelineTransport { 4 | name: string; 5 | getAgent:(sender: TimelineSender, encrypted : boolean)=>(data : any, callback : Function) => void; 6 | } 7 | 8 | export default TimelineTransport; 9 | -------------------------------------------------------------------------------- /src/core/transports/assistant_to_the_transport_manager.ts: -------------------------------------------------------------------------------- 1 | import Util from '../util'; 2 | import * as Collections from '../utils/collections'; 3 | import TransportManager from './transport_manager'; 4 | import TransportConnection from './transport_connection'; 5 | import Transport from './transport'; 6 | import PingDelayOptions from './ping_delay_options'; 7 | 8 | /** Creates transport connections monitored by a transport manager. 9 | * 10 | * When a transport is closed, it might mean the environment does not support 11 | * it. It's possible that messages get stuck in an intermediate buffer or 12 | * proxies terminate inactive connections. To combat these problems, 13 | * assistants monitor the connection lifetime, report unclean exits and 14 | * adjust ping timeouts to keep the connection active. The decision to disable 15 | * a transport is the manager's responsibility. 16 | * 17 | * @param {TransportManager} manager 18 | * @param {TransportConnection} transport 19 | * @param {Object} options 20 | */ 21 | export default class AssistantToTheTransportManager { 22 | manager : TransportManager; 23 | transport : Transport; 24 | minPingDelay: number; 25 | maxPingDelay: number; 26 | pingDelay: number; 27 | 28 | constructor(manager : TransportManager, transport : Transport, options : PingDelayOptions) { 29 | this.manager = manager; 30 | this.transport = transport; 31 | this.minPingDelay = options.minPingDelay; 32 | this.maxPingDelay = options.maxPingDelay; 33 | this.pingDelay = undefined; 34 | } 35 | 36 | /** Creates a transport connection. 37 | * 38 | * This function has the same API as Transport#createConnection. 39 | * 40 | * @param {String} name 41 | * @param {Number} priority 42 | * @param {String} key the application key 43 | * @param {Object} options 44 | * @returns {TransportConnection} 45 | */ 46 | createConnection(name : string, priority : number, key : string, options : Object) : TransportConnection { 47 | options = Collections.extend({}, options, { 48 | activityTimeout: this.pingDelay 49 | }); 50 | var connection = this.transport.createConnection( 51 | name, priority, key, options 52 | ); 53 | 54 | var openTimestamp = null; 55 | 56 | var onOpen = function() { 57 | connection.unbind("open", onOpen); 58 | connection.bind("closed", onClosed); 59 | openTimestamp = Util.now(); 60 | }; 61 | var onClosed = (closeEvent)=> { 62 | connection.unbind("closed", onClosed); 63 | 64 | if (closeEvent.code === 1002 || closeEvent.code === 1003) { 65 | // we don't want to use transports not obeying the protocol 66 | this.manager.reportDeath(); 67 | } else if (!closeEvent.wasClean && openTimestamp) { 68 | // report deaths only for short-living transport 69 | var lifespan = Util.now() - openTimestamp; 70 | if (lifespan < 2 * this.maxPingDelay) { 71 | this.manager.reportDeath(); 72 | this.pingDelay = Math.max(lifespan / 2, this.minPingDelay); 73 | } 74 | } 75 | }; 76 | 77 | connection.bind("open", onOpen); 78 | return connection; 79 | } 80 | 81 | /** Returns whether the transport is supported in the environment. 82 | * 83 | * This function has the same API as Transport#isSupported. Might return false 84 | * when the manager decides to kill the transport. 85 | * 86 | * @param {Object} environment the environment details (encryption, settings) 87 | * @returns {Boolean} true when the transport is supported 88 | */ 89 | isSupported(environment : string) : boolean { 90 | return this.manager.isAlive() && this.transport.isSupported(environment); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/core/transports/ping_delay_options.ts: -------------------------------------------------------------------------------- 1 | interface PingDelayOptions { 2 | minPingDelay?: number; 3 | maxPingDelay?: number; 4 | pingDelay?: number; 5 | } 6 | 7 | export default PingDelayOptions; 8 | -------------------------------------------------------------------------------- /src/core/transports/transport.ts: -------------------------------------------------------------------------------- 1 | import Factory from "../utils/factory"; 2 | import TransportHooks from "./transport_hooks"; 3 | import TransportConnection from "./transport_connection"; 4 | 5 | /** Provides interface for transport connection instantiation. 6 | * 7 | * Takes transport-specific hooks as the only argument, which allow checking 8 | * for transport support and creating its connections. 9 | * 10 | * Supported hooks: * - file - the name of the file to be fetched during initialization 11 | * - urls - URL scheme to be used by transport 12 | * - handlesActivityCheck - true when the transport handles activity checks 13 | * - supportsPing - true when the transport has a ping/activity API 14 | * - isSupported - tells whether the transport is supported in the environment 15 | * - getSocket - creates a WebSocket-compatible transport socket 16 | * 17 | * See transports.js for specific implementations. 18 | * 19 | * @param {Object} hooks object containing all needed transport hooks 20 | */ 21 | export default class Transport { 22 | hooks: TransportHooks; 23 | 24 | constructor(hooks : TransportHooks) { 25 | this.hooks = hooks; 26 | } 27 | 28 | /** Returns whether the transport is supported in the environment. 29 | * 30 | * @param {Object} envronment te environment details (encryption, settings) 31 | * @returns {Boolean} true when the transport is supported 32 | */ 33 | isSupported(environment : any) : boolean { 34 | return this.hooks.isSupported(environment); 35 | } 36 | 37 | 38 | /** Creates a transport connection. 39 | * 40 | * @param {String} name 41 | * @param {Number} priority 42 | * @param {String} key the application key 43 | * @param {Object} options 44 | * @returns {TransportConnection} 45 | */ 46 | createConnection(name : string, priority : number, key : string, options : any) : TransportConnection { 47 | return new TransportConnection( 48 | this.hooks, name, priority, key, options 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/transports/transport_connection_options.ts: -------------------------------------------------------------------------------- 1 | import Timeline from '../timeline/timeline'; 2 | 3 | interface TransportConnectionOptions { 4 | timeline: Timeline, 5 | activityTimeout: number; 6 | } 7 | 8 | export default TransportConnectionOptions; 9 | -------------------------------------------------------------------------------- /src/core/transports/transport_hooks.ts: -------------------------------------------------------------------------------- 1 | import Factory from "../utils/factory"; 2 | import URLScheme from "./url_scheme"; 3 | import Socket from "../socket"; 4 | 5 | interface TransportHooks { 6 | file?: string; 7 | urls: URLScheme; 8 | handlesActivityChecks: boolean; 9 | supportsPing: boolean; 10 | isInitialized() : boolean; 11 | isSupported(environment?: any): boolean; 12 | getSocket(url : string, options?: any): Socket; 13 | beforeOpen?: Function; 14 | } 15 | 16 | export default TransportHooks; 17 | -------------------------------------------------------------------------------- /src/core/transports/transport_manager.ts: -------------------------------------------------------------------------------- 1 | import AssistantToTheTransportManager from './assistant_to_the_transport_manager'; 2 | import Transport from "./transport"; 3 | import PingDelayOptions from "./ping_delay_options"; 4 | import Factory from "../utils/factory"; 5 | 6 | export interface TransportManagerOptions extends PingDelayOptions { 7 | lives?: number; 8 | } 9 | 10 | /** Keeps track of the number of lives left for a transport. 11 | * 12 | * In the beginning of a session, transports may be assigned a number of 13 | * lives. When an AssistantToTheTransportManager instance reports a transport 14 | * connection closed uncleanly, the transport loses a life. When the number 15 | * of lives drops to zero, the transport gets disabled by its manager. 16 | * 17 | * @param {Object} options 18 | */ 19 | export default class TransportManager { 20 | options: TransportManagerOptions; 21 | livesLeft: number; 22 | 23 | constructor(options : TransportManagerOptions){ 24 | this.options = options || {}; 25 | this.livesLeft = this.options.lives || Infinity; 26 | } 27 | 28 | /** Creates a assistant for the transport. 29 | * 30 | * @param {Transport} transport 31 | * @returns {AssistantToTheTransportManager} 32 | */ 33 | getAssistant(transport : Transport) : AssistantToTheTransportManager { 34 | return Factory.createAssistantToTheTransportManager(this, transport, { 35 | minPingDelay: this.options.minPingDelay, 36 | maxPingDelay: this.options.maxPingDelay 37 | }); 38 | } 39 | 40 | /** Returns whether the transport has any lives left. 41 | * 42 | * @returns {Boolean} 43 | */ 44 | isAlive() : boolean { 45 | return this.livesLeft > 0; 46 | } 47 | 48 | /** Takes one life from the transport. */ 49 | reportDeath() { 50 | this.livesLeft -= 1; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/core/transports/transports_table.ts: -------------------------------------------------------------------------------- 1 | import Transport from './transport'; 2 | 3 | interface TransportsTable { 4 | ws: Transport; 5 | xhr_streaming: Transport; 6 | xdr_streaming?: Transport; 7 | xhr_polling: Transport; 8 | xdr_polling?: Transport; 9 | sockjs?: Transport; 10 | } 11 | 12 | export default TransportsTable; 13 | -------------------------------------------------------------------------------- /src/core/transports/url_scheme.ts: -------------------------------------------------------------------------------- 1 | export interface URLSchemeParams { 2 | encrypted: boolean; 3 | hostEncrypted: string; 4 | hostUnencrypted: string; 5 | httpPath: string; 6 | } 7 | 8 | interface URLScheme { 9 | getInitial(key : string, params : any) : string; 10 | getPath?(key : string, options : any) : string; 11 | } 12 | 13 | export default URLScheme; 14 | -------------------------------------------------------------------------------- /src/core/transports/url_schemes.ts: -------------------------------------------------------------------------------- 1 | import Defaults from "../defaults"; 2 | import {default as URLScheme, URLSchemeParams} from "./url_scheme"; 3 | 4 | function getGenericURL(baseScheme : string, params : URLSchemeParams, path : string): string { 5 | var scheme = baseScheme + (params.encrypted ? "s" : ""); 6 | var host = params.encrypted ? params.hostEncrypted : params.hostUnencrypted; 7 | return scheme + "://" + host + path; 8 | } 9 | 10 | function getGenericPath(key : string, queryString?:string) : string { 11 | var path = "/app/" + key; 12 | var query = 13 | "?protocol=" + Defaults.PROTOCOL + 14 | "&client=js" + 15 | "&version=" + Defaults.VERSION + 16 | (queryString ? ("&" + queryString) : ""); 17 | return path + query; 18 | } 19 | 20 | export var ws : URLScheme = { 21 | getInitial: function(key : string , params : URLSchemeParams) : string { 22 | return getGenericURL("ws", params, getGenericPath(key, "flash=false")); 23 | } 24 | }; 25 | 26 | export var http : URLScheme = { 27 | getInitial: function(key : string, params : URLSchemeParams) : string { 28 | var path = (params.httpPath || "/pusher") + getGenericPath(key); 29 | return getGenericURL("http", params, path); 30 | } 31 | }; 32 | 33 | export var sockjs : URLScheme = { 34 | getInitial: function(key : string, params : URLSchemeParams) : string { 35 | return getGenericURL("http", params, params.httpPath || "/pusher"); 36 | }, 37 | getPath: function(key : string, params : URLSchemeParams) : string { 38 | return getGenericPath(key); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/core/util.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from "./utils/collections"; 2 | import TimedCallback from "./utils/timers/timed_callback"; 3 | import {OneOffTimer, PeriodicTimer} from "./utils/timers"; 4 | 5 | var Util = { 6 | 7 | getGlobal() : any { 8 | return Function("return this")(); 9 | }, 10 | 11 | now() : number { 12 | if (Date.now) { 13 | return Date.now(); 14 | } else { 15 | return new Date().valueOf(); 16 | } 17 | }, 18 | 19 | defer(callback : TimedCallback) : OneOffTimer { 20 | return new OneOffTimer(0, callback); 21 | }, 22 | 23 | /** Builds a function that will proxy a method call to its first argument. 24 | * 25 | * Allows partial application of arguments, so additional arguments are 26 | * prepended to the argument list. 27 | * 28 | * @param {String} name method name 29 | * @return {Function} proxy function 30 | */ 31 | method(name : string, ...args : any[]) : Function { 32 | var boundArguments = Array.prototype.slice.call(arguments, 1); 33 | return function(object) { 34 | return object[name].apply(object, boundArguments.concat(arguments)); 35 | }; 36 | } 37 | } 38 | 39 | export default Util; 40 | -------------------------------------------------------------------------------- /src/core/utils/factory.ts: -------------------------------------------------------------------------------- 1 | import AssistantToTheTransportManager from "../transports/assistant_to_the_transport_manager"; 2 | import PingDelayOptions from '../transports/ping_delay_options'; 3 | import Transport from "../transports/transport"; 4 | import TransportManager from "../transports/transport_manager"; 5 | import Handshake from "../connection/handshake"; 6 | import TransportConnection from "../transports/transport_connection"; 7 | import SocketHooks from "../http/socket_hooks"; 8 | import HTTPSocket from "../http/http_socket"; 9 | import Authorizer from "../auth/pusher_authorizer"; 10 | import {AuthorizerOptions} from '../auth/options'; 11 | import Timeline from "../timeline/timeline"; 12 | import {default as TimelineSender, TimelineSenderOptions} from "../timeline/timeline_sender"; 13 | import PresenceChannel from "../channels/presence_channel"; 14 | import PrivateChannel from "../channels/private_channel"; 15 | import Channel from "../channels/channel"; 16 | import ConnectionManager from "../connection/connection_manager"; 17 | import ConnectionManagerOptions from '../connection/connection_manager_options'; 18 | import Ajax from "../http/ajax"; 19 | import Channels from "../channels/channels"; 20 | import Pusher from '../pusher'; 21 | 22 | var Factory = { 23 | 24 | createChannels() : Channels { 25 | return new Channels(); 26 | }, 27 | 28 | createConnectionManager(key : string, options : ConnectionManagerOptions) : ConnectionManager { 29 | return new ConnectionManager(key, options); 30 | }, 31 | 32 | createChannel(name: string, pusher: Pusher) : Channel { 33 | return new Channel(name, pusher); 34 | }, 35 | 36 | createPrivateChannel(name: string, pusher: Pusher) : PrivateChannel { 37 | return new PrivateChannel(name, pusher); 38 | }, 39 | 40 | createPresenceChannel(name: string, pusher: Pusher) : PresenceChannel { 41 | return new PresenceChannel(name, pusher); 42 | }, 43 | 44 | createTimelineSender(timeline : Timeline, options : TimelineSenderOptions) { 45 | return new TimelineSender(timeline, options); 46 | }, 47 | 48 | createAuthorizer(channel : Channel, options : AuthorizerOptions) : Authorizer { 49 | return new Authorizer(channel, options); 50 | }, 51 | 52 | createHandshake(transport : TransportConnection, callback : (HandshakePayload)=>void) : Handshake { 53 | return new Handshake(transport, callback); 54 | }, 55 | 56 | createAssistantToTheTransportManager(manager : TransportManager, transport : Transport, options : PingDelayOptions) : AssistantToTheTransportManager { 57 | return new AssistantToTheTransportManager(manager, transport, options); 58 | } 59 | 60 | } 61 | 62 | export default Factory; 63 | -------------------------------------------------------------------------------- /src/core/utils/timers/abstract_timer.ts: -------------------------------------------------------------------------------- 1 | import TimedCallback from "./timed_callback"; 2 | import {Delay, Scheduler, Canceller} from "./scheduling"; 3 | 4 | abstract class Timer { 5 | protected clear: Canceller; 6 | protected timer: number | void; 7 | 8 | constructor(set: Scheduler, clear: Canceller, delay: Delay, callback: TimedCallback) { 9 | this.clear = clear; 10 | this.timer = set(() => { 11 | if (this.timer) { 12 | this.timer = callback(this.timer); 13 | } 14 | }, delay); 15 | } 16 | 17 | /** Returns whether the timer is still running. 18 | * 19 | * @return {Boolean} 20 | */ 21 | isRunning(): boolean { 22 | return this.timer !== null; 23 | } 24 | 25 | /** Aborts a timer when it's running. */ 26 | ensureAborted() { 27 | if (this.timer) { 28 | this.clear(this.timer); 29 | this.timer = null; 30 | } 31 | } 32 | } 33 | 34 | export default Timer; 35 | -------------------------------------------------------------------------------- /src/core/utils/timers/index.ts: -------------------------------------------------------------------------------- 1 | import Timer from "./abstract_timer"; 2 | import TimedCallback from "./timed_callback"; 3 | import {Delay} from "./scheduling"; 4 | 5 | var global = Function("return this")(); 6 | 7 | // We need to bind clear functions this way to avoid exceptions on IE8 8 | function clearTimeout(timer) { 9 | global.clearTimeout(timer); 10 | } 11 | function clearInterval(timer) { 12 | global.clearInterval(timer); 13 | } 14 | 15 | /** Cross-browser compatible one-off timer abstraction. 16 | * 17 | * @param {Number} delay 18 | * @param {Function} callback 19 | */ 20 | export class OneOffTimer extends Timer { 21 | constructor(delay : Delay, callback : TimedCallback) { 22 | super(setTimeout, clearTimeout, delay, function(timer){ 23 | callback(); 24 | return null; 25 | }) 26 | } 27 | } 28 | 29 | /** Cross-browser compatible periodic timer abstraction. 30 | * 31 | * @param {Number} delay 32 | * @param {Function} callback 33 | */ 34 | export class PeriodicTimer extends Timer { 35 | constructor(delay : Delay, callback : TimedCallback) { 36 | super(setInterval, clearInterval, delay, function(timer){ 37 | callback(); 38 | return timer; 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/core/utils/timers/scheduling.ts: -------------------------------------------------------------------------------- 1 | interface Scheduler { 2 | (TimedCallback, number): number 3 | } 4 | 5 | interface Canceller { 6 | (number): void 7 | } 8 | 9 | type Delay = number; 10 | 11 | export {Scheduler, Canceller, Delay}; 12 | -------------------------------------------------------------------------------- /src/core/utils/timers/timed_callback.ts: -------------------------------------------------------------------------------- 1 | interface TimedCallback { 2 | (number?): number | void; 3 | } 4 | 5 | export default TimedCallback; 6 | -------------------------------------------------------------------------------- /src/d.ts/faye-websocket/faye-websocket.d.ts: -------------------------------------------------------------------------------- 1 | declare module "faye-websocket" { 2 | interface MessageEvent { 3 | data: any; 4 | } 5 | 6 | interface CloseEvent { 7 | code: number; 8 | reason: string; 9 | wasClean: boolean; 10 | } 11 | 12 | export class Client { 13 | public onopen: () => void; 14 | public onmessage: (event: MessageEvent) => void; 15 | public onclose: (event: CloseEvent) => void; 16 | 17 | constructor(url: string); 18 | send(data: string): void; 19 | close(code?: number, reason?: string): void; 20 | } 21 | } -------------------------------------------------------------------------------- /src/d.ts/fetch/fetch.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for isomorphic-fetch 2 | // Project: https://github.com/matthew-andrews/isomorphic-fetch 3 | // Definitions by: Todd Lucas 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | declare enum RequestContext { 7 | "audio", "beacon", "cspreport", "download", "embed", "eventsource", 8 | "favicon", "fetch", "font", "form", "frame", "hyperlink", "iframe", 9 | "image", "imageset", "import", "internal", "location", "manifest", 10 | "object", "ping", "plugin", "prefetch", "script", "serviceworker", 11 | "sharedworker", "subresource", "style", "track", "video", "worker", 12 | "xmlhttprequest", "xslt" 13 | } 14 | declare enum RequestMode { "same-origin", "no-cors", "cors" } 15 | declare enum RequestCredentials { "omit", "same-origin", "include" } 16 | declare enum RequestCache { 17 | "default", "no-store", "reload", "no-cache", "force-cache", 18 | "only-if-cached" 19 | } 20 | declare enum ResponseType { "basic", "cors", "default", "error", "opaque" } 21 | 22 | declare type HeaderInit = Headers | Array; 23 | declare type BodyInit = Blob | FormData | string; 24 | declare type RequestInfo = Request | string; 25 | 26 | interface RequestInit { 27 | method?: string; 28 | headers?: HeaderInit | { [index: string]: string }; 29 | body?: BodyInit; 30 | mode?: string | RequestMode; 31 | credentials?: string | RequestCredentials; 32 | cache?: string | RequestCache; 33 | } 34 | 35 | interface IHeaders { 36 | get(name: string): string; 37 | getAll(name: string): Array; 38 | has(name: string): boolean; 39 | } 40 | 41 | declare class Headers implements IHeaders { 42 | append(name: string, value: string): void; 43 | delete(name: string):void; 44 | get(name: string): string; 45 | getAll(name: string): Array; 46 | has(name: string): boolean; 47 | set(name: string, value: string): void; 48 | } 49 | 50 | interface IBody { 51 | bodyUsed: boolean; 52 | arrayBuffer(): Promise; 53 | blob(): Promise; 54 | formData(): Promise; 55 | json(): Promise; 56 | json(): Promise; 57 | text(): Promise; 58 | } 59 | 60 | declare class Body implements IBody { 61 | bodyUsed: boolean; 62 | arrayBuffer(): Promise; 63 | blob(): Promise; 64 | formData(): Promise; 65 | json(): Promise; 66 | json(): Promise; 67 | text(): Promise; 68 | } 69 | 70 | interface IRequest extends IBody { 71 | method: string; 72 | url: string; 73 | headers: Headers; 74 | context: string | RequestContext; 75 | referrer: string; 76 | mode: string | RequestMode; 77 | credentials: string | RequestCredentials; 78 | cache: string | RequestCache; 79 | } 80 | 81 | declare class Request extends Body implements IRequest { 82 | constructor(input: string | Request, init?: RequestInit); 83 | method: string; 84 | url: string; 85 | headers: Headers; 86 | context: string | RequestContext; 87 | referrer: string; 88 | mode: string | RequestMode; 89 | credentials: string | RequestCredentials; 90 | cache: string | RequestCache; 91 | } 92 | 93 | interface IResponse extends IBody { 94 | url: string; 95 | status: number; 96 | statusText: string; 97 | ok: boolean; 98 | headers: IHeaders; 99 | type: string | ResponseType; 100 | size: number; 101 | timeout: number; 102 | redirect(url: string, status: number): IResponse; 103 | error(): IResponse; 104 | clone(): IResponse; 105 | } 106 | 107 | interface IFetchStatic { 108 | Promise: any; 109 | Headers: IHeaders 110 | Request: IRequest; 111 | Response: IResponse; 112 | (url: string | IRequest, init?: RequestInit): Promise; 113 | } 114 | 115 | declare module "isomorphic-fetch" { 116 | export default IFetchStatic; 117 | } 118 | 119 | declare var fetch: IFetchStatic; 120 | -------------------------------------------------------------------------------- /src/d.ts/fetch/promise.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for es6-promise 2 | // Project: https://github.com/jakearchibald/ES6-Promise 3 | // Definitions by: François de Campredon , vvakame 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | interface Thenable { 7 | then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Thenable; 8 | then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Thenable; 9 | catch(onRejected?: (error: any) => U | Thenable): Thenable; 10 | } 11 | 12 | declare class Promise implements Thenable { 13 | /** 14 | * If you call resolve in the body of the callback passed to the constructor, 15 | * your promise is fulfilled with result object passed to resolve. 16 | * If you call reject your promise is rejected with the object passed to reject. 17 | * For consistency and debugging (eg stack traces), obj should be an instanceof Error. 18 | * Any errors thrown in the constructor callback will be implicitly passed to reject(). 19 | */ 20 | constructor(callback: (resolve : (value?: T | Thenable) => void, reject: (error?: any) => void) => void); 21 | 22 | /** 23 | * onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. 24 | * Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. 25 | * Both callbacks have a single parameter , the fulfillment value or rejection reason. 26 | * "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. 27 | * If an error is thrown in the callback, the returned promise rejects with that error. 28 | * 29 | * @param onFulfilled called when/if "promise" resolves 30 | * @param onRejected called when/if "promise" rejects 31 | */ 32 | then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => U | Thenable): Promise; 33 | then(onFulfilled?: (value: T) => U | Thenable, onRejected?: (error: any) => void): Promise; 34 | 35 | /** 36 | * Sugar for promise.then(undefined, onRejected) 37 | * 38 | * @param onRejected called when/if "promise" rejects 39 | */ 40 | catch(onRejected?: (error: any) => U | Thenable): Promise; 41 | } 42 | 43 | declare namespace Promise { 44 | /** 45 | * Make a new promise from the thenable. 46 | * A thenable is promise-like in as far as it has a "then" method. 47 | */ 48 | function resolve(value?: T | Thenable): Promise; 49 | 50 | /** 51 | * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error 52 | */ 53 | function reject(error: any): Promise; 54 | function reject(error: T): Promise; 55 | 56 | /** 57 | * Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. 58 | * the array passed to all can be a mixture of promise-like objects and other objects. 59 | * The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value. 60 | */ 61 | function all(promises: (T | Thenable)[]): Promise; 62 | 63 | /** 64 | * Make a Promise that fulfills when any item fulfills, and rejects if any item rejects. 65 | */ 66 | function race(promises: (T | Thenable)[]): Promise; 67 | } 68 | 69 | declare module 'es6-promise' { 70 | var foo: typeof Promise; // Temp variable to reference Promise in local context 71 | namespace rsvp { 72 | export var Promise: typeof foo; 73 | } 74 | export = rsvp; 75 | } 76 | -------------------------------------------------------------------------------- /src/d.ts/module/module.d.ts: -------------------------------------------------------------------------------- 1 | declare var module: { 2 | exports : any; 3 | } 4 | 5 | declare var require: { 6 | (path: string): T; 7 | (paths: string[], callback: (...modules: any[]) => void): void; 8 | ensure: (paths: string[], callback: (require: (path: string) => T) => void) => void; 9 | }; 10 | -------------------------------------------------------------------------------- /src/d.ts/react-native/react-native.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-native" { 2 | export var NetInfo: any; 3 | } 4 | -------------------------------------------------------------------------------- /src/d.ts/window/events.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | attachEvent: any; 3 | detachEvent: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/d.ts/window/sockjs.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | SockJS: any; 3 | } 4 | -------------------------------------------------------------------------------- /src/d.ts/window/websocket.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | WebSocket: any; 3 | MozWebSocket: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/d.ts/window/xmlhttprequest.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | XMLHttpRequest: any; 3 | } 4 | -------------------------------------------------------------------------------- /src/d.ts/xmlhttprequest/xmlhttprequest.d.ts: -------------------------------------------------------------------------------- 1 | declare module "xmlhttprequest" { 2 | export class XMLHttpRequest { 3 | open(method : string, url : string, async : boolean); 4 | send(payload : any) : Function; 5 | setRequestHeader(key : string, value : string) : void; 6 | onreadystatechange : Function; 7 | withCredentials: any; 8 | 9 | ontimeout: Function; 10 | onerror: Function; 11 | onprogress: Function; 12 | onload: Function; 13 | abort: Function; 14 | 15 | responseText: string; 16 | status: number; 17 | readyState: number; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/runtimes/interface.ts: -------------------------------------------------------------------------------- 1 | import {AuthTransports} from 'core/auth/auth_transports'; 2 | import TimelineSender from 'core/timeline/timeline_sender'; 3 | import TimelineTransport from 'core/timeline/timeline_transport'; 4 | import Ajax from 'core/http/ajax'; 5 | import Reachability from 'core/reachability'; 6 | import TransportsTable from 'core/transports/transports_table'; 7 | import Socket from 'core/socket'; 8 | import HTTPFactory from 'core/http/http_factory'; 9 | import HTTPRequest from 'core/http/http_request'; 10 | import Pusher from 'core/pusher'; 11 | 12 | /* 13 | This interface is implemented in web/runtime, node/runtime, react-native/runtime 14 | and worker/runtime. Its job is to be the only point of contact to platform-specific 15 | code for the core library. When the core library imports "runtime", Webpack will 16 | look for src/runtimes//runtime.ts. This is how PusherJS keeps 17 | core and platform-specific code separate. 18 | */ 19 | interface Runtime { 20 | setup(PusherClass : { 21 | new (key: string, options: any) : Pusher; 22 | ready() : void; 23 | }) : void; 24 | getProtocol() : string; 25 | getGlobal() : any; 26 | getAuthorizers() : AuthTransports; 27 | getLocalStorage() : any; 28 | TimelineTransport: TimelineTransport; 29 | createXHR() : Ajax; 30 | createWebSocket(url : string) : Socket; 31 | getNetwork() : Reachability; 32 | getDefaultStrategy(config : any) : any; 33 | Transports: TransportsTable; 34 | getWebSocketAPI() : new(url: string) => Socket; 35 | getXHRAPI() : new() => Ajax; 36 | addUnloadListener(listener : Function) : void; 37 | removeUnloadListener(listener : Function) : void; 38 | transportConnectionInitializer: Function; 39 | HTTPFactory: HTTPFactory; 40 | isXHRSupported() : boolean; 41 | createSocketRequest(method : string, url : string) : HTTPRequest; 42 | } 43 | 44 | export default Runtime; 45 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/auth/xhr_auth.ts: -------------------------------------------------------------------------------- 1 | import Logger from 'core/logger'; 2 | import TimelineSender from 'core/timeline/timeline_sender' 3 | import * as Collections from 'core/utils/collections'; 4 | import Util from 'core/util'; 5 | import Runtime from 'runtime'; 6 | import {AuthTransport} from 'core/auth/auth_transports'; 7 | import AbstractRuntime from 'runtimes/interface'; 8 | 9 | var ajax : AuthTransport = function(context : AbstractRuntime, socketId, callback){ 10 | var self = this, xhr; 11 | 12 | xhr = Runtime.createXHR(); 13 | xhr.open("POST", self.options.authEndpoint, true); 14 | 15 | // add request headers 16 | xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 17 | for(var headerName in this.authOptions.headers) { 18 | xhr.setRequestHeader(headerName, this.authOptions.headers[headerName]); 19 | } 20 | 21 | xhr.onreadystatechange = function() { 22 | if (xhr.readyState === 4) { 23 | if (xhr.status === 200) { 24 | var data, parsed = false; 25 | 26 | try { 27 | data = JSON.parse(xhr.responseText); 28 | parsed = true; 29 | } catch (e) { 30 | callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText); 31 | } 32 | 33 | if (parsed) { // prevents double execution. 34 | callback(false, data); 35 | } 36 | } else { 37 | Logger.warn("Couldn't get auth info from your webapp", xhr.status); 38 | callback(true, xhr.status); 39 | } 40 | } 41 | }; 42 | 43 | xhr.send(this.composeQuery(socketId)); 44 | return xhr; 45 | } 46 | 47 | export default ajax; 48 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/default_strategy.ts: -------------------------------------------------------------------------------- 1 | var getDefaultStrategy = function(config) { 2 | var wsStrategy; 3 | if (config.encrypted) { 4 | wsStrategy = [ 5 | ":best_connected_ever", 6 | ":ws_loop", 7 | [":delayed", 2000, [":http_loop"]] 8 | ]; 9 | } else { 10 | wsStrategy = [ 11 | ":best_connected_ever", 12 | ":ws_loop", 13 | [":delayed", 2000, [":wss_loop"]], 14 | [":delayed", 5000, [":http_loop"]] 15 | ]; 16 | } 17 | 18 | return [ 19 | [":def", "ws_options", { 20 | hostUnencrypted: config.wsHost + ":" + config.wsPort, 21 | hostEncrypted: config.wsHost + ":" + config.wssPort 22 | }], 23 | [":def", "wss_options", [":extend", ":ws_options", { 24 | encrypted: true 25 | }]], 26 | [":def", "http_options", { 27 | hostUnencrypted: config.httpHost + ":" + config.httpPort, 28 | hostEncrypted: config.httpHost + ":" + config.httpsPort, 29 | httpPath: config.httpPath 30 | }], 31 | [":def", "timeouts", { 32 | loop: true, 33 | timeout: 15000, 34 | timeoutLimit: 60000 35 | }], 36 | 37 | [":def", "ws_manager", [":transport_manager", { 38 | lives: 2, 39 | minPingDelay: 10000, 40 | maxPingDelay: config.activity_timeout 41 | }]], 42 | [":def", "streaming_manager", [":transport_manager", { 43 | lives: 2, 44 | minPingDelay: 10000, 45 | maxPingDelay: config.activity_timeout 46 | }]], 47 | 48 | [":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"], 49 | [":def_transport", "wss", "ws", 3, ":wss_options", ":ws_manager"], 50 | [":def_transport", "xhr_streaming", "xhr_streaming", 1, ":http_options", ":streaming_manager"], 51 | [":def_transport", "xhr_polling", "xhr_polling", 1, ":http_options"], 52 | 53 | [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]], 54 | [":def", "wss_loop", [":sequential", ":timeouts", ":wss"]], 55 | 56 | [":def", "streaming_loop", [":sequential", ":timeouts", ":xhr_streaming"]], 57 | [":def", "polling_loop", [":sequential", ":timeouts", ":xhr_polling"]], 58 | 59 | [":def", "http_loop", [":if", [":is_supported", ":streaming_loop"], [ 60 | ":best_connected_ever", 61 | ":streaming_loop", 62 | [":delayed", 4000, [":polling_loop"]] 63 | ], [ 64 | ":polling_loop" 65 | ]]], 66 | 67 | [":def", "strategy", 68 | [":cached", 1800000, 69 | [":first_connected", 70 | [":if", [":is_supported", ":ws"], 71 | wsStrategy, 72 | ":http_loop" 73 | ] 74 | ] 75 | ] 76 | ] 77 | ]; 78 | }; 79 | 80 | export default getDefaultStrategy; 81 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/http/http.ts: -------------------------------------------------------------------------------- 1 | import HTTPRequest from "core/http/http_request"; 2 | import HTTPSocket from "core/http/http_socket"; 3 | import SocketHooks from "core/http/socket_hooks"; 4 | import RequestHooks from "core/http/request_hooks"; 5 | import streamingHooks from 'core/http/http_streaming_socket'; 6 | import pollingHooks from 'core/http/http_polling_socket'; 7 | import xhrHooks from './http_xhr_request'; 8 | import HTTPFactory from 'core/http/http_factory'; 9 | 10 | var HTTP : HTTPFactory = { 11 | 12 | createStreamingSocket(url : string) : HTTPSocket { 13 | return this.createSocket(streamingHooks, url); 14 | }, 15 | 16 | createPollingSocket(url : string) : HTTPSocket { 17 | return this.createSocket(pollingHooks, url); 18 | }, 19 | 20 | createSocket(hooks : SocketHooks, url : string) : HTTPSocket { 21 | return new HTTPSocket(hooks, url); 22 | }, 23 | 24 | createXHR(method : string, url : string) : HTTPRequest { 25 | return this.createRequest(xhrHooks, method, url); 26 | }, 27 | 28 | createRequest(hooks : RequestHooks, method : string, url : string) : HTTPRequest { 29 | return new HTTPRequest(hooks, method, url); 30 | } 31 | } 32 | 33 | export default HTTP; 34 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/http/http_xhr_request.ts: -------------------------------------------------------------------------------- 1 | import HTTPRequest from 'core/http/http_request'; 2 | import RequestHooks from "core/http/request_hooks"; 3 | import Ajax from "core/http/ajax"; 4 | import Runtime from "runtime"; 5 | 6 | var hooks : RequestHooks = { 7 | getRequest: function(socket : HTTPRequest) : Ajax { 8 | var Constructor = Runtime.getXHRAPI(); 9 | var xhr = new Constructor(); 10 | xhr.onreadystatechange = xhr.onprogress = function() { 11 | switch (xhr.readyState) { 12 | case 3: 13 | if (xhr.responseText && xhr.responseText.length > 0) { 14 | socket.onChunk(xhr.status, xhr.responseText); 15 | } 16 | break; 17 | case 4: 18 | // this happens only on errors, never after calling close 19 | if (xhr.responseText && xhr.responseText.length > 0) { 20 | socket.onChunk(xhr.status, xhr.responseText); 21 | } 22 | socket.emit("finished", xhr.status); 23 | socket.close(); 24 | break; 25 | } 26 | }; 27 | return xhr; 28 | }, 29 | abortRequest: function(xhr : Ajax) { 30 | xhr.onreadystatechange = null; 31 | xhr.abort(); 32 | } 33 | }; 34 | 35 | export default hooks; 36 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/runtime.ts: -------------------------------------------------------------------------------- 1 | import * as Collections from 'core/utils/collections'; 2 | import Transports from "isomorphic/transports/transports"; 3 | import TimelineSender from 'core/timeline/timeline_sender'; 4 | import Ajax from 'core/http/ajax'; 5 | import getDefaultStrategy from './default_strategy'; 6 | import TransportsTable from "core/transports/transports_table"; 7 | import transportConnectionInitializer from './transports/transport_connection_initializer'; 8 | import HTTPFactory from './http/http'; 9 | 10 | var Isomorphic : any = { 11 | getDefaultStrategy, 12 | Transports: Transports, 13 | transportConnectionInitializer, 14 | HTTPFactory, 15 | 16 | setup(PusherClass) : void { 17 | PusherClass.ready(); 18 | }, 19 | 20 | getGlobal() : any { 21 | return Function("return this")(); 22 | }, 23 | 24 | getLocalStorage() : any { 25 | return undefined; 26 | }, 27 | 28 | getClientFeatures() : any[] { 29 | return Collections.keys( 30 | Collections.filterObject( 31 | { "ws": Transports.ws }, 32 | function (t) { return t.isSupported({}); } 33 | ) 34 | ); 35 | }, 36 | 37 | getProtocol() : string { 38 | return "http:"; 39 | }, 40 | 41 | isXHRSupported() : boolean { 42 | return true; 43 | }, 44 | 45 | createSocketRequest(method : string, url : string) { 46 | if (this.isXHRSupported()) { 47 | return this.HTTPFactory.createXHR(method, url); 48 | } else { 49 | throw "Cross-origin HTTP requests are not supported"; 50 | } 51 | }, 52 | 53 | createXHR() : Ajax { 54 | var Constructor = this.getXHRAPI(); 55 | return new Constructor(); 56 | }, 57 | 58 | createWebSocket(url : string) : any { 59 | var Constructor = this.getWebSocketAPI(); 60 | return new Constructor(url); 61 | }, 62 | 63 | addUnloadListener(listener : any) {}, 64 | removeUnloadListener(listener : any) {} 65 | } 66 | 67 | export default Isomorphic; 68 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/timeline/xhr_timeline.ts: -------------------------------------------------------------------------------- 1 | import Logger from 'core/logger'; 2 | import TimelineSender from 'core/timeline/timeline_sender' 3 | import * as Collections from 'core/utils/collections'; 4 | import Util from 'core/util'; 5 | import Runtime from 'runtime'; 6 | import TimelineTransport from 'core/timeline/timeline_transport'; 7 | 8 | var getAgent = function(sender : TimelineSender, encrypted : boolean) { 9 | return function(data : any, callback : Function) { 10 | var scheme = "http" + (encrypted ? "s" : "") + "://"; 11 | var url = scheme + (sender.host || sender.options.host) + sender.options.path; 12 | var query = Collections.buildQueryString(data); 13 | 14 | url += ("/" + 2 + "?" + query); 15 | 16 | var xhr = Runtime.createXHR(); 17 | xhr.open("GET", url, true); 18 | 19 | xhr.onreadystatechange = function(){ 20 | if (xhr.readyState === 4) { 21 | let {status, responseText} = xhr; 22 | if (status !== 200) { 23 | Logger.debug(`TimelineSender Error: received ${status} from stats.pusher.com`); 24 | return; 25 | } 26 | 27 | try { 28 | var {host} = JSON.parse(responseText); 29 | } catch(e) { 30 | Logger.debug(`TimelineSenderError: invalid response ${responseText}`); 31 | } 32 | if (host) { 33 | sender.host = host; 34 | } 35 | } 36 | } 37 | 38 | xhr.send(); 39 | } 40 | }; 41 | 42 | var xhr = { 43 | name: 'xhr', 44 | getAgent 45 | } 46 | 47 | export default xhr; 48 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/transports/transport_connection_initializer.ts: -------------------------------------------------------------------------------- 1 | /** Initializes the transport. 2 | * 3 | * Fetches resources if needed and then transitions to initialized. 4 | */ 5 | export default function() { 6 | var self = this; 7 | 8 | self.timeline.info(self.buildTimelineMessage({ 9 | transport: self.name + (self.options.encrypted ? "s" : "") 10 | })); 11 | 12 | if (self.hooks.isInitialized()) { 13 | self.changeState("initialized"); 14 | } else { 15 | self.onClose(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/runtimes/isomorphic/transports/transports.ts: -------------------------------------------------------------------------------- 1 | import * as URLSchemes from "core/transports/url_schemes"; 2 | import URLScheme from 'core/transports/url_scheme' 3 | import Transport from "core/transports/transport"; 4 | import Util from "core/util"; 5 | import * as Collections from "core/utils/collections"; 6 | import TransportHooks from "core/transports/transport_hooks"; 7 | import TransportsTable from 'core/transports/transports_table'; 8 | import Runtime from 'runtime'; 9 | 10 | /** WebSocket transport. 11 | * 12 | * Uses native WebSocket implementation, including MozWebSocket supported by 13 | * earlier Firefox versions. 14 | */ 15 | var WSTransport = new Transport( { 16 | urls: URLSchemes.ws, 17 | handlesActivityChecks: false, 18 | supportsPing: false, 19 | 20 | isInitialized: function() { 21 | return Boolean(Runtime.getWebSocketAPI()); 22 | }, 23 | isSupported: function() : boolean { 24 | return Boolean(Runtime.getWebSocketAPI()); 25 | }, 26 | getSocket: function(url) { 27 | return Runtime.createWebSocket(url); 28 | } 29 | }); 30 | 31 | var httpConfiguration = { 32 | urls: URLSchemes.http, 33 | handlesActivityChecks: false, 34 | supportsPing: true, 35 | isInitialized: function() { 36 | return true; 37 | } 38 | }; 39 | 40 | export var streamingConfiguration = Collections.extend( 41 | { getSocket: function(url) { 42 | return Runtime.HTTPFactory.createStreamingSocket(url); 43 | } 44 | }, 45 | httpConfiguration 46 | ); 47 | export var pollingConfiguration = Collections.extend( 48 | { getSocket: function(url) { 49 | return Runtime.HTTPFactory.createPollingSocket(url); 50 | } 51 | }, 52 | httpConfiguration 53 | ); 54 | 55 | var xhrConfiguration = { 56 | isSupported: function() : boolean { 57 | return Runtime.isXHRSupported(); 58 | } 59 | }; 60 | 61 | /** HTTP streaming transport using CORS-enabled XMLHttpRequest. */ 62 | var XHRStreamingTransport = new Transport( 63 | Collections.extend({}, streamingConfiguration, xhrConfiguration) 64 | ); 65 | 66 | /** HTTP long-polling transport using CORS-enabled XMLHttpRequest. */ 67 | var XHRPollingTransport = new Transport( 68 | Collections.extend({}, pollingConfiguration, xhrConfiguration) 69 | ); 70 | 71 | var Transports : TransportsTable = { 72 | ws: WSTransport, 73 | xhr_streaming: XHRStreamingTransport, 74 | xhr_polling: XHRPollingTransport 75 | } 76 | 77 | export default Transports; 78 | -------------------------------------------------------------------------------- /src/runtimes/node/net_info.ts: -------------------------------------------------------------------------------- 1 | import {default as EventsDispatcher} from 'core/events/dispatcher' 2 | import Reachability from 'core/reachability'; 3 | 4 | export class NetInfo extends EventsDispatcher implements Reachability { 5 | 6 | isOnline() : boolean { 7 | return true; 8 | } 9 | 10 | } 11 | 12 | export var Network = new NetInfo(); 13 | -------------------------------------------------------------------------------- /src/runtimes/node/runtime.ts: -------------------------------------------------------------------------------- 1 | import Isomorphic from 'isomorphic/runtime'; 2 | import {Client as WebSocket} from "faye-websocket"; 3 | import {XMLHttpRequest} from "xmlhttprequest"; 4 | import Runtime from "../interface"; 5 | import {Network} from './net_info'; 6 | import xhrAuth from 'isomorphic/auth/xhr_auth'; 7 | import {AuthTransports} from 'core/auth/auth_transports'; 8 | import xhrTimeline from 'isomorphic/timeline/xhr_timeline'; 9 | 10 | // Very verbose but until unavoidable until 11 | // TypeScript 2.1, when spread attributes will be 12 | // supported 13 | const { 14 | getDefaultStrategy, 15 | Transports, 16 | setup, 17 | getProtocol, 18 | isXHRSupported, 19 | getGlobal, 20 | getLocalStorage, 21 | createXHR, 22 | createWebSocket, 23 | addUnloadListener, 24 | removeUnloadListener, 25 | transportConnectionInitializer, 26 | createSocketRequest, 27 | HTTPFactory 28 | } = Isomorphic; 29 | 30 | const NodeJS : Runtime = { 31 | getDefaultStrategy, 32 | Transports, 33 | setup, 34 | getProtocol, 35 | isXHRSupported, 36 | createSocketRequest, 37 | getGlobal, 38 | getLocalStorage, 39 | createXHR, 40 | createWebSocket, 41 | addUnloadListener, 42 | removeUnloadListener, 43 | transportConnectionInitializer, 44 | HTTPFactory, 45 | 46 | TimelineTransport: xhrTimeline, 47 | 48 | getAuthorizers() : AuthTransports { 49 | return {ajax: xhrAuth}; 50 | }, 51 | 52 | getWebSocketAPI() { 53 | return WebSocket; 54 | }, 55 | 56 | getXHRAPI() { 57 | return XMLHttpRequest; 58 | }, 59 | 60 | getNetwork() { 61 | return Network; 62 | } 63 | }; 64 | 65 | export default NodeJS; 66 | -------------------------------------------------------------------------------- /src/runtimes/react-native/net_info.ts: -------------------------------------------------------------------------------- 1 | import {NetInfo as NativeNetInfo} from 'react-native'; 2 | import EventsDispatcher from 'core/events/dispatcher'; 3 | import Util from 'core/util'; 4 | import Reachability from 'core/reachability'; 5 | 6 | function hasOnlineConnectionState(connectionState) : boolean{ 7 | return connectionState.toLowerCase() !== "none"; 8 | } 9 | 10 | export class NetInfo extends EventsDispatcher implements Reachability { 11 | online: boolean; 12 | 13 | constructor() { 14 | super(); 15 | this.online = true; 16 | 17 | NativeNetInfo.fetch().done((connectionState)=>{ 18 | this.online = hasOnlineConnectionState(connectionState); 19 | }); 20 | 21 | NativeNetInfo.addEventListener('change', (connectionState)=>{ 22 | var isNowOnline = hasOnlineConnectionState(connectionState); 23 | 24 | // React Native counts the switch from Wi-Fi to Cellular 25 | // as a state change. Return if current and previous states 26 | // are both online/offline 27 | if (this.online === isNowOnline) return; 28 | this.online = isNowOnline; 29 | if (this.online){ 30 | this.emit("online"); 31 | } else { 32 | this.emit("offline"); 33 | } 34 | }); 35 | } 36 | 37 | isOnline() : boolean { 38 | return this.online; 39 | } 40 | } 41 | 42 | export var Network = new NetInfo(); 43 | -------------------------------------------------------------------------------- /src/runtimes/react-native/runtime.ts: -------------------------------------------------------------------------------- 1 | import Isomorphic from 'isomorphic/runtime'; 2 | import Runtime from "../interface"; 3 | import {Network} from './net_info'; 4 | import xhrAuth from 'isomorphic/auth/xhr_auth'; 5 | import {AuthTransports} from 'core/auth/auth_transports'; 6 | import xhrTimeline from 'isomorphic/timeline/xhr_timeline'; 7 | 8 | // Very verbose but until unavoidable until 9 | // TypeScript 2.1, when spread attributes will be 10 | // supported 11 | const { 12 | getDefaultStrategy, 13 | Transports, 14 | setup, 15 | getProtocol, 16 | isXHRSupported, 17 | getGlobal, 18 | getLocalStorage, 19 | createXHR, 20 | createWebSocket, 21 | addUnloadListener, 22 | removeUnloadListener, 23 | transportConnectionInitializer, 24 | createSocketRequest, 25 | HTTPFactory 26 | } = Isomorphic; 27 | 28 | const ReactNative : Runtime = { 29 | getDefaultStrategy, 30 | Transports, 31 | setup, 32 | getProtocol, 33 | isXHRSupported, 34 | getGlobal, 35 | getLocalStorage, 36 | createXHR, 37 | createWebSocket, 38 | addUnloadListener, 39 | removeUnloadListener, 40 | transportConnectionInitializer, 41 | createSocketRequest, 42 | HTTPFactory, 43 | 44 | TimelineTransport: xhrTimeline, 45 | 46 | getAuthorizers() : AuthTransports { 47 | return {ajax: xhrAuth}; 48 | }, 49 | 50 | getWebSocketAPI() { 51 | return WebSocket; 52 | }, 53 | 54 | getXHRAPI() { 55 | return XMLHttpRequest; 56 | }, 57 | 58 | getNetwork() { 59 | return Network; 60 | } 61 | }; 62 | 63 | export default ReactNative; 64 | -------------------------------------------------------------------------------- /src/runtimes/web/auth/jsonp_auth.ts: -------------------------------------------------------------------------------- 1 | import Browser from '../browser'; 2 | import Logger from 'core/logger'; 3 | import JSONPRequest from '../dom/jsonp_request'; 4 | import {ScriptReceivers} from '../dom/script_receiver_factory'; 5 | import {AuthTransport} from 'core/auth/auth_transports'; 6 | 7 | var jsonp : AuthTransport = function(context : Browser, socketId, callback){ 8 | if(this.authOptions.headers !== undefined) { 9 | Logger.warn("Warn", "To send headers with the auth request, you must use AJAX, rather than JSONP."); 10 | } 11 | 12 | var callbackName = context.nextAuthCallbackID.toString(); 13 | context.nextAuthCallbackID++; 14 | 15 | var document = context.getDocument(); 16 | var script = document.createElement("script"); 17 | // Hacked wrapper. 18 | context.auth_callbacks[callbackName] = function(data) { 19 | callback(false, data); 20 | }; 21 | 22 | var callback_name = "Pusher.auth_callbacks['" + callbackName + "']"; 23 | script.src = this.options.authEndpoint + 24 | '?callback=' + 25 | encodeURIComponent(callback_name) + 26 | '&' + 27 | this.composeQuery(socketId); 28 | 29 | var head = document.getElementsByTagName("head")[0] || document.documentElement; 30 | head.insertBefore( script, head.firstChild ); 31 | }; 32 | 33 | export default jsonp; 34 | -------------------------------------------------------------------------------- /src/runtimes/web/browser.ts: -------------------------------------------------------------------------------- 1 | import AbstractRuntime from "runtimes/interface"; 2 | import {ScriptReceiverFactory} from './dom/script_receiver_factory'; 3 | import ScriptRequest from './dom/script_request'; 4 | import JSONPRequest from './dom/jsonp_request'; 5 | import Ajax from 'core/http/ajax'; 6 | 7 | interface Browser extends AbstractRuntime { 8 | // for jsonp auth 9 | nextAuthCallbackID: number; 10 | auth_callbacks: any; 11 | ScriptReceivers: ScriptReceiverFactory; 12 | DependenciesReceivers: ScriptReceiverFactory; 13 | onDocumentBody(callback : Function); 14 | getDocument() : any; 15 | 16 | createJSONPRequest(url : string, data : any) : JSONPRequest; 17 | createScriptRequest(src : string) : ScriptRequest; 18 | 19 | isXDRSupported(encrypted?:boolean) : boolean; 20 | createXMLHttpRequest() : Ajax; 21 | createMicrosoftXHR() : Ajax; 22 | } 23 | 24 | export default Browser; 25 | -------------------------------------------------------------------------------- /src/runtimes/web/default_strategy.ts: -------------------------------------------------------------------------------- 1 | var getDefaultStrategy = function(config : any) : any { 2 | var wsStrategy; 3 | if (config.encrypted) { 4 | wsStrategy = [ 5 | ":best_connected_ever", 6 | ":ws_loop", 7 | [":delayed", 2000, [":http_fallback_loop"]] 8 | ]; 9 | } else { 10 | wsStrategy = [ 11 | ":best_connected_ever", 12 | ":ws_loop", 13 | [":delayed", 2000, [":wss_loop"]], 14 | [":delayed", 5000, [":http_fallback_loop"]] 15 | ]; 16 | } 17 | 18 | return [ 19 | [":def", "ws_options", { 20 | hostUnencrypted: config.wsHost + ":" + config.wsPort, 21 | hostEncrypted: config.wsHost + ":" + config.wssPort 22 | }], 23 | [":def", "wss_options", [":extend", ":ws_options", { 24 | encrypted: true 25 | }]], 26 | [":def", "sockjs_options", { 27 | hostUnencrypted: config.httpHost + ":" + config.httpPort, 28 | hostEncrypted: config.httpHost + ":" + config.httpsPort, 29 | httpPath: config.httpPath 30 | }], 31 | [":def", "timeouts", { 32 | loop: true, 33 | timeout: 15000, 34 | timeoutLimit: 60000 35 | }], 36 | 37 | [":def", "ws_manager", [":transport_manager", { 38 | lives: 2, 39 | minPingDelay: 10000, 40 | maxPingDelay: config.activity_timeout 41 | }]], 42 | [":def", "streaming_manager", [":transport_manager", { 43 | lives: 2, 44 | minPingDelay: 10000, 45 | maxPingDelay: config.activity_timeout 46 | }]], 47 | 48 | [":def_transport", "ws", "ws", 3, ":ws_options", ":ws_manager"], 49 | [":def_transport", "wss", "ws", 3, ":wss_options", ":ws_manager"], 50 | [":def_transport", "sockjs", "sockjs", 1, ":sockjs_options"], 51 | [":def_transport", "xhr_streaming", "xhr_streaming", 1, ":sockjs_options", ":streaming_manager"], 52 | [":def_transport", "xdr_streaming", "xdr_streaming", 1, ":sockjs_options", ":streaming_manager"], 53 | [":def_transport", "xhr_polling", "xhr_polling", 1, ":sockjs_options"], 54 | [":def_transport", "xdr_polling", "xdr_polling", 1, ":sockjs_options"], 55 | 56 | [":def", "ws_loop", [":sequential", ":timeouts", ":ws"]], 57 | [":def", "wss_loop", [":sequential", ":timeouts", ":wss"]], 58 | [":def", "sockjs_loop", [":sequential", ":timeouts", ":sockjs"]], 59 | 60 | [":def", "streaming_loop", [":sequential", ":timeouts", 61 | [":if", [":is_supported", ":xhr_streaming"], 62 | ":xhr_streaming", 63 | ":xdr_streaming" 64 | ] 65 | ]], 66 | [":def", "polling_loop", [":sequential", ":timeouts", 67 | [":if", [":is_supported", ":xhr_polling"], 68 | ":xhr_polling", 69 | ":xdr_polling" 70 | ] 71 | ]], 72 | 73 | [":def", "http_loop", [":if", [":is_supported", ":streaming_loop"], [ 74 | ":best_connected_ever", 75 | ":streaming_loop", 76 | [":delayed", 4000, [":polling_loop"]] 77 | ], [ 78 | ":polling_loop" 79 | ]]], 80 | 81 | [":def", "http_fallback_loop", 82 | [":if", [":is_supported", ":http_loop"], [ 83 | ":http_loop" 84 | ], [ 85 | ":sockjs_loop" 86 | ]] 87 | ], 88 | 89 | [":def", "strategy", 90 | [":cached", 1800000, 91 | [":first_connected", 92 | [":if", [":is_supported", ":ws"], 93 | wsStrategy, 94 | ":http_fallback_loop" 95 | ] 96 | ] 97 | ] 98 | ] 99 | ]; 100 | }; 101 | 102 | export default getDefaultStrategy; 103 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/dependencies.ts: -------------------------------------------------------------------------------- 1 | import {ScriptReceiverFactory} from './script_receiver_factory'; 2 | import Defaults from 'core/defaults'; 3 | import DependencyLoader from './dependency_loader'; 4 | 5 | export var DependenciesReceivers = new ScriptReceiverFactory( 6 | "_pusher_dependencies", "Pusher.DependenciesReceivers" 7 | ); 8 | 9 | export var Dependencies = new DependencyLoader({ 10 | cdn_http: Defaults.cdn_http, 11 | cdn_https: Defaults.cdn_https, 12 | version: Defaults.VERSION, 13 | suffix: Defaults.dependency_suffix, 14 | receivers: DependenciesReceivers 15 | }); 16 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/dependency_loader.ts: -------------------------------------------------------------------------------- 1 | import {ScriptReceivers, ScriptReceiverFactory} from './script_receiver_factory'; 2 | import Runtime from 'runtime'; 3 | import ScriptRequest from './script_request'; 4 | 5 | /** Handles loading dependency files. 6 | * 7 | * Dependency loaders don't remember whether a resource has been loaded or 8 | * not. It is caller's responsibility to make sure the resource is not loaded 9 | * twice. This is because it's impossible to detect resource loading status 10 | * without knowing its content. 11 | * 12 | * Options: 13 | * - cdn_http - url to HTTP CND 14 | * - cdn_https - url to HTTPS CDN 15 | * - version - version of pusher-js 16 | * - suffix - suffix appended to all names of dependency files 17 | * 18 | * @param {Object} options 19 | */ 20 | export default class DependencyLoader { 21 | options: any; 22 | receivers: ScriptReceiverFactory; 23 | loading: any; 24 | 25 | constructor(options : any) { 26 | this.options = options; 27 | this.receivers = options.receivers || ScriptReceivers; 28 | this.loading = {}; 29 | } 30 | 31 | /** Loads the dependency from CDN. 32 | * 33 | * @param {String} name 34 | * @param {Function} callback 35 | */ 36 | load(name : string, options: any, callback : Function) { 37 | var self = this; 38 | 39 | if (self.loading[name] && self.loading[name].length > 0) { 40 | self.loading[name].push(callback); 41 | } else { 42 | self.loading[name] = [callback]; 43 | 44 | var request = Runtime.createScriptRequest(self.getPath(name, options)); 45 | var receiver = self.receivers.create(function(error) { 46 | self.receivers.remove(receiver); 47 | 48 | if (self.loading[name]) { 49 | var callbacks = self.loading[name]; 50 | delete self.loading[name]; 51 | 52 | var successCallback = function(wasSuccessful) { 53 | if (!wasSuccessful) { 54 | request.cleanup(); 55 | } 56 | }; 57 | for (var i = 0; i < callbacks.length; i++) { 58 | callbacks[i](error, successCallback); 59 | } 60 | } 61 | }); 62 | request.send(receiver); 63 | } 64 | } 65 | 66 | /** Returns a root URL for pusher-js CDN. 67 | * 68 | * @returns {String} 69 | */ 70 | getRoot(options : any) : string { 71 | var cdn; 72 | var protocol = Runtime.getDocument().location.protocol; 73 | if ((options && options.encrypted) || protocol === "https:") { 74 | cdn = this.options.cdn_https; 75 | } else { 76 | cdn = this.options.cdn_http; 77 | } 78 | // make sure there are no double slashes 79 | return cdn.replace(/\/*$/, "") + "/" + this.options.version; 80 | } 81 | 82 | /** Returns a full path to a dependency file. 83 | * 84 | * @param {String} name 85 | * @returns {String} 86 | */ 87 | getPath(name : string, options : any) : string { 88 | return this.getRoot(options) + '/' + name + this.options.suffix + '.js'; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/jsonp_request.ts: -------------------------------------------------------------------------------- 1 | import ScriptReceiver from './script_receiver'; 2 | import ScriptRequest from './script_request'; 3 | import * as Collections from 'core/utils/collections'; 4 | import Util from 'core/util'; 5 | import Runtime from '../runtime'; 6 | 7 | /** Sends data via JSONP. 8 | * 9 | * Data is a key-value map. Its values are JSON-encoded and then passed 10 | * through base64. Finally, keys and encoded values are appended to the query 11 | * string. 12 | * 13 | * The class itself does not guarantee raising errors on failures, as it's not 14 | * possible to support such feature on all browsers. Instead, JSONP endpoint 15 | * should call back in a way that's easy to distinguish from browser calls, 16 | * for example by passing a second argument to the receiver. 17 | * 18 | * @param {String} url 19 | * @param {Object} data key-value map of data to be submitted 20 | */ 21 | export default class JSONPRequest { 22 | url: string; 23 | data: any; 24 | request: ScriptRequest; 25 | 26 | constructor(url : string, data : any) { 27 | this.url = url; 28 | this.data = data; 29 | } 30 | 31 | /** Sends the actual JSONP request. 32 | * 33 | * @param {ScriptReceiver} receiver 34 | */ 35 | send(receiver : ScriptReceiver) { 36 | if (this.request) { 37 | return; 38 | } 39 | 40 | var query = Collections.buildQueryString(this.data); 41 | var url = this.url + "/" + receiver.number + "?" + query; 42 | this.request = Runtime.createScriptRequest(url); 43 | this.request.send(receiver); 44 | } 45 | 46 | /** Cleans up the DOM remains of the JSONP request. */ 47 | cleanup() { 48 | if (this.request) { 49 | this.request.cleanup(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/script_receiver.ts: -------------------------------------------------------------------------------- 1 | interface ScriptReceiver { 2 | number: number; 3 | id: string; 4 | name: string; 5 | callback: Function; 6 | } 7 | 8 | export default ScriptReceiver; 9 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/script_receiver_factory.ts: -------------------------------------------------------------------------------- 1 | import ScriptReceiver from './script_receiver'; 2 | 3 | /** Builds receivers for JSONP and Script requests. 4 | * 5 | * Each receiver is an object with following fields: 6 | * - number - unique (for the factory instance), numerical id of the receiver 7 | * - id - a string ID that can be used in DOM attributes 8 | * - name - name of the function triggering the receiver 9 | * - callback - callback function 10 | * 11 | * Receivers are triggered only once, on the first callback call. 12 | * 13 | * Receivers can be called by their name or by accessing factory object 14 | * by the number key. 15 | * 16 | * @param {String} prefix the prefix used in ids 17 | * @param {String} name the name of the object 18 | */ 19 | export class ScriptReceiverFactory { 20 | lastId: number; 21 | prefix: string; 22 | name: string; 23 | 24 | constructor(prefix : string, name : string) { 25 | this.lastId = 0; 26 | this.prefix = prefix; 27 | this.name = name; 28 | } 29 | 30 | create(callback : Function) : ScriptReceiver { 31 | this.lastId++; 32 | 33 | var number = this.lastId; 34 | var id = this.prefix + number; 35 | var name = this.name + "[" + number + "]"; 36 | 37 | var called = false; 38 | var callbackWrapper = function() { 39 | if (!called) { 40 | callback.apply(null, arguments); 41 | called = true; 42 | } 43 | }; 44 | 45 | this[number] = callbackWrapper; 46 | return { number: number, id: id, name: name, callback: callbackWrapper }; 47 | } 48 | 49 | remove(receiver : ScriptReceiver) { 50 | delete this[receiver.number]; 51 | } 52 | } 53 | 54 | export var ScriptReceivers = new ScriptReceiverFactory( 55 | "_pusher_script_", "Pusher.ScriptReceivers" 56 | ); 57 | -------------------------------------------------------------------------------- /src/runtimes/web/dom/script_request.ts: -------------------------------------------------------------------------------- 1 | import ScriptReceiver from './script_receiver'; 2 | 3 | /** Sends a generic HTTP GET request using a script tag. 4 | * 5 | * By constructing URL in a specific way, it can be used for loading 6 | * JavaScript resources or JSONP requests. It can notify about errors, but 7 | * only in certain environments. Please take care of monitoring the state of 8 | * the request yourself. 9 | * 10 | * @param {String} src 11 | */ 12 | export default class ScriptRequest { 13 | src: string; 14 | script: any; 15 | errorScript: any; 16 | 17 | constructor(src : string) { 18 | this.src = src; 19 | } 20 | 21 | send(receiver : ScriptReceiver) { 22 | var self = this; 23 | var errorString = "Error loading " + self.src; 24 | 25 | self.script = document.createElement("script"); 26 | self.script.id = receiver.id; 27 | self.script.src = self.src; 28 | self.script.type = "text/javascript"; 29 | self.script.charset = "UTF-8"; 30 | 31 | if (self.script.addEventListener) { 32 | self.script.onerror = function() { 33 | receiver.callback(errorString); 34 | }; 35 | self.script.onload = function() { 36 | receiver.callback(null); 37 | }; 38 | } else { 39 | self.script.onreadystatechange = function() { 40 | if (self.script.readyState === 'loaded' || 41 | self.script.readyState === 'complete') { 42 | receiver.callback(null); 43 | } 44 | }; 45 | } 46 | 47 | // Opera<11.6 hack for missing onerror callback 48 | if (self.script.async === undefined && (document).attachEvent && 49 | /opera/i.test(navigator.userAgent)) { 50 | self.errorScript = document.createElement("script"); 51 | self.errorScript.id = receiver.id + "_error"; 52 | self.errorScript.text = receiver.name + "('" + errorString + "');"; 53 | self.script.async = self.errorScript.async = false; 54 | } else { 55 | self.script.async = true; 56 | } 57 | 58 | var head = document.getElementsByTagName('head')[0]; 59 | head.insertBefore(self.script, head.firstChild); 60 | if (self.errorScript) { 61 | head.insertBefore(self.errorScript, self.script.nextSibling); 62 | } 63 | } 64 | 65 | /** Cleans up the DOM remains of the script request. */ 66 | cleanup() { 67 | if (this.script) { 68 | this.script.onload = this.script.onerror = null; 69 | this.script.onreadystatechange = null; 70 | } 71 | if (this.script && this.script.parentNode) { 72 | this.script.parentNode.removeChild(this.script); 73 | } 74 | if (this.errorScript && this.errorScript.parentNode) { 75 | this.errorScript.parentNode.removeChild(this.errorScript); 76 | } 77 | this.script = null; 78 | this.errorScript = null; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/runtimes/web/http/http.ts: -------------------------------------------------------------------------------- 1 | import xdrHooks from './http_xdomain_request'; 2 | import HTTP from 'isomorphic/http/http'; 3 | 4 | HTTP.createXDR = function(method, url) { 5 | return this.createRequest(xdrHooks, method, url); 6 | } 7 | 8 | export default HTTP; 9 | -------------------------------------------------------------------------------- /src/runtimes/web/http/http_xdomain_request.ts: -------------------------------------------------------------------------------- 1 | import HTTPRequest from "core/http/http_request"; 2 | import RequestHooks from "core/http/request_hooks"; 3 | import Ajax from "core/http/ajax"; 4 | import * as Errors from "core/errors"; 5 | 6 | var hooks : RequestHooks = { 7 | getRequest: function(socket : HTTPRequest) : Ajax { 8 | var xdr = new (window).XDomainRequest(); 9 | xdr.ontimeout = function() { 10 | socket.emit("error", new Errors.RequestTimedOut()); 11 | socket.close(); 12 | }; 13 | xdr.onerror = function(e) { 14 | socket.emit("error", e); 15 | socket.close(); 16 | }; 17 | xdr.onprogress = function() { 18 | if (xdr.responseText && xdr.responseText.length > 0) { 19 | socket.onChunk(200, xdr.responseText); 20 | } 21 | }; 22 | xdr.onload = function() { 23 | if (xdr.responseText && xdr.responseText.length > 0) { 24 | socket.onChunk(200, xdr.responseText); 25 | } 26 | socket.emit("finished", 200); 27 | socket.close(); 28 | }; 29 | return xdr; 30 | }, 31 | abortRequest: function(xdr : Ajax) { 32 | xdr.ontimeout = xdr.onerror = xdr.onprogress = xdr.onload = null; 33 | xdr.abort(); 34 | } 35 | }; 36 | 37 | export default hooks; 38 | -------------------------------------------------------------------------------- /src/runtimes/web/net_info.ts: -------------------------------------------------------------------------------- 1 | import Reachability from 'core/reachability'; 2 | import {default as EventsDispatcher} from 'core/events/dispatcher' 3 | 4 | /** Really basic interface providing network availability info. 5 | * 6 | * Emits: 7 | * - online - when browser goes online 8 | * - offline - when browser goes offline 9 | */ 10 | export class NetInfo extends EventsDispatcher implements Reachability { 11 | 12 | constructor() { 13 | super(); 14 | var self = this; 15 | // This is okay, as IE doesn't support this stuff anyway. 16 | if (window.addEventListener !== undefined) { 17 | window.addEventListener("online", function() { 18 | self.emit('online'); 19 | }, false); 20 | window.addEventListener("offline", function() { 21 | self.emit('offline'); 22 | }, false); 23 | } 24 | } 25 | 26 | /** Returns whether browser is online or not 27 | * 28 | * Offline means definitely offline (no connection to router). 29 | * Inverse does NOT mean definitely online (only currently supported in Safari 30 | * and even there only means the device has a connection to the router). 31 | * 32 | * @return {Boolean} 33 | */ 34 | isOnline() : boolean { 35 | if (window.navigator.onLine === undefined) { 36 | return true; 37 | } else { 38 | return window.navigator.onLine; 39 | } 40 | } 41 | } 42 | 43 | export var Network = new NetInfo(); 44 | -------------------------------------------------------------------------------- /src/runtimes/web/timeline/jsonp_timeline.ts: -------------------------------------------------------------------------------- 1 | import TimelineSender from 'core/timeline/timeline_sender'; 2 | import TimelineTransport from 'core/timeline/timeline_transport'; 3 | import Browser from 'runtime'; 4 | import {AuthTransport} from 'core/auth/auth_transports'; 5 | import {ScriptReceivers} from '../dom/script_receiver_factory'; 6 | 7 | var getAgent = function(sender : TimelineSender, encrypted : boolean) { 8 | return function(data : any, callback : Function) { 9 | var scheme = "http" + (encrypted ? "s" : "") + "://"; 10 | var url = scheme + (sender.host || sender.options.host) + sender.options.path; 11 | var request = Browser.createJSONPRequest(url, data); 12 | 13 | var receiver = Browser.ScriptReceivers.create(function(error, result){ 14 | ScriptReceivers.remove(receiver); 15 | request.cleanup(); 16 | 17 | if (result && result.host) { 18 | sender.host = result.host; 19 | } 20 | if (callback) { 21 | callback(error, result); 22 | } 23 | }); 24 | request.send(receiver); 25 | } 26 | }; 27 | 28 | var jsonp = { 29 | name: 'jsonp', 30 | getAgent 31 | } 32 | 33 | export default jsonp; 34 | -------------------------------------------------------------------------------- /src/runtimes/web/transports/transport_connection_initializer.ts: -------------------------------------------------------------------------------- 1 | import {Dependencies} from '../dom/dependencies'; 2 | 3 | /** Initializes the transport. 4 | * 5 | * Fetches resources if needed and then transitions to initialized. 6 | */ 7 | export default function() { 8 | var self = this; 9 | 10 | self.timeline.info(self.buildTimelineMessage({ 11 | transport: self.name + (self.options.encrypted ? "s" : "") 12 | })); 13 | 14 | if (self.hooks.isInitialized()) { 15 | self.changeState("initialized"); 16 | } else if (self.hooks.file) { 17 | self.changeState("initializing"); 18 | Dependencies.load( 19 | self.hooks.file, 20 | { encrypted: self.options.encrypted }, 21 | function(error, callback) { 22 | if (self.hooks.isInitialized()) { 23 | self.changeState("initialized"); 24 | callback(true); 25 | } else { 26 | if (error) { 27 | self.onError(error); 28 | } 29 | self.onClose(); 30 | callback(false); 31 | } 32 | } 33 | ); 34 | } else { 35 | self.onClose(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/runtimes/web/transports/transports.ts: -------------------------------------------------------------------------------- 1 | import { 2 | default as Transports, 3 | streamingConfiguration, 4 | pollingConfiguration 5 | } from 'isomorphic/transports/transports'; 6 | import Transport from 'core/transports/transport'; 7 | import TransportHooks from 'core/transports/transport_hooks'; 8 | import * as URLSchemes from 'core/transports/url_schemes'; 9 | import Runtime from 'runtime'; 10 | import {Dependencies} from '../dom/dependencies'; 11 | import * as Collections from "core/utils/collections"; 12 | 13 | var SockJSTransport = new Transport({ 14 | file: "sockjs", 15 | urls: URLSchemes.sockjs, 16 | handlesActivityChecks: true, 17 | supportsPing: false, 18 | 19 | isSupported: function() { 20 | return true; 21 | }, 22 | isInitialized: function() { 23 | return window.SockJS !== undefined; 24 | }, 25 | getSocket: function(url, options) { 26 | return new window.SockJS(url, null, { 27 | js_path: Dependencies.getPath("sockjs", { 28 | encrypted: options.encrypted 29 | }), 30 | ignore_null_origin: options.ignoreNullOrigin 31 | }); 32 | }, 33 | beforeOpen: function(socket, path) { 34 | socket.send(JSON.stringify({ 35 | path: path 36 | })); 37 | } 38 | }); 39 | 40 | var xdrConfiguration = { 41 | isSupported: function(environment) : boolean { 42 | var yes = Runtime.isXDRSupported(environment.encrypted); 43 | return yes; 44 | } 45 | }; 46 | 47 | /** HTTP streaming transport using XDomainRequest (IE 8,9). */ 48 | var XDRStreamingTransport = new Transport( 49 | Collections.extend({}, streamingConfiguration, xdrConfiguration) 50 | ); 51 | 52 | /** HTTP long-polling transport using XDomainRequest (IE 8,9). */ 53 | var XDRPollingTransport = new Transport( 54 | Collections.extend({}, pollingConfiguration, xdrConfiguration) 55 | ); 56 | 57 | Transports.xdr_streaming = XDRStreamingTransport; 58 | Transports.xdr_polling = XDRPollingTransport; 59 | Transports.sockjs = SockJSTransport; 60 | 61 | export default Transports; 62 | -------------------------------------------------------------------------------- /src/runtimes/worker/auth/fetch_auth.ts: -------------------------------------------------------------------------------- 1 | import AbstractRuntime from 'runtimes/interface'; 2 | import Logger from 'core/logger'; 3 | import {AuthTransport} from 'core/auth/auth_transports'; 4 | 5 | var fetchAuth : AuthTransport = function(context, socketId, callback){ 6 | var headers = new Headers(); 7 | headers.set("Content-Type", "application/x-www-form-urlencoded"); 8 | 9 | for (var headerName in this.authOptions.headers) { 10 | headers.set(headerName, this.authOptions.headers[headerName]); 11 | } 12 | 13 | var body = this.composeQuery(socketId); 14 | var request = new Request(this.options.authEndpoint, { 15 | headers, 16 | body, 17 | method: "POST", 18 | }); 19 | 20 | return fetch(request).then((response)=>{ 21 | let {status} = response; 22 | if (status === 200) { 23 | return response.text(); 24 | } else { 25 | Logger.warn("Couldn't get auth info from your webapp", status); 26 | throw status; 27 | } 28 | }).then((data)=>{ 29 | try { 30 | data = JSON.parse(data); 31 | } catch (e) { 32 | var message = 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + data; 33 | Logger.warn(message); 34 | throw message; 35 | } 36 | callback(false, data); 37 | }).catch((err)=>{ 38 | callback(true, err); 39 | }); 40 | } 41 | 42 | export default fetchAuth; 43 | -------------------------------------------------------------------------------- /src/runtimes/worker/net_info.ts: -------------------------------------------------------------------------------- 1 | import {default as EventsDispatcher} from 'core/events/dispatcher' 2 | import Reachability from 'core/reachability'; 3 | 4 | export class NetInfo extends EventsDispatcher implements Reachability { 5 | 6 | isOnline() : boolean { 7 | return true; 8 | } 9 | 10 | } 11 | 12 | export var Network = new NetInfo(); 13 | -------------------------------------------------------------------------------- /src/runtimes/worker/runtime.ts: -------------------------------------------------------------------------------- 1 | import Isomorphic from 'isomorphic/runtime'; 2 | import Runtime from "../interface"; 3 | import {Network} from './net_info'; 4 | import fetchAuth from './auth/fetch_auth'; 5 | import {AuthTransports} from 'core/auth/auth_transports'; 6 | import fetchTimeline from './timeline/fetch_timeline'; 7 | 8 | // Very verbose but until unavoidable until 9 | // TypeScript 2.1, when spread attributes will be 10 | // supported 11 | const { 12 | getDefaultStrategy, 13 | Transports, 14 | setup, 15 | getProtocol, 16 | isXHRSupported, 17 | getGlobal, 18 | getLocalStorage, 19 | createXHR, 20 | createWebSocket, 21 | addUnloadListener, 22 | removeUnloadListener, 23 | transportConnectionInitializer, 24 | createSocketRequest, 25 | HTTPFactory 26 | } = Isomorphic; 27 | 28 | const Worker : Runtime = { 29 | getDefaultStrategy, 30 | Transports, 31 | setup, 32 | getProtocol, 33 | isXHRSupported, 34 | getGlobal, 35 | getLocalStorage, 36 | createXHR, 37 | createWebSocket, 38 | addUnloadListener, 39 | removeUnloadListener, 40 | transportConnectionInitializer, 41 | createSocketRequest, 42 | HTTPFactory, 43 | 44 | TimelineTransport: fetchTimeline, 45 | 46 | getAuthorizers() : AuthTransports { 47 | return {ajax: fetchAuth}; 48 | }, 49 | 50 | getWebSocketAPI() { 51 | return WebSocket; 52 | }, 53 | 54 | getXHRAPI() { 55 | return XMLHttpRequest; 56 | }, 57 | 58 | getNetwork() { 59 | return Network; 60 | } 61 | }; 62 | 63 | export default Worker; 64 | -------------------------------------------------------------------------------- /src/runtimes/worker/timeline/fetch_timeline.ts: -------------------------------------------------------------------------------- 1 | import Logger from 'core/logger'; 2 | import TimelineSender from 'core/timeline/timeline_sender' 3 | import * as Collections from 'core/utils/collections'; 4 | import Util from 'core/util'; 5 | import Runtime from 'runtime'; 6 | import TimelineTransport from 'core/timeline/timeline_transport'; 7 | 8 | var getAgent = function(sender : TimelineSender, encrypted : boolean) { 9 | return function(data : any, callback : Function) { 10 | var scheme = "http" + (encrypted ? "s" : "") + "://"; 11 | var url = scheme + (sender.host || sender.options.host) + sender.options.path; 12 | var query = Collections.buildQueryString(data); 13 | url += ("/" + 2 + "?" + query); 14 | 15 | fetch(url). 16 | then((response) => { 17 | if (response.status !== 200) { 18 | throw(`received ${response.status} from stats.pusher.com`) 19 | } 20 | return response.json(); 21 | }).then(({host})=>{ 22 | if (host) { 23 | sender.host = host; 24 | } 25 | }).catch((err)=> { 26 | Logger.debug("TimelineSender Error: ", err); 27 | }); 28 | } 29 | } 30 | 31 | var fetchTimeline = { 32 | name: 'xhr', 33 | getAgent 34 | } 35 | 36 | export default fetchTimeline; 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "lib/", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "target": "es3", 8 | "removeComments": true 9 | }, 10 | "files": [ 11 | "src/core/pusher.ts", 12 | "src/d.ts/faye-websocket/faye-websocket.d.ts", 13 | "src/d.ts/xmlhttprequest/xmlhttprequest.d.ts", 14 | "src/d.ts/window/websocket.d.ts", 15 | "src/d.ts/window/xmlhttprequest.d.ts", 16 | "src/d.ts/window/sockjs.d.ts", 17 | "src/d.ts/window/events.d.ts", 18 | "src/d.ts/react-native/react-native.d.ts", 19 | "src/d.ts/module/module.d.ts", 20 | "src/d.ts/fetch/promise.d.ts", 21 | "src/d.ts/fetch/fetch.d.ts", 22 | "src/runtimes/web/net_info.ts", 23 | "src/runtimes/react-native/net_info.ts" 24 | ], 25 | "exclude": [ 26 | "node_modules" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /webpack/config.min.js: -------------------------------------------------------------------------------- 1 | var MINIFY = process.env.MINIFY; 2 | if (!MINIFY) throw "Please specify a build to minifiy, e.g. MINIFY=web ..." 3 | 4 | var config = require('./config.' + MINIFY); 5 | var webpack = require('webpack'); 6 | var objectAssign = require('object-assign-deep'); 7 | 8 | var config = objectAssign(config, { 9 | output: { 10 | filename: "[name].min.js" 11 | }, 12 | plugins: [ 13 | new webpack.optimize.UglifyJsPlugin() 14 | ] 15 | }); 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /webpack/config.node.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var objectAssign = require('object-assign-deep'); 3 | 4 | /* 5 | Upon importing the 'runtime' module, this node build is made to look at 6 | src/runtimes/node/runtime.ts by the below webpack resolution config. 7 | This is achieved by adding 'src/runtimes/node' to the resolve.modulesDirectories array 8 | 9 | -- CONVENIENCE -- 10 | We also add 'src/runtimes' to the list for convenient referencing of 'isomorphic/' implementations. 11 | We also add 'src/' so that the runtimes/node folder can conveniently import 'core/' modules. 12 | */ 13 | module.exports = objectAssign(require('./config.shared'), { 14 | output: { 15 | library: "Pusher", 16 | libraryTarget:"commonjs2", 17 | path: path.join(__dirname, "../dist/node"), 18 | filename: "pusher.js" 19 | }, 20 | target: "node", 21 | resolve: { 22 | modulesDirectories: ['src/', 'src/runtimes/node', 'src/runtimes'] 23 | }, 24 | externals: { 25 | "faye-websocket": "commonjs faye-websocket", 26 | "xmlhttprequest": "commonjs xmlhttprequest" 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /webpack/config.react-native.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var NormalModuleReplacementPlugin = require('webpack').NormalModuleReplacementPlugin; 3 | var version = require('../package').version; 4 | var objectAssign = require('object-assign-deep'); 5 | 6 | /* 7 | Upon importing the 'runtime' module, this react-native build is made to look at 8 | src/runtimes/react-native/runtime.ts by the below webpack resolution config. 9 | This is achieved by adding 'src/runtimes/react-native' to the resolve.modulesDirectories array 10 | 11 | -- CONVENIENCE -- 12 | We also add 'src/runtimes' to the list for convenient referencing of 'isomorphic/' implementations. 13 | We also add 'src/' so that the runtimes/react-native folder can conveniently import 'core/' modules. 14 | */ 15 | module.exports = objectAssign(require('./config.shared'),{ 16 | output: { 17 | library: "Pusher", 18 | libraryTarget:"commonjs2", 19 | path: path.join(__dirname, "../dist/react-native"), 20 | filename: "pusher.js" 21 | }, 22 | target: "node", 23 | externals: { 24 | "react-native": "react-native", // our Reachability implementation needs to reference react-native. 25 | }, 26 | resolve: { 27 | modulesDirectories: ['src/', 'src/runtimes/react-native', 'src/runtimes'] 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /webpack/config.shared.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var fs = require('fs'); 5 | var StringReplacePlugin = require("string-replace-webpack-plugin"); 6 | var Config = require('./hosting_config'); 7 | 8 | var lookup = { 9 | "": Config.version, 10 | "": Config.cdn_http, 11 | "": Config.cdn_https, 12 | "": Config.dependency_suffix 13 | }; 14 | 15 | var replacements = []; 16 | 17 | for (let i of Object.keys(lookup)) { 18 | replacements.push({ 19 | pattern: i, 20 | replacement: function() { 21 | return lookup[i]; 22 | } 23 | }); 24 | } 25 | 26 | module.exports = { 27 | entry: { 28 | pusher: "./src/core/index", 29 | }, 30 | resolve: { 31 | extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'] 32 | }, 33 | module: { 34 | loaders: [ 35 | { test: /\.ts$/, loader: 'ts-loader' }, 36 | { 37 | test: /.*/, 38 | loader: StringReplacePlugin.replace({replacements: replacements}) 39 | }, 40 | { test : /\.js$/, loader: 'es3ify-loader'} 41 | ] 42 | }, 43 | plugins: [ 44 | new webpack.BannerPlugin(fs.readFileSync('./src/core/pusher-licence.js', 'utf8').replace("", Config.version), {raw: true}), 45 | new StringReplacePlugin() 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /webpack/config.web.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var webpack = require('webpack'); 3 | var NormalModuleReplacementPlugin = webpack.NormalModuleReplacementPlugin; 4 | var objectAssign = require('object-assign-deep'); 5 | 6 | /* 7 | Upon importing the 'runtime' module, this web build is made to look at 8 | src/runtimes/web/runtime.ts by the below webpack resolution config. 9 | This is achieved by adding 'src/runtimes/web' to the resolve.modulesDirectories array 10 | 11 | -- CONVENIENCE -- 12 | We also add 'src/runtimes' to the list for convenient referencing of 'isomorphic/' implementations. 13 | We also add 'src/' so that the runtimes/web folder can conveniently import 'core/' modules. 14 | */ 15 | var config = objectAssign(require('./config.shared'),{ 16 | output: { 17 | library: "Pusher", 18 | path: path.join(__dirname, "../dist/web"), 19 | filename: "pusher.js", 20 | libraryTarget: "umd" 21 | }, 22 | resolve: { 23 | modulesDirectories: ['src/', 'src/runtimes/web', 'src/runtimes'] 24 | } 25 | }); 26 | 27 | module.exports = config; 28 | -------------------------------------------------------------------------------- /webpack/config.worker.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var NormalModuleReplacementPlugin = require('webpack').NormalModuleReplacementPlugin; 3 | var version = require('../package').version; 4 | var objectAssign = require('object-assign-deep'); 5 | 6 | /* 7 | Upon importing the 'runtime' module, this worker build is made to look at 8 | src/runtimes/worker/runtime.ts by the below webpack resolution config. 9 | This is achieved by adding 'src/runtimes/worker' to the resolve.modulesDirectories array 10 | 11 | -- CONVENIENCE -- 12 | We also add 'src/runtimes' to the list for convenient referencing of 'isomorphic/' implementations. 13 | We also add 'src/' so that the runtimes/worker folder can conveniently import 'core/' modules. 14 | */ 15 | var config = objectAssign(require('./config.shared'),{ 16 | output: { 17 | library: "Pusher", 18 | path: path.join(__dirname, "../dist/worker"), 19 | filename: "pusher.worker.js" 20 | }, 21 | resolve: { 22 | modulesDirectories: ['src/', 'src/runtimes/worker', 'src/runtimes'] 23 | } 24 | }); 25 | 26 | /* 27 | We want the file to be called pusher.worker.js and not pusher.js 28 | */ 29 | config.entry = { 30 | "pusher.worker": "./src/core/index", 31 | }; 32 | 33 | module.exports = config; 34 | -------------------------------------------------------------------------------- /webpack/dev.server.js: -------------------------------------------------------------------------------- 1 | var config = require("./config.web"); 2 | var webpack = require('webpack'); 3 | var WebpackDevServer = require('webpack-dev-server'); 4 | 5 | var port = parseInt(process.env.PORT) || 5555; 6 | 7 | config.entry.app = [ 8 | "webpack-dev-server/client?http://localhost:"+port+"/", 9 | "webpack/hot/dev-server" 10 | ]; 11 | 12 | var compiler = webpack(config); 13 | var server = new WebpackDevServer(compiler, { 14 | hot: true 15 | }); 16 | 17 | server.listen(port); 18 | -------------------------------------------------------------------------------- /webpack/hosting_config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | version: process.env.VERSION || require('../package').version, 3 | cdn_http: process.env.CDN_HTTP || 'http://js.pusher.com', 4 | cdn_https: process.env.CDN_HTTPS || 'https://js.pusher.com', 5 | dependency_suffix: (process.env.MINIFY ? '.min' : '' ) 6 | } 7 | --------------------------------------------------------------------------------