[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
This commit is contained in:
Jatin Chugh 2023-02-20 21:16:09 +05:30 committed by GitHub
parent 472a19b67d
commit 04907df376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 108 deletions

View File

@ -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"
]
}

View File

@ -1,8 +1,8 @@
<div class="container">
<button mat-button (click)="openInApp()" data-automation-id="open-in-app-button">
<span>{{ 'APP.DIALOGS.MOBILE_APP.MOBILE_APP_BUTTON_LABEL' }}</span>
<button mat-button (click)="openInApp()" data-automation-id="open-in-app-button" class="open-in-app">
<span>{{ 'APP.DIALOGS.MOBILE_APP.MOBILE_APP_BUTTON_LABEL' | translate }}</span>
</button>
<button mat-button mat-dialog-close>
<button mat-button (click)="onCloseDialog()">
<mat-icon>close</mat-icon>
</button>
</div>

View File

@ -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;
}

View File

@ -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<OpenInAppComponent>;
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();
});
});

View File

@ -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<OpenInAppComponent>
) {
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();
}
}

View File

@ -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();
});
});

View File

@ -46,31 +46,42 @@ export class AcaMobileAppSwitcherService {
this.mobileAppSwitchConfig = this.config.get<MobileAppSwitchConfigurationOptions>('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;
}
}

View File

@ -182,9 +182,9 @@ export class AppService implements OnDestroy {
const isMobileSwitchEnabled: boolean = this.config.get<boolean>('mobileAppSwitch.enabled', false);
if (isMobileSwitchEnabled) {
this.acaMobileAppSwitcherService.checkForMobileApp();
this.acaMobileAppSwitcherService.resolveExistenceOfDialog();
} else {
this.acaMobileAppSwitcherService.reset();
this.acaMobileAppSwitcherService.clearSessionExpireTime();
}
}

View File

@ -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 {