MOBILE-2253 emulator: Mock InAppBrowser

main
Dani Palou 2017-12-08 13:45:30 +01:00
parent 26d5690c36
commit 4a423f9cd3
7 changed files with 372 additions and 66 deletions

View File

@ -0,0 +1,253 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CoreAppProvider } from '../../../providers/app';
import { CoreFileProvider } from '../../../providers/file';
import { CoreUrlUtilsProvider } from '../../../providers/utils/url';
import { Observable, Observer } from 'rxjs';
import { InAppBrowserObject, InAppBrowserEvent } from '@ionic-native/in-app-browser';
/**
* Emulates the Cordova InAppBrowserObject in desktop apps.
*/
export class InAppBrowserObjectMock extends InAppBrowserObject {
protected window;
protected browserWindow;
protected screen;
protected isSSO: boolean;
protected isLinux: boolean;
constructor(appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private urlUtils: CoreUrlUtilsProvider,
private url: string, target?: string, options = '') {
super(url, target, options);
if (!appProvider.isDesktop()) {
// This plugin is only supported in desktop.
return;
}
this.browserWindow = require('electron').remote.BrowserWindow;
this.screen = require('electron').screen;
this.isSSO = !!(url && url.match(/\/launch\.php\?service=.+&passport=/));
this.isLinux = appProvider.isLinux();
let width = 800,
height = 600,
display,
bwOptions: any = {};
if (screen) {
display = this.screen.getPrimaryDisplay();
if (display && display.workArea) {
width = display.workArea.width || width;
height = display.workArea.height || height;
}
}
// Create the BrowserWindow options based on the received options.
bwOptions.width = width;
bwOptions.height = height;
if (options.indexOf('hidden=yes') != -1) {
bwOptions.show = false;
}
if (options.indexOf('location=no') != -1) {
bwOptions.frame = false;
}
if (options.indexOf('fullscreen=yes') != -1) {
bwOptions.fullscreen = true;
}
this.window = new this.browserWindow(bwOptions);
this.window.loadURL(url);
if (this.isLinux && this.isSSO) {
// SSO in Linux. Simulate it's an iOS device so we can retrieve the launch URL.
// This is needed because custom URL scheme is not supported in Linux.
let userAgent = 'Mozilla/5.0 (iPad) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60';
this.window.webContents.setUserAgent(userAgent);
}
}
/**
* Close the window.
*/
close() : void {
this.window.close();
}
/**
* Execute a JS script.
*
* @param {any} details Details of the script to run, specifying either a file or code key.
* @return {Promise<any>} Promise resolved when done.
*/
executeScript(details: any) : Promise<any> {
return new Promise((resolve, reject) => {
if (details.code) {
this.window.webContents.executeJavaScript(details.code, false, resolve);
} else if (details.file) {
this.fileProvider.readFile(details.file).then((code) => {
this.window.webContents.executeJavaScript(code, false, resolve);
}).catch(reject);
} else {
reject('executeScript requires exactly one of code or file to be specified');
}
});
}
/**
* Recursive function to get the launch URL from the contents of a BrowserWindow.
*
* @param {number} [retry=0] Retry number.
* @return {Promise<string>} Promise resolved with the launch URL.
*/
protected getLaunchUrl(retry = 0) : Promise<string> {
return new Promise((resolve, reject) => {
// Execute Javascript to retrieve the launch link.
let jsCode = 'var el = document.querySelector("#launchapp"); el && el.href;',
found = false;
this.window.webContents.executeJavaScript(jsCode).then((launchUrl) => {
found = true;
resolve(launchUrl);
});
setTimeout(() => {
if (found) {
// URL found, stop.
} else if (retry > 5) {
// Waited enough, stop.
reject();
} else {
this.getLaunchUrl(retry + 1).then(resolve, reject);
}
}, 300);
});
}
/**
* Hide the window.
*/
hide() : void {
this.window.hide();
}
/**
* Insert CSS.
*
* @param {any} details Details of the CSS to insert, specifying either a file or code key.
* @return {Promise<any>} Promise resolved when done.
*/
insertCSS(details: any) : Promise<any> {
return new Promise((resolve, reject) => {
if (details.code) {
this.window.webContents.insertCSS(details.code);
resolve();
} else if (details.file) {
this.fileProvider.readFile(details.file).then((code) => {
this.window.webContents.insertCSS(code);
resolve();
}).catch(reject);
} else {
reject('insertCSS requires exactly one of code or file to be specified');
}
});
}
/**
* Listen to events happening.
*
* @param {string} name Name of the event.
* @return {Observable<InAppBrowserEvent>} Observable that will listen to the event on subscribe, and will stop listening
* to the event on unsubscribe.
*/
on(name: string) : Observable<InAppBrowserEvent> {
// Create the observable.
return new Observable<InAppBrowserEvent>((observer: Observer<InAppBrowserEvent>) => {
// Helper functions to handle events.
let received = (event, url) => {
try {
event.url = url || this.window.getURL();
event.type = name;
observer.next(event);
} catch(ex) {}
},
finishLoad = (event) => {
// Check if user is back to launch page.
if (this.urlUtils.removeUrlParams(this.url) == this.urlUtils.removeUrlParams(this.window.getURL())) {
// The launch page was loaded. Search for the launch link.
this.getLaunchUrl().then((launchUrl) => {
if (launchUrl) {
// Launch URL retrieved, send it and stop listening.
received(event, launchUrl);
}
});
}
};
switch (name) {
case 'loadstart':
this.window.webContents.on('did-start-loading', received);
if (this.isLinux && this.isSSO) {
// Linux doesn't support custom URL Schemes. Check if launch page is loaded.
// listeners[callback].push(finishLoad);
this.window.webContents.on('did-finish-load', finishLoad);
}
break;
case 'loadstop':
this.window.webContents.on('did-finish-load', received);
break;
case 'loaderror':
this.window.webContents.on('did-fail-load', received);
break;
case 'exit':
this.window.on('close', received);
break;
}
return () => {
// Unsubscribing. We need to remove the listeners.
switch (name) {
case 'loadstart':
this.window.webContents.removeListener('did-start-loading', received);
this.window.webContents.removeListener('did-finish-load', finishLoad);
break;
case 'loadstop':
this.window.webContents.removeListener('did-finish-load', received);
break;
case 'loaderror':
this.window.webContents.removeListener('did-fail-load', received);
break;
case 'exit':
this.window.removeListener('close', received);
break;
}
};
});
}
/**
* Show the window.
*/
show() : void {
this.window.show();
}
}

