Update dependencies, refactor authentication, and enhance UI components
- Upgraded @reduxjs/toolkit to version 2.9.0 and added new dependencies including @techniker-me/pcast-api and moment. - Refactored authentication logic and added middleware for improved request handling. - Introduced new UI components such as buttons, loaders, and forms, along with a theme system following SOLID principles. - Updated routing to include protected routes and improved the login form with better error handling. - Removed unused CSS and organized the project structure for better maintainability.
This commit is contained in:
41
src/services/PCastApi.service.ts
Normal file
41
src/services/PCastApi.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {ApplicationCredentials, Channels, PCastApi, Reporting, Streams} from '@techniker-me/pcast-api';
|
||||
|
||||
export default class PCastApiService {
|
||||
private static _instance: PCastApi;
|
||||
|
||||
public static initialize(pcastUri: string, applciationCredentials: ApplicationCredentials) {
|
||||
PCastApiService._instance = PCastApi.create(pcastUri, applciationCredentials);
|
||||
}
|
||||
|
||||
public static getInstance(): PCastApiService {
|
||||
if (!PCastApiService._instance) {
|
||||
throw new Error('PCastApiService has not been initialized');
|
||||
}
|
||||
|
||||
return PCastApiService._instance;
|
||||
}
|
||||
|
||||
static get channels(): Channels {
|
||||
if (!PCastApiService._instance) {
|
||||
throw new Error('PCastApiService has not been initialized');
|
||||
}
|
||||
|
||||
return PCastApiService._instance.channels;
|
||||
}
|
||||
|
||||
static get streams(): Streams {
|
||||
if (!PCastApiService._instance) {
|
||||
throw new Error('PCastApiService has not been initialized');
|
||||
}
|
||||
|
||||
return PCastApiService._instance.streams;
|
||||
}
|
||||
|
||||
static get reporting(): Reporting {
|
||||
if (!PCastApiService._instance) {
|
||||
throw new Error('PCastApiService has not been initialized');
|
||||
}
|
||||
|
||||
return PCastApiService._instance.reporting;
|
||||
}
|
||||
}
|
||||
6
src/services/UserDataStore/IUserDataStore.ts
Normal file
6
src/services/UserDataStore/IUserDataStore.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default interface IUserDataStore {
|
||||
setItem(key: string, value: string): void;
|
||||
getItem(key: string): string | null;
|
||||
removeItem(key: string): void;
|
||||
clear(): void;
|
||||
}
|
||||
23
src/services/UserDataStore/IndexedDB.ts
Normal file
23
src/services/UserDataStore/IndexedDB.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import IUserDataStore from './IUserDataStore';
|
||||
|
||||
export class IndexedDB implements IUserDataStore {
|
||||
static isSupported(): boolean {
|
||||
return 'indexedDB' in window;
|
||||
}
|
||||
|
||||
public getItem(key: string): string | null {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public removeItem(key: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
}
|
||||
23
src/services/UserDataStore/LocalStorage.ts
Normal file
23
src/services/UserDataStore/LocalStorage.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import IUserDataStore from './IUserDataStore';
|
||||
|
||||
export class LocalStorage implements IUserDataStore {
|
||||
static isSupported(): boolean {
|
||||
return 'localStorage' in window;
|
||||
}
|
||||
|
||||
public getItem(key: string): string | null {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public removeItem(key: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
}
|
||||
23
src/services/UserDataStore/ObjectStore.ts
Normal file
23
src/services/UserDataStore/ObjectStore.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import IUserDataStore from './IUserDataStore';
|
||||
|
||||
export class ObjectStrore implements IUserDataStore {
|
||||
static isSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public getItem(key: string): string | null {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public setItem(key: string, value: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public removeItem(key: string): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
throw new Error('Not Implemented');
|
||||
}
|
||||
}
|
||||
24
src/services/UserDataStore/index.ts
Normal file
24
src/services/UserDataStore/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import IUserDataStore from './IUserDataStore';
|
||||
import {IndexedDB} from './IndexedDB';
|
||||
import {LocalStorage} from './LocalStorage';
|
||||
import {ObjectStrore} from './ObjectStore';
|
||||
|
||||
class UserDataStoreService {
|
||||
private static _instance: IUserDataStore;
|
||||
|
||||
static {
|
||||
if (IndexedDB.isSupported()) {
|
||||
this._instance = new IndexedDB();
|
||||
} else if (LocalStorage.isSupported()) {
|
||||
this._instance = new LocalStorage();
|
||||
} else {
|
||||
this._instance = new ObjectStrore();
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): IUserDataStore {
|
||||
return this._instance;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserDataStoreService.getInstance();
|
||||
@@ -20,6 +20,7 @@ export interface IPhenixWebSocketResponse {
|
||||
sessionId: string;
|
||||
redirect: string;
|
||||
roles: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export class PhenixWebSocket extends MQWebSocket {
|
||||
@@ -60,7 +61,9 @@ export class PhenixWebSocket extends MQWebSocket {
|
||||
|
||||
public async sendMessage<T>(kind: PhenixWebSocketMessage, message: T): Promise<IPhenixWebSocketResponse> {
|
||||
if (this._status.value !== PhenixWebSocketStatus.Online) {
|
||||
throw new Error(`Unable to send message, web socket is not [Online] WebSocket status [${PhenixWebSocketStatusMapping.convertPhenixWebSocketStatusToPhenixWebSocketStatusType(this._status.value)}]`);
|
||||
throw new Error(
|
||||
`Unable to send message, web socket is not [Online] WebSocket status [${PhenixWebSocketStatusMapping.convertPhenixWebSocketStatusToPhenixWebSocketStatusType(this._status.value)}]`
|
||||
);
|
||||
}
|
||||
|
||||
this._pendingRequests++;
|
||||
@@ -89,27 +92,27 @@ export class PhenixWebSocket extends MQWebSocket {
|
||||
private initialize(): void {
|
||||
super.onEvent('connected', () => {
|
||||
this.setStatus(PhenixWebSocketStatus.Online);
|
||||
})
|
||||
});
|
||||
|
||||
super.onEvent('disconnected', () => {
|
||||
this.setStatus(PhenixWebSocketStatus.Offline);
|
||||
})
|
||||
});
|
||||
|
||||
super.onEvent('error', (error: unknown) => {
|
||||
this._logger.error('Error [%s]', error);
|
||||
this.setStatus(PhenixWebSocketStatus.Error);
|
||||
})
|
||||
});
|
||||
|
||||
super.onEvent('reconnecting', () => {
|
||||
this.setStatus(PhenixWebSocketStatus.Reconnecting);
|
||||
})
|
||||
});
|
||||
|
||||
super.onEvent('reconnected', () => {
|
||||
this.setStatus(PhenixWebSocketStatus.Online);
|
||||
})
|
||||
});
|
||||
|
||||
super.onEvent('timeout', () => {
|
||||
this.setStatus(PhenixWebSocketStatus.Error);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
src/services/platform-detection.service.ts
Normal file
191
src/services/platform-detection.service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||
*/
|
||||
|
||||
type NavigatorUAData = {
|
||||
brands?: { brand: string; version: string }[];
|
||||
mobile?: boolean;
|
||||
platform?: string;
|
||||
getHighEntropyValues?: (hints: string[]) => Promise<Record<string, string>>;
|
||||
toJSON?: () => object;
|
||||
};
|
||||
|
||||
export default class PlatformDetectionService {
|
||||
private static readonly _userAgent: string = globalThis.navigator?.userAgent ?? '';
|
||||
// @ts-expect-error NavigatorUAData is experimental and not defined in the lib dom yet https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
||||
private static readonly _userAgentData: NavigatorUAData | undefined = globalThis.navigator?.userAgentData;
|
||||
|
||||
private static readonly _areClientHintsSupported: boolean = !!PlatformDetectionService._userAgentData;
|
||||
private static _platform: string = 'Unknown';
|
||||
private static _platformVersion: string = '';
|
||||
private static _browserName: string = 'Unknown';
|
||||
private static _browserVersion: string = '?';
|
||||
private static _isWebview: boolean = false;
|
||||
|
||||
static {
|
||||
if (PlatformDetectionService._areClientHintsSupported) {
|
||||
PlatformDetectionService.initFromClientHints();
|
||||
} else {
|
||||
PlatformDetectionService.initFromUserAgent();
|
||||
}
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
throw new Error('PlatformDetectionService is a static class that may not be instantiated');
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
static get platform(): string {
|
||||
return PlatformDetectionService._platform;
|
||||
}
|
||||
static get platformVersion(): string {
|
||||
return PlatformDetectionService._platformVersion;
|
||||
}
|
||||
static get userAgent(): string {
|
||||
return PlatformDetectionService._userAgent;
|
||||
}
|
||||
static get browserName(): string {
|
||||
return PlatformDetectionService._browserName;
|
||||
}
|
||||
static get browserVersion(): string {
|
||||
return PlatformDetectionService._browserVersion;
|
||||
}
|
||||
static get isWebview(): boolean {
|
||||
return PlatformDetectionService._isWebview;
|
||||
}
|
||||
static get areClientHintsSupported(): boolean {
|
||||
return PlatformDetectionService._areClientHintsSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional async initialization for high-entropy values like platformVersion
|
||||
*/
|
||||
static async initAsync(): Promise<void> {
|
||||
if (PlatformDetectionService._areClientHintsSupported && PlatformDetectionService._userAgentData?.getHighEntropyValues) {
|
||||
const values = await PlatformDetectionService._userAgentData.getHighEntropyValues(['platformVersion']);
|
||||
|
||||
if (values.platformVersion) {
|
||||
PlatformDetectionService._platformVersion = values.platformVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Init strategies ----
|
||||
private static initFromClientHints() {
|
||||
const data = PlatformDetectionService._userAgentData as NavigatorUAData;
|
||||
const nonChromiumBrand = data.brands?.find(b => b.brand !== 'Chromium');
|
||||
|
||||
PlatformDetectionService._browserName = nonChromiumBrand?.brand ?? 'Unknown';
|
||||
PlatformDetectionService._browserVersion = nonChromiumBrand?.version ?? '?';
|
||||
PlatformDetectionService._platform = data.platform ?? 'Unknown';
|
||||
PlatformDetectionService._isWebview = PlatformDetectionService.extractIsWebviewFromUserAgent(); // Fallback check
|
||||
}
|
||||
|
||||
private static initFromUserAgent() {
|
||||
PlatformDetectionService._platform = PlatformDetectionService.extractPlatformFromUserAgent();
|
||||
PlatformDetectionService._platformVersion = PlatformDetectionService.extractPlatformVersionFromUserAgent();
|
||||
PlatformDetectionService._browserName = PlatformDetectionService.extractBrowserNameFromUserAgent();
|
||||
PlatformDetectionService._browserVersion = PlatformDetectionService.extractBrowserVersionFromUserAgent();
|
||||
PlatformDetectionService._isWebview = PlatformDetectionService.extractIsWebviewFromUserAgent();
|
||||
}
|
||||
|
||||
// ---- Helpers ----
|
||||
private static extractBrowserNameFromUserAgent(): string {
|
||||
if (/Edg\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Edge';
|
||||
}
|
||||
|
||||
if (/OPR\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Opera';
|
||||
}
|
||||
|
||||
if (/Firefox\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Firefox';
|
||||
}
|
||||
|
||||
if (/Trident\/.*rv:/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'IE';
|
||||
}
|
||||
|
||||
if (/Chrome\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Chrome';
|
||||
}
|
||||
|
||||
if (/Safari\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Safari';
|
||||
}
|
||||
|
||||
if (/ReactNative\//.test(PlatformDetectionService._userAgent)) {
|
||||
return 'ReactNative';
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
private static extractBrowserVersionFromUserAgent(): string {
|
||||
return (
|
||||
PlatformDetectionService.matchVersion(/Edg\/([\d.]+)/) ??
|
||||
PlatformDetectionService.matchVersion(/OPR\/([\d.]+)/) ??
|
||||
PlatformDetectionService.matchVersion(/Firefox\/([\d.]+)/) ??
|
||||
PlatformDetectionService.matchVersion(/rv:([\d.]+)/) ?? // IE
|
||||
PlatformDetectionService.matchVersion(/Chrome\/([\d.]+)/) ??
|
||||
PlatformDetectionService.matchVersion(/Version\/([\d.]+)/) ?? // Safari often uses "Version/"
|
||||
PlatformDetectionService.matchVersion(/Safari\/([\d.]+)/) ??
|
||||
PlatformDetectionService.matchVersion(/ReactNative\/([\d.]+)/) ??
|
||||
'?'
|
||||
);
|
||||
}
|
||||
|
||||
private static extractPlatformFromUserAgent(): string {
|
||||
if (/Windows/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Windows';
|
||||
}
|
||||
|
||||
if (/iPhone|iPad|iPod/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'iOS';
|
||||
}
|
||||
|
||||
if (/Mac OS X/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'macOS';
|
||||
}
|
||||
|
||||
if (/Android/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Android';
|
||||
}
|
||||
|
||||
if (/Linux/.test(PlatformDetectionService._userAgent)) {
|
||||
return 'Linux';
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
private static extractPlatformVersionFromUserAgent(): string {
|
||||
switch (PlatformDetectionService._platform) {
|
||||
case 'Windows':
|
||||
return PlatformDetectionService.matchVersion(/Windows NT ([\d.]+)/) ?? '';
|
||||
case 'iOS':
|
||||
return PlatformDetectionService.matchVersion(/OS ([\d_]+)/)?.replace(/_/g, '.') ?? '';
|
||||
case 'macOS':
|
||||
return PlatformDetectionService.matchVersion(/Mac OS X ([\d_]+)/)?.replace(/_/g, '.') ?? '';
|
||||
case 'Android':
|
||||
return PlatformDetectionService.matchVersion(/Android ([\d.]+)/) ?? '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private static extractIsWebviewFromUserAgent(): boolean {
|
||||
return (
|
||||
/; wv/.test(PlatformDetectionService._userAgent) || // Android webview
|
||||
(/Android/.test(PlatformDetectionService._userAgent) && /Version\/[\d.]+/.test(PlatformDetectionService._userAgent)) || // Some Android webviews
|
||||
/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/.test(PlatformDetectionService._userAgent) // IOS webview
|
||||
);
|
||||
}
|
||||
|
||||
private static matchVersion(pattern: RegExp): string | null {
|
||||
const match = PlatformDetectionService._userAgent.match(pattern);
|
||||
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user