chancezeus / angular-laravel-echo

A Angular service for working with laravel-echo
MIT License
19 stars 21 forks source link

Error on Angular 7.3.3 #11

Open dkeepersun opened 5 years ago

dkeepersun commented 5 years ago

node:v11.10.0 typescript:

image

image

chancezeus commented 5 years ago

Laravel Echo itself does not provide a ".d.ts" file, I created one manually which is available on git repo and within the installed package inside node_modules. You should be able to copy that into the (root of the) src folder (or typings folder if using a separate typings folder).

The Echo typings rely on @types/socket.io-client and @types/pusher-js. I have (as of yet) been unable to (properly) distribute these typings in a way that uses them automatically (and I'm no typings wizard in that regard) so if anyone can have a look at them and fix them where needed/make it possible for them to be automatically used without breaking other parts it would be much appreciated.

mmschuler commented 5 years ago

here you go.

laravel-echo.d.ts - serveral changes

import * as pusher from 'pusher-js';
import {Pusher} from 'pusher-js';
import * as io from 'socket.io-client';

export declare namespace Echo {
  export interface EchoStatic {
    /**
     * The broadcasting connector.
     */
    connector: Echo.PusherConnector | Echo.SocketIoConnector | Echo.NullConnector;

    /**
     * The echo options.
     */
    options: Echo.Config;

    /**
     * Create a new class instance.
     *
     * @param {Echo.Config} options
     * @returns {EchoStatic}
     */
    new(options: Echo.Config): EchoStatic;

    /**
     * Register a Vue HTTP interceptor to add the X-Socket-ID header.
     */
    registerVueRequestInterceptor(): void;

    /**
     * Register an Axios HTTP interceptor to add the X-Socket-ID header.
     */
    registerAxiosRequestInterceptor(): void;

    /**
     * Register jQuery AjaxSetup to add the X-Socket-ID header.
     */
    registerjQueryAjaxSetup(): void;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} channel
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.Channel}
     */
    listen(channel: string, event: string, callback: (event: any) => void): Echo.Channel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    channel(channel: string): Echo.Channel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    private(channel: string): Echo.PrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PresenceChannel}
     */
    join(channel: string): Echo.PresenceChannel;

    /**
     * Leave the given channel.
     *
     * @param {string} channel
     */
    leave(channel: string): void;

    /**
     * Get the Socket ID for the connection.
     *
     * @returns {string}
     */
    socketId(): string;

    /**
     * Disconnect from the Echo server.
     */
    disconnect(): void;
  }

  export interface Config {
    /**
     * Authentication information for the underlying connector
     */
    auth?: {
      /**
       * Headers to be included with the request
       */
      headers?: { [key: string]: any };
      params?: { [key: string]: any };
    };
    /**
     * The authentication endpoint
     */
    authEndpoint?: string;
    /**
     * The broadcaster to use
     */
    broadcaster?: 'socket.io' | 'pusher' | 'null';
    /**
     * The application CSRF token
     */
    csrfToken?: string | null;
    /**
     * The namespace to use for events
     */
    namespace?: string;
  }

  export interface NullConfig extends Config {
    broadcaster: 'null';
  }

  export interface PusherConfig extends Config, pusher.Config {
    broadcaster?: 'pusher';

    /**
     * A pusher client instance to use
     */
    client?: Pusher;
    /**
     * The pusher host to connect to
     */
    host?: string | null;
    /**
     * The pusher auth key
     */
    key?: string | null;
  }

  export interface SocketIoConfig extends Config, SocketIOClient.ConnectOpts {
    broadcaster: 'socket.io';

    /**
     * A reference to the socket.io client to use
     */
    client?: SocketIOClientStatic;

    /**
     * The url of the laravel echo server instance
     */
    host: string;
  }

  export interface Connector {
    /**
     * All of the subscribed channel names.
     */
    channels: any;

    /**
     * Connector options.
     */
    options: Config;

    /**
     * Create a new class instance.
     *
     * @param {Echo.Config} options
     * @returns {Echo.Connector}
     */
    (options: Config): Connector;

    /**
     * Create a fresh connection.
     */
    connect(): void;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: (event: any) => void): Channel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    channel(channel: string): Channel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PrivateChannel}
     */
    privateChannel(channel: string): PrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PresenceChannel}
     */
    presenceChannel(channel: string): PresenceChannel;

    /**
     * Leave the given channel.
     *
     * @param {string} channel
     */
    leave(channel: string): void;

    /**
     * Get the socket_id of the connection.
     *
     * @returns {string}
     */
    socketId(): string;

    /**
     * Disconnect from the Echo server.
     */
    disconnect(): void;
  }

  export interface NullConnector extends Connector {
    /**
     * Create a new class instance.
     *
     * @param {Echo.NullConfig} options
     * @returns {Echo.NullConnector}
     */
    (options: NullConfig): Connector;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: pusher.EventCallback): NullChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherChannel}
     */
    channel(name: string): NullChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPrivateChannel}
     */
    privateChannel(name: string): NullPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPresenceChannel}
     */
    presenceChannel(name: string): NullPresenceChannel;
  }

  export interface PusherConnector extends Connector {
    /**
     * The Pusher instance.
     */
    pusher: Pusher;

    /**
     * Create a new class instance.
     *
     * @param {Echo.PusherConfig} options
     * @returns {Echo.PusherConnector}
     */
    (options: PusherConfig): Connector;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: pusher.EventCallback): PusherChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherChannel}
     */
    channel(name: string): PusherChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPrivateChannel}
     */
    privateChannel(name: string): PusherPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPresenceChannel}
     */
    presenceChannel(name: string): PusherPresenceChannel;
  }

  export interface SocketIoConnector extends Connector {
    /**
     * The Socket.io connection instance.
     */
    socket: SocketIOClient.Socket;

    /**
     * Create a new class instance.
     *
     * @param {Echo.SocketIoConfig} options
     * @returns {Echo.SocketIoConnector}
     */
    (options: SocketIoConfig): Connector;

    /**
     * Get socket.io module from global scope or options.
     *
     * @returns {typeof io}
     */
    getSocketIO(): SocketIOClientStatic;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    listen(name: string, event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoChannel}
     */
    channel(name: string): SocketIoChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoPrivateChannel}
     */
    privateChannel(name: string): SocketIoPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoPresenceChannel}
     */
    presenceChannel(name: string): SocketIoPresenceChannel;
  }

  export interface Channel {
    /**
     * The name of the channel.
     */
    name: string;

    /**
     * Channel options.
     */
    options: any;

    /**
     * The event formatter.
     */
    eventFormatter: EventFormatter;

    /**
     * Listen for an event on the channel instance.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.Channel}
     */
    listen(event: string, callback: (event: any) => void): Channel;

    /**
     * Listen for a notification on the channel instance.
     *
     * @param {(notification: any) => void} callback
     * @returns {Echo.Channel}
     */
    notification(callback: (notification: any) => void): Channel;

    /**
     * Listen for a whisper event on the channel instance.
     *
     * @param {string} event
     * @param {(data: any) => void} callback
     * @returns {Echo.Channel}
     */
    listenForWhisper(event: string, callback: (data: any) => void): Channel;
  }

  export interface PrivateChannel extends Channel {
    /**
     * Trigger client event on the channel.
     *
     * @param {string} event
     * @param data
     * @returns {Echo.PrivateChannel}
     */
    whisper(event: string, data: any): PrivateChannel;
  }

  export interface PresenceChannel extends PrivateChannel {
    /**
     * Register a callback to be called anytime the member list changes.
     *
     * @param {(users: any[]) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    here(callback: (users: any[]) => void): PresenceChannel;

    /**
     * Listen for someone joining the channel.
     *
     * @param {(user: any) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    joining(callback: (user: any) => void): PresenceChannel;

    /**
     * Listen for someone leaving the channel.
     *
     * @param {(user: any) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    leaving(callback: (user: any) => void): PresenceChannel;
  }

  export interface NullChannel extends Channel {
    /**
     * Subscribe to a Null channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a Null channel.
     */
    unsubscribe(): void;

    /**
     * Stop listening for an event on the channel instance.
     *
     * @param {string} event
     * @returns {Echo.NullChannel}
     */
    stopListening(event: string): Channel;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {Null.EventCallback} callback
     * @returns {Echo.NullChannel}
     */
    on(event: string, callback: pusher.EventCallback): Channel;
  }

  export interface NullPrivateChannel extends NullChannel, PrivateChannel {
  }

  export interface NullPresenceChannel extends NullPrivateChannel, PresenceChannel {
  }

  export interface PusherChannel extends Channel {
    /**
     * The pusher client instance
     */
    pusher: Pusher;

    /**
     * The subscription of the channel.
     */
    subscription: pusher.Channel;

    /**
     * Create a new class instance.
     *
     * @param {pusher} pusher
     * @param {string} name
     * @param options
     * @returns {Echo.PusherChannel}
     */
    (pusher: Pusher, name: string, options: any): PusherChannel;

    /**
     * Subscribe to a Pusher channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a Pusher channel.
     */
    unsubscribe(): void;

    /**
     * Stop listening for an event on the channel instance.
     *
     * @param {string} event
     * @returns {Echo.PusherChannel}
     */
    stopListening(event: string): Channel;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    on(event: string, callback: pusher.EventCallback): Channel;
  }

  export interface PusherPrivateChannel extends PusherChannel, PrivateChannel {
  }

  export interface PusherPresenceChannel extends PusherPrivateChannel, PresenceChannel {
  }

  export interface SocketIoChannel extends Channel {

    /**
     * The SocketIo client instance
     */
    socket: typeof io;

    /**
     * The event callbacks applied to the channel.
     */
    events: any;

    /**
     * Create a new class instance.
     *
     * @param {io} socket
     * @param {string} name
     * @param options
     * @returns {Echo.SocketIoChannel}
     */
    (socket: typeof io, name: string, options: any): SocketIoChannel;

    /**
     * Subscribe to a SocketIo channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a SocketIo channel.
     */
    unsubscribe(): void;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    on(event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Attach a 'reconnect' listener and bind the event.
     */
    configureReconnector(): void;

    /**
     * Bind the channel's socket to an event and store the callback.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    bind(event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Unbind the channel's socket from all stored event callbacks.
     */
    unbind(): void;
  }

  export interface SocketIoPrivateChannel extends SocketIoChannel, PrivateChannel {
  }

  export interface SocketIoPresenceChannel extends SocketIoPrivateChannel, PresenceChannel {
  }

  export interface EventFormatter {
    /**
     * Event namespace.
     */
    namespace: string | boolean;

    /**
     * Create a new class instance.
     *
     * @param {string | boolean} namespace
     * @returns {Echo.EventFormatter}
     */
    (namespace: string | boolean): EventFormatter;

    /**
     * Format the given event name.
     *
     * @param {string} event
     * @returns {string}
     */
    format(event: string): string;

    /**
     * Set the event namespace.
     *
     * @param {string | boolean} value
     */
    setNamespace(value: string | boolean): void;
  }
}