View File

@ -19,6 +19,7 @@ import { Clipboard } from '@ionic-native/clipboard';
import { File } from '@ionic-native/file';
import { FileTransfer } from '@ionic-native/file-transfer';
import { Globalization } from '@ionic-native/globalization';
import { InAppBrowser } from '@ionic-native/in-app-browser';
import { LocalNotifications } from '@ionic-native/local-notifications';
import { Network } from '@ionic-native/network';
import { Zip } from '@ionic-native/zip';
@ -27,16 +28,17 @@ import { ClipboardMock } from './providers/clipboard';
import { FileMock } from './providers/file';
import { FileTransferMock } from './providers/file-transfer';
import { GlobalizationMock } from './providers/globalization';
import { InAppBrowserMock } from './providers/inappbrowser';
import { LocalNotificationsMock } from './providers/local-notifications';
import { NetworkMock } from './providers/network';
import { ZipMock } from './providers/zip';
import { InAppBrowser } from '@ionic-native/in-app-browser';
import { CoreEmulatorHelperProvider } from './providers/helper';
import { CoreAppProvider } from '../../providers/app';
import { CoreFileProvider } from '../../providers/file';
import { CoreTextUtilsProvider } from '../../providers/utils/text';
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
import { CoreUrlUtilsProvider } from '../../providers/utils/url';
import { CoreUtilsProvider } from '../../providers/utils/utils';
import { CoreInitDelegate } from '../../providers/init';
@ -77,6 +79,13 @@ import { CoreInitDelegate } from '../../providers/init';
return appProvider.isMobile() ? new Globalization() : new GlobalizationMock(appProvider);
}
},
{
provide: InAppBrowser,
deps: [CoreAppProvider, CoreFileProvider, CoreUrlUtilsProvider],
useFactory: (appProvider: CoreAppProvider, fileProvider: CoreFileProvider, urlUtils: CoreUrlUtilsProvider) => {
return !appProvider.isDesktop() ? new InAppBrowser() : new InAppBrowserMock(appProvider, fileProvider, urlUtils);
}
},
{
provide: LocalNotifications,
deps: [CoreAppProvider, CoreUtilsProvider],
@ -101,7 +110,6 @@ import { CoreInitDelegate } from '../../providers/init';
return appProvider.isMobile() ? new Zip() : new ZipMock(file, mimeUtils);
}
},
InAppBrowser
]
})
export class CoreEmulatorModule {

View File

@ -13,7 +13,6 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreAppProvider } from '../../../providers/app';
import { CoreFileProvider } from '../../../providers/file';
import { CoreUtilsProvider } from '../../../providers/utils/utils';
import { File } from '@ionic-native/file';
@ -31,61 +30,7 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler {
blocking = true;
constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider,
initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, private appProvider: CoreAppProvider) {}
/**
* Check if the app is running in a Linux environment.
*
* @return {boolean} Whether it's running in a Linux environment.
*/
isLinux() : boolean {
if (!this.appProvider.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('linux') === 0;
} catch(ex) {
return false;
}
}
/**
* Check if the app is running in a Mac OS environment.
*
* @return {boolean} Whether it's running in a Mac OS environment.
*/
isMac() : boolean {
if (!this.appProvider.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('darwin') === 0;
} catch(ex) {
return false;
}
}
/**
* Check if the app is running in a Windows environment.
*
* @return {boolean} Whether it's running in a Windows environment.
*/
isWindows() : boolean {
if (!this.appProvider.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('win') === 0;
} catch(ex) {
return false;
}
}
initDelegate: CoreInitDelegate, private localNotif: LocalNotifications) {}
/**
* Load the Mocks that need it.
@ -98,7 +43,7 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler {
promises.push((<any>this.file).load().then((basePath: string) => {
this.fileProvider.setHTMLBasePath(basePath);
}));
promises.push((<any>this.localNotif).load(this.isWindows()));
promises.push((<any>this.localNotif).load());
(<any>window).FileTransferError = FileTransferErrorMock;

View File

@ -0,0 +1,48 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable } from '@angular/core';
import { InAppBrowser, InAppBrowserObject } from '@ionic-native/in-app-browser';
import { CoreAppProvider } from '../../../providers/app';
import { CoreFileProvider } from '../../../providers/file';
import { CoreUrlUtilsProvider } from '../../../providers/utils/url';
import { InAppBrowserObjectMock } from '../classes/inappbrowserobject';
/**
* Emulates the Cordova InAppBrowser plugin in desktop apps.
*/
@Injectable()
export class InAppBrowserMock extends InAppBrowser {
constructor(private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider,
private urlUtils: CoreUrlUtilsProvider) {
super();
}
/**
* Opens a URL in a new InAppBrowser instance, the current browser instance, or the system browser.
*
* @param {string} url The URL to load.
* @param {string} [target] The target in which to load the URL, an optional parameter that defaults to _self.
* @param {string} [options] Options for the InAppBrowser.
* @return {InAppBrowserObject} The new instance.
*/
create(url: string, target?: string, options = '') : InAppBrowserObject {
if (!this.appProvider.isDesktop()) {
return super.create(url, target, options);
}
return new InAppBrowserObjectMock(this.appProvider, this.fileProvider, this.urlUtils, url, target, options);
}
}

