Merge pull request #2565 from NoelDeMartin/MOBILE-3320

MOBILE-3320 tsconfig: Use strict checks
main
Dani Palou 2020-10-19 16:03:20 +02:00 committed by GitHub
commit b1e637812e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 249 additions and 202 deletions

View File

@ -148,6 +148,7 @@ var appConfig = {
'warn', 'warn',
{ {
allowFinally: true, allowFinally: true,
terminationMethod: ['catch', 'finally'],
}, },
], ],
'arrow-body-style': ['error', 'as-needed'], 'arrow-body-style': ['error', 'as-needed'],

View File

@ -40,7 +40,7 @@ export class CoreSingletonsFactory {
/** /**
* Angular injector used to resolve singleton instances. * Angular injector used to resolve singleton instances.
*/ */
private injector: Injector; private injector?: Injector;
/** /**
* Set the injector that will be used to resolve instances in the singletons created with this factory. * Set the injector that will be used to resolve instances in the singletons created with this factory.
@ -68,6 +68,10 @@ export class CoreSingletonsFactory {
static get instance(): Service { static get instance(): Service {
// Initialize instances lazily. // Initialize instances lazily.
if (!this.serviceInstance) { if (!this.serviceInstance) {
if (!factory.injector) {
throw new Error('Can\'t resolve a singleton instance without an injector');
}
this.serviceInstance = factory.injector.get(injectionToken); this.serviceInstance = factory.injector.get(injectionToken);
} }

View File

@ -133,8 +133,8 @@ export interface SQLiteDBForeignKeySchema {
*/ */
export class SQLiteDB { export class SQLiteDB {
db: SQLiteObject; db?: SQLiteObject;
promise: Promise<void>; promise!: Promise<void>;
/** /**
* Create and open the database. * Create and open the database.
@ -164,7 +164,7 @@ export class SQLiteDB {
foreignKeys?: SQLiteDBForeignKeySchema[], foreignKeys?: SQLiteDBForeignKeySchema[],
tableCheck?: string, tableCheck?: string,
): string { ): string {
const columnsSql = []; const columnsSql: string[] = [];
let sql = `CREATE TABLE IF NOT EXISTS ${name} (`; let sql = `CREATE TABLE IF NOT EXISTS ${name} (`;
// First define all the columns. // First define all the columns.
@ -225,8 +225,8 @@ export class SQLiteDB {
for (const index in foreignKeys) { for (const index in foreignKeys) {
const foreignKey = foreignKeys[index]; const foreignKey = foreignKeys[index];
if (!foreignKey.columns || !!foreignKey.columns.length) { if (!foreignKey?.columns.length) {
return; continue;
} }
sql += `, FOREIGN KEY (${foreignKey.columns.join(', ')}) REFERENCES ${foreignKey.table} `; sql += `, FOREIGN KEY (${foreignKey.columns.join(', ')}) REFERENCES ${foreignKey.table} `;
@ -251,7 +251,7 @@ export class SQLiteDB {
async close(): Promise<void> { async close(): Promise<void> {
await this.ready(); await this.ready();
await this.db.close(); await this.db!.close();
} }
/** /**
@ -348,10 +348,7 @@ export class SQLiteDB {
* @return Promise resolved when success. * @return Promise resolved when success.
*/ */
async createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise<void> { async createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise<void> {
const promises = []; const promises = tables.map(table => this.createTableFromSchema(table));
tables.forEach((table) => {
promises.push(this.createTableFromSchema(table));
});
await Promise.all(promises); await Promise.all(promises);
} }
@ -432,7 +429,7 @@ export class SQLiteDB {
async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> { async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> {
await this.ready(); await this.ready();
return this.db.executeSql(sql, params); return this.db!.executeSql(sql, params);
} }
/** /**
@ -447,7 +444,7 @@ export class SQLiteDB {
async executeBatch(sqlStatements: (string | string[] | any)[]): Promise<void> { async executeBatch(sqlStatements: (string | string[] | any)[]): Promise<void> {
await this.ready(); await this.ready();
await this.db.sqlBatch(sqlStatements); await this.db!.sqlBatch(sqlStatements);
} }
/** /**
@ -532,7 +529,7 @@ export class SQLiteDB {
* @return Promise resolved with the field's value. * @return Promise resolved with the field's value.
*/ */
async getFieldSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValue> { async getFieldSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValue> {
const record = await this.getRecordSql(sql, params); const record = await this.getRecordSql<Record<string, SQLiteDBRecordValue>>(sql, params);
if (!record) { if (!record) {
throw new CoreError('No record found.'); throw new CoreError('No record found.');
} }
@ -552,7 +549,7 @@ export class SQLiteDB {
getInOrEqual( getInOrEqual(
items: SQLiteDBRecordValue | SQLiteDBRecordValue[], items: SQLiteDBRecordValue | SQLiteDBRecordValue[],
equal: boolean = true, equal: boolean = true,
onEmptyItems?: SQLiteDBRecordValue, onEmptyItems?: SQLiteDBRecordValue | null,
): SQLiteDBQueryParams { ): SQLiteDBQueryParams {
let sql = ''; let sql = '';
let params: SQLiteDBRecordValue[]; let params: SQLiteDBRecordValue[];
@ -564,7 +561,7 @@ export class SQLiteDB {
// Handle onEmptyItems on empty array of items. // Handle onEmptyItems on empty array of items.
if (Array.isArray(items) && !items.length) { if (Array.isArray(items) && !items.length) {
if (onEmptyItems === null) { // Special case, NULL value. if (onEmptyItems === null || typeof onEmptyItems === 'undefined') { // Special case, NULL value.
sql = equal ? ' IS NULL' : ' IS NOT NULL'; sql = equal ? ' IS NULL' : ' IS NOT NULL';
return { sql, params: [] }; return { sql, params: [] };
@ -758,7 +755,7 @@ export class SQLiteDB {
const result = await this.execute(sql, params); const result = await this.execute(sql, params);
// Retrieve the records. // Retrieve the records.
const records = []; const records: T[] = [];
for (let i = 0; i < result.rows.length; i++) { for (let i = 0; i < result.rows.length; i++) {
records.push(result.rows.item(i)); records.push(result.rows.item(i));
} }
@ -868,7 +865,7 @@ export class SQLiteDB {
* @param limitNum How many results to return. * @param limitNum How many results to return.
* @return Normalised limit params in array: [limitFrom, limitNum]. * @return Normalised limit params in array: [limitFrom, limitNum].
*/ */
normaliseLimitFromNum(limitFrom: number, limitNum: number): number[] { normaliseLimitFromNum(limitFrom?: number, limitNum?: number): number[] {
// We explicilty treat these cases as 0. // We explicilty treat these cases as 0.
if (!limitFrom || limitFrom === -1) { if (!limitFrom || limitFrom === -1) {
limitFrom = 0; limitFrom = 0;
@ -893,7 +890,7 @@ export class SQLiteDB {
async open(): Promise<void> { async open(): Promise<void> {
await this.ready(); await this.ready();
await this.db.open(); await this.db!.open();
} }
/** /**
@ -994,11 +991,7 @@ export class SQLiteDB {
return 0; return 0;
} }
const sets = []; const sets = Object.keys(data).map(key => `${key} = ?`);
for (const key in data) {
sets.push(`${key} = ?`);
}
let sql = `UPDATE ${table} SET ${sets.join(', ')}`; let sql = `UPDATE ${table} SET ${sets.join(', ')}`;
if (where) { if (where) {
sql += ` WHERE ${where}`; sql += ` WHERE ${where}`;
@ -1029,8 +1022,8 @@ export class SQLiteDB {
}; };
} }
const where = []; const where: string[] = [];
const params = []; const params: SQLiteDBRecordValue[] = [];
for (const key in conditions) { for (const key in conditions) {
const value = conditions[key]; const value = conditions[key];
@ -1064,7 +1057,7 @@ export class SQLiteDB {
}; };
} }
const params = []; const params: SQLiteDBRecordValue[] = [];
let sql = ''; let sql = '';
values.forEach((value) => { values.forEach((value) => {

View File

@ -20,7 +20,6 @@ import { SQLiteDB } from '@classes/sqlitedb';
* Class to mock the interaction with the SQLite database. * Class to mock the interaction with the SQLite database.
*/ */
export class SQLiteDBMock extends SQLiteDB { export class SQLiteDBMock extends SQLiteDB {
promise: Promise<void>;
/** /**
* Create and open the database. * Create and open the database.
@ -46,9 +45,11 @@ export class SQLiteDBMock extends SQLiteDB {
* *
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
emptyDatabase(): Promise<any> { async emptyDatabase(): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => { return new Promise((resolve, reject): void => {
this.db.transaction((tx) => { this.db!.transaction((tx) => {
// Query all tables from sqlite_master that we have created and can modify. // Query all tables from sqlite_master that we have created and can modify.
const args = []; const args = [];
const query = `SELECT * FROM sqlite_master const query = `SELECT * FROM sqlite_master
@ -63,7 +64,7 @@ export class SQLiteDBMock extends SQLiteDB {
} }
// Drop all the tables. // Drop all the tables.
const promises = []; const promises: Promise<void>[] = [];
for (let i = 0; i < result.rows.length; i++) { for (let i = 0; i < result.rows.length; i++) {
promises.push(new Promise((resolve, reject): void => { promises.push(new Promise((resolve, reject): void => {
@ -73,7 +74,7 @@ export class SQLiteDBMock extends SQLiteDB {
})); }));
} }
Promise.all(promises).then(resolve, reject); Promise.all(promises).then(resolve).catch(reject);
}, reject); }, reject);
}); });
}); });
@ -88,14 +89,18 @@ export class SQLiteDBMock extends SQLiteDB {
* @param params Query parameters. * @param params Query parameters.
* @return Promise resolved with the result. * @return Promise resolved with the result.
*/ */
execute(sql: string, params?: any[]): Promise<any> { async execute(sql: string, params?: any[]): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => { return new Promise((resolve, reject): void => {
// With WebSQL, all queries must be run in a transaction. // With WebSQL, all queries must be run in a transaction.
this.db.transaction((tx) => { this.db!.transaction((tx) => {
tx.executeSql(sql, params, (tx, results) => { tx.executeSql(sql, params, (tx, results) => {
resolve(results); resolve(results);
}, (tx, error) => { }, (tx, error) => {
// eslint-disable-next-line no-console
console.error(sql, params, error); console.error(sql, params, error);
reject(error); reject(error);
}); });
}); });
@ -110,11 +115,13 @@ export class SQLiteDBMock extends SQLiteDB {
* @param sqlStatements SQL statements to execute. * @param sqlStatements SQL statements to execute.
* @return Promise resolved with the result. * @return Promise resolved with the result.
*/ */
executeBatch(sqlStatements: any[]): Promise<any> { async executeBatch(sqlStatements: any[]): Promise<any> {
await this.ready();
return new Promise((resolve, reject): void => { return new Promise((resolve, reject): void => {
// Create a transaction to execute the queries. // Create a transaction to execute the queries.
this.db.transaction((tx) => { this.db!.transaction((tx) => {
const promises = []; const promises: Promise<void>[] = [];
// Execute all the queries. Each statement can be a string or an array. // Execute all the queries. Each statement can be a string or an array.
sqlStatements.forEach((statement) => { sqlStatements.forEach((statement) => {
@ -133,7 +140,9 @@ export class SQLiteDBMock extends SQLiteDB {
tx.executeSql(query, params, (tx, results) => { tx.executeSql(query, params, (tx, results) => {
resolve(results); resolve(results);
}, (tx, error) => { }, (tx, error) => {
// eslint-disable-next-line no-console
console.error(query, params, error); console.error(query, params, error);
reject(error); reject(error);
}); });
})); }));

View File

@ -45,13 +45,13 @@ export class CoreAppProvider {
protected db: SQLiteDB; protected db: SQLiteDB;
protected logger: CoreLogger; protected logger: CoreLogger;
protected ssoAuthenticationDeferred: PromiseDefer<void>; protected ssoAuthenticationDeferred?: PromiseDefer<void>;
protected isKeyboardShown = false; protected isKeyboardShown = false;
protected keyboardOpening = false; protected keyboardOpening = false;
protected keyboardClosing = false; protected keyboardClosing = false;
protected backActions: {callback: () => boolean; priority: number}[] = []; protected backActions: {callback: () => boolean; priority: number}[] = [];
protected mainMenuId = 0; protected mainMenuId = 0;
protected mainMenuOpen: number; protected mainMenuOpen?: number;
protected forceOffline = false; protected forceOffline = false;
// Variables for DB. // Variables for DB.
@ -224,7 +224,7 @@ export class CoreAppProvider {
* @param storesConfig Config params to send the user to the right place. * @param storesConfig Config params to send the user to the right place.
* @return Store URL. * @return Store URL.
*/ */
getAppStoreUrl(storesConfig: CoreStoreConfig): string { getAppStoreUrl(storesConfig: CoreStoreConfig): string | null {
if (this.isMac() && storesConfig.mac) { if (this.isMac() && storesConfig.mac) {
return 'itms-apps://itunes.apple.com/app/' + storesConfig.mac; return 'itms-apps://itunes.apple.com/app/' + storesConfig.mac;
} }
@ -332,6 +332,8 @@ export class CoreAppProvider {
try { try {
// @todo return require('os').platform().indexOf('linux') === 0; // @todo return require('os').platform().indexOf('linux') === 0;
return false;
} catch (ex) { } catch (ex) {
return false; return false;
} }
@ -349,6 +351,8 @@ export class CoreAppProvider {
try { try {
// @todo return require('os').platform().indexOf('darwin') === 0; // @todo return require('os').platform().indexOf('darwin') === 0;
return false;
} catch (ex) { } catch (ex) {
return false; return false;
} }
@ -439,6 +443,8 @@ export class CoreAppProvider {
try { try {
// @todo return require('os').platform().indexOf('win') === 0; // @todo return require('os').platform().indexOf('win') === 0;
return false;
} catch (ex) { } catch (ex) {
return false; return false;
} }
@ -521,7 +527,9 @@ export class CoreAppProvider {
* @return Promise resolved once SSO authentication finishes. * @return Promise resolved once SSO authentication finishes.
*/ */
async waitForSSOAuthentication(): Promise<void> { async waitForSSOAuthentication(): Promise<void> {
await this.ssoAuthenticationDeferred && this.ssoAuthenticationDeferred.promise; const promise = this.ssoAuthenticationDeferred?.promise;
await promise;
} }
/** /**
@ -530,7 +538,7 @@ export class CoreAppProvider {
* @param timeout Maximum time to wait, use null to wait forever. * @param timeout Maximum time to wait, use null to wait forever.
*/ */
async waitForResume(timeout: number | null = null): Promise<void> { async waitForResume(timeout: number | null = null): Promise<void> {
let deferred = CoreUtils.instance.promiseDefer<void>(); let deferred: PromiseDefer<void> | null = CoreUtils.instance.promiseDefer<void>();
const stopWaiting = () => { const stopWaiting = () => {
if (!deferred) { if (!deferred) {
@ -556,13 +564,13 @@ export class CoreAppProvider {
* @return Object with siteid, state, params and timemodified. * @return Object with siteid, state, params and timemodified.
*/ */
getRedirect<Params extends Record<string, unknown> = Record<string, unknown>>(): CoreRedirectData<Params> { getRedirect<Params extends Record<string, unknown> = Record<string, unknown>>(): CoreRedirectData<Params> {
if (localStorage && localStorage.getItem) { if (localStorage?.getItem) {
try { try {
const paramsJson = localStorage.getItem('CoreRedirectParams'); const paramsJson = localStorage.getItem('CoreRedirectParams');
const data: CoreRedirectData<Params> = { const data: CoreRedirectData<Params> = {
siteId: localStorage.getItem('CoreRedirectSiteId'), siteId: localStorage.getItem('CoreRedirectSiteId') || undefined,
page: localStorage.getItem('CoreRedirectState'), page: localStorage.getItem('CoreRedirectState') || undefined,
timemodified: parseInt(localStorage.getItem('CoreRedirectTime'), 10), timemodified: parseInt(localStorage.getItem('CoreRedirectTime') || '0', 10),
}; };
if (paramsJson) { if (paramsJson) {

View File

@ -14,7 +14,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreUtils, PromiseDefer } from '@services/utils/utils'; import { CoreUtils, PromiseDefer, OrderedPromiseData } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { makeSingleton } from '@singletons/core.singletons'; import { makeSingleton } from '@singletons/core.singletons';
@ -56,7 +56,7 @@ export class CoreInitDelegate {
protected initProcesses: { [s: string]: CoreInitHandler } = {}; protected initProcesses: { [s: string]: CoreInitHandler } = {};
protected logger: CoreLogger; protected logger: CoreLogger;
protected readiness: CoreInitReadinessPromiseDefer<void>; protected readiness?: CoreInitReadinessPromiseDefer<void>;
constructor() { constructor() {
this.logger = CoreLogger.getInstance('CoreInitDelegate'); this.logger = CoreLogger.getInstance('CoreInitDelegate');
@ -68,7 +68,7 @@ export class CoreInitDelegate {
* Reserved for core use, do not call directly. * Reserved for core use, do not call directly.
*/ */
executeInitProcesses(): void { executeInitProcesses(): void {
let ordered = []; const ordered: CoreInitHandler[] = [];
if (typeof this.readiness == 'undefined') { if (typeof this.readiness == 'undefined') {
this.initReadiness(); this.initReadiness();
@ -78,15 +78,15 @@ export class CoreInitDelegate {
for (const name in this.initProcesses) { for (const name in this.initProcesses) {
ordered.push(this.initProcesses[name]); ordered.push(this.initProcesses[name]);
} }
ordered.sort((a, b) => b.priority - a.priority); ordered.sort((a, b) => (b.priority || 0) - (a.priority || 0));
ordered = ordered.map((data: CoreInitHandler) => ({ const orderedPromises: OrderedPromiseData[] = ordered.map((data: CoreInitHandler) => ({
function: this.prepareProcess.bind(this, data), function: this.prepareProcess.bind(this, data),
blocking: !!data.blocking, blocking: !!data.blocking,
})); }));
// Execute all the processes in order to solve dependencies. // Execute all the processes in order to solve dependencies.
CoreUtils.instance.executeOrderedPromises(ordered).finally(this.readiness.resolve); CoreUtils.instance.executeOrderedPromises(orderedPromises).finally(this.readiness!.resolve);
} }
/** /**
@ -94,7 +94,9 @@ export class CoreInitDelegate {
*/ */
protected initReadiness(): void { protected initReadiness(): void {
this.readiness = CoreUtils.instance.promiseDefer(); this.readiness = CoreUtils.instance.promiseDefer();
this.readiness.promise.then(() => this.readiness.resolved = true);
// eslint-disable-next-line promise/catch-or-return
this.readiness.promise.then(() => this.readiness!.resolved = true);
} }
/** /**
@ -103,7 +105,7 @@ export class CoreInitDelegate {
* @return Whether it's ready. * @return Whether it's ready.
*/ */
isReady(): boolean { isReady(): boolean {
return this.readiness.resolved; return this.readiness?.resolved || false;
} }
/** /**
@ -133,7 +135,7 @@ export class CoreInitDelegate {
this.initReadiness(); this.initReadiness();
} }
await this.readiness.promise; await this.readiness!.promise;
} }
/** /**

View File

@ -30,9 +30,9 @@ export class CoreLangProvider {
protected fallbackLanguage = 'en'; // Always use English as fallback language since it contains all strings. protected fallbackLanguage = 'en'; // Always use English as fallback language since it contains all strings.
protected defaultLanguage = CoreConfigConstants.default_lang || 'en'; // Lang to use if device lang not valid or is forced. protected defaultLanguage = CoreConfigConstants.default_lang || 'en'; // Lang to use if device lang not valid or is forced.
protected currentLanguage: string; // Save current language in a variable to speed up the get function. protected currentLanguage?: string; // Save current language in a variable to speed up the get function.
protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool. protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool.
protected customStringsRaw: string; protected customStringsRaw?: string;
protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins. protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins.
constructor() { constructor() {
@ -123,7 +123,7 @@ export class CoreLangProvider {
* @return Promise resolved when the change is finished. * @return Promise resolved when the change is finished.
*/ */
async changeCurrentLanguage(language: string): Promise<void> { async changeCurrentLanguage(language: string): Promise<void> {
const promises = []; const promises: Promise<unknown>[] = [];
// Change the language, resolving the promise when we receive the first value. // Change the language, resolving the promise when we receive the first value.
promises.push(new Promise((resolve, reject) => { promises.push(new Promise((resolve, reject) => {
@ -363,8 +363,8 @@ export class CoreLangProvider {
if (currentLangChanged) { if (currentLangChanged) {
// Some lang strings have changed, emit an event to update the pipes. // Some lang strings have changed, emit an event to update the pipes.
Translate.instance.onLangChange.emit({ Translate.instance.onLangChange.emit({
lang: this.currentLanguage, lang: this.currentLanguage!,
translations: Translate.instance.translations[this.currentLanguage], translations: Translate.instance.translations[this.currentLanguage!],
}); });
} }
} }

View File

@ -113,7 +113,7 @@ export class CoreIframeUtilsProvider {
} }
// Remove the warning and show the iframe // Remove the warning and show the iframe
CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); CoreDomUtils.instance.removeElement(element.parentElement!, 'div.core-iframe-offline-warning');
element.classList.remove('core-iframe-offline-disabled'); element.classList.remove('core-iframe-offline-disabled');
if (isSubframe) { if (isSubframe) {
@ -131,9 +131,9 @@ export class CoreIframeUtilsProvider {
* @param element Element to treat (iframe, embed, ...). * @param element Element to treat (iframe, embed, ...).
* @return Window and Document. * @return Window and Document.
*/ */
getContentWindowAndDocument(element: CoreFrameElement): { window: Window; document: Document } { getContentWindowAndDocument(element: CoreFrameElement): { window: Window | null; document: Document | null } {
let contentWindow: Window = 'contentWindow' in element ? element.contentWindow : undefined; let contentWindow: Window | null = 'contentWindow' in element ? element.contentWindow : null;
let contentDocument: Document; let contentDocument: Document | null = null;
try { try {
contentDocument = 'contentDocument' in element && element.contentDocument contentDocument = 'contentDocument' in element && element.contentDocument
@ -209,7 +209,8 @@ export class CoreIframeUtilsProvider {
contentWindow.open = (url: string, name: string) => { contentWindow.open = (url: string, name: string) => {
this.windowOpen(url, name, element, navCtrl); this.windowOpen(url, name, element, navCtrl);
return null; // eslint-disable-next-line @typescript-eslint/no-explicit-any
return null as any;
}; };
} }
@ -233,31 +234,35 @@ export class CoreIframeUtilsProvider {
* @param navCtrl NavController to use if a link can be opened in the app. * @param navCtrl NavController to use if a link can be opened in the app.
*/ */
treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void { treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void {
if (element) { if (!element) {
return;
}
const treatElement = (sendResizeEvent: boolean = false) => {
this.checkOnlineFrameInOffline(element, isSubframe); this.checkOnlineFrameInOffline(element, isSubframe);
let winAndDoc = this.getContentWindowAndDocument(element); const { window, document } = this.getContentWindowAndDocument(element);
// Redefine window.open in this element and sub frames, it might have been loaded already. // Redefine window.open in this element and sub frames, it might have been loaded already.
this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document, navCtrl); if (window && document) {
this.redefineWindowOpen(element, window, document, navCtrl);
}
// Treat links. // Treat links.
this.treatFrameLinks(element, winAndDoc.document); if (document) {
this.treatFrameLinks(element, document);
}
element.addEventListener('load', () => { // Send a resize events to the iframe so it calculates the right size if needed.
this.checkOnlineFrameInOffline(element, isSubframe); if (window && sendResizeEvent) {
setTimeout(() => window.dispatchEvent(new Event('resize')), 1000);
}
};
// Element loaded, redefine window.open and treat links again. treatElement();
winAndDoc = this.getContentWindowAndDocument(element);
this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document, navCtrl);
this.treatFrameLinks(element, winAndDoc.document);
if (winAndDoc.window) { // Element loaded, redefine window.open and treat links again.
// Send a resize events to the iframe so it calculates the right size if needed. element.addEventListener('load', () => treatElement(true));
setTimeout(() => {
winAndDoc.window.dispatchEvent(new Event('resize'));
}, 1000);
}
});
}
} }
/** /**
@ -279,7 +284,7 @@ export class CoreIframeUtilsProvider {
} }
// Find the link being clicked. // Find the link being clicked.
let el = <Element> event.target; let el: Element | null = event.target as Element;
while (el && el.tagName !== 'A') { while (el && el.tagName !== 'A') {
el = el.parentElement; el = el.parentElement;
} }
@ -386,19 +391,24 @@ export class CoreIframeUtilsProvider {
} }
const urlParts = CoreUrl.parse(link.href); const urlParts = CoreUrl.parse(link.href);
if (!link.href || (urlParts.protocol && urlParts.protocol == 'javascript')) { if (!link.href || !urlParts || (urlParts.protocol && urlParts.protocol == 'javascript')) {
// Links with no URL and Javascript links are ignored. // Links with no URL and Javascript links are ignored.
return; return;
} }
if (!CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol)) { if (urlParts.protocol && !CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol)) {
// Scheme suggests it's an external resource. // Scheme suggests it's an external resource.
event && event.preventDefault(); event && event.preventDefault();
const frameSrc = element && ((<HTMLFrameElement> element).src || (<HTMLObjectElement> element).data); const frameSrc = element && ((<HTMLFrameElement> element).src || (<HTMLObjectElement> element).data);
// If the frame is not local, check the target to identify how to treat the link. // If the frame is not local, check the target to identify how to treat the link.
if (element && !CoreUrlUtils.instance.isLocalFileUrl(frameSrc) && (!link.target || link.target == '_self')) { if (
element &&
frameSrc &&
!CoreUrlUtils.instance.isLocalFileUrl(frameSrc) &&
(!link.target || link.target == '_self')
) {
// Load the link inside the frame itself. // Load the link inside the frame itself.
if (element.tagName.toLowerCase() == 'object') { if (element.tagName.toLowerCase() == 'object') {
element.setAttribute('data', link.href); element.setAttribute('data', link.href);
@ -455,8 +465,8 @@ export class CoreIframeUtilsProvider {
const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
userScriptWindow.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
userScriptWindow.WKUserScript.addScript({ userScriptWindow.WKUserScript?.addScript({
id: 'CoreIframeUtilsRecaptchaScript', id: 'CoreIframeUtilsRecaptchaScript',
file: recaptchaPath, file: recaptchaPath,
injectionTime: WKUserScriptInjectionTime.END, injectionTime: WKUserScriptInjectionTime.END,

View File

@ -116,7 +116,7 @@ export class CoreMimetypeUtilsProvider {
*/ */
protected fillGroupMimeInfo(group: string): void { protected fillGroupMimeInfo(group: string): void {
const mimetypes = {}; // Use an object to prevent duplicates. const mimetypes = {}; // Use an object to prevent duplicates.
const extensions = []; // Extensions are unique. const extensions: string[] = []; // Extensions are unique.
for (const extension in this.extToMime) { for (const extension in this.extToMime) {
const data = this.extToMime[extension]; const data = this.extToMime[extension];
@ -140,13 +140,13 @@ export class CoreMimetypeUtilsProvider {
* @param url URL of the file. It will be used if there's more than one possible extension. * @param url URL of the file. It will be used if there's more than one possible extension.
* @return Extension. * @return Extension.
*/ */
getExtension(mimetype: string, url?: string): string { getExtension(mimetype: string, url?: string): string | undefined {
mimetype = mimetype || ''; mimetype = mimetype || '';
mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any.
if (mimetype == 'application/x-forcedownload' || mimetype == 'application/forcedownload') { if (mimetype == 'application/x-forcedownload' || mimetype == 'application/forcedownload') {
// Couldn't get the right mimetype, try to guess it. // Couldn't get the right mimetype, try to guess it.
return this.guessExtensionFromUrl(url); return url && this.guessExtensionFromUrl(url);
} }
const extensions = this.mimeToExt[mimetype]; const extensions = this.mimeToExt[mimetype];
@ -154,7 +154,7 @@ export class CoreMimetypeUtilsProvider {
if (extensions.length > 1 && url) { if (extensions.length > 1 && url) {
// There's more than one possible extension. Check if the URL has extension. // There's more than one possible extension. Check if the URL has extension.
const candidate = this.guessExtensionFromUrl(url); const candidate = this.guessExtensionFromUrl(url);
if (extensions.indexOf(candidate) != -1) { if (candidate && extensions.indexOf(candidate) != -1) {
return candidate; return candidate;
} }
} }
@ -173,19 +173,22 @@ export class CoreMimetypeUtilsProvider {
const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename; const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename;
const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype
? this.getExtension(file.mimetype) ? this.getExtension(file.mimetype)
: this.getFileExtension(filename); : (filename && this.getFileExtension(filename));
const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype ? file.mimetype : this.getMimeType(extension); const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype
? file.mimetype
: (extension && this.getMimeType(extension));
// @todo linting: See if this can be removed // @todo linting: See if this can be removed
(file as CoreWSExternalFile).mimetype = mimeType; (file as CoreWSExternalFile).mimetype = mimeType;
if (this.canBeEmbedded(extension)) { if (extension && this.canBeEmbedded(extension)) {
const embedType = this.getExtensionType(extension); const embedType = this.getExtensionType(extension);
// @todo linting: See if this can be removed // @todo linting: See if this can be removed
(file as { embedType: string }).embedType = embedType; (file as { embedType?: string }).embedType = embedType;
path = CoreFile.instance.convertFileSrc(path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl)); path = path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl);
path = path && CoreFile.instance.convertFileSrc(path);
switch (embedType) { switch (embedType) {
case 'image': case 'image':
@ -223,7 +226,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension. * @param extension Extension.
* @return Icon. Undefined if not found. * @return Icon. Undefined if not found.
*/ */
getExtensionIconName(extension: string): string { getExtensionIconName(extension: string): string | undefined {
if (this.extToMime[extension]) { if (this.extToMime[extension]) {
if (this.extToMime[extension].icon) { if (this.extToMime[extension].icon) {
return this.extToMime[extension].icon; return this.extToMime[extension].icon;
@ -242,7 +245,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension. * @param extension Extension.
* @return Type of the extension. * @return Type of the extension.
*/ */
getExtensionType(extension: string): string { getExtensionType(extension: string): string | undefined {
extension = this.cleanExtension(extension); extension = this.cleanExtension(extension);
if (this.extToMime[extension] && this.extToMime[extension].string) { if (this.extToMime[extension] && this.extToMime[extension].string) {
@ -270,8 +273,8 @@ export class CoreMimetypeUtilsProvider {
* @return The path to a file icon. * @return The path to a file icon.
*/ */
getFileIcon(filename: string): string { getFileIcon(filename: string): string {
const ext = this.getFileExtension(filename); const extension = this.getFileExtension(filename);
const icon = this.getExtensionIconName(ext) || 'unknown'; const icon = (extension && this.getExtensionIconName(extension)) || 'unknown';
return this.getFileIconForType(icon); return this.getFileIconForType(icon);
} }
@ -302,14 +305,14 @@ export class CoreMimetypeUtilsProvider {
* @param fileUrl The file URL. * @param fileUrl The file URL.
* @return The lowercased extension without the dot, or undefined. * @return The lowercased extension without the dot, or undefined.
*/ */
guessExtensionFromUrl(fileUrl: string): string { guessExtensionFromUrl(fileUrl: string): string | undefined {
const split = fileUrl.split('.'); const split = fileUrl.split('.');
let candidate; let candidate;
let extension; let extension;
let position; let position;
if (split.length > 1) { if (split.length > 1) {
candidate = split.pop().toLowerCase(); candidate = split.pop()!.toLowerCase();
// Remove params if any. // Remove params if any.
position = candidate.indexOf('?'); position = candidate.indexOf('?');
if (position > -1) { if (position > -1) {
@ -338,7 +341,7 @@ export class CoreMimetypeUtilsProvider {
* @param filename The file name. * @param filename The file name.
* @return The lowercased extension, or undefined. * @return The lowercased extension, or undefined.
*/ */
getFileExtension(filename: string): string { getFileExtension(filename: string): string | undefined {
const dot = filename.lastIndexOf('.'); const dot = filename.lastIndexOf('.');
let ext; let ext;
@ -382,7 +385,7 @@ export class CoreMimetypeUtilsProvider {
* @param extension Extension. * @param extension Extension.
* @return Mimetype. * @return Mimetype.
*/ */
getMimeType(extension: string): string { getMimeType(extension: string): string | undefined {
extension = this.cleanExtension(extension); extension = this.cleanExtension(extension);
if (this.extToMime[extension] && this.extToMime[extension].type) { if (this.extToMime[extension] && this.extToMime[extension].type) {
@ -400,9 +403,9 @@ export class CoreMimetypeUtilsProvider {
*/ */
getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string { getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string {
const langPrefix = 'assets.mimetypes.'; const langPrefix = 'assets.mimetypes.';
let filename = ''; let filename: string | undefined = '';
let mimetype = ''; let mimetype: string | undefined = '';
let extension = ''; let extension: string | undefined = '';
if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) { if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) {
// It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable. // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
@ -419,7 +422,7 @@ export class CoreMimetypeUtilsProvider {
if (!mimetype) { if (!mimetype) {
// Try to calculate the mimetype using the extension. // Try to calculate the mimetype using the extension.
mimetype = this.getMimeType(extension); mimetype = extension && this.getMimeType(extension);
} }
} }
@ -478,7 +481,7 @@ export class CoreMimetypeUtilsProvider {
* @param mimetype Mimetype. * @param mimetype Mimetype.
* @return Type of the mimetype. * @return Type of the mimetype.
*/ */
getMimetypeType(mimetype: string): string { getMimetypeType(mimetype: string): string | undefined {
mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any.
const extensions = this.mimeToExt[mimetype]; const extensions = this.mimeToExt[mimetype];
@ -542,9 +545,9 @@ export class CoreMimetypeUtilsProvider {
isExtensionInGroup(extension: string, groups: string[]): boolean { isExtensionInGroup(extension: string, groups: string[]): boolean {
extension = this.cleanExtension(extension); extension = this.cleanExtension(extension);
if (groups && groups.length && this.extToMime[extension] && this.extToMime[extension].groups) { if (groups?.length && this.extToMime[extension]?.groups) {
for (let i = 0; i < this.extToMime[extension].groups.length; i++) { for (let i = 0; i < this.extToMime[extension].groups!.length; i++) {
const group = this.extToMime[extension].groups[i]; const group = this.extToMime[extension].groups![i];
if (groups.indexOf(group) != -1) { if (groups.indexOf(group) != -1) {
return true; return true;
} }

View File

@ -175,11 +175,11 @@ export class CoreTextUtilsProvider {
// Filter invalid messages, and convert them to messages in case they're errors. // Filter invalid messages, and convert them to messages in case they're errors.
const messages: string[] = []; const messages: string[] = [];
paragraphs.forEach((paragraph) => { paragraphs.forEach(paragraph => {
// If it's an error, get its message. // If it's an error, get its message.
const message = this.getErrorMessageFromError(paragraph); const message = this.getErrorMessageFromError(paragraph);
if (paragraph) { if (paragraph && message) {
messages.push(message); messages.push(message);
} }
}); });
@ -248,8 +248,7 @@ export class CoreTextUtilsProvider {
// First, we use a regexpr. // First, we use a regexpr.
text = text.replace(/(<([^>]+)>)/ig, ''); text = text.replace(/(<([^>]+)>)/ig, '');
// Then, we rely on the browser. We need to wrap the text to be sure is HTML. // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
const element = this.convertToElement(text); text = this.convertToElement(text).textContent!;
text = element.textContent;
// Recover or remove new lines. // Recover or remove new lines.
text = this.replaceNewLines(text, singleLine ? ' ' : '<br>'); text = this.replaceNewLines(text, singleLine ? ' ' : '<br>');
@ -326,7 +325,7 @@ export class CoreTextUtilsProvider {
text = text.replace(/_/gi, ' '); text = text.replace(/_/gi, ' ');
// This RegEx will detect any word change including Unicode chars. Some languages without spaces won't be counted fine. // This RegEx will detect any word change including Unicode chars. Some languages without spaces won't be counted fine.
return text.match(/\S+/gi).length; return text.match(/\S+/gi)?.length || 0;
} }
/** /**
@ -359,8 +358,7 @@ export class CoreTextUtilsProvider {
*/ */
decodeHTMLEntities(text: string): string { decodeHTMLEntities(text: string): string {
if (text) { if (text) {
const element = this.convertToElement(text); text = this.convertToElement(text).textContent!;
text = element.textContent;
} }
return text; return text;
@ -512,7 +510,7 @@ export class CoreTextUtilsProvider {
if (clean) { if (clean) {
formatted = this.cleanTags(formatted, singleLine); formatted = this.cleanTags(formatted, singleLine);
} }
if (shortenLength > 0) { if (shortenLength && shortenLength > 0) {
formatted = this.shortenText(formatted, shortenLength); formatted = this.shortenText(formatted, shortenLength);
} }
if (highlight) { if (highlight) {
@ -529,10 +527,11 @@ export class CoreTextUtilsProvider {
* @param error Error object. * @param error Error object.
* @return Error message, undefined if not found. * @return Error message, undefined if not found.
*/ */
getErrorMessageFromError(error: string | CoreError | CoreTextErrorObject): string { getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject): string | undefined {
if (typeof error == 'string') { if (typeof error == 'string') {
return error; return error;
} }
if (error instanceof CoreError) { if (error instanceof CoreError) {
return error.message; return error.message;
} }
@ -546,12 +545,12 @@ export class CoreTextUtilsProvider {
* @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute.
* @return Pluginfile URL, undefined if no files found. * @return Pluginfile URL, undefined if no files found.
*/ */
getTextPluginfileUrl(files: CoreWSExternalFile[]): string { getTextPluginfileUrl(files: CoreWSExternalFile[]): string | undefined {
if (files && files.length) { if (files?.length) {
const url = files[0].fileurl; const url = files[0].fileurl;
// Remove text after last slash (encoded or not). // Remove text after last slash (encoded or not).
return url.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F'))); return url?.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
} }
return undefined; return undefined;
@ -876,7 +875,7 @@ export class CoreTextUtilsProvider {
// Current lang not found. Try to find the first language. // Current lang not found. Try to find the first language.
const matches = text.match(anyLangRegEx); const matches = text.match(anyLangRegEx);
if (matches && matches[0]) { if (matches && matches[0]) {
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)![1];
currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g'); currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g');
} else { } else {
// No multi-lang tag found, stop. // No multi-lang tag found, stop.

View File

@ -42,9 +42,9 @@ export class CoreUtilsProvider {
protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]'];
protected logger: CoreLogger; protected logger: CoreLogger;
protected iabInstance: InAppBrowserObject; protected iabInstance?: InAppBrowserObject;
protected uniqueIds: {[name: string]: number} = {}; protected uniqueIds: {[name: string]: number} = {};
protected qrScanData: {deferred: PromiseDefer<string>; observable: Subscription}; protected qrScanData?: {deferred: PromiseDefer<string>; observable: Subscription};
constructor(protected zone: NgZone) { constructor(protected zone: NgZone) {
this.logger = CoreLogger.getInstance('CoreUtilsProvider'); this.logger = CoreLogger.getInstance('CoreUtilsProvider');
@ -61,22 +61,14 @@ export class CoreUtilsProvider {
* @return New error message. * @return New error message.
*/ */
addDataNotDownloadedError(error: Error | string, defaultError?: string): string { addDataNotDownloadedError(error: Error | string, defaultError?: string): string {
let errorMessage = error; const errorMessage = CoreTextUtils.instance.getErrorMessageFromError(error) || defaultError || '';
if (error && typeof error != 'string') { if (this.isWebServiceError(error)) {
errorMessage = CoreTextUtils.instance.getErrorMessageFromError(error); return errorMessage;
} }
if (typeof errorMessage != 'string') { // Local error. Add an extra warning.
errorMessage = defaultError || ''; return errorMessage + '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
}
if (!this.isWebServiceError(error)) {
// Local error. Add an extra warning.
errorMessage += '<br><br>' + Translate.instance.instant('core.errorsomedatanotdownloaded');
}
return errorMessage;
} }
/** /**
@ -116,12 +108,16 @@ export class CoreUtilsProvider {
* @param result Object where to put the properties. If not defined, a new object will be created. * @param result Object where to put the properties. If not defined, a new object will be created.
* @return The object. * @return The object.
*/ */
arrayToObject(array: unknown[], propertyName?: string, result?: unknown): unknown { arrayToObject<T extends Record<string, unknown> | string>(
result = result || {}; array: T[],
array.forEach((entry) => { propertyName?: string,
result: Record<string, T> = {},
): Record<string, T> {
for (const entry of array) {
const key = propertyName ? entry[propertyName] : entry; const key = propertyName ? entry[propertyName] : entry;
result[key] = entry; result[key] = entry;
}); }
return result; return result;
} }
@ -144,7 +140,7 @@ export class CoreUtilsProvider {
maxLevels: number = 0, maxLevels: number = 0,
level: number = 0, level: number = 0,
undefinedIsNull: boolean = true, undefinedIsNull: boolean = true,
): boolean { ): boolean | undefined {
if (typeof itemA == 'function' || typeof itemB == 'function') { if (typeof itemA == 'function' || typeof itemB == 'function') {
return true; // Don't compare functions. return true; // Don't compare functions.
} else if (typeof itemA == 'object' && typeof itemB == 'object') { } else if (typeof itemA == 'object' && typeof itemB == 'object') {
@ -266,9 +262,9 @@ export class CoreUtilsProvider {
} }
return newArray; return newArray;
} else if (typeof source == 'object' && source !== null) { } else if (this.isObject(source)) {
// Check if the object shouldn't be copied. // Check if the object shouldn't be copied.
if (source && source.toString && this.DONT_CLONE.indexOf(source.toString()) != -1) { if (source.toString && this.DONT_CLONE.indexOf(source.toString()) != -1) {
// Object shouldn't be copied, return it as it is. // Object shouldn't be copied, return it as it is.
return source; return source;
} }
@ -365,7 +361,7 @@ export class CoreUtilsProvider {
* @return Promise resolved when all promises are resolved. * @return Promise resolved when all promises are resolved.
*/ */
executeOrderedPromises(orderedPromisesData: OrderedPromiseData[]): Promise<void> { executeOrderedPromises(orderedPromisesData: OrderedPromiseData[]): Promise<void> {
const promises = []; const promises: Promise<void>[] = [];
let dependency = Promise.resolve(); let dependency = Promise.resolve();
// Execute all the processes in order. // Execute all the processes in order.
@ -465,8 +461,8 @@ export class CoreUtilsProvider {
checkAll?: boolean, checkAll?: boolean,
...args: P ...args: P
): Promise<string[]> { ): Promise<string[]> {
const promises = []; const promises: Promise<false | number>[] = [];
const enabledSites = []; const enabledSites: string[] = [];
for (const i in siteIds) { for (const i in siteIds) {
const siteId = siteIds[i]; const siteId = siteIds[i];
@ -626,7 +622,7 @@ export class CoreUtilsProvider {
// Get the keys of the countries. // Get the keys of the countries.
return this.getCountryList().then((countries) => { return this.getCountryList().then((countries) => {
// Sort translations. // Sort translations.
const sortedCountries = []; const sortedCountries: { code: string; name: string }[] = [];
Object.keys(countries).sort((a, b) => countries[a].localeCompare(countries[b])).forEach((key) => { Object.keys(countries).sort((a, b) => countries[a].localeCompare(countries[b])).forEach((key) => {
sortedCountries.push({ code: key, name: countries[key] }); sortedCountries.push({ code: key, name: countries[key] });
@ -669,7 +665,7 @@ export class CoreUtilsProvider {
const table = await CoreLang.instance.getTranslationTable(lang); const table = await CoreLang.instance.getTranslationTable(lang);
// Gather all the keys for countries, // Gather all the keys for countries,
const keys = []; const keys: string[] = [];
for (const name in table) { for (const name in table) {
if (name.indexOf('assets.countries.') === 0) { if (name.indexOf('assets.countries.') === 0) {
@ -696,7 +692,7 @@ export class CoreUtilsProvider {
getMimeTypeFromUrl(url: string): Promise<string> { getMimeTypeFromUrl(url: string): Promise<string> {
// First check if it can be guessed from the URL. // First check if it can be guessed from the URL.
const extension = CoreMimetypeUtils.instance.guessExtensionFromUrl(url); const extension = CoreMimetypeUtils.instance.guessExtensionFromUrl(url);
const mimetype = CoreMimetypeUtils.instance.getMimeType(extension); const mimetype = extension && CoreMimetypeUtils.instance.getMimeType(extension);
if (mimetype) { if (mimetype) {
return Promise.resolve(mimetype); return Promise.resolve(mimetype);
@ -730,6 +726,16 @@ export class CoreUtilsProvider {
return 'isFile' in file; return 'isFile' in file;
} }
/**
* Check if a value is an object.
*
* @param object Variable.
* @return Type guard indicating if this is an object.
*/
isObject(object: unknown): object is Record<string, unknown> {
return typeof object === 'object' && object !== null;
}
/** /**
* Given a list of files, check if there are repeated names. * Given a list of files, check if there are repeated names.
* *
@ -741,12 +747,12 @@ export class CoreUtilsProvider {
return false; return false;
} }
const names = []; const names: string[] = [];
// Check if there are 2 files with the same name. // Check if there are 2 files with the same name.
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
const name = this.isFileEntry(file) ? file.name : file.filename; const name = (this.isFileEntry(file) ? file.name : file.filename) || '';
if (names.indexOf(name) > -1) { if (names.indexOf(name) > -1) {
return Translate.instance.instant('core.filenameexist', { $a: name }); return Translate.instance.instant('core.filenameexist', { $a: name });
@ -885,7 +891,7 @@ export class CoreUtilsProvider {
path = CoreFile.instance.unconvertFileSrc(path); path = CoreFile.instance.unconvertFileSrc(path);
const extension = CoreMimetypeUtils.instance.getFileExtension(path); const extension = CoreMimetypeUtils.instance.getFileExtension(path);
const mimetype = CoreMimetypeUtils.instance.getMimeType(extension); const mimetype = extension && CoreMimetypeUtils.instance.getMimeType(extension);
if (mimetype == 'text/html' && CoreApp.instance.isAndroid()) { if (mimetype == 'text/html' && CoreApp.instance.isAndroid()) {
// Open HTML local files in InAppBrowser, in system browser some embedded files aren't loaded. // Open HTML local files in InAppBrowser, in system browser some embedded files aren't loaded.
@ -902,7 +908,7 @@ export class CoreUtilsProvider {
} }
try { try {
await FileOpener.instance.open(path, mimetype); await FileOpener.instance.open(path, mimetype || '');
} catch (error) { } catch (error) {
this.logger.error('Error opening file ' + path + ' with mimetype ' + mimetype); this.logger.error('Error opening file ' + path + ' with mimetype ' + mimetype);
this.logger.error('Error: ', JSON.stringify(error)); this.logger.error('Error: ', JSON.stringify(error));
@ -924,7 +930,7 @@ export class CoreUtilsProvider {
* @param options Override default options passed to InAppBrowser. * @param options Override default options passed to InAppBrowser.
* @return The opened window. * @return The opened window.
*/ */
openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject { openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject | undefined {
if (!url) { if (!url) {
return; return;
} }
@ -950,7 +956,7 @@ export class CoreUtilsProvider {
if (CoreApp.instance.isDesktop() || CoreApp.instance.isMobile()) { if (CoreApp.instance.isDesktop() || CoreApp.instance.isMobile()) {
let loadStopSubscription; let loadStopSubscription;
const loadStartUrls = []; const loadStartUrls: string[] = [];
// Trigger global events when a url is loaded or the window is closed. This is to make it work like in Ionic 1. // Trigger global events when a url is loaded or the window is closed. This is to make it work like in Ionic 1.
const loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => { const loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => {
@ -1076,10 +1082,10 @@ export class CoreUtilsProvider {
if (typeof value == 'undefined' || value == null) { if (typeof value == 'undefined' || value == null) {
// Filter undefined and null values. // Filter undefined and null values.
return; return;
} else if (typeof value == 'object') { } else if (this.isObject(value)) {
// It's an object, return at least an entry for each property. // It's an object, return at least an entry for each property.
const keys = Object.keys(value); const keys = Object.keys(value);
let entries = []; let entries: unknown[] = [];
keys.forEach((key) => { keys.forEach((key) => {
const newElKey = elKey ? elKey + '[' + key + ']' : key; const newElKey = elKey ? elKey + '[' + key + ']' : key;
@ -1110,9 +1116,9 @@ export class CoreUtilsProvider {
if (sortByKey || sortByValue) { if (sortByKey || sortByValue) {
return entries.sort((a, b) => { return entries.sort((a, b) => {
if (sortByKey) { if (sortByKey) {
return a[keyName] >= b[keyName] ? 1 : -1; return (a[keyName] as number) >= (b[keyName] as number) ? 1 : -1;
} else { } else {
return a[valueName] >= b[valueName] ? 1 : -1; return (a[valueName] as number) >= (b[valueName] as number) ? 1 : -1;
} }
}); });
} }
@ -1135,7 +1141,7 @@ export class CoreUtilsProvider {
keyName: string, keyName: string,
valueName: string, valueName: string,
keyPrefix?: string, keyPrefix?: string,
): {[name: string]: unknown} { ): {[name: string]: unknown} | undefined {
if (!objects) { if (!objects) {
return; return;
} }
@ -1206,13 +1212,13 @@ export class CoreUtilsProvider {
* @return The deferred promise. * @return The deferred promise.
*/ */
promiseDefer<T>(): PromiseDefer<T> { promiseDefer<T>(): PromiseDefer<T> {
const deferred: PromiseDefer<T> = {}; const deferred: Partial<PromiseDefer<T>> = {};
deferred.promise = new Promise((resolve, reject): void => { deferred.promise = new Promise((resolve, reject): void => {
deferred.resolve = resolve; deferred.resolve = resolve;
deferred.reject = reject; deferred.reject = reject;
}); });
return deferred; return deferred as PromiseDefer<T>;
} }
/** /**
@ -1257,7 +1263,11 @@ export class CoreUtilsProvider {
* @param key Key to check. * @param key Key to check.
* @return Whether the two objects/arrays have the same value (or lack of one) for a given key. * @return Whether the two objects/arrays have the same value (or lack of one) for a given key.
*/ */
sameAtKeyMissingIsBlank(obj1: unknown, obj2: unknown, key: string): boolean { sameAtKeyMissingIsBlank(
obj1: Record<string, unknown> | unknown[],
obj2: Record<string, unknown> | unknown[],
key: string,
): boolean {
let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : ''; let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : '';
let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : '';
@ -1426,19 +1436,19 @@ export class CoreUtilsProvider {
* @return Array without duplicate values. * @return Array without duplicate values.
*/ */
uniqueArray<T>(array: T[], key?: string): T[] { uniqueArray<T>(array: T[], key?: string): T[] {
const filtered = [];
const unique = {}; // Use an object to make it faster to check if it's duplicate. const unique = {}; // Use an object to make it faster to check if it's duplicate.
array.forEach((entry) => { return array.filter(entry => {
const value = key ? entry[key] : entry; const value = key ? entry[key] : entry;
if (!unique[value]) { if (value in unique) {
unique[value] = true; unique[value] = true;
filtered.push(entry);
}
});
return filtered; return true;
}
return false;
});
} }
/** /**
@ -1487,7 +1497,14 @@ export class CoreUtilsProvider {
* *
* @return Promise resolved with the QR string, rejected if error or cancelled. * @return Promise resolved with the QR string, rejected if error or cancelled.
*/ */
async startScanQR(): Promise<string> { async startScanQR(): Promise<string | undefined> {
try {
return this.startScanQR();
} catch (error) {
// do nothing
}
if (!CoreApp.instance.isMobile()) { if (!CoreApp.instance.isMobile()) {
return Promise.reject('QRScanner isn\'t available in desktop apps.'); return Promise.reject('QRScanner isn\'t available in desktop apps.');
} }
@ -1613,21 +1630,21 @@ export type PromiseDefer<T> = {
/** /**
* The promise. * The promise.
*/ */
promise?: Promise<T>; promise: Promise<T>;
/** /**
* Function to resolve the promise. * Function to resolve the promise.
* *
* @param value The resolve value. * @param value The resolve value.
*/ */
resolve?: (value?: T) => void; // Function to resolve the promise. resolve: (value?: T) => void; // Function to resolve the promise.
/** /**
* Function to reject the promise. * Function to reject the promise.
* *
* @param reason The reject param. * @param reason The reject param.
*/ */
reject?: (reason?: unknown) => void; reject: (reason?: unknown) => void;
}; };
/** /**

View File

@ -15,6 +15,12 @@
import moment from 'moment'; import moment from 'moment';
import { environment } from '@/environments/environment'; import { environment } from '@/environments/environment';
/**
* Log function type.
*/
type LogFunction = (...data: unknown[]) => void;
/** /**
* Helper service to display messages in the console. * Helper service to display messages in the console.
* *
@ -34,9 +40,13 @@ export class CoreLogger {
debug: LogFunction; debug: LogFunction;
error: LogFunction; error: LogFunction;
// Avoid creating singleton instances. // Avoid creating instances.
private constructor() { private constructor(log: LogFunction, info: LogFunction, warn: LogFunction, debug: LogFunction, error: LogFunction) {
// Nothing to do. this.log = log;
this.info = info;
this.warn = warn;
this.debug = debug;
this.error = error;
} }
/** /**
@ -54,29 +64,23 @@ export class CoreLogger {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
const muted = () => {}; const muted = () => {};
return { return new CoreLogger(muted, muted, muted, muted, muted);
log: muted,
info: muted,
warn: muted,
debug: muted,
error: muted,
};
} }
className = className || ''; className = className || '';
return { return new CoreLogger(
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
log: CoreLogger.prepareLogFn(console.log.bind(console), className), CoreLogger.prepareLogFn(console.log.bind(console), className),
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
info: CoreLogger.prepareLogFn(console.info.bind(console), className), CoreLogger.prepareLogFn(console.info.bind(console), className),
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
warn: CoreLogger.prepareLogFn(console.warn.bind(console), className), CoreLogger.prepareLogFn(console.warn.bind(console), className),
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
debug: CoreLogger.prepareLogFn(console.debug.bind(console), className), CoreLogger.prepareLogFn(console.debug.bind(console), className),
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
error: CoreLogger.prepareLogFn(console.error.bind(console), className), CoreLogger.prepareLogFn(console.error.bind(console), className),
}; );
} }
/** /**
@ -96,8 +100,3 @@ export class CoreLogger {
} }
} }
/**
* Log function type.
*/
type LogFunction = (...data: unknown[]) => void;

View File

@ -7,6 +7,8 @@
"declaration": false, "declaration": false,
"downlevelIteration": true, "downlevelIteration": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,