MOBILE-2261 core: Emulate simple Cordova plugins

SQLite, Clipboard, Globalization, Network and URL Scheme
main
Dani Palou 2017-10-30 16:40:50 +01:00
parent e3d5c95c87
commit 4ebc79fca7
10 changed files with 527 additions and 14 deletions

View File

@ -116,10 +116,10 @@
<plugin name="phonegap-plugin-push" spec="~1.10.5">
<variable name="SENDER_ID" value="694767596569" />
</plugin>
<plugin name="cordova-universal-clipboard" spec="~0.1.0" />
<plugin name="nl.kingsquare.cordova.background-audio" spec="~1.0.1" />
<plugin name="ch.ti8m.documenthandler" spec="https://github.com/ti8m/DocumentHandler" />
<plugin name="net.tunts.webintent" spec="https://github.com/Tunts/WebIntent" />
<plugin name="cordova-sqlite-storage" spec="~2.0.4" />
<plugin name="cordova-plugin-ionic-webview" spec="~1.1.16" />
<plugin name="cordova-clipboard" spec="~1.1.0" />
</widget>

20
package-lock.json generated
View File

@ -54,11 +54,21 @@
"resolved": "https://registry.npmjs.org/@angular/tsc-wrapped/-/tsc-wrapped-4.4.3.tgz",
"integrity": "sha1-LT84IQodTbA/yG3PHglYErhc0Rk="
},
"@ionic-native/clipboard": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@ionic-native/clipboard/-/clipboard-4.3.2.tgz",
"integrity": "sha512-dutKKMnpyhFeEPc09O5MzrtPKtv13f3X4mCmDmUEj5CExMecTOhox/rnIB70t8GP2/3bTC/CSr39DhkLLS8MzA=="
},
"@ionic-native/core": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.3.0.tgz",
"integrity": "sha512-Pf0qCzqlVFmIpZpvo35Kl0e+1K8GUgPMcKBnN57gWh+5Ecj3dPcb+MbP4murJo/dnFsIYPYdXRZRf74hjo6gtw=="
},
"@ionic-native/globalization": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.3.2.tgz",
"integrity": "sha512-sDyriA3/xspu6RM8arEOlhOkSxvRwLDNdvbBoZ59i+Dn5i6bjoE4hMMEFvUlnzvOZKn5GusurrWPz5cmfA9aPw=="
},
"@ionic-native/keyboard": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-4.3.2.tgz",
@ -110,6 +120,11 @@
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
},
"@types/cordova-plugin-globalization": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-globalization/-/cordova-plugin-globalization-0.0.3.tgz",
"integrity": "sha1-ySA6HENtPS0DBXiffJwrq6i6KK0="
},
"@types/cordova-plugin-network-information": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-network-information/-/cordova-plugin-network-information-0.0.3.tgz",
@ -120,6 +135,11 @@
"resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.30.tgz",
"integrity": "sha1-PWCmv23aOOP4pGlhFZg3nx9klQk="
},
"@types/node": {
"version": "8.0.47",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.47.tgz",
"integrity": "sha512-kOwL746WVvt/9Phf6/JgX/bsGQvbrK5iUgzyfwZNcKVFcjAUVSpF9HxevLTld2SG9aywYHOILj38arDdY1r/iQ=="
},
"@types/promise.prototype.finally": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@types/promise.prototype.finally/-/promise.prototype.finally-2.0.2.tgz",

View File

@ -33,7 +33,9 @@
"@angular/http": "4.4.3",
"@angular/platform-browser": "4.4.3",
"@angular/platform-browser-dynamic": "4.4.3",
"@ionic-native/clipboard": "^4.3.2",
"@ionic-native/core": "4.3.0",
"@ionic-native/globalization": "^4.3.2",
"@ionic-native/keyboard": "^4.3.2",
"@ionic-native/network": "^4.3.2",
"@ionic-native/splash-screen": "4.3.0",
@ -43,7 +45,9 @@
"@ngx-translate/core": "^8.0.0",
"@ngx-translate/http-loader": "^2.0.0",
"@types/cordova": "0.0.34",
"@types/cordova-plugin-globalization": "0.0.3",
"@types/cordova-plugin-network-information": "0.0.3",
"@types/node": "^8.0.47",
"@types/promise.prototype.finally": "^2.0.2",
"electron-builder-squirrel-windows": "^19.3.0",
"electron-windows-notifications": "^1.1.13",
@ -58,5 +62,8 @@
"devDependencies": {
"@ionic/app-scripts": "3.0.0",
"typescript": "2.3.4"
},
"browser": {
"electron": false
}
}

