From 48a7f409929de9d22941b2f9c93ca3149023cb8d Mon Sep 17 00:00:00 2001 From: Josef Date: Tue, 25 Nov 2025 15:07:19 +0100 Subject: [PATCH] Sync User System --- apps/administration/AdministrationApp.ts | 12 + apps/administration/ui/admin-button.html | 3 + apps/administration/ui/admin-ui.html | 15 ++ apps/administration/ui/admin-ui.ts | 6 + browser/dom/DOMElement.ts | 39 ++++ browser/users/UserApp.ts | 13 ++ browser/users/UserSystem.ts | 286 +++++++++++++++++++++++ browser/xhttp/Request.ts | 40 ++++ 8 files changed, 414 insertions(+) create mode 100644 apps/administration/AdministrationApp.ts create mode 100644 apps/administration/ui/admin-button.html create mode 100644 apps/administration/ui/admin-ui.html create mode 100644 apps/administration/ui/admin-ui.ts create mode 100644 browser/dom/DOMElement.ts create mode 100644 browser/users/UserApp.ts create mode 100644 browser/users/UserSystem.ts diff --git a/apps/administration/AdministrationApp.ts b/apps/administration/AdministrationApp.ts new file mode 100644 index 0000000..e17a651 --- /dev/null +++ b/apps/administration/AdministrationApp.ts @@ -0,0 +1,12 @@ +import { UserApp } from "../../browser/users/UserApp"; +import { UserSystem } from "../../browser/users/UserSystem"; + +export class AdministrationApp extends UserApp +{ + static readonly ID = "administration"; + + constructor( userSystem:UserSystem ) + { + super( userSystem, AdministrationApp.ID ); + } +} \ No newline at end of file diff --git a/apps/administration/ui/admin-button.html b/apps/administration/ui/admin-button.html new file mode 100644 index 0000000..8cd1ce5 --- /dev/null +++ b/apps/administration/ui/admin-button.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/administration/ui/admin-ui.html b/apps/administration/ui/admin-ui.html new file mode 100644 index 0000000..4d3d4c3 --- /dev/null +++ b/apps/administration/ui/admin-ui.html @@ -0,0 +1,15 @@ + + + + + Hello World! + + \ No newline at end of file diff --git a/apps/administration/ui/admin-ui.ts b/apps/administration/ui/admin-ui.ts new file mode 100644 index 0000000..1af3d24 --- /dev/null +++ b/apps/administration/ui/admin-ui.ts @@ -0,0 +1,6 @@ +import { DOMElement } from "../../../browser/dom/DOMElement"; + +export class AdminUI extends DOMElement +{ + +} \ No newline at end of file diff --git a/browser/dom/DOMElement.ts b/browser/dom/DOMElement.ts new file mode 100644 index 0000000..4405750 --- /dev/null +++ b/browser/dom/DOMElement.ts @@ -0,0 +1,39 @@ +import { ElementType } from "./ElementType"; +import { MouseEventCallback, OnClick } from "./EventListeners"; + + +export class DomElementConstructorType +{ + type:ElementType; +} + +export class DOMElement +{ + _element:T; + get element(){ return this._element; } + + constructor( e:T ) + { + this._element = e; + } + + get( constructorType:DomElementConstructorType ) + { + let element = constructorType.type.query( this._element ); + + return new ( constructorType as any )( element ); + } + + getAll( constructorType:DomElementConstructorType ) + { + let elements = constructorType.type.queryAll( this._element ); + + return elements.map( e => new ( constructorType as any )( e ) ); + } + + + onClick( action:MouseEventCallback, options?: any ) + { + OnClick.add( this.element as any as HTMLElement, action, options ); + } +} \ No newline at end of file diff --git a/browser/users/UserApp.ts b/browser/users/UserApp.ts new file mode 100644 index 0000000..260a89d --- /dev/null +++ b/browser/users/UserApp.ts @@ -0,0 +1,13 @@ +import { UserSystem } from "./UserSystem"; + +export class UserApp +{ + protected _userSystem:UserSystem; + protected _appID:string; + + constructor( userSystem:UserSystem, appID:string ) + { + this._userSystem = userSystem; + this._appID = appID; + } +} \ No newline at end of file diff --git a/browser/users/UserSystem.ts b/browser/users/UserSystem.ts new file mode 100644 index 0000000..647514f --- /dev/null +++ b/browser/users/UserSystem.ts @@ -0,0 +1,286 @@ +import { Property } from "../events/Property"; +import { DataMessage } from "../messages/DataMessage"; +import { Message } from "../messages/Message"; +import { MessageTypes } from "../messages/MessageType"; +import { Request } from "../xhttp/Request"; + +export class EmailData +{ + email:string; +} + +export class EmailPasswordData extends EmailData +{ + password:string; +} + +export class TokenData +{ + token:string; +} + +export class PageTokenData +{ + pageToken:string; +} + +export class LinkUserData extends PageTokenData +{ + userToken:string; + renewal:string; +} + +export class LocationData +{ + country:string; + city:string; + km_range:number; +} + +export class UserLoginDataInfo +{ + time:string; + location:LocationData; + app:string; + os:string; + deviceType:string; +} + +export class UserData +{ + email:string; + id:string; + name:string; + lastLogins:UserLoginDataInfo[]; +} + +export class SessionData +{ + token:string; + renewal:string; +} + +export class UserSystem +{ + static productionURL = "https://backend.rokojori.com:1712"; + static localURL = "http://192.168.178.51:1712"; + + _isLocal = false; + _loggedIn = false; + _userData:UserData; + + readonly userID = new Property( null ); + + _sessionData:SessionData; + + get sessionToken(){ return this._sessionData?.token || null }; + + get isLoggedIn() + { + return this._loggedIn; + } + + get userName() + { + return this._userData?.name || null; + } + + get url() + { + return this._isLocal ? UserSystem.localURL : UserSystem.productionURL; + } + + + constructor( isLocal:boolean ) + { + this._isLocal = isLocal; + } + + + async renew():Promise + { + if ( ! this._sessionData ) + { + return Promise.resolve(); + } + + let functionURL = this.url + `/renew`; + + let oldSessionData = this._sessionData; + + try + { + + let result = await Request.post>( functionURL, oldSessionData ); + + if ( MessageTypes.Error === result.type ) + { + this._sessionData = null; + } + else + { + this._sessionData = result.data; + this._userData = await this.info(); + } + + } + catch ( e ) + { + console.error( e ); + } + + + return Promise.resolve(); + } + + + + async confirmSignup( id:string, token:string ):Promise + { + let functionURL = this.url + `/confirm-signup?id=${id}&token=${token}` ; + + try + { + let result = await Request.get( functionURL ); + + return Promise.resolve( result.type !== MessageTypes.Error ); + } + catch ( e ) + { + console.error( e ); + } + + + return Promise.resolve( false ); + } + + async signup( email:string, password:string ):Promise + { + let functionURL = this.url + "/signup"; + + let input:EmailPasswordData = + { + email: email, + password: password + }; + + try + { + let result = await Request.post( functionURL, input ); + + return Promise.resolve( result.type !== MessageTypes.Error ); + } + catch ( e ) + { + console.error( e ); + } + + + return Promise.resolve( false ); + } + + async info():Promise + { + if ( this._sessionData === null ) + { + return Promise.resolve( null ); + } + + let functionURL = this.url + "/info"; + + try + { + let result = await Request.post>( functionURL, { token: this.sessionToken } ); + return Promise.resolve( result.data ); + } + catch( e ) + { + return Promise.resolve( null ); + } + } + + + async login( email:string, password:string, debugOutput:string[] = undefined ):Promise + { + let functionURL = this.url + "/login"; + + let input:EmailPasswordData = + { + email: email, + password: password + }; + + try + { + let result = await Request.post>( functionURL, input ); + + if ( debugOutput ) + { + debugOutput.push( JSON.stringify( result ) ); + } + + + if ( result.type === MessageTypes.Error ) + { + return Promise.resolve( false ); + } + + console.log( "Login Result:", result ); + + + this._sessionData = result.data; + + let userData = await this.info(); + + console.log( "userData Result:", userData ); + + this._loggedIn = true; + this._userData = userData; + this.userID.value = this._userData.id; + + return Promise.resolve( true ); + } + catch ( e ) + { + console.error( e ); + + if ( debugOutput ) + { + debugOutput.push( JSON.stringify( e.message ) ); + } + } + + + return Promise.resolve( false ); + } + + async logout():Promise + { + if ( ! this._loggedIn ) + { + return; + } + + let functionURL = this.url + "/logout"; + + await Request.post>( functionURL, { token: this.sessionToken } ); + + this._loggedIn = false; + this._sessionData = null; + this._userData = null; + + this.userID.value = null; + + return Promise.resolve(); + + } + + async requestPasswordChange( email:string ):Promise + { + let functionURL = this.url + "/request-password-change"; + + let result = await Request.post( functionURL, { email: email } ); + + return Promise.resolve( MessageTypes.Error !== result.type ); + } +} \ No newline at end of file diff --git a/browser/xhttp/Request.ts b/browser/xhttp/Request.ts index 2eb17d9..0472efe 100644 --- a/browser/xhttp/Request.ts +++ b/browser/xhttp/Request.ts @@ -1,5 +1,45 @@ export class Request { + static get( url:string ):Promise + { + let promise = new Promise + ( + ( resolve, reject ) => + { + let xhr = new XMLHttpRequest(); + + console.log( "get", url ); + xhr.open( "GET", url, true ); + xhr.responseType = "text"; + + xhr.onload= + () => + { + console.log( xhr.responseURL, xhr.responseText ); + + if ( xhr.status !== 200 || xhr.responseText.startsWith( "ERROR:" ) ) + { + reject( xhr.responseText ) + } + else + { + resolve( JSON.parse( xhr.responseText ) as O ); + } + + }; + + xhr.onerror=(e)=> + { + reject( e ); + } + + xhr.send(); + } + ); + + return promise; + } + static post( url:string, input:I ):Promise { let promise = new Promise