diff --git a/pusher-js/pusher-js-tests.ts b/pusher-js/pusher-js-tests.ts
new file mode 100644
index 000000000..c672ff58e
--- /dev/null
+++ b/pusher-js/pusher-js-tests.ts
@@ -0,0 +1,353 @@
+///
+
+import Pusher = require('pusher-js');
+import { PresenceChannel } from "pusher-js";
+
+var API_KEY: string;
+var pusher: Pusher.Pusher;
+
+//
+// Configuration
+//
+
+pusher = new Pusher(API_KEY, {
+ authEndpoint: "http://example.com/pusher/auth"
+});
+
+pusher = new Pusher(API_KEY, {
+ auth: {
+ params: { foo: "bar" },
+ headers: { baz: "boo" }
+ }
+});
+
+pusher = new Pusher(API_KEY, {
+ auth: {
+ params: { foo: "bar" },
+ headers: { "X-CSRF-Token": "SOME_CSRF_TOKEN" }
+ }
+});
+
+pusher = new Pusher(API_KEY, { cluster: "eu" });
+
+pusher = new Pusher(API_KEY, { enabledTransports: ["ws"] });
+
+pusher = new Pusher(API_KEY, { disabledTransports: ["sockjs"] });
+
+// will only use WebSockets
+pusher = new Pusher(API_KEY, {
+ enabledTransports: ["ws", "xhr_streaming"],
+ disabledTransports: ["xhr_streaming"]
+});
+
+//
+// Connection
+//
+
+var socket: Pusher.Pusher;
+var my_channel: Pusher.Channel;
+var channels: Pusher.Channel[];
+
+socket = new Pusher(API_KEY);
+
+//
+// Subscribing to channels
+//
+
+my_channel = socket.subscribe('my-channel');
+
+my_channel = socket.subscribe('private-my-channel');
+
+channels = socket.allChannels();
+console.group('Pusher - subscribed to:');
+for (var i = 0; i < channels.length; i++) {
+ var channel = channels[i];
+ console.log(channel.name);
+}
+console.groupEnd();
+
+my_channel = socket.subscribe('my-channel');
+socket.bind('new-comment',
+ function(data: any) {
+ // add comment into page
+ }
+);
+
+var channel: Pusher.Channel;
+
+var context = { title: 'Pusher' };
+var handler = function(){
+ console.log('My name is ' + this.title);
+};
+channel.bind('new-comment', handler, context);
+
+channel.unbind('new-comment', handler); // removes just `handler` for the `new-comment` event
+channel.unbind('new-comment'); // removes all handlers for the `new-comment` event
+channel.unbind(null, handler); // removes `handler` for all events
+channel.unbind(null, null, context); // removes all handlers for `context`
+channel.unbind(); // removes all handlers on `channel`
+
+
+//
+//
+// Samples from Pusher Documentation
+//
+//
+
+//
+// JavaScript Quick Start Guide
+//
+
+channel.bind('my-event', function(data: any) {
+ alert('An event was triggered with message: ' + data.message);
+});
+
+//
+// Client API Overview
+//
+
+var options: Pusher.Config;
+
+var channelName: string;
+var privateChannelName: string;
+var presenceChannelName: string;
+var add_member: Function;
+var remove_member: Function;
+var update_member_count: Function;
+var eventName: string;
+var callback: Function;
+var applicationKey: string;
+var log: Function;
+var $: any;
+var data: any;
+
+// Connecting to Pusher
+
+pusher = new Pusher(applicationKey, options);
+
+options = {
+ encrypted: true, // true/false
+ auth: {
+ params: { // {key: value} pairs
+ param1: 'value1',
+ param2: 'value2'
+ },
+ headers: { // {key: value} pairs
+ header1: 'value1',
+ header2: 'value2'
+ }
+ }
+};
+
+pusher = new Pusher('app_key', {
+ auth: {
+ params: {
+ CSRFToken: 'some_csrf_token'
+ }
+ }
+});
+
+pusher = new Pusher('app_key', {
+ auth: {
+ headers: {
+ 'X-CSRF-Token': 'some_csrf_token'
+ }
+ }
+});
+
+pusher = new Pusher('app_key', { cluster: 'eu' });
+
+pusher = new Pusher('app_key', { encrypted: true } );
+
+pusher = new Pusher('app_key');
+pusher.connection.bind( 'error', function( err: any ) {
+ if( err.data.code === 4004 ) {
+ log('>>> detected limit error');
+ }
+});
+
+// Disconnecting from Pusher
+
+pusher.disconnect();
+
+// Connection States
+
+pusher = new Pusher('YOUR_APP_KEY');
+
+pusher.connection.bind('connected', function() {
+ $('div#status').text('Real time is go!');
+});
+
+pusher.connection.bind('connecting_in', function(delay: any) {
+ alert("I haven't been able to establish a connection for this feature. " +
+ "I will try again in " + delay + " seconds.")
+});
+
+pusher.connection.bind('state_change', function(states: any) {
+ // states = {previous: 'oldState', current: 'newState'}
+ $('div#status').text("Pusher's current state is " + states.current);
+});
+
+var connectionState: string = pusher.connection.state;
+
+// Accessing channels
+
+channel = pusher.channel(channelName);
+
+// Public channels
+
+channel = pusher.subscribe(channelName);
+
+pusher.unsubscribe(channelName);
+
+// Private channels
+
+var privateChannel = pusher.subscribe(privateChannelName);
+
+// Presence channels
+
+var presenceChannel: PresenceChannel = pusher.subscribe(presenceChannelName);
+
+var count: number = presenceChannel.members.count;
+
+presenceChannel.members.each(function(member: Pusher.UserInfo) {
+ var userId = member.id;
+ var userInfo = member.info;
+});
+
+var some_user_id: number;
+var user = presenceChannel.members.get(some_user_id);
+
+var me = presenceChannel.members.me;
+
+pusher = new Pusher('app_key');
+presenceChannel = pusher.subscribe('presence-example');
+presenceChannel.bind('pusher:subscription_succeeded', function() {
+ var me = presenceChannel.members.me;
+ var userId = me.id;
+ var userInfo = me.info;
+});
+
+channel = pusher.subscribe('presence-meeting-11');
+
+channel.bind('pusher:subscription_succeeded', function(members: Pusher.Members) {
+ // for example
+ update_member_count(members.count);
+
+ members.each(function(member) {
+ // for example:
+ add_member(member.id, member.info);
+ });
+});
+
+channel.bind('pusher:member_added', function(member: Pusher.UserInfo) {
+ // for example:
+ add_member(member.id, member.info);
+});
+
+channel.bind('pusher:member_removed', function(member: Pusher.UserInfo) {
+ // for example:
+ remove_member(member.id, member.info);
+});
+
+// Pusher Events
+
+channel.bind(eventName, callback);
+
+pusher = new Pusher('APP_KEY');
+channel = pusher.subscribe('APPL');
+channel.bind('new-price',
+ function(data: any) {
+ // add new price into the APPL widget
+ }
+);
+
+var context = { title: 'Pusher' };
+var handler = function(){
+ console.log('My name is ' + this.title);
+};
+channel.bind('new-comment', handler, context);
+
+pusher.bind(eventName, callback);
+
+pusher = new Pusher('APP_KEY');
+var channel1 = pusher.subscribe('test_channel_1');
+var channel2 = pusher.subscribe('test_channel_2');
+var channel3 = pusher.subscribe('test_channel_3');
+
+var eventName = 'new-comment';
+callback = function(data: any) {
+ // add comment into page
+};
+
+// listen for 'new-comment' event on channel 1, 2 and 3
+pusher.bind(eventName, callback);
+
+// Unbinding from Events
+
+channel.unbind(eventName, callback);
+
+pusher = new Pusher('APP_KEY');
+channel = pusher.subscribe('APPL');
+callback = function(data: any) {};
+channel.bind('new-price', callback);
+
+channel.unbind('new-price', callback);
+
+// Pusher channel events
+
+channel.bind('pusher:subscription_succeeded', function() {
+});
+
+pusher = new Pusher('APP_KEY');
+channel = pusher.subscribe('private-channel');
+channel.bind('pusher:subscription_error', function(status: number) {
+ if(status == 408 || status == 503){
+ // retry?
+ }
+});
+
+// Triggering Client Events
+
+var triggered = channel.trigger(eventName, data);
+pusher = new Pusher('YOUR_APP_KEY');
+channel = pusher.subscribe('private-channel');
+channel.bind('pusher:subscription_succeeded', function() {
+ var triggered = channel.trigger('client-someeventname', { your: data });
+});
+
+// Best practice when sending client events
+
+var outputEl = document.getElementById('client_event_example_log');
+var state: any = {
+ currentX: 0,
+ currentY: 0,
+ lastX: undefined,
+ lastY: undefined
+};
+
+pusher = new Pusher("YOUR_APP_KEY");
+channel = pusher.subscribe("private-mousemoves");
+
+// this method should be bound as a 'mousemove' event listener
+document.body.addEventListener('mousemove', onMouseMove, false);
+function onMouseMove(ev: any){
+ ev = ev || window.event;
+ state.currentX = ev.pageX || ev.clientX;
+ state.currentY = ev.pageY || ev.clientY;
+}
+
+setInterval(function(){
+ if(state.currentX !== state.lastX || state.currentY !== state.lastY){
+ state.lastX = state.currentX;
+ state.lastY = state.currentY;
+
+ var text = document.createTextNode(
+ 'Triggering event due to state change: x: ' + state.currentX + ', y: ' + state.currentY
+ );
+ outputEl.replaceChild( text, outputEl.firstChild );
+
+ channel.trigger("client-mouse-moved", {x:state.currentX, y: state.currentY});
+ }
+}, 300); // send every 300 milliseconds if position has changed
+
diff --git a/pusher-js/pusher-js.d.ts b/pusher-js/pusher-js.d.ts
new file mode 100644
index 000000000..9306faa8f
--- /dev/null
+++ b/pusher-js/pusher-js.d.ts
@@ -0,0 +1,231 @@
+// Type definitions for pusher-js 3.0.0
+// Project: https://github.com/pusher/pusher-js
+// Definitions by: Qubo
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare module "pusher-js" {
+
+ namespace pusher {
+ interface PusherStatic {
+ new(apiKey: string, config?: Config): Pusher;
+ }
+
+ interface Pusher {
+ subscribe(name: string): Channel;
+ subscribeAll(): void;
+ unsubscribe(name: string): void;
+ channel(name: string): Channel;
+ allChannels(): Channel[];
+ bind(eventName: string, callback: Function): Pusher;
+ bind_all(callback: Function): Pusher;
+ disconnect(): void;
+ key: string;
+ config: Config; //TODO: add GlobalConfig typings
+ channels: any; //TODO: Type this
+ global_emitter: EventsDispatcher;
+ sessionId: number;
+ timeline: any; //TODO: Type this
+ connection: ConnectionManager;
+ }
+
+ interface Config {
+ /**
+ * Forces the connection to use encrypted transports.
+ */
+ encrypted?: boolean;
+
+ /**
+ * Endpoint on your server that will return the authentication signature needed for private channels.
+ */
+ authEndpoint?: string;
+
+ /**
+ * Defines how the authentication endpoint, defined using authEndpoint, will be called.
+ * There are two options available: ajax and jsonp.
+ */
+ authTransport?: string;
+
+ /**
+ * Allows passing additional data to authorizers. Supports query string params and headers (AJAX only).
+ * For example, following will pass foo=bar via the query string and baz: boo via headers:
+ */
+ auth?: AuthConfig;
+
+
+ /**
+ * Allows connecting to a different datacenter by setting up correct hostnames and ports for the connection.
+ */
+ cluster?: string;
+
+
+ /**
+ * Disables stats collection, so that connection metrics are not submitted to Pusher’s servers.
+ */
+ disableStats?: boolean;
+
+ /**
+ * Specifies which transports should be used by Pusher to establish a connection.
+ * Useful for applications running in controlled, well-behaving environments.
+ * Available transports: ws, wss, xhr_streaming, xhr_polling, sockjs.
+ * Additional transports may be added in the future and without adding them to this list, they will be disabled.
+ */
+ enabledTransports?: string[];
+
+
+ /**
+ * Specified which transports must not be used by Pusher to establish a connection.
+ * This settings overwrites transports whitelisted via the enabledTransports options.
+ * Available transports: ws, wss, xhr_streaming, xhr_polling, sockjs.
+ * Additional transports may be added in the future and without adding them to this list, they will be enabled.
+ */
+ disabledTransports?: string[];
+
+ /**
+ * Ignores null origin checks for HTTP fallbacks. Use with care, it should be disabled only if necessary (i.e. PhoneGap).
+ */
+ ignoreNullOrigin?: boolean;
+
+ /**
+ * After this time (in miliseconds) without any messages received from the server,
+ * a ping message will be sent to check if the connection is still working.
+ * Default value is is supplied by the server, low values will result in unnecessary traffic.
+ */
+ activityTimeout?: number;
+
+ /**
+ * Time before the connection is terminated after sending a ping message.
+ * Default is 30000 (30s). Low values will cause false disconnections, if latency is high.
+ */
+ pongTimeout?: number;
+
+ wsHost?: string;
+ wsPort?: number;
+ wssPort?: number;
+ httpHost?: string;
+ httpPort?: number;
+ httpsPort?: number;
+ }
+
+ interface AuthConfig {
+ params?: { [key: string]: any };
+ headers?: { [key: string]: any };
+ }
+
+ interface GenericEventsDispatcher extends EventsDispatcher {
+ bind(eventName: string, callback: Function, context?: any): Self;
+ bind_all(callback: Function): Self;
+ unbind(eventName?: string, callback?: Function, context?: any): Self;
+ unbind_all(eventName?: string, callback?: Function): Self;
+ emit(eventName: string, data?: any): Self;
+ }
+
+ interface Channel extends GenericEventsDispatcher {
+ /** Triggers an event */
+ trigger(eventName: string, data?: any): boolean;
+ pusher: Pusher;
+ name: string;
+ subscribed: boolean;
+ /**
+ * Authenticates the connection as a member of the channel.
+ * @param {String} socketId
+ * @param {Function} callback
+ */
+ authorize(socketId: string, callback: (data: any) => void): void;
+ }
+
+ interface EventsDispatcher {
+ bind(eventName: string, callback: Function, context?: any): EventsDispatcher;
+ bind_all(callback: Function): EventsDispatcher;
+ unbind(eventName?: string, callback?: Function, context?: any): EventsDispatcher;
+ unbind_all(eventName?: string, callback?: Function): EventsDispatcher;
+ emit(eventName: string, data?: any): EventsDispatcher;
+ }
+
+ interface ConnectionManager extends GenericEventsDispatcher {
+ key: string;
+ options: any; //TODO: Timeline.js
+ state: string;
+ connection: any; //TODO: Type this
+ encrypted: boolean;
+ timeline: any; //TODO: Type this
+ connectionCallbacks: {
+ message: (message: string) => void;
+ ping: () => void;
+ activity: () => void;
+ error: (error: any) => void;
+ closed: () => void;
+ };
+ errorCallbacks: {
+ ssl_only: () => void;
+ refused: () => void;
+ backoff: () => void;
+ retry: () => void;
+ };
+ handshakeCallbacks: {
+ ssl_only: () => void;
+ refused: () => void;
+ backoff: () => void;
+ retry: () => void;
+ connected: (handshake: any) => void; //TODO: Type this
+ };
+ /**
+ * Establishes a connection to Pusher.
+ *
+ * Does nothing when connection is already established. See top-level doc
+ * to find events emitted on connection attempts.
+ */
+ connect(): void;
+ /**
+ * Sends raw data.
+ * @param {String} data
+ */
+ send(data: string): boolean;
+ /** Sends an event.
+ *
+ * @param {String} name
+ * @param {String} data
+ * @param {String} [channel]
+ * @returns {Boolean} whether message was sent or not
+ */
+ send_event(name: string, data: string, channel: string): boolean;
+ /** Closes the connection. */
+ disconnect(): void;
+ isEncrypted(): boolean;
+ }
+
+ interface PresenceChannel extends Channel {
+ members: Members;
+ }
+
+ interface Members {
+ /**
+ * Returns member's info for given id.
+ *
+ * Resulting object containts two fields - id and info.
+ *
+ * @param {Number} id
+ * @return {Object} member's info or null
+ */
+ get(id: number): T;
+ /**
+ * Calls back for each member in unspecified order.
+ *
+ * @param {Function} callback
+ */
+ each(callback: (member: any) => void): void;
+ members: { [id: number]: UserInfo };
+ count: number;
+ myID: number;
+ me: UserInfo;
+ }
+
+ interface UserInfo {
+ id: number;
+ info: T;
+ }
+ }
+
+ var pusher: pusher.PusherStatic;
+
+ export = pusher;
+}