From d2d2c2f5bb343144cf31097b9716848fe7249e1e Mon Sep 17 00:00:00 2001 From: Kristof Boucher Charbonneau Date: Fri, 7 Apr 2017 02:25:59 -0400 Subject: [PATCH] Integration with the socket server --- tasklist/package.json | 1 + tasklist/src/config/development.json | 3 +- tasklist/src/config/production.json | 3 +- tasklist/src/pages/login/login.ts | 42 +- tasklist/src/services/socketService.ts | 97 +++ tasklist/typings.json | 5 + .../globals/socket.io-client/index.d.ts | 668 ++++++++++++++++++ .../globals/socket.io-client/typings.json | 8 + tasklist/typings/index.d.ts | 1 + 9 files changed, 823 insertions(+), 5 deletions(-) create mode 100644 tasklist/src/services/socketService.ts create mode 100644 tasklist/typings.json create mode 100644 tasklist/typings/globals/socket.io-client/index.d.ts create mode 100644 tasklist/typings/globals/socket.io-client/typings.json create mode 100644 tasklist/typings/index.d.ts diff --git a/tasklist/package.json b/tasklist/package.json index 84f4fc4..f5927f6 100644 --- a/tasklist/package.json +++ b/tasklist/package.json @@ -24,6 +24,7 @@ "ionic-native": "2.2.11", "ionicons": "3.0.0", "rxjs": "5.0.0-beta.12", + "socket.io-client": "^1.7.3", "sw-toolbox": "3.4.0", "zone.js": "0.6.26" }, diff --git a/tasklist/src/config/development.json b/tasklist/src/config/development.json index 25d1a03..0c5cd7d 100644 --- a/tasklist/src/config/development.json +++ b/tasklist/src/config/development.json @@ -1,3 +1,4 @@ { - "apiUrl": "http://127.0.0.1:8000/api/v1" + "apiUrl": "http://127.0.0.1:8000/api/v1", + "socketUrl": "http://127.0.0.1:3000" } diff --git a/tasklist/src/config/production.json b/tasklist/src/config/production.json index a327371..fccc7f4 100644 --- a/tasklist/src/config/production.json +++ b/tasklist/src/config/production.json @@ -1,3 +1,4 @@ { - "apiUrl": "http://198.27.119.182/api/v1" + "apiUrl": "http://198.27.119.182/api/v1", + "socketUrl": "http://198.27.119.182:3000" } diff --git a/tasklist/src/pages/login/login.ts b/tasklist/src/pages/login/login.ts index af895e1..5322353 100644 --- a/tasklist/src/pages/login/login.ts +++ b/tasklist/src/pages/login/login.ts @@ -4,12 +4,13 @@ import { NavController, ToastController } from 'ionic-angular'; import { ListProject } from '../listProject/listProject'; import {AuthenticationService} from '../../services/authenticationService'; +import {SocketService} from '../../services/socketService'; import { Storage } from '@ionic/storage'; @Component({ selector: 'page-login', templateUrl: 'login.html', - providers: [AuthenticationService] + providers: [AuthenticationService,SocketService] }) export class Login { username: string; @@ -18,6 +19,7 @@ export class Login { constructor( public navCtrl: NavController, private authenticationService: AuthenticationService, + private socketService: SocketService, public toastCtrl: ToastController, public storage: Storage ) { @@ -30,8 +32,25 @@ export class Login { this.authenticationService.authenticate(this.username, this.password).subscribe( data => { - this.storage.set('token', data.token) + this.storage.set('token', data.token); + this.storage.set('userid', data.id); this.navCtrl.setRoot(ListProject, { }); + + // When a user is authenticated start the connection to the socket server + this.socketService.connect(data.token, data.id).then(() => { + // Subscribe to the main channel: task + this.socketService.subscribe('task').then((stream:any) => { + console.log(stream); + stream.subscribe(data => { + // Toast notification + let toast = this.toastCtrl.create({ + message: "You have a new assgined task: " + data.title, + dismissOnPageChange: true + }); + toast.present(); + }) + }) + }); } ); }); @@ -45,8 +64,25 @@ export class Login { // Redirect after control this.storage.set('username', this.username); this.storage.set('password', this.password); - this.storage.set('token', data.token) + this.storage.set('token', data.token); + this.storage.set('userid', data.id); this.navCtrl.setRoot(ListProject, { }); + + // When a user is authenticated start the connection to the socket server + this.socketService.connect(data.token, data.id).then(() => { + // Subscribe to the main channel: task + this.socketService.subscribe('task').then((stream:any) => { + console.log(stream); + stream.subscribe(data => { + // Toast notification + let toast = this.toastCtrl.create({ + message: "You have a new assgined task: " + data.title, + dismissOnPageChange: true + }); + toast.present(); + }) + }) + }); }, err => { let toast = this.toastCtrl.create({ diff --git a/tasklist/src/services/socketService.ts b/tasklist/src/services/socketService.ts new file mode 100644 index 0000000..32d367d --- /dev/null +++ b/tasklist/src/services/socketService.ts @@ -0,0 +1,97 @@ +import {Http} from '@angular/http'; +import 'rxjs/add/operator/map'; +import { Observable } from 'rxjs/Rx'; +import { Injectable } from "@angular/core"; +import { Storage } from '@ionic/storage'; +import { Config } from '../config/config'; +import * as io from "socket.io-client"; + +/** + * + */ + +@Injectable() +export class SocketService { + private _socketUrl:string; + private _socket:any; + + constructor (public storage: Storage, protected http:Http, private _config:Config) { + this._socketUrl = _config.get('socketUrl'); + this._socket = null; + } + + connect(token:string, userid:string) { + return new Promise((resolve, reject) => { + this._socket = io.connect(this._socketUrl); + this._socket.on('connect', () => { + // When one is connected, proceed to authentication + this._socket.emit('authenticate', { + token: token, + id: userid + }, (res) => { + console.log(res); + if( res.status ) { + resolve(); + } else { + reject(); + } + }); + }); + }); + + } + + subscribe(channel:string) { + return new Promise((resolve, reject) => { + if( this._socket == null || !this._socket.connected ) { + reject(); + return; + } + + this._socket.emit('subscribe', {channel}, (res) => { + if( res.status ) { + const key = 'on' + channel.charAt(0).toUpperCase() + channel.slice(1); + const stream = Observable.fromEvent(this._socket, key); + resolve(stream); + } else { + reject(); + } + }); + }); + } + + unsubscribe(channel:string) { + return new Promise((resolve, reject) => { + if( this._socket == null || !this._socket.connected ) { + reject(); + return; + } + + this._socket.emit('unsubscribe', {channel}, (res) => { + if( res.status ) { + resolve(); + } else { + reject(); + } + }); + }); + } + + emit(channel:string, event:string, data:Object = {}) { + return new Promise((resolve, reject) => { + if( this._socket == null || !this._socket.connected ) { + reject(); + return; + } + + this._socket.emit(channel + '.' + event, data, (res) => { + if(res.status) { + resolve(); + } else { + reject(); + } + }); + }); + } + +} diff --git a/tasklist/typings.json b/tasklist/typings.json new file mode 100644 index 0000000..00d8aca --- /dev/null +++ b/tasklist/typings.json @@ -0,0 +1,5 @@ +{ + "globalDependencies": { + "socket.io-client": "registry:dt/socket.io-client#1.4.4+20161116080703" + } +} diff --git a/tasklist/typings/globals/socket.io-client/index.d.ts b/tasklist/typings/globals/socket.io-client/index.d.ts new file mode 100644 index 0000000..a7bae6a --- /dev/null +++ b/tasklist/typings/globals/socket.io-client/index.d.ts @@ -0,0 +1,668 @@ +// Generated by typings +// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/8a087da260f4d46ab13450cf531417b940c11797/socket.io-client/index.d.ts +declare var io: SocketIOClientStatic; + +declare module 'socket.io-client' { + export = io; +} + +interface SocketIOClientStatic { + + /** + * Looks up an existing 'Manager' for multiplexing. If the user summons: + * 'io( 'http://localhost/a' );' + * 'io( 'http://localhost/b' );' + * + * We reuse the existing instance based on the same scheme/port/host, and + * we initialize sockets for each namespace. If autoConnect isn't set to + * false in the options, then we'll automatically connect + * @param uri The uri that we'll connect to, including the namespace, where '/' is the default one (e.g. http://localhost:4000/somenamespace) + * @opts Any connect options that we want to pass along + * @return A Socket object + */ + ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket; + + /** + * Auto-connects to the window location and defalt namespace. + * E.g. window.protocol + '//' + window.host + ':80/' + * @opts Any connect options that we want to pass along + * @return A Socket object + */ + ( opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket; + + /** + * @see the default constructor (io(uri, opts)) + */ + connect( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket; + + /** + * @see the default constructor (io(opts)) + */ + connect( opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Socket; + + /** + * The socket.io protocol revision number this client works with + * @default 4 + */ + protocol: number; + + /** + * Socket constructor - exposed for the standalone build + */ + Socket: SocketIOClient.Socket; + + /** + * Manager constructor - exposed for the standalone build + */ + Manager: SocketIOClient.ManagerStatic; +} + +declare namespace SocketIOClient { + + /** + * The base emiter class, used by Socket and Manager + */ + interface Emitter { + /** + * Adds a listener for a particular event. Calling multiple times will add + * multiple listeners + * @param event The event that we're listening for + * @param fn The function to call when we get the event. Parameters depend on the + * event in question + * @return This Emitter + */ + on( event: string, fn: Function ):Emitter; + + /** + * @see on( event, fn ) + */ + addEventListener( event: string, fn: Function ):Emitter; + + /** + * Adds a listener for a particular event that will be invoked + * a single time before being automatically removed + * @param event The event that we're listening for + * @param fn The function to call when we get the event. Parameters depend on + * the event in question + * @return This Emitter + */ + once( event: string, fn: Function ):Emitter; + + /** + * Removes a listener for a particular type of event. This will either + * remove a specific listener, or all listeners for this type of event + * @param event The event that we want to remove the listener of + * @param fn The function to remove, or null if we want to remove all functions + * @return This Emitter + */ + off( event: string, fn?: Function ):Emitter; + + /** + * @see off( event, fn ) + */ + removeListener( event: string, fn?: Function ):Emitter; + + /** + * @see off( event, fn ) + */ + removeEventListener( event: string, fn?: Function ):Emitter; + + /** + * Removes all event listeners on this object + * @return This Emitter + */ + removeAllListeners():Emitter; + + /** + * Emits 'event' with the given args + * @param event The event that we want to emit + * @param args Optional arguments to emit with the event + * @return Emitter + */ + emit( event: string, ...args: any[] ):Emitter; + + /** + * Returns all the callbacks for a particular event + * @param event The event that we're looking for the callbacks of + * @return An array of callback Functions, or an empty array if we don't have any + */ + listeners( event: string ):Function[]; + + /** + * Returns if we have listeners for a particular event + * @param event The event that we want to check if we've listeners for + * @return True if we have listeners for this event, false otherwise + */ + hasListeners( event: string ):boolean; + } + + /** + * The Socket static interface + */ + interface SocketStatic { + + /** + * Creates a new Socket, used for communicating with a specific namespace + * @param io The Manager that's controlling this socket + * @param nsp The namespace that this socket is for (@default '/') + * @return A new Socket + */ + ( io: SocketIOClient.Manager, nsp: string ): Socket; + + /** + * Creates a new Socket, used for communicating with a specific namespace + * @param io The Manager that's controlling this socket + * @param nsp The namespace that this socket is for (@default '/') + * @return A new Socket + */ + new ( url: string, opts: any ): SocketIOClient.Manager; + } + + /** + * The Socket that we use to connect to a Namespace on the server + */ + interface Socket extends Emitter { + + /** + * The Manager that's controller this socket + */ + io: SocketIOClient.Manager; + + /** + * The namespace that this socket is for + * @default '/' + */ + nsp: string; + + /** + * The ID of the socket; matches the server ID and is set when we're connected, and cleared + * when we're disconnected + */ + id: string; + + /** + * Are we currently connected? + * @default false + */ + connected: boolean; + + /** + * Are we currently disconnected? + * @default true + */ + disconnected: boolean; + + /** + * Opens our socket so that it connects. If the 'autoConnect' option for io is + * true (default), then this is called automatically when the Socket is created + */ + open(): Socket; + + /** + * @see open(); + */ + connect(): Socket; + + /** + * Sends a 'message' event + * @param args Any optional arguments that we want to send + * @see emit + * @return This Socket + */ + send( ...args: any[] ):Socket; + + /** + * An override of the base emit. If the event is one of: + * connect + * connect_error + * connect_timeout + * connecting + * disconnect + * error + * reconnect + * reconnect_attempt + * reconnect_failed + * reconnect_error + * reconnecting + * ping + * pong + * then the event is emitted normally. Otherwise, if we're connected, the + * event is sent. Otherwise, it's buffered. + * + * If the last argument is a function, then it will be called + * as an 'ack' when the response is received. The parameter(s) of the + * ack will be whatever data is returned from the event + * @param event The event that we're emitting + * @param args Optional arguments to send with the event + * @return This Socket + */ + emit( event: string, ...args: any[] ):Socket; + + /** + * Disconnects the socket manually + * @return This Socket + */ + close():Socket; + + /** + * @see close() + */ + disconnect():Socket; + + /** + * Sets the compress flag. + * @param compress If `true`, compresses the sending data + * @return this Socket + */ + compress(compress: boolean):Socket; + } + + /** + * The Manager static interface + */ + interface ManagerStatic { + /** + * Creates a new Manager + * @param uri The URI that we're connecting to (e.g. http://localhost:4000) + * @param opts Any connection options that we want to use (and pass to engine.io) + * @return A Manager + */ + ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Manager; + + /** + * Creates a new Manager with the default URI (window host) + * @param opts Any connection options that we want to use (and pass to engine.io) + */ + ( opts: SocketIOClient.ConnectOpts ):SocketIOClient.Manager; + + /** + * @see default constructor + */ + new ( uri: string, opts?: SocketIOClient.ConnectOpts ): SocketIOClient.Manager; + + /** + * @see default constructor + */ + new ( opts: SocketIOClient.ConnectOpts ):SocketIOClient.Manager; + } + + /** + * The Manager class handles all the Namespaces and Sockets that we're using + */ + interface Manager extends Emitter { + + /** + * All the namespaces currently controlled by this Manager, and the Sockets + * that we're using to communicate with them + */ + nsps: { [namespace:string]: Socket }; + + /** + * The connect options that we used when creating this Manager + */ + opts: SocketIOClient.ConnectOpts; + + /** + * The state of the Manager. Either 'closed', 'opening', or 'open' + */ + readyState: string; + + /** + * The URI that this manager is for (host + port), e.g. 'http://localhost:4000' + */ + uri: string; + + /** + * The currently connected sockets + */ + connecting: Socket[]; + + /** + * If we should auto connect (also used when creating Sockets). Set via the + * opts object + */ + autoConnect: boolean; + + /** + * Gets if we should reconnect automatically + * @default true + */ + reconnection(): boolean; + + /** + * Sets if we should reconnect automatically + * @param v True if we should reconnect automatically, false otherwise + * @default true + * @return This Manager + */ + reconnection( v: boolean ): Manager; + + /** + * Gets the number of reconnection attempts we should try before giving up + * @default Infinity + */ + reconnectionAttempts(): number; + + /** + * Sets the number of reconnection attempts we should try before giving up + * @param v The number of attempts we should do before giving up + * @default Infinity + * @return This Manager + */ + reconnectionAttempts( v: number ): Manager; + + /** + * Gets the delay in milliseconds between each reconnection attempt + * @default 1000 + */ + reconnectionDelay(): number; + + /** + * Sets the delay in milliseconds between each reconnection attempt + * @param v The delay in milliseconds + * @default 1000 + * @return This Manager + */ + reconnectionDelay( v: number ): Manager; + + /** + * Gets the max reconnection delay in milliseconds between each reconnection + * attempt + * @default 5000 + */ + reconnectionDelayMax(): number; + + /** + * Sets the max reconnection delay in milliseconds between each reconnection + * attempt + * @param v The max reconnection dleay in milliseconds + * @return This Manager + */ + reconnectionDelayMax( v: number ): Manager; + + /** + * Gets the randomisation factor used in the exponential backoff jitter + * when reconnecting + * @default 0.5 + */ + randomizationFactor(): number; + + /** + * Sets the randomisation factor used in the exponential backoff jitter + * when reconnecting + * @param The reconnection randomisation factor + * @default 0.5 + * @return This Manager + */ + randomizationFactor( v: number ): Manager; + + /** + * Gets the timeout in milliseconds for our connection attempts + * @default 20000 + */ + timeout(): number; + + /** + * Sets the timeout in milliseconds for our connection attempts + * @param The connection timeout milliseconds + * @return This Manager + */ + timeout(v: number): Manager; + + /** + * Sets the current transport socket and opens our connection + * @param fn An optional callback to call when our socket has either opened, or + * failed. It can take one optional parameter of type Error + * @return This Manager + */ + open( fn?: (err?: any) => void ): Manager; + + /** + * @see open( fn ); + */ + connect( fn?: (err?: any) => void ): Manager; + + /** + * Creates a new Socket for the given namespace + * @param nsp The namespace that this Socket is for + * @return A new Socket, or if one has already been created for this namespace, + * an existing one + */ + socket( nsp: string ): Socket; + } + + /** + * Options we can pass to the socket when connecting + */ + interface ConnectOpts { + + /** + * Should we force a new Manager for this connection? + * @default false + */ + forceNew?: boolean; + + /** + * Should we multiplex our connection (reuse existing Manager) ? + * @default true + */ + multiplex?: boolean; + + /** + * The path to get our client file from, in the case of the server + * serving it + * @default '/socket.io' + */ + path?: string; + + /** + * Should we allow reconnections? + * @default true + */ + reconnection?: boolean; + + /** + * How many reconnection attempts should we try? + * @default Infinity + */ + reconnectionAttempts?: number; + + /** + * The time delay in milliseconds between reconnection attempts + * @default 1000 + */ + reconnectionDelay?: number; + + /** + * The max time delay in milliseconds between reconnection attempts + * @default 5000 + */ + reconnectionDelayMax?: number; + + /** + * Used in the exponential backoff jitter when reconnecting + * @default 0.5 + */ + randomizationFactor?: number; + + /** + * The timeout in milliseconds for our connection attempt + * @default 20000 + */ + timeout?: number; + + /** + * Should we automically connect? + * @default true + */ + autoConnect?: boolean; + + /** + * The host that we're connecting to. Set from the URI passed when connecting + */ + host?: string; + + /** + * The hostname for our connection. Set from the URI passed when connecting + */ + hostname?: string; + + /** + * If this is a secure connection. Set from the URI passed when connecting + */ + secure?: boolean; + + /** + * The port for our connection. Set from the URI passed when connecting + */ + port?: string; + + /** + * Any query parameters in our uri. Set from the URI passed when connecting + */ + query?: Object; + + /** + * `http.Agent` to use, defaults to `false` (NodeJS only) + */ + agent?: string|boolean; + + /** + * Whether the client should try to upgrade the transport from + * long-polling to something better. + * @default true + */ + upgrade?: boolean; + + /** + * Forces JSONP for polling transport. + */ + forceJSONP?: boolean; + + /** + * Determines whether to use JSONP when necessary for polling. If + * disabled (by settings to false) an error will be emitted (saying + * "No transports available") if no other transports are available. + * If another transport is available for opening a connection (e.g. + * WebSocket) that transport will be used instead. + * @default true + */ + jsonp?: boolean; + + /** + * Forces base 64 encoding for polling transport even when XHR2 + * responseType is available and WebSocket even if the used standard + * supports binary. + */ + forceBase64?: boolean; + + /** + * Enables XDomainRequest for IE8 to avoid loading bar flashing with + * click sound. default to `false` because XDomainRequest has a flaw + * of not sending cookie. + * @default false + */ + enablesXDR?: boolean; + + /** + * The param name to use as our timestamp key + * @default 't' + */ + timestampParam?: string; + + /** + * Whether to add the timestamp with each transport request. Note: this + * is ignored if the browser is IE or Android, in which case requests + * are always stamped + * @default false + */ + timestampRequests?: boolean; + + /** + * A list of transports to try (in order). Engine.io always attempts to + * connect directly with the first one, provided the feature detection test + * for it passes. + * @default ['polling','websocket'] + */ + transports?: string[]; + + /** + * The port the policy server listens on + * @default 843 + */ + policyPost?: number; + + /** + * If true and if the previous websocket connection to the server succeeded, + * the connection attempt will bypass the normal upgrade process and will + * initially try websocket. A connection attempt following a transport error + * will use the normal upgrade process. It is recommended you turn this on + * only when using SSL/TLS connections, or if you know that your network does + * not block websockets. + * @default false + */ + rememberUpgrade?: boolean; + + /** + * Are we only interested in transports that support binary? + */ + onlyBinaryUpgrades?: boolean; + + /** + * Header options for Node.js client + */ + extraHeaders?: Object; + + /** + * (SSL) Certificate, Private key and CA certificates to use for SSL. + * Can be used in Node.js client environment to manually specify + * certificate information. + */ + pfx?: string; + + /** + * (SSL) Private key to use for SSL. Can be used in Node.js client + * environment to manually specify certificate information. + */ + key?: string; + + /** + * (SSL) A string or passphrase for the private key or pfx. Can be + * used in Node.js client environment to manually specify certificate + * information. + */ + passphrase?: string + + /** + * (SSL) Public x509 certificate to use. Can be used in Node.js client + * environment to manually specify certificate information. + */ + cert?: string; + + /** + * (SSL) An authority certificate or array of authority certificates to + * check the remote host against.. Can be used in Node.js client + * environment to manually specify certificate information. + */ + ca?: string|string[]; + + /** + * (SSL) A string describing the ciphers to use or exclude. Consult the + * [cipher format list] + * (http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for + * details on the format.. Can be used in Node.js client environment to + * manually specify certificate information. + */ + ciphers?: string; + + /** + * (SSL) If true, the server certificate is verified against the list of + * supplied CAs. An 'error' event is emitted if verification fails. + * Verification happens at the connection level, before the HTTP request + * is sent. Can be used in Node.js client environment to manually specify + * certificate information. + */ + rejectUnauthorized?: boolean; + + } +} diff --git a/tasklist/typings/globals/socket.io-client/typings.json b/tasklist/typings/globals/socket.io-client/typings.json new file mode 100644 index 0000000..a0f0119 --- /dev/null +++ b/tasklist/typings/globals/socket.io-client/typings.json @@ -0,0 +1,8 @@ +{ + "resolution": "main", + "tree": { + "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/8a087da260f4d46ab13450cf531417b940c11797/socket.io-client/index.d.ts", + "raw": "registry:dt/socket.io-client#1.4.4+20161116080703", + "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/8a087da260f4d46ab13450cf531417b940c11797/socket.io-client/index.d.ts" + } +} diff --git a/tasklist/typings/index.d.ts b/tasklist/typings/index.d.ts new file mode 100644 index 0000000..5f7ba1d --- /dev/null +++ b/tasklist/typings/index.d.ts @@ -0,0 +1 @@ +///