View File

@ -1,13 +1,12 @@
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { SQLite } from '@ionic-native/sqlite';
import { Keyboard } from '@ionic-native/keyboard';
import { Network } from '@ionic-native/network';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
@ -16,6 +15,7 @@ import { CoreLoggerProvider } from '../providers/logger';
import { CoreDbProvider } from '../providers/db';
import { CoreAppProvider } from '../providers/app';
import { CoreConfigProvider } from '../providers/config';
import { CoreEmulatorModule } from '../core/emulator/emulator.module';
// For translate loader. AoT requires an exported function for factories.
export function createTranslateLoader(http: HttpClient) {
@ -28,6 +28,7 @@ export function createTranslateLoader(http: HttpClient) {
],
imports: [
BrowserModule,
HttpClientModule,
IonicModule.forRoot(MyApp),
TranslateModule.forRoot({
loader: {
@ -35,7 +36,8 @@ export function createTranslateLoader(http: HttpClient) {
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
})
}),
CoreEmulatorModule
],
bootstrap: [IonicApp],
entryComponents: [
@ -46,12 +48,11 @@ export function createTranslateLoader(http: HttpClient) {
SplashScreen,
SQLite,
Keyboard,
Network,
{provide: ErrorHandler, useClass: IonicErrorHandler},
CoreLoggerProvider,
CoreDbProvider,
CoreAppProvider,
CoreConfigProvider,
CoreConfigProvider
]
})
export class AppModule {}

View File

@ -0,0 +1,153 @@
// (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 { SQLiteDB } from '../../../classes/sqlitedb';
/**
* Class to mock the interaction with the SQLite database.
*/
export class SQLiteDBMock extends SQLiteDB {
promise: Promise<void>;
/**
* Create and open the database.
*
* @param {string} name Database name.
*/
constructor(public name: string) {
super(name, null, null);
}
/**
* Close the database.
*
* @return {Promise<any>} Promise resolved when done.
*/
close() : Promise<any> {
// WebSQL databases aren't closed.
return Promise.resolve();
}
/**
* Drop all the data in the database.
*
* @return {Promise<any>} Promise resolved when done.
*/
emptyDatabase() : Promise<any> {
return new Promise((resolve, reject) => {
this.db.transaction(tx => {
// Query all tables from sqlite_master that we have created and can modify.
let args = [],
query = `SELECT * FROM sqlite_master
WHERE name NOT LIKE 'sqlite\\_%' escape '\\' AND name NOT LIKE '\\_%' escape '\\'`;
tx.executeSql(query, args, (tx, result) => {
if (result.rows.length <= 0) {
// No tables to delete, stop.
resolve();
return;
}
// Drop all the tables.
let promises = [];
for (let i = 0; i < result.rows.length; i++) {
promises.push(new Promise((resolve, reject) => {
// Drop the table.
let name = JSON.stringify(result.rows.item(i).name);
tx.executeSql('DROP TABLE ' + name, [], resolve, reject);
}));
}
Promise.all(promises).then(resolve, reject);
}, reject);
});
});
}
/**
* Execute a SQL query.
*
* @param {string} sql SQL query to execute.
* @param {any[]} params Query parameters.
* @return {Promise<any>} Promise resolved with the result.
*/
protected execute(sql: string, params?: any[]) : Promise<any> {
return new Promise((resolve, reject) => {
// With WebSQL, all queries must be run in a transaction.
this.db.transaction((tx) => {
tx.executeSql(sql, params, (tx, results) => {
resolve(results);
}, reject);
});
});
}
/**
* Execute a set of SQL queries. This operation is atomic.
*
* @param {any[]} sqlStatements SQL statements to execute.
* @return {Promise<any>} Promise resolved with the result.
*/
protected executeBatch(sqlStatements: any[]) : Promise<any> {
return new Promise((resolve, reject) => {
// Create a transaction to execute the queries.
this.db.transaction((tx) => {
let promises = [];
// Execute all the queries. Each statement can be a string or an array.
sqlStatements.forEach((statement) => {
promises.push(new Promise((resolve, reject) => {
let query,
params;
if (Array.isArray(statement)) {
query = statement[0];
params = statement[1];
} else {
query = statement;
params = null;
}
tx.executeSql(query, params, (tx, results) => {
resolve(results);
}, reject);
}));
});
Promise.all(promises).then(resolve, reject);
});
});
}
/**
* Initialize the database.
*/
init(): void {
// This DB is for desktop apps, so use a big size to be sure it isn't filled.
this.db = (<any>window).openDatabase(this.name, '1.0', this.name, 500 * 1024 * 1024);
this.promise = Promise.resolve();
}
/**
* Open the database. Only needed if it was closed before, a database is automatically opened when created.
*
* @return {Promise<any>} Promise resolved when done.
*/
open() : Promise<any> {
// WebSQL databases can't closed, so the open method isn't needed.
return Promise.resolve();
}
}

