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-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-toolbar color="primary">
|
||||
<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 {
|
||||
AlertController,
|
||||
@ -23,6 +23,7 @@ import {NodeTypeIcons} from './structures/node-types';
|
||||
import {NavigationService} from './service/navigation.service';
|
||||
import {DatabaseService} from './service/db/database.service';
|
||||
import {EditorService} from './service/editor.service';
|
||||
import {debug} from './utility';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -88,7 +89,7 @@ export class AppComponent implements OnInit {
|
||||
private platform: Platform,
|
||||
private splashScreen: SplashScreen,
|
||||
private statusBar: StatusBar,
|
||||
private api: ApiService,
|
||||
public readonly api: ApiService,
|
||||
protected router: Router,
|
||||
protected alerts: AlertController,
|
||||
protected popover: PopoverController,
|
||||
@ -99,27 +100,7 @@ export class AppComponent implements OnInit {
|
||||
protected toasts: ToastController,
|
||||
protected db: DatabaseService,
|
||||
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() {
|
||||
if ( !this.showedNewVersionAlert && await this.session.newVersionAvailable() ) {
|
||||
@ -144,20 +125,8 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if ( !this.initialized$.getValue() ) {
|
||||
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
||||
this.onMenuRefresh(quiet);
|
||||
});
|
||||
|
||||
const sub = this.initialized$.subscribe((didInit) => {
|
||||
if (didInit) {
|
||||
this._doInit();
|
||||
sub.unsubscribe();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._doInit();
|
||||
}
|
||||
debug('Initializing application.');
|
||||
this.initializeApp();
|
||||
}
|
||||
|
||||
showOptions($event) {
|
||||
@ -262,7 +231,7 @@ export class AppComponent implements OnInit {
|
||||
});
|
||||
} else if ( result.data === 'export_html' ) {
|
||||
this.exportTargetAsHTML();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
await popover.present();
|
||||
@ -420,19 +389,24 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
async initializeApp() {
|
||||
console.log('app', this);
|
||||
debug('app', this);
|
||||
this.loader = await this.loading.create({
|
||||
message: 'Starting up...',
|
||||
cssClass: 'noded-loading-mask',
|
||||
showBackdrop: true,
|
||||
});
|
||||
|
||||
debug('Initializing platform and database...');
|
||||
await this.loader.present();
|
||||
await this.platform.ready();
|
||||
await this.db.createSchemata();
|
||||
|
||||
let toast: any;
|
||||
|
||||
debug('Subscribing to offline changes...');
|
||||
this.api.offline$.subscribe(async isOffline => {
|
||||
if ( isOffline && !this.showedOfflineAlert ) {
|
||||
debug('Application went offline!');
|
||||
toast = await this.toasts.create({
|
||||
cssClass: 'compat-toast-container',
|
||||
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;
|
||||
await toast.present();
|
||||
} else if ( !isOffline && this.showedOfflineAlert ) {
|
||||
debug('Appliation went online!');
|
||||
await toast.dismiss();
|
||||
this.showedOfflineAlert = false;
|
||||
await this.api.syncOfflineData();
|
||||
}
|
||||
});
|
||||
|
||||
debug('Getting initial status...');
|
||||
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 ) {
|
||||
debug('Trying to resume session...');
|
||||
await this.api.resumeSession();
|
||||
debug('Checking new status...');
|
||||
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 ) {
|
||||
debug('Not authenticated! Redirecting.');
|
||||
window.location.href = `${stat.system_base}start`;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
debug('Unauthenticated offline user. Purging local data!');
|
||||
await this.db.purge();
|
||||
window.location.href = `${stat.system_base}start`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug('Set app name and system base:', stat.app_name, stat.system_base);
|
||||
this.session.appName = stat.app_name;
|
||||
this.session.systemBase = stat.system_base;
|
||||
|
||||
debug('Initializing session...');
|
||||
await this.session.initialize();
|
||||
|
||||
if ( this.session.get('user.preferences.dark_mode') ) {
|
||||
this.toggleDark();
|
||||
}
|
||||
|
||||
debug('Hiding native splash screen & setting status bar styles...');
|
||||
await this.statusBar.styleDefault();
|
||||
await this.splashScreen.hide();
|
||||
|
||||
@ -495,11 +494,13 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
if ( this.isPrefetch() ) {
|
||||
this.loader.message = 'Downloading data...'; // TODO actually do the prefetch
|
||||
if ( this.isPrefetch() && !this.api.isPublicUser ) {
|
||||
debug('Pre-fetching offline data...');
|
||||
this.loader.message = 'Downloading data...';
|
||||
try {
|
||||
await this.api.prefetchOfflineData();
|
||||
} catch (e) {
|
||||
debug('Pre-fetch error:', e);
|
||||
this.toasts.create({
|
||||
cssClass: 'compat-toast-container',
|
||||
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);
|
||||
|
||||
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 node = this.findNode(id);
|
||||
if ( 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() {
|
||||
|
@ -8,12 +8,14 @@
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
|
||||
|
||||
<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-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-row>
|
||||
<ion-row align-items-center *ngIf="api.isPublicUser">
|
||||
Redirecting...
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
@ -1,15 +1,28 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../service/api.service';
|
||||
import {isDebug, debug} from '../utility';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: 'home.page.html',
|
||||
styleUrls: ['home.page.scss'],
|
||||
})
|
||||
export class HomePage {
|
||||
export class HomePage implements OnInit {
|
||||
|
||||
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 {Page} from './db/Page';
|
||||
import {PageNode} from './db/PageNode';
|
||||
import {debug} from '../utility';
|
||||
|
||||
export class ResourceNotAvailableOfflineError extends Error {
|
||||
constructor(msg = 'This resource is not yet available offline on this device.') {
|
||||
@ -30,6 +31,9 @@ export class OfflinePrefetchWouldOverwriteLocalDataError extends Error {
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
public isAuthenticated = false;
|
||||
public isPublicUser = false;
|
||||
public systemBase?: string;
|
||||
protected baseEndpoint: string = environment.backendBase;
|
||||
protected statUrl: string = environment.statUrl;
|
||||
protected versionUrl: string = environment.versionUrl;
|
||||
@ -149,6 +153,10 @@ export class ApiService {
|
||||
});
|
||||
}
|
||||
|
||||
public forceRestart() {
|
||||
window.location.href = `${this.systemBase || '/'}start`;
|
||||
}
|
||||
|
||||
public checkOnline(): Promise<boolean> {
|
||||
return new Promise(res => {
|
||||
fetch(this.statUrl).then(resp => {
|
||||
@ -170,6 +178,10 @@ export class ApiService {
|
||||
}
|
||||
|
||||
public async needsSync() {
|
||||
if ( !this.isAuthenticated || this.isPublicUser ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const offlineKV = await this.db.getKeyValue('needs_online_sync');
|
||||
return Boolean(offlineKV.data);
|
||||
}
|
||||
@ -456,6 +468,10 @@ export class ApiService {
|
||||
|
||||
public getDeviceToken(): Promise<string> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isPublicUser ) {
|
||||
return res();
|
||||
}
|
||||
|
||||
const tokenKV = await this.db.getKeyValue('device_token');
|
||||
if ( !tokenKV.data && this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
@ -483,6 +499,11 @@ export class ApiService {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( !this.isOffline ) {
|
||||
this.getDeviceToken().then(token => {
|
||||
debug('Got device token:', token);
|
||||
if ( !token ) {
|
||||
return res();
|
||||
}
|
||||
|
||||
this.post(`/session/resume/${token}`).subscribe({
|
||||
next: result => {
|
||||
res();
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {environment} from '../environments/environment';
|
||||
|
||||
export function uuid_v4() {
|
||||
// @ts-ignore
|
||||
@ -17,3 +18,29 @@ export function debounce(func: (...args: any[]) => any, timeout?: number) {
|
||||
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',
|
||||
statUrl: '/stat?ngsw-bypass',
|
||||
versionUrl: '/i/version.html?ngsw-bypass',
|
||||
outputDebug: false,
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ export const environment = {
|
||||
backendBase: '/link_api/api/v1',
|
||||
statUrl: '/link_api/stat?ngsw-bypass',
|
||||
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
||||
outputDebug: true,
|
||||
};
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user