import {Injectable} from "@angular/core"; import {Storage} from '@ionic/storage'; import Socket = SocketIOClient.Socket; import {Observable, ReplaySubject} from "rxjs"; import * as io from 'socket.io-client'; @Injectable() export class SocketService { public socket: Socket; private connected: Promise; private asyncSocket = new AsyncSocket(); constructor(public storage: Storage) { this.reconnect(); } // Waits for successful socket connection, gets the token out of local storage and then authenticates the user with the server authenticate() { console.log('%cBeginning socket authentication', 'color: grey'); this.storage.get('token').then(token => { console.log('%cToken was returned: ', 'color: grey', token); if (token) { this.connected.then(() => { console.log('%cSocket connection established', 'color: grey'); this.socket .emit('authenticate', {token: token}) .once('authenticated', () => { console.log('%cSocket auth successful', 'color:green; font-size:larger; font-weight: 700'); this.asyncSocket.connect(this.socket); this.socket.emit('app:init', {}); }) .once('unauthorized', function (msg) { console.error('Socket auth failed: ', msg); }); console.log('%cEmitted socket authentication', 'color: grey'); }); } else { console.warn('No token found'); } }); } observe(event: string): Observable { console.debug('SocketService.observe(', event,')'); return Observable.bindCallback((event: string, f) => this.asyncSocket.on(event, f))(event); } observeOnce(event: string): Observable { return Observable.bindCallback((event: string, f) => this.asyncSocket.once(event, f))(event); } emit(event: string, payload: any = {}) { this.asyncSocket.emit(event, payload); } emitOnce(event: string, payload: any = {}) { this.emit(event, payload); return this.observeOnce(event); } // Will connect the socket and resolve the internal promise once successful reconnect() { console.log('%cStarting socket reconnect', 'color: grey'); this.connected = new Promise(resolve => { this.socket = io('https://example.com', { forceNew: true, secure: true, reconnectionDelay: 500, reconnectionDelayMax: 2000 }); this.socket.once('connect', () => { resolve(true); console.log('%cResolving socket connection promise', 'color: grey'); }); }); } } class AsyncSocket { private onCalls = new ReplaySubject<{event: string, f: Function}>(); private onceCalls = new ReplaySubject<{event: string, f: Function}>(); private emits = new ReplaySubject<{event: string, payload: any}>(); private socket: Socket; on(event: string, f: any) { console.debug('Queueing on for: ', event, f); this.onCalls.next({event: event, f: f}); } once(event: string, f: any) { console.debug('Queueing once for: ', event, f); this.onceCalls.next({event: event, f: f}); } emit(event: string, payload: any) { console.debug('Queueing emit for: ', event, payload); this.emits.next({event: event, payload: payload}); } connect(socket: Socket) { console.log('%cConnecting queued socket things to actual socket', 'color: grey'); this.socket = socket; this.socket.on('likePost', (res => console.info(res))); this.onCalls.do(call => console.debug('Calling on for ', call)).subscribe(call => this.socket.on(call.event, call.f)); this.onceCalls.do(call => console.debug('Calling once for ', call)).subscribe(call => this.socket.once(call.event, call.f)); this.emits.do(call => console.debug('Calling emit for ', call)).subscribe(call => this.socket.emit(call.event, call.payload)); } }