lib.service.d.ts - i only changed the import - but I'm not 100 sure anymore.. ^^

import { InjectionToken, NgZone } from '@angular/core';
import { Observable } from 'rxjs';
import {Echo} from '../../laravel-echo';
/**
 * The token used to inject the config in Angular's DI system
 */
export declare const ECHO_CONFIG: InjectionToken<EchoConfig>;
/**
 * Service configuration
 */
export interface EchoConfig {
    /**
     * The name of the user model of the backend application
     */
    userModel: string;
    /**
     * The name of the namespace for notifications of the backend application
     */
    notificationNamespace: string | null;
    /**
     * Laravel Echo configuration
     */
    options: Echo.Config;
}
export interface NullEchoConfig extends EchoConfig {
    /**
     * Laravel Echo configuration
     */
    options: Echo.NullConfig;
}
export interface PusherEchoConfig extends EchoConfig {
    /**
     * Laravel Echo configuration
     */
    options: Echo.PusherConfig;
}
export interface SocketIoEchoConfig extends EchoConfig {
    /**
     * Laravel Echo configuration
     */
    options: Echo.SocketIoConfig;
}
/**
 * Possible channel types
 */
export declare type ChannelType = 'public' | 'presence' | 'private';
/**
 * Raw events from the underlying connection
 */
export interface ConnectionEvent {
    /**
     * The event type
     */
    type: string;
}
/**
 * Null connection events
 */
export interface NullConnectionEvent extends ConnectionEvent {
    /**
     * The event type
     */
    type: 'connected';
}
/**
 * Socket.io connection events
 */
export interface SocketIoConnectionEvent extends ConnectionEvent {
    /**
     * The event type
     */
    type: 'connect' | 'connect_error' | 'connect_timeout' | 'error' | 'disconnect' | 'reconnect' | 'reconnect_attempt' | 'reconnecting' | 'reconnect_error' | 'reconnect_failed' | 'ping' | 'pong';
}
/**
 * Socket.io disconnect event
 */
export interface SocketIoConnectionDisconnectEvent extends SocketIoConnectionEvent {
    /**
     * The event type
     */
    type: 'disconnect';
    /**
     * The reason, either "io server disconnect" or "io client disconnect"
     */
    reason: string;
}
/**
 * Socket.io (*_)error event
 */
export interface SocketIoConnectionErrorEvent extends SocketIoConnectionEvent {
    /**
     * The event type
     */
    type: 'connect_error' | 'error' | 'reconnect_error';
    /**
     * The error object
     */
    error: any;
}
/**
 * Socket.io reconnect event
 */
export interface SocketIoConnectionReconnectEvent extends SocketIoConnectionEvent {
    /**
     * The event type
     */
    type: 'reconnect' | 'reconnect_attempt' | 'reconnecting';
    /**
     * The current attempt count
     */
    attemptNumber: number;
}
/**
 * Socket.io timeout event
 */
export interface SocketIoConnectionTimeoutEvent extends SocketIoConnectionEvent {
    /**
     * The event type
     */
    type: 'connect_timeout';
    /**
     * The timeout
     */
    timeout: number;
}
/**
 * Socket.io pong event
 */
export interface SocketIoConnectionPongEvent extends SocketIoConnectionEvent {
    /**
     * The event type
     */
    type: 'pong';
    /**
     * The latency
     */
    latency: number;
}
/**
 * All Socket.io events
 */
export declare type SocketIoConnectionEvents = SocketIoConnectionEvent | SocketIoConnectionDisconnectEvent | SocketIoConnectionErrorEvent | SocketIoConnectionReconnectEvent | SocketIoConnectionTimeoutEvent | SocketIoConnectionPongEvent;
/**
 * Pusher connection states
 */
