MOBILE-3689 init: Replace /login/init with guards
parent
033860d18b
commit
2a5e29b1c3
|
@ -26,6 +26,7 @@ import {
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
|
import { CoreRedirectGuard } from '@guards/redirect';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build app routes.
|
* Build app routes.
|
||||||
|
@ -34,7 +35,16 @@ import { CoreArray } from '@singletons/array';
|
||||||
* @return App routes.
|
* @return App routes.
|
||||||
*/
|
*/
|
||||||
function buildAppRoutes(injector: Injector): Routes {
|
function buildAppRoutes(injector: Injector): Routes {
|
||||||
return CoreArray.flatten(injector.get<Routes[]>(APP_ROUTES, []));
|
const appRoutes = CoreArray.flatten(injector.get<Routes[]>(APP_ROUTES, []));
|
||||||
|
|
||||||
|
return appRoutes.map(route => {
|
||||||
|
route.canLoad = route.canLoad ?? [];
|
||||||
|
route.canActivate = route.canActivate ?? [];
|
||||||
|
route.canLoad.push(CoreRedirectGuard);
|
||||||
|
route.canActivate.push(CoreRedirectGuard);
|
||||||
|
|
||||||
|
return route;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,17 +17,16 @@ import { Observable } from 'rxjs';
|
||||||
import { AppComponent } from '@/app/app.component';
|
import { AppComponent } from '@/app/app.component';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreLangProvider } from '@services/lang';
|
import { CoreLang, CoreLangProvider } from '@services/lang';
|
||||||
import { Network, Platform, NgZone } from '@singletons';
|
import { Network, Platform, NgZone } from '@singletons';
|
||||||
|
|
||||||
import { mock, mockSingleton, renderComponent, RenderConfig } from '@/testing/utils';
|
import { mockSingleton, renderComponent } from '@/testing/utils';
|
||||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
|
|
||||||
let langProvider: CoreLangProvider;
|
let langProvider: CoreLangProvider;
|
||||||
let navigator: CoreNavigatorService;
|
let navigator: CoreNavigatorService;
|
||||||
let config: Partial<RenderConfig>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockSingleton(CoreApp, { setStatusBarColor: jest.fn() });
|
mockSingleton(CoreApp, { setStatusBarColor: jest.fn() });
|
||||||
|
@ -36,23 +35,18 @@ describe('AppComponent', () => {
|
||||||
mockSingleton(NgZone, { run: jest.fn() });
|
mockSingleton(NgZone, { run: jest.fn() });
|
||||||
|
|
||||||
navigator = mockSingleton(CoreNavigator, ['navigate']);
|
navigator = mockSingleton(CoreNavigator, ['navigate']);
|
||||||
langProvider = mock<CoreLangProvider>(['clearCustomStrings']);
|
langProvider = mockSingleton(CoreLang, ['clearCustomStrings']);
|
||||||
config = {
|
|
||||||
providers: [
|
|
||||||
{ provide: CoreLangProvider, useValue: langProvider },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render', async () => {
|
it('should render', async () => {
|
||||||
const fixture = await renderComponent(AppComponent, config);
|
const fixture = await renderComponent(AppComponent);
|
||||||
|
|
||||||
expect(fixture.debugElement.componentInstance).toBeTruthy();
|
expect(fixture.debugElement.componentInstance).toBeTruthy();
|
||||||
expect(fixture.nativeElement.querySelector('ion-router-outlet')).toBeTruthy();
|
expect(fixture.nativeElement.querySelector('ion-router-outlet')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cleans up on logout', async () => {
|
it('cleans up on logout', async () => {
|
||||||
const fixture = await renderComponent(AppComponent, config);
|
const fixture = await renderComponent(AppComponent);
|
||||||
|
|
||||||
fixture.componentInstance.ngOnInit();
|
fixture.componentInstance.ngOnInit();
|
||||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||||
|
@ -61,6 +55,4 @@ describe('AppComponent', () => {
|
||||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it.todo('shows loading while app isn\'t ready');
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { IonRouterOutlet } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreLangProvider } from '@services/lang';
|
import { CoreLang } from '@services/lang';
|
||||||
import { CoreLoginHelperProvider } from '@features/login/services/login-helper';
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
import {
|
import {
|
||||||
CoreEvents,
|
CoreEvents,
|
||||||
CoreEventSessionExpiredData,
|
CoreEventSessionExpiredData,
|
||||||
|
@ -23,23 +24,20 @@ import {
|
||||||
CoreEventSiteData,
|
CoreEventSiteData,
|
||||||
CoreEventSiteUpdatedData,
|
CoreEventSiteUpdatedData,
|
||||||
} from '@singletons/events';
|
} from '@singletons/events';
|
||||||
import { Network, NgZone, Platform } from '@singletons';
|
import { Network, NgZone, Platform, SplashScreen } from '@singletons';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: 'app.component.html',
|
templateUrl: 'app.component.html',
|
||||||
styleUrls: ['app.component.scss'],
|
styleUrls: ['app.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
constructor(
|
@ViewChild(IonRouterOutlet) outlet?: IonRouterOutlet;
|
||||||
protected langProvider: CoreLangProvider,
|
|
||||||
protected loginHelper: CoreLoginHelperProvider,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
|
@ -58,7 +56,7 @@ export class AppComponent implements OnInit {
|
||||||
CoreNavigator.instance.navigate('/login/sites', { reset: true });
|
CoreNavigator.instance.navigate('/login/sites', { reset: true });
|
||||||
|
|
||||||
// Unload lang custom strings.
|
// Unload lang custom strings.
|
||||||
this.langProvider.clearCustomStrings();
|
CoreLang.instance.clearCustomStrings();
|
||||||
|
|
||||||
// Remove version classes from body.
|
// Remove version classes from body.
|
||||||
this.removeVersionClass();
|
this.removeVersionClass();
|
||||||
|
@ -66,20 +64,20 @@ export class AppComponent implements OnInit {
|
||||||
|
|
||||||
// Listen for session expired events.
|
// Listen for session expired events.
|
||||||
CoreEvents.on(CoreEvents.SESSION_EXPIRED, (data: CoreEventSessionExpiredData) => {
|
CoreEvents.on(CoreEvents.SESSION_EXPIRED, (data: CoreEventSessionExpiredData) => {
|
||||||
this.loginHelper.sessionExpired(data);
|
CoreLoginHelper.instance.sessionExpired(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for passwordchange and usernotfullysetup events to open InAppBrowser.
|
// Listen for passwordchange and usernotfullysetup events to open InAppBrowser.
|
||||||
CoreEvents.on(CoreEvents.PASSWORD_CHANGE_FORCED, (data: CoreEventSiteData) => {
|
CoreEvents.on(CoreEvents.PASSWORD_CHANGE_FORCED, (data: CoreEventSiteData) => {
|
||||||
this.loginHelper.passwordChangeForced(data.siteId!);
|
CoreLoginHelper.instance.passwordChangeForced(data.siteId!);
|
||||||
});
|
});
|
||||||
CoreEvents.on(CoreEvents.USER_NOT_FULLY_SETUP, (data: CoreEventSiteData) => {
|
CoreEvents.on(CoreEvents.USER_NOT_FULLY_SETUP, (data: CoreEventSiteData) => {
|
||||||
this.loginHelper.openInAppForEdit(data.siteId!, '/user/edit.php', 'core.usernotfullysetup');
|
CoreLoginHelper.instance.openInAppForEdit(data.siteId!, '/user/edit.php', 'core.usernotfullysetup');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for sitepolicynotagreed event to accept the site policy.
|
// Listen for sitepolicynotagreed event to accept the site policy.
|
||||||
CoreEvents.on(CoreEvents.SITE_POLICY_NOT_AGREED, (data: CoreEventSiteData) => {
|
CoreEvents.on(CoreEvents.SITE_POLICY_NOT_AGREED, (data: CoreEventSiteData) => {
|
||||||
this.loginHelper.sitePolicyNotAgreed(data.siteId);
|
CoreLoginHelper.instance.sitePolicyNotAgreed(data.siteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
CoreEvents.on(CoreEvents.LOGIN, async (data: CoreEventSiteData) => {
|
CoreEvents.on(CoreEvents.LOGIN, async (data: CoreEventSiteData) => {
|
||||||
|
@ -119,6 +117,17 @@ export class AppComponent implements OnInit {
|
||||||
this.onPlatformReady();
|
this.onPlatformReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (!this.outlet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreSubscriptions.once(this.outlet.activateEvents, () => SplashScreen.instance.hide());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async init function on platform ready.
|
* Async init function on platform ready.
|
||||||
*/
|
*/
|
||||||
|
@ -155,8 +164,9 @@ export class AppComponent implements OnInit {
|
||||||
*/
|
*/
|
||||||
protected loadCustomStrings(): void {
|
protected loadCustomStrings(): void {
|
||||||
const currentSite = CoreSites.instance.getCurrentSite();
|
const currentSite = CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
if (currentSite) {
|
if (currentSite) {
|
||||||
this.langProvider.loadCustomStringsFromSite(currentSite);
|
CoreLang.instance.loadCustomStringsFromSite(currentSite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
|
@ -38,6 +38,7 @@ import { CoreLinkDirective } from './link';
|
||||||
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
|
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
|
||||||
import { CoreFilterDelegate } from '@features/filter/services/filter-delegate';
|
import { CoreFilterDelegate } from '@features/filter/services/filter-delegate';
|
||||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||||
|
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
||||||
|
@ -567,12 +568,7 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve): void => {
|
return new Promise(resolve => CoreSubscriptions.once(externalImage.onLoad, resolve));
|
||||||
const subscription = externalImage.onLoad.subscribe(() => {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Automatically reject the promise after 5 seconds to prevent blocking the user forever.
|
// Automatically reject the promise after 5 seconds to prevent blocking the user forever.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, CanLoad, UrlTree } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { Router } from '@singletons';
|
||||||
|
|
||||||
|
import { CoreLoginHelper } from '../services/login-helper';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreLoginHasSitesGuard implements CanActivate, CanLoad {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
canActivate(): Promise<true | UrlTree> {
|
||||||
|
return this.guard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
canLoad(): Promise<true | UrlTree> {
|
||||||
|
return this.guard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user has any sites stored.
|
||||||
|
*/
|
||||||
|
private async guard(): Promise<true | UrlTree> {
|
||||||
|
const sites = await CoreUtils.instance.ignoreErrors(CoreSites.instance.getSites(), []);
|
||||||
|
|
||||||
|
if (sites.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [path, params] = CoreLoginHelper.instance.getAddSiteRouteInfo();
|
||||||
|
const route = Router.instance.parseUrl(path);
|
||||||
|
|
||||||
|
route.queryParams = params;
|
||||||
|
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,16 +21,13 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreLoginSiteHelpComponent } from './components/site-help/site-help';
|
import { CoreLoginSiteHelpComponent } from './components/site-help/site-help';
|
||||||
import { CoreLoginSiteOnboardingComponent } from './components/site-onboarding/site-onboarding';
|
import { CoreLoginSiteOnboardingComponent } from './components/site-onboarding/site-onboarding';
|
||||||
|
import { CoreLoginHasSitesGuard } from './guards/has-sites';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'init',
|
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
redirectTo: 'sites',
|
||||||
{
|
|
||||||
path: 'init',
|
|
||||||
loadChildren: () => import('./pages/init/init.module').then( m => m.CoreLoginInitPageModule),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'site',
|
path: 'site',
|
||||||
|
@ -43,6 +40,8 @@ const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'sites',
|
path: 'sites',
|
||||||
loadChildren: () => import('./pages/sites/sites.module').then( m => m.CoreLoginSitesPageModule),
|
loadChildren: () => import('./pages/sites/sites.module').then( m => m.CoreLoginSitesPageModule),
|
||||||
|
canLoad: [CoreLoginHasSitesGuard],
|
||||||
|
canActivate: [CoreLoginHasSitesGuard],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'forgottenpassword',
|
path: 'forgottenpassword',
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<ion-content fullscreen="true" scrollY="false">
|
|
||||||
<div class="core-bglogo" slot="fixed">
|
|
||||||
<div class="core-center-spinner">
|
|
||||||
<ion-spinner></ion-spinner>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ion-content>
|
|
|
@ -1,25 +0,0 @@
|
||||||
ion-content::part(background) {
|
|
||||||
--background: var(--core-splash-screen-background, #ffffff);
|
|
||||||
|
|
||||||
background-image: url("~@/assets/img/splash.png");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100%;
|
|
||||||
background-size: var(--core-splash-bgsize, 100vmax);
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.core-bglogo {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.core-center-spinner {
|
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-spinner {
|
|
||||||
--color: var(--core-splash-spinner-color, var(--core-color));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
// (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 { Component, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
import { CoreApp, CoreRedirectData } from '@services/app';
|
|
||||||
import { ApplicationInit, SplashScreen } from '@singletons';
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page that displays a "splash screen" while the app is being initialized.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'page-core-login-init',
|
|
||||||
templateUrl: 'init.html',
|
|
||||||
styleUrls: ['init.scss'],
|
|
||||||
})
|
|
||||||
export class CoreLoginInitPage implements OnInit {
|
|
||||||
|
|
||||||
// @todo this page should be removed in favor of native splash
|
|
||||||
// or a splash component rendered in the root app component
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the component.
|
|
||||||
*/
|
|
||||||
async ngOnInit(): Promise<void> {
|
|
||||||
// Wait for the app to be ready.
|
|
||||||
await ApplicationInit.instance.donePromise;
|
|
||||||
|
|
||||||
// Check if there was a pending redirect.
|
|
||||||
const redirectData = CoreApp.instance.getRedirect();
|
|
||||||
|
|
||||||
if (redirectData.siteId) {
|
|
||||||
await this.handleRedirect(redirectData);
|
|
||||||
} else {
|
|
||||||
await this.loadPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
|
|
||||||
setTimeout(() => {
|
|
||||||
SplashScreen.instance.hide();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Treat redirect data.
|
|
||||||
*
|
|
||||||
* @param redirectData Redirect data.
|
|
||||||
*/
|
|
||||||
protected async handleRedirect(redirectData: CoreRedirectData): Promise<void> {
|
|
||||||
// Unset redirect data.
|
|
||||||
CoreApp.instance.storeRedirect('', '', {});
|
|
||||||
|
|
||||||
// Only accept the redirect if it was stored less than 20 seconds ago.
|
|
||||||
if (redirectData.timemodified && Date.now() - redirectData.timemodified < 20000) {
|
|
||||||
if (redirectData.siteId != CoreConstants.NO_SITE_ID) {
|
|
||||||
// The redirect is pointing to a site, load it.
|
|
||||||
try {
|
|
||||||
const loggedIn = await CoreSites.instance.loadSite(
|
|
||||||
redirectData.siteId!,
|
|
||||||
redirectData.page,
|
|
||||||
redirectData.params,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!loggedIn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CoreNavigator.instance.navigateToSiteHome({
|
|
||||||
params: {
|
|
||||||
redirectPath: redirectData.page,
|
|
||||||
redirectParams: redirectData.params,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
// Site doesn't exist.
|
|
||||||
return this.loadPage();
|
|
||||||
}
|
|
||||||
} else if (redirectData.page) {
|
|
||||||
// No site to load, open the page.
|
|
||||||
// @todo return CoreNavigator.instance.goToNoSitePage(redirectData.page, redirectData.params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.loadPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the right page.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async loadPage(): Promise<void> {
|
|
||||||
if (CoreSites.instance.isLoggedIn()) {
|
|
||||||
if (CoreLoginHelper.instance.isSiteLoggedOut()) {
|
|
||||||
await CoreSites.instance.logout();
|
|
||||||
|
|
||||||
return this.loadPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
await CoreNavigator.instance.navigateToSiteHome();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CoreNavigator.instance.navigate('/login/sites', { reset: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -45,13 +45,7 @@ export class CoreLoginSitesPage implements OnInit {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
const sites = await CoreUtils.instance.ignoreErrors(CoreSites.instance.getSortedSites());
|
const sites = await CoreUtils.instance.ignoreErrors(CoreSites.instance.getSortedSites(), [] as CoreSiteBasicInfo[]);
|
||||||
|
|
||||||
if (!sites || sites.length == 0) {
|
|
||||||
CoreLoginHelper.instance.goToAddSite(true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove protocol from the url to show more url text.
|
// Remove protocol from the url to show more url text.
|
||||||
this.sites = sites.map((site) => {
|
this.sites = sites.map((site) => {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreUrl } from '@singletons/url';
|
import { CoreUrl } from '@singletons/url';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper provider that provides some common features regarding authentication.
|
* Helper provider that provides some common features regarding authentication.
|
||||||
|
@ -408,22 +409,27 @@ export class CoreLoginHelperProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise<void> {
|
async goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise<void> {
|
||||||
let pageRoute: string;
|
const [path, params] = this.getAddSiteRouteInfo(showKeyboard);
|
||||||
let params: Params;
|
|
||||||
|
|
||||||
|
await CoreNavigator.instance.navigate(path, { params, reset: setRoot });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get path and params to visit the route to add site.
|
||||||
|
*
|
||||||
|
* @param showKeyboard Whether to show keyboard in the new page. Only if no fixed URL set.
|
||||||
|
* @return Path and params.
|
||||||
|
*/
|
||||||
|
getAddSiteRouteInfo(showKeyboard?: boolean): [string, Params] {
|
||||||
if (this.isFixedUrlSet()) {
|
if (this.isFixedUrlSet()) {
|
||||||
// Fixed URL is set, go to credentials page.
|
// Fixed URL is set, go to credentials page.
|
||||||
const fixedSites = this.getFixedSites();
|
const fixedSites = this.getFixedSites();
|
||||||
const url = typeof fixedSites == 'string' ? fixedSites : fixedSites[0].url;
|
const url = typeof fixedSites == 'string' ? fixedSites : fixedSites[0].url;
|
||||||
|
|
||||||
pageRoute = '/login/credentials';
|
return ['/login/credentials', { siteUrl: url }];
|
||||||
params = { siteUrl: url };
|
|
||||||
} else {
|
|
||||||
pageRoute = '/login/site';
|
|
||||||
params = { showKeyboard: showKeyboard };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreNavigator.instance.navigate(pageRoute, { params, reset: setRoot });
|
return ['/login/site', CoreObject.withoutEmpty({ showKeyboard: showKeyboard })];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
// (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 { CoreApp } from '@services/app';
|
|
||||||
import { CoreLoginInitPage } from '@features/login/pages/init/init';
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { ApplicationInit, SplashScreen } from '@singletons';
|
|
||||||
|
|
||||||
import { mockSingleton, renderComponent } from '@/testing/utils';
|
|
||||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
|
|
||||||
|
|
||||||
describe('CoreLoginInitPage', () => {
|
|
||||||
|
|
||||||
let navigator: CoreNavigatorService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockSingleton(CoreApp, { getRedirect: () => ({}) });
|
|
||||||
mockSingleton(ApplicationInit, { donePromise: Promise.resolve() });
|
|
||||||
mockSingleton(CoreSites, { isLoggedIn: () => false });
|
|
||||||
mockSingleton(SplashScreen, ['hide']);
|
|
||||||
|
|
||||||
navigator = mockSingleton(CoreNavigator, ['navigate']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render', async () => {
|
|
||||||
const fixture = await renderComponent(CoreLoginInitPage, {});
|
|
||||||
|
|
||||||
expect(fixture.debugElement.componentInstance).toBeTruthy();
|
|
||||||
expect(fixture.nativeElement.querySelector('ion-spinner')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('navigates to sites page after loading', async () => {
|
|
||||||
const fixture = await renderComponent(CoreLoginInitPage, {});
|
|
||||||
|
|
||||||
fixture.componentInstance.ngOnInit();
|
|
||||||
await ApplicationInit.instance.donePromise;
|
|
||||||
|
|
||||||
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -13,28 +13,44 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router, CanLoad, CanActivate, UrlTree } from '@angular/router';
|
import { CanLoad, CanActivate, UrlTree } from '@angular/router';
|
||||||
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { ApplicationInit } from '@singletons';
|
import { Router } from '@singletons';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AuthGuard implements CanLoad, CanActivate {
|
export class CoreMainMenuAuthGuard implements CanLoad, CanActivate {
|
||||||
|
|
||||||
constructor(private router: Router) {}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
canActivate(): Promise<true | UrlTree> {
|
canActivate(): Promise<true | UrlTree> {
|
||||||
return this.guard();
|
return this.guard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
canLoad(): Promise<true | UrlTree> {
|
canLoad(): Promise<true | UrlTree> {
|
||||||
return this.guard();
|
return this.guard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current user should be redirected to the authentication page.
|
||||||
|
*/
|
||||||
private async guard(): Promise<true | UrlTree> {
|
private async guard(): Promise<true | UrlTree> {
|
||||||
await ApplicationInit.instance.donePromise;
|
if (!CoreSites.instance.isLoggedIn()) {
|
||||||
|
return Router.instance.parseUrl('/login');
|
||||||
|
}
|
||||||
|
|
||||||
return CoreSites.instance.isLoggedIn() || this.router.parseUrl('/login');
|
if (CoreLoginHelper.instance.isSiteLoggedOut()) {
|
||||||
|
await CoreSites.instance.logout();
|
||||||
|
|
||||||
|
return Router.instance.parseUrl('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { AuthGuard } from '@guards/auth';
|
import { CoreMainMenuAuthGuard } from '@features/mainmenu/guards/auth';
|
||||||
|
|
||||||
import { AppRoutingModule } from '@/app/app-routing.module';
|
import { AppRoutingModule } from '@/app/app-routing.module';
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ const appRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'main',
|
path: 'main',
|
||||||
loadChildren: () => import('./mainmenu-lazy.module').then(m => m.CoreMainMenuLazyModule),
|
loadChildren: () => import('./mainmenu-lazy.module').then(m => m.CoreMainMenuLazyModule),
|
||||||
canActivate: [AuthGuard],
|
canActivate: [CoreMainMenuAuthGuard],
|
||||||
canLoad: [AuthGuard],
|
canLoad: [CoreMainMenuAuthGuard],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, CanLoad, UrlTree } from '@angular/router';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { Router } from '@singletons';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
import { CoreConstants } from '../constants';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreRedirectGuard implements CanLoad, CanActivate {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
canLoad(): Promise<true | UrlTree> {
|
||||||
|
return this.guard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
canActivate(): Promise<true | UrlTree> {
|
||||||
|
return this.guard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is a pending redirect and trigger it.
|
||||||
|
*/
|
||||||
|
private async guard(): Promise<true | UrlTree> {
|
||||||
|
const redirect = CoreApp.instance.getRedirect();
|
||||||
|
|
||||||
|
if (!redirect) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Only accept the redirect if it was stored less than 20 seconds ago.
|
||||||
|
if (!redirect.timemodified || Date.now() - redirect.timemodified < 20000) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to site path.
|
||||||
|
if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) {
|
||||||
|
const loggedIn = await CoreSites.instance.loadSite(
|
||||||
|
redirect.siteId,
|
||||||
|
redirect.page,
|
||||||
|
redirect.params,
|
||||||
|
);
|
||||||
|
const route = Router.instance.parseUrl('/main');
|
||||||
|
|
||||||
|
route.queryParams = CoreObject.withoutEmpty({
|
||||||
|
redirectPath: redirect.page,
|
||||||
|
redirectParams: redirect.params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return loggedIn ? route : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort redirect.
|
||||||
|
if (!redirect.page) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to non-site path.
|
||||||
|
const route = Router.instance.parseUrl(redirect.page);
|
||||||
|
|
||||||
|
route.queryParams = CoreObject.withoutEmpty({
|
||||||
|
redirectPath: redirect.page,
|
||||||
|
redirectParams: redirect.params,
|
||||||
|
});
|
||||||
|
|
||||||
|
return route;
|
||||||
|
} finally {
|
||||||
|
CoreApp.instance.forgetRedirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,27 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { CoreApp } from '@services/app';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
|
||||||
|
|
||||||
import { CoreLoginInitPage } from './init';
|
export default function(): void {
|
||||||
|
CoreApp.instance.consumeStorageRedirect();
|
||||||
const routes: Routes = [
|
}
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: CoreLoginInitPage,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
IonicModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
CoreLoginInitPage,
|
|
||||||
],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class CoreLoginInitPageModule {}
|
|
|
@ -25,6 +25,7 @@ import { makeSingleton, Keyboard, Network, StatusBar, Platform, Device } from '@
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreColors } from '@singletons/colors';
|
import { CoreColors } from '@singletons/colors';
|
||||||
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
|
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object responsible of managing schema versions.
|
* Object responsible of managing schema versions.
|
||||||
|
@ -58,6 +59,7 @@ export class CoreAppProvider {
|
||||||
protected keyboardClosing = false;
|
protected keyboardClosing = false;
|
||||||
protected backActions: {callback: () => boolean; priority: number}[] = [];
|
protected backActions: {callback: () => boolean; priority: number}[] = [];
|
||||||
protected forceOffline = false;
|
protected forceOffline = false;
|
||||||
|
protected redirect?: CoreRedirectData;
|
||||||
|
|
||||||
// Variables for DB.
|
// Variables for DB.
|
||||||
protected schemaVersionsManager: Promise<SchemaVersionsManager>;
|
protected schemaVersionsManager: Promise<SchemaVersionsManager>;
|
||||||
|
@ -516,32 +518,50 @@ export class CoreAppProvider {
|
||||||
await deferred.promise;
|
await deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read redirect data from local storage and clear it if it existed.
|
||||||
|
*/
|
||||||
|
consumeStorageRedirect(): void {
|
||||||
|
if (!localStorage?.getItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read data from storage.
|
||||||
|
const jsonData = localStorage.getItem('CoreRedirect');
|
||||||
|
|
||||||
|
if (!jsonData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear storage.
|
||||||
|
localStorage.removeItem('CoreRedirect');
|
||||||
|
|
||||||
|
// Remember redirect data.
|
||||||
|
const data: CoreRedirectData = JSON.parse(jsonData);
|
||||||
|
|
||||||
|
if (!CoreObject.isEmpty(data)) {
|
||||||
|
this.redirect = data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error loading redirect data:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forget redirect data.
|
||||||
|
*/
|
||||||
|
forgetRedirect(): void {
|
||||||
|
delete this.redirect;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve redirect data.
|
* Retrieve redirect data.
|
||||||
*
|
*
|
||||||
* @return Object with siteid, state, params and timemodified.
|
* @return Object with siteid, state, params and timemodified.
|
||||||
*/
|
*/
|
||||||
getRedirect(): CoreRedirectData {
|
getRedirect(): CoreRedirectData | null {
|
||||||
if (localStorage?.getItem) {
|
return this.redirect || null;
|
||||||
try {
|
|
||||||
const paramsJson = localStorage.getItem('CoreRedirectParams');
|
|
||||||
const data: CoreRedirectData = {
|
|
||||||
siteId: localStorage.getItem('CoreRedirectSiteId') || undefined,
|
|
||||||
page: localStorage.getItem('CoreRedirectState') || undefined,
|
|
||||||
timemodified: parseInt(localStorage.getItem('CoreRedirectTime') || '0', 10),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (paramsJson) {
|
|
||||||
data.params = JSON.parse(paramsJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (ex) {
|
|
||||||
this.logger.error('Error loading redirect data:', ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -552,17 +572,19 @@ export class CoreAppProvider {
|
||||||
* @param params Page params.
|
* @param params Page params.
|
||||||
*/
|
*/
|
||||||
storeRedirect(siteId: string, page: string, params: Params): void {
|
storeRedirect(siteId: string, page: string, params: Params): void {
|
||||||
if (localStorage && localStorage.setItem) {
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('CoreRedirectSiteId', siteId);
|
const redirect: CoreRedirectData = {
|
||||||
localStorage.setItem('CoreRedirectState', page);
|
siteId,
|
||||||
localStorage.setItem('CoreRedirectParams', JSON.stringify(params));
|
page,
|
||||||
localStorage.setItem('CoreRedirectTime', String(Date.now()));
|
params,
|
||||||
|
timemodified: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem('CoreRedirect', JSON.stringify(redirect));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The back button event is triggered when the user presses the native
|
* The back button event is triggered when the user presses the native
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreConstants } from '@/core/constants';
|
||||||
import { LangChangeEvent } from '@ngx-translate/core';
|
import { LangChangeEvent } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from '@services/app';
|
import { CoreAppProvider } from '@services/app';
|
||||||
import { CoreConfig } from '@services/config';
|
import { CoreConfig } from '@services/config';
|
||||||
|
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
import { makeSingleton, Translate, Platform } from '@singletons';
|
import { makeSingleton, Translate, Platform } from '@singletons';
|
||||||
|
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
@ -128,44 +129,25 @@ export class CoreLangProvider {
|
||||||
|
|
||||||
// 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) => {
|
||||||
const subscription = Translate.instance.use(language).subscribe((data) => {
|
CoreSubscriptions.once(Translate.instance.use(language), data => {
|
||||||
// It's a language override, load the original one first.
|
// It's a language override, load the original one first.
|
||||||
const fallbackLang = Translate.instance.instant('core.parentlanguage');
|
const fallbackLang = Translate.instance.instant('core.parentlanguage');
|
||||||
|
|
||||||
if (fallbackLang != '' && fallbackLang != 'core.parentlanguage' && fallbackLang != language) {
|
if (fallbackLang != '' && fallbackLang != 'core.parentlanguage' && fallbackLang != language) {
|
||||||
const fallbackSubs = Translate.instance.use(fallbackLang).subscribe((fallbackData) => {
|
CoreSubscriptions.once(
|
||||||
|
Translate.instance.use(fallbackLang),
|
||||||
|
fallbackData => {
|
||||||
data = Object.assign(fallbackData, data);
|
data = Object.assign(fallbackData, data);
|
||||||
resolve(data);
|
|
||||||
|
|
||||||
// Data received, unsubscribe. Use a timeout because we can receive a value immediately.
|
resolve(data);
|
||||||
setTimeout(() => {
|
},
|
||||||
fallbackSubs.unsubscribe();
|
|
||||||
});
|
|
||||||
}, () => {
|
|
||||||
// Resolve with the original language.
|
// Resolve with the original language.
|
||||||
resolve(data);
|
() => resolve(data),
|
||||||
|
);
|
||||||
// Error received, unsubscribe. Use a timeout because we can receive a value immediately.
|
|
||||||
setTimeout(() => {
|
|
||||||
fallbackSubs.unsubscribe();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
}
|
}
|
||||||
|
}, reject);
|
||||||
// Data received, unsubscribe. Use a timeout because we can receive a value immediately.
|
|
||||||
setTimeout(() => {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
});
|
|
||||||
}, (error) => {
|
|
||||||
reject(error);
|
|
||||||
|
|
||||||
// Error received, unsubscribe. Use a timeout because we can receive a value immediately.
|
|
||||||
setTimeout(() => {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Change the config.
|
// Change the config.
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { EventEmitter } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribable object.
|
||||||
|
*/
|
||||||
|
type Subscribable<T> = EventEmitter<T> | Observable<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton with helpers to work with subscriptions.
|
||||||
|
*/
|
||||||
|
export class CoreSubscriptions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen once to a subscribable object.
|
||||||
|
*
|
||||||
|
* @param subscribable Subscribable to listen to.
|
||||||
|
* @param onSuccess Callback to run when the subscription is updated.
|
||||||
|
* @param onError Callback to run when the an error happens.
|
||||||
|
*/
|
||||||
|
static once<T>(subscribable: Subscribable<T>, onSuccess: (value: T) => unknown, onError?: (error: unknown) => unknown): void {
|
||||||
|
const subscription = subscribable.subscribe(
|
||||||
|
value => {
|
||||||
|
// Unsubscribe using a timeout because we can receive a value immediately.
|
||||||
|
setTimeout(() => subscription.unsubscribe(), 0);
|
||||||
|
|
||||||
|
onSuccess(value);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// Unsubscribe using a timeout because we can receive a value immediately.
|
||||||
|
setTimeout(() => subscription.unsubscribe(), 0);
|
||||||
|
|
||||||
|
onError?.call(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue