diff --git a/packages-exp/auth-exp/src/core/util/browser.test.ts b/packages-exp/auth-exp/src/core/util/browser.test.ts new file mode 100644 index 00000000000..8a99a13a49a --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/browser.test.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; +import { getBrowserName, BrowserName } from './browser'; + +describe('getBrowserName', () => { + it('should recognize Opera', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36 OPR/36.0.2130.74'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.OPERA); + }); + + it('should recognize IE', () => { + const userAgent = + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.IE); + }); + + it('should recognize Edge', () => { + const userAgent = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.EDGE); + }); + + it('should recognize Firefox', () => { + const userAgent = + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.FIREFOX); + }); + + it('should recognize Silk', () => { + const userAgent = + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Silk/44.1.54 like Chrome/44.0.2403.63 Safari/537.36'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.SILK); + }); + + it('should recognize Safari', () => { + const userAgent = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11-4) AppleWebKit/601.5.17 (KHTML, like Gecko) Version/9.1 Safari/601.5.17'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.SAFARI); + }); + + it('should recognize Chrome', () => { + const userAgent = + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.CHROME); + }); + + it('should recognize Android', () => { + const userAgent = + 'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.ANDROID); + }); + + it('should recognize Blackberry', () => { + const userAgent = + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.BLACKBERRY); + }); + + it('should recognize IE Mobile', () => { + const userAgent = + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0;Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.IEMOBILE); + }); + + it('should recognize WebOS', () => { + const userAgent = + 'Mozilla/5.0 (webOS/1.3; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Desktop/1.0'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.WEBOS); + }); + + it('should recognize an unlisted browser', () => { + const userAgent = + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Awesome/2.0.012'; + expect(getBrowserName(userAgent)).to.eq('Awesome'); + }); + + it('should default to Other', () => { + const userAgent = + 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12D508 [FBAN/FBIOS;FBAV/27.0.0.10.12;FBBV/8291884;FBDV/iPhone7,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/8.2;FBSS/3; FBCR/vodafoneIE;FBID/phone;FBLC/en_US;FBOP/5]'; + expect(getBrowserName(userAgent)).to.eq(BrowserName.OTHER); + }); +}); diff --git a/packages-exp/auth-exp/src/core/util/browser.ts b/packages-exp/auth-exp/src/core/util/browser.ts new file mode 100644 index 00000000000..5020f00a447 --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/browser.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Enums for Browser name. + */ +export enum BrowserName { + ANDROID = 'Android', + BLACKBERRY = 'Blackberry', + EDGE = 'Edge', + FIREFOX = 'Firefox', + IE = 'IE', + IEMOBILE = 'IEMobile', + OPERA = 'Opera', + OTHER = 'Other', + CHROME = 'Chrome', + SAFARI = 'Safari', + SILK = 'Silk', + WEBOS = 'Webos' +} + +/** + * Determine the browser for the purposes of reporting usage to the API + */ +export function getBrowserName(userAgent: string): BrowserName | string { + const ua = userAgent.toLowerCase(); + if (ua.includes('opera/') || ua.includes('opr/') || ua.includes('opios/')) { + return BrowserName.OPERA; + } else if (ua.includes('iemobile')) { + // Windows phone IEMobile browser. + return BrowserName.IEMOBILE; + } else if (ua.includes('msie') || ua.includes('trident/')) { + return BrowserName.IE; + } else if (ua.includes('edge/')) { + return BrowserName.EDGE; + } else if (ua.includes('firefox/')) { + return BrowserName.FIREFOX; + } else if (ua.includes('silk/')) { + return BrowserName.SILK; + } else if (ua.includes('blackberry')) { + // Blackberry browser. + return BrowserName.BLACKBERRY; + } else if (ua.includes('webos')) { + // WebOS default browser. + return BrowserName.WEBOS; + } else if ( + ua.includes('safari/') && + !ua.includes('chrome/') && + !ua.includes('crios/') && + !ua.includes('android') + ) { + return BrowserName.SAFARI; + } else if ( + (ua.includes('chrome/') || ua.includes('crios/')) && + !ua.includes('edge/') + ) { + return BrowserName.CHROME; + } else if (ua.includes('android')) { + // Android stock browser. + return BrowserName.ANDROID; + } else { + // Most modern browsers have name/version at end of user agent string. + const re = /([a-zA-Z\d\.]+)\/[a-zA-Z\d\.]*$/; + const matches = userAgent.match(re); + if (matches?.length === 2) { + return matches[1]; + } + } + return BrowserName.OTHER; +} diff --git a/packages-exp/auth-exp/src/core/util/version.test.ts b/packages-exp/auth-exp/src/core/util/version.test.ts new file mode 100644 index 00000000000..eea1c2ffe55 --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/version.test.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SDK_VERSION } from '@firebase/app-exp'; +import { expect } from 'chai'; +import { ClientPlatform, getClientVersion } from './version'; + +describe('getClientVersion', () => { + context('browser', () => { + it('should set the correct version', () => { + expect(getClientVersion(ClientPlatform.BROWSER)).to.eq( + `Chrome/JsCore/${SDK_VERSION}/FirebaseCore-web` + ); + }); + }); + + context('worker', () => { + it('should set the correct version', () => { + expect(getClientVersion(ClientPlatform.WORKER)).to.eq( + `Chrome-Worker/JsCore/${SDK_VERSION}/FirebaseCore-web` + ); + }); + }); + + context('React Native', () => { + it('should set the correct version', () => { + expect(getClientVersion(ClientPlatform.REACT_NATIVE)).to.eq( + `ReactNative/JsCore/${SDK_VERSION}/FirebaseCore-web` + ); + }); + }); +}); diff --git a/packages-exp/auth-exp/src/core/util/version.ts b/packages-exp/auth-exp/src/core/util/version.ts new file mode 100644 index 00000000000..470213e764f --- /dev/null +++ b/packages-exp/auth-exp/src/core/util/version.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SDK_VERSION } from '@firebase/app-exp'; +import { getBrowserName } from './browser'; +import { getUA } from '@firebase/util'; + +const CLIENT_IMPLEMENTATION = 'JsCore'; + +export enum ClientPlatform { + BROWSER = 'Browser', + NODE = 'Node', + REACT_NATIVE = 'ReactNative', + WORKER = 'Worker' +} + +enum ClientFramework { + // No other framework used. + DEFAULT = 'FirebaseCore-web', + // Firebase Auth used with FirebaseUI-web. + // TODO: Pass this in when used in conjunction with FirebaseUI + FIREBASEUI = 'FirebaseUI-web' +} + +/* + * Determine the SDK version string + * + * TODO: This should be set on the Auth object during initialization + */ +export function getClientVersion(clientPlatform: ClientPlatform): string { + let reportedPlatform: string; + switch (clientPlatform) { + case ClientPlatform.BROWSER: + // In a browser environment, report the browser name. + reportedPlatform = getBrowserName(getUA()); + break; + case ClientPlatform.WORKER: + // Technically a worker runs from a browser but we need to differentiate a + // worker from a browser. + // For example: Chrome-Worker/JsCore/4.9.1/FirebaseCore-web. + reportedPlatform = `${getBrowserName(getUA())}-${clientPlatform}`; + break; + default: + reportedPlatform = clientPlatform; + } + return `${reportedPlatform}/${CLIENT_IMPLEMENTATION}/${SDK_VERSION}/${ClientFramework.DEFAULT}`; +}