export declare type PusherStates = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed' | 'disconnected';
/**
 * Pusher connection events
 */
export interface PusherConnectionEvent {
    type: PusherStates | 'connecting_in';
}
/**
 * Pusher connecting in event
 */
export interface PusherConnectionConnectingInEvent extends PusherConnectionEvent {
    type: 'connecting_in';
    delay: number;
}
/**
 * All pusher events
 */
export declare type PusherConnectionEvents = PusherConnectionEvent | PusherConnectionConnectingInEvent;
/**
 * All connection events
 */
export declare type ConnectionEvents = NullConnectionEvent | SocketIoConnectionEvents | PusherConnectionEvents;
/**
 * The service class, use this as something like
 * (or use the [[AngularLaravelEchoModule.forRoot]] method):
 *
 * ```js
 * export const echoConfig: SocketIoEchoConfig = {
 *   userModel: 'App.User',
 *   notificationNamespace: 'App\\Notifications',
 *   options: {
 *     broadcaster: 'socket.io',
 *     host: window.location.hostname + ':6001'
 *   }
 * }
 *
 * @NgModule({
 *   ...
 *   providers: [
 *     ...
 *     EchoService,
 *     { provide: ECHO_CONFIG, useValue: echoConfig }
 *     ...
 *   ]
 *   ...
 * })
 * ```
 *
 * and import it in your component as
 *
 * ```js
 * @Component({
 * ...
 * })
 * export class ExampleComponent {
 *   constructor(echoService: EchoService) {
 *   }
 * }
 * ```
 */
export declare class EchoService {
    private ngZone;
    private config;
    private readonly _echo;
    private readonly options;
    private readonly typeFormatter;
    private readonly connected$;
    private readonly connectionState$;
    private readonly channels;
    private readonly notificationListeners;
    private userChannelName;
    /**
     * Create a new service instance.
     *
     * @param ngZone NgZone instance
     * @param config Service configuration
     */
    constructor(ngZone: NgZone, config: EchoConfig);
    /**
     * Is the socket currently connected
     */
    readonly connected: boolean;
    /**
     * Observable of connection state changes, emits true when connected and false when disconnected
     */
    readonly connectionState: Observable<boolean>;
    /**
     * Observable of raw events of the underlying connection
     */
    readonly rawConnectionState: Observable<ConnectionEvents>;
    /**
     * The echo instance, can be used to implement any custom requirements outside of this service (remember to include NgZone.run calls)
     */
    readonly echo: Echo.EchoStatic;
    /**
     * The socket ID
     */
    readonly socketId: string;
    /**
     * Gets the named and optionally typed channel from the channels array if it exists
     *
     * @param name The name of the channel
     * @param type The type of channel to lookup
     * @returns The channel if found or null
     */
    private getChannelFromArray;
    /**
     * Gets the named and optionally typed channel from the channels array or throws if it does not exist
     *
     * @param name The name of the channel
     * @param type The type of channel to lookup
     * @returns The channel
     */
    private requireChannelFromArray;
    /**
     * Fetch or create a public channel
     *
     * @param name The name of the channel to join
     * @returns The fetched or created channel
     */
    private publicChannel;
    /**
     * Fetch or create a presence channel and subscribe to the presence events
     *
     * @param name The name of the channel to join
     * @returns The fetched or created channel
     */
    private presenceChannel;
    /**
     * Fetch or create a private channel
     *
     * @param name The name of the channel to join
     * @returns The fetched or created channel
     */
    private privateChannel;
    /**
     * Set authentication data and connect to and start listening for notifications on the users private channel
     *
     * @param headers Authentication headers to send when talking to the service
     * @param userId The current user's id
     * @returns The instance for chaining
     */
    login(headers: {
        [key: string]: string;
    }, userId: string | number): EchoService;
    /**
     * Clear authentication data and close any presence or private channels.
     *
     * @returns The instance for chaining
     */
    logout(): EchoService;
    /**
     * Join a channel of specified name and type.
     *
     * @param name The name of the channel to join
     * @param type The type of channel to join
     * @returns The instance for chaining
     */
    join(name: string, type: ChannelType): EchoService;
    /**
     * Leave a channel of the specified name.
     *
     * @param name The name of the channel to leave
     * @returns The instance for chaining
     */
    leave(name: string): EchoService;
    /**
     * Listen for events on the specified channel.
     *
     * @param name The name of the channel
     * @param event The name of the event
     * @returns An observable that emits the event data of the specified event when it arrives
     */
    listen(name: string, event: string): Observable<any>;
    /**
     * Listen for client sent events (whispers) on the specified private or presence channel channel.
     *
     * @param name The name of the channel
     * @param event The name of the event
     * @returns An observable that emits the whisper data of the specified event when it arrives
     */
    listenForWhisper(name: string, event: string): Observable<any>;
    /**
     * Listen for notifications on the users private channel.
     *
     * @param type The type of notification to listen for or `*` for any
     * @param name Optional a different channel to receive notifications on
     * @returns An observable that emits the notification of the specified type when it arrives
     */
    notification(type: string, name?: string): Observable<any>;
    /**
     * Listen for users joining the specified presence channel.
     *
     * @param name The name of the channel
     * @returns An observable that emits the user when he joins the specified channel
     */
    joining(name: string): Observable<any>;
    /**
     * Listen for users leaving the specified presence channel.
     *
     * @param name The name of the channel
     * @returns An observable that emits the user when he leaves the specified channel
     */
    leaving(name: string): Observable<any>;
    /**
     * Listen for user list updates on the specified presence channel.
     *
     * @param name The name of the channel
     * @returns An observable that emits the initial user list as soon as it's available
     */
    users(name: string): Observable<any[]>;
    /**
     * Send a client event to the specified presence or private channel (whisper).
     *
     * @param name The name of the channel
     * @param event The name of the event
     * @param data The payload for the event
     * @returns The instance for chaining
     */
    whisper(name: string, event: string, data: any): EchoService;
}
mmschuler commented 5 years ago

i tried to make a pull request..

here are the actual files:

lib.service.ts

import {Inject, Injectable, InjectionToken, NgZone} from '@angular/core';
import {Observable, of, ReplaySubject, Subject, throwError} from 'rxjs';
import {distinctUntilChanged, map, shareReplay, startWith} from 'rxjs/operators';
import {Echo} from '../../laravel-echo';
import * as io from 'socket.io-client';

/**
 * The token used to inject the config in Angular's DI system
 */
export const ECHO_CONFIG = new InjectionToken<EchoConfig>('echo.config');

/**
 * Service configuration
 */
export interface EchoConfig {
  /**
   * The name of the user model of the backend application
   */
  userModel: string;
  /**
   * The name of the namespace for notifications of the backend application
   */
  notificationNamespace: string | null;
  /**
   * Laravel Echo configuration
   */
  options: Echo.Config;
}

export interface NullEchoConfig extends EchoConfig {
  /**
   * Laravel Echo configuration
   */
  options: Echo.NullConfig;
}

export interface PusherEchoConfig extends EchoConfig {
  /**
   * Laravel Echo configuration
   */
  options: Echo.PusherConfig;
}

export interface SocketIoEchoConfig extends EchoConfig {
  /**
   * Laravel Echo configuration
   */
  options: Echo.SocketIoConfig;
}

