Initial Commit

This commit is contained in:
2025-09-07 01:46:37 -04:00
commit 66986cca51
272 changed files with 15331 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
import Strings from 'lang/Strings';
import ILogger from '../../logger/LoggerInterface';
import LoggerFactory from '../../logger/LoggerFactory';
import {Subject, ReadOnlySubject} from '@techniker-me/tools';
import {MQWebSocket} from 'phenix-web-proto'; //TODO(AZ): add types
import PCastProtobuf from './proto/pcast.proto.json' with {type: 'json'};
import AnalyticsProtobuf from './proto/Analytics.proto.json' with {type: 'json'};
import {PhenixWebSocketStatus, PhenixWebSocketStatusMapping} from './PhenixWebSocketStatus';
import {PhenixWebSocketMessage, PhenixWebSocketMessageMapping} from './PhenixWebSocketMessage';
export type AuthenticationResponse = {
status: string;
applicationId?: string;
sessionId?: string;
};
export interface IPhenixWebSocketResponse {
status: 'ok';
applicationId: string;
sessionId: string;
redirect: string;
roles: string[];
[key: string]: unknown;
}
export class PhenixWebSocket extends MQWebSocket {
private readonly _logger: ILogger;
private readonly _status: Subject<PhenixWebSocketStatus> = new Subject<PhenixWebSocketStatus>(PhenixWebSocketStatus.Offline);
private readonly _readOnlySubject: ReadOnlySubject<PhenixWebSocketStatus> = new ReadOnlySubject<PhenixWebSocketStatus>(this._status);
private readonly _socketId: string = Strings.randomString(10);
private _sessionId: string | null = null;
private _pendingRequests: number = 0;
constructor(url: string) {
const logger = LoggerFactory.getLogger('PhenixWebSocket');
super(url, logger, [PCastProtobuf, AnalyticsProtobuf]);
this._logger = logger;
this.initialize();
}
get status(): ReadOnlySubject<PhenixWebSocketStatus> {
return this._readOnlySubject;
}
get pendingRequests(): number {
return this._pendingRequests;
}
get socketId(): string {
return this._socketId;
}
get sessionId(): string | null {
return this._sessionId;
}
set sessionId(sessionId: string | null) {
this._sessionId = sessionId;
}
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)}]`
);
}
this._pendingRequests++;
const messageKind = PhenixWebSocketMessageMapping.convertPhenixWebSocketMessageToPhenixWebSocketMessageType(kind);
this._logger.debug(`Sending [${messageKind}] message [%j]`, message);
return new Promise<IPhenixWebSocketResponse>((resolve, reject) => {
super.sendRequest(messageKind, message, (error: unknown, response: IPhenixWebSocketResponse) => {
this._pendingRequests--;
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
private setStatus(status: PhenixWebSocketStatus): void {
this._status.value = status;
}
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);
});
}
}

View File

@@ -0,0 +1,33 @@
import assertUnreachable from 'lang/assertUnreachable';
export enum PhenixWebSocketMessage {
Authenticate = 0,
Bye = 1
}
export type PhenixWebSocketMessageType = 'pcast.Authenticate' | 'pcast.Bye';
export class PhenixWebSocketMessageMapping {
public static convertPhenixWebSocketMessageToPhenixWebSocketMessageType(message: PhenixWebSocketMessage): PhenixWebSocketMessageType {
switch (message) {
case PhenixWebSocketMessage.Authenticate:
return 'pcast.Authenticate';
case PhenixWebSocketMessage.Bye:
return 'pcast.Bye';
default:
assertUnreachable(message);
}
}
public static convertPhenixWebSocketMessageTypeToPhenixWebSocketMessage(messageType: PhenixWebSocketMessageType): PhenixWebSocketMessage {
switch (messageType) {
case 'pcast.Authenticate':
return PhenixWebSocketMessage.Authenticate;
case 'pcast.Bye':
return PhenixWebSocketMessage.Bye;
default:
assertUnreachable(messageType);
}
}
}

View File

@@ -0,0 +1,44 @@
import assertUnreachable from '../../../lang/assertUnreachable';
export enum PhenixWebSocketStatus {
Offline = 0,
Online = 1,
Reconnecting = 2,
Error = 3
}
export type PhenixWebSocketStatusType = 'Offline' | 'Online' | 'Reconnecting' | 'Error';
export class PhenixWebSocketStatusMapping {
public static convertPhenixWebSocketStatusToPhenixWebSocketStatusType(status: PhenixWebSocketStatus): PhenixWebSocketStatusType {
switch (status) {
case PhenixWebSocketStatus.Offline:
return 'Offline';
case PhenixWebSocketStatus.Online:
return 'Online';
case PhenixWebSocketStatus.Reconnecting:
return 'Reconnecting';
case PhenixWebSocketStatus.Error:
return 'Error';
default:
assertUnreachable(status);
}
}
public static convertPhenixWebSocketStatusTypeToPhenixWebSocketStatus(statusType: PhenixWebSocketStatusType): PhenixWebSocketStatus {
switch (statusType) {
case 'Offline':
return PhenixWebSocketStatus.Offline;
case 'Online':
return PhenixWebSocketStatus.Online;
case 'Reconnecting':
return PhenixWebSocketStatus.Reconnecting;
case 'Error':
return PhenixWebSocketStatus.Error;
default:
assertUnreachable(statusType);
}
}
}

View File

@@ -0,0 +1,391 @@
{
"package": "analytics",
"messages": [
{
"name": "Usage",
"fields": [
{
"rule": "required",
"type": "uint64",
"name": "streams",
"id": 1
},
{
"rule": "required",
"type": "uint64",
"name": "users",
"id": 2
},
{
"rule": "required",
"type": "uint64",
"name": "devices",
"id": 3
},
{
"rule": "required",
"type": "uint64",
"name": "minutes",
"id": 4
}
]
},
{
"name": "UsageByType",
"fields": [
{
"rule": "required",
"type": "string",
"name": "type",
"id": 1
},
{
"rule": "required",
"type": "string",
"name": "subtype",
"id": 2
},
{
"rule": "required",
"type": "Usage",
"name": "usage",
"id": 3
}
]
},
{
"name": "UsageByCountry",
"fields": [
{
"rule": "required",
"type": "string",
"name": "continent",
"id": 1
},
{
"rule": "required",
"type": "string",
"name": "country",
"id": 2
},
{
"rule": "repeated",
"type": "UsageByType",
"name": "usageByType",
"id": 3
}
]
},
{
"name": "GetGeographicUsage",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "applicationIds",
"id": 1
},
{
"rule": "required",
"type": "uint64",
"name": "start",
"id": 2
},
{
"rule": "required",
"type": "uint64",
"name": "end",
"id": 3
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 4
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 5
}
]
},
{
"name": "GetGeographicUsageResponse",
"fields": [
{
"rule": "required",
"type": "string",
"name": "status",
"id": 1
},
{
"rule": "optional",
"type": "Usage",
"name": "usage",
"id": 2
},
{
"rule": "repeated",
"type": "UsageByType",
"name": "usageByType",
"id": 3
},
{
"rule": "repeated",
"type": "UsageByCountry",
"name": "usageByCountry",
"id": 4
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 5
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 6
}
]
},
{
"name": "CDF",
"fields": [
{
"rule": "repeated",
"type": "double",
"name": "data",
"id": 1
}
]
},
{
"name": "GetTimeToFirstFrameCDF",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "applicationIds",
"id": 1
},
{
"rule": "required",
"type": "uint64",
"name": "start",
"id": 2
},
{
"rule": "required",
"type": "uint64",
"name": "end",
"id": 3
},
{
"rule": "required",
"type": "Kind",
"name": "kind",
"id": 4
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 5
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 6
}
],
"enums": [
{
"name": "Kind",
"values": [
{
"name": "All",
"id": 0
},
{
"name": "RealTime",
"id": 1
},
{
"name": "Live",
"id": 2
},
{
"name": "Dash",
"id": 3
},
{
"name": "Hls",
"id": 4
},
{
"name": "PeerAssisted",
"id": 5
}
]
}
]
},
{
"name": "GetTimeToFirstFrameCDFResponse",
"fields": [
{
"rule": "required",
"type": "string",
"name": "status",
"id": 1
},
{
"rule": "optional",
"type": "uint64",
"name": "count",
"id": 2
},
{
"rule": "optional",
"type": "double",
"name": "average",
"id": 3
},
{
"rule": "optional",
"type": "CDF",
"name": "cdf",
"id": 4
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 5
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 6
}
]
},
{
"name": "GetActiveUsers",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "applicationIds",
"id": 1
},
{
"rule": "required",
"type": "uint64",
"name": "snapshotTime",
"id": 2
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 3
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 4
}
]
},
{
"name": "UsersAndSessionsGrouped",
"fields": [
{
"rule": "required",
"type": "string",
"name": "groupName",
"id": 1
},
{
"rule": "required",
"type": "uint64",
"name": "users",
"id": 2
},
{
"rule": "required",
"type": "uint64",
"name": "sessions",
"id": 3
}
]
},
{
"name": "GetActiveUsersResponse",
"fields": [
{
"rule": "required",
"type": "string",
"name": "status",
"id": 1
},
{
"rule": "optional",
"type": "uint64",
"name": "users",
"id": 2
},
{
"rule": "optional",
"type": "uint64",
"name": "sessions",
"id": 3
},
{
"rule": "repeated",
"type": "UsersAndSessionsGrouped",
"name": "byPlatform",
"id": 4
},
{
"rule": "repeated",
"type": "UsersAndSessionsGrouped",
"name": "byManufacturer",
"id": 5
},
{
"rule": "repeated",
"type": "UsersAndSessionsGrouped",
"name": "byCity",
"id": 6
},
{
"rule": "repeated",
"type": "UsersAndSessionsGrouped",
"name": "byCountry",
"id": 7
},
{
"rule": "optional",
"type": "string",
"name": "continuationId",
"id": 8
},
{
"rule": "optional",
"type": "string",
"name": "routeKey",
"id": 9
}
]
}
]
}

File diff suppressed because it is too large Load Diff