Sync User System

This commit is contained in:
Josef 2025-11-25 15:07:19 +01:00
parent be4d6d9392
commit 48a7f40992
8 changed files with 414 additions and 0 deletions

View File

@ -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 );
}
}

View File

@ -0,0 +1,3 @@
<admin-button>
<button>@{}</button>
</admin-button>

View File

@ -0,0 +1,15 @@
<style>
admin-ui
{
display:block;
background:red;
}
</style>
<admin-ui>
<admin-button>
Hello World!
</admin-button>
</admin-ui>

View File

@ -0,0 +1,6 @@
import { DOMElement } from "../../../browser/dom/DOMElement";
export class AdminUI extends DOMElement<HTMLElement>
{
}

39
browser/dom/DOMElement.ts Normal file
View File

@ -0,0 +1,39 @@
import { ElementType } from "./ElementType";
import { MouseEventCallback, OnClick } from "./EventListeners";
export class DomElementConstructorType
{
type:ElementType<any>;
}
export class DOMElement<T extends Element>
{
_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 );
}
}

13
browser/users/UserApp.ts Normal file
View File

@ -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;
}
}

286
browser/users/UserSystem.ts Normal file
View File

@ -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<string>( 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<void>
{
if ( ! this._sessionData )
{
return Promise.resolve();
}
let functionURL = this.url + `/renew`;
let oldSessionData = this._sessionData;
try
{
let result = await Request.post<SessionData,DataMessage<SessionData>>( 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<boolean>
{
let functionURL = this.url + `/confirm-signup?id=${id}&token=${token}` ;
try
{
let result = await Request.get<Message>( functionURL );
return Promise.resolve( result.type !== MessageTypes.Error );
}
catch ( e )
{
console.error( e );
}
return Promise.resolve( false );
}
async signup( email:string, password:string ):Promise<boolean>
{
let functionURL = this.url + "/signup";
let input:EmailPasswordData =
{
email: email,
password: password
};
try
{
let result = await Request.post<EmailPasswordData,Message>( functionURL, input );
return Promise.resolve( result.type !== MessageTypes.Error );
}
catch ( e )
{
console.error( e );
}
return Promise.resolve( false );
}
async info():Promise<UserData>
{
if ( this._sessionData === null )
{
return Promise.resolve( null );
}
let functionURL = this.url + "/info";
try
{
let result = await Request.post<TokenData,DataMessage<UserData>>( 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<boolean>
{
let functionURL = this.url + "/login";
let input:EmailPasswordData =
{
email: email,
password: password
};
try
{
let result = await Request.post<EmailPasswordData,DataMessage<SessionData>>( 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<void>
{
if ( ! this._loggedIn )
{
return;
}
let functionURL = this.url + "/logout";
await Request.post<TokenData,DataMessage<TokenData>>( 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<boolean>
{
let functionURL = this.url + "/request-password-change";
let result = await Request.post<EmailData,Message>( functionURL, { email: email } );
return Promise.resolve( MessageTypes.Error !== result.type );
}
}

View File

@ -1,5 +1,45 @@
export class Request
{
static get<O>( url:string ):Promise<O>
{
let promise = new Promise<O>
(
( 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<I,O>( url:string, input:I ):Promise<O>
{
let promise = new Promise<O>