/**
 * Possible channel types
 */
export type ChannelType = 'public' | 'presence' | 'private';

/**
 * Raw events from the underlying connection
 */
export interface ConnectionEvent {
  /**
   * The event type
   */
  type: string;
}

/**
 * Null connection events
 */
export interface NullConnectionEvent extends ConnectionEvent {
  /**
   * The event type
   */
  type: 'connected'
}

/**
 * Socket.io connection events
 */
export interface SocketIoConnectionEvent extends ConnectionEvent {
  /**
   * The event type
   */
  type: 'connect' |
    'connect_error' |
    'connect_timeout' |
    'error' |
    'disconnect' |
    'reconnect' |
    'reconnect_attempt' |
    'reconnecting' |
    'reconnect_error' |
    'reconnect_failed' |
    'ping' |
    'pong';
}

/**
 * Socket.io disconnect event
 */
export interface SocketIoConnectionDisconnectEvent extends SocketIoConnectionEvent {
  /**
   * The event type
   */
  type: 'disconnect';
  /**
   * The reason, either "io server disconnect" or "io client disconnect"
   */
  reason: string;
}

/**
 * Socket.io (*_)error event
 */
export interface SocketIoConnectionErrorEvent extends SocketIoConnectionEvent {
  /**
   * The event type
   */
  type: 'connect_error' | 'error' | 'reconnect_error';
  /**
   * The error object
   */
  error: any;
}

/**
 * Socket.io reconnect event
 */
export interface SocketIoConnectionReconnectEvent extends SocketIoConnectionEvent {
  /**
   * The event type
   */
  type: 'reconnect' | 'reconnect_attempt' | 'reconnecting';
  /**
   * The current attempt count
   */
  attemptNumber: number;
}

/**
 * Socket.io timeout event
 */
export interface SocketIoConnectionTimeoutEvent extends SocketIoConnectionEvent {
  /**
   * The event type
   */
  type: 'connect_timeout';
  /**
   * The timeout
   */
  timeout: number;
}

/**
 * Socket.io pong event
 */
export interface SocketIoConnectionPongEvent extends SocketIoConnectionEvent {
  /**
   * The event type
   */
  type: 'pong';
  /**
   * The latency
   */
  latency: number;
}

/**
 * All Socket.io events
 */
export type SocketIoConnectionEvents = SocketIoConnectionEvent |
  SocketIoConnectionDisconnectEvent |
  SocketIoConnectionErrorEvent |
  SocketIoConnectionReconnectEvent |
  SocketIoConnectionTimeoutEvent |
  SocketIoConnectionPongEvent;

/**
 * Pusher connection states
 */
export type PusherStates = 'initialized' |
  'connecting' |
  'connected' |
  'unavailable' |
  'failed' |
  'disconnected';

/**
 * Pusher connection events
 */
export interface PusherConnectionEvent {
  type: PusherStates | 'connecting_in';
}

/**
 * Pusher connecting in event
 */
export interface PusherConnectionConnectingInEvent extends PusherConnectionEvent {
  type: 'connecting_in';
  delay: number;
}

/**
 * All pusher events
 */
export type PusherConnectionEvents = PusherConnectionEvent | PusherConnectionConnectingInEvent;

/**
 * All connection events
 */
export type ConnectionEvents = NullConnectionEvent | SocketIoConnectionEvents | PusherConnectionEvents;

/**
 * @hidden
 */
interface Channel {
  name: string;
  channel: Echo.Channel;
  type: ChannelType;
  listeners: {
    [key: string]: Subject<any>;
  };
  notificationListeners?: {
    [key: string]: Subject<any>;
  };
  users?: any[] | null;
}

/**
 * @hidden
 */
class TypeFormatter {
  /**
   * The namespace of the notifications.
   */
  private namespace: string | null = null;

  /**
   * Constructs a new formatter instance
   *
   * @param namespace The namespace of the notifications.
   */
  constructor(namespace: string | null) {
    this.setNamespace(namespace);
  }

  /**
   * Formats the supplied type
   *
   * @param notificationType The FQN of the notification class
   * @returns The optimized type
   */
  format(notificationType: string): string {
    if (!this.namespace) {
      return notificationType;
    }

    if (notificationType.indexOf(this.namespace) === 0) {
      return notificationType.substr(this.namespace.length);
    }

    return notificationType;
  }

  /**
   * Sets the namespace
   *
   * @param namespace The namespace of the notifications.
   * @returns The instance for chaining
   */
  setNamespace(namespace: string | null): TypeFormatter {
    this.namespace = namespace;

    return this;
  }
}

/**
 * The service class, use this as something like
 * (or use the [[AngularLaravelEchoModule.forRoot]] method):
 *
 * ```js
 * export const echoConfig: SocketIoEchoConfig = {
 *   userModel: 'App.User',
 *   notificationNamespace: 'App\\Notifications',
 *   options: {
 *     broadcaster: 'socket.io',
 *     host: window.location.hostname + ':6001'
 *   }
 * }
 *
 * @NgModule({
 *   ...
 *   providers: [
 *     ...
 *     EchoService,
 *     { provide: ECHO_CONFIG, useValue: echoConfig }
 *     ...
 *   ]
 *   ...
 * })
 * ```
 *
 * and import it in your component as
 *
 * ```js
 * @Component({
 * ...
 * })
 * export class ExampleComponent {
 *   constructor(echoService: EchoService) {
 *   }
 * }
 * ```
 */
@Injectable()
export class EchoService {
  private readonly _echo: Echo.EchoStatic;
  private readonly options: Echo.Config;
  private readonly typeFormatter: TypeFormatter;
  private readonly connected$: Observable<boolean>;
  private readonly connectionState$: Observable<ConnectionEvents>;

  private readonly channels: Array<Channel> = [];
  private readonly notificationListeners: { [key: string]: Subject<any> } = {};

  private userChannelName: string | null = null;

