From 81405ee06ca1ac151ea613189a39e606223685b1 Mon Sep 17 00:00:00 2001 From: Jonathan Casarrubias Date: Tue, 20 Dec 2016 18:32:50 -0600 Subject: [PATCH] Release 2.1.0-rc.6 :rocket: - Milestone Details: https://github.com/mean-expert-official/loopback-sdk-builder/milestone/28?closed=1 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/268 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/266 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/265 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/264 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/262 - Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/151 --- CHANGELOG.md | 11 +++ lib/angular2/index.js | 15 +++- lib/angular2/shared/index.ejs | 4 +- lib/angular2/shared/models/flref.ejs | 8 +- lib/angular2/shared/services/core/auth.ejs | 2 +- lib/angular2/shared/services/core/base.ejs | 5 +- .../shared/services/core/realtime.ejs | 19 ++-- lib/angular2/shared/sockets/connections.ts | 45 ++++++++-- .../shared/storage/internal.storage.ts | 13 --- .../shared/storage/storage.browser.ts | 37 ++++++++ lib/angular2/shared/storage/storage.native.ts | 16 +++- lib/angular2/shared/storage/storage.swaps.ts | 35 ++++++++ package.json | 2 +- .../src/app/room-service.service.spec.ts | 89 ++++++++++--------- tests/angular2/src/app/shared/sdk/index.ts | 8 +- .../src/app/shared/sdk/models/FireLoopRef.ts | 12 +-- .../shared/sdk/services/core/auth.service.ts | 2 +- .../shared/sdk/services/core/base.service.ts | 19 ++-- .../app/shared/sdk/services/core/real.time.ts | 39 +++++++- .../shared/sdk/sockets/socket.connections.ts | 45 ++++++++-- .../app/shared/sdk/storage/cookie.browser.ts | 2 +- .../shared/sdk/storage/internal.storage.ts | 13 --- .../app/shared/sdk/storage/storage.browser.ts | 37 ++++++++ .../app/shared/sdk/storage/storage.swaps.ts | 35 ++++++++ 24 files changed, 382 insertions(+), 131 deletions(-) delete mode 100644 lib/angular2/shared/storage/internal.storage.ts create mode 100644 lib/angular2/shared/storage/storage.browser.ts create mode 100644 lib/angular2/shared/storage/storage.swaps.ts delete mode 100644 tests/angular2/src/app/shared/sdk/storage/internal.storage.ts create mode 100644 tests/angular2/src/app/shared/sdk/storage/storage.browser.ts create mode 100644 tests/angular2/src/app/shared/sdk/storage/storage.swaps.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4637eac1..5f4e56ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ This file is created to keep history of the LoopBack SDK Builder, it does not consider or keeps any history of its parent module `loopback-sdk-angular`. +## Release 2.1.0-rc.6 + +- Milestone Details: https://github.com/mean-expert-official/loopback-sdk-builder/milestone/28?closed=1 + +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/268 +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/266 +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/265 +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/264 +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/262 +- Fix: https://github.com/mean-expert-official/loopback-sdk-builder/issues/151 + ## Release 2.1.0-rc.5 - Milestone Details: https://github.com/mean-expert-official/loopback-sdk-builder/milestone/27?closed=1 diff --git a/lib/angular2/index.js b/lib/angular2/index.js index e3f513e9..1a8d552e 100644 --- a/lib/angular2/index.js +++ b/lib/angular2/index.js @@ -147,8 +147,8 @@ module.exports = function generate(ctx) { * STORAGE */ { - template: './shared/storage/internal.storage.ts', - output: '/storage/internal.storage.ts', + template: './shared/storage/storage.swaps.ts', + output: '/storage/storage.swaps.ts', params: {} } ]; @@ -159,6 +159,11 @@ module.exports = function generate(ctx) { output: '/storage/cookie.browser.ts', params: {} }); + schema.push({ + template: './shared/storage/storage.browser.ts', + output: '/storage/storage.browser.ts', + params: {} + }); } // Add Server Specific Code if (ctx.driver === 'ng2universal') { @@ -470,7 +475,7 @@ module.exports = function generate(ctx) { { module: 'LoopBackAuth', from: './services/core/auth.service'}, { module: 'LoggerService', from: './services/custom/logger.service'}, { module: 'SDKModels', from: './services/custom/SDKModels'}, - { module: 'InternalStorage', from: './storage/internal.storage'}, + { module: 'InternalStorage, SDKStorage', from: './storage/storage.swaps'}, { module: 'HttpModule', from: '@angular/http'}, { module: 'CommonModule', from: '@angular/common'}, { module: 'NgModule, ModuleWithProviders', from: '@angular/core'} @@ -479,6 +484,7 @@ module.exports = function generate(ctx) { switch (driver) { case 'ng2web': imports.push({ module: 'CookieBrowser', from: './storage/cookie.browser'}); + imports.push({ module: 'StorageBrowser', from: './storage/storage.browser'}); if (isIo === 'enabled') { imports.push({ module: 'SocketBrowser', from: './sockets/socket.browser'}); } @@ -486,6 +492,7 @@ module.exports = function generate(ctx) { case 'ng2universal': imports.push({ module: 'CookieBrowser', from: './storage/cookie.browser'}); imports.push({ module: 'CookieNode', from: './storage/cookie.node'}); + imports.push({ module: 'StorageBrowser', from: './storage/storage.browser'}); if (isIo === 'enabled') { imports.push({ module: 'SocketBrowser', from: './sockets/socket.browser'}); imports.push({ module: 'SocketNode', from: './sockets/socket.node'}); @@ -528,6 +535,7 @@ module.exports = function generate(ctx) { case 'browser': if (driver === 'ng2web' || driver === 'ng2universal') { imports.push('{ provide: InternalStorage, useClass: CookieBrowser }'); + imports.push('{ provide: SDKStorage, useClass: StorageBrowser }'); if (isIo === 'enabled') { imports.push('{ provide: SocketDriver, useClass: SocketBrowser }'); } @@ -544,6 +552,7 @@ module.exports = function generate(ctx) { case 'nativescript': if (driver === 'ng2native') { imports.push('{ provide: InternalStorage, useClass: StorageNative }'); + imports.push('{ provide: SDKStorage, useClass: StorageNative }'); if (isIo === 'enabled') { imports.push('{ provide: SocketDriver, useClass: SocketNative }'); } diff --git a/lib/angular2/shared/index.ejs b/lib/angular2/shared/index.ejs index 21ca391b..25446059 100644 --- a/lib/angular2/shared/index.ejs +++ b/lib/angular2/shared/index.ejs @@ -18,14 +18,14 @@ * // App Root * import { AppComponent } from './app.component'; * // Feature Modules -* import { SDKModule } from './shared/sdk/sdk.module'; +* import { SDK[Browser|Node|Native]Module } from './shared/sdk/sdk.module'; * // Import Routing * import { routing } from './app.routing'; * @NgModule({ * imports: [ * BrowserModule, * routing, -* SDKModule.forRoot() +* SDK[Browser|Node|Native]Module.forRoot() * ], * declarations: [ AppComponent ], * bootstrap: [ AppComponent ] diff --git a/lib/angular2/shared/models/flref.ejs b/lib/angular2/shared/models/flref.ejs index 98e0e012..5d2bbd93 100644 --- a/lib/angular2/shared/models/flref.ejs +++ b/lib/angular2/shared/models/flref.ejs @@ -165,7 +165,7 @@ export class FireLoopRef { } sbj.next(data); }; - this.socket.on(nowEvent, pullNow); + this.socket.onZone(nowEvent, pullNow); return sbj.asObservable(); } /** @@ -176,12 +176,12 @@ export class FireLoopRef { **/ private broadcasts(event: string, request: any): Observable { let sbj: Subject = new Subject(); - this.socket.on( + this.socket.onZone( `${event}.broadcast.announce.${ this.id }`, (res: T) => this.socket.emit(`${event}.broadcast.request.${ this.id }`, request) ); - this.socket.on(`${ event }.broadcast.${ this.id }`, (data) => sbj.next(data)); + this.socket.onZone(`${ event }.broadcast.${ this.id }`, (data) => sbj.next(data)); return sbj.asObservable(); } /** @@ -203,7 +203,7 @@ export class FireLoopRef { parent: this.parent && this.parent.instance ? this.parent.instance : null }; this.socket.emit(event, config); - this.socket.on(`${ this.model.getModelName() }.value.result.${ this.id }`, (res: any) => + this.socket.onZone(`${ this.model.getModelName() }.value.result.${ this.id }`, (res: any) => subject.next(res.error ? Observable.throw(res.error) : res) ); return subject.asObservable(); diff --git a/lib/angular2/shared/services/core/auth.ejs b/lib/angular2/shared/services/core/auth.ejs index 58dd9dfe..e0aef835 100644 --- a/lib/angular2/shared/services/core/auth.ejs +++ b/lib/angular2/shared/services/core/auth.ejs @@ -1,7 +1,7 @@ /* tslint:disable */ declare var Object: any; import { Injectable, Inject } from '@angular/core'; -import { InternalStorage } from '../../storage/internal.storage'; +import { InternalStorage } from '../../storage/storage.swaps'; import { SDKToken, AccessToken } from '../../models/BaseModels'; /** diff --git a/lib/angular2/shared/services/core/base.ejs b/lib/angular2/shared/services/core/base.ejs index 026da912..6544205d 100644 --- a/lib/angular2/shared/services/core/base.ejs +++ b/lib/angular2/shared/services/core/base.ejs @@ -88,9 +88,8 @@ export abstract class BaseLoopBackApi { body = postBody; } // Separate filter object from url params - let filter: string = ''; if (urlParams.filter) { - filter = `?filter=${ encodeURI(JSON.stringify(urlParams.filter))}`; + headers.append('filter', JSON.stringify(urlParams.filter)); delete urlParams.filter; } @@ -98,7 +97,7 @@ export abstract class BaseLoopBackApi { let request: Request = new Request({ headers : headers, method : method, - url : `${requestUrl}${filter}`, + url : requestUrl, search : Object.keys(urlParams).length > 0 ? this.searchParams.getURLSearchParams() : null, body : body ? JSON.stringify(body) : undefined diff --git a/lib/angular2/shared/services/core/realtime.ejs b/lib/angular2/shared/services/core/realtime.ejs index 40d84bfb..fb000e8f 100644 --- a/lib/angular2/shared/services/core/realtime.ejs +++ b/lib/angular2/shared/services/core/realtime.ejs @@ -22,10 +22,13 @@ export class RealTime { @Inject(SDKModels) protected models: SDKModels, @Inject(LoopBackAuth) protected auth: LoopBackAuth, @Inject(JSONSearchParams) protected searchParams: JSONSearchParams - ) { - this.socket = this.getConnection(); - this.IO = new IO(this.socket); - this.FireLoop = new FireLoop(this.socket, models); + ) {} + + disconnect(): void { + this.connected = false; + this.IO = null; + this.FireLoop = null; + this.connections.disconnect(); } getConnection(): void { @@ -41,11 +44,17 @@ export class RealTime { if (this.connected) { subject.next(); } else { + this.socket = this.getConnection(); + this.IO = new IO(this.socket); + this.FireLoop = new FireLoop(this.socket, this.models); this.socket.on('connect', () => { this.connected = true; subject.next(); }); - this.socket.on('disconnect', () => this.connected = false); + this.socket.on('disconnect', () => { + subject.complete(); + this.disconnect(); + }); } return subject.asObservable(); } diff --git a/lib/angular2/shared/sockets/connections.ts b/lib/angular2/shared/sockets/connections.ts index 1d817b2b..12e47a9d 100644 --- a/lib/angular2/shared/sockets/connections.ts +++ b/lib/angular2/shared/sockets/connections.ts @@ -1,5 +1,5 @@ /* tslint:disable */ -import { Injectable, Inject } from '@angular/core'; +import { Injectable, Inject, NgZone } from '@angular/core'; import { SocketDriver } from './socket.driver'; import { AccessToken } from '../models'; /** @@ -14,16 +14,25 @@ import { AccessToken } from '../models'; @Injectable() export class SocketConnections { private connections: any = {}; - private configured: boolean = false; - constructor(@Inject(SocketDriver) private driver: SocketDriver) {} + private configured: boolean = false; + constructor( + @Inject(SocketDriver) private driver: SocketDriver, + @Inject(NgZone) private zone: NgZone + ) { } getHandler(url: string, token: AccessToken) { + console.log('Getting handler for socket connections'); if (!this.connections[url]) { console.log('Creating a new connection with: ', url); - let config: any = { log: false, secure: false, forceWebsockets: true }; + let config: any = { log: false, secure: false, forceNew: true, forceWebsockets: true }; this.connections[url] = this.driver.connect(url, config); + this.connections[url].onZone = ((event: string, handler: Function) => { + this.connections[url].on(event, (data: any) => { + this.zone.run(() => handler(data)); + }); + }); this.connections[url].on('connect', () => { - if (!this.configured) - this.setupConnection(url, token, config); + if (!this.configured) + this.setupConnection(url, token, config); }); let forceConfig: any = setInterval(() => { if (!this.configured && this.connections[url].connected) { @@ -40,17 +49,35 @@ export class SocketConnections { return this.connections[url]; } + public disconnect() { + Object.keys(this.connections).forEach((connKey) => { + if (this.connections[connKey].connected) { + this.connections[connKey].disconnect(); + } + }); + this.connections = {}; + this.configured = false; + } + private setupConnection(url: string, token: AccessToken, config: any): void { this.configured = true; console.log('Connected to %s', url); - if(token.id) { + if (token.id) { + console.log('Emitting authentication', token.id); this.connections[url].emit('authentication', token); } this.connections[url].on('unauthorized', (res: any) => console.error('Unauthenticated', res)); - setInterval(() => this.connections[url].emit('lb-ping'), 15000); + let heartbeater: any = setInterval(() => { + if (this.connections[url]) { + this.connections[url].emit('lb-ping'); + } else { + clearInterval(heartbeater); + } + }, 15000); this.connections[url].on('lb-pong', (data: any) => console.info('Heartbeat: ', data)); this.connections[url].on('disconnect', (data: any) => { - console.info('Unexpected disconnection from IO - Socket IO will try to reconnect'); + this.disconnect(); + console.info('Disconnected from WebSocket server'); }); } } diff --git a/lib/angular2/shared/storage/internal.storage.ts b/lib/angular2/shared/storage/internal.storage.ts deleted file mode 100644 index ea25828c..00000000 --- a/lib/angular2/shared/storage/internal.storage.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @module InternalStorage - * @author Jonathan Casarrubias - * @license MIT - * @description - * The InternalStorage class is used for dependency injection swapping. - * It will be provided using factory method from different sources. - **/ -export class InternalStorage { - get(key: string): any {} - set(key: string, value: any): any {} - remove(key: string): any {} -} diff --git a/lib/angular2/shared/storage/storage.browser.ts b/lib/angular2/shared/storage/storage.browser.ts new file mode 100644 index 00000000..f5ba7b01 --- /dev/null +++ b/lib/angular2/shared/storage/storage.browser.ts @@ -0,0 +1,37 @@ +/* tslint:disable */ +import { Injectable } from '@angular/core'; +/** +* @module StorageBrowser +* @author Jonathan Casarrubias +* @license MIT +* @description +* Stand-alone cookie service for browsers +**/ +@Injectable() +export class StorageBrowser { + set(key: string, value: any) { + localStorage.setItem( + key, + typeof value === 'object' ? JSON.stringify(value) : value + ); + } + get(key: string): any { + let data: string = localStorage.getItem(key); + return this.isJSON(data) ? JSON.parse(data) : data; + } + remove(key: string): any { + if (localStorage[key]) { + localStorage.removeItem(key); + } else { + console.log('Trying to remove unexisting key: ', key); + } + } + private isJSON(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } +} diff --git a/lib/angular2/shared/storage/storage.native.ts b/lib/angular2/shared/storage/storage.native.ts index 3368a31d..0af78fd9 100644 --- a/lib/angular2/shared/storage/storage.native.ts +++ b/lib/angular2/shared/storage/storage.native.ts @@ -5,10 +5,14 @@ import { Injectable } from '@angular/core'; @Injectable() export class StorageNative { set(key: string, value: any) { - AppSettings.setString(key, String(value)); + AppSettings.setString( + key, + String(typeof value === 'object' ? JSON.stringify(value) : value) + ); } get(key: string): any { - return AppSettings.getString(key); + let data: string = AppSettings.getString(key); + return this.isJSON(data) ? JSON.parse(data) : data; } remove(key: string): any { if (AppSettings.hasKey(key)) { @@ -17,4 +21,12 @@ export class StorageNative { console.log('Trying to remove unexisting key: ', key); } } + private isJSON(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } } diff --git a/lib/angular2/shared/storage/storage.swaps.ts b/lib/angular2/shared/storage/storage.swaps.ts new file mode 100644 index 00000000..192b2c1a --- /dev/null +++ b/lib/angular2/shared/storage/storage.swaps.ts @@ -0,0 +1,35 @@ +/** + * @module Storage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The InternalStorage class is used for dependency injection swapping. + * It will be provided using factory method from different sources. + **/ +class Storage { + get(key: string): any {} + set(key: string, value: any): any {} + remove(key: string): any {} +} +/** + * @module InternalStorage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The InternalStorage class is used for dependency injection swapping. + * It will be provided using factory method from different sources. + * This is mainly required because Angular Universal integration. + * It does inject a CookieStorage instead of LocalStorage. + **/ +export class InternalStorage extends Storage {} +/** + * @module SDKStorage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The SDKStorage class is used for dependency injection swapping. + * It will be provided using factory method according the right environment. + * This is created for public usage, to allow persisting custom data + * Into the local storage API. + **/ +export class SDKStorage extends Storage {} diff --git a/package.json b/package.json index 173e3c47..e0417115 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mean-expert/loopback-sdk-builder", - "version": "2.1.0-rc.5", + "version": "2.1.0-rc.6", "description": "Tool for auto-generating Software Development Kits (SDKs) for LoopBack", "bin": { "lb-sdk": "bin/lb-sdk" diff --git a/tests/angular2/src/app/room-service.service.spec.ts b/tests/angular2/src/app/room-service.service.spec.ts index e254225a..08334b01 100644 --- a/tests/angular2/src/app/room-service.service.spec.ts +++ b/tests/angular2/src/app/room-service.service.spec.ts @@ -28,54 +28,59 @@ describe('Service: Room Service', () => { it('should listen for child_added using FireLoop API', - inject([RealTime], (realTime: RealTime) => { - let room: Room = new Room(); - room.name = Date.now().toString(); - let ref: FireLoopRef = realTime.FireLoop.ref(Room); - let subscription = ref.on('child_added', { where: room }).subscribe((result: Room) => { - expect(result.id).toBeTruthy(); - expect(result.name).toBe(room.name); - subscription.unsubscribe(); - }); - ref.create(room).subscribe(); - }) + inject([RealTime], (realTime: RealTime) => + realTime.onReady().subscribe(() => { + let room: Room = new Room(); + room.name = Date.now().toString(); + let ref: FireLoopRef = realTime.FireLoop.ref(Room); + let subscription = ref.on('child_added', { where: room }).subscribe((result: Room) => { + expect(result.id).toBeTruthy(); + expect(result.name).toBe(room.name); + subscription.unsubscribe(); + }); + ref.create(room).subscribe(); + }) + ) ); it('should listen for child_changed using FireLoop API', - inject([RealTime], (realTime: RealTime) => { - let room: Room = new Room(); - room.name = Date.now().toString(); - let name2 = room.name + 'SSSS'; - let ref: FireLoopRef = realTime.FireLoop.ref(Room); - let subscription = ref.on('child_changed').subscribe((result: Room) => { - expect(result.id).toBeTruthy(); - expect(result.name).toBe(name2); - subscription.unsubscribe(); - }); - ref.create(room).subscribe((res: Room) => { - res.name = name2; - ref.upsert(res).subscribe(); - }); - }) + inject([RealTime], (realTime: RealTime) => + realTime.onReady().subscribe(() => { + let room: Room = new Room(); + room.name = Date.now().toString(); + let name2 = room.name + 'SSSS'; + let ref: FireLoopRef = realTime.FireLoop.ref(Room); + let subscription = ref.on('child_changed').subscribe((result: Room) => { + expect(result.id).toBeTruthy(); + expect(result.name).toBe(name2); + subscription.unsubscribe(); + }); + ref.create(room).subscribe((res: Room) => { + res.name = name2; + ref.upsert(res).subscribe(); + }); + }) + ) ); it('should listen for child_removed using FireLoop API', - inject([RealTime], (realTime: RealTime) => { - let room: Room = new Room(); - room.name = Date.now().toString(); - let ref: FireLoopRef = realTime.FireLoop.ref(Room); - let subscription = ref.on('child_removed').subscribe((result: Room) => { - console.log(result); - expect(result.id).toBeTruthy(); - expect(result.name).toBe(room.name); - subscription.unsubscribe(); - }); - ref.create(room).subscribe((result: Room) => ref.remove(result).subscribe()); - }) + inject([RealTime], (realTime: RealTime) => realTime.onReady().subscribe(() => { + let room: Room = new Room(); + room.name = Date.now().toString(); + let ref: FireLoopRef = realTime.FireLoop.ref(Room); + let subscription = ref.on('child_removed').subscribe((result: Room) => { + console.log(result); + expect(result.id).toBeTruthy(); + expect(result.name).toBe(room.name); + subscription.unsubscribe(); + }); + ref.create(room).subscribe((result: Room) => ref.remove(result).subscribe()); + }) + ) ); it('should set data using FireLoop API', - inject([RealTime], (realTime: RealTime) => { + inject([RealTime], (realTime: RealTime) => realTime.onReady().subscribe(() => { let room: Room = new Room(); room.name = Date.now().toString(); let ref: FireLoopRef = realTime.FireLoop.ref(Room); @@ -85,10 +90,10 @@ describe('Service: Room Service', () => { subscription.unsubscribe(); }); }) - ); + )); it('should create child data using FireLoop API', - inject([RealTime], (realTime: RealTime) => { + inject([RealTime], (realTime: RealTime) => realTime.onReady().subscribe(() => { let room: Room = new Room(); room.name = Date.now().toString(); let message: Message = new Message({ text : 'Hello Child Reference' }); @@ -104,7 +109,7 @@ describe('Service: Room Service', () => { MessageReference.create(message).subscribe((res: Message) => console.log(res.text)); }); }) - ); + )); it('should create a new room instance', async(inject([RoomApi], (roomApi: RoomApi) => { diff --git a/tests/angular2/src/app/shared/sdk/index.ts b/tests/angular2/src/app/shared/sdk/index.ts index 30a98a94..d37dc9c9 100644 --- a/tests/angular2/src/app/shared/sdk/index.ts +++ b/tests/angular2/src/app/shared/sdk/index.ts @@ -18,14 +18,14 @@ * // App Root * import { AppComponent } from './app.component'; * // Feature Modules -* import { SDKModule } from './shared/sdk/sdk.module'; +* import { SDK[Browser|Node|Native]Module } from './shared/sdk/sdk.module'; * // Import Routing * import { routing } from './app.routing'; * @NgModule({ * imports: [ * BrowserModule, * routing, -* SDKModule.forRoot() +* SDK[Browser|Node|Native]Module.forRoot() * ], * declarations: [ AppComponent ], * bootstrap: [ AppComponent ] @@ -38,11 +38,12 @@ import { ErrorHandler } from './services/core/error.service'; import { LoopBackAuth } from './services/core/auth.service'; import { LoggerService } from './services/custom/logger.service'; import { SDKModels } from './services/custom/SDKModels'; -import { InternalStorage } from './storage/internal.storage'; +import { InternalStorage, SDKStorage } from './storage/storage.swaps'; import { HttpModule } from '@angular/http'; import { CommonModule } from '@angular/common'; import { NgModule, ModuleWithProviders } from '@angular/core'; import { CookieBrowser } from './storage/cookie.browser'; +import { StorageBrowser } from './storage/storage.browser'; import { SocketBrowser } from './sockets/socket.browser'; import { SocketDriver } from './sockets/socket.driver'; import { SocketConnections } from './sockets/socket.connections'; @@ -98,6 +99,7 @@ export class SDKBrowserModule { StorageApi, CoreApi, { provide: InternalStorage, useClass: CookieBrowser }, + { provide: SDKStorage, useClass: StorageBrowser }, { provide: SocketDriver, useClass: SocketBrowser } ] }; diff --git a/tests/angular2/src/app/shared/sdk/models/FireLoopRef.ts b/tests/angular2/src/app/shared/sdk/models/FireLoopRef.ts index edc9cf93..5d2bbd93 100644 --- a/tests/angular2/src/app/shared/sdk/models/FireLoopRef.ts +++ b/tests/angular2/src/app/shared/sdk/models/FireLoopRef.ts @@ -160,10 +160,12 @@ export class FireLoopRef { let nowEvent: any = `${event}.pull.requested.${ this.id }`; this.socket.emit(`${event}.pull.request.${ this.id }`, request); function pullNow(data: any) { - that.socket.removeListener(nowEvent, pullNow); + if (that.socket.removeListener) { + that.socket.removeListener(nowEvent, pullNow); + } sbj.next(data); }; - this.socket.on(nowEvent, pullNow); + this.socket.onZone(nowEvent, pullNow); return sbj.asObservable(); } /** @@ -174,12 +176,12 @@ export class FireLoopRef { **/ private broadcasts(event: string, request: any): Observable { let sbj: Subject = new Subject(); - this.socket.on( + this.socket.onZone( `${event}.broadcast.announce.${ this.id }`, (res: T) => this.socket.emit(`${event}.broadcast.request.${ this.id }`, request) ); - this.socket.on(`${ event }.broadcast.${ this.id }`, (data) => sbj.next(data)); + this.socket.onZone(`${ event }.broadcast.${ this.id }`, (data) => sbj.next(data)); return sbj.asObservable(); } /** @@ -201,7 +203,7 @@ export class FireLoopRef { parent: this.parent && this.parent.instance ? this.parent.instance : null }; this.socket.emit(event, config); - this.socket.on(`${ this.model.getModelName() }.value.result.${ this.id }`, (res: any) => + this.socket.onZone(`${ this.model.getModelName() }.value.result.${ this.id }`, (res: any) => subject.next(res.error ? Observable.throw(res.error) : res) ); return subject.asObservable(); diff --git a/tests/angular2/src/app/shared/sdk/services/core/auth.service.ts b/tests/angular2/src/app/shared/sdk/services/core/auth.service.ts index 58dd9dfe..e0aef835 100644 --- a/tests/angular2/src/app/shared/sdk/services/core/auth.service.ts +++ b/tests/angular2/src/app/shared/sdk/services/core/auth.service.ts @@ -1,7 +1,7 @@ /* tslint:disable */ declare var Object: any; import { Injectable, Inject } from '@angular/core'; -import { InternalStorage } from '../../storage/internal.storage'; +import { InternalStorage } from '../../storage/storage.swaps'; import { SDKToken, AccessToken } from '../../models/BaseModels'; /** diff --git a/tests/angular2/src/app/shared/sdk/services/core/base.service.ts b/tests/angular2/src/app/shared/sdk/services/core/base.service.ts index c836158b..52490828 100644 --- a/tests/angular2/src/app/shared/sdk/services/core/base.service.ts +++ b/tests/angular2/src/app/shared/sdk/services/core/base.service.ts @@ -107,9 +107,8 @@ export abstract class BaseLoopBackApi { body = postBody; } // Separate filter object from url params - let filter: string = ''; if (urlParams.filter) { - filter = `?filter=${ encodeURI(JSON.stringify(urlParams.filter))}`; + headers.append('filter', JSON.stringify(urlParams.filter)); delete urlParams.filter; } @@ -117,7 +116,7 @@ export abstract class BaseLoopBackApi { let request: Request = new Request({ headers : headers, method : method, - url : `${requestUrl}${filter}`, + url : requestUrl, search : Object.keys(urlParams).length > 0 ? this.searchParams.getURLSearchParams() : null, body : body ? JSON.stringify(body) : undefined @@ -133,7 +132,7 @@ export abstract class BaseLoopBackApi { * @description * Generic create method */ - public create(data: T): Observable { + public create(data: any = {}): Observable { return this.request('POST', [ LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), @@ -147,7 +146,7 @@ export abstract class BaseLoopBackApi { * @description * Generic create method */ - public createMany(data: T[]): Observable { + public createMany(data: any = {}): Observable { return this.request('POST', [ LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), @@ -199,7 +198,7 @@ export abstract class BaseLoopBackApi { LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), this.model.getModelDefinition().plural, - 'exists' + ':id/exists' ].join('/'), { id }, undefined, undefined); } /** @@ -289,7 +288,7 @@ export abstract class BaseLoopBackApi { * @description * Generic upsert method */ - public upsert(data: T): Observable { + public upsert(data: any = {}): Observable { return this.request('PUT', [ LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), @@ -303,7 +302,7 @@ export abstract class BaseLoopBackApi { * @description * Generic upsertWithWhere method */ - public upsertWithWhere(where: any = {}, data: T): Observable { + public upsertWithWhere(where: any = {}, data: any = {}): Observable { let _urlParams: any = {}; if (where) _urlParams.where = where; return this.request('PUT', [ @@ -320,7 +319,7 @@ export abstract class BaseLoopBackApi { * @description * Generic replaceOrCreate method */ - public replaceOrCreate(data: T): Observable { + public replaceOrCreate(data: any = {}): Observable { return this.request('PUT', [ LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), @@ -335,7 +334,7 @@ export abstract class BaseLoopBackApi { * @description * Generic replaceById method */ - public replaceById(id: any, data: T): Observable { + public replaceById(id: any, data: any = {}): Observable { return this.request('POST', [ LoopBackConfig.getPath(), LoopBackConfig.getApiVersion(), diff --git a/tests/angular2/src/app/shared/sdk/services/core/real.time.ts b/tests/angular2/src/app/shared/sdk/services/core/real.time.ts index f3fe65aa..fb000e8f 100644 --- a/tests/angular2/src/app/shared/sdk/services/core/real.time.ts +++ b/tests/angular2/src/app/shared/sdk/services/core/real.time.ts @@ -6,25 +6,56 @@ import { LoopBackConfig } from '../../lb.config'; import { FireLoop } from '../../models/FireLoop'; import { SocketConnections } from '../../sockets/socket.connections'; import { SDKModels } from '../custom/SDKModels'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; @Injectable() export class RealTime { public IO: IO; public FireLoop: FireLoop; + private connected: boolean = false; + private socket: any; constructor( @Inject(SocketConnections) protected connections: SocketConnections, @Inject(SDKModels) protected models: SDKModels, @Inject(LoopBackAuth) protected auth: LoopBackAuth, @Inject(JSONSearchParams) protected searchParams: JSONSearchParams - ) { - let socket: any = this.getConnection(); - this.IO = new IO(socket); - this.FireLoop = new FireLoop(socket, models); + ) {} + + disconnect(): void { + this.connected = false; + this.IO = null; + this.FireLoop = null; + this.connections.disconnect(); } getConnection(): void { return this.connections.getHandler(LoopBackConfig.getPath(), this.auth.getToken()); } + /** + * @method onReady + * @description + * Will trigger when FireLoop is Ready for broadcasting. + **/ + public onReady(): Observable { + let subject: Subject = new Subject(); + if (this.connected) { + subject.next(); + } else { + this.socket = this.getConnection(); + this.IO = new IO(this.socket); + this.FireLoop = new FireLoop(this.socket, this.models); + this.socket.on('connect', () => { + this.connected = true; + subject.next(); + }); + this.socket.on('disconnect', () => { + subject.complete(); + this.disconnect(); + }); + } + return subject.asObservable(); + } } diff --git a/tests/angular2/src/app/shared/sdk/sockets/socket.connections.ts b/tests/angular2/src/app/shared/sdk/sockets/socket.connections.ts index 1d817b2b..12e47a9d 100644 --- a/tests/angular2/src/app/shared/sdk/sockets/socket.connections.ts +++ b/tests/angular2/src/app/shared/sdk/sockets/socket.connections.ts @@ -1,5 +1,5 @@ /* tslint:disable */ -import { Injectable, Inject } from '@angular/core'; +import { Injectable, Inject, NgZone } from '@angular/core'; import { SocketDriver } from './socket.driver'; import { AccessToken } from '../models'; /** @@ -14,16 +14,25 @@ import { AccessToken } from '../models'; @Injectable() export class SocketConnections { private connections: any = {}; - private configured: boolean = false; - constructor(@Inject(SocketDriver) private driver: SocketDriver) {} + private configured: boolean = false; + constructor( + @Inject(SocketDriver) private driver: SocketDriver, + @Inject(NgZone) private zone: NgZone + ) { } getHandler(url: string, token: AccessToken) { + console.log('Getting handler for socket connections'); if (!this.connections[url]) { console.log('Creating a new connection with: ', url); - let config: any = { log: false, secure: false, forceWebsockets: true }; + let config: any = { log: false, secure: false, forceNew: true, forceWebsockets: true }; this.connections[url] = this.driver.connect(url, config); + this.connections[url].onZone = ((event: string, handler: Function) => { + this.connections[url].on(event, (data: any) => { + this.zone.run(() => handler(data)); + }); + }); this.connections[url].on('connect', () => { - if (!this.configured) - this.setupConnection(url, token, config); + if (!this.configured) + this.setupConnection(url, token, config); }); let forceConfig: any = setInterval(() => { if (!this.configured && this.connections[url].connected) { @@ -40,17 +49,35 @@ export class SocketConnections { return this.connections[url]; } + public disconnect() { + Object.keys(this.connections).forEach((connKey) => { + if (this.connections[connKey].connected) { + this.connections[connKey].disconnect(); + } + }); + this.connections = {}; + this.configured = false; + } + private setupConnection(url: string, token: AccessToken, config: any): void { this.configured = true; console.log('Connected to %s', url); - if(token.id) { + if (token.id) { + console.log('Emitting authentication', token.id); this.connections[url].emit('authentication', token); } this.connections[url].on('unauthorized', (res: any) => console.error('Unauthenticated', res)); - setInterval(() => this.connections[url].emit('lb-ping'), 15000); + let heartbeater: any = setInterval(() => { + if (this.connections[url]) { + this.connections[url].emit('lb-ping'); + } else { + clearInterval(heartbeater); + } + }, 15000); this.connections[url].on('lb-pong', (data: any) => console.info('Heartbeat: ', data)); this.connections[url].on('disconnect', (data: any) => { - console.info('Unexpected disconnection from IO - Socket IO will try to reconnect'); + this.disconnect(); + console.info('Disconnected from WebSocket server'); }); } } diff --git a/tests/angular2/src/app/shared/sdk/storage/cookie.browser.ts b/tests/angular2/src/app/shared/sdk/storage/cookie.browser.ts index baf0ad04..79b0ef6d 100644 --- a/tests/angular2/src/app/shared/sdk/storage/cookie.browser.ts +++ b/tests/angular2/src/app/shared/sdk/storage/cookie.browser.ts @@ -26,7 +26,7 @@ export class CookieBrowser { set(key: string, value: any, expires?: Date) { this.cookies[key] = value; - let cookie = `${key}=${value}${expires ? `; expires=${ expires.toUTCString() }` : ''}`; + let cookie = `${key}=${value}; path=/${expires ? `; expires=${ expires.toUTCString() }` : ''}`; window.document.cookie = cookie; } diff --git a/tests/angular2/src/app/shared/sdk/storage/internal.storage.ts b/tests/angular2/src/app/shared/sdk/storage/internal.storage.ts deleted file mode 100644 index ea25828c..00000000 --- a/tests/angular2/src/app/shared/sdk/storage/internal.storage.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @module InternalStorage - * @author Jonathan Casarrubias - * @license MIT - * @description - * The InternalStorage class is used for dependency injection swapping. - * It will be provided using factory method from different sources. - **/ -export class InternalStorage { - get(key: string): any {} - set(key: string, value: any): any {} - remove(key: string): any {} -} diff --git a/tests/angular2/src/app/shared/sdk/storage/storage.browser.ts b/tests/angular2/src/app/shared/sdk/storage/storage.browser.ts new file mode 100644 index 00000000..f5ba7b01 --- /dev/null +++ b/tests/angular2/src/app/shared/sdk/storage/storage.browser.ts @@ -0,0 +1,37 @@ +/* tslint:disable */ +import { Injectable } from '@angular/core'; +/** +* @module StorageBrowser +* @author Jonathan Casarrubias +* @license MIT +* @description +* Stand-alone cookie service for browsers +**/ +@Injectable() +export class StorageBrowser { + set(key: string, value: any) { + localStorage.setItem( + key, + typeof value === 'object' ? JSON.stringify(value) : value + ); + } + get(key: string): any { + let data: string = localStorage.getItem(key); + return this.isJSON(data) ? JSON.parse(data) : data; + } + remove(key: string): any { + if (localStorage[key]) { + localStorage.removeItem(key); + } else { + console.log('Trying to remove unexisting key: ', key); + } + } + private isJSON(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } +} diff --git a/tests/angular2/src/app/shared/sdk/storage/storage.swaps.ts b/tests/angular2/src/app/shared/sdk/storage/storage.swaps.ts new file mode 100644 index 00000000..192b2c1a --- /dev/null +++ b/tests/angular2/src/app/shared/sdk/storage/storage.swaps.ts @@ -0,0 +1,35 @@ +/** + * @module Storage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The InternalStorage class is used for dependency injection swapping. + * It will be provided using factory method from different sources. + **/ +class Storage { + get(key: string): any {} + set(key: string, value: any): any {} + remove(key: string): any {} +} +/** + * @module InternalStorage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The InternalStorage class is used for dependency injection swapping. + * It will be provided using factory method from different sources. + * This is mainly required because Angular Universal integration. + * It does inject a CookieStorage instead of LocalStorage. + **/ +export class InternalStorage extends Storage {} +/** + * @module SDKStorage + * @author Jonathan Casarrubias + * @license MIT + * @description + * The SDKStorage class is used for dependency injection swapping. + * It will be provided using factory method according the right environment. + * This is created for public usage, to allow persisting custom data + * Into the local storage API. + **/ +export class SDKStorage extends Storage {}