diff --git a/config.xml b/config.xml index 6f949ffa9..1282a7a65 100644 --- a/config.xml +++ b/config.xml @@ -8,6 +8,7 @@ + @@ -38,6 +39,8 @@ + + @@ -171,6 +174,7 @@ + @@ -178,6 +182,8 @@ + + diff --git a/config/webpack.config.js b/config/webpack.config.js index 3f622bcd3..1c6ae7ca5 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -12,7 +12,8 @@ const customConfig = { '@providers': resolve('./src/providers'), '@components': resolve('./src/components'), '@directives': resolve('./src/directives'), - '@pipes': resolve('./src/pipes') + '@pipes': resolve('./src/pipes'), + '@singletons': resolve('./src/singletons'), } }, externals: [ diff --git a/package-lock.json b/package-lock.json index 88471ab97..15e6dbdf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.8.1", + "version": "3.8.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -147,6 +147,11 @@ "resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.20.0.tgz", "integrity": "sha512-zyxaW+vZb1OHeDgGbrZHQe3hy30K4YeKjGr8KNGcwq+k2ZHkfqo/H6XIwf2m/UlFTgacvdR9XZtfP+6N0suybg==" }, + "@ionic-native/http": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/http/-/http-4.20.0.tgz", + "integrity": "sha512-DF+Y1oYoHTv9Y22a2jLgniOmj9Twba+9j8rzHA4xboVT2HpB6bsBSWOktdAXDVjoajXiLsA/u7fh6YD8//NVGg==" + }, "@ionic-native/in-app-browser": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-4.20.0.tgz", @@ -1366,7 +1371,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "" + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -2620,6 +2626,11 @@ } } }, + "cordova-plugin-advanced-http": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-advanced-http/-/cordova-plugin-advanced-http-2.4.1.tgz", + "integrity": "sha512-6G8MTy/d02jE6n3Y9CVyCtD5hZGiBb+/dR2AIzhKN1RGGz38g1D2C8yE4MqHRvnmry6k/KHQWT1MsHNXrjouXQ==" + }, "cordova-plugin-badge": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/cordova-plugin-badge/-/cordova-plugin-badge-0.8.8.tgz", @@ -2675,6 +2686,11 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.1.3.tgz", "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, + "cordova-plugin-ionic-webview": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-webview/-/cordova-plugin-ionic-webview-4.1.3.tgz", + "integrity": "sha512-hlrUF0kLjjEkZmpYlLJO0NnXmVjMmQ3MOZVXm1ytDihLPKHklYCOpCvjA5Wz3hJrPD1shFEsqi/SPnp873AsdQ==" + }, "cordova-plugin-local-notification": { "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#0bb96b757fb484553ceabf35a59802f7983a2836", "from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle" @@ -2709,6 +2725,10 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.4.tgz", "integrity": "sha512-EYC5eQFVkoYXq39l7tYKE6lEjHJ04mvTmKXxGL7quHLdFPfJMNzru/UYpn92AOfpl3PQaZmou78C7EgmFOwFQQ==" }, + "cordova-plugin-wkwebview-cookies": { + "version": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git#0f20979803ffd9b13250215975692703342d759c", + "from": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git" + }, "cordova-plugin-zip": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cordova-plugin-zip/-/cordova-plugin-zip-3.1.0.tgz", @@ -6672,7 +6692,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, "micromatch": { @@ -11323,7 +11344,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "" + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -13484,7 +13506,8 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, "micromatch": { diff --git a/package.json b/package.json index bd1de5e56..941127fae 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@ionic-native/file-transfer": "4.20.0", "@ionic-native/geolocation": "4.20.0", "@ionic-native/globalization": "4.20.0", + "@ionic-native/http": "^4.20.0", "@ionic-native/in-app-browser": "4.20.0", "@ionic-native/keyboard": "4.20.0", "@ionic-native/local-notifications": "4.20.0", @@ -81,6 +82,7 @@ "cordova-android-support-gradle-release": "3.0.1", "cordova-clipboard": "1.3.0", "cordova-ios": "5.1.1", + "cordova-plugin-advanced-http": "2.4.1", "cordova-plugin-badge": "0.8.8", "cordova-plugin-camera": "4.1.0", "cordova-plugin-customurlscheme": "5.0.0", @@ -92,6 +94,7 @@ "cordova-plugin-globalization": "1.11.0", "cordova-plugin-inappbrowser": "3.2.0", "cordova-plugin-ionic-keyboard": "2.1.3", + "cordova-plugin-ionic-webview": "4.1.3", "cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle", "cordova-plugin-media-capture": "3.0.3", "cordova-plugin-network-information": "2.0.2", @@ -99,6 +102,7 @@ "cordova-plugin-splashscreen": "5.0.3", "cordova-plugin-statusbar": "2.4.3", "cordova-plugin-whitelist": "1.3.4", + "cordova-plugin-wkwebview-cookies": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git", "cordova-plugin-zip": "3.1.0", "cordova-sqlite-storage": "4.0.0", "cordova-support-google-services": "1.3.2", @@ -185,7 +189,12 @@ }, "cordova-plugin-geolocation": { "GEOLOCATION_USAGE_DESCRIPTION": "To locate you" - } + }, + "cordova-plugin-ionic-webview": {}, + "cordova-plugin-advanced-http": { + "OKHTTP_VERSION": "3.10.0" + }, + "cordova-plugin-wkwebview-cookies": {} } }, "main": "desktop/electron.js", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 81fdb84a7..c36f76078 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,7 +14,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgModule, COMPILER_OPTIONS } from '@angular/core'; +import { NgModule, COMPILER_OPTIONS, Injector } from '@angular/core'; import { IonicApp, IonicModule, Platform, Content, ScrollEvent, Config, Refresher } from 'ionic-angular'; import { assert } from 'ionic-angular/util/util'; import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; @@ -155,6 +155,8 @@ import { AddonQtypeModule } from '@addon/qtype/qtype.module'; import { AddonStorageManagerModule } from '@addon/storagemanager/storagemanager.module'; import { AddonFilterModule } from '@addon/filter/filter.module'; +import { setSingletonsInjector } from '@singletons/core.singletons'; + // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http, './assets/lang/', '.json'); @@ -357,6 +359,7 @@ export class AppModule { private eventsProvider: CoreEventsProvider, cronDelegate: CoreCronDelegate, siteInfoCronHandler: CoreSiteInfoCronHandler, + injector: Injector, ) { // Register a handler for platform ready. initDelegate.registerProcess({ @@ -391,6 +394,9 @@ export class AppModule { // Register handlers. cronDelegate.register(siteInfoCronHandler); + // Set the injector. + setSingletonsInjector(injector); + // Set transition animation. config.setTransition('core-page-transition', CorePageTransition); config.setTransition('core-modal-lateral-transition', CoreModalLateralTransition); diff --git a/src/classes/native-to-angular-http.ts b/src/classes/native-to-angular-http.ts new file mode 100644 index 000000000..e21e7eb7e --- /dev/null +++ b/src/classes/native-to-angular-http.ts @@ -0,0 +1,98 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { HttpResponse as AngularHttpResponse, HttpHeaders } from '@angular/common/http'; +import { HTTPResponse as NativeHttpResponse } from '@ionic-native/http'; + +const HTTP_STATUS_MESSAGES = { + 100: 'Continue', + 101: 'Switching Protocol', + 102: 'Processing', + 103: 'Early Hints', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + 300: 'Multiple Choice', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'unused', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'I\'m a teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', +}; + +/** + * Class that adapts a Cordova plugin http response to an Angular http response. + */ +export class CoreNativeToAngularHttpResponse extends AngularHttpResponse { + + constructor(protected nativeResponse: NativeHttpResponse) { + super({ + body: nativeResponse.data, + headers: new HttpHeaders(nativeResponse.headers), + status: nativeResponse.status, + statusText: HTTP_STATUS_MESSAGES[nativeResponse.status] || '', + url: nativeResponse.url || '' + }); + } +} diff --git a/src/classes/singletons-factory.ts b/src/classes/singletons-factory.ts new file mode 100644 index 000000000..791b192f0 --- /dev/null +++ b/src/classes/singletons-factory.ts @@ -0,0 +1,76 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Injector, Type } from '@angular/core'; + +/** + * Stub class used to type anonymous classes created in CoreSingletonsFactory#makeSingleton method. + */ +class CoreSingleton {} + +/** + * Token that can be used to resolve instances from the injector. + */ +export type CoreInjectionToken = Type | Type | string; + +/** + * Singleton class created using the factory. + */ +export type CoreSingletonClass = typeof CoreSingleton & { instance: Service }; + +/** + * Factory used to create CoreSingleton classes that get instances from an injector. + */ +export class CoreSingletonsFactory { + + /** + * Angular injector used to resolve singleton instances. + */ + private injector: Injector; + + /** + * Set the injector that will be used to resolve instances in the singletons created with this factory. + * + * @param injector Injector. + */ + setInjector(injector: Injector): void { + this.injector = injector; + } + + /** + * Make a singleton that will hold an instance resolved from the factory injector. + * + * @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the + * provider was defined using a class or the string used in the `provide` key if it was defined using an object. + */ + makeSingleton(injectionToken: CoreInjectionToken): CoreSingletonClass { + // tslint:disable: no-this-assignment + const factory = this; + + return class { + + private static _instance: Service; + + static get instance(): Service { + // Initialize instances lazily. + if (!this._instance) { + this._instance = factory.injector.get(injectionToken); + } + + return this._instance; + } + + }; + } +} diff --git a/src/classes/site.ts b/src/classes/site.ts index 54b2dd294..befacd29e 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -14,7 +14,6 @@ import { Injector } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { HttpClient } from '@angular/common/http'; import { SQLiteDB } from './sqlitedb'; import { CoreAppProvider } from '@providers/app'; import { CoreDbProvider } from '@providers/db'; @@ -190,7 +189,6 @@ export class CoreSite { protected domUtils: CoreDomUtilsProvider; protected eventsProvider: CoreEventsProvider; protected fileProvider: CoreFileProvider; - protected http: HttpClient; protected textUtils: CoreTextUtilsProvider; protected timeUtils: CoreTimeUtilsProvider; protected translate: TranslateService; @@ -256,7 +254,6 @@ export class CoreSite { this.domUtils = injector.get(CoreDomUtilsProvider); this.eventsProvider = injector.get(CoreEventsProvider); this.fileProvider = injector.get(CoreFileProvider); - this.http = injector.get(HttpClient); this.textUtils = injector.get(CoreTextUtilsProvider); this.timeUtils = injector.get(CoreTimeUtilsProvider); this.translate = injector.get(TranslateService); @@ -1357,55 +1354,62 @@ export class CoreSite { * @param retrying True if we're retrying the check. * @return Promise resolved when the check is done. */ - checkLocalMobilePlugin(retrying?: boolean): Promise { + async checkLocalMobilePlugin(retrying?: boolean): Promise { const checkUrl = this.siteUrl + '/local/mobile/check.php', service = CoreConfigConstants.wsextservice; if (!service) { // External service not defined. - return Promise.resolve({ code: 0 }); + return { code: 0 }; } - const promise = this.http.post(checkUrl, { service: service }).timeout(this.wsProvider.getRequestTimeout()).toPromise(); + let data; - return promise.then((data: any) => { - if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') { - if (!retrying) { - this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl); + try { + const response = await this.wsProvider.sendHTTPRequest(checkUrl, { + method: 'post', + data: { service: service }, + }); - return this.checkLocalMobilePlugin(true); - } else { - return Promise.reject(data.error); - } - } else if (typeof data == 'undefined' || typeof data.code == 'undefined') { - // The local_mobile returned something we didn't expect. Let's assume it's not installed. - return { code: 0, warning: 'core.login.localmobileunexpectedresponse' }; - } - - const code = parseInt(data.code, 10); - if (data.error) { - switch (code) { - case 1: - // Site in maintenance mode. - return Promise.reject(this.translate.instant('core.login.siteinmaintenance')); - case 2: - // Web services not enabled. - return Promise.reject(this.translate.instant('core.login.webservicesnotenabled')); - case 3: - // Extended service not enabled, but the official is enabled. - return { code: 0 }; - case 4: - // Neither extended or official services enabled. - return Promise.reject(this.translate.instant('core.login.mobileservicesnotenabled')); - default: - return Promise.reject(this.translate.instant('core.unexpectederror')); - } - } else { - return { code: code, service: service, coreSupported: !!data.coresupported }; - } - }, () => { + data = response.body; + } catch (ex) { return { code: 0 }; - }); + } + + if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') { + if (!retrying) { + this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl); + + return this.checkLocalMobilePlugin(true); + } else { + throw data.error; + } + } else if (typeof data == 'undefined' || typeof data.code == 'undefined') { + // The local_mobile returned something we didn't expect. Let's assume it's not installed. + return { code: 0, warning: 'core.login.localmobileunexpectedresponse' }; + } + + const code = parseInt(data.code, 10); + if (data.error) { + switch (code) { + case 1: + // Site in maintenance mode. + throw this.translate.instant('core.login.siteinmaintenance'); + case 2: + // Web services not enabled. + throw this.translate.instant('core.login.webservicesnotenabled'); + case 3: + // Extended service not enabled, but the official is enabled. + return { code: 0 }; + case 4: + // Neither extended or official services enabled. + throw this.translate.instant('core.login.mobileservicesnotenabled'); + default: + throw this.translate.instant('core.unexpectederror'); + } + } else { + return { code: code, service: service, coreSupported: !!data.coresupported }; + } } /** @@ -1970,7 +1974,7 @@ export class CoreSite { url = this.fixPluginfileURL(url); this.tokenPluginFileWorksPromise = this.wsProvider.performHead(url).then((result) => { - return result.ok; + return result.status >= 200 && result.status < 300; }).catch((error) => { // Error performing head request. return false; diff --git a/src/components/iframe/iframe.ts b/src/components/iframe/iframe.ts index 516012a8f..8f7ce3b49 100644 --- a/src/components/iframe/iframe.ts +++ b/src/components/iframe/iframe.ts @@ -16,13 +16,16 @@ import { Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; -import { NavController } from 'ionic-angular'; +import { NavController, Platform } from 'ionic-angular'; +import { CoreFile } from '@providers/file'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreIframeUtilsProvider } from '@providers/utils/iframe'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreUrl } from '@singletons/url'; +import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies'; @Component({ selector: 'core-iframe', @@ -49,7 +52,8 @@ export class CoreIframeComponent implements OnInit, OnChanges { protected navCtrl: NavController, protected urlUtils: CoreUrlUtilsProvider, protected utils: CoreUtilsProvider, - @Optional() protected svComponent: CoreSplitViewComponent) { + @Optional() protected svComponent: CoreSplitViewComponent, + protected platform: Platform) { this.logger = logger.getInstance('CoreIframe'); this.loaded = new EventEmitter(); @@ -91,10 +95,28 @@ export class CoreIframeComponent implements OnInit, OnChanges { /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange }): void { + async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise { if (changes.src) { - const youtubeUrl = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue); - this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(youtubeUrl || changes.src.currentValue); + const url = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue; + + if (this.platform.is('ios')) { + // Save a "fake" cookie for the iframe's domain to fix a bug in WKWebView. + try { + const win = window; + const urlParts = CoreUrl.parse(url); + + await win.WKWebViewCookies.setCookie({ + name: 'MoodleAppCookieForWKWebView', + value: '1', + domain: urlParts.domain, + }); + } catch (err) { + // Ignore errors. + this.logger.error('Error setting cookie', err); + } + } + + this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(CoreFile.instance.convertFileSrc(url)); } } } diff --git a/src/config.json b/src/config.json index 50897768a..a81136800 100644 --- a/src/config.json +++ b/src/config.json @@ -93,5 +93,6 @@ "statusbarbgremotetheme": "#000000", "statusbarlighttextremotetheme": true, "enableanalytics": false, - "forceColorScheme": "" + "forceColorScheme": "", + "webviewscheme": "moodleappfs" } \ No newline at end of file diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts index 51cbc8afd..fc8268f61 100644 --- a/src/core/compile/providers/compile.ts +++ b/src/core/compile/providers/compile.ts @@ -56,7 +56,7 @@ import { Md5 } from 'ts-md5/dist/md5'; // Import core classes that can be useful for site plugins. import { CoreSyncBaseProvider } from '@classes/base-sync'; -import { CoreUrl } from '@classes/utils/url'; +import { CoreUrl } from '@singletons/url'; import { CoreCache } from '@classes/cache'; import { CoreDelegate } from '@classes/delegate'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 75fe4b2b9..821509cda 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -27,6 +27,7 @@ import { CoreH5PUtilsProvider } from './utils'; import { CoreH5PContentValidator } from '../classes/content-validator'; import { TranslateService } from '@ngx-translate/core'; import { FileEntry } from '@ionic-native/file'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Service to provide H5P functionalities. @@ -410,7 +411,7 @@ export class CoreH5PProvider { * @return Promise resolved with all of the files content in one string. */ protected concatenateFiles(assets: CoreH5PDependencyAsset[], type: string): Promise { - const basePath = this.fileProvider.getBasePathInstant(); + const basePath = this.fileProvider.convertFileSrc(this.fileProvider.getBasePathInstant()); let content = '', promise = Promise.resolve(); // Use a chain of promises so the order is kept. @@ -495,7 +496,8 @@ export class CoreH5PProvider { const contentId = this.getContentId(id), basePath = this.fileProvider.getBasePathInstant(), - contentUrl = this.textUtils.concatenatePaths(basePath, this.getContentFolderPath(content.folderName, site.getId())); + contentUrl = this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths( + basePath, this.getContentFolderPath(content.folderName, site.getId()))); // Create the settings needed for the content. const contentSettings = { @@ -560,6 +562,40 @@ export class CoreH5PProvider { }); } + /** + * Delete all content indexes of all sites from filesystem. + * + * @return Promise resolved when done. + */ + async deleteAllContentIndexes(): Promise { + const siteIds = await this.sitesProvider.getSitesIds(); + + await Promise.all(siteIds.map((siteId) => this.deleteAllContentIndexesForSite(siteId))); + } + + /** + * Delete all content indexes for a certain site from filesystem. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + async deleteAllContentIndexesForSite(siteId?: string): Promise { + + const site = await this.sitesProvider.getSite(siteId); + + const records = await site.getDb().getAllRecords(this.CONTENT_TABLE); + + const promises = records.map(async (record) => { + try { + await this.fileProvider.removeFile(this.getContentIndexPath(record.foldername, site.getId())); + } catch (err) { + // Ignore errors, maybe the file doesn't exist. + } + }); + + await Promise.all(promises); + } + /** * Delete cached assets from DB and filesystem. * @@ -1033,12 +1069,11 @@ export class CoreH5PProvider { settings.loadedJs = []; settings.loadedCss = []; - const libUrl = this.getCoreH5PPath(), - relPath = this.urlUtils.removeProtocolAndWWW(libUrl); + const libUrl = this.getCoreH5PPath(); // Add core stylesheets. CoreH5PProvider.STYLES.forEach((style) => { - settings.core.styles.push(relPath + style); + settings.core.styles.push(libUrl + style); cssRequires.push(libUrl + style); }); @@ -1279,8 +1314,10 @@ export class CoreH5PProvider { return { baseUrl: this.fileProvider.getWWWPath(), - url: this.textUtils.concatenatePaths(basePath, this.getExternalH5PFolderPath(site.getId())), - urlLibraries: this.textUtils.concatenatePaths(basePath, this.getLibrariesFolderPath(site.getId())), + url: this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths( + basePath, this.getExternalH5PFolderPath(site.getId()))), + urlLibraries: this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths( + basePath, this.getLibrariesFolderPath(site.getId()))), postUserStatistics: false, ajax: ajaxPaths, saveFreq: false, @@ -2623,6 +2660,8 @@ export class CoreH5PProvider { } } +export class CoreH5P extends makeSingleton(CoreH5PProvider) {} + /** * Display options behaviour constants. */ diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index ebf6cfcaf..400491069 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -22,7 +22,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreConfigConstants } from '../../../../configconstants'; import { CoreLoginHelperProvider } from '../../providers/helper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { CoreUrl } from '@classes/utils/url'; +import { CoreUrl } from '@singletons/url'; import { TranslateService } from '@ngx-translate/core'; /** diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts index 1879772c7..47b4c776e 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -16,6 +16,7 @@ import { Directive, Input, AfterViewInit, ElementRef, OnChanges, SimpleChange, O import { Platform } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; +import { CoreFile } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; @@ -52,9 +53,15 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { invalid = false; - constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider, - private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, - private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) { + constructor(element: ElementRef, + logger: CoreLoggerProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected platform: Platform, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected appProvider: CoreAppProvider, + protected utils: CoreUtilsProvider) { // This directive can be added dynamically. In that case, the first param is the HTMLElement. this.element = element.nativeElement || element; this.logger = logger.getInstance('CoreExternalContentDirective'); @@ -179,7 +186,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { * @param siteId Site ID. * @return Promise resolved if the element is successfully treated. */ - protected handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise { + protected async handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise { const tagName = this.element.tagName; @@ -214,72 +221,70 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { this.addSource(url); } - return Promise.reject(null); + throw 'Non-downloadable URL'; } - // Get the webservice pluginfile URL, we ignore failures here. - return this.sitesProvider.getSite(siteId).then((site) => { - if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) { - this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken. + const site = await this.sitesProvider.getSite(siteId); - return Promise.reject(null); + if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) { + this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken. + + throw 'Site doesn\'t allow downloading files.'; + } + + // Download images, tracks and posters if size is unknown. + const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster'; + let finalUrl: string; + + if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' && tagName !== 'AUDIO') { + finalUrl = await this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown); + } else { + finalUrl = await this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown); + + finalUrl = CoreFile.instance.convertFileSrc(finalUrl); + } + + if (finalUrl.match(/^https?:\/\//i)) { + /* In iOS, if we use the same URL in embedded file and background download then the download only + downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */ + finalUrl = finalUrl + '#moodlemobile-embedded'; + } + + this.logger.debug('Using URL ' + finalUrl + ' for ' + url); + if (tagName === 'SOURCE') { + // The browser does not catch changes in SRC, we need to add a new source. + this.addSource(finalUrl); + } else { + if (tagName === 'IMG') { + this.loaded = false; + this.waitForLoad(); + } + this.element.setAttribute(targetAttr, finalUrl); + this.element.setAttribute('data-original-' + targetAttr, url); + } + + // Set events to download big files (not downloaded automatically). + if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' && + (tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) { + const eventName = tagName == 'A' ? 'click' : 'play'; + let clickableEl = this.element; + + if (tagName == 'SOURCE') { + clickableEl = this.domUtils.closest(this.element, 'video,audio'); + if (!clickableEl) { + return; + } } - // Download images, tracks and posters if size is unknown. - const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster'; - let promise; - - if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' && - tagName !== 'AUDIO') { - promise = this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown); - } else { - promise = this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown); - } - - return promise.then((finalUrl) => { - if (finalUrl.match(/^https?:\/\//i)) { - /* In iOS, if we use the same URL in embedded file and background download then the download only - downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */ - finalUrl = finalUrl + '#moodlemobile-embedded'; - } - - this.logger.debug('Using URL ' + finalUrl + ' for ' + url); - if (tagName === 'SOURCE') { - // The browser does not catch changes in SRC, we need to add a new source. - this.addSource(finalUrl); - } else { - if (tagName === 'IMG') { - this.loaded = false; - this.waitForLoad(); - } - this.element.setAttribute(targetAttr, finalUrl); - this.element.setAttribute('data-original-' + targetAttr, url); - } - - // Set events to download big files (not downloaded automatically). - if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' && - (tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) { - const eventName = tagName == 'A' ? 'click' : 'play'; - let clickableEl = this.element; - - if (tagName == 'SOURCE') { - clickableEl = this.domUtils.closest(this.element, 'video,audio'); - if (!clickableEl) { - return; - } - } - - clickableEl.addEventListener(eventName, () => { - // User played media or opened a downloadable link. - // Download the file if in wifi and it hasn't been downloaded already (for big files). - if (this.appProvider.isWifi()) { - // We aren't using the result, so it doesn't matter which of the 2 functions we call. - this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false); - } - }); + clickableEl.addEventListener(eventName, () => { + // User played media or opened a downloadable link. + // Download the file if in wifi and it hasn't been downloaded already (for big files). + if (this.appProvider.isWifi()) { + // We aren't using the result, so it doesn't matter which of the 2 functions we call. + this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false); } }); - }); + } } /** diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 7057b269f..9dfe8a746 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -74,26 +74,27 @@ export class CoreFormatTextDirective implements OnChanges { protected loadingChangedListener; constructor(element: ElementRef, - private sitesProvider: CoreSitesProvider, - private domUtils: CoreDomUtilsProvider, - private textUtils: CoreTextUtilsProvider, - private translate: TranslateService, - private platform: Platform, - private utils: CoreUtilsProvider, - private urlUtils: CoreUrlUtilsProvider, - private loggerProvider: CoreLoggerProvider, - private filepoolProvider: CoreFilepoolProvider, - private appProvider: CoreAppProvider, - private contentLinksHelper: CoreContentLinksHelperProvider, - @Optional() private navCtrl: NavController, - @Optional() private content: Content, @Optional() - private svComponent: CoreSplitViewComponent, - private iframeUtils: CoreIframeUtilsProvider, - private eventsProvider: CoreEventsProvider, - private filterProvider: CoreFilterProvider, - private filterHelper: CoreFilterHelperProvider, - private filterDelegate: CoreFilterDelegate, - private viewContainerRef: ViewContainerRef) { + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected translate: TranslateService, + protected platform: Platform, + protected utils: CoreUtilsProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected loggerProvider: CoreLoggerProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected appProvider: CoreAppProvider, + protected contentLinksHelper: CoreContentLinksHelperProvider, + @Optional() protected navCtrl: NavController, + @Optional() protected content: Content, @Optional() + protected svComponent: CoreSplitViewComponent, + protected iframeUtils: CoreIframeUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected filterProvider: CoreFilterProvider, + protected filterHelper: CoreFilterHelperProvider, + protected filterDelegate: CoreFilterDelegate, + protected viewContainerRef: ViewContainerRef, + ) { this.element = element.nativeElement; this.element.classList.add('opacity-hide'); // Hide contents until they're treated. diff --git a/src/directives/link.ts b/src/directives/link.ts index 5ccfe3c64..5d85e2199 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -92,7 +92,7 @@ export class CoreLinkDirective implements OnInit { protected navigate(href: string): void { const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; - if (href.indexOf('cdvfile://') === 0 || href.indexOf('file://') === 0 || href.indexOf('filesystem:') === 0) { + if (this.urlUtils.isLocalFileUrl(href)) { // We have a local file. this.utils.openFile(href).catch((error) => { this.domUtils.showErrorModal(error); diff --git a/src/index.html b/src/index.html index fa07d2622..78f94ce36 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,7 @@ Moodle Desktop - + diff --git a/src/providers/app.ts b/src/providers/app.ts index 7a50ff553..4d29185e5 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -23,6 +23,7 @@ import { CoreLoggerProvider } from './logger'; import { CoreEventsProvider } from './events'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreConfigConstants } from '../configconstants'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Data stored for a redirect to another page/site. @@ -703,3 +704,5 @@ export class CoreAppProvider { this.forceOffline = !!value; } } + +export class CoreApp extends makeSingleton(CoreAppProvider) {} diff --git a/src/providers/config.ts b/src/providers/config.ts index 709b5ce8e..cf62a9012 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreAppProvider, CoreAppSchema } from './app'; import { SQLiteDB } from '@classes/sqlitedb'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Factory to provide access to dynamic and permanent config and settings. @@ -102,3 +103,5 @@ export class CoreConfigProvider { return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value }); } } + +export class CoreConfig extends makeSingleton(CoreConfigProvider) {} diff --git a/src/providers/cron.ts b/src/providers/cron.ts index f0b130e9f..0cdf5e7e2 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -20,6 +20,7 @@ import { CoreLoggerProvider } from './logger'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; import { SQLiteDB } from '@classes/sqlitedb'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Interface that all cron handlers must implement. @@ -554,3 +555,5 @@ export class CoreCronDelegate { delete this.handlers[name].timeout; } } + +export class CoreCron extends makeSingleton(CoreCronDelegate) {} diff --git a/src/providers/db.ts b/src/providers/db.ts index 6805c82e1..8e6c03299 100644 --- a/src/providers/db.ts +++ b/src/providers/db.ts @@ -17,6 +17,7 @@ import { SQLite } from '@ionic-native/sqlite'; import { Platform } from 'ionic-angular'; import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDBMock } from '@core/emulator/classes/sqlitedb'; +import { makeSingleton } from '@singletons/core.singletons'; /** * This service allows interacting with the local database to store and retrieve data. @@ -81,3 +82,5 @@ export class CoreDbProvider { }); } } + +export class CoreDB extends makeSingleton(CoreDbProvider) {} diff --git a/src/providers/events.ts b/src/providers/events.ts index 4da2900c0..a5e6e3bef 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { CoreLoggerProvider } from '@providers/logger'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Observer instance to stop listening to an event. @@ -174,3 +175,5 @@ export class CoreEventsProvider { } } } + +export class CoreEvents extends makeSingleton(CoreEventsProvider) {} diff --git a/src/providers/file-helper.ts b/src/providers/file-helper.ts index 73daa59b3..00b4b4150 100644 --- a/src/providers/file-helper.ts +++ b/src/providers/file-helper.ts @@ -21,6 +21,7 @@ import { CoreSitesProvider } from './sites'; import { CoreWSProvider } from './ws'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Provider to provide some helper functions regarding files and packages. @@ -333,5 +334,6 @@ export class CoreFileHelperProvider { throw new Error('Couldn\'t determine file size: ' + file.fileurl); } - } + +export class CoreFileHelper extends makeSingleton(CoreFileHelperProvider) {} diff --git a/src/providers/file-session.ts b/src/providers/file-session.ts index 79798ae0f..552284a86 100644 --- a/src/providers/file-session.ts +++ b/src/providers/file-session.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from './sites'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Helper to store some temporary data for file submission. @@ -146,3 +147,5 @@ export class CoreFileSessionProvider { this.files[siteId][component][id] = newFiles; } } + +export class CoreFileSession extends makeSingleton(CoreFileSessionProvider) {} diff --git a/src/providers/file.ts b/src/providers/file.ts index 07d1628f5..1bee27880 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -20,7 +20,9 @@ import { CoreAppProvider } from './app'; import { CoreLoggerProvider } from './logger'; import { CoreMimetypeUtilsProvider } from './utils/mimetype'; import { CoreTextUtilsProvider } from './utils/text'; +import { CoreConfigConstants } from '../configconstants'; import { Zip } from '@ionic-native/zip'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Progress event used when writing a file data into a file. @@ -945,6 +947,7 @@ export class CoreFileProvider { /** * Get the internal URL of a file. + * Please notice that with WKWebView these URLs no longer work in mobile. Use fileEntry.toURL() along with convertFileSrc. * * @param fileEntry File Entry. * @return Internal URL. @@ -1269,4 +1272,31 @@ export class CoreFileProvider { return window.location.href; } + + /** + * Helper function to call Ionic WebView convertFileSrc only in the needed platforms. + * This is needed to make files work with the Ionic WebView plugin. + * + * @param src Source to convert. + * @return Converted src. + */ + convertFileSrc(src: string): string { + return this.appProvider.isMobile() ? ( window).Ionic.WebView.convertFileSrc(src) : src; + } + + /** + * Undo the conversion of convertFileSrc. + * + * @param src Source to unconvert. + * @return Unconverted src. + */ + unconvertFileSrc(src: string): string { + if (!this.appProvider.isMobile()) { + return src; + } + + return src.replace(CoreConfigConstants.webviewscheme + '://localhost/_app_file_', 'file://'); + } } + +export class CoreFile extends makeSingleton(CoreFileProvider) {} diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index a3889773d..0f939b65d 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -31,6 +31,7 @@ import { CoreUtilsProvider } from './utils/utils'; import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { Md5 } from 'ts-md5/dist/md5'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Entry from filepool. @@ -471,11 +472,21 @@ export class CoreFilepoolProvider { protected packagesPromises = {}; protected filePromises: { [s: string]: { [s: string]: Promise } } = {}; - constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, - private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, - private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider, - private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate, - network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider, + constructor(logger: CoreLoggerProvider, + protected appProvider: CoreAppProvider, + protected fileProvider: CoreFileProvider, + protected sitesProvider: CoreSitesProvider, + protected wsProvider: CoreWSProvider, + protected textUtils: CoreTextUtilsProvider, + protected utils: CoreUtilsProvider, + protected mimeUtils: CoreMimetypeUtilsProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected timeUtils: CoreTimeUtilsProvider, + protected eventsProvider: CoreEventsProvider, + initDelegate: CoreInitDelegate, + network: Network, + protected pluginFileDelegate: CorePluginFileDelegate, + protected domUtils: CoreDomUtilsProvider, zone: NgZone) { this.logger = logger.getInstance('CoreFilepoolProvider'); @@ -1901,8 +1912,7 @@ export class CoreFilepoolProvider { if (this.fileProvider.isAvailable()) { return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => { return this.fileProvider.getFile(path).then((fileEntry) => { - // We use toInternalURL so images are loaded in iOS8 using img HTML tags. - return this.fileProvider.getInternalURL(fileEntry); + return this.fileProvider.convertFileSrc(fileEntry.toURL()); }); }); } @@ -3250,3 +3260,5 @@ export class CoreFilepoolProvider { }); } } + +export class CoreFilepool extends makeSingleton(CoreFilepoolProvider) {} diff --git a/src/providers/groups.ts b/src/providers/groups.ts index d0c96e8d2..e05335501 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from './sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Group info for an activity. @@ -449,3 +450,5 @@ export class CoreGroupsProvider { return groupInfo.defaultGroupId; } } + +export class CoreGroups extends makeSingleton(CoreGroupsProvider) {} diff --git a/src/providers/init.ts b/src/providers/init.ts index f44e6f79e..3ac620352 100644 --- a/src/providers/init.ts +++ b/src/providers/init.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { Platform } from 'ionic-angular'; import { CoreLoggerProvider } from './logger'; import { CoreUtilsProvider } from './utils/utils'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Interface that all init handlers must implement. @@ -175,3 +176,5 @@ export class CoreInitDelegate { this.initProcesses[handler.name] = handler; } } + +export class CoreInit extends makeSingleton(CoreInitDelegate) {} diff --git a/src/providers/lang.ts b/src/providers/lang.ts index df16004e3..b598be1b7 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -20,6 +20,7 @@ import { Platform, Config } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreConfigProvider } from './config'; import { CoreConfigConstants } from '../configconstants'; +import { makeSingleton } from '@singletons/core.singletons'; /* * Service to handle language features, like changing the current language. @@ -453,3 +454,5 @@ export class CoreLangProvider { } } } + +export class CoreLang extends makeSingleton(CoreLangProvider) {} diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 78f2ccb08..2ac4dbdfb 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -27,6 +27,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../configconstants'; import { Subject, Subscription } from 'rxjs'; +import { makeSingleton } from '@singletons/core.singletons'; /* * Generated class for the LocalNotificationsProvider provider. @@ -754,3 +755,5 @@ export class CoreLocalNotificationsProvider { return this.appDB.updateRecords(this.COMPONENTS_TABLE, {id: newId}, {id: oldId}); } } + +export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {} diff --git a/src/providers/logger.ts b/src/providers/logger.ts index 1453f8cbf..12ad8a79f 100644 --- a/src/providers/logger.ts +++ b/src/providers/logger.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import * as moment from 'moment'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Helper service to display messages in the console. @@ -72,3 +73,5 @@ export class CoreLoggerProvider { }; } } + +export class CoreLogger extends makeSingleton(CoreLoggerProvider) {} diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 42ee781a7..b08989dc2 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -19,6 +19,7 @@ import { CoreSitesProvider } from './sites'; import { CoreWSExternalFile } from '@providers/ws'; import { FileEntry } from '@ionic-native/file'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Interface that all plugin file handlers must implement. @@ -371,3 +372,5 @@ export class CorePluginFileDelegate extends CoreDelegate { return Promise.resolve(); } } + +export class CorePluginFile extends makeSingleton(CorePluginFileDelegate) {} diff --git a/src/providers/sites-factory.ts b/src/providers/sites-factory.ts index f97d334cb..2ce61382b 100644 --- a/src/providers/sites-factory.ts +++ b/src/providers/sites-factory.ts @@ -14,6 +14,7 @@ import { Injectable, Injector } from '@angular/core'; import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons/core.singletons'; /* * Provider to create sites instances. @@ -56,3 +57,5 @@ export class CoreSitesFactoryProvider { return methods; } } + +export class CoreSitesFactory extends makeSingleton(CoreSitesFactoryProvider) {} diff --git a/src/providers/sites.ts b/src/providers/sites.ts index dcf33d0c4..f0f85df99 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -30,6 +30,7 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { Md5 } from 'ts-md5/dist/md5'; import { WP_PROVIDER } from '@app/app.module'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Response of checking if a site exists and its configuration. @@ -1925,5 +1926,6 @@ export class CoreSitesProvider { return {}; } } - } + +export class CoreSites extends makeSingleton(CoreSitesProvider) {} diff --git a/src/providers/sync.ts b/src/providers/sync.ts index 739d1b59f..c68514165 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from './events'; import { CoreSitesProvider, CoreSiteSchema } from './sites'; +import { makeSingleton } from '@singletons/core.singletons'; /* * Service that provides some features regarding synchronization. @@ -206,3 +207,5 @@ export class CoreSyncProvider { } } } + +export class CoreSync extends makeSingleton(CoreSyncProvider) {} diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index b32ef3499..bf76dec22 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -17,6 +17,8 @@ import { CoreConfigProvider } from './config'; import { CoreInitHandler, CoreInitDelegate } from './init'; import { CoreLoggerProvider } from './logger'; import { CoreConfigConstants } from '../configconstants'; +import { CoreH5P } from '@core/h5p/providers/h5p'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Factory to handle app updates. This factory shouldn't be used outside of core. @@ -33,7 +35,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { protected VERSION_APPLIED = 'version_applied'; protected logger; - constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider) { + constructor(logger: CoreLoggerProvider, + protected configProvider: CoreConfigProvider) { this.logger = logger.getInstance('CoreUpdateManagerProvider'); } @@ -43,19 +46,24 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return Promise resolved when the update process finishes. */ - load(): Promise { - const promises = [], - versionCode = CoreConfigConstants.versioncode; + async load(): Promise { + const promises = []; + const versionCode = CoreConfigConstants.versioncode; - return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => { + const versionApplied: number = await this.configProvider.get(this.VERSION_APPLIED, 0); - // Put here the code to treat app updates. + if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) { + promises.push(CoreH5P.instance.deleteAllContentIndexes()); + } - return Promise.all(promises).then(() => { - return this.configProvider.set(this.VERSION_APPLIED, versionCode); - }).catch((error) => { - this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error); - }); - }); + try { + await Promise.all(promises); + + await this.configProvider.set(this.VERSION_APPLIED, versionCode); + } catch (error) { + this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error); + } } } + +export class CoreUpdateManager extends makeSingleton(CoreUpdateManagerProvider) {} diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts index b7ed851c3..5c22508d2 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -28,6 +28,7 @@ import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate' import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; import { CoreConfigConstants } from '../configconstants'; import { CoreConstants } from '@core/constants'; +import { makeSingleton } from '@singletons/core.singletons'; /** * All params that can be in a custom URL scheme. @@ -488,3 +489,5 @@ export class CoreCustomURLSchemesProvider { return url.indexOf(CoreConfigConstants.customurlscheme + '://token=') != -1; } } + +export class CoreCustomURLSchemes extends makeSingleton(CoreCustomURLSchemesProvider) {} diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index c692faf65..743a333ec 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -30,6 +30,7 @@ import { CoreConstants } from '@core/constants'; import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip'; import { Md5 } from 'ts-md5/dist/md5'; import { Subject } from 'rxjs'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Interface that defines an extension of the Ionic Alert class, to support multiple listeners. @@ -1666,3 +1667,5 @@ export class CoreDomUtilsProvider { }, siteId); } } + +export class CoreDomUtils extends makeSingleton(CoreDomUtilsProvider) {} diff --git a/src/providers/utils/iframe.ts b/src/providers/utils/iframe.ts index e60c19b58..69c4251f8 100644 --- a/src/providers/utils/iframe.ts +++ b/src/providers/utils/iframe.ts @@ -25,6 +25,7 @@ import { CoreTextUtilsProvider } from './text'; import { CoreUrlUtilsProvider } from './url'; import { CoreUtilsProvider } from './utils'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { makeSingleton } from '@singletons/core.singletons'; /* * "Utils" service with helper functions for iframes, embed and similar. @@ -224,7 +225,7 @@ export class CoreIframeUtilsProvider { } else { element.setAttribute('src', url); } - } else if (url.indexOf('cdvfile://') === 0 || url.indexOf('file://') === 0) { + } else if (this.urlUtils.isLocalFileUrl(url)) { // It's a local file. this.utils.openFile(url).catch((error) => { this.domUtils.showErrorModal(error); @@ -352,16 +353,14 @@ export class CoreIframeUtilsProvider { return; } - if (scheme && scheme != 'file' && scheme != 'filesystem') { + if (!this.urlUtils.isLocalFileUrlScheme(scheme)) { // Scheme suggests it's an external resource. event.preventDefault(); - const frameSrc = ( element).src || ( element).data, - frameScheme = this.urlUtils.getUrlScheme(frameSrc); + const frameSrc = ( element).src || ( element).data; // If the frame is not local, check the target to identify how to treat the link. - if (frameScheme && frameScheme != 'file' && frameScheme != 'filesystem' && - (!link.target || link.target == '_self')) { + if (!this.urlUtils.isLocalFileUrl(frameSrc) && (!link.target || link.target == '_self')) { // Load the link inside the frame itself. if (element.tagName.toLowerCase() == 'object') { element.setAttribute('data', link.href); @@ -396,6 +395,8 @@ export class CoreIframeUtilsProvider { } } +export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {} + /** * Subtype of HTMLAnchorElement, with some calculated data. */ diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index c162f3dc7..eae580a46 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -14,9 +14,11 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; +import { CoreFile } from '../file'; import { CoreLoggerProvider } from '../logger'; import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; +import { makeSingleton } from '@singletons/core.singletons'; /* * "Utils" service with helper functions for mimetypes and extensions. @@ -164,7 +166,7 @@ export class CoreMimetypeUtilsProvider { if (this.canBeEmbedded(ext)) { file.embedType = this.getExtensionType(ext); - path = path || file.fileurl || (file.toURL && file.toURL()); + path = CoreFile.instance.convertFileSrc(path || file.fileurl || (file.toURL && file.toURL())); if (file.embedType == 'image') { return ''; @@ -549,3 +551,5 @@ export class CoreMimetypeUtilsProvider { return path; } } + +export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {} diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 8565b6dfc..66e617091 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -17,6 +17,7 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { ModalController, Platform } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreLangProvider } from '../lang'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Different type of errors the app can treat. @@ -1106,3 +1107,5 @@ export class CoreTextUtilsProvider { return _unserialize((data + ''), 0)[2]; } } + +export class CoreTextUtils extends makeSingleton(CoreTextUtilsProvider) {} diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index ca0d464fe..470c30f44 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; import { CoreConstants } from '@core/constants'; +import { makeSingleton } from '@singletons/core.singletons'; /* * "Utils" service with helper functions for date and time. @@ -353,3 +354,5 @@ export class CoreTimeUtilsProvider { } } } + +export class CoreTimeUtils extends makeSingleton(CoreTimeUtilsProvider) {} diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index 5c8c0e35a..f9b07929b 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreLangProvider } from '../lang'; import { CoreTextUtilsProvider } from './text'; +import { makeSingleton } from '@singletons/core.singletons'; +import { CoreConfigConstants } from '../../configconstants'; /* * "Utils" service with helper functions for URLs. @@ -423,6 +425,26 @@ export class CoreUrlUtilsProvider { return /^https?\:\/\/.+/i.test(url); } + /** + * Check whether an URL belongs to a local file. + * + * @param url URL to check. + * @return Whether the URL belongs to a local file. + */ + isLocalFileUrl(url: string): boolean { + return this.isLocalFileUrlScheme(this.getUrlScheme(url)); + } + + /** + * Check whether a URL scheme belongs to a local file. + * + * @param scheme Scheme to check. + * @return Whether the scheme belongs to a local file. + */ + isLocalFileUrlScheme(scheme: string): boolean { + return scheme == 'cdvfile' || scheme == 'file' || scheme == 'filesystem' || scheme == CoreConfigConstants.webviewscheme; + } + /** * Returns if a URL is a pluginfile URL. * @@ -498,3 +520,5 @@ export class CoreUrlUtilsProvider { return url; } } + +export class CoreUrlUtils extends makeSingleton(CoreUrlUtilsProvider) {} diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index bebc3ce1c..b3781a706 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -27,6 +27,8 @@ import { CoreLoggerProvider } from '../logger'; import { TranslateService } from '@ngx-translate/core'; import { CoreLangProvider } from '../lang'; import { CoreWSProvider, CoreWSError } from '../ws'; +import { CoreFile } from '../file'; +import { makeSingleton } from '@singletons/core.singletons'; /** * Deferred promise. It's similar to the result of $q.defer() in AngularJS. @@ -862,8 +864,11 @@ export class CoreUtilsProvider { * @return Promise resolved when done. */ openFile(path: string): Promise { - const extension = this.mimetypeUtils.getFileExtension(path), - mimetype = this.mimetypeUtils.getMimeType(extension); + // Convert the path to a native path if needed. + path = CoreFile.instance.unconvertFileSrc(path); + + const extension = this.mimetypeUtils.getFileExtension(path); + const mimetype = this.mimetypeUtils.getMimeType(extension); // Path needs to be decoded, the file won't be opened if the path has %20 instead of spaces and so. try { @@ -1416,3 +1421,5 @@ export class CoreUtilsProvider { return debounced; } } + +export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 8440316ae..57c91985a 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -24,6 +24,9 @@ import { CoreTextUtilsProvider } from './utils/text'; import { CoreConstants } from '@core/constants'; import { Md5 } from 'ts-md5/dist/md5'; import { CoreInterceptor } from '@classes/interceptor'; +import { makeSingleton } from '@singletons/core.singletons'; +import { Observable } from 'rxjs/Observable'; +import { CoreNativeToAngularHttpResponse } from '@classes/native-to-angular-http'; /** * PreSets accepted by the WS call. @@ -80,6 +83,61 @@ export interface CoreWSAjaxPreSets { useGet?: boolean; } +/** + * Options for HTTP requests. + */ +export type HttpRequestOptions = { + /** + * The HTTP method. + */ + method: string; + + /** + * Payload to send to the server. Only applicable on post, put or patch methods. + */ + data?: any; + + /** + * Query params to be appended to the URL (only applicable on get, head, delete, upload or download methods). + */ + params?: any; + + /** + * Response type. Defaults to json. + */ + responseType?: 'json' | 'text' | 'arraybuffer' | 'blob'; + + /** + * Timeout for the request in seconds. If undefined, the default value will be used. If null, no timeout. + */ + timeout?: number | null; + + /** + * Serializer to use. Defaults to 'urlencoded'. Only for mobile environments. + */ + serializer?: string; + + /** + * Whether to follow redirects. Defaults to true. Only for mobile environments. + */ + followRedirect?: boolean; + + /** + * Headers. Only for mobile environments. + */ + headers?: {[name: string]: string}; + + /** + * File paths to use for upload or download. Only for mobile environments. + */ + filePath?: string; + + /** + * Name to use during upload. Only for mobile environments. + */ + name?: string; +}; + /** * This service allows performing WS calls and download/upload files. */ @@ -376,8 +434,8 @@ export class CoreWSProvider { return Promise.resolve(this.mimeTypeCache[url]); } - return this.performHead(url).then((data) => { - let mimeType = data.headers.get('Content-Type'); + return this.performHead(url).then((response) => { + let mimeType = response.headers.get('Content-Type'); if (mimeType) { // Remove "parameters" like charset. mimeType = mimeType.split(';')[0]; @@ -398,8 +456,8 @@ export class CoreWSProvider { * @return Promise resolved with the size or -1 if failure. */ getRemoteFileSize(url: string): Promise { - return this.performHead(url).then((data) => { - const size = parseInt(data.headers.get('Content-Length'), 10); + return this.performHead(url).then((response) => { + const size = parseInt(response.headers.get('Content-Length'), 10); if (size) { return size; @@ -462,12 +520,12 @@ export class CoreWSProvider { preSets.responseExpected = true; } - const script = preSets.noLogin ? 'service-nologin.php' : 'service.php', - ajaxData = JSON.stringify([{ - index: 0, - methodname: method, - args: this.convertValuesToString(data) - }]); + const script = preSets.noLogin ? 'service-nologin.php' : 'service.php'; + const ajaxData = [{ + index: 0, + methodname: method, + args: this.convertValuesToString(data) + }]; // The info= parameter has no function. It is just to help with debugging. // We call it info to match the parameter name use by Moodle's AMD ajax module. @@ -475,13 +533,22 @@ export class CoreWSProvider { if (preSets.noLogin && preSets.useGet) { // Send params using GET. - siteUrl += '&args=' + encodeURIComponent(ajaxData); - promise = this.http.get(siteUrl).timeout(this.getRequestTimeout()).toPromise(); + siteUrl += '&args=' + encodeURIComponent(JSON.stringify(ajaxData)); + + promise = this.sendHTTPRequest(siteUrl, { + method: 'get', + }); } else { - promise = this.http.post(siteUrl, ajaxData).timeout(this.getRequestTimeout()).toPromise(); + promise = this.sendHTTPRequest(siteUrl, { + method: 'post', + data: ajaxData, + serializer: 'json', + }); } - return promise.then((data: any) => { + return promise.then((response: HttpResponse) => { + let data = response.body; + // Some moodle web services return null. // If the responseExpected value is set then so long as no data is returned, we create a blank object. if (!data && !preSets.responseExpected) { @@ -535,8 +602,11 @@ export class CoreWSProvider { let promise = this.getPromiseHttp('head', url); if (!promise) { - promise = this.http.head(url, {observe: 'response', responseType: 'blob'}).timeout(this.getRequestTimeout()) - .toPromise(); + promise = this.sendHTTPRequest(url, { + method: 'head', + responseType: 'text', + }); + promise = this.setPromiseHttp(promise, 'head', url); } @@ -570,6 +640,7 @@ export class CoreWSProvider { const promise = this.http.post(requestUrl, ajaxData, options).timeout(this.getRequestTimeout()).toPromise(); return promise.then((data: any) => { + // Some moodle web services return null. // If the responseExpected value is set to false, we create a blank object if the response is null. if (!data && !preSets.responseExpected) { @@ -870,15 +941,107 @@ export class CoreWSProvider { */ async getText(url: string): Promise { // Fetch the URL content. - const content = await this.http.get(url, { responseType: 'text' }).toPromise(); + const options: HttpRequestOptions = { + method: 'get', + responseType: 'text', + }; + + const response = await this.sendHTTPRequest(url, options); + + const content = response.body; + if (typeof content !== 'string') { - return Promise.reject(null); + throw 'Error reading content'; } return content; } + + /** + * Send an HTTP request. In mobile devices it will use the cordova plugin. + * + * @param url URL of the request. + * @param options Options for the request. + * @return Promise resolved with the response. + */ + async sendHTTPRequest(url: string, options: HttpRequestOptions): Promise> { + + // Set default values. + options.responseType = options.responseType || 'json'; + options.timeout = typeof options.timeout == 'undefined' ? this.getRequestTimeout() : options.timeout; + + if (this.appProvider.isMobile()) { + // Use the cordova plugin. + if (url.indexOf('file://') === 0) { + // We cannot load local files using the http native plugin. Use file provider instead. + const format = options.responseType == 'json' ? CoreFileProvider.FORMATJSON : CoreFileProvider.FORMATTEXT; + + const content = await this.fileProvider.readFile(url, format); + + return new HttpResponse({ + body: content, + headers: null, + status: 200, + statusText: 'OK', + url: url + }); + } + + return new Promise>((resolve, reject): void => { + // We cannot use Ionic Native plugin because it doesn't have the sendRequest method. + ( cordova).plugin.http.sendRequest(url, options, (response) => { + resolve(new CoreNativeToAngularHttpResponse(response)); + }, reject); + }); + } else { + let observable: Observable; + + // Use Angular's library. + switch (options.method) { + case 'get': + observable = this.http.get(url, { + headers: options.headers, + params: options.params, + observe: 'response', + responseType: options.responseType, + }); + break; + + case 'post': + if (options.serializer == 'json') { + options.data = JSON.stringify(options.data); + } + + observable = this.http.post(url, options.data, { + headers: options.headers, + observe: 'response', + responseType: options.responseType, + }); + break; + + case 'head': + observable = this.http.head(url, { + headers: options.headers, + observe: 'response', + responseType: options.responseType + }); + break; + + default: + return Promise.reject('Method not implemented yet.'); + } + + if (options.timeout) { + observable = observable.timeout(options.timeout); + } + + return observable.toPromise(); + } + } } +export class CoreWS extends makeSingleton(CoreWSProvider) {} + /** * Error returned by a WS call. */ diff --git a/src/singletons/core.singletons.ts b/src/singletons/core.singletons.ts new file mode 100644 index 000000000..7eacfe6bc --- /dev/null +++ b/src/singletons/core.singletons.ts @@ -0,0 +1,49 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { AlertController, App } from 'ionic-angular'; +import { Injector } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; + +import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory'; + +const factory = new CoreSingletonsFactory(); + +/** + * Set the injector that will be used to resolve instances in the singletons of this module. + * + * @param injector Module injector. + */ +export function setSingletonsInjector(injector: Injector): void { + factory.setInjector(injector); +} + +/** + * Make a singleton for this module. + * + * @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the + * provider was defined using a class or the string used in the `provide` key if it was defined using an object. + */ +export function makeSingleton(injectionToken: CoreInjectionToken): CoreSingletonClass { + return factory.makeSingleton(injectionToken); +} + +export class Translate extends makeSingleton(TranslateService) {} + +export class Alerts extends makeSingleton(AlertController) {} + +export class Ionic extends makeSingleton(App) {} + +export class Http extends makeSingleton(HttpClient) {} diff --git a/src/classes/utils/url.ts b/src/singletons/url.ts similarity index 100% rename from src/classes/utils/url.ts rename to src/singletons/url.ts diff --git a/tsconfig.json b/tsconfig.json index 84a65ecd5..22454c549 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,8 @@ "@providers/*": ["providers/*"], "@components/*": ["components/*"], "@directives/*": ["directives/*"], - "@pipes/*": ["pipes/*"] + "@pipes/*": ["pipes/*"], + "@singletons/*": ["singletons/*"] }, "typeRoots": [ "node_modules/@types"