From 04907df3768714f77cf99324e6a06ac2574bb26d Mon Sep 17 00:00:00 2001 From: Jatin Chugh <105338943+jatin2008@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:16:09 +0530 Subject: [PATCH] [MOBILEAPPS-1701] Custom URL schema issues fixed (#2998) * Custom URL schema issues * code implementation changes for checkForMobileAppFlag * updated test cases * requested changed addressed * requested changed addressed * cspell changes * review comments addressed --- cspell.json | 130 +++++++++--------- .../open-in-app/open-in-app.component.html | 6 +- .../open-in-app/open-in-app.component.scss | 6 +- .../open-in-app/open-in-app.component.spec.ts | 24 +++- .../open-in-app/open-in-app.component.ts | 11 +- .../aca-mobile-app-switcher.service.spec.ts | 45 +++--- .../aca-mobile-app-switcher.service.ts | 46 +++++-- .../src/lib/services/app.service.ts | 4 +- projects/aca-shared/src/lib/shared.module.ts | 5 +- 9 files changed, 169 insertions(+), 108 deletions(-) diff --git a/cspell.json b/cspell.json index 980839774..59e7d6583 100644 --- a/cspell.json +++ b/cspell.json @@ -2,82 +2,84 @@ "version": "0.1", "language": "en", "words": [ - "Kerberos", - "succes", - "sharedlinks", - "Redistributable", - "fullscreen", - "LGPL", - "mincount", - "QNAME", - "PNAME", "ADDFEATURES", - "CHECKIN", - "thumbnailed", - "superadmin", - "SUPERADMIN", - "hruser", - "salesuser", - "networkidle", - "domcontentloaded", - "mimetype", - - "ngrx", - "ngstack", - "sidenav", - "injectable", - "iosamw", - "truthy", - "cryptodoc", - "mysites", "afts", "androidamw", + "cardview", + "CHECKIN", "classlist", - "folderlink", - "filelink", - "formcontrolname", - "datetimepicker", + "cryptodoc", "datatable", - "repo", - "snackbar", - "promisify", - "xdescribe", - "unfavorite", + "dateitem", + "datetimepicker", + "denysvuika", "devtools", - "gitter", - "jira", - "markdownlint", - "uploader", - "nginx", "docx", - "SOLR", - "simpletask", - "titlecase", - - "unshare", - "qshare", - "validators", + "domcontentloaded", + "errored", + "erroredSpy", + "exif", + "filelink", + "folderlink", + "formcontrolname", + "fullscreen", + "gitter", "guid", + "hruser", + "injectable", + "iosamw", + "jira", + "jsonp", + "Kerberos", + "keycodes", + "LGPL", + "markdownlint", + "mimetype", + "mincount", + "mobileapps", + "mysites", + "networkidle", + "nginx", + "ngrx", + "ngstack", + "noderef", + "pdfjs", + "PNAME", "polyfill", "polyfills", - "jsonp", - "pdfjs", - "xpath", - "tooltip", - "tooltips", - "unindent", - "exif", - "cardview", - "webm", - "keycodes", - "denysvuika", + "promisify", + "QNAME", + "qshare", + "Redistributable", + "repo", + "salesuser", + "sharedlinks", + "sidenav", + "simpletask", + "snackbar", + "SOLR", "submenu", "submenus", - "dateitem", + "succes", + "superadmin", + "thumbnailed", + "titlecase", + "tooltip", + "tooltips", + "truthy", + "unfavorite", + "unindent", + "unshare", + "uploader", + "validators", "versionable", - "erroredSpy", - "errored", - "noderef" + "webm", + "xdescribe", + "xpath" ], - "dictionaries": ["html", "en-gb", "en_US"] + "dictionaries": [ + "html", + "en-gb", + "en_US" + ] } diff --git a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.html b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.html index d3697ea2c..4433ab6b3 100644 --- a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.html +++ b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.html @@ -1,8 +1,8 @@
- -
diff --git a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.scss b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.scss index 35a050dfa..115858f5e 100644 --- a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.scss +++ b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.scss @@ -6,8 +6,12 @@ } .mat-dialog-container{ - padding: 12px; + padding: 5px; border-radius: 36px; background-color: var(--theme-blue-button-color); color: var(--theme-about-panel-background-color); } + +.open-in-app.mat-button { + overflow-x: hidden; +} diff --git a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.spec.ts b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.spec.ts index 4f4540ed3..35b7807f7 100644 --- a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.spec.ts +++ b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.spec.ts @@ -1,16 +1,29 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { By } from '@angular/platform-browser'; import { OpenInAppComponent } from './open-in-app.component'; +import { initialState, LibTestingModule } from '../../testing/lib-testing-module'; +import { provideMockStore } from '@ngrx/store/testing'; +import { TranslateModule } from '@ngx-translate/core'; describe('OpenInAppComponent', () => { let fixture: ComponentFixture; let component: OpenInAppComponent; + const mockDialogRef = { + close: jasmine.createSpy('close'), + open: jasmine.createSpy('open') + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OpenInAppComponent], - providers: [{ provide: MAT_DIALOG_DATA, useValue: { redirectUrl: 'mockRedirectUrl' } }] + imports: [LibTestingModule, TranslateModule], + providers: [ + provideMockStore({ initialState }), + { provide: MAT_DIALOG_DATA, useValue: { redirectUrl: 'mockRedirectUrl' } }, + { provide: MatDialogRef, useValue: mockDialogRef } + ] }).compileComponents(); fixture = TestBed.createComponent(OpenInAppComponent); @@ -35,4 +48,11 @@ describe('OpenInAppComponent', () => { expect(currentLocation).toBe('mockRedirectUrl'); }); + + it('should set the value `mobile_notification_expires_in` in session storage on dialog close', async () => { + sessionStorage.clear(); + component.onCloseDialog(); + expect(sessionStorage.getItem('mobile_notification_expires_in')).not.toBeNull(); + expect(mockDialogRef.close).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.ts b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.ts index 14e53cd5c..b6f66824a 100644 --- a/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.ts +++ b/projects/aca-shared/src/lib/components/open-in-app/open-in-app.component.ts @@ -24,7 +24,7 @@ */ import { Component, Inject, ViewEncapsulation } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; export interface OpenInAppDialogOptions { redirectUrl: string; @@ -41,7 +41,8 @@ export class OpenInAppComponent { constructor( @Inject(MAT_DIALOG_DATA) - public data: OpenInAppDialogOptions + public data: OpenInAppDialogOptions, + private dialog: MatDialogRef ) { if (data) { this.redirectUrl = data.redirectUrl; @@ -51,4 +52,10 @@ export class OpenInAppComponent { openInApp(): void { this.window.location.href = this.redirectUrl; } + + onCloseDialog(): void { + const time: number = new Date().getTime(); + sessionStorage.setItem('mobile_notification_expires_in', time.toString()); + this.dialog.close(); + } } diff --git a/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.spec.ts b/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.spec.ts index f16c62085..dfd94195d 100644 --- a/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.spec.ts +++ b/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.spec.ts @@ -60,7 +60,7 @@ describe('AcaMobileAppSwitcherService', () => { spyOnProperty(window.navigator, 'userAgent').and.returnValue('iphone'); const url: string = window.location.href; const iphoneUrl: string = appConfig.config.mobileAppSwitch.iphoneUrl + url; - service.showAppNotification(); + service.identifyBrowserAndSetRedirectURL(); expect(service.redirectUrl).toEqual(iphoneUrl); }); @@ -68,38 +68,49 @@ describe('AcaMobileAppSwitcherService', () => { spyOnProperty(window.navigator, 'userAgent').and.returnValue('android'); const url: string = window.location.href; const androidUrl: string = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2; - service.showAppNotification(); + service.identifyBrowserAndSetRedirectURL(); expect(service.redirectUrl).toEqual(androidUrl); }); - it('should check if `showAppNotification` function is called', () => { - const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification'); - service.checkForMobileApp(); - expect(showAppNotificationSpy).toHaveBeenCalled(); + it('should check if `identifyBrowserAndSetRedirectURL` function is called', () => { + const identifyBrowserAndSetRedirectURLSpy: jasmine.Spy<() => void> = spyOn(service, 'identifyBrowserAndSetRedirectURL'); + service.resolveExistenceOfDialog(); + expect(identifyBrowserAndSetRedirectURLSpy).toHaveBeenCalled(); }); - it('should not display `openInApp` dialog box when timeDifference is less than the session time', () => { - service.checkForMobileApp(); - const showAppNotificationSpy: jasmine.Spy<() => void> = spyOn(service, 'showAppNotification'); - service.checkForMobileApp(); - expect(showAppNotificationSpy).not.toHaveBeenCalled(); + it('should not display `openInApp` dialog box after closing the same and time difference less than session time', () => { + const time: number = new Date().getTime(); + sessionStorage.setItem('mobile_notification_expires_in', time.toString()); + const identifyBrowserAndSetRedirectURLSpy: jasmine.Spy<() => void> = spyOn(service, 'identifyBrowserAndSetRedirectURL'); + service.verifySessionExistsForDialog(); + expect(identifyBrowserAndSetRedirectURLSpy).not.toHaveBeenCalled(); }); it('should check if `openInApp` dialog box is getting opened with `iphone` url', () => { - const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp'); const url: string = window.location.href; service.redirectUrl = appConfig.config.mobileAppSwitch.iphoneUrl + url; - service.showAppNotification(); - expect(openInAppSpy).toHaveBeenCalled(); + service.identifyBrowserAndSetRedirectURL(); expect(mockDialogRef.open).toHaveBeenCalled(); }); it('should check if `openInApp` dialog box is getting opened with `android` url', () => { - const openInAppSpy: jasmine.Spy<(redirectUrl: string) => void> = spyOn(service, 'openInApp'); const url: string = window.location.href; service.redirectUrl = appConfig.config.mobileAppSwitch.androidUrlPart1 + url + appConfig.config.mobileAppSwitch.androidUrlPart2; - service.showAppNotification(); - expect(openInAppSpy).toHaveBeenCalled(); + service.identifyBrowserAndSetRedirectURL(); expect(mockDialogRef.open).toHaveBeenCalled(); }); + + it('should not display Open in app dialog when the web application is opened within mobile application', () => { + spyOn(service, 'getCurrentUrl').and.returnValue('localhost?mobileapps=true'); + const verifySessionExistsForDialogSpy: jasmine.Spy<() => void> = spyOn(service, 'verifySessionExistsForDialog'); + service.resolveExistenceOfDialog(); + expect(verifySessionExistsForDialogSpy).not.toHaveBeenCalled(); + }); + + it('should display Open in app dialog when the web application is opened in mobile browser', () => { + spyOn(service, 'getCurrentUrl').and.returnValue('localhost'); + const verifySessionExistsForDialogSpy: jasmine.Spy<() => void> = spyOn(service, 'verifySessionExistsForDialog'); + service.resolveExistenceOfDialog(); + expect(verifySessionExistsForDialogSpy).toHaveBeenCalled(); + }); }); diff --git a/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.ts b/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.ts index 24feeb8c5..0105f662d 100644 --- a/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.ts +++ b/projects/aca-shared/src/lib/services/aca-mobile-app-switcher.service.ts @@ -46,31 +46,42 @@ export class AcaMobileAppSwitcherService { this.mobileAppSwitchConfig = this.config.get('mobileAppSwitch'); } - checkForMobileApp(): void { - const currentTime: number = new Date().getTime(); - const sessionTime: string = sessionStorage.getItem('mobile_notification_expires_in'); + resolveExistenceOfDialog(): void { + const url: string = this.getCurrentUrl(); + const queryParams: string = url.split('?')[1]; + let queryParamsList = []; + let hideBanner = false; + if (queryParams !== null && queryParams !== undefined) { + queryParamsList = queryParams.split('&'); + hideBanner = queryParamsList.some((param) => param.split('=')[0] === 'mobileapps' && param.split('=')[1] === 'true'); + } + if (!hideBanner) { + this.verifySessionExistsForDialog(); + } + } + verifySessionExistsForDialog(): void { + const sessionTime: string = sessionStorage.getItem('mobile_notification_expires_in'); if (sessionTime !== null) { + const currentTime: number = new Date().getTime(); const sessionConvertedTime: number = parseFloat(sessionTime); const timeDifference: number = (currentTime - sessionConvertedTime) / (1000 * 60 * 60); const sessionTimeForOpenAppDialogDisplay: number = parseFloat(this.mobileAppSwitchConfig.sessionTimeForOpenAppDialogDisplay); if (timeDifference > sessionTimeForOpenAppDialogDisplay) { - this.showAppNotification(); + this.clearSessionExpireTime(); + this.identifyBrowserAndSetRedirectURL(); } } else { - this.showAppNotification(); + this.identifyBrowserAndSetRedirectURL(); } } - showAppNotification(): void { + identifyBrowserAndSetRedirectURL(): void { const ua: string = navigator.userAgent.toLowerCase(); const isAndroid: boolean = ua.indexOf('android') > -1; const isIOS: boolean = ua.indexOf('iphone') > -1 || ua.indexOf('ipad') > -1 || ua.indexOf('ipod') > -1; - const currentUrl: string = window.location.href; - const time: number = new Date().getTime(); - - sessionStorage.setItem('mobile_notification_expires_in', time.toString()); + const currentUrl: string = this.getCurrentUrl(); if (isIOS === true) { this.redirectUrl = this.mobileAppSwitchConfig.iphoneUrl + currentUrl; @@ -79,22 +90,27 @@ export class AcaMobileAppSwitcherService { } if (this.redirectUrl !== undefined && this.redirectUrl !== null) { - this.openInApp(this.redirectUrl); + this.openDialog(this.redirectUrl); } } - openInApp(redirectUrl: string): void { + openDialog(redirectUrl: string): void { this.dialog.open(OpenInAppComponent, { data: { redirectUrl }, - width: '75%', + hasBackdrop: false, + width: 'auto', role: 'dialog', - position: { bottom: '50px' } + position: { bottom: '20px' } }); } - reset(): void { + clearSessionExpireTime(): void { sessionStorage.removeItem('mobile_notification_expires_in'); } + + getCurrentUrl(): string { + return window.location.href; + } } diff --git a/projects/aca-shared/src/lib/services/app.service.ts b/projects/aca-shared/src/lib/services/app.service.ts index 5ba628e35..bc442c91f 100644 --- a/projects/aca-shared/src/lib/services/app.service.ts +++ b/projects/aca-shared/src/lib/services/app.service.ts @@ -182,9 +182,9 @@ export class AppService implements OnDestroy { const isMobileSwitchEnabled: boolean = this.config.get('mobileAppSwitch.enabled', false); if (isMobileSwitchEnabled) { - this.acaMobileAppSwitcherService.checkForMobileApp(); + this.acaMobileAppSwitcherService.resolveExistenceOfDialog(); } else { - this.acaMobileAppSwitcherService.reset(); + this.acaMobileAppSwitcherService.clearSessionExpireTime(); } } diff --git a/projects/aca-shared/src/lib/shared.module.ts b/projects/aca-shared/src/lib/shared.module.ts index 16e53b0d8..1378be197 100644 --- a/projects/aca-shared/src/lib/shared.module.ts +++ b/projects/aca-shared/src/lib/shared.module.ts @@ -32,10 +32,11 @@ import { OpenInAppComponent } from './components/open-in-app/open-in-app.compone import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatDialogModule } from '@angular/material/dialog'; +import { TranslateModule } from '@ngx-translate/core'; @NgModule({ - imports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule], - exports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule], + imports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule, TranslateModule], + exports: [ContextActionsModule, MatButtonModule, MatIconModule, MatDialogModule, TranslateModule], declarations: [OpenInAppComponent] }) export class SharedModule {