  /**
   * Create a new service instance.
   *
   * @param ngZone NgZone instance
   * @param config Service configuration
   */
  constructor(private ngZone: NgZone,
              @Inject(ECHO_CONFIG) private config: EchoConfig) {
    let options = Object.assign({}, config.options);
    if (options.broadcaster === 'socket.io') {
      options = Object.assign({
        client: io
      }, options);
    }

    this._echo = {options: options} as Echo.EchoStatic;

    this.options = this.echo.connector.options;

    this.typeFormatter = new TypeFormatter(config.notificationNamespace);

    switch (options.broadcaster) {
      case 'null':
        this.connectionState$ = of<NullConnectionEvent>({type: 'connected'});
        break;
      case 'socket.io':
        this.connectionState$ = new Observable<SocketIoConnectionEvents>((subscriber: any) => {
          const socket = (<Echo.SocketIoConnector>this._echo.connector).socket;

          const handleConnect = () => this.ngZone.run(
            () => subscriber.next({type: 'connect'})
          );

          const handleConnectError = (error: any) => this.ngZone.run(
            () => subscriber.next({type: 'connect_error', error})
          );

          const handleConnectTimeout = (timeout: number) => this.ngZone.run(
            () => subscriber.next({type: 'connect_timeout', timeout})
          );

          const handleError = (error: any) => this.ngZone.run(
            () => subscriber.next({type: 'error', error})
          );

          const handleDisconnect = (reason: string) => this.ngZone.run(
            () => subscriber.next({type: 'disconnect', reason})
          );

          const handleReconnect = (attemptNumber: number) => this.ngZone.run(
            () => subscriber.next({type: 'reconnect', attemptNumber})
          );

          const handleReconnectAttempt = (attemptNumber: number) => this.ngZone.run(
            () => subscriber.next({type: 'reconnect_attempt', attemptNumber})
          );

          const handleReconnecting = (attemptNumber: number) => this.ngZone.run(
            () => subscriber.next({type: 'reconnecting', attemptNumber})
          );

          const handleReconnectError = (error: any) => this.ngZone.run(
            () => subscriber.next({type: 'reconnect_error', error})
          );

          const handleReconnectFailed = () => this.ngZone.run(
            () => subscriber.next({type: 'reconnect_failed'})
          );

          const handlePing = () => this.ngZone.run(
            () => subscriber.next({type: 'ping'})
          );

          const handlePong = (latency: number) => this.ngZone.run(
            () => subscriber.next({type: 'pong', latency})
          );

          socket.on('connect', handleConnect);
          socket.on('connect_error', handleConnectError);
          socket.on('connect_timeout', handleConnectTimeout);
          socket.on('error', handleError);
          socket.on('disconnect', handleDisconnect);
          socket.on('reconnect', handleReconnect);
          socket.on('reconnect_attempt', handleReconnectAttempt);
          socket.on('reconnecting', handleReconnecting);
          socket.on('reconnect_error', handleReconnectError);
          socket.on('reconnect_failed', handleReconnectFailed);
          socket.on('ping', handlePing);
          socket.on('pong', handlePong);

          return () => {
            socket.off('connect', handleConnect);
            socket.off('connect_error', handleConnectError);
            socket.off('connect_timeout', handleConnectTimeout);
            socket.off('error', handleError);
            socket.off('disconnect', handleDisconnect);
            socket.off('reconnect', handleReconnect);
            socket.off('reconnect_attempt', handleReconnectAttempt);
            socket.off('reconnecting', handleReconnecting);
            socket.off('reconnect_error', handleReconnectError);
            socket.off('reconnect_failed', handleReconnectFailed);
            socket.off('ping', handlePing);
            socket.off('pong', handlePong);
          };
        }).pipe(shareReplay(1));
        break;
      case 'pusher':
        this.connectionState$ = new Observable<PusherConnectionEvents>((subscriber: any) => {
          const socket = (<Echo.PusherConnector>this._echo.connector).pusher.connection;

          const handleStateChange = ({current}: { current: PusherStates }) => this.ngZone.run(
            () => subscriber.next({type: current})
          );

          const handleConnectingIn = (delay: number) => this.ngZone.run(
            () => subscriber.next({type: 'connecting_in', delay})
          );

          socket.bind('state_change', handleStateChange);
          socket.bind('connecting_in', handleConnectingIn);

          return () => {
            socket.unbind('state_change', handleStateChange);
            socket.unbind('connecting_in', handleConnectingIn);
          };
        }).pipe(shareReplay(1));
        break;
      default:
        this.connectionState$ = throwError(new Error('unsupported'));
        break;
    }

    this.connected$ = (<Observable<SocketIoConnectionEvents>>this.connectionState$).pipe(
      map(() => this.connected),
      startWith(this.connected),
      distinctUntilChanged(),
      shareReplay(1)
    );
  }

  /**
   * Is the socket currently connected
   */
  get connected(): boolean {
    if (this.options.broadcaster === 'null') {
      // Null broadcaster is always connected
      return true;
    }

    if (this.options.broadcaster === 'pusher') {
      return (<Echo.PusherConnector>this._echo.connector).pusher.connection.state === 'connected';
    }

    return (<Echo.SocketIoConnector>this._echo.connector).socket.connected;
  }

  /**
   * Observable of connection state changes, emits true when connected and false when disconnected
   */
  get connectionState(): Observable<boolean> {
    return this.connected$;
  }

  /**
   * Observable of raw events of the underlying connection
   */
  get rawConnectionState(): Observable<ConnectionEvents> {
    return this.connectionState$;
  }

  /**
   * The echo instance, can be used to implement any custom requirements outside of this service (remember to include NgZone.run calls)
   */
  get echo(): Echo.EchoStatic {
    return this._echo;
  }

  /**
   * The socket ID
   */
  get socketId(): string {
    return this.echo.socketId();
  }

  /**
   * Gets the named and optionally typed channel from the channels array if it exists
   *
   * @param name The name of the channel
   * @param type The type of channel to lookup
   * @returns The channel if found or null
   */
  private getChannelFromArray(name: string, type: ChannelType | null = null): Channel | null {
    const channel = this.channels.find(c => c.name === name);
    if (channel) {
      if (type && channel.type !== type) {
        throw new Error(`Channel ${name} is not a ${type} channel`);
      }

      return channel;
    }

    return null;
  }

  /**
   * Gets the named and optionally typed channel from the channels array or throws if it does not exist
   *
   * @param name The name of the channel
   * @param type The type of channel to lookup
   * @returns The channel
   */
  private requireChannelFromArray(name: string, type: ChannelType | null = null): Channel {
    const channel = this.getChannelFromArray(name, type);
    if (!channel) {
      if (type) {
        throw new Error(`${type[0].toUpperCase()}${type.substr(1)} channel ${name} does not exist`);
      }

      throw new Error(`Channel ${name} does not exist`);
    }

    return channel;
  }

  /**
   * Fetch or create a public channel
   *
   * @param name The name of the channel to join
   * @returns The fetched or created channel
   */
  private publicChannel(name: string): Echo.Channel {
    let channel = this.getChannelFromArray(name, 'public');
    if (channel) {
      return channel.channel;
    }

    const echoChannel = this.echo.channel(name);

    channel = {
      name,
      channel: echoChannel,
      type: 'public',
      listeners: {},
    };

    this.channels.push(channel);

    return echoChannel;
  }

  /**
   * Fetch or create a presence channel and subscribe to the presence events
   *
   * @param name The name of the channel to join
   * @returns The fetched or created channel
   */
  private presenceChannel(name: string): Echo.PresenceChannel {
    let channel = this.getChannelFromArray(name, 'presence');
    if (channel) {
      return channel.channel as Echo.PresenceChannel;
    }

    const echoChannel = this.echo.join(name);

    channel = {
      name,
      channel: echoChannel,
      type: 'presence',
      listeners: {},
      users: null,
    };

    this.channels.push(channel);

    echoChannel.here((users: any[]) => {
      this.ngZone.run(() => {
        if (channel) {
          channel.users = users;

          if (channel.listeners['_users_']) {
            channel.listeners['_users_'].next(JSON.parse(JSON.stringify(users)));
          }
        }
      });
    });

    echoChannel.joining((user: any) => {
      this.ngZone.run(() => {
        if (channel) {
          channel.users = channel.users || [];
          channel.users.push(user);

          if (channel.listeners['_joining_']) {
            channel.listeners['_joining_'].next(JSON.parse(JSON.stringify(user)));
          }
        }
      });
    });

    echoChannel.leaving((user: any) => {
      this.ngZone.run(() => {
        if (channel) {
          channel.users = channel.users || [];

          const existing = channel.users.find(e => e === user);
          if (existing) {
            const index = channel.users.indexOf(existing);

            if (index !== -1) {
              channel.users.splice(index, 1);
            }
          }

          if (channel.listeners['_leaving_']) {
            channel.listeners['_leaving_'].next(JSON.parse(JSON.stringify(user)));
          }
        }
      });
    });

    return echoChannel;
  }