View File

@ -0,0 +1,69 @@
// (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 { NgModule } from '@angular/core';
import { Platform } from 'ionic-angular';
import { CoreAppProvider } from '../../providers/app';
import { Clipboard } from '@ionic-native/clipboard';
import { Globalization } from '@ionic-native/globalization';
import { Network } from '@ionic-native/network';
import { ClipboardMock } from './providers/clipboard';
import { GlobalizationMock } from './providers/globalization';
import { NetworkMock } from './providers/network';
@NgModule({
declarations: [
],
imports: [
],
providers: [
ClipboardMock,
GlobalizationMock,
{
provide: Clipboard,
deps: [CoreAppProvider],
useFactory: (appProvider: CoreAppProvider) => {
return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider);
}
},
{
provide: Globalization,
deps: [CoreAppProvider],
useFactory: (appProvider: CoreAppProvider) => {
return appProvider.isMobile() ? new Globalization() : new GlobalizationMock(appProvider);
}
},
{
provide: Network,
deps: [Platform],
useFactory: (platform: Platform) => {
// Use platform instead of CoreAppProvider to prevent circular dependencies.
return platform.is('cordova') ? new Network() : new NetworkMock();
}
}
]
})
export class CoreEmulatorModule {
constructor(appProvider: CoreAppProvider) {
let win = <any>window; // Convert the "window" to "any" type to be able to use non-standard properties.
// Emulate Custom URL Scheme plugin in desktop apps.
if (appProvider.isDesktop()) {
require('electron').ipcRenderer.on('mmAppLaunched', function(event, url) {
win.handleOpenURL && win.handleOpenURL(url);
});
}
}
}

View File

@ -0,0 +1,102 @@
// (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 { Clipboard } from '@ionic-native/clipboard';
import { CoreAppProvider } from '../../../providers/app';
/**
* Emulates the Cordova Clipboard plugin in desktop apps and in browser.
*/
@Injectable()
export class ClipboardMock extends Clipboard {
isDesktop: boolean;
clipboard: any;
copyTextarea: HTMLTextAreaElement;
constructor(appProvider: CoreAppProvider) {
super();
this.isDesktop = appProvider.isDesktop();
if (this.isDesktop) {
this.clipboard = require('electron').clipboard;
} else {
// In browser the text must be selected in order to copy it. Create a hidden textarea to put the text in it.
this.copyTextarea = document.createElement('textarea');
this.copyTextarea.className = 'mm-browser-copy-area';
this.copyTextarea.setAttribute('aria-hidden', 'true');
document.body.appendChild(this.copyTextarea);
}
}
/**
* Copy some text to the clipboard.
*
* @param {string} text The text to copy.
* @return {Promise<any>} Promise resolved when copied.
*/
copy(text: string) : Promise<any> {
return new Promise((resolve, reject) => {
if (this.isDesktop) {
this.clipboard.writeText(text);
resolve();
} else {
// Put the text in the hidden textarea and select it.
this.copyTextarea.innerHTML = text;
this.copyTextarea.select();
try {
if (document.execCommand('copy')) {
resolve();
} else {
reject();
}
} catch (err) {
reject();
}
this.copyTextarea.innerHTML = '';
}
});
}
/*
* Get the text stored in the clipboard.
*
* @return {Promise<any>} Promise resolved with the text.
*/
paste() : Promise<any> {
return new Promise((resolve, reject) => {
if (this.isDesktop) {
resolve(this.clipboard.readText());
} else {
// Paste the text in the hidden textarea and get it.
this.copyTextarea.innerHTML = '';
this.copyTextarea.select();
try {
if (document.execCommand('paste')) {
resolve(this.copyTextarea.innerHTML);
} else {
reject();
}
} catch (err) {
reject();
}
this.copyTextarea.innerHTML = '';
}
});
}
}

