MOBILE-2253 emulator: Mock InAppBrowser
parent
26d5690c36
commit
4a423f9cd3
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue