├── .travis.yml ├── LICENSE.txt ├── package-lock.json ├── package.json ├── readme.md ├── rollup.config.js ├── src ├── channel │ ├── channel.ts │ ├── index.ts │ ├── presence-channel.ts │ ├── pusher-channel.ts │ ├── pusher-presence-channel.ts │ ├── pusher-private-channel.ts │ ├── ratchet-channel.ts │ ├── ratchet-presence-channel.ts │ ├── ratchet-private-channel.ts │ ├── socketio-channel.ts │ ├── socketio-presence-channel.ts │ └── socketio-private-channel.ts ├── connector │ ├── connector.ts │ ├── index.ts │ ├── pusher-connector.ts │ ├── ratchet-connector.ts │ └── socketio-connector.ts ├── echo.ts └── util │ ├── event-formatter.ts │ └── index.ts ├── tests └── util │ └── event-formatter.test.ts ├── tsconfig.json └── typings └── index.d.ts /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-echo-ratchet", 3 | "version": "1.4.3", 4 | "description": "Laravel Echo library for beautiful Pusher, Socket.IO and now Ratchet integration", 5 | "license": "MIT", 6 | "main": "dist/echo.js", 7 | "scripts": { 8 | "compile": "./node_modules/.bin/rollup -c", 9 | "prepublish": "npm run compile", 10 | "test": "jest" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/simonhamp/echo" 15 | }, 16 | "keywords": [ 17 | "laravel", 18 | "pusher", 19 | "socket.io" 20 | ], 21 | "author": { 22 | "name": "Taylor Otwell" 23 | }, 24 | "homepage": "https://github.com/laravel/echo", 25 | "engines": { 26 | "node": ">6.0.*" 27 | }, 28 | "devDependencies": { 29 | "@types/jest": "^22.1.0", 30 | "@types/node": "^6.0.101", 31 | "babel-plugin-transform-object-assign": "^6.8.0", 32 | "babel-preset-es2015-rollup": "^3.0.0", 33 | "babel-preset-stage-2": "^6.5.0", 34 | "jest": "^22.1.0", 35 | "pusher-js": "^3.2.1", 36 | "rollup": "^0.31.0", 37 | "rollup-plugin-babel": "^2.4.0", 38 | "rollup-plugin-typescript": "^0.7.5", 39 | "ts-jest": "^22.0.0" 40 | }, 41 | "jest": { 42 | "transform": { 43 | "^.+\\.tsx?$": "ts-jest" 44 | }, 45 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 46 | "moduleFileExtensions": [ 47 | "ts", 48 | "tsx", 49 | "js", 50 | "jsx", 51 | "json", 52 | "node" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ## Introduction 4 | 5 | In many modern web applications, WebSockets are used to implement realtime, live-updating user interfaces. When some data is updated on the server, a message is typically sent over a WebSocket connection to be handled by the client. This provides a more robust, efficient alternative to continually polling your application for changes. 6 | 7 | To assist you in building these types of applications, Laravel makes it easy to "broadcast" your events over a WebSocket connection. Broadcasting your Laravel events allows you to share the same event names between your server-side code and your client-side JavaScript application. 8 | 9 | Laravel Echo is a JavaScript library that makes it painless to subscribe to channels and listen for events broadcast by Laravel. You may install Echo via the NPM package manager. 10 | 11 | ## Documentation 12 | 13 | Official documentation [is located here](http://laravel.com/docs/broadcasting). 14 | 15 | ## Now with Ratchet support 16 | 17 | This fork of Laravel Echo aims to add [Ratchet](http://socketo.me/) support to your application in a way that stays true to the goals of Laravel Echo. 18 | 19 | Best used with a purpose-built Ratchet solution for Laravel, e.g. [laravel-ratchet](https://github.com/askedio/laravel-ratchet). 20 | 21 | ## Installation 22 | 23 | ```bash 24 | $ npm i laravel-echo-ratchet --save 25 | ``` 26 | 27 | This fork of Echo requires a slightly different setup to the original. Other than this, it works identically to the original, so dropping it in will not harm your application if you're using Pusher or Socket.IO. 28 | 29 | If you've previously installed Echo, simply update your `package.json` dependency to the following: 30 | 31 | ```json 32 | dependencies: { 33 | "laravel-echo-ratchet": "^1.4.0" 34 | } 35 | ``` 36 | 37 | Then in your `bootstrap.js` (instead of `import Echo from 'laravel-echo'`: 38 | 39 | ```js 40 | import Echo from 'laravel-echo-ratchet' 41 | ``` 42 | 43 | ## Using with Ratchet 44 | 45 | To use with your Ratchet server, use the following as your Echo instantiator in your `bootstrap.js` file, replacing the parts in `{}` as appropriate: 46 | 47 | ```js 48 | window.Echo = new Echo({ 49 | broadcaster: 'ratchet', 50 | host: 'ws://{ratchet-server-ip}:{port}', 51 | }); 52 | ``` 53 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript'; 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | export default { 5 | entry: './src/echo.ts', 6 | dest: './dist/echo.js', 7 | plugins: [ 8 | typescript(), 9 | babel({ 10 | exclude: 'node_modules/**', 11 | presets: ['es2015-rollup', 'stage-2'], 12 | plugins: ['transform-object-assign'] 13 | }) 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/channel/channel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class represents a basic channel. 3 | */ 4 | export abstract class Channel { 5 | /** 6 | * The Echo options. 7 | * 8 | * @type {any} 9 | */ 10 | options: any; 11 | 12 | /** 13 | * Listen for an event on the channel instance. 14 | * 15 | * @param {string} event 16 | * @param {Function} callback 17 | * @return {Channel} 18 | */ 19 | abstract listen(event: string, callback: Function): Channel; 20 | 21 | /** 22 | * Listen for an event on the channel instance. 23 | * 24 | * @param {string} event 25 | * @param {Function} callback 26 | * @return {Channel} 27 | */ 28 | notification(callback: Function): Channel { 29 | return this.listen('.Illuminate\\Notifications\\Events\\BroadcastNotificationCreated', callback); 30 | } 31 | 32 | /** 33 | * Listen for a whisper event on the channel instance. 34 | * 35 | * @param {string} event 36 | * @param {Function} callback 37 | * @return {PusherChannel} 38 | */ 39 | listenForWhisper(event: string, callback: Function): Channel { 40 | return this.listen('.client-' + event, callback); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/channel/index.ts: -------------------------------------------------------------------------------- 1 | export * from './channel'; 2 | export * from './presence-channel'; 3 | export * from './pusher-channel'; 4 | export * from './pusher-private-channel'; 5 | export * from './pusher-presence-channel'; 6 | export * from './socketio-channel'; 7 | export * from './socketio-private-channel'; 8 | export * from './socketio-presence-channel'; 9 | export * from './ratchet-channel'; 10 | export * from './ratchet-private-channel'; 11 | export * from './ratchet-presence-channel'; 12 | -------------------------------------------------------------------------------- /src/channel/presence-channel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This interface represents a presence channel. 3 | */ 4 | export interface PresenceChannel { 5 | /** 6 | * Register a callback to be called anytime the member list changes. 7 | * 8 | * @param {Function} callback 9 | * @return {object} PresenceChannel 10 | */ 11 | here(callback: Function): PresenceChannel; 12 | 13 | /** 14 | * Listen for someone joining the channel. 15 | * 16 | * @param {Function} callback 17 | * @return {PresenceChannel} 18 | */ 19 | joining(callback: Function): PresenceChannel; 20 | 21 | /** 22 | * Listen for someone leaving the channel. 23 | * 24 | * @param {Function} callback 25 | * @return {PresenceChannel} 26 | */ 27 | leaving(callback: Function): PresenceChannel; 28 | } 29 | -------------------------------------------------------------------------------- /src/channel/pusher-channel.ts: -------------------------------------------------------------------------------- 1 | import { EventFormatter } from './../util'; 2 | import { Channel } from './channel'; 3 | 4 | /** 5 | * This class represents a Pusher channel. 6 | */ 7 | export class PusherChannel extends Channel { 8 | /** 9 | * The Pusher client instance. 10 | * 11 | * @type {any} 12 | */ 13 | pusher: any; 14 | 15 | /** 16 | * The name of the channel. 17 | * 18 | * @type {object} 19 | */ 20 | name: any; 21 | 22 | /** 23 | * Channel options. 24 | * 25 | * @type {any} 26 | */ 27 | options: any; 28 | 29 | /** 30 | * The event formatter. 31 | * 32 | * @type {EventFormatter} 33 | */ 34 | eventFormatter: EventFormatter; 35 | 36 | /** 37 | * The subsciption of the channel. 38 | * 39 | * @type {any} 40 | */ 41 | subscription: any; 42 | 43 | /** 44 | * Create a new class instance. 45 | * 46 | * @param {any} pusher 47 | * @param {object} name 48 | * @param {any} options 49 | */ 50 | constructor(pusher: any, name: any, options: any) { 51 | super(); 52 | 53 | this.name = name; 54 | this.pusher = pusher; 55 | this.options = options; 56 | 57 | this.eventFormatter = new EventFormatter(this.options.namespace); 58 | 59 | this.subscribe(); 60 | } 61 | 62 | /** 63 | * Subscribe to a Pusher channel. 64 | * 65 | * @param {string} channel 66 | * @return {object} 67 | */ 68 | subscribe(): any { 69 | this.subscription = this.pusher.subscribe(this.name); 70 | } 71 | 72 | /** 73 | * Unsubscribe from a Pusher channel. 74 | * 75 | * @return {void} 76 | */ 77 | unsubscribe(): void { 78 | this.pusher.unsubscribe(this.name); 79 | } 80 | 81 | /** 82 | * Listen for an event on the channel instance. 83 | * 84 | * @param {string} event 85 | * @param {Function} callback 86 | * @return {PusherChannel} 87 | */ 88 | listen(event: string, callback: Function): PusherChannel { 89 | this.on(this.eventFormatter.format(event), callback); 90 | 91 | return this; 92 | } 93 | 94 | /** 95 | * Stop listening for an event on the channel instance. 96 | * 97 | * @param {string} event 98 | * @return {PusherChannel} 99 | */ 100 | stopListening(event: string): PusherChannel { 101 | this.subscription.unbind(this.eventFormatter.format(event)); 102 | 103 | return this; 104 | } 105 | 106 | /** 107 | * Bind a channel to an event. 108 | * 109 | * @param {string} event 110 | * @param {Function} callback 111 | * @return {void} 112 | */ 113 | on(event: string, callback: Function): PusherChannel { 114 | this.subscription.bind(event, callback); 115 | 116 | return this; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/channel/pusher-presence-channel.ts: -------------------------------------------------------------------------------- 1 | import { PusherChannel } from './pusher-channel'; 2 | import { PresenceChannel } from './presence-channel'; 3 | 4 | /** 5 | * This class represents a Pusher presence channel. 6 | */ 7 | export class PusherPresenceChannel extends PusherChannel implements PresenceChannel { 8 | /** 9 | * Register a callback to be called anytime the member list changes. 10 | * 11 | * @param {Function} callback 12 | * @return {object} this 13 | */ 14 | here(callback): PusherPresenceChannel { 15 | this.on('pusher:subscription_succeeded', (data) => { 16 | callback(Object.keys(data.members).map(k => data.members[k])); 17 | }); 18 | 19 | return this; 20 | } 21 | 22 | /** 23 | * Listen for someone joining the channel. 24 | * 25 | * @param {Function} callback 26 | * @return {PusherPresenceChannel} 27 | */ 28 | joining(callback): PusherPresenceChannel { 29 | this.on('pusher:member_added', (member) => { 30 | callback(member.info); 31 | }); 32 | 33 | return this; 34 | } 35 | 36 | /** 37 | * Listen for someone leaving the channel. 38 | * 39 | * @param {Function} callback 40 | * @return {PusherPresenceChannel} 41 | */ 42 | leaving(callback): PusherPresenceChannel { 43 | this.on('pusher:member_removed', (member) => { 44 | callback(member.info); 45 | }); 46 | 47 | return this; 48 | } 49 | 50 | /** 51 | * Trigger client event on the channel. 52 | * 53 | * @param {Function} callback 54 | * @return {PusherPresenceChannel} 55 | */ 56 | whisper(eventName, data): PusherPresenceChannel { 57 | this.pusher.channels.channels[this.name].trigger(`client-${eventName}`, data); 58 | 59 | return this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/channel/pusher-private-channel.ts: -------------------------------------------------------------------------------- 1 | import { PusherChannel } from './pusher-channel'; 2 | 3 | /** 4 | * This class represents a Pusher private channel. 5 | */ 6 | export class PusherPrivateChannel extends PusherChannel { 7 | /** 8 | * Trigger client event on the channel. 9 | * 10 | * @param {Function} callback 11 | * @return {PusherPrivateChannel} 12 | */ 13 | whisper(eventName, data): PusherPrivateChannel { 14 | this.pusher.channels.channels[this.name].trigger(`client-${eventName}`, data); 15 | 16 | return this; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/channel/ratchet-channel.ts: -------------------------------------------------------------------------------- 1 | import { EventFormatter } from './../util'; 2 | import { Channel } from './channel'; 3 | 4 | /** 5 | * This class represents a Ratchet channel. 6 | */ 7 | export class RatchetChannel extends Channel { 8 | /** 9 | * The Ratchet client instance. 10 | * 11 | * @type {any} 12 | */ 13 | socket: any; 14 | 15 | /** 16 | * The name of the channel. 17 | * 18 | * @type {object} 19 | */ 20 | name: any; 21 | 22 | /** 23 | * Channel options. 24 | * 25 | * @type {any} 26 | */ 27 | options: any; 28 | 29 | /** 30 | * The event formatter. 31 | * 32 | * @type {EventFormatter} 33 | */ 34 | eventFormatter: EventFormatter; 35 | 36 | /** 37 | * The event callbacks applied to the channel. 38 | * 39 | * @type {any} 40 | */ 41 | events: any = {}; 42 | 43 | /** 44 | * Create a new class instance. 45 | * 46 | * @param {any} socket 47 | * @param {string} name 48 | * @param {any} options 49 | */ 50 | constructor(socket: any, name: string, options: any) { 51 | super(); 52 | 53 | this.name = name; 54 | this.socket = socket; 55 | this.options = options; 56 | this.eventFormatter = new EventFormatter(this.options.namespace); 57 | 58 | this.subscribe(); 59 | 60 | this.configureReconnector(); 61 | } 62 | 63 | /** 64 | * Subscribe to a Ratchet channel. 65 | * 66 | * @return {object} 67 | */ 68 | subscribe(): any { 69 | this.socket.emit('subscribe', { 70 | channel: this.name, 71 | auth: this.options.auth || {} 72 | }); 73 | } 74 | 75 | /** 76 | * Unsubscribe from channel and ubind event callbacks. 77 | * 78 | * @return {void} 79 | */ 80 | unsubscribe(): void { 81 | this.unbind(); 82 | 83 | this.socket.emit('unsubscribe', { 84 | channel: this.name, 85 | auth: this.options.auth || {} 86 | }); 87 | } 88 | 89 | /** 90 | * Listen for an event on the channel instance. 91 | * 92 | * @param {string} event 93 | * @param {Function} callback 94 | * @return {RatchetChannel} 95 | */ 96 | listen(event: string, callback: Function): RatchetChannel { 97 | this.on(this.eventFormatter.format(event), callback); 98 | 99 | return this; 100 | } 101 | 102 | /** 103 | * Bind the channel's socket to an event and store the callback. 104 | * 105 | * @param {string} event 106 | * @param {Function} callback 107 | */ 108 | on(event: string, callback: Function): void { 109 | let listener = (channel, data) => { 110 | if (this.name == channel) { 111 | callback(data); 112 | } 113 | }; 114 | 115 | this.bind(event, listener); 116 | } 117 | 118 | /** 119 | * Attach a 'reconnect' listener and bind the event. 120 | */ 121 | configureReconnector(): void { 122 | let listener = () => { 123 | this.subscribe(); 124 | }; 125 | 126 | this.bind('reconnect', listener); 127 | } 128 | 129 | /** 130 | * Bind the channel's socket to an event and store the callback. 131 | * 132 | * @param {string} event 133 | * @param {Function} callback 134 | * @return {void} 135 | */ 136 | bind(event: string, callback: Function): void { 137 | this.events[event] = this.events[event] || []; 138 | 139 | this.events[event].push(callback); 140 | } 141 | 142 | /** 143 | * Unbind the channel's socket from all stored event callbacks. 144 | * 145 | * @return {void} 146 | */ 147 | unbind(): void { 148 | Object.keys(this.events).forEach(event => { 149 | this.events[event].forEach(callback => { 150 | this.socket.removeListener(event, callback); 151 | }); 152 | 153 | delete this.events[event]; 154 | }); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/channel/ratchet-presence-channel.ts: -------------------------------------------------------------------------------- 1 | import { PresenceChannel, RatchetPrivateChannel } from './'; 2 | 3 | /** 4 | * This class represents a Ratchet presence channel. 5 | */ 6 | export class RatchetPresenceChannel extends RatchetPrivateChannel implements PresenceChannel { 7 | /** 8 | * Register a callback to be called anytime the member list changes. 9 | * 10 | * @param {Function} callback 11 | * @return {object} RatchetPresenceChannel 12 | */ 13 | here(callback: Function): RatchetPresenceChannel { 14 | this.on('presence:subscribed', (members) => { 15 | callback(members.map(m => m.user_info)); 16 | }); 17 | 18 | return this; 19 | } 20 | 21 | /** 22 | * Listen for someone joining the channel. 23 | * 24 | * @param {Function} callback 25 | * @return {RatchetPresenceChannel} 26 | */ 27 | joining(callback: Function): RatchetPresenceChannel { 28 | this.on('presence:joining', (member) => callback(member.user_info)); 29 | 30 | return this; 31 | } 32 | 33 | /** 34 | * Listen for someone leaving the channel. 35 | * 36 | * @param {Function} callback 37 | * @return {RatchetPresenceChannel} 38 | */ 39 | leaving(callback: Function): RatchetPresenceChannel { 40 | this.on('presence:leaving', (member) => callback(member.user_info)); 41 | 42 | return this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/channel/ratchet-private-channel.ts: -------------------------------------------------------------------------------- 1 | import { RatchetChannel } from './'; 2 | 3 | /** 4 | * This class represents a Ratchet presence channel. 5 | */ 6 | export class RatchetPrivateChannel extends RatchetChannel { 7 | /** 8 | * Trigger client event on the channel. 9 | * 10 | * @param {string} eventName 11 | * @param {object} data 12 | * @return {RatchetPrivateChannel} 13 | */ 14 | whisper(eventName, data) { 15 | this.socket.emit('client event', { 16 | channel: this.name, 17 | event: `client-${eventName}`, 18 | data: data 19 | }); 20 | 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/channel/socketio-channel.ts: -------------------------------------------------------------------------------- 1 | import { EventFormatter } from './../util'; 2 | import { Channel } from './channel'; 3 | 4 | /** 5 | * This class represents a Socket.io channel. 6 | */ 7 | export class SocketIoChannel extends Channel { 8 | /** 9 | * The Socket.io client instance. 10 | * 11 | * @type {any} 12 | */ 13 | socket: any; 14 | 15 | /** 16 | * The name of the channel. 17 | * 18 | * @type {object} 19 | */ 20 | name: any; 21 | 22 | /** 23 | * Channel options. 24 | * 25 | * @type {any} 26 | */ 27 | options: any; 28 | 29 | /** 30 | * The event formatter. 31 | * 32 | * @type {EventFormatter} 33 | */ 34 | eventFormatter: EventFormatter; 35 | 36 | /** 37 | * The event callbacks applied to the channel. 38 | * 39 | * @type {any} 40 | */ 41 | events: any = {}; 42 | 43 | /** 44 | * Create a new class instance. 45 | * 46 | * @param {any} socket 47 | * @param {string} name 48 | * @param {any} options 49 | */ 50 | constructor(socket: any, name: string, options: any) { 51 | super(); 52 | 53 | this.name = name; 54 | this.socket = socket; 55 | this.options = options; 56 | this.eventFormatter = new EventFormatter(this.options.namespace); 57 | 58 | this.subscribe(); 59 | 60 | this.configureReconnector(); 61 | } 62 | 63 | /** 64 | * Subscribe to a Socket.io channel. 65 | * 66 | * @return {object} 67 | */ 68 | subscribe(): any { 69 | this.socket.emit('subscribe', { 70 | channel: this.name, 71 | auth: this.options.auth || {} 72 | }); 73 | } 74 | 75 | /** 76 | * Unsubscribe from channel and ubind event callbacks. 77 | * 78 | * @return {void} 79 | */ 80 | unsubscribe(): void { 81 | this.unbind(); 82 | 83 | this.socket.emit('unsubscribe', { 84 | channel: this.name, 85 | auth: this.options.auth || {} 86 | }); 87 | } 88 | 89 | /** 90 | * Listen for an event on the channel instance. 91 | * 92 | * @param {string} event 93 | * @param {Function} callback 94 | * @return {SocketIoChannel} 95 | */ 96 | listen(event: string, callback: Function): SocketIoChannel { 97 | this.on(this.eventFormatter.format(event), callback); 98 | 99 | return this; 100 | } 101 | 102 | /** 103 | * Bind the channel's socket to an event and store the callback. 104 | * 105 | * @param {string} event 106 | * @param {Function} callback 107 | */ 108 | on(event: string, callback: Function): void { 109 | let listener = (channel, data) => { 110 | if (this.name == channel) { 111 | callback(data); 112 | } 113 | }; 114 | 115 | this.socket.on(event, listener); 116 | this.bind(event, listener); 117 | } 118 | 119 | /** 120 | * Attach a 'reconnect' listener and bind the event. 121 | */ 122 | configureReconnector(): void { 123 | let listener = () => { 124 | this.subscribe(); 125 | }; 126 | 127 | this.socket.on('reconnect', listener); 128 | 129 | this.bind('reconnect', listener); 130 | } 131 | 132 | /** 133 | * Bind the channel's socket to an event and store the callback. 134 | * 135 | * @param {string} event 136 | * @param {Function} callback 137 | * @return {void} 138 | */ 139 | bind(event: string, callback: Function): void { 140 | this.events[event] = this.events[event] || []; 141 | 142 | this.events[event].push(callback); 143 | } 144 | 145 | /** 146 | * Unbind the channel's socket from all stored event callbacks. 147 | * 148 | * @return {void} 149 | */ 150 | unbind(): void { 151 | Object.keys(this.events).forEach(event => { 152 | this.events[event].forEach(callback => { 153 | this.socket.removeListener(event, callback); 154 | }); 155 | 156 | delete this.events[event]; 157 | }); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/channel/socketio-presence-channel.ts: -------------------------------------------------------------------------------- 1 | import { PresenceChannel, SocketIoPrivateChannel } from './'; 2 | 3 | /** 4 | * This class represents a Socket.io presence channel. 5 | */ 6 | export class SocketIoPresenceChannel extends SocketIoPrivateChannel implements PresenceChannel { 7 | /** 8 | * Register a callback to be called anytime the member list changes. 9 | * 10 | * @param {Function} callback 11 | * @return {object} SocketIoPresenceChannel 12 | */ 13 | here(callback: Function): SocketIoPresenceChannel { 14 | this.on('presence:subscribed', (members) => { 15 | callback(members.map(m => m.user_info)); 16 | }); 17 | 18 | return this; 19 | } 20 | 21 | /** 22 | * Listen for someone joining the channel. 23 | * 24 | * @param {Function} callback 25 | * @return {SocketIoPresenceChannel} 26 | */ 27 | joining(callback: Function): SocketIoPresenceChannel { 28 | this.on('presence:joining', (member) => callback(member.user_info)); 29 | 30 | return this; 31 | } 32 | 33 | /** 34 | * Listen for someone leaving the channel. 35 | * 36 | * @param {Function} callback 37 | * @return {SocketIoPresenceChannel} 38 | */ 39 | leaving(callback: Function): SocketIoPresenceChannel { 40 | this.on('presence:leaving', (member) => callback(member.user_info)); 41 | 42 | return this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/channel/socketio-private-channel.ts: -------------------------------------------------------------------------------- 1 | import { SocketIoChannel } from './'; 2 | 3 | /** 4 | * This class represents a Socket.io presence channel. 5 | */ 6 | export class SocketIoPrivateChannel extends SocketIoChannel { 7 | /** 8 | * Trigger client event on the channel. 9 | * 10 | * @param {string} eventName 11 | * @param {object} data 12 | * @return {PusherPrivateChannel} 13 | */ 14 | whisper(eventName, data) { 15 | this.socket.emit('client event', { 16 | channel: this.name, 17 | event: `client-${eventName}`, 18 | data: data 19 | }); 20 | 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/connector/connector.ts: -------------------------------------------------------------------------------- 1 | import { Channel, PresenceChannel } from './../channel'; 2 | 3 | export abstract class Connector { 4 | 5 | /** 6 | * Default connector options. 7 | * 8 | * @type {Object} 9 | */ 10 | private _defaultOptions: any = { 11 | auth: { 12 | headers: {} 13 | }, 14 | authEndpoint: '/broadcasting/auth', 15 | broadcaster: 'pusher', 16 | csrfToken: null, 17 | host: null, 18 | key: null, 19 | namespace: 'App.Events' 20 | }; 21 | 22 | /** 23 | * Connector options. 24 | * 25 | * @type {object} 26 | */ 27 | options: any; 28 | 29 | /** 30 | * Create a new class instance. 31 | * 32 | * @params {any} options 33 | */ 34 | constructor(options: any) { 35 | this.setOptions(options); 36 | 37 | this.connect(); 38 | } 39 | 40 | /** 41 | * Merge the custom options with the defaults. 42 | * 43 | * @param {any} options 44 | * @return {any} 45 | */ 46 | protected setOptions(options: any): any { 47 | this.options = Object.assign(this._defaultOptions, options); 48 | 49 | if (this.csrfToken()) { 50 | this.options.auth.headers['X-CSRF-TOKEN'] = this.csrfToken(); 51 | } 52 | 53 | return options; 54 | } 55 | 56 | /** 57 | * Extract the CSRF token from the page. 58 | * 59 | * @return {string} 60 | */ 61 | protected csrfToken(): string { 62 | let selector; 63 | 64 | if (typeof window !== 'undefined' && window['Laravel'] && window['Laravel'].csrfToken) { 65 | return window['Laravel'].csrfToken; 66 | } else if (this.options.csrfToken) { 67 | return this.options.csrfToken; 68 | } else if (typeof document !== 'undefined' && (selector = document.querySelector('meta[name="csrf-token"]'))) { 69 | return selector.getAttribute('content'); 70 | } 71 | 72 | return null; 73 | } 74 | 75 | /** 76 | * Create a fresh connection. 77 | * 78 | * @retrn void 79 | */ 80 | abstract connect(): void; 81 | 82 | /** 83 | * Get a channel instance by name. 84 | * 85 | * @param {string} channel 86 | * @return {PusherChannel} 87 | */ 88 | abstract channel(channel: string): Channel; 89 | 90 | /** 91 | * Get a private channel instance by name. 92 | * 93 | * @param {string} channel 94 | * @return {Channel} 95 | */ 96 | abstract privateChannel(channel: string): Channel; 97 | 98 | /** 99 | * Get a presence channel instance by name. 100 | * 101 | * @param {string} channel 102 | * @return {PresenceChannel} 103 | */ 104 | abstract presenceChannel(channel: string): PresenceChannel; 105 | 106 | /** 107 | * Leave the given channel. 108 | * 109 | * @param {string} channel 110 | * @return {void} 111 | */ 112 | abstract leave(channel: string): void; 113 | 114 | /** 115 | * Get the socket_id of the connection. 116 | * 117 | * @return {string} 118 | */ 119 | abstract socketId(): string; 120 | 121 | /** 122 | * Disconnect from the Echo server. 123 | * 124 | * @return void 125 | */ 126 | abstract disconnect(): void; 127 | } 128 | -------------------------------------------------------------------------------- /src/connector/index.ts: -------------------------------------------------------------------------------- 1 | export * from './connector'; 2 | export * from './pusher-connector'; 3 | export * from './socketio-connector'; 4 | export * from './ratchet-connector'; 5 | -------------------------------------------------------------------------------- /src/connector/pusher-connector.ts: -------------------------------------------------------------------------------- 1 | import { Connector} from './connector'; 2 | import { 3 | PusherChannel, PusherPrivateChannel, PusherPresenceChannel, PresenceChannel 4 | } from './../channel'; 5 | 6 | /** 7 | * This class creates a connector to Pusher. 8 | */ 9 | export class PusherConnector extends Connector { 10 | /** 11 | * The Pusher instance. 12 | * 13 | * @type {object} 14 | */ 15 | pusher: any; 16 | 17 | /** 18 | * All of the subscribed channel names. 19 | * 20 | * @type {array} 21 | */ 22 | channels: any = {}; 23 | 24 | /** 25 | * Create a fresh Pusher connection. 26 | * 27 | * @return void 28 | */ 29 | connect(): void { 30 | this.pusher = new Pusher(this.options.key, this.options); 31 | } 32 | 33 | /** 34 | * Listen for an event on a channel instance. 35 | * 36 | * @param {string} name 37 | * @param {event} string 38 | * @param {Function} callback 39 | * @return {PusherChannel} 40 | */ 41 | listen(name: string, event: string, callback: Function): PusherChannel { 42 | return this.channel(name).listen(event, callback); 43 | } 44 | 45 | /** 46 | * Get a channel instance by name. 47 | * 48 | * @param {string} name 49 | * @return {PusherChannel} 50 | */ 51 | channel(name: string): PusherChannel { 52 | if (!this.channels[name]) { 53 | this.channels[name] = new PusherChannel( 54 | this.pusher, 55 | name, 56 | this.options 57 | ); 58 | } 59 | 60 | return this.channels[name]; 61 | } 62 | 63 | /** 64 | * Get a private channel instance by name. 65 | * 66 | * @param {string} name 67 | * @return {PusherPrivateChannel} 68 | */ 69 | privateChannel(name: string): PusherChannel { 70 | if (!this.channels['private-' + name]) { 71 | this.channels['private-' + name] = new PusherPrivateChannel( 72 | this.pusher, 73 | 'private-' + name, 74 | this.options 75 | ); 76 | } 77 | 78 | return this.channels['private-' + name]; 79 | } 80 | 81 | /** 82 | * Get a presence channel instance by name. 83 | * 84 | * @param {string} name 85 | * @return {PresenceChannel} 86 | */ 87 | presenceChannel(name: string): PresenceChannel { 88 | if (!this.channels['presence-' + name]) { 89 | this.channels['presence-' + name] = new PusherPresenceChannel( 90 | this.pusher, 91 | 'presence-' + name, 92 | this.options 93 | ); 94 | } 95 | 96 | return this.channels['presence-' + name]; 97 | } 98 | 99 | /** 100 | * Leave the given channel. 101 | * 102 | * @param {string} channel 103 | */ 104 | leave(name: string) { 105 | let channels = [name, 'private-' + name, 'presence-' + name]; 106 | 107 | channels.forEach((name: string, index: number) => { 108 | if (this.channels[name]) { 109 | this.channels[name].unsubscribe(); 110 | 111 | delete this.channels[name]; 112 | } 113 | }); 114 | } 115 | 116 | /** 117 | * Get the socket ID for the connection. 118 | * 119 | * @return {string} 120 | */ 121 | socketId(): string { 122 | return this.pusher.connection.socket_id; 123 | } 124 | 125 | /** 126 | * Disconnect Pusher connection. 127 | * 128 | * @return void 129 | */ 130 | disconnect(): void { 131 | this.pusher.disconnect(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/connector/ratchet-connector.ts: -------------------------------------------------------------------------------- 1 | import { Connector } from './connector'; 2 | import { RatchetChannel, RatchetPrivateChannel, RatchetPresenceChannel } from './../channel'; 3 | 4 | /** 5 | * This class creates a connnector to a Ratchet server. 6 | */ 7 | export class RatchetConnector extends Connector { 8 | /** 9 | * The WebSocket connection instance. 10 | * 11 | * @type {object} 12 | */ 13 | socket: any; 14 | 15 | /** 16 | * All of the subscribed channel names. 17 | * 18 | * @type {any} 19 | */ 20 | channels: any = {}; 21 | 22 | /** 23 | * Create a fresh Ratchet connection. 24 | * 25 | * @return WebSocket 26 | */ 27 | connect(): WebSocket { 28 | if (this.options.protocols) { 29 | this.socket = new WebSocket(this.options.host, this.options.protocols); 30 | } else { 31 | this.socket = new WebSocket(this.options.host); 32 | } 33 | 34 | this.extendSocket(); 35 | 36 | return this.socket; 37 | } 38 | 39 | /** 40 | * Attach event handlers to the socket. 41 | * 42 | * @return {void} 43 | */ 44 | extendSocket(): void { 45 | // Extend the socket with a queue for events 46 | this.socket.queue = []; 47 | 48 | this.socket.id = this.generateId(); 49 | 50 | // Extend the socket with an emit function (mimic SocketIO API) 51 | this.socket.emit = (event: string, message: object) => { 52 | return this.emit(event, message); 53 | }; 54 | 55 | // Add main event handlers 56 | this.socket.addEventListener('open', () => { 57 | this.open(); 58 | }); 59 | 60 | this.socket.addEventListener('message', (message) => { 61 | this.receive(message); 62 | }); 63 | } 64 | 65 | /** 66 | * Send a packet over the connection. 67 | * 68 | * @param {string} event 69 | * @param {object} message 70 | * @return {void} 71 | */ 72 | emit(event: string, message: object): void { 73 | // Stringify the event 74 | var packet = JSON.stringify({"event":event, "message":message}); 75 | 76 | // Queue the packet if the connection isn't ready 77 | if (this.socket.readyState !== this.socket.OPEN) { 78 | this.socket.queue.push(packet); 79 | return; 80 | } 81 | 82 | // Otherwise send immediately 83 | this.socket.send(packet); 84 | } 85 | 86 | /** 87 | * Handle when the connection is set up successfully. 88 | * 89 | * @return {void} 90 | */ 91 | open(): void { 92 | // Send any queued events 93 | var socket = this.socket; 94 | 95 | socket.queue.forEach(function(packet){ 96 | socket.send(packet); 97 | }); 98 | 99 | // Reset the queue 100 | this.socket.queue = []; 101 | } 102 | 103 | /** 104 | * Handle a message received from the server. 105 | * 106 | * @param {MessageEvent} event 107 | * @return {void} 108 | */ 109 | receive(message: MessageEvent): void { 110 | // Pick apart the message to determine where it should go 111 | var packet = JSON.parse(message.data); 112 | 113 | if (packet.event && packet.channel && typeof packet.payload !== "undefined") { 114 | // Fire the callbacks for the right event on the appropriate channel 115 | var events = this.channel(packet.channel).events[packet.event]; 116 | 117 | if (typeof events !== "undefined") { 118 | events.forEach(function(callback){ 119 | callback(packet.channel, packet.payload); 120 | }); 121 | } 122 | } else { 123 | // Looks like a poorly formatted message 124 | throw 'Invalid message received via socket.'; 125 | } 126 | } 127 | 128 | /** 129 | * Listen for an event on a channel instance. 130 | * 131 | * @param {string} name 132 | * @param {string} event 133 | * @param {Function} callback 134 | * @return {RatchetChannel} 135 | */ 136 | listen(name: string, event: string, callback: Function): RatchetChannel { 137 | return this.channel(name).listen(event, callback); 138 | } 139 | 140 | /** 141 | * Get a channel instance by name. 142 | * 143 | * @param {string} name 144 | * @return {RatchetChannel} 145 | */ 146 | channel(name: string): RatchetChannel { 147 | if (!this.channels[name]) { 148 | this.channels[name] = new RatchetChannel( 149 | this.socket, 150 | name, 151 | this.options 152 | ); 153 | } 154 | 155 | return this.channels[name]; 156 | } 157 | 158 | /** 159 | * Get a private channel instance by name. 160 | * 161 | * @param {string} name 162 | * @return {RatchetChannel} 163 | */ 164 | privateChannel(name: string): RatchetPrivateChannel { 165 | if (!this.channels['private-' + name]) { 166 | this.channels['private-' + name] = new RatchetPrivateChannel( 167 | this.socket, 168 | 'private-' + name, 169 | this.options 170 | ); 171 | } 172 | 173 | return this.channels['private-' + name]; 174 | } 175 | 176 | /** 177 | * Get a presence channel instance by name. 178 | * 179 | * @param {string} name 180 | * @return {RatchetPresenceChannel} 181 | */ 182 | presenceChannel(name: string): RatchetPresenceChannel { 183 | if (!this.channels['presence-' + name]) { 184 | this.channels['presence-' + name] = new RatchetPresenceChannel( 185 | this.socket, 186 | 'presence-' + name, 187 | this.options 188 | ); 189 | } 190 | 191 | return this.channels['presence-' + name]; 192 | } 193 | 194 | /** 195 | * Leave the given channel. 196 | * 197 | * @param {string} name 198 | * @return {void} 199 | */ 200 | leave(name: string): void { 201 | let channels = [name, 'private-' + name, 'presence-' + name]; 202 | 203 | channels.forEach(name => { 204 | if (this.channels[name]) { 205 | this.channels[name].unsubscribe(); 206 | 207 | delete this.channels[name]; 208 | } 209 | }); 210 | } 211 | 212 | /** 213 | * Get the socket ID for the connection. 214 | * 215 | * @return {string} 216 | */ 217 | socketId(): string { 218 | return this.socket.id; 219 | } 220 | 221 | /** 222 | * Generate an ID for the socket. 223 | * 224 | * @see https://jsperf.com/uuid4/8 225 | * @return {string} 226 | */ 227 | generateId(): string { 228 | var c = '0123456789ABCDEF'.split(''), 229 | id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.split(''), 230 | r; 231 | 232 | id[0] = c[(r = Math.random() * 0x100000000) & 0xf]; 233 | id[1] = c[(r >>>= 4) & 0xf]; 234 | id[2] = c[(r >>>= 4) & 0xf]; 235 | id[3] = c[(r >>>= 4) & 0xf]; 236 | id[4] = c[(r >>>= 4) & 0xf]; 237 | id[5] = c[(r >>>= 4) & 0xf]; 238 | id[6] = c[(r >>>= 4) & 0xf]; 239 | id[7] = c[(r >>>= 4) & 0xf]; 240 | 241 | id[9] = c[(r = Math.random() * 0x100000000) & 0xf]; 242 | id[10] = c[(r >>>= 4) & 0xf]; 243 | id[11] = c[(r >>>= 4) & 0xf]; 244 | id[12] = c[(r >>>= 4) & 0xf]; 245 | id[15] = c[(r >>>= 4) & 0xf]; 246 | id[16] = c[(r >>>= 4) & 0xf]; 247 | id[17] = c[(r >>>= 4) & 0xf]; 248 | 249 | id[19] = c[(r = Math.random() * 0x100000000) & 0x3 | 0x8]; 250 | id[20] = c[(r >>>= 4) & 0xf]; 251 | id[21] = c[(r >>>= 4) & 0xf]; 252 | id[22] = c[(r >>>= 4) & 0xf]; 253 | id[24] = c[(r >>>= 4) & 0xf]; 254 | id[25] = c[(r >>>= 4) & 0xf]; 255 | id[26] = c[(r >>>= 4) & 0xf]; 256 | id[27] = c[(r >>>= 4) & 0xf]; 257 | 258 | id[28] = c[(r = Math.random() * 0x100000000) & 0xf]; 259 | id[29] = c[(r >>>= 4) & 0xf]; 260 | id[30] = c[(r >>>= 4) & 0xf]; 261 | id[31] = c[(r >>>= 4) & 0xf]; 262 | id[32] = c[(r >>>= 4) & 0xf]; 263 | id[33] = c[(r >>>= 4) & 0xf]; 264 | id[34] = c[(r >>>= 4) & 0xf]; 265 | id[35] = c[(r >>>= 4) & 0xf]; 266 | 267 | return id.join(''); 268 | } 269 | 270 | /** 271 | * Disconnect Ratchet connection. 272 | * 273 | * @return void 274 | */ 275 | disconnect(): void { 276 | this.socket.disconnect(); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/connector/socketio-connector.ts: -------------------------------------------------------------------------------- 1 | import { Connector } from './connector'; 2 | import { SocketIoChannel, SocketIoPrivateChannel, SocketIoPresenceChannel } from './../channel'; 3 | 4 | /** 5 | * This class creates a connnector to a Socket.io server. 6 | */ 7 | export class SocketIoConnector extends Connector { 8 | /** 9 | * The Socket.io connection instance. 10 | * 11 | * @type {object} 12 | */ 13 | socket: any; 14 | 15 | /** 16 | * All of the subscribed channel names. 17 | * 18 | * @type {any} 19 | */ 20 | channels: any = {}; 21 | 22 | /** 23 | * Create a fresh Socket.io connection. 24 | * 25 | * @return void 26 | */ 27 | connect(): void { 28 | let io = this.getSocketIO(); 29 | 30 | this.socket = io(this.options.host, this.options); 31 | 32 | return this.socket; 33 | } 34 | 35 | /** 36 | * Get socket.io module from global scope or options. 37 | * 38 | * @type {object} 39 | */ 40 | getSocketIO(): any { 41 | if (typeof io !== 'undefined') { 42 | return io; 43 | } 44 | 45 | if (this.options.client !== 'undefined') { 46 | return this.options.client; 47 | } 48 | 49 | throw new Error('Socket.io client not found. Should be globally available or passed via options.client'); 50 | } 51 | 52 | /** 53 | * Listen for an event on a channel instance. 54 | * 55 | * @param {string} name 56 | * @param {string} event 57 | * @param {Function} callback 58 | * @return {SocketIoChannel} 59 | */ 60 | listen(name: string, event: string, callback: Function): SocketIoChannel { 61 | return this.channel(name).listen(event, callback); 62 | } 63 | 64 | /** 65 | * Get a channel instance by name. 66 | * 67 | * @param {string} name 68 | * @return {SocketIoChannel} 69 | */ 70 | channel(name: string): SocketIoChannel { 71 | if (!this.channels[name]) { 72 | this.channels[name] = new SocketIoChannel( 73 | this.socket, 74 | name, 75 | this.options 76 | ); 77 | } 78 | 79 | return this.channels[name]; 80 | } 81 | 82 | /** 83 | * Get a private channel instance by name. 84 | * 85 | * @param {string} name 86 | * @return {SocketIoChannel} 87 | */ 88 | privateChannel(name: string): SocketIoPrivateChannel { 89 | if (!this.channels['private-' + name]) { 90 | this.channels['private-' + name] = new SocketIoPrivateChannel( 91 | this.socket, 92 | 'private-' + name, 93 | this.options 94 | ); 95 | } 96 | 97 | return this.channels['private-' + name]; 98 | } 99 | 100 | /** 101 | * Get a presence channel instance by name. 102 | * 103 | * @param {string} name 104 | * @return {SocketIoPresenceChannel} 105 | */ 106 | presenceChannel(name: string): SocketIoPresenceChannel { 107 | if (!this.channels['presence-' + name]) { 108 | this.channels['presence-' + name] = new SocketIoPresenceChannel( 109 | this.socket, 110 | 'presence-' + name, 111 | this.options 112 | ); 113 | } 114 | 115 | return this.channels['presence-' + name]; 116 | } 117 | 118 | /** 119 | * Leave the given channel. 120 | * 121 | * @param {string} name 122 | * @return {void} 123 | */ 124 | leave(name: string): void { 125 | let channels = [name, 'private-' + name, 'presence-' + name]; 126 | 127 | channels.forEach(name => { 128 | if (this.channels[name]) { 129 | this.channels[name].unsubscribe(); 130 | 131 | delete this.channels[name]; 132 | } 133 | }); 134 | } 135 | 136 | /** 137 | * Get the socket ID for the connection. 138 | * 139 | * @return {string} 140 | */ 141 | socketId(): string { 142 | return this.socket.id; 143 | } 144 | 145 | /** 146 | * Disconnect Socketio connection. 147 | * 148 | * @return void 149 | */ 150 | disconnect(): void { 151 | this.socket.disconnect(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/echo.ts: -------------------------------------------------------------------------------- 1 | import { EventFormatter } from './util'; 2 | import { Channel, PresenceChannel } from './channel' 3 | import { PusherConnector, SocketIoConnector, RatchetConnector } from './connector'; 4 | 5 | /** 6 | * This class is the primary API for interacting with broadcasting. 7 | */ 8 | class Echo { 9 | 10 | /** 11 | * The broadcasting connector. 12 | * 13 | * @type {object} 14 | */ 15 | connector: any; 16 | 17 | /** 18 | * The Echo options. 19 | * 20 | * @type {array} 21 | */ 22 | options: any; 23 | 24 | /** 25 | * Create a new class instance. 26 | * 27 | * @param {object} options 28 | */ 29 | constructor(options: any) { 30 | this.options = options; 31 | 32 | if (typeof Vue === 'function' && Vue.http) { 33 | this.registerVueRequestInterceptor(); 34 | } 35 | 36 | if (typeof axios === 'function') { 37 | this.registerAxiosRequestInterceptor(); 38 | } 39 | 40 | if (typeof jQuery === 'function') { 41 | this.registerjQueryAjaxSetup(); 42 | } 43 | 44 | if (this.options.broadcaster == 'pusher') { 45 | this.connector = new PusherConnector(this.options); 46 | } else if (this.options.broadcaster == 'socket.io') { 47 | this.connector = new SocketIoConnector(this.options); 48 | } else if (this.options.broadcaster == 'ratchet') { 49 | this.connector = new RatchetConnector(this.options); 50 | } 51 | } 52 | 53 | /** 54 | * Register a Vue HTTP interceptor to add the X-Socket-ID header. 55 | */ 56 | registerVueRequestInterceptor() { 57 | Vue.http.interceptors.push((request, next) => { 58 | if (this.socketId()) { 59 | request.headers.set('X-Socket-ID', this.socketId()); 60 | } 61 | 62 | next(); 63 | }); 64 | } 65 | 66 | /** 67 | * Register an Axios HTTP interceptor to add the X-Socket-ID header. 68 | */ 69 | registerAxiosRequestInterceptor() { 70 | axios.interceptors.request.use((config) => { 71 | if (this.socketId()) { 72 | config.headers['X-Socket-Id'] = this.socketId(); 73 | } 74 | 75 | return config; 76 | }); 77 | } 78 | 79 | /** 80 | * Register jQuery AjaxSetup to add the X-Socket-ID header. 81 | */ 82 | registerjQueryAjaxSetup() { 83 | if (typeof jQuery.ajax != 'undefined') { 84 | jQuery.ajaxSetup({ 85 | beforeSend: (xhr) => { 86 | if (this.socketId()) { 87 | xhr.setRequestHeader('X-Socket-Id', this.socketId()); 88 | } 89 | } 90 | }); 91 | } 92 | } 93 | 94 | /** 95 | * Listen for an event on a channel instance. 96 | */ 97 | listen(channel: string, event: string, callback: Function) { 98 | return this.connector.listen(channel, event, callback); 99 | } 100 | 101 | /** 102 | * Get a channel instance by name. 103 | * 104 | * @param {string} channel 105 | * @return {object} 106 | */ 107 | channel(channel: string): Channel { 108 | return this.connector.channel(channel); 109 | } 110 | 111 | /** 112 | * Get a private channel instance by name. 113 | * 114 | * @param {string} channel 115 | * @return {object} 116 | */ 117 | private(channel: string): Channel { 118 | return this.connector.privateChannel(channel); 119 | } 120 | 121 | /** 122 | * Get a presence channel instance by name. 123 | * 124 | * @param {string} channel 125 | * @return {object} 126 | */ 127 | join(channel: string): PresenceChannel { 128 | return this.connector.presenceChannel(channel); 129 | } 130 | 131 | /** 132 | * Leave the given channel. 133 | * 134 | * @param {string} channel 135 | */ 136 | leave(channel: string) { 137 | this.connector.leave(channel); 138 | } 139 | 140 | /** 141 | * Get the Socket ID for the connection. 142 | * 143 | * @return {string} 144 | */ 145 | socketId(): string { 146 | return this.connector.socketId(); 147 | } 148 | 149 | /** 150 | * Disconnect from the Echo server. 151 | * 152 | * @return void 153 | */ 154 | disconnect(): void { 155 | this.connector.disconnect(); 156 | } 157 | } 158 | 159 | module.exports = Echo; 160 | -------------------------------------------------------------------------------- /src/util/event-formatter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Event name formatter 3 | */ 4 | export class EventFormatter { 5 | 6 | /** 7 | * Event namespace. 8 | * 9 | * @type {string} 10 | */ 11 | namespace: string | boolean; 12 | 13 | /** 14 | * Create a new class instance. 15 | * 16 | * @params {string | boolean} namespace 17 | */ 18 | constructor(namespace: string | boolean) { 19 | this.setNamespace(namespace); 20 | } 21 | 22 | /** 23 | * Format the given event name. 24 | * 25 | * @param {string} event 26 | * @return {string} 27 | */ 28 | format(event: string): string { 29 | if (event.charAt(0) === '.' || event.charAt(0) === '\\') { 30 | return event.substr(1); 31 | } else if (this.namespace) { 32 | event = this.namespace + '.' + event; 33 | } 34 | 35 | return event.replace(/\./g, '\\'); 36 | } 37 | 38 | /** 39 | * Set the event namespace. 40 | * 41 | * @param {string} value 42 | * @return {void} 43 | */ 44 | setNamespace(value: string | boolean): void { 45 | this.namespace = value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './event-formatter' 2 | -------------------------------------------------------------------------------- /tests/util/event-formatter.test.ts: -------------------------------------------------------------------------------- 1 | import { EventFormatter } from '../../src/util'; 2 | 3 | describe('EventFormatter', () => { 4 | let eventFormatter; 5 | 6 | beforeEach(() => { 7 | eventFormatter = new EventFormatter('App.Events'); 8 | }); 9 | 10 | test("prepends an event with a namespace and replaces dot separators with backslashes", () => { 11 | let formatted = eventFormatter.format("Users.UserCreated"); 12 | 13 | expect(formatted).toBe("App\\Events\\Users\\UserCreated"); 14 | }); 15 | 16 | test('does not prepend a namespace when an event starts with a dot', () => { 17 | let formatted = eventFormatter.format('.App\\Users\\UserCreated'); 18 | 19 | expect(formatted).toBe('App\\Users\\UserCreated'); 20 | }); 21 | 22 | test('does not prepend a namespace when an event starts with a backslash', () => { 23 | let formatted = eventFormatter.format('\\App\\Users\\UserCreated'); 24 | 25 | expect(formatted).toBe('App\\Users\\UserCreated'); 26 | }); 27 | 28 | test('does not replace dot separators when the event starts with a dot', () => { 29 | let formatted = eventFormatter.format('.users.created'); 30 | 31 | expect(formatted).toBe('users.created'); 32 | }); 33 | 34 | test('does not replace dot separators when the event starts with a backslash', () => { 35 | let formatted = eventFormatter.format('\\users.created'); 36 | 37 | expect(formatted).toBe('users.created'); 38 | }); 39 | 40 | test('does not prepend a namespace when none is set', () => { 41 | let eventFormatter = new EventFormatter(false); 42 | 43 | let formatted = eventFormatter.format('Users.UserCreated'); 44 | 45 | expect(formatted).toBe('Users\\UserCreated'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es6", 4 | "target": "es6", 5 | "moduleResolution": "node", 6 | "removeComments": true, 7 | "isolatedModules": false, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "noImplicitAny": false, 12 | "noImplicitUseStrict": false, 13 | "noLib": false, 14 | "preserveConstEnums": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "pretty": true, 17 | "outDir": "build" 18 | }, 19 | "exclude": [ 20 | "node_modules/*", 21 | "typings/browser", 22 | "typings/browser.d.ts" 23 | ], 24 | "files": [ 25 | "typings/index.d.ts", 26 | "src/echo.ts" 27 | ], 28 | "compileOnSave": false, 29 | "buildOnSave": false, 30 | "atom": { 31 | "rewriteTsconfig": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare let Pusher: any; 2 | declare let io: any; 3 | declare let Vue: any; 4 | declare let axios: any; 5 | declare let jQuery: any; 6 | --------------------------------------------------------------------------------