View File

@ -0,0 +1,74 @@
// (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 { Globalization } from '@ionic-native/globalization';
import { CoreAppProvider } from '../../../providers/app';
/**
* Emulates the Cordova Globalization plugin in desktop apps and in browser.
*/
@Injectable()
export class GlobalizationMock extends Globalization {
constructor(private appProvider: CoreAppProvider) {
super();
}
/**
* Get the current locale.
*
* @return {string} Locale name.
*/
private getCurrentlocale() : string {
// Get browser language.
var navLang = (<any>navigator).userLanguage || navigator.language;
try {
if (this.appProvider.isDesktop()) {
var locale = require('electron').remote.app.getLocale();
return locale || navLang;
} else {
return navLang;
}
} catch(ex) {
// Something went wrong, return browser language.
return navLang;
}
}
/**
* Get the current locale name.
*
* @return {Promise<{value: string}>} Promise resolved with an object with the language string.
*/
getLocaleName() : Promise<{value: string}> {
var locale = this.getCurrentlocale();
if (locale) {
return Promise.resolve({value: locale});
} else {
var error = {code: GlobalizationError.UNKNOWN_ERROR, message: 'Cannot get language'};
return Promise.reject(error);
}
}
/*
* Get the current preferred language.
*
* @return {Promise<{value: string}>} Promise resolved with an object with the language string.
*/
getPreferredLanguage() : Promise<{value: string}> {
return this.getLocaleName();
}
}

View File

@ -0,0 +1,79 @@
// (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 { Network } from '@ionic-native/network';
import { Observable, Subject } from 'rxjs';
/**
* Emulates the Cordova Globalization plugin in desktop apps and in browser.
*/
@Injectable()
export class NetworkMock extends Network {
type: null;
constructor() {
super();
(<any>window).Connection = {
UNKNOWN: 'unknown',
ETHERNET: 'ethernet',
WIFI: 'wifi',
CELL_2G: '2g',
CELL_3G: '3g',
CELL_4G: '4g',
CELL: 'cellular',
NONE: 'none'
};
}
/**
* Returns an observable to watch connection changes.
*
* @return {Observable<any>} Observable.
*/
onchange(): Observable<any> {
return Observable.merge(this.onConnect(), this.onDisconnect());
}
/**
* Returns an observable to notify when the app is connected.
*
* @return {Observable<any>} Observable.
*/
onConnect() : Observable<any> {
let observable = new Subject<any>();
window.addEventListener('online', (ev) => {
observable.next(ev);
}, false);
return observable;
}
/**
* Returns an observable to notify when the app is disconnected.
*
* @return {Observable<any>} Observable.
*/
onDisconnect() : Observable<any> {
let observable = new Subject<any>();
window.addEventListener('offline', (ev) => {
observable.next(ev);
}, false);
return observable;
}
}

View File

@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { SQLite } from '@ionic-native/sqlite';
import { Platform } from 'ionic-angular';
import { SQLiteDB } from '../classes/sqlitedb';
import { SQLiteDBMock } from '../core/emulator/classes/sqlitedb';
/**
* This service allows interacting with the local database to store and retrieve data.
@ -38,7 +39,11 @@ export class CoreDbProvider {
*/
getDB(name: string, forceNew?: boolean) : SQLiteDB {
if (typeof this.dbInstances[name] === 'undefined' || forceNew) {
if (this.platform.is('cordova')) {
this.dbInstances[name] = new SQLiteDB(name, this.sqlite, this.platform);
} else {
this.dbInstances[name] = new SQLiteDBMock(name);
}
}
return this.dbInstances[name];
}
@ -60,15 +65,18 @@ export class CoreDbProvider {
}
return promise.then(() => {
let db = this.dbInstances[name];
delete this.dbInstances[name];
if (this.platform.is('cordova')) {
return this.sqlite.deleteDatabase({
name: name,
location: 'default'
});
} else {
// In WebSQL we cannot delete the database, just empty it.
return db.emptyDatabase();
}
});
}
}