Refactor application initialization & allow public user load
This commit is contained in:
parent
556806ea69
commit
a5dc7f7a19
@ -1,6 +1,6 @@
|
|||||||
<ion-app class="dark">
|
<ion-app class="dark">
|
||||||
<ion-split-pane contentId="main-content" *ngIf="ready$ | async">
|
<ion-split-pane contentId="main-content" *ngIf="ready$ | async">
|
||||||
<ion-menu class="sidebar" contentId="main-content" content="content" type="push" side="start">
|
<ion-menu class="sidebar" contentId="main-content" content="content" type="push" side="start" *ngIf="!api.isPublicUser">
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="primary">
|
||||||
<ion-title style="font-weight: bold; color: white;">{{ appName }}
|
<ion-title style="font-weight: bold; color: white;">{{ appName }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild, HostListener, Host} from '@angular/core';
|
import {Component, OnInit, ViewChild, HostListener, Host} from '@angular/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AlertController,
|
AlertController,
|
||||||
@ -23,6 +23,7 @@ import {NodeTypeIcons} from './structures/node-types';
|
|||||||
import {NavigationService} from './service/navigation.service';
|
import {NavigationService} from './service/navigation.service';
|
||||||
import {DatabaseService} from './service/db/database.service';
|
import {DatabaseService} from './service/db/database.service';
|
||||||
import {EditorService} from './service/editor.service';
|
import {EditorService} from './service/editor.service';
|
||||||
|
import {debug} from './utility';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -88,7 +89,7 @@ export class AppComponent implements OnInit {
|
|||||||
private platform: Platform,
|
private platform: Platform,
|
||||||
private splashScreen: SplashScreen,
|
private splashScreen: SplashScreen,
|
||||||
private statusBar: StatusBar,
|
private statusBar: StatusBar,
|
||||||
private api: ApiService,
|
public readonly api: ApiService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected alerts: AlertController,
|
protected alerts: AlertController,
|
||||||
protected popover: PopoverController,
|
protected popover: PopoverController,
|
||||||
@ -99,27 +100,7 @@ export class AppComponent implements OnInit {
|
|||||||
protected toasts: ToastController,
|
protected toasts: ToastController,
|
||||||
protected db: DatabaseService,
|
protected db: DatabaseService,
|
||||||
protected editor: EditorService,
|
protected editor: EditorService,
|
||||||
) {
|
) { }
|
||||||
this.initializeApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _doInit() {
|
|
||||||
await this.db.createSchemata();
|
|
||||||
|
|
||||||
this.reloadMenuItems().subscribe(() => {
|
|
||||||
this.ready$.next(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loader.dismiss();
|
|
||||||
this.menuTree.treeModel.expandAll();
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
if ( !this.versionInterval ) {
|
|
||||||
this.versionInterval = setInterval(() => {
|
|
||||||
this.checkNewVersion();
|
|
||||||
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkNewVersion() {
|
async checkNewVersion() {
|
||||||
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
||||||
@ -144,20 +125,8 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if ( !this.initialized$.getValue() ) {
|
debug('Initializing application.');
|
||||||
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
this.initializeApp();
|
||||||
this.onMenuRefresh(quiet);
|
|
||||||
});
|
|
||||||
|
|
||||||
const sub = this.initialized$.subscribe((didInit) => {
|
|
||||||
if (didInit) {
|
|
||||||
this._doInit();
|
|
||||||
sub.unsubscribe();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._doInit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showOptions($event) {
|
showOptions($event) {
|
||||||
@ -262,7 +231,7 @@ export class AppComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
} else if ( result.data === 'export_html' ) {
|
} else if ( result.data === 'export_html' ) {
|
||||||
this.exportTargetAsHTML();
|
this.exportTargetAsHTML();
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await popover.present();
|
await popover.present();
|
||||||
@ -420,19 +389,24 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initializeApp() {
|
async initializeApp() {
|
||||||
console.log('app', this);
|
debug('app', this);
|
||||||
this.loader = await this.loading.create({
|
this.loader = await this.loading.create({
|
||||||
message: 'Starting up...',
|
message: 'Starting up...',
|
||||||
cssClass: 'noded-loading-mask',
|
cssClass: 'noded-loading-mask',
|
||||||
showBackdrop: true,
|
showBackdrop: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debug('Initializing platform and database...');
|
||||||
await this.loader.present();
|
await this.loader.present();
|
||||||
await this.platform.ready();
|
await this.platform.ready();
|
||||||
|
await this.db.createSchemata();
|
||||||
|
|
||||||
let toast: any;
|
let toast: any;
|
||||||
|
|
||||||
|
debug('Subscribing to offline changes...');
|
||||||
this.api.offline$.subscribe(async isOffline => {
|
this.api.offline$.subscribe(async isOffline => {
|
||||||
if ( isOffline && !this.showedOfflineAlert ) {
|
if ( isOffline && !this.showedOfflineAlert ) {
|
||||||
|
debug('Application went offline!');
|
||||||
toast = await this.toasts.create({
|
toast = await this.toasts.create({
|
||||||
cssClass: 'compat-toast-container',
|
cssClass: 'compat-toast-container',
|
||||||
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
|
message: 'Uh, oh! It looks like you\'re offline. Some features might not work as expected...',
|
||||||
@ -441,39 +415,64 @@ export class AppComponent implements OnInit {
|
|||||||
this.showedOfflineAlert = true;
|
this.showedOfflineAlert = true;
|
||||||
await toast.present();
|
await toast.present();
|
||||||
} else if ( !isOffline && this.showedOfflineAlert ) {
|
} else if ( !isOffline && this.showedOfflineAlert ) {
|
||||||
|
debug('Appliation went online!');
|
||||||
await toast.dismiss();
|
await toast.dismiss();
|
||||||
this.showedOfflineAlert = false;
|
this.showedOfflineAlert = false;
|
||||||
await this.api.syncOfflineData();
|
await this.api.syncOfflineData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debug('Getting initial status...');
|
||||||
let stat: any = await this.session.stat();
|
let stat: any = await this.session.stat();
|
||||||
|
debug('Got stat:', stat);
|
||||||
|
|
||||||
if ( !stat.authenticated_user ) {
|
if ( stat.public_user ) {
|
||||||
|
this.api.isPublicUser = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( stat.authenticated_user ) {
|
||||||
|
this.api.isAuthenticated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.systemBase = stat.system_base;
|
||||||
|
|
||||||
|
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
||||||
|
debug('Unauthenticated or public user...');
|
||||||
if ( !this.api.isOffline ) {
|
if ( !this.api.isOffline ) {
|
||||||
|
debug('Trying to resume session...');
|
||||||
await this.api.resumeSession();
|
await this.api.resumeSession();
|
||||||
|
debug('Checking new status...');
|
||||||
stat = await this.session.stat();
|
stat = await this.session.stat();
|
||||||
|
debug('Got session resume stat:', stat);
|
||||||
|
|
||||||
|
this.api.isAuthenticated = stat.authenticated_user;
|
||||||
|
this.api.isPublicUser = stat.public_user;
|
||||||
|
|
||||||
if ( !stat.authenticated_user ) {
|
if ( !stat.authenticated_user ) {
|
||||||
|
debug('Not authenticated! Redirecting.');
|
||||||
window.location.href = `${stat.system_base}start`;
|
window.location.href = `${stat.system_base}start`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
debug('Unauthenticated offline user. Purging local data!');
|
||||||
await this.db.purge();
|
await this.db.purge();
|
||||||
window.location.href = `${stat.system_base}start`;
|
window.location.href = `${stat.system_base}start`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug('Set app name and system base:', stat.app_name, stat.system_base);
|
||||||
this.session.appName = stat.app_name;
|
this.session.appName = stat.app_name;
|
||||||
this.session.systemBase = stat.system_base;
|
this.session.systemBase = stat.system_base;
|
||||||
|
|
||||||
|
debug('Initializing session...');
|
||||||
await this.session.initialize();
|
await this.session.initialize();
|
||||||
|
|
||||||
if ( this.session.get('user.preferences.dark_mode') ) {
|
if ( this.session.get('user.preferences.dark_mode') ) {
|
||||||
this.toggleDark();
|
this.toggleDark();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug('Hiding native splash screen & setting status bar styles...');
|
||||||
await this.statusBar.styleDefault();
|
await this.statusBar.styleDefault();
|
||||||
await this.splashScreen.hide();
|
await this.splashScreen.hide();
|
||||||
|
|
||||||
@ -495,11 +494,13 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.isPrefetch() ) {
|
if ( this.isPrefetch() && !this.api.isPublicUser ) {
|
||||||
this.loader.message = 'Downloading data...'; // TODO actually do the prefetch
|
debug('Pre-fetching offline data...');
|
||||||
|
this.loader.message = 'Downloading data...';
|
||||||
try {
|
try {
|
||||||
await this.api.prefetchOfflineData();
|
await this.api.prefetchOfflineData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
debug('Pre-fetch error:', e);
|
||||||
this.toasts.create({
|
this.toasts.create({
|
||||||
cssClass: 'compat-toast-container',
|
cssClass: 'compat-toast-container',
|
||||||
message: 'An error occurred while pre-fetching offline data. Not all data was saved.',
|
message: 'An error occurred while pre-fetching offline data. Not all data was saved.',
|
||||||
@ -514,13 +515,37 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
this.initialized$.next(true);
|
this.initialized$.next(true);
|
||||||
|
|
||||||
if ( this.session.get('user.preferences.default_page') ) {
|
if ( !this.api.isPublicUser && this.session.get('user.preferences.default_page') ) {
|
||||||
|
debug('Navigating to default page!');
|
||||||
const id = this.session.get('user.preferences.default_page');
|
const id = this.session.get('user.preferences.default_page');
|
||||||
const node = this.findNode(id);
|
const node = this.findNode(id);
|
||||||
if ( node ) {
|
if ( node ) {
|
||||||
this.navigateEditorToNode(node);
|
this.navigateEditorToNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug('Creating menu subscription...');
|
||||||
|
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
||||||
|
this.onMenuRefresh(quiet);
|
||||||
|
});
|
||||||
|
|
||||||
|
debug('Reloading menu items...');
|
||||||
|
this.reloadMenuItems().subscribe(() => {
|
||||||
|
debug('Reloaded menu items. Displaying interface.');
|
||||||
|
this.ready$.next(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loader.dismiss();
|
||||||
|
this.menuTree?.treeModel?.expandAll();
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
if ( !this.versionInterval ) {
|
||||||
|
this.versionInterval = setInterval(() => {
|
||||||
|
debug('Checking for new application version.');
|
||||||
|
this.checkNewVersion();
|
||||||
|
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async doPrefetch() {
|
async doPrefetch() {
|
||||||
|
@ -8,12 +8,14 @@
|
|||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|
||||||
|
|
||||||
<ion-grid style="height: 100%; justify-content: center; display: flex; flex-direction: column; font-size: 24pt; color: #ccc;">
|
<ion-grid style="height: 100%; justify-content: center; display: flex; flex-direction: column; font-size: 24pt; color: #ccc;">
|
||||||
<ion-row align-items-center>
|
<ion-row align-items-center *ngIf="!api.isPublicUser">
|
||||||
<ion-col>Hi, there! Select or create a page to get started.</ion-col>
|
<ion-col>Hi, there! Select or create a page to get started.</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
<ion-row align-items-center style="margin-top: 30px;">
|
<ion-row align-items-center style="margin-top: 30px;" *ngIf="!api.isPublicUser">
|
||||||
<ion-col>(You can press <code>Ctrl</code> + <code>/</code> to search everywhere.)</ion-col>
|
<ion-col>(You can press <code>Ctrl</code> + <code>/</code> to search everywhere.)</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
|
<ion-row align-items-center *ngIf="api.isPublicUser">
|
||||||
|
Redirecting...
|
||||||
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {ApiService} from '../service/api.service';
|
import {ApiService} from '../service/api.service';
|
||||||
|
import {isDebug, debug} from '../utility';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
templateUrl: 'home.page.html',
|
templateUrl: 'home.page.html',
|
||||||
styleUrls: ['home.page.scss'],
|
styleUrls: ['home.page.scss'],
|
||||||
})
|
})
|
||||||
export class HomePage {
|
export class HomePage implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected api: ApiService,
|
public readonly api: ApiService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
||||||
|
if ( isDebug() ) {
|
||||||
|
debug('Forcing authentication...');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.api.forceRestart();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
this.api.forceRestart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import {DatabaseEntry} from './db/DatabaseEntry';
|
|||||||
import {FileGroup} from './db/FileGroup';
|
import {FileGroup} from './db/FileGroup';
|
||||||
import {Page} from './db/Page';
|
import {Page} from './db/Page';
|
||||||
import {PageNode} from './db/PageNode';
|
import {PageNode} from './db/PageNode';
|
||||||
|
import {debug} from '../utility';
|
||||||
|
|
||||||
export class ResourceNotAvailableOfflineError extends Error {
|
export class ResourceNotAvailableOfflineError extends Error {
|
||||||
constructor(msg = 'This resource is not yet available offline on this device.') {
|
constructor(msg = 'This resource is not yet available offline on this device.') {
|
||||||
@ -30,6 +31,9 @@ export class OfflinePrefetchWouldOverwriteLocalDataError extends Error {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
|
public isAuthenticated = false;
|
||||||
|
public isPublicUser = false;
|
||||||
|
public systemBase?: string;
|
||||||
protected baseEndpoint: string = environment.backendBase;
|
protected baseEndpoint: string = environment.backendBase;
|
||||||
protected statUrl: string = environment.statUrl;
|
protected statUrl: string = environment.statUrl;
|
||||||
protected versionUrl: string = environment.versionUrl;
|
protected versionUrl: string = environment.versionUrl;
|
||||||
@ -149,6 +153,10 @@ export class ApiService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public forceRestart() {
|
||||||
|
window.location.href = `${this.systemBase || '/'}start`;
|
||||||
|
}
|
||||||
|
|
||||||
public checkOnline(): Promise<boolean> {
|
public checkOnline(): Promise<boolean> {
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
fetch(this.statUrl).then(resp => {
|
fetch(this.statUrl).then(resp => {
|
||||||
@ -170,6 +178,10 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async needsSync() {
|
public async needsSync() {
|
||||||
|
if ( !this.isAuthenticated || this.isPublicUser ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const offlineKV = await this.db.getKeyValue('needs_online_sync');
|
const offlineKV = await this.db.getKeyValue('needs_online_sync');
|
||||||
return Boolean(offlineKV.data);
|
return Boolean(offlineKV.data);
|
||||||
}
|
}
|
||||||
@ -456,6 +468,10 @@ export class ApiService {
|
|||||||
|
|
||||||
public getDeviceToken(): Promise<string> {
|
public getDeviceToken(): Promise<string> {
|
||||||
return new Promise(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
|
if ( this.isPublicUser ) {
|
||||||
|
return res();
|
||||||
|
}
|
||||||
|
|
||||||
const tokenKV = await this.db.getKeyValue('device_token');
|
const tokenKV = await this.db.getKeyValue('device_token');
|
||||||
if ( !tokenKV.data && this.isOffline ) {
|
if ( !tokenKV.data && this.isOffline ) {
|
||||||
return rej(new ResourceNotAvailableOfflineError());
|
return rej(new ResourceNotAvailableOfflineError());
|
||||||
@ -483,6 +499,11 @@ export class ApiService {
|
|||||||
return new Promise(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
if ( !this.isOffline ) {
|
if ( !this.isOffline ) {
|
||||||
this.getDeviceToken().then(token => {
|
this.getDeviceToken().then(token => {
|
||||||
|
debug('Got device token:', token);
|
||||||
|
if ( !token ) {
|
||||||
|
return res();
|
||||||
|
}
|
||||||
|
|
||||||
this.post(`/session/resume/${token}`).subscribe({
|
this.post(`/session/resume/${token}`).subscribe({
|
||||||
next: result => {
|
next: result => {
|
||||||
res();
|
res();
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import {environment} from '../environments/environment';
|
||||||
|
|
||||||
export function uuid_v4() {
|
export function uuid_v4() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -17,3 +18,29 @@ export function debounce(func: (...args: any[]) => any, timeout?: number) {
|
|||||||
timer = setTimeout(next, timeout > 0 ? timeout : 300);
|
timer = setTimeout(next, timeout > 0 ? timeout : 300);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function debugRun(closure: any) {
|
||||||
|
if ( typeof closure === 'function' ) {
|
||||||
|
return closure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDebug() {
|
||||||
|
return environment.outputDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debug(...out: any[]) {
|
||||||
|
const caller = (new Error())?.stack?.split('\n')?.[1]?.split('@')?.[0]?.replace(/[\/\\<]/g, '');
|
||||||
|
|
||||||
|
// Define different types of styles
|
||||||
|
const baseStyles = [
|
||||||
|
'color: #fff',
|
||||||
|
'background-color: #449',
|
||||||
|
'padding: 2px 4px',
|
||||||
|
'border-radius: 2px'
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
if ( environment.outputDebug ) {
|
||||||
|
console.log(`%c${caller}`, baseStyles, ...out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,4 +3,5 @@ export const environment = {
|
|||||||
backendBase: '/api/v1',
|
backendBase: '/api/v1',
|
||||||
statUrl: '/stat?ngsw-bypass',
|
statUrl: '/stat?ngsw-bypass',
|
||||||
versionUrl: '/i/version.html?ngsw-bypass',
|
versionUrl: '/i/version.html?ngsw-bypass',
|
||||||
|
outputDebug: false,
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ export const environment = {
|
|||||||
backendBase: '/link_api/api/v1',
|
backendBase: '/link_api/api/v1',
|
||||||
statUrl: '/link_api/stat?ngsw-bypass',
|
statUrl: '/link_api/stat?ngsw-bypass',
|
||||||
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
||||||
|
outputDebug: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
Reference in New Issue
Block a user