View File

@ -436,15 +436,14 @@ export class LocalNotificationsMock extends LocalNotifications {
/**
* Loads an initialize the API for desktop.
*
* @param {boolean} isWindows Whether the app is running in a Windows environment.
* @return {Promise<any>} Promise resolved when done.
*/
load(isWindows: boolean) : Promise<any> {
load() : Promise<any> {
if (!this.appProvider.isDesktop()) {
return Promise.resolve();
}
if (isWindows) {
if (this.appProvider.isWindows()) {
try {
this.winNotif = require('electron-windows-notifications');
} catch(ex) {}

View File

@ -28,7 +28,6 @@ import { CoreUrlUtilsProvider } from '../../../providers/utils/url';
import { CoreUtilsProvider } from '../../../providers/utils/utils';
import { CoreConfigConstants } from '../../../configconstants';
import { CoreConstants } from '../../constants';
import { CoreEmulatorHelperProvider } from '../../emulator/providers/helper';
import { Md5 } from 'ts-md5/dist/md5';
export interface CoreLoginSSOData {
@ -55,7 +54,7 @@ export class CoreLoginHelperProvider {
private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider,
private urlUtils: CoreUrlUtilsProvider,private configProvider: CoreConfigProvider, private platform: Platform,
private emulatorHelper: CoreEmulatorHelperProvider, private initDelegate: CoreInitDelegate) {
private initDelegate: CoreInitDelegate) {
this.logger = logger.getInstance('CoreLoginHelper');
}
@ -591,7 +590,7 @@ export class CoreLoginHelperProvider {
* @return {boolean} True if embedded browser, false othwerise.
*/
isSSOEmbeddedBrowser(code: number) : boolean {
if (this.appProvider.isDesktop() && this.emulatorHelper.isLinux()) {
if (this.appProvider.isLinux()) {
// In Linux desktop apps, always use embedded browser.
return true;
}
@ -635,7 +634,7 @@ export class CoreLoginHelperProvider {
loginUrl += '&oauthsso=' + params.id;
if (this.appProvider.isDesktop() && this.emulatorHelper.isLinux()) {
if (this.appProvider.isLinux()) {
// In Linux desktop apps, always use embedded browser.
this.utils.openInApp(loginUrl);
} else {

View File

@ -115,6 +115,42 @@ export class CoreAppProvider {
return this.isKeyboardShown;
};
/**
* Check if the app is running in a Linux environment.
*
* @return {boolean} Whether it's running in a Linux environment.
*/
isLinux() : boolean {
if (!this.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('linux') === 0;
} catch(ex) {
return false;
}
}
/**
* Check if the app is running in a Mac OS environment.
*
* @return {boolean} Whether it's running in a Mac OS environment.
*/
isMac() : boolean {
if (!this.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('darwin') === 0;
} catch(ex) {
return false;
}
}
/**
* Checks if the app is running in a mobile or tablet device (Cordova).
*
@ -154,6 +190,24 @@ export class CoreAppProvider {
return limited.indexOf(type) > -1;
};
/**
* Check if the app is running in a Windows environment.
*
* @return {boolean} Whether it's running in a Windows environment.
*/
isWindows() : boolean {
if (!this.isDesktop()) {
return false;
}
try {
var os = require('os');
return os.platform().indexOf('win') === 0;
} catch(ex) {
return false;
}
}
/**
* Open the keyboard.
*/