  /**
   * Fetch or create a private channel
   *
   * @param name The name of the channel to join
   * @returns The fetched or created channel
   */
  private privateChannel(name: string): Echo.PrivateChannel {
    let channel = this.getChannelFromArray(name, 'private');
    if (channel) {
      return channel.channel as Echo.PrivateChannel;
    }

    const echoChannel = this.echo.private(name);

    channel = {
      name,
      channel: echoChannel,
      type: 'private',
      listeners: {},
    };

    this.channels.push(channel);

    return echoChannel;
  }

  /**
   * Set authentication data and connect to and start listening for notifications on the users private channel
   *
   * @param headers Authentication headers to send when talking to the service
   * @param userId The current user's id
   * @returns The instance for chaining
   */
  login(headers: { [key: string]: string }, userId: string | number): EchoService {
    const newChannelName = `${this.config.userModel.replace('\\', '.')}.${userId}`;

    if (this.userChannelName && this.userChannelName !== newChannelName) {
      this.logout();
    }

    this.options.auth = this.options.auth || {};
    this.options.auth.headers = Object.assign({}, headers);

    if (this.options.broadcaster === 'pusher') {
      const connector = (<Echo.PusherConnector>this._echo.connector);

      if (connector.pusher.config.auth !== this.options.auth) {
        connector.pusher.config.auth = this.options.auth;
      }
    }

    if (this.userChannelName !== newChannelName) {
      this.userChannelName = newChannelName;

      this.privateChannel(newChannelName).notification((notification: any) => {
        const type = this.typeFormatter.format(notification.type);

        if (this.notificationListeners[type]) {
          this.ngZone.run(() => this.notificationListeners[type].next(notification));
        }

        if (this.notificationListeners['*']) {
          this.ngZone.run(() => this.notificationListeners['*'].next(notification));
        }
      });
    }

    return this;
  }

  /**
   * Clear authentication data and close any presence or private channels.
   *
   * @returns The instance for chaining
   */
  logout(): EchoService {
    this.channels
      .filter(channel => channel.type !== 'public')
      .forEach(channel => this.leave(channel.name));

    this.options.auth = this.options.auth || {};
    this.options.auth.headers = {};

    return this;
  }

  /**
   * Join a channel of specified name and type.
   *
   * @param name The name of the channel to join
   * @param type The type of channel to join
   * @returns The instance for chaining
   */
  join(name: string, type: ChannelType): EchoService {
    switch (type) {
      case 'public':
        this.publicChannel(name);
        break;
      case 'presence':
        this.presenceChannel(name);
        break;
      case 'private':
        this.privateChannel(name);
        break;
    }

    return this;
  }

  /**
   * Leave a channel of the specified name.
   *
   * @param name The name of the channel to leave
   * @returns The instance for chaining
   */
  leave(name: string): EchoService {
    const channel = this.getChannelFromArray(name);
    if (channel) {
      this.echo.leave(name);

      Object.keys(channel.listeners).forEach(key => channel.listeners[key].complete());

      if (channel.notificationListeners) {
        Object.keys(channel.notificationListeners).forEach(
          key => channel.notificationListeners && channel.notificationListeners[key].complete()
        );
      }

      const index = this.channels.indexOf(channel);
      if (index !== -1) {
        this.channels.splice(index, 1);
      }
    }

    return this;
  }

  /**
   * Listen for events on the specified channel.
   *
   * @param name The name of the channel
   * @param event The name of the event
   * @returns An observable that emits the event data of the specified event when it arrives
   */
  listen(name: string, event: string): Observable<any> {
    const channel = this.requireChannelFromArray(name);
    if (!channel.listeners[event]) {
      const listener = new Subject<any>();

      channel.channel.listen(event, (e: any) => this.ngZone.run(() => listener.next(e)));

      channel.listeners[event] = listener;
    }

    return channel.listeners[event].asObservable();
  }

  /**
   * Listen for client sent events (whispers) on the specified private or presence channel channel.
   *
   * @param name The name of the channel
   * @param event The name of the event
   * @returns An observable that emits the whisper data of the specified event when it arrives
   */
  listenForWhisper(name: string, event: string): Observable<any> {
    const channel = this.requireChannelFromArray(name);
    if (channel.type === 'public') {
      return throwError(new Error('Whisper is not available on public channels'));
    }

    if (!channel.listeners[`_whisper_${event}_`]) {
      const listener = new Subject<any>();

      channel.channel.listenForWhisper(event, (e: any) => this.ngZone.run(() => listener.next(e)));

      channel.listeners[`_whisper_${event}_`] = listener;
    }

    return channel.listeners[`_whisper_${event}_`].asObservable();
  }

  /**
   * Listen for notifications on the users private channel.
   *
   * @param type The type of notification to listen for or `*` for any
   * @param name Optional a different channel to receive notifications on
   * @returns An observable that emits the notification of the specified type when it arrives
   */
  notification(type: string, name?: string): Observable<any> {
    type = this.typeFormatter.format(type);

    if (name && name !== this.userChannelName) {
      const channel = this.requireChannelFromArray(name);

      if (!channel.notificationListeners) {
        channel.notificationListeners = {};

        channel.channel.notification((notification: any) => {
          const notificationType = this.typeFormatter.format(notification.type);

          if (channel.notificationListeners) {
            if (channel.notificationListeners[notificationType]) {
              this.ngZone.run(() => channel.notificationListeners && channel.notificationListeners[notificationType].next(notification));
            }

            if (channel.notificationListeners['*']) {
              this.ngZone.run(() => channel.notificationListeners && channel.notificationListeners['*'].next(notification));
            }
          }
        });
      }

      if (!channel.notificationListeners[type]) {
        channel.notificationListeners[type] = new Subject<any>();
      }

      return channel.notificationListeners[type].asObservable();
    }

    if (!this.notificationListeners[type]) {
      this.notificationListeners[type] = new Subject<any>();
    }

    return this.notificationListeners[type].asObservable();
  }

  /**
   * Listen for users joining the specified presence channel.
   *
   * @param name The name of the channel
   * @returns An observable that emits the user when he joins the specified channel
   */
  joining(name: string): Observable<any> {
    const channel = this.requireChannelFromArray(name, 'presence');

    if (!channel.listeners[`_joining_`]) {
      channel.listeners['_joining_'] = new Subject<any>();
    }

    return channel.listeners['_joining_'].asObservable();
  }

  /**
   * Listen for users leaving the specified presence channel.
   *
   * @param name The name of the channel
   * @returns An observable that emits the user when he leaves the specified channel
   */
  leaving(name: string): Observable<any> {
    const channel = this.requireChannelFromArray(name, 'presence');

    if (!channel.listeners[`_leaving_`]) {
      channel.listeners['_leaving_'] = new Subject<any>();
    }

    return channel.listeners['_leaving_'].asObservable();
  }

  /**
   * Listen for user list updates on the specified presence channel.
   *
   * @param name The name of the channel
   * @returns An observable that emits the initial user list as soon as it's available
   */
  users(name: string): Observable<any[]> {
    const channel = this.requireChannelFromArray(name, 'presence');

    if (!channel.listeners[`_users_`]) {
      channel.listeners['_users_'] = new ReplaySubject<any[]>(1);
    }

    return channel.listeners['_users_'].asObservable();
  }

