diff --git a/src/platform/platform.ts b/src/platform/platform.ts index 523bd8a6e5..8467c8b83e 100644 --- a/src/platform/platform.ts +++ b/src/platform/platform.ts @@ -1,7 +1,8 @@ -import { EventEmitter, NgZone } from '@angular/core'; +import { EventEmitter, NgZone, OpaqueToken } from '@angular/core'; -import { getQuerystring } from '../util/util'; +import { QueryParams } from './query-params'; import { ready, windowDimensions, flushDimensionCache } from '../util/dom'; +import { setupPlatformRegistry } from './registry'; /** * @name Platform @@ -28,13 +29,11 @@ import { ready, windowDimensions, flushDimensionCache } from '../util/dom'; * @demo /docs/v2/demos/platform/ */ export class Platform { - private _platforms: Array; private _versions: {[name: string]: PlatformVersion} = {}; private _dir: string; private _lang: string; - private _url: string; - private _qs: any; private _ua: string; + private _qp: QueryParams; private _bPlt: string; private _onResizes: Array = []; private _readyPromise: Promise; @@ -42,10 +41,13 @@ export class Platform { private _resizeTm: any; private _bbActions: BackButtonAction[] = []; + /** @private */ zone: NgZone; - constructor(platforms: string[] = []) { - this._platforms = platforms; + /** @private */ + _platforms: string[] = []; + + constructor() { this._readyPromise = new Promise(res => { this._readyResolve = res; } ); this.backButton.subscribe(() => { @@ -172,7 +174,7 @@ export class Platform { * @private */ version(): PlatformVersion { - for (let platformName in this._versions) { + for (var platformName in this._versions) { if (this._versions[platformName]) { return this._versions[platformName]; } @@ -358,7 +360,7 @@ export class Platform { * the its back button action. */ registerBackButtonAction(fn: Function, priority: number = 0): Function { - let action: BackButtonAction = {fn, priority}; + const action: BackButtonAction = {fn, priority}; this._bbActions.push(action); @@ -391,28 +393,6 @@ export class Platform { // Getter/Setter Methods // ********************************************** - /** - * @private - */ - setUrl(url: string) { - this._url = url; - this._qs = getQuerystring(url); - } - - /** - * @private - */ - url(): string { - return this._url; - } - - /** - * @private - */ - query(key: string): string { - return (this._qs || {})[key]; - } - /** * @private */ @@ -420,6 +400,13 @@ export class Platform { this._ua = userAgent; } + /** + * @private + */ + setQueryParams(queryParams: QueryParams) { + this._qp = queryParams; + } + /** * @private */ @@ -477,7 +464,7 @@ export class Platform { * @private */ windowResize() { - let self = this; + const self = this; clearTimeout(self._resizeTm); self._resizeTm = setTimeout(() => { @@ -501,7 +488,7 @@ export class Platform { self._onResizes.push(cb); return function() { - let index = self._onResizes.indexOf(cb); + const index = self._onResizes.indexOf(cb); if (index > -1) { self._onResizes.splice(index, 1); } @@ -544,7 +531,7 @@ export class Platform { * @private */ testQuery(queryValue: string, queryTestValue: string): boolean { - let valueSplit = queryValue.toLowerCase().split(';'); + const valueSplit = queryValue.toLowerCase().split(';'); return valueSplit.indexOf(queryTestValue) > -1; } @@ -552,7 +539,7 @@ export class Platform { * @private */ testNavigatorPlatform(navigatorPlatformExpression: string): boolean { - let rgx = new RegExp(navigatorPlatformExpression, 'i'); + const rgx = new RegExp(navigatorPlatformExpression, 'i'); return rgx.test(this._bPlt); } @@ -561,7 +548,7 @@ export class Platform { */ matchUserAgentVersion(userAgentExpression: RegExp): any { if (this._ua && userAgentExpression) { - let val = this._ua.match(userAgentExpression); + const val = this._ua.match(userAgentExpression); if (val) { return { major: val[1], @@ -575,14 +562,14 @@ export class Platform { * @private */ isPlatformMatch(queryStringName: string, userAgentAtLeastHas?: string[], userAgentMustNotHave: string[] = []): boolean { - let queryValue = this.query('ionicplatform'); + const queryValue = this._qp.get('ionicplatform'); if (queryValue) { return this.testQuery(queryValue, queryStringName); } userAgentAtLeastHas = userAgentAtLeastHas || [queryStringName]; - let userAgent = this._ua.toLowerCase(); + const userAgent = this._ua.toLowerCase(); for (var i = 0; i < userAgentAtLeastHas.length; i++) { if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) { @@ -598,13 +585,10 @@ export class Platform { return false; } - /** - * @private - */ + /** @private */ load() { let rootPlatformNode: PlatformNode; let enginePlatformNode: PlatformNode; - let self = this; // figure out the most specific platform and active engine let tmpPlatform: PlatformNode; @@ -779,17 +763,17 @@ class PlatformNode { return this; } - let platform: PlatformNode = null; - let rootPlatform: PlatformNode = null; + let platformNode: PlatformNode = null; + let rootPlatformNode: PlatformNode = null; for (let i = 0; i < parents.length; i++) { - platform = new PlatformNode(parents[i]); - platform.child = this; + platformNode = new PlatformNode(parents[i]); + platformNode.child = this; - rootPlatform = platform.getRoot(p); - if (rootPlatform) { - this.parent = platform; - return rootPlatform; + rootPlatformNode = platformNode.getRoot(p); + if (rootPlatformNode) { + this.parent = platformNode; + return rootPlatformNode; } } } @@ -841,3 +825,38 @@ interface BackButtonAction { fn: Function; priority: number; } + +export function setupPlatform(queryParams: QueryParams, userAgent: string, navigatorPlatform: string, dir: string, lang: string, zone: NgZone): Platform { + setupPlatformRegistry(); + + const p = new Platform(); + p.setUserAgent(userAgent); + p.setQueryParams(queryParams); + p.setNavigatorPlatform(navigatorPlatform); + p.setDir(dir, false); + p.setLang(lang, false); + p.setZone(zone); + p.load(); + return p; +} + +export const UserAgent = new OpaqueToken('USERAGENT'); +export const UserNavigatorPlatform = new OpaqueToken('USERNAVPLT'); +export const UserDir = new OpaqueToken('USERDIR'); +export const UserLang = new OpaqueToken('USERLANG'); + + +export function providePlatform(): any { + return { + provide: Platform, + useFactory: setupPlatform, + deps: [ + QueryParams, + UserAgent, + UserNavigatorPlatform, + UserDir, + UserLang, + NgZone + ] + }; +} diff --git a/src/platform/query-params.ts b/src/platform/query-params.ts new file mode 100644 index 0000000000..2b60955424 --- /dev/null +++ b/src/platform/query-params.ts @@ -0,0 +1,44 @@ +import { OpaqueToken } from '@angular/core'; + + +export class QueryParams { + data: {[key: string]: any} = {}; + + constructor(url: string) { + if (url) { + const startIndex = url.indexOf('?'); + if (startIndex > -1) { + const queries = url.slice(startIndex + 1).split('&'); + for (var i = 0; i < queries.length; i++) { + if (queries[i].indexOf('=') > 0) { + var split = queries[i].split('='); + if (split.length > 1) { + this.data[split[0].toLowerCase()] = split[1].split('#')[0]; + } + } + } + } + } + } + + get(key: string): any { + return this.data[key.toLowerCase()]; + } + +} + +export const UserUrl = new OpaqueToken('USERURL'); + +export function setupQueryParams(url: string): QueryParams { + return new QueryParams(url); +} + +export function provideQueryParams(): any { + return { + provide: QueryParams, + useFactory: setupQueryParams, + deps: [ + UserUrl + ] + }; +} diff --git a/src/platform/registry.ts b/src/platform/registry.ts index 0a67c15ee0..38a6f9877e 100644 --- a/src/platform/registry.ts +++ b/src/platform/registry.ts @@ -5,22 +5,21 @@ const win: any = window; const doc: any = document; -Platform.register({ +const PLATFORM_CORE: any = { name: 'core', settings: { mode: 'md', keyboardHeight: 290 } -}); -Platform.setDefault('core'); +}; -Platform.register({ +const PLATFORM_MOBILE: any = { name: 'mobile' -}); +}; -Platform.register({ +const PLATFORM_PHABLET: any = { name: 'phablet', isMatch(p: Platform) { let smallest = Math.min(p.width(), p.height()); @@ -28,10 +27,10 @@ Platform.register({ return (smallest > 390 && smallest < 520) && (largest > 620 && largest < 800); } -}); +}; -Platform.register({ +const PLATFORM_TABLET: any = { name: 'tablet', isMatch(p: Platform) { let smallest = Math.min(p.width(), p.height()); @@ -39,10 +38,10 @@ Platform.register({ return (smallest > 460 && smallest < 820) && (largest > 780 && largest < 1400); } -}); +}; -Platform.register({ +const PLATFORM_ANDROID: any = { name: 'android', superset: 'mobile', subsets: [ @@ -81,11 +80,10 @@ Platform.register({ versionParser(p: Platform): any { return p.matchUserAgentVersion(/Android (\d+).(\d+)?/); } -}); +}; - -Platform.register({ +const PLATFORM_IOS: any = { name: 'ios', superset: 'mobile', subsets: [ @@ -113,10 +111,10 @@ Platform.register({ versionParser(p: Platform): any { return p.matchUserAgentVersion(/OS (\d+)_(\d+)?/); } -}); +}; -Platform.register({ +const PLATFORM_IPAD: any = { name: 'ipad', superset: 'tablet', settings: { @@ -125,10 +123,10 @@ Platform.register({ isMatch(p: Platform): boolean { return p.isPlatformMatch('ipad'); } -}); +}; -Platform.register({ +const PLATFORM_IPHONE: any = { name: 'iphone', subsets: [ 'phablet' @@ -136,10 +134,10 @@ Platform.register({ isMatch(p: Platform): boolean { return p.isPlatformMatch('iphone'); } -}); +}; -Platform.register({ +const PLATFORM_WINDOWS: any = { name: 'windows', superset: 'mobile', subsets: [ @@ -157,10 +155,10 @@ Platform.register({ versionParser(p: Platform): any { return p.matchUserAgentVersion(/Windows Phone (\d+).(\d+)?/); } -}); +}; -Platform.register({ +const PLATFORM_CORDOVA: any = { name: 'cordova', isEngine: true, initialize: function(p: Platform) { @@ -205,7 +203,7 @@ Platform.register({ isMatch(): boolean { return !!(win.cordova || win.PhoneGap || win.phonegap); } -}); +}; function isIOSDevice(p: Platform) { @@ -215,3 +213,17 @@ function isIOSDevice(p: Platform) { // an actual iPad will return true, a chrome dev tools iPad will return false return p.testNavigatorPlatform('iphone|ipad|ipod'); } + +export function setupPlatformRegistry() { + Platform.register(PLATFORM_CORE); + Platform.register(PLATFORM_MOBILE); + Platform.register(PLATFORM_PHABLET); + Platform.register(PLATFORM_TABLET); + Platform.register(PLATFORM_ANDROID); + Platform.register(PLATFORM_IOS); + Platform.register(PLATFORM_IPAD); + Platform.register(PLATFORM_IPHONE); + Platform.register(PLATFORM_WINDOWS); + Platform.register(PLATFORM_CORDOVA); + Platform.setDefault('core'); +} diff --git a/src/platform/test/platform.spec.ts b/src/platform/test/platform.spec.ts index 01ee3bd086..0f6a38a8ea 100644 --- a/src/platform/test/platform.spec.ts +++ b/src/platform/test/platform.spec.ts @@ -1,6 +1,8 @@ -import { Platform } from '../../../src'; +import { Platform } from '../platform'; +import { QueryParams } from '../query-params'; +import { setupPlatformRegistry } from '../registry'; +import { setupModeConfig } from '../../config/modes'; -export function run() { describe('Platform', () => { @@ -69,9 +71,10 @@ describe('Platform', () => { }); - it('should set core as the fallback', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent('idk'); platform.load(); @@ -80,23 +83,10 @@ describe('Platform', () => { expect(platform.is('core')).toEqual(true); }); - it('should get case insensitive querystring value', () => { - let platform = new Platform(); - platform.setUrl('/?KEY=value'); - - expect(platform.query('key')).toEqual('value'); - }); - - it('should get querystring value', () => { - let platform = new Platform(); - platform.setUrl('/?key=value'); - - expect(platform.query('key')).toEqual('value'); - }); - it('should set windows via querystring', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=windows'); + let qp = new QueryParams('/?ionicplatform=windows'); + platform.setQueryParams(qp); platform.load(); expect(platform.is('core')).toEqual(false); @@ -108,7 +98,8 @@ describe('Platform', () => { it('should set ios via querystring', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=ios'); + let qp = new QueryParams('/?ionicplatform=ios'); + platform.setQueryParams(qp); platform.load(); expect(platform.is('core')).toEqual(false); @@ -120,7 +111,8 @@ describe('Platform', () => { it('should set windows via querystring, even with android user agent', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=windows'); + let qp = new QueryParams('/?ionicplatform=windows'); + platform.setQueryParams(qp); platform.setUserAgent(ANDROID_UA); platform.load(); @@ -132,7 +124,8 @@ describe('Platform', () => { it('should set ios via querystring, even with android user agent', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=ios'); + let qp = new QueryParams('/?ionicplatform=ios'); + platform.setQueryParams(qp); platform.setUserAgent(ANDROID_UA); platform.load(); @@ -144,7 +137,8 @@ describe('Platform', () => { it('should set android via querystring', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=android'); + let qp = new QueryParams('/?ionicplatform=android'); + platform.setQueryParams(qp); platform.load(); expect(platform.is('core')).toEqual(false); @@ -155,7 +149,8 @@ describe('Platform', () => { it('should set android via querystring, even with ios user agent', () => { let platform = new Platform(); - platform.setUrl('/?ionicplatform=android'); + let qp = new QueryParams('/?ionicplatform=android'); + platform.setQueryParams(qp); platform.setUserAgent(IPHONE_UA); platform.load(); @@ -167,6 +162,8 @@ describe('Platform', () => { it('should set windows platform via user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS_PHONE_UA); platform.load(); @@ -179,6 +176,8 @@ describe('Platform', () => { it('should set windows platform via windows8 mobile user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS8_PHONE_UA); platform.load(); @@ -191,6 +190,8 @@ describe('Platform', () => { it('should set windows platform via windows7 mobile user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS7_PHONE_UA); platform.load(); @@ -203,6 +204,8 @@ describe('Platform', () => { it('should set android via user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(ANDROID_UA); platform.load(); @@ -215,6 +218,8 @@ describe('Platform', () => { it('should set iphone via user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(IPHONE_UA); platform.load(); @@ -229,6 +234,8 @@ describe('Platform', () => { it('should set ipad via user agent', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(IPAD_UA); platform.load(); @@ -243,6 +250,8 @@ describe('Platform', () => { it('should set core platform for osx desktop firefox', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(OSX_10_FIREFOX_43_UA); platform.load(); @@ -257,6 +266,8 @@ describe('Platform', () => { it('should set core platform for osx desktop safari', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(OSX_10_SAFARI_9_UA); platform.load(); @@ -271,6 +282,8 @@ describe('Platform', () => { it('should set core platform for osx desktop chrome', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(OSX_10_CHROME_49_UA); platform.load(); @@ -285,6 +298,8 @@ describe('Platform', () => { it('should set core platform for windows desktop chrome', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS_10_CHROME_40_UA); platform.load(); @@ -299,6 +314,8 @@ describe('Platform', () => { it('should set core platform for windows desktop edge', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS_10_EDGE_12_UA); platform.load(); @@ -313,6 +330,8 @@ describe('Platform', () => { it('should set core platform for windows desktop IE', () => { let platform = new Platform(); + let qp = new QueryParams(''); + platform.setQueryParams(qp); platform.setUserAgent(WINDOWS_8_IE_11_UA); platform.load(); @@ -325,9 +344,12 @@ describe('Platform', () => { expect(platform.is('tablet')).toEqual(false); }); -}); + beforeEach(() => { + setupModeConfig(); + setupPlatformRegistry(); + }); -} +}); const OSX_10_FIREFOX_43_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0'; const OSX_10_SAFARI_9_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4'; diff --git a/src/platform/test/query-params.spec.ts b/src/platform/test/query-params.spec.ts new file mode 100644 index 0000000000..883283bd38 --- /dev/null +++ b/src/platform/test/query-params.spec.ts @@ -0,0 +1,99 @@ +import { QueryParams } from '../query-params'; +import '../registry'; +import '../../config/modes'; + + +describe('QueryParams', () => { + + it('should get case insensitive querystring value', () => { + let qp = new QueryParams('/?KEY=value'); + expect(qp.get('key')).toEqual('value'); + }); + + it('should get querystring value', () => { + let qp = new QueryParams('/?key=value'); + expect(qp.get('key')).toEqual('value'); + }); + + it('should have no entries for empty url', () => { + let qp = new QueryParams(''); + expect(qp.data).toEqual({}); + + qp = new QueryParams(null); + expect(qp.data).toEqual({}); + + qp = new QueryParams(undefined); + expect(qp.data).toEqual({}); + }); + + it('should have no entries when without ?', () => { + let qp = new QueryParams('http://localhost:1234/'); + expect(qp.data).toEqual({}); + }); + + it('should have no entries with only ?', () => { + let qp = new QueryParams('http://localhost:1234/?'); + expect(qp.data).toEqual({}); + }); + + it('should have no entries for key with no =', () => { + let qp = new QueryParams('http://localhost:1234/?key'); + expect(qp.data).toEqual({}); + }); + + it('should have no entries with only #?', () => { + let qp = new QueryParams('http://localhost:1234/#?'); + expect(qp.data).toEqual({}); + }); + + it('should have no entries with only #?=', () => { + let qp = new QueryParams('http://localhost:1234/#?='); + expect(qp.data).toEqual({}); + }); + + it('should have no entries for url with no "?" character', () => { + let qp = new QueryParams('http://localhost:1234/#key1=1&key2=2'); + expect(qp.data).toEqual({}); + }); + + it('should contain key/value entries for all the parameters after "?" character', () => { + let qp = new QueryParams('http://localhost:1234/#key1=1&key2x=2x?key3=3&key4=4'); + expect(qp.data).toEqual({ + key3: '3', + key4: '4' + }); + }); + + it('should lowercase param keys', () => { + let qp = new QueryParams('http://localhost:1234/#?KEY1=1&kEy2=2'); + expect(qp.data).toEqual({ + key1: '1', + key2: '2' + }); + }); + + it('should not include any values when # comes after ?', () => { + let qp = new QueryParams('http://localhost:1234/?key1=1#key2=2'); + expect(qp.data).toEqual({ + key1: '1' + }); + }); + + it('should ignore empty ?& and &&', () => { + let qp = new QueryParams('http://localhost:1234/#?&&'); + expect(qp.data).toEqual({}); + + qp = new QueryParams('http://localhost:1234/#?&&key1=1&key2=2&&'); + expect(qp.data).toEqual({ + key1: '1', + key2: '2' + }); + }); + + it('should get "" when key has no value', () => { + let qp = new QueryParams('http://localhost:1234/#?key='); + expect(qp.data).toEqual({ + key: '' + }); + }); +});