Initial Commit
This commit is contained in:
118
src/services/net/websockets/PhenixWebSocket.ts
Normal file
118
src/services/net/websockets/PhenixWebSocket.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
33
src/services/net/websockets/PhenixWebSocketMessage.ts
Normal file
33
src/services/net/websockets/PhenixWebSocketMessage.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/services/net/websockets/PhenixWebSocketStatus.ts
Normal file
44
src/services/net/websockets/PhenixWebSocketStatus.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
391
src/services/net/websockets/proto/analytics.proto.json
Normal file
391
src/services/net/websockets/proto/analytics.proto.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1777
src/services/net/websockets/proto/pcast.proto.json
Normal file
1777
src/services/net/websockets/proto/pcast.proto.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user