  /**
   * Send a client event to the specified presence or private channel (whisper).
   *
   * @param name The name of the channel
   * @param event The name of the event
   * @param data The payload for the event
   * @returns The instance for chaining
   */
  whisper(name: string, event: string, data: any): EchoService {
    const channel = this.requireChannelFromArray(name);
    if (channel.type === 'public') {
      throw new Error('Whisper is not available on public channels');
    }

    const echoChannel = channel.channel as Echo.PrivateChannel;

    echoChannel.whisper(event, data);

    return this;
  }
}

laravel-echo.d.ts

import * as pusher from 'pusher-js';
import {Pusher} from 'pusher-js';
import * as io from 'socket.io-client';

export declare module Echo {
  export interface EchoStatic {
    /**
     * The broadcasting connector.
     */
    connector: Echo.PusherConnector | Echo.SocketIoConnector | Echo.NullConnector;

    /**
     * The echo options.
     */
    options: Echo.Config;

    /**
     * Create a new class instance.
     *
     * @param {Echo.Config} options
     * @returns {EchoStatic}
     */
    new(options: Echo.Config): EchoStatic;

    /**
     * Register a Vue HTTP interceptor to add the X-Socket-ID header.
     */
    registerVueRequestInterceptor(): void;

    /**
     * Register an Axios HTTP interceptor to add the X-Socket-ID header.
     */
    registerAxiosRequestInterceptor(): void;

    /**
     * Register jQuery AjaxSetup to add the X-Socket-ID header.
     */
    registerjQueryAjaxSetup(): void;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} channel
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.Channel}
     */
    listen(channel: string, event: string, callback: (event: any) => void): Echo.Channel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    channel(channel: string): Echo.Channel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    private(channel: string): Echo.PrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PresenceChannel}
     */
    join(channel: string): Echo.PresenceChannel;

    /**
     * Leave the given channel.
     *
     * @param {string} channel
     */
    leave(channel: string): void;

    /**
     * Get the Socket ID for the connection.
     *
     * @returns {string}
     */
    socketId(): string;

    /**
     * Disconnect from the Echo server.
     */
    disconnect(): void;
  }

  export interface Config {
    /**
     * Authentication information for the underlying connector
     */
    auth?: {
      /**
       * Headers to be included with the request
       */
      headers?: { [key: string]: any };
      params?: { [key: string]: any };
    };
    /**
     * The authentication endpoint
     */
    authEndpoint?: string;
    /**
     * The broadcaster to use
     */
    broadcaster?: 'socket.io' | 'pusher' | 'null';
    /**
     * The application CSRF token
     */
    csrfToken?: string | null;
    /**
     * The namespace to use for events
     */
    namespace?: string;
  }

  export interface NullConfig extends Config {
    broadcaster: 'null';
  }

  export interface PusherConfig extends Config, pusher.Config {
    broadcaster?: 'pusher';

    /**
     * A pusher client instance to use
     */
    client?: Pusher;
    /**
     * The pusher host to connect to
     */
    host?: string | null;
    /**
     * The pusher auth key
     */
    key?: string | null;
  }

  export interface SocketIoConfig extends Config, SocketIOClient.ConnectOpts {
    broadcaster: 'socket.io';

    /**
     * A reference to the socket.io client to use
     */
    client?: SocketIOClientStatic;

    /**
     * The url of the laravel echo server instance
     */
    host: string;
  }

  export interface Connector {
    /**
     * All of the subscribed channel names.
     */
    channels: any;

    /**
     * Connector options.
     */
    options: Config;

    /**
     * Create a new class instance.
     *
     * @param {Echo.Config} options
     * @returns {Echo.Connector}
     */
    (options: Config): Connector;

    /**
     * Create a fresh connection.
     */
    connect(): void;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: (event: any) => void): Channel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.Channel}
     */
    channel(channel: string): Channel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PrivateChannel}
     */
    privateChannel(channel: string): PrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} channel
     * @returns {Echo.PresenceChannel}
     */
    presenceChannel(channel: string): PresenceChannel;

    /**
     * Leave the given channel.
     *
     * @param {string} channel
     */
    leave(channel: string): void;

    /**
     * Get the socket_id of the connection.
     *
     * @returns {string}
     */
    socketId(): string;

    /**
     * Disconnect from the Echo server.
     */
    disconnect(): void;
  }

  export interface NullConnector extends Connector {
    /**
     * Create a new class instance.
     *
     * @param {Echo.NullConfig} options
     * @returns {Echo.NullConnector}
     */
    (options: NullConfig): Connector;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: pusher.EventCallback): NullChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherChannel}
     */
    channel(name: string): NullChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPrivateChannel}
     */
    privateChannel(name: string): NullPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPresenceChannel}
     */
    presenceChannel(name: string): NullPresenceChannel;
  }

  export interface PusherConnector extends Connector {
    /**
     * The Pusher instance.
     */
    pusher: Pusher;

    /**
     * Create a new class instance.
     *
     * @param {Echo.PusherConfig} options
     * @returns {Echo.PusherConnector}
     */
    (options: PusherConfig): Connector;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    listen(name: string, event: string, callback: pusher.EventCallback): PusherChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherChannel}
     */
    channel(name: string): PusherChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPrivateChannel}
     */
    privateChannel(name: string): PusherPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.PusherPresenceChannel}
     */
    presenceChannel(name: string): PusherPresenceChannel;
  }

  export interface SocketIoConnector extends Connector {
    /**
     * The Socket.io connection instance.
     */
    socket: SocketIOClient.Socket;

    /**
     * Create a new class instance.
     *
     * @param {Echo.SocketIoConfig} options
     * @returns {Echo.SocketIoConnector}
     */
    (options: SocketIoConfig): Connector;

    /**
     * Get socket.io module from global scope or options.
     *
     * @returns {typeof io}
     */
    getSocketIO(): SocketIOClientStatic;

    /**
     * Listen for an event on a channel instance.
     *
     * @param {string} name
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    listen(name: string, event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Get a channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoChannel}
     */
    channel(name: string): SocketIoChannel;

    /**
     * Get a private channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoPrivateChannel}
     */
    privateChannel(name: string): SocketIoPrivateChannel;

    /**
     * Get a presence channel instance by name.
     *
     * @param {string} name
     * @returns {Echo.SocketIoPresenceChannel}
     */
    presenceChannel(name: string): SocketIoPresenceChannel;
  }

  export interface Channel {
    /**
     * The name of the channel.
     */
    name: string;

    /**
     * Channel options.
     */
    options: any;

    /**
     * The event formatter.
     */
    eventFormatter: EventFormatter;

    /**
     * Listen for an event on the channel instance.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.Channel}
     */
    listen(event: string, callback: (event: any) => void): Channel;

    /**
     * Listen for a notification on the channel instance.
     *
     * @param {(notification: any) => void} callback
     * @returns {Echo.Channel}
     */
    notification(callback: (notification: any) => void): Channel;

    /**
     * Listen for a whisper event on the channel instance.
     *
     * @param {string} event
     * @param {(data: any) => void} callback
     * @returns {Echo.Channel}
     */
    listenForWhisper(event: string, callback: (data: any) => void): Channel;
  }

  export interface PrivateChannel extends Channel {
    /**
     * Trigger client event on the channel.
     *
     * @param {string} event
     * @param data
     * @returns {Echo.PrivateChannel}
     */
    whisper(event: string, data: any): PrivateChannel;
  }

  export interface PresenceChannel extends PrivateChannel {
    /**
     * Register a callback to be called anytime the member list changes.
     *
     * @param {(users: any[]) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    here(callback: (users: any[]) => void): PresenceChannel;

    /**
     * Listen for someone joining the channel.
     *
     * @param {(user: any) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    joining(callback: (user: any) => void): PresenceChannel;

    /**
     * Listen for someone leaving the channel.
     *
     * @param {(user: any) => void} callback
     * @returns {Echo.PresenceChannel}
     */
    leaving(callback: (user: any) => void): PresenceChannel;
  }

  export interface NullChannel extends Channel {
    /**
     * Subscribe to a Null channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a Null channel.
     */
    unsubscribe(): void;

    /**
     * Stop listening for an event on the channel instance.
     *
     * @param {string} event
     * @returns {Echo.NullChannel}
     */
    stopListening(event: string): Channel;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {Null.EventCallback} callback
     * @returns {Echo.NullChannel}
     */
    on(event: string, callback: pusher.EventCallback): Channel;
  }

  export interface NullPrivateChannel extends NullChannel, PrivateChannel {
  }

  export interface NullPresenceChannel extends NullPrivateChannel, PresenceChannel {
  }

  export interface PusherChannel extends Channel {
    /**
     * The pusher client instance
     */
    pusher: Pusher;

    /**
     * The subscription of the channel.
     */
    subscription: pusher.Channel;

    /**
     * Create a new class instance.
     *
     * @param {pusher} pusher
     * @param {string} name
     * @param options
     * @returns {Echo.PusherChannel}
     */
    (pusher: Pusher, name: string, options: any): PusherChannel;

    /**
     * Subscribe to a Pusher channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a Pusher channel.
     */
    unsubscribe(): void;

    /**
     * Stop listening for an event on the channel instance.
     *
     * @param {string} event
     * @returns {Echo.PusherChannel}
     */
    stopListening(event: string): Channel;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {pusher.EventCallback} callback
     * @returns {Echo.PusherChannel}
     */
    on(event: string, callback: pusher.EventCallback): Channel;
  }

  export interface PusherPrivateChannel extends PusherChannel, PrivateChannel {
  }

  export interface PusherPresenceChannel extends PusherPrivateChannel, PresenceChannel {
  }

  export interface SocketIoChannel extends Channel {

    /**
     * The SocketIo client instance
     */
    socket: typeof io;

    /**
     * The event callbacks applied to the channel.
     */
    events: any;

    /**
     * Create a new class instance.
     *
     * @param {io} socket
     * @param {string} name
     * @param options
     * @returns {Echo.SocketIoChannel}
     */
    (socket: typeof io, name: string, options: any): SocketIoChannel;

    /**
     * Subscribe to a SocketIo channel.
     */
    subscribe(): void;

    /**
     * Unsubscribe from a SocketIo channel.
     */
    unsubscribe(): void;

    /**
     * Bind a channel to an event.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    on(event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Attach a 'reconnect' listener and bind the event.
     */
    configureReconnector(): void;

    /**
     * Bind the channel's socket to an event and store the callback.
     *
     * @param {string} event
     * @param {(event: any) => void} callback
     * @returns {Echo.SocketIoChannel}
     */
    bind(event: string, callback: (event: any) => void): SocketIoChannel;

    /**
     * Unbind the channel's socket from all stored event callbacks.
     */
    unbind(): void;
  }

  export interface SocketIoPrivateChannel extends SocketIoChannel, PrivateChannel {
  }

  export interface SocketIoPresenceChannel extends SocketIoPrivateChannel, PresenceChannel {
  }

  export interface EventFormatter {
    /**
     * Event namespace.
     */
    namespace: string | boolean;

    /**
     * Create a new class instance.
     *
     * @param {string | boolean} namespace
     * @returns {Echo.EventFormatter}
     */
    (namespace: string | boolean): EventFormatter;

    /**
     * Format the given event name.
     *
     * @param {string} event
     * @returns {string}
     */
    format(event: string): string;

    /**
     * Set the event namespace.
     *
     * @param {string | boolean} value
     */
    setNamespace(value: string | boolean): void;
  }
}
renatop7 commented 5 years ago

