Merge pull request #3777 from alfonso-salces/MOBILE-4405
Mobile 4405 - Implement auto logout
This commit is contained in:
		
						commit
						038bcfaa30
					
				
							
								
								
									
										274
									
								
								src/core/features/autologout/services/autologout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								src/core/features/autologout/services/autologout.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,274 @@
 | 
			
		||||
// (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 { CoreSite } from '@classes/site';
 | 
			
		||||
import { CorePlatform } from '@services/platform';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreStorage } from '@services/storage';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Auto logout service
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreAutoLogoutService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Timestamp indicating the last time the application was in the foreground.
 | 
			
		||||
     */
 | 
			
		||||
    protected static readonly TIMESTAMP_DB_KEY = 'CoreAutoLogoutTimestamp';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * How often we will store a timestamp (in miliseconds).
 | 
			
		||||
     */
 | 
			
		||||
    protected static readonly DEFAULT_TIMESTAMP_STORE_TIME = 10000;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Grace period if you return to the application too soon (in miliseconds).
 | 
			
		||||
     */
 | 
			
		||||
    protected static readonly GRACE_PERIOD = 30000;
 | 
			
		||||
 | 
			
		||||
    protected platformResumeSubscription?: Subscription;
 | 
			
		||||
    protected platformPauseSubscription?: Subscription;
 | 
			
		||||
    protected interval?: ReturnType<typeof setInterval>;
 | 
			
		||||
    protected backgroundTimestamp?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize.
 | 
			
		||||
     */
 | 
			
		||||
    initialize(): void {
 | 
			
		||||
        CoreEvents.on(CoreEvents.LOGIN, async() => await this.refreshListeners());
 | 
			
		||||
        CoreEvents.on(CoreEvents.LOGOUT, async() => {
 | 
			
		||||
            this.cancelListeners();
 | 
			
		||||
            const storage = CoreStorage.forCurrentSite();
 | 
			
		||||
            await storage.remove(CoreAutoLogoutService.TIMESTAMP_DB_KEY);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh listeners for auto logout.
 | 
			
		||||
     */
 | 
			
		||||
    async refreshListeners(): Promise<void> {
 | 
			
		||||
        if (!CoreSites.isLoggedIn()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const site = CoreSites.getCurrentSite();
 | 
			
		||||
 | 
			
		||||
        if (!site) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const autoLogoutType = Number(site.getStoredConfig('tool_mobile_autologout'));
 | 
			
		||||
        this.cancelListeners();
 | 
			
		||||
 | 
			
		||||
        if (!autoLogoutType || autoLogoutType === CoreAutoLogoutType.NEVER) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (autoLogoutType === CoreAutoLogoutType.CUSTOM) {
 | 
			
		||||
            await this.setTimestamp();
 | 
			
		||||
            this.setInterval();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.platformPauseSubscription = CorePlatform.pause.subscribe(async () => {
 | 
			
		||||
            this.backgroundTimestamp = new Date().getTime();
 | 
			
		||||
            this.clearInterval();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.platformResumeSubscription = CorePlatform.resume.subscribe(async () => {
 | 
			
		||||
            if (autoLogoutType !== CoreAutoLogoutType.CUSTOM) {
 | 
			
		||||
                await this.handleAppClosed(site);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const autoLogoutTime = Number(site.getStoredConfig('tool_mobile_autologouttime'));
 | 
			
		||||
            const loggedOut = await this.handleSessionClosed(autoLogoutTime, site);
 | 
			
		||||
 | 
			
		||||
            if (!loggedOut) {
 | 
			
		||||
                await this.setTimestamp();
 | 
			
		||||
                this.setInterval();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set site logged out.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId site id.
 | 
			
		||||
     */
 | 
			
		||||
    protected async logout(siteId: string): Promise<void> {
 | 
			
		||||
        await CoreSites.setSiteLoggedOut(siteId, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves stored timestamp.
 | 
			
		||||
     */
 | 
			
		||||
    protected async setTimestamp(): Promise<void> {
 | 
			
		||||
        const date = new Date().getTime();
 | 
			
		||||
        const storage = CoreStorage.forCurrentSite();
 | 
			
		||||
        await storage.set(CoreAutoLogoutService.TIMESTAMP_DB_KEY, date);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gives if auto logout can be displayed.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns true if can display, false if not.
 | 
			
		||||
     */
 | 
			
		||||
    async canShowPreference(): Promise<boolean> {
 | 
			
		||||
        const site = CoreSites.getCurrentSite();
 | 
			
		||||
 | 
			
		||||
        if (!site) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const autoLogoutType = Number(site.getStoredConfig('tool_mobile_autologout'));
 | 
			
		||||
 | 
			
		||||
        return autoLogoutType !== CoreAutoLogoutType.NEVER;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancel uncompleted listeners.
 | 
			
		||||
     */
 | 
			
		||||
    protected cancelListeners(): void {
 | 
			
		||||
        this.clearInterval();
 | 
			
		||||
        this.platformResumeSubscription?.unsubscribe();
 | 
			
		||||
        this.platformPauseSubscription?.unsubscribe();
 | 
			
		||||
        delete this.platformPauseSubscription;
 | 
			
		||||
        delete this.platformResumeSubscription;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set interval.
 | 
			
		||||
     */
 | 
			
		||||
    protected setInterval(): void {
 | 
			
		||||
        this.interval = setInterval(async () => await this.setTimestamp(), CoreAutoLogoutService.DEFAULT_TIMESTAMP_STORE_TIME);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clear interval.
 | 
			
		||||
     */
 | 
			
		||||
    protected clearInterval(): void {
 | 
			
		||||
        if (!this.interval) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        clearInterval(this.interval);
 | 
			
		||||
        delete this.interval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logout user if his session is expired.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sessionDuration Session duration.
 | 
			
		||||
     * @param site Current site.
 | 
			
		||||
     * @returns Whether site has been logged out.
 | 
			
		||||
     */
 | 
			
		||||
    async handleSessionClosed(sessionDuration: number, site: CoreSite): Promise<boolean> {
 | 
			
		||||
        if (!site.id) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const storage = CoreStorage.forSite(site);
 | 
			
		||||
        const savedTimestamp = await storage.get<number>(CoreAutoLogoutService.TIMESTAMP_DB_KEY);
 | 
			
		||||
 | 
			
		||||
        if (!savedTimestamp) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get expiration time from site preferences as miliseconds.
 | 
			
		||||
        const expirationDate = savedTimestamp + ((sessionDuration || 0) * 1000);
 | 
			
		||||
        await storage.remove(CoreAutoLogoutService.TIMESTAMP_DB_KEY);
 | 
			
		||||
 | 
			
		||||
        if (new Date().getTime() < expirationDate) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.logout(site.id);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logout if user closed the app.
 | 
			
		||||
     *
 | 
			
		||||
     * @param site Current site.
 | 
			
		||||
     * @returns Whether site has been logged out.
 | 
			
		||||
     */
 | 
			
		||||
    async handleAppClosed(site: CoreSite): Promise<boolean> {
 | 
			
		||||
        if (!site.id) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            this.backgroundTimestamp &&
 | 
			
		||||
            (this.backgroundTimestamp + CoreAutoLogoutService.GRACE_PERIOD) > new Date().getTime()
 | 
			
		||||
        ) {
 | 
			
		||||
            delete this.backgroundTimestamp;
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.logout(site.id);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getConfig(): { autoLogoutType: CoreAutoLogoutType; autoLogoutTime: number } {
 | 
			
		||||
        const site = CoreSites.getRequiredCurrentSite();
 | 
			
		||||
        const autoLogoutType = Number(site.getStoredConfig('tool_mobile_autologout'));
 | 
			
		||||
        const autoLogoutTime = Number(site.getStoredConfig('tool_mobile_autologouttime'));
 | 
			
		||||
 | 
			
		||||
        return { autoLogoutType, autoLogoutTime };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CoreAutoLogoutSessionConfig = {
 | 
			
		||||
    type: CoreAutoLogoutType.CUSTOM;
 | 
			
		||||
    sessionDuration: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreAutoLogoutOtherConfig = {
 | 
			
		||||
    type: Exclude<CoreAutoLogoutType, CoreAutoLogoutType.CUSTOM>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Possible automatic logout cases.
 | 
			
		||||
 */
 | 
			
		||||
export enum CoreAutoLogoutType {
 | 
			
		||||
    /**
 | 
			
		||||
     * Disabled automatic logout.
 | 
			
		||||
     */
 | 
			
		||||
    NEVER = 0,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When the user closes the app, in next login he need to login again.
 | 
			
		||||
     */
 | 
			
		||||
    INMEDIATE = 1,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This applies when session time is set. If the user closes the app more time than the specified,
 | 
			
		||||
     * then, the user must login again.
 | 
			
		||||
     */
 | 
			
		||||
    CUSTOM = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreAutoLogoutConfig = CoreAutoLogoutSessionConfig | CoreAutoLogoutOtherConfig;
 | 
			
		||||
 | 
			
		||||
export const CoreAutoLogout = makeSingleton(CoreAutoLogoutService);
 | 
			
		||||
@ -158,6 +158,7 @@ import { ADDON_PRIVATEFILES_SERVICES } from '@addons/privatefiles/privatefiles.m
 | 
			
		||||
import { AddonModAssignComponentsModule } from '@addons/mod/assign/components/components.module';
 | 
			
		||||
import { CorePromisedValue } from '@classes/promised-value';
 | 
			
		||||
import { CorePlatform } from '@services/platform';
 | 
			
		||||
import { CoreAutoLogoutService } from '@features/autologout/services/autologout';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to provide functionalities regarding compiling dynamic HTML and Javascript.
 | 
			
		||||
@ -268,6 +269,7 @@ export class CoreCompileProvider {
 | 
			
		||||
    injectLibraries(instance: any, extraProviders: Type<unknown>[] = []): void {
 | 
			
		||||
        const providers = [
 | 
			
		||||
            ...CORE_SERVICES,
 | 
			
		||||
            CoreAutoLogoutService,
 | 
			
		||||
            ...CORE_BLOCK_SERVICES,
 | 
			
		||||
            ...CORE_COMMENTS_SERVICES,
 | 
			
		||||
            ...CORE_CONTENTLINKS_SERVICES,
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,11 @@ import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreLocalNotifications } from '@services/local-notifications';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreStorage } from '@services/storage';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Init databases instances.
 | 
			
		||||
 */
 | 
			
		||||
export default async function(): Promise<void> {
 | 
			
		||||
    await Promise.all([
 | 
			
		||||
        CoreApp.initializeDatabase(),
 | 
			
		||||
@ -27,5 +31,6 @@ export default async function(): Promise<void> {
 | 
			
		||||
        CoreFilepool.initializeDatabase(),
 | 
			
		||||
        CoreLocalNotifications.initializeDatabase(),
 | 
			
		||||
        CoreSites.initializeDatabase(),
 | 
			
		||||
        CoreStorage.initializeDatabase(),
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreAutoLogout } from '@features/autologout/services/autologout';
 | 
			
		||||
import { CoreConfig } from '@services/config';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreLang } from '@services/lang';
 | 
			
		||||
@ -31,5 +32,6 @@ export default async function(): Promise<void> {
 | 
			
		||||
        CoreNetwork.initialize(),
 | 
			
		||||
        CoreUpdateManager.initialize(),
 | 
			
		||||
        CoreTimeUtils.initialize(),
 | 
			
		||||
        CoreAutoLogout.initialize(),
 | 
			
		||||
    ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,13 @@ import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { SITE_SCHEMA as FILEPOOL_SITE_SCHEMA } from './filepool';
 | 
			
		||||
import { SITE_SCHEMA as SITES_SITE_SCHEMA } from './sites';
 | 
			
		||||
import { SITE_SCHEMA as SYNC_SITE_SCHEMA } from './sync';
 | 
			
		||||
import { SITE_SCHEMA as STORAGE_SITE_SCHEMA } from './storage';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Give database providers.
 | 
			
		||||
 *
 | 
			
		||||
 * @returns database providers
 | 
			
		||||
 */
 | 
			
		||||
export function getDatabaseProviders(): Provider[] {
 | 
			
		||||
    return [{
 | 
			
		||||
        provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
@ -26,6 +32,7 @@ export function getDatabaseProviders(): Provider[] {
 | 
			
		||||
            FILEPOOL_SITE_SCHEMA,
 | 
			
		||||
            SITES_SITE_SCHEMA,
 | 
			
		||||
            SYNC_SITE_SCHEMA,
 | 
			
		||||
            STORAGE_SITE_SCHEMA,
 | 
			
		||||
        ],
 | 
			
		||||
        multi: true,
 | 
			
		||||
    }];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										55
									
								
								src/core/services/database/storage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/core/services/database/storage.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
// (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 { SQLiteDBTableSchema } from '@classes/sqlitedb';
 | 
			
		||||
import { CoreAppSchema } from '@services/app';
 | 
			
		||||
import { CoreSiteSchema } from '@services/sites';
 | 
			
		||||
 | 
			
		||||
export const TABLE_NAME = 'core_storage';
 | 
			
		||||
 | 
			
		||||
export const TABLE_SCHEMA: SQLiteDBTableSchema = {
 | 
			
		||||
    name: TABLE_NAME,
 | 
			
		||||
    columns: [
 | 
			
		||||
        {
 | 
			
		||||
            name: 'key',
 | 
			
		||||
            type: 'TEXT',
 | 
			
		||||
            primaryKey: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            name: 'value',
 | 
			
		||||
            type: 'TEXT',
 | 
			
		||||
            notNull: true,
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const APP_SCHEMA: CoreAppSchema = {
 | 
			
		||||
    name: 'CoreStorageService',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [TABLE_SCHEMA],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'CoreStorageService',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [TABLE_SCHEMA],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Storage table record type.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreStorageRecord = {
 | 
			
		||||
    key: string;
 | 
			
		||||
    value: string;
 | 
			
		||||
};
 | 
			
		||||
@ -65,6 +65,7 @@ import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest
 | 
			
		||||
import { CoreLang, CoreLangFormat } from '@services/lang';
 | 
			
		||||
import { CoreNative } from '@features/native/services/native';
 | 
			
		||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
 | 
			
		||||
import { CoreAutoLogoutType, CoreAutoLogout } from '@features/autologout/services/autologout';
 | 
			
		||||
 | 
			
		||||
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
 | 
			
		||||
export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id';
 | 
			
		||||
@ -1461,6 +1462,8 @@ export class CoreSitesProvider {
 | 
			
		||||
     * @returns Promise resolved if a session is restored.
 | 
			
		||||
     */
 | 
			
		||||
    async restoreSession(): Promise<void> {
 | 
			
		||||
        await this.handleAutoLogout();
 | 
			
		||||
 | 
			
		||||
        if (this.sessionRestored) {
 | 
			
		||||
            return Promise.reject(new CoreError('Session already restored.'));
 | 
			
		||||
        }
 | 
			
		||||
@ -1477,6 +1480,30 @@ export class CoreSitesProvider {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle auto logout by checking autologout type and time if its required.
 | 
			
		||||
     */
 | 
			
		||||
    async handleAutoLogout(): Promise<void> {
 | 
			
		||||
        await CoreUtils.ignoreErrors(( async () => {
 | 
			
		||||
            const siteId = await this.getStoredCurrentSiteId();
 | 
			
		||||
            const site = await this.getSite(siteId);
 | 
			
		||||
            const autoLogoutType = Number(site.getStoredConfig('tool_mobile_autologout'));
 | 
			
		||||
            const autoLogoutTime = Number(site.getStoredConfig('tool_mobile_autologouttime'));
 | 
			
		||||
 | 
			
		||||
            if (autoLogoutType === CoreAutoLogoutType.NEVER || !site.id) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (autoLogoutType === CoreAutoLogoutType.CUSTOM) {
 | 
			
		||||
                await CoreAutoLogout.handleSessionClosed(autoLogoutTime, site);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await CoreAutoLogout.handleAppClosed(site);
 | 
			
		||||
        })());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark a site as logged out so the user needs to authenticate again.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										222
									
								
								src/core/services/storage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/core/services/storage.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,222 @@
 | 
			
		||||
// (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 { Inject, Injectable, Optional } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
 | 
			
		||||
import { CoreDatabaseTable } from '@classes/database/database-table';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { SQLiteDB } from '@classes/sqlitedb';
 | 
			
		||||
 | 
			
		||||
import { APP_SCHEMA, CoreStorageRecord, TABLE_NAME } from './database/storage';
 | 
			
		||||
import { CoreSites } from './sites';
 | 
			
		||||
import { CoreSite } from '@classes/site';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to store data using key-value pairs.
 | 
			
		||||
 *
 | 
			
		||||
 * The data can be scoped to a single site using CoreStorage.forSite(site), and it will be automatically cleared
 | 
			
		||||
 * when the site is deleted.
 | 
			
		||||
 *
 | 
			
		||||
 * For tabular data, use CoreAppProvider.getDB() or CoreSite.getDb().
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreStorageService {
 | 
			
		||||
 | 
			
		||||
    table: AsyncInstance<CoreStorageTable>;
 | 
			
		||||
 | 
			
		||||
    constructor(@Optional() @Inject(null) lazyTableConstructor?: () => Promise<CoreStorageTable>) {
 | 
			
		||||
        this.table = asyncInstance(lazyTableConstructor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize database.
 | 
			
		||||
     */
 | 
			
		||||
    async initializeDatabase(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreApp.createTablesFromSchema(APP_SCHEMA);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.initializeTable(CoreApp.getDB());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize table.
 | 
			
		||||
     *
 | 
			
		||||
     * @param database Database.
 | 
			
		||||
     */
 | 
			
		||||
    async initializeTable(database: SQLiteDB): Promise<void> {
 | 
			
		||||
        const table = await getStorageTable(database);
 | 
			
		||||
 | 
			
		||||
        this.table.setInstance(table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key Data key.
 | 
			
		||||
     * @param defaultValue Value to return if the key wasn't found.
 | 
			
		||||
     * @returns Data value.
 | 
			
		||||
     */
 | 
			
		||||
    async get<T=unknown>(key: string): Promise<T | null>;
 | 
			
		||||
    async get<T>(key: string, defaultValue: T): Promise<T>;
 | 
			
		||||
    async get<T=unknown>(key: string, defaultValue: T | null = null): Promise<T | null> {
 | 
			
		||||
        try {
 | 
			
		||||
            const { value } = await this.table.getOneByPrimaryKey({ key });
 | 
			
		||||
 | 
			
		||||
            return JSON.parse(value);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            return defaultValue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get value directly from the database, without using any optimizations..
 | 
			
		||||
     *
 | 
			
		||||
     * @param key Data key.
 | 
			
		||||
     * @param defaultValue Value to return if the key wasn't found.
 | 
			
		||||
     * @returns Data value.
 | 
			
		||||
     */
 | 
			
		||||
    async getFromDB<T=unknown>(key: string): Promise<T | null>;
 | 
			
		||||
    async getFromDB<T>(key: string, defaultValue: T): Promise<T>;
 | 
			
		||||
    async getFromDB<T=unknown>(key: string, defaultValue: T | null = null): Promise<T | null> {
 | 
			
		||||
        try {
 | 
			
		||||
            const db = CoreApp.getDB();
 | 
			
		||||
            const { value } = await db.getRecord<CoreStorageRecord>(TABLE_NAME, { key });
 | 
			
		||||
 | 
			
		||||
            return JSON.parse(value);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            return defaultValue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key Data key.
 | 
			
		||||
     * @param value Data value.
 | 
			
		||||
     */
 | 
			
		||||
    async set(key: string, value: unknown): Promise<void> {
 | 
			
		||||
        await this.table.insert({ key, value: JSON.stringify(value) });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if value exists.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key Data key.
 | 
			
		||||
     * @returns Whether key exists or not.
 | 
			
		||||
     */
 | 
			
		||||
    async has(key: string): Promise<boolean> {
 | 
			
		||||
        return this.table.hasAny({ key });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key Data key.
 | 
			
		||||
     */
 | 
			
		||||
    async remove(key: string): Promise<void> {
 | 
			
		||||
        await this.table.deleteByPrimaryKey({ key });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the core_storage table of the current site.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns CoreStorageService instance with the core_storage table.
 | 
			
		||||
     */
 | 
			
		||||
    forCurrentSite(): AsyncInstance<Omit<CoreStorageService, 'forSite' | 'forCurrentSite'>> {
 | 
			
		||||
        return asyncInstance(async () => {
 | 
			
		||||
            const siteId = await CoreSites.getStoredCurrentSiteId();
 | 
			
		||||
            const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
            if (!(siteId in SERVICE_INSTANCES)) {
 | 
			
		||||
                SERVICE_INSTANCES[siteId] = asyncInstance(async () => {
 | 
			
		||||
                    const instance = new CoreStorageService();
 | 
			
		||||
                    await instance.initializeTable(site.getDb());
 | 
			
		||||
 | 
			
		||||
                    return instance;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await SERVICE_INSTANCES[siteId].getInstance();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the core_storage table for the provided site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param site Site from which we will obtain the storage.
 | 
			
		||||
     * @returns CoreStorageService instance with the core_storage table.
 | 
			
		||||
     */
 | 
			
		||||
    forSite(site: CoreSite): AsyncInstance<Omit<CoreStorageService, 'forSite' | 'forCurrentSite'>> {
 | 
			
		||||
        const siteId = site.getId();
 | 
			
		||||
 | 
			
		||||
        return asyncInstance(async () => {
 | 
			
		||||
            if (!(siteId in SERVICE_INSTANCES)) {
 | 
			
		||||
                const instance = new CoreStorageService();
 | 
			
		||||
                await instance.initializeTable(site.getDb());
 | 
			
		||||
 | 
			
		||||
                SERVICE_INSTANCES[siteId] = asyncInstance(() => instance);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await SERVICE_INSTANCES[siteId].getInstance();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CoreStorage = makeSingleton(CoreStorageService);
 | 
			
		||||
 | 
			
		||||
const SERVICE_INSTANCES: Record<string, AsyncInstance<CoreStorageService>> = {};
 | 
			
		||||
const TABLE_INSTANCES: WeakMap<SQLiteDB, Promise<CoreStorageTable>> = new WeakMap();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper function to get a storage table for the given database.
 | 
			
		||||
 *
 | 
			
		||||
 * @param database Database.
 | 
			
		||||
 * @returns Storage table.
 | 
			
		||||
 */
 | 
			
		||||
function getStorageTable(database: SQLiteDB): Promise<CoreStorageTable> {
 | 
			
		||||
    const existingTable = TABLE_INSTANCES.get(database);
 | 
			
		||||
 | 
			
		||||
    if (existingTable) {
 | 
			
		||||
        return existingTable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const table = new Promise<CoreStorageTable>((resolve, reject) => {
 | 
			
		||||
        const tableProxy = new CoreDatabaseTableProxy<CoreStorageRecord, 'key'>(
 | 
			
		||||
            { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
 | 
			
		||||
            database,
 | 
			
		||||
            TABLE_NAME,
 | 
			
		||||
            ['key'],
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        tableProxy.initialize()
 | 
			
		||||
            .then(() => resolve(tableProxy))
 | 
			
		||||
            .catch(reject);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    TABLE_INSTANCES.set(database, table);
 | 
			
		||||
 | 
			
		||||
    return table;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Storage table.
 | 
			
		||||
 */
 | 
			
		||||
type CoreStorageTable = CoreDatabaseTable<CoreStorageRecord, 'key'>;
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user