306 lines
8.3 KiB
TypeScript
Raw Normal View History

2019-09-18 15:02:21 +09:00
import { Injectable, Inject } from '@angular/core';
import { Subscription, Observable, Subject } from 'rxjs';
import { share, switchMap, retryWhen, delay } from 'rxjs/operators';
import { QueueingSubject } from 'queueing-subject';
import {
makeWebSocketObservable,
GetWebSocketResponses,
NormalClosureMessage
} from '@ucap-webmessenger/web-socket';
import { _MODULE_CONFIG } from '../types/token';
import { ModuleConfig } from '../types/module-config';
import { PacketBody } from '../models/packet';
import {
PacketBodyValueDivider,
PacketBodyDivider
} from '../types/packet-body-divider';
import { PacketBodyValue } from '../types/packet-body-value.type';
2019-09-19 10:40:16 +09:00
import { SSVC_TYPE_ERROR_RES, ServerErrorCode } from '../types/service';
2019-09-18 15:02:21 +09:00
interface RequestState {
subject: Subject<ServerResponse>;
request: {
serviceType: number;
subServiceType: number;
bodyList: PacketBody[];
};
}
export interface ServerResponse {
serviceType: number;
subServiceType: number;
senderSeq: number;
bodyList: any[];
}
@Injectable({
providedIn: 'root'
})
export class ProtocolService {
// tslint:disable-next-line: variable-name
private _requestId: number | null = null;
private readonly pendingRequests: Map<number, RequestState>;
private readonly input$: QueueingSubject<string>;
private socket$: Observable<GetWebSocketResponses<any>>;
2019-09-19 10:40:16 +09:00
private messages$: Observable<any>;
2019-09-18 15:02:21 +09:00
private messagesSubscription: Subscription | null = null;
constructor(@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig) {
this.pendingRequests = new Map();
this.input$ = new QueueingSubject<string>();
}
2019-09-19 10:40:16 +09:00
public connect(serverIp: string | null = null): void {
this.socket$ = makeWebSocketObservable(
this.moduleConfig.urls.base + serverIp ? serverIp : ''
);
this.messages$ = this.socket$.pipe(
2019-09-18 15:02:21 +09:00
switchMap(getResponses => getResponses(this.input$)),
retryWhen(errors =>
errors.pipe(delay(this.moduleConfig.reconnect.delay))
),
share()
);
2019-09-19 10:40:16 +09:00
this.messagesSubscription = this.messages$.subscribe(
2019-09-18 15:02:21 +09:00
(message: string) => {
const arg = message.split(PacketBodyDivider);
if (2 > arg.length) {
// OnError(3);
return;
}
const res = this.decodePacket(arg);
2019-09-19 10:40:16 +09:00
let requestState: RequestState | null = null;
if (res.requestId) {
requestState = this.pendingRequests.get(res.requestId);
this.pendingRequests.delete(res.requestId);
}
if (SSVC_TYPE_ERROR_RES === res.response.subServiceType) {
const errorCode: ServerErrorCode = res.response
.bodyList[0] as ServerErrorCode;
if (requestState) {
requestState.subject.error(errorCode);
}
return;
}
if (requestState) {
requestState.subject.next(res.response);
}
2019-09-18 15:02:21 +09:00
},
(error: Error) => {
const { message } = error;
if (message === NormalClosureMessage) {
console.log('server closed the websocket connection normally');
} else {
console.log('socket was disconnected due to error:', message);
}
},
() => {
// The clean termination only happens in response to the last
// subscription to the observable being unsubscribed, any
// other closure is considered an error.
console.log('the connection was closed in response to the user');
}
);
}
public disconnect(): void {
this.messagesSubscription.unsubscribe();
}
public call(
serviceType: number | null,
subServiceType: number | null,
...bodyList: PacketBody[]
): Observable<ServerResponse> {
return this.sendInternal(true, serviceType, subServiceType, bodyList);
}
public send(
serviceType: number,
subServiceType: number,
...bodyList: PacketBody[]
): void {
this.sendInternal(false, serviceType, subServiceType, bodyList);
}
private sendInternal(
hasResponse: boolean,
serviceType: number,
subServiceType: number,
bodyList: PacketBody[]
): Observable<ServerResponse> | undefined {
let packet: string;
let responseSubject: Subject<ServerResponse> | null = null;
if (hasResponse) {
const requestId = this.requestId;
packet = this.encodePacket(serviceType, subServiceType, [
...bodyList,
{ type: PacketBodyValue.RequestId, value: requestId }
]);
responseSubject = new Subject<ServerResponse>();
this.pendingRequests.set(requestId, {
subject: responseSubject,
request: { serviceType, subServiceType, bodyList }
});
} else {
packet = this.encodePacket(serviceType, subServiceType, bodyList);
}
this.input$.next(packet);
return responseSubject ? responseSubject.asObservable() : undefined;
}
private get requestId() {
if (
null === this._requestId ||
this.moduleConfig.requestId.max < this._requestId
) {
this._requestId = this.moduleConfig.requestId.min;
}
return this._requestId;
}
private decodePacket(
arg: string[]
): { requestId: number; response: ServerResponse } | null {
const cmdArg = arg[0].split(PacketBodyValueDivider);
if (2 > cmdArg.length) {
// OnError(3);
return null;
}
2019-09-19 10:40:16 +09:00
const serviceType = Number(cmdArg[0]);
const subServiceType = Number(cmdArg[1]);
2019-09-18 15:02:21 +09:00
const seqArg = arg[1].split(PacketBodyValueDivider);
if (2 > seqArg.length) {
// OnError(3);
return null;
}
2019-09-19 10:40:16 +09:00
const senderSeq = Number(seqArg[0]);
2019-09-18 15:02:21 +09:00
const bodyList: any[] | null = null;
let requestId: number | null = null;
for (let i = 2; i < arg.length; i++) {
const bodyArg = arg[i].split(PacketBodyValueDivider);
if (2 > bodyArg.length) {
// OnError(3);
return null;
}
const valueType: PacketBodyValue = bodyArg[0] as PacketBodyValue;
const value = bodyArg[0];
switch (valueType) {
case PacketBodyValue.None:
bodyList.push(value);
break;
case PacketBodyValue.Binary:
bodyList.push(value);
break;
case PacketBodyValue.Integer:
2019-09-19 10:40:16 +09:00
bodyList.push(Number(value));
2019-09-18 15:02:21 +09:00
break;
case PacketBodyValue.String:
bodyList.push(value);
break;
case PacketBodyValue.EncodedString:
bodyList.push(value);
break;
case PacketBodyValue.MultiByteString:
bodyList.push(value);
break;
case PacketBodyValue.WideString:
bodyList.push(value);
break;
case PacketBodyValue.IuId:
bodyList.push(value);
break;
case PacketBodyValue.RequestId:
requestId = parseInt(value, 10);
break;
default:
break;
}
}
2019-09-19 10:40:16 +09:00
return {
requestId,
response: {
serviceType,
subServiceType,
senderSeq,
bodyList
}
};
2019-09-18 15:02:21 +09:00
}
private encodePacket(
serviceType: number | null,
subServiceType: number | null,
bodyList: PacketBody[] = []
): string {
const packet: string[] = [];
packet.push(serviceType ? serviceType.toString() : '0');
packet.push(PacketBodyValueDivider);
packet.push(subServiceType ? subServiceType.toString() : '0');
packet.push(PacketBodyDivider);
for (const body of bodyList) {
packet.push(body.type);
packet.push(PacketBodyValueDivider);
switch (body.type) {
case PacketBodyValue.None:
packet.push(body.value.toString());
break;
case PacketBodyValue.Binary:
packet.push(body.value.toString());
break;
case PacketBodyValue.Integer:
packet.push(body.value.toString());
break;
case PacketBodyValue.String:
packet.push(body.value);
break;
case PacketBodyValue.EncodedString:
packet.push(body.value);
break;
case PacketBodyValue.MultiByteString:
packet.push(body.value);
break;
case PacketBodyValue.WideString:
packet.push(body.value);
break;
case PacketBodyValue.IuId:
packet.push(body.value);
break;
case PacketBodyValue.RequestId:
packet.push(body.value.toString());
break;
default:
break;
}
packet.push(PacketBodyDivider);
}
return packet.join('');
}
}