@chancezeus @mmschuler hey guys, I'm trying to make this work with my Angular 7 (7.2.0) project and I had no luck...

I tried creating the typings file in src folder like @chancezeus said but I have this errors:

ERROR in src/laravel-echo.d.ts(2,27): error TS1147: Import declarations in a namespace cannot reference a module. src/laravel-echo.d.ts(2,27): error TS2307: Cannot find module 'pusher-js'. src/laravel-echo.d.ts(3,24): error TS1147: Import declarations in a namespace cannot reference a module. src/laravel-echo.d.ts(3,24): error TS2307: Cannot find module 'pusher-js'. src/laravel-echo.d.ts(4,23): error TS1147: Import declarations in a namespace cannot reference a module. src/laravel-echo.d.ts(4,23): error TS2307: Cannot find module 'socket.io-client'. src/laravel-echo.d.ts(143,44): error TS2503: Cannot find namespace 'SocketIOClient'. src/laravel-echo.d.ts(149,14): error TS2304: Cannot find name 'SocketIOClientStatic'. src/laravel-echo.d.ts(332,13): error TS2503: Cannot find namespace 'SocketIOClient'. src/laravel-echo.d.ts(347,20): error TS2304: Cannot find name 'SocketIOClientStatic'. src/laravel-echo.d.ts(490,33): error TS2503: Cannot find namespace 'Null'.

And if I try using @mmschuler code I have a plethora of new errors...

I don't know what to do anymore, can you guys help me?

Thanks in advance

chancezeus commented 5 years ago

I just saw @manniniandrea created a fork with a "Angular 7" compatibility fix. I'm unsure if the fix works and I'm swamped at work at the moment so if he or anyone else can confirm the fix works I'll merge the changes and release an updated versions of the package asap.

renatop7 commented 5 years ago

@chancezeus I managed to make @mmschuler's code works like this:

First I got the types for pusher and socket.io: npm i --save-dev @types/pusher @types/socket.io-client

Copied the files @mmschuler posted into a new folder "types" in the root directory of the project.

And edited tsconfig.app.json:

...
"paths": { "*": ["types/*"] },
"allowSyntheticDefaultImports": true,
"noImplicitAny": false,
"skipLibCheck": true,
...

Only after that I could compile without errors.

In my research to make the code work I saw some devs talking about some issues while setting "allowSyntheticDefaultImports" and "noImplicitAny"

So I don't know if this works for everyone/project

mmschuler commented 5 years ago

I have made a fork on npm @chancezeus @renatop7:

https://www.npmjs.com/package/ngx-laravel-echo

I will delete this fork until this issue get fixed here..

mmschuler commented 5 years ago

Me again ^^

I can share the source files too - if you want. I had to change a few things, because some import / export things get changed in the actual typescript version.

cesaric commented 4 years ago

Me again ^^

I can share the source files too - if you want. I had to change a few things, because some import / export things get changed in the actual typescript version.

I'm using ngx-laravel-echo and just posted my setup/config for anyone having trouble: https://github.com/mmschuler/ngx-laravel-echo/issues/2