@dooboostore/simple-boot-http-server 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/cjs/SimpleBootHttpServer.js +80 -40
- package/dist/cjs/SimpleBootHttpServer.js.map +3 -3
- package/dist/cjs/codes/HttpHeaders.js +14 -3
- package/dist/cjs/codes/HttpHeaders.js.map +2 -2
- package/dist/cjs/codes/Mimes.js +2 -0
- package/dist/cjs/codes/Mimes.js.map +2 -2
- package/dist/cjs/decorators/MethodMapping.js +40 -9
- package/dist/cjs/decorators/MethodMapping.js.map +3 -3
- package/dist/cjs/endpoints/WebSocketEndPoint.js +16 -0
- package/dist/cjs/endpoints/WebSocketEndPoint.js.map +7 -0
- package/dist/cjs/errors/BadRequestError.js +2 -1
- package/dist/cjs/errors/BadRequestError.js.map +2 -2
- package/dist/cjs/errors/ForbiddenError.js +2 -1
- package/dist/cjs/errors/ForbiddenError.js.map +2 -2
- package/dist/cjs/errors/InternalServerError.js +2 -1
- package/dist/cjs/errors/InternalServerError.js.map +2 -2
- package/dist/cjs/errors/NotFoundError.js +2 -1
- package/dist/cjs/errors/NotFoundError.js.map +2 -2
- package/dist/cjs/errors/UnauthorizedError.js +2 -1
- package/dist/cjs/errors/UnauthorizedError.js.map +2 -2
- package/dist/cjs/filters/CacheFilter.js +75 -35
- package/dist/cjs/filters/CacheFilter.js.map +3 -3
- package/dist/cjs/filters/IntentSchemeFilter.js +97 -0
- package/dist/cjs/filters/IntentSchemeFilter.js.map +7 -0
- package/dist/cjs/filters/ResourceFilter.js +6 -2
- package/dist/cjs/filters/ResourceFilter.js.map +2 -2
- package/dist/cjs/filters/TopicProtocolFilter.js +343 -0
- package/dist/cjs/filters/TopicProtocolFilter.js.map +7 -0
- package/dist/cjs/filters/index.js +5 -1
- package/dist/cjs/filters/index.js.map +2 -2
- package/dist/cjs/index.js +30 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/inject/InjectSituationType.js +1 -1
- package/dist/cjs/inject/InjectSituationType.js.map +2 -2
- package/dist/cjs/models/RequestResponse.js +70 -91
- package/dist/cjs/models/RequestResponse.js.map +3 -3
- package/dist/cjs/models/datas/body/index.js +28 -0
- package/dist/cjs/models/datas/body/index.js.map +7 -0
- package/dist/cjs/models/datas/index.js +32 -0
- package/dist/cjs/models/datas/index.js.map +7 -0
- package/dist/cjs/models/datas/path/ReqPathData.js +29 -0
- package/dist/cjs/models/datas/path/ReqPathData.js.map +7 -0
- package/dist/cjs/models/datas/{search → path}/ReqSearchParamsObj.js.map +1 -1
- package/dist/cjs/models/datas/path/index.js +24 -0
- package/dist/cjs/models/datas/path/index.js.map +7 -0
- package/dist/cjs/models/index.js +4 -16
- package/dist/cjs/models/index.js.map +2 -2
- package/dist/cjs/models/method/index.js +22 -0
- package/dist/cjs/models/method/index.js.map +7 -0
- package/dist/cjs/models/method/patch/index.js +22 -0
- package/dist/cjs/models/method/patch/index.js.map +7 -0
- package/dist/cjs/option/HttpServerOption.js +25 -5
- package/dist/cjs/option/HttpServerOption.js.map +2 -2
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js +144 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxy.js.map +7 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js +35 -0
- package/dist/cjs/proxy/SymbolIntentApiServiceProxyBase.js.map +7 -0
- package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js +324 -0
- package/dist/cjs/proxy/TopicProtocolApiServiceProxy.js.map +7 -0
- package/dist/cjs/proxy/index.js +26 -0
- package/dist/cjs/proxy/index.js.map +7 -0
- package/dist/cjs/resolvers/ResourceResolver.js +2 -2
- package/dist/cjs/resolvers/ResourceResolver.js.map +2 -2
- package/dist/cjs/session/SessionManager.js +2 -2
- package/dist/cjs/session/SessionManager.js.map +2 -2
- package/dist/cjs/websocket/WebSocketClient.js +554 -0
- package/dist/cjs/websocket/WebSocketClient.js.map +7 -0
- package/dist/cjs/websocket/WebSocketManager.js +652 -0
- package/dist/cjs/websocket/WebSocketManager.js.map +7 -0
- package/dist/cjs/websocket/index.js +24 -0
- package/dist/cjs/websocket/index.js.map +7 -0
- package/dist/esm/SimpleBootHttpServer.js +60 -20
- package/dist/esm/SimpleBootHttpServer.js.map +2 -2
- package/dist/esm/codes/HttpHeaders.js +12 -2
- package/dist/esm/codes/HttpHeaders.js.map +2 -2
- package/dist/esm/codes/Mimes.js +2 -0
- package/dist/esm/codes/Mimes.js.map +2 -2
- package/dist/esm/decorators/MethodMapping.js +24 -3
- package/dist/esm/decorators/MethodMapping.js.map +2 -2
- package/dist/esm/endpoints/WebSocketEndPoint.js +1 -0
- package/dist/esm/endpoints/WebSocketEndPoint.js.map +7 -0
- package/dist/esm/errors/BadRequestError.js +2 -1
- package/dist/esm/errors/BadRequestError.js.map +2 -2
- package/dist/esm/errors/ForbiddenError.js +2 -1
- package/dist/esm/errors/ForbiddenError.js.map +2 -2
- package/dist/esm/errors/InternalServerError.js +2 -1
- package/dist/esm/errors/InternalServerError.js.map +2 -2
- package/dist/esm/errors/NotFoundError.js +2 -1
- package/dist/esm/errors/NotFoundError.js.map +2 -2
- package/dist/esm/errors/UnauthorizedError.js +2 -1
- package/dist/esm/errors/UnauthorizedError.js.map +2 -2
- package/dist/esm/filters/CacheFilter.js +67 -27
- package/dist/esm/filters/CacheFilter.js.map +2 -2
- package/dist/esm/filters/IntentSchemeFilter.js +74 -0
- package/dist/esm/filters/IntentSchemeFilter.js.map +7 -0
- package/dist/esm/filters/ResourceFilter.js +6 -2
- package/dist/esm/filters/ResourceFilter.js.map +2 -2
- package/dist/esm/filters/TopicProtocolFilter.js +310 -0
- package/dist/esm/filters/TopicProtocolFilter.js.map +7 -0
- package/dist/esm/filters/index.js +2 -0
- package/dist/esm/filters/index.js.map +2 -2
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/inject/InjectSituationType.js +1 -1
- package/dist/esm/inject/InjectSituationType.js.map +2 -2
- package/dist/esm/models/RequestResponse.js +65 -86
- package/dist/esm/models/RequestResponse.js.map +2 -2
- package/dist/esm/models/datas/body/index.js +5 -0
- package/dist/esm/models/datas/body/index.js.map +7 -0
- package/dist/esm/models/datas/index.js +7 -0
- package/dist/esm/models/datas/index.js.map +7 -0
- package/dist/esm/models/datas/path/ReqPathData.js +6 -0
- package/dist/esm/models/datas/path/ReqPathData.js.map +7 -0
- package/dist/esm/models/datas/{search → path}/ReqSearchParamsObj.js.map +1 -1
- package/dist/esm/models/datas/path/index.js +3 -0
- package/dist/esm/models/datas/path/index.js.map +7 -0
- package/dist/esm/models/index.js +2 -8
- package/dist/esm/models/index.js.map +2 -2
- package/dist/esm/models/method/index.js +2 -0
- package/dist/esm/models/method/index.js.map +7 -0
- package/dist/esm/models/method/patch/index.js +2 -0
- package/dist/esm/models/method/patch/index.js.map +7 -0
- package/dist/esm/option/HttpServerOption.js +24 -4
- package/dist/esm/option/HttpServerOption.js.map +2 -2
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js +121 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxy.js.map +7 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js +12 -0
- package/dist/esm/proxy/SymbolIntentApiServiceProxyBase.js.map +7 -0
- package/dist/esm/proxy/TopicProtocolApiServiceProxy.js +301 -0
- package/dist/esm/proxy/TopicProtocolApiServiceProxy.js.map +7 -0
- package/dist/esm/proxy/index.js +4 -0
- package/dist/esm/proxy/index.js.map +7 -0
- package/dist/esm/resolvers/ResourceResolver.js +1 -1
- package/dist/esm/resolvers/ResourceResolver.js.map +1 -1
- package/dist/esm/session/SessionManager.js +1 -1
- package/dist/esm/session/SessionManager.js.map +1 -1
- package/dist/esm/websocket/WebSocketClient.js +529 -0
- package/dist/esm/websocket/WebSocketClient.js.map +7 -0
- package/dist/esm/websocket/WebSocketManager.js +622 -0
- package/dist/esm/websocket/WebSocketManager.js.map +7 -0
- package/dist/esm/websocket/index.js +3 -0
- package/dist/esm/websocket/index.js.map +7 -0
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js +18209 -5299
- package/dist/esm-bundle/dooboostore-simple-boot-http-server.esm.js.map +4 -4
- package/dist/types/SimpleBootHttpServer.d.ts +5 -3
- package/dist/types/SimpleBootHttpServer.d.ts.map +1 -1
- package/dist/types/codes/HttpHeaders.d.ts +3 -0
- package/dist/types/codes/HttpHeaders.d.ts.map +1 -1
- package/dist/types/codes/Mimes.d.ts +3 -1
- package/dist/types/codes/Mimes.d.ts.map +1 -1
- package/dist/types/decorators/MethodMapping.d.ts +13 -2
- package/dist/types/decorators/MethodMapping.d.ts.map +1 -1
- package/dist/types/endpoints/WebSocketEndPoint.d.ts +17 -0
- package/dist/types/endpoints/WebSocketEndPoint.d.ts.map +1 -0
- package/dist/types/errors/BadRequestError.d.ts +2 -2
- package/dist/types/errors/BadRequestError.d.ts.map +1 -1
- package/dist/types/errors/ForbiddenError.d.ts +2 -2
- package/dist/types/errors/ForbiddenError.d.ts.map +1 -1
- package/dist/types/errors/InternalServerError.d.ts +2 -2
- package/dist/types/errors/InternalServerError.d.ts.map +1 -1
- package/dist/types/errors/NotFoundError.d.ts +2 -2
- package/dist/types/errors/NotFoundError.d.ts.map +1 -1
- package/dist/types/errors/UnauthorizedError.d.ts +2 -2
- package/dist/types/errors/UnauthorizedError.d.ts.map +1 -1
- package/dist/types/filters/CacheFilter.d.ts +6 -0
- package/dist/types/filters/CacheFilter.d.ts.map +1 -1
- package/dist/types/filters/IntentSchemeFilter.d.ts +20 -0
- package/dist/types/filters/IntentSchemeFilter.d.ts.map +1 -0
- package/dist/types/filters/ResourceFilter.d.ts.map +1 -1
- package/dist/types/filters/TopicProtocolFilter.d.ts +34 -0
- package/dist/types/filters/TopicProtocolFilter.d.ts.map +1 -0
- package/dist/types/filters/index.d.ts +2 -0
- package/dist/types/filters/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/inject/InjectSituationType.d.ts.map +1 -1
- package/dist/types/models/RequestResponse.d.ts +12 -7
- package/dist/types/models/RequestResponse.d.ts.map +1 -1
- package/dist/types/models/datas/body/index.d.ts +5 -0
- package/dist/types/models/datas/body/index.d.ts.map +1 -0
- package/dist/types/models/datas/index.d.ts +7 -0
- package/dist/types/models/datas/index.d.ts.map +1 -0
- package/dist/types/models/datas/path/ReqPathData.d.ts +4 -0
- package/dist/types/models/datas/path/ReqPathData.d.ts.map +1 -0
- package/dist/types/models/datas/path/ReqSearchParamsObj.d.ts.map +1 -0
- package/dist/types/models/datas/path/index.d.ts +3 -0
- package/dist/types/models/datas/path/index.d.ts.map +1 -0
- package/dist/types/models/index.d.ts +2 -8
- package/dist/types/models/index.d.ts.map +1 -1
- package/dist/types/models/method/index.d.ts +2 -0
- package/dist/types/models/method/index.d.ts.map +1 -0
- package/dist/types/models/method/patch/index.d.ts +2 -0
- package/dist/types/models/method/patch/index.d.ts.map +1 -0
- package/dist/types/option/HttpServerOption.d.ts +30 -20
- package/dist/types/option/HttpServerOption.d.ts.map +1 -1
- package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts +16 -0
- package/dist/types/proxy/SymbolIntentApiServiceProxy.d.ts.map +1 -0
- package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts +5 -0
- package/dist/types/proxy/SymbolIntentApiServiceProxyBase.d.ts.map +1 -0
- package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts +28 -0
- package/dist/types/proxy/TopicProtocolApiServiceProxy.d.ts.map +1 -0
- package/dist/types/proxy/index.d.ts +4 -0
- package/dist/types/proxy/index.d.ts.map +1 -0
- package/dist/types/session/SessionManager.d.ts +2 -17
- package/dist/types/session/SessionManager.d.ts.map +1 -1
- package/dist/types/websocket/WebSocketClient.d.ts +88 -0
- package/dist/types/websocket/WebSocketClient.d.ts.map +1 -0
- package/dist/types/websocket/WebSocketManager.d.ts +163 -0
- package/dist/types/websocket/WebSocketManager.d.ts.map +1 -0
- package/dist/types/websocket/index.d.ts +3 -0
- package/dist/types/websocket/index.d.ts.map +1 -0
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js +18108 -5198
- package/dist/umd-bundle/dooboostore-simple-boot-http-server.umd.js.map +4 -4
- package/dist/umd-bundle/websocket-client.umd.js +11374 -0
- package/dist/umd-bundle/websocket-client.umd.js.map +7 -0
- package/package.json +24 -15
- package/src/SimpleBootHttpServer.ts +367 -313
- package/src/codes/HttpHeaders.ts +13 -2
- package/src/codes/Mimes.ts +27 -25
- package/src/decorators/MethodMapping.ts +17 -4
- package/src/endpoints/WebSocketEndPoint.ts +12 -0
- package/src/errors/BadRequestError.ts +7 -3
- package/src/errors/ForbiddenError.ts +7 -3
- package/src/errors/InternalServerError.ts +7 -3
- package/src/errors/NotFoundError.ts +7 -3
- package/src/errors/UnauthorizedError.ts +7 -3
- package/src/filters/CacheFilter.ts +112 -67
- package/src/filters/IntentSchemeFilter.ts +69 -0
- package/src/filters/ResourceFilter.ts +10 -2
- package/src/filters/TopicProtocolFilter.ts +349 -0
- package/src/filters/index.ts +2 -0
- package/src/index.ts +13 -12
- package/src/inject/InjectSituationType.ts +1 -1
- package/src/models/RequestResponse.ts +136 -242
- package/src/models/datas/body/index.ts +4 -0
- package/src/models/datas/index.ts +6 -0
- package/src/models/datas/path/ReqPathData.ts +3 -0
- package/src/models/datas/path/index.ts +2 -0
- package/src/models/index.ts +2 -8
- package/src/models/method/index.ts +1 -0
- package/src/models/method/patch/index.ts +1 -0
- package/src/option/HttpServerOption.ts +73 -23
- package/src/proxy/SymbolIntentApiServiceProxy.ts +126 -0
- package/src/proxy/SymbolIntentApiServiceProxyBase.ts +16 -0
- package/src/proxy/TopicProtocolApiServiceProxy.ts +328 -0
- package/src/proxy/index.ts +3 -0
- package/src/resolvers/ResourceResolver.ts +1 -1
- package/src/session/SessionManager.ts +3 -3
- package/src/websocket/WebSocketClient.ts +637 -0
- package/src/websocket/WebSocketManager.ts +713 -0
- package/src/websocket/index.ts +2 -0
- package/tsconfig.json +3 -1
- package/README.MD +0 -36
- package/dist/types/models/datas/search/ReqSearchParamsObj.d.ts.map +0 -1
- /package/dist/cjs/models/datas/{search → path}/ReqSearchParamsObj.js +0 -0
- /package/dist/esm/models/datas/{search → path}/ReqSearchParamsObj.js +0 -0
- /package/dist/types/models/datas/{search → path}/ReqSearchParamsObj.d.ts +0 -0
- /package/src/models/datas/{search → path}/ReqSearchParamsObj.ts +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {RandomUtils} from '@dooboostore/core
|
|
2
|
-
import {HttpServerOption} from '../option/HttpServerOption';
|
|
1
|
+
import {RandomUtils} from '@dooboostore/core'
|
|
2
|
+
import { HttpServerOption, SessionOption } from '../option/HttpServerOption';
|
|
3
3
|
import {RequestResponse} from '../models/RequestResponse';
|
|
4
4
|
|
|
5
5
|
export class SessionManager {
|
|
6
6
|
private sessions = new Map<string, { access: number, data?: any }>();
|
|
7
|
-
public sessionOption:
|
|
7
|
+
public sessionOption: SessionOption;
|
|
8
8
|
|
|
9
9
|
constructor(private option: HttpServerOption) {
|
|
10
10
|
this.sessionOption = option.sessionOption;
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import {RandomUtils} from '@dooboostore/core';
|
|
2
|
+
import {TopicRequest, TopicResponse, TopicServerEventRequest, TopicServerEventResponse, TopicServerEventUnSubscribeRequest, TopicSubscribeRequest, TopicType, TopicUnsubscribeRequest} from './WebSocketManager';
|
|
3
|
+
import {MessageOperator, Observable, Subject} from '@dooboostore/core';
|
|
4
|
+
|
|
5
|
+
export * from '@dooboostore/core';
|
|
6
|
+
|
|
7
|
+
type TopicObserverNextCallBack<T> = <R = any>(data: T, meta: { socket: WebSocket }) => void;
|
|
8
|
+
type TopicObserverEventCallBack = { [event: string]: <T, R = any>(data: T, meta: { socket: WebSocket }) => Promise<R> | void | any } | (<T, R = any>(event: string, data: T, meta: { socket: WebSocket }) => Promise<R> | void | any);
|
|
9
|
+
type TopicObserverErrorCallBack = <R = any>(err: any) => void;
|
|
10
|
+
export type TopicObserverCallBack<T> = { next: TopicObserverNextCallBack<T>; event?: TopicObserverEventCallBack; error?: TopicObserverErrorCallBack; complete?: () => void } | TopicObserverNextCallBack<T>;
|
|
11
|
+
|
|
12
|
+
export interface WebSocketClientConfig {
|
|
13
|
+
retryConnectionCount?: number;
|
|
14
|
+
retryConnectionDelay?: number;
|
|
15
|
+
maxPendingMessages?: number;
|
|
16
|
+
maxPendingBinaryMessages?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type OnUnsubscribeCallback = (type: 'server' | 'manual') => void;
|
|
20
|
+
export type Subscription = {
|
|
21
|
+
closed: boolean;
|
|
22
|
+
unsubscribe: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class WebSocketClient {
|
|
26
|
+
private websocket: WebSocket;
|
|
27
|
+
private topics = new Map<string, { observer: TopicObserverCallBack<any>; subscription: Subscription; onUnsubscribes: OnUnsubscribeCallback[]; target: string }>();
|
|
28
|
+
private observer?: (data: TopicResponse | TopicServerEventRequest | TopicServerEventUnSubscribeRequest) => void;
|
|
29
|
+
private pendingMessages: any[] = [];
|
|
30
|
+
private pendingBinaryMessages: Array<ArrayBuffer | Blob | Uint8Array> = [];
|
|
31
|
+
private isConnected = false;
|
|
32
|
+
private onOpenCallback: () => void;
|
|
33
|
+
private onCloseCallback: () => void;
|
|
34
|
+
private _subject = new Subject<TopicResponse | TopicServerEventRequest | TopicServerEventUnSubscribeRequest>();
|
|
35
|
+
// Reconnection properties
|
|
36
|
+
private connectionUrl: string | URL;
|
|
37
|
+
private connectionProtocols?: string | string[];
|
|
38
|
+
private retryConnectionCount: number;
|
|
39
|
+
private retryConnectionDelay: number;
|
|
40
|
+
private currentRetryCount = 0;
|
|
41
|
+
private reconnectTimeout?: number;
|
|
42
|
+
private isManualClose = false;
|
|
43
|
+
private isConnecting = false;
|
|
44
|
+
private maxPendingMessages: number;
|
|
45
|
+
private maxPendingBinaryMessages: number;
|
|
46
|
+
|
|
47
|
+
constructor(connection: { url: string | URL; protocols?: string | string[] }, config?: WebSocketClientConfig);
|
|
48
|
+
constructor(connection: string, config?: WebSocketClientConfig);
|
|
49
|
+
constructor(connection: URL, config?: WebSocketClientConfig);
|
|
50
|
+
constructor(connection: { url: string | URL; protocols?: string | string[] } | string | URL, config?: WebSocketClientConfig) {
|
|
51
|
+
// Store connection info for reconnection
|
|
52
|
+
if (typeof connection === 'string' || connection instanceof URL) {
|
|
53
|
+
this.connectionUrl = connection;
|
|
54
|
+
} else {
|
|
55
|
+
this.connectionUrl = connection.url;
|
|
56
|
+
this.connectionProtocols = connection.protocols;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Set config with defaults
|
|
60
|
+
this.retryConnectionCount = config?.retryConnectionCount ?? 0;
|
|
61
|
+
this.retryConnectionDelay = config?.retryConnectionDelay ?? 1000;
|
|
62
|
+
this.maxPendingMessages = config?.maxPendingMessages ?? 1000;
|
|
63
|
+
this.maxPendingBinaryMessages = config?.maxPendingBinaryMessages ?? 100;
|
|
64
|
+
|
|
65
|
+
this.connect();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private get observable() {
|
|
69
|
+
return this._subject.asObservable();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private connect() {
|
|
73
|
+
if (this.isConnecting) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (this.websocket && (this.websocket.readyState === WebSocket.OPEN || this.websocket.readyState === WebSocket.CONNECTING)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.isConnecting = true;
|
|
80
|
+
if (this.connectionProtocols) {
|
|
81
|
+
this.websocket = new WebSocket(this.connectionUrl, this.connectionProtocols);
|
|
82
|
+
} else {
|
|
83
|
+
this.websocket = new WebSocket(this.connectionUrl);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.websocket.binaryType = 'arraybuffer';
|
|
87
|
+
|
|
88
|
+
this.websocket.addEventListener('open', () => {
|
|
89
|
+
this.isConnecting = false;
|
|
90
|
+
this.isConnected = true;
|
|
91
|
+
this.currentRetryCount = 0; // Reset retry count on successful connection
|
|
92
|
+
if (this.reconnectTimeout) {
|
|
93
|
+
clearTimeout(this.reconnectTimeout);
|
|
94
|
+
this.reconnectTimeout = undefined;
|
|
95
|
+
}
|
|
96
|
+
console.log('WebSocket connected');
|
|
97
|
+
this.onOpenCallback?.();
|
|
98
|
+
|
|
99
|
+
// Re-subscribe to all existing topics after reconnection
|
|
100
|
+
this.topics.forEach((topicData, uuid) => {
|
|
101
|
+
// closed된 subscription은 재구독하지 않음
|
|
102
|
+
if (!topicData.subscription.closed) {
|
|
103
|
+
const subscribeTopic: TopicSubscribeRequest = {
|
|
104
|
+
type: 'subscribe',
|
|
105
|
+
uuid,
|
|
106
|
+
target: topicData.target
|
|
107
|
+
};
|
|
108
|
+
// console.log('????')
|
|
109
|
+
this.websocket.send(JSON.stringify(subscribeTopic));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// 대기 중인 모든 메시지 전송
|
|
114
|
+
while (this.pendingMessages.length > 0) {
|
|
115
|
+
const message = this.pendingMessages.shift();
|
|
116
|
+
this.websocket.send(JSON.stringify(message));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
while (this.pendingBinaryMessages.length > 0) {
|
|
120
|
+
const message = this.pendingBinaryMessages.shift();
|
|
121
|
+
if (message) {
|
|
122
|
+
this.websocket.send(message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.websocket.addEventListener('close', event => {
|
|
128
|
+
this.isConnecting = false;
|
|
129
|
+
this.isConnected = false;
|
|
130
|
+
console.log('WebSocket closed', event.code, event.reason);
|
|
131
|
+
this.onCloseCallback?.();
|
|
132
|
+
|
|
133
|
+
// Attempt reconnection if not manually closed
|
|
134
|
+
if (!this.isManualClose) {
|
|
135
|
+
this.attemptReconnect();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.websocket.addEventListener('error', error => {
|
|
140
|
+
console.error('WebSocket error:', error);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
this.websocket.addEventListener('message', async event => {
|
|
144
|
+
const topic = await this.parseIncomingTopic(event.data);
|
|
145
|
+
if (!topic) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this._subject.next(topic);
|
|
149
|
+
this.observer?.(topic);
|
|
150
|
+
if (topic.uuid) {
|
|
151
|
+
const topicData = this.topics.get(topic.uuid);
|
|
152
|
+
if (topicData) {
|
|
153
|
+
const observer = topicData.observer;
|
|
154
|
+
const nextCallback = typeof observer === 'function' ? observer : observer.next;
|
|
155
|
+
const errorCallback = typeof observer === 'function' ? undefined : observer.error;
|
|
156
|
+
const eventCallback = typeof observer === 'function' ? undefined : observer.event;
|
|
157
|
+
if ('state' in topic && topic.state === 'success') {
|
|
158
|
+
await nextCallback(topic.body, { socket: this.websocket });
|
|
159
|
+
} else if ('state' in topic && topic.state === 'error' && errorCallback) {
|
|
160
|
+
await errorCallback(topic.body);
|
|
161
|
+
} else if ('type' in topic && topic.type === 'unsubscribe') {
|
|
162
|
+
topicData.subscription.closed = true;
|
|
163
|
+
this.topics.delete(topic.uuid);
|
|
164
|
+
topicData.onUnsubscribes.forEach(it => it('server'));
|
|
165
|
+
} else if ('type' in topic && topic.type === 'event' && eventCallback) {
|
|
166
|
+
/////////// event ///////////
|
|
167
|
+
let eventResponse = typeof eventCallback === 'function' ? await eventCallback(topic.event, topic.body, { socket: this.websocket }) : await eventCallback[topic.event]?.(topic.body, { socket: this.websocket });
|
|
168
|
+
|
|
169
|
+
if (eventResponse !== undefined && eventResponse !== null) {
|
|
170
|
+
const value: TopicServerEventResponse = {
|
|
171
|
+
uuid: topic.uuid,
|
|
172
|
+
eventUUID: topic.eventUUID,
|
|
173
|
+
type: topic.type,
|
|
174
|
+
body: eventResponse
|
|
175
|
+
};
|
|
176
|
+
this.websocket.send(JSON.stringify(value));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private attemptReconnect() {
|
|
185
|
+
if (this.currentRetryCount >= this.retryConnectionCount) {
|
|
186
|
+
console.error(`WebSocket reconnection failed after ${this.retryConnectionCount} attempts`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (this.reconnectTimeout) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.currentRetryCount++;
|
|
194
|
+
console.log(`Attempting to reconnect... (${this.currentRetryCount}/${this.retryConnectionCount})`);
|
|
195
|
+
|
|
196
|
+
this.reconnectTimeout = window.setTimeout(() => {
|
|
197
|
+
this.reconnectTimeout = undefined;
|
|
198
|
+
this.connect();
|
|
199
|
+
// Subscribe messages are already in pendingMessages and will be sent automatically
|
|
200
|
+
}, this.retryConnectionDelay);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private sendMessage(message: TopicRequest | TopicUnsubscribeRequest | TopicSubscribeRequest) {
|
|
204
|
+
if (this.isConnected && this.websocket.readyState === WebSocket.OPEN) {
|
|
205
|
+
this.websocket.send(JSON.stringify(message));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (message.type === 'subscribe' || message.type === 'unsubscribe') {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (this.maxPendingMessages <= 0) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (this.pendingMessages.length >= this.maxPendingMessages) {
|
|
215
|
+
this.pendingMessages.shift();
|
|
216
|
+
}
|
|
217
|
+
this.pendingMessages.push(message);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
sendBinary(data: ArrayBuffer | Blob | Uint8Array) {
|
|
221
|
+
if (this.isConnected && this.websocket.readyState === WebSocket.OPEN) {
|
|
222
|
+
this.websocket.send(data);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (this.maxPendingBinaryMessages <= 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (this.pendingBinaryMessages.length >= this.maxPendingBinaryMessages) {
|
|
229
|
+
this.pendingBinaryMessages.shift();
|
|
230
|
+
}
|
|
231
|
+
this.pendingBinaryMessages.push(data);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async parseIncomingTopic(data: any): Promise<TopicResponse | TopicServerEventRequest | TopicServerEventUnSubscribeRequest | undefined> {
|
|
235
|
+
if (typeof data === 'string') {
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(data);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn('Invalid WebSocket payload:', error);
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (data instanceof ArrayBuffer) {
|
|
245
|
+
return this.parseBinaryTopic(data);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (typeof Blob !== 'undefined' && data instanceof Blob) {
|
|
249
|
+
const buffer = await data.arrayBuffer();
|
|
250
|
+
return this.parseBinaryTopic(buffer);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private parseBinaryTopic(data: ArrayBuffer): TopicResponse | TopicServerEventRequest | TopicServerEventUnSubscribeRequest | undefined {
|
|
257
|
+
if (data.byteLength < 4) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
const view = new DataView(data);
|
|
261
|
+
const jsonLength = view.getUint32(0, true);
|
|
262
|
+
const jsonStart = 4;
|
|
263
|
+
const jsonEnd = jsonStart + jsonLength;
|
|
264
|
+
if (data.byteLength < jsonEnd) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const jsonBytes = new Uint8Array(data, jsonStart, jsonLength);
|
|
269
|
+
let meta: any;
|
|
270
|
+
try {
|
|
271
|
+
const decoder = new TextDecoder();
|
|
272
|
+
meta = JSON.parse(decoder.decode(jsonBytes));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.warn('Invalid WebSocket binary payload:', error);
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const filesMeta = Array.isArray(meta.files) ? meta.files : [];
|
|
279
|
+
const fileMap = new Map<string, any>();
|
|
280
|
+
filesMeta.forEach((file: any) => {
|
|
281
|
+
if (!file || typeof file.id !== 'string' || typeof file.size !== 'number' || typeof file.offset !== 'number') {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const start = jsonEnd + file.offset;
|
|
285
|
+
const end = start + file.size;
|
|
286
|
+
if (start < jsonEnd || end > data.byteLength) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const slice = data.slice(start, end);
|
|
290
|
+
const mime = file.mime || 'application/octet-stream';
|
|
291
|
+
let fileValue: any;
|
|
292
|
+
if (typeof File !== 'undefined') {
|
|
293
|
+
fileValue = new File([slice], file.name || 'file', { type: mime });
|
|
294
|
+
} else {
|
|
295
|
+
fileValue = { name: file.name || 'file', mime, size: file.size, buffer: slice };
|
|
296
|
+
}
|
|
297
|
+
fileMap.set(file.id, fileValue);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const replaceFileRefs = (value: any): any => {
|
|
301
|
+
if (value && typeof value === 'object' && '$file' in value && typeof value.$file === 'string') {
|
|
302
|
+
const file = fileMap.get(value.$file);
|
|
303
|
+
if (file) {
|
|
304
|
+
return file;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (Array.isArray(value)) {
|
|
308
|
+
return value.map(item => replaceFileRefs(item));
|
|
309
|
+
}
|
|
310
|
+
if (value && typeof value === 'object') {
|
|
311
|
+
const result: Record<string, any> = {};
|
|
312
|
+
Object.keys(value).forEach(key => {
|
|
313
|
+
result[key] = replaceFileRefs(value[key]);
|
|
314
|
+
});
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
return value;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
meta.body = replaceFileRefs(meta.body);
|
|
321
|
+
return meta as TopicResponse | TopicServerEventRequest | TopicServerEventUnSubscribeRequest;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private isBinaryPayload(value: any) {
|
|
325
|
+
if (!value) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
if (value && typeof value === 'object' && 'buffer' in value) {
|
|
338
|
+
const bufferValue = (value as any).buffer;
|
|
339
|
+
return this.isBinaryPayload(bufferValue);
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private hasBinaryPayload(value: any): boolean {
|
|
345
|
+
if (this.isBinaryPayload(value)) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(value)) {
|
|
349
|
+
return value.some(item => this.hasBinaryPayload(item));
|
|
350
|
+
}
|
|
351
|
+
if (value && typeof value === 'object') {
|
|
352
|
+
return Object.keys(value).some(key => this.hasBinaryPayload(value[key]));
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private async buildBinaryMessage(body: any, meta: { type?: TopicType; uuid: string; requestUUID: string }) {
|
|
358
|
+
const fileRefs = new Map<any, string>();
|
|
359
|
+
const fileEntries: Array<{ id: string; name: string; mime: string; buffer: Promise<ArrayBuffer | SharedArrayBuffer> }> = [];
|
|
360
|
+
let fileIndex = 0;
|
|
361
|
+
|
|
362
|
+
const registerFile = (value: any) => {
|
|
363
|
+
const existing = fileRefs.get(value);
|
|
364
|
+
if (existing) {
|
|
365
|
+
return existing;
|
|
366
|
+
}
|
|
367
|
+
const id = `f${++fileIndex}`;
|
|
368
|
+
fileRefs.set(value, id);
|
|
369
|
+
|
|
370
|
+
let name = 'file';
|
|
371
|
+
let mime = 'application/octet-stream';
|
|
372
|
+
let buffer: Promise<ArrayBuffer | SharedArrayBuffer>;
|
|
373
|
+
|
|
374
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
375
|
+
const fileLike = value as File | Blob;
|
|
376
|
+
if ('name' in fileLike && typeof (fileLike as File).name === 'string') {
|
|
377
|
+
name = (fileLike as File).name || name;
|
|
378
|
+
}
|
|
379
|
+
mime = fileLike.type || mime;
|
|
380
|
+
buffer = fileLike.arrayBuffer();
|
|
381
|
+
} else if (value && typeof value === 'object' && 'buffer' in value) {
|
|
382
|
+
const fileLike = value as any;
|
|
383
|
+
if (typeof fileLike.name === 'string') {
|
|
384
|
+
name = fileLike.name || name;
|
|
385
|
+
}
|
|
386
|
+
if (typeof fileLike.mime === 'string') {
|
|
387
|
+
mime = fileLike.mime || mime;
|
|
388
|
+
}
|
|
389
|
+
const innerBuffer = fileLike.buffer;
|
|
390
|
+
if (typeof Blob !== 'undefined' && innerBuffer instanceof Blob) {
|
|
391
|
+
buffer = innerBuffer.arrayBuffer();
|
|
392
|
+
} else if (typeof Uint8Array !== 'undefined' && innerBuffer instanceof Uint8Array) {
|
|
393
|
+
const sliced = innerBuffer.buffer.slice(innerBuffer.byteOffset, innerBuffer.byteOffset + innerBuffer.byteLength);
|
|
394
|
+
buffer = Promise.resolve(sliced instanceof ArrayBuffer ? sliced : new Uint8Array(sliced).buffer);
|
|
395
|
+
} else if (typeof ArrayBuffer !== 'undefined' && innerBuffer instanceof ArrayBuffer) {
|
|
396
|
+
buffer = Promise.resolve(innerBuffer as ArrayBuffer);
|
|
397
|
+
} else {
|
|
398
|
+
buffer = Promise.resolve(new ArrayBuffer(0));
|
|
399
|
+
}
|
|
400
|
+
} else if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
|
|
401
|
+
const view = value as Uint8Array;
|
|
402
|
+
const sliced = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
403
|
+
buffer = Promise.resolve(sliced instanceof ArrayBuffer ? sliced : new Uint8Array(sliced).buffer);
|
|
404
|
+
} else if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
|
|
405
|
+
buffer = Promise.resolve(value as ArrayBuffer);
|
|
406
|
+
} else {
|
|
407
|
+
buffer = Promise.resolve(new ArrayBuffer(0));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fileEntries.push({ id, name, mime, buffer });
|
|
411
|
+
return id;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const replaceFiles = (value: any): any => {
|
|
415
|
+
if (this.isBinaryPayload(value)) {
|
|
416
|
+
const id = registerFile(value);
|
|
417
|
+
return { $file: id };
|
|
418
|
+
}
|
|
419
|
+
if (Array.isArray(value)) {
|
|
420
|
+
return value.map(item => replaceFiles(item));
|
|
421
|
+
}
|
|
422
|
+
if (value && typeof value === 'object') {
|
|
423
|
+
const result: Record<string, any> = {};
|
|
424
|
+
Object.keys(value).forEach(key => {
|
|
425
|
+
result[key] = replaceFiles(value[key]);
|
|
426
|
+
});
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
return value;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const payload = replaceFiles(body);
|
|
433
|
+
const buffers = await Promise.all(fileEntries.map(entry => entry.buffer));
|
|
434
|
+
|
|
435
|
+
let offset = 0;
|
|
436
|
+
const files = fileEntries.map((entry, index) => {
|
|
437
|
+
const size = buffers[index].byteLength;
|
|
438
|
+
const fileMeta = {
|
|
439
|
+
id: entry.id,
|
|
440
|
+
name: entry.name,
|
|
441
|
+
mime: entry.mime,
|
|
442
|
+
size,
|
|
443
|
+
offset
|
|
444
|
+
};
|
|
445
|
+
offset += size;
|
|
446
|
+
return fileMeta;
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const json = JSON.stringify({
|
|
450
|
+
type: meta.type,
|
|
451
|
+
uuid: meta.uuid,
|
|
452
|
+
requestUUID: meta.requestUUID,
|
|
453
|
+
body: payload,
|
|
454
|
+
files
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const encoder = new TextEncoder();
|
|
458
|
+
const jsonBytes = encoder.encode(json);
|
|
459
|
+
const header = new Uint8Array(4);
|
|
460
|
+
new DataView(header.buffer).setUint32(0, jsonBytes.byteLength, true);
|
|
461
|
+
|
|
462
|
+
const totalSize = header.byteLength + jsonBytes.byteLength + offset;
|
|
463
|
+
const combined = new Uint8Array(totalSize);
|
|
464
|
+
combined.set(header, 0);
|
|
465
|
+
combined.set(jsonBytes, header.byteLength);
|
|
466
|
+
|
|
467
|
+
let cursor = header.byteLength + jsonBytes.byteLength;
|
|
468
|
+
buffers.forEach(buffer => {
|
|
469
|
+
const view = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer);
|
|
470
|
+
combined.set(view, cursor);
|
|
471
|
+
cursor += buffer.byteLength;
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
return combined.buffer;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
subject(target: string, option?: { type?: TopicType; subScribeBody?: any; observer?: TopicObserverCallBack<any>; onUnsubscribe?: OnUnsubscribeCallback }) {
|
|
478
|
+
const self = this;
|
|
479
|
+
const uuid = RandomUtils.uuid4();
|
|
480
|
+
const onUnsubscribes: OnUnsubscribeCallback[] = [];
|
|
481
|
+
if (option?.onUnsubscribe) {
|
|
482
|
+
onUnsubscribes.push(option.onUnsubscribe);
|
|
483
|
+
}
|
|
484
|
+
if (option?.observer && typeof option.observer !== 'function' && option.observer.complete) {
|
|
485
|
+
onUnsubscribes.push(() => (option.observer as any).complete());
|
|
486
|
+
}
|
|
487
|
+
const topicSubject = new Subject<any>();
|
|
488
|
+
onUnsubscribes.push(() => topicSubject.complete());
|
|
489
|
+
|
|
490
|
+
let isClosed = false;
|
|
491
|
+
let activeSubscription: Subscription | undefined;
|
|
492
|
+
|
|
493
|
+
const unsubscribeLogic = () => {
|
|
494
|
+
if (isClosed) return;
|
|
495
|
+
const unSubscribeTopic: TopicUnsubscribeRequest = { type: 'unsubscribe', uuid };
|
|
496
|
+
self.sendMessage(unSubscribeTopic);
|
|
497
|
+
isClosed = true;
|
|
498
|
+
self.topics.delete(uuid);
|
|
499
|
+
onUnsubscribes.forEach(it => it('manual'));
|
|
500
|
+
if (activeSubscription) {
|
|
501
|
+
activeSubscription.closed = true;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const doSubscribe = <T>(observer?: TopicObserverCallBack<T>) => {
|
|
506
|
+
const subscription: Subscription = {
|
|
507
|
+
closed: isClosed,
|
|
508
|
+
unsubscribe: unsubscribeLogic
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const wrappedObserver: TopicObserverCallBack<T> = {
|
|
512
|
+
next: (data, meta) => {
|
|
513
|
+
topicSubject.next(data);
|
|
514
|
+
if (observer) {
|
|
515
|
+
return typeof observer === 'function' ? observer(data, meta) : observer.next(data, meta);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
error: err => {
|
|
519
|
+
topicSubject.error(err);
|
|
520
|
+
if (observer && typeof observer !== 'function' && observer.error) {
|
|
521
|
+
observer.error(err);
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
event: observer && typeof observer !== 'function' ? observer.event : undefined
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
self.topics.set(uuid, { observer: wrappedObserver, target, subscription, onUnsubscribes });
|
|
528
|
+
const subscribeTopic: TopicSubscribeRequest = { type: 'subscribe', uuid, target: target, body: option?.subScribeBody };
|
|
529
|
+
|
|
530
|
+
self.sendMessage(subscribeTopic);
|
|
531
|
+
// console.log('----------')
|
|
532
|
+
activeSubscription = subscription;
|
|
533
|
+
return subscription;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
if (option?.observer) {
|
|
537
|
+
doSubscribe(option.observer);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
get closed() {
|
|
542
|
+
return isClosed;
|
|
543
|
+
},
|
|
544
|
+
unsubscribe: unsubscribeLogic,
|
|
545
|
+
get observable() {
|
|
546
|
+
return new Observable<any>(subscriber => {
|
|
547
|
+
if (!activeSubscription && !isClosed) {
|
|
548
|
+
doSubscribe();
|
|
549
|
+
}
|
|
550
|
+
return topicSubject.subscribe({
|
|
551
|
+
next: value => subscriber.next(value),
|
|
552
|
+
error: err => subscriber.error(err),
|
|
553
|
+
complete: () => subscriber.complete()
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
send: (body?: any) => {
|
|
558
|
+
if (!activeSubscription && !self.topics.has(uuid) && !isClosed) {
|
|
559
|
+
doSubscribe();
|
|
560
|
+
}
|
|
561
|
+
const requestUUID = RandomUtils.uuid4();
|
|
562
|
+
if (self.hasBinaryPayload(body)) {
|
|
563
|
+
self.buildBinaryMessage(body, { type: option?.type, uuid, requestUUID }).then(buffer => self.sendBinary(buffer));
|
|
564
|
+
} else {
|
|
565
|
+
const value: TopicRequest = { type: option?.type, uuid, requestUUID, body: body };
|
|
566
|
+
self.sendMessage(value);
|
|
567
|
+
}
|
|
568
|
+
return topicSubject.pipe(MessageOperator.filter(data => data && data.requestUUID === requestUUID));
|
|
569
|
+
},
|
|
570
|
+
onUnsubscribe: (callback: OnUnsubscribeCallback) => {
|
|
571
|
+
onUnsubscribes.push(callback);
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
onOpen(callback: () => void) {
|
|
577
|
+
this.onOpenCallback = callback;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
onClose(callback: () => void) {
|
|
581
|
+
this.onCloseCallback = callback;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
subscribe<T = any>(observer: (data: TopicResponse<T> | TopicServerEventRequest<T>) => void) {
|
|
585
|
+
this.observer = observer;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
close() {
|
|
589
|
+
this.isManualClose = true;
|
|
590
|
+
if (this.reconnectTimeout) {
|
|
591
|
+
clearTimeout(this.reconnectTimeout);
|
|
592
|
+
this.reconnectTimeout = undefined;
|
|
593
|
+
}
|
|
594
|
+
this.pendingMessages = [];
|
|
595
|
+
this.pendingBinaryMessages = [];
|
|
596
|
+
this.isConnecting = false;
|
|
597
|
+
this.topics.forEach(topicData => {
|
|
598
|
+
topicData.subscription.closed = true;
|
|
599
|
+
topicData.onUnsubscribes.forEach(it => it('manual'));
|
|
600
|
+
});
|
|
601
|
+
this.topics.clear();
|
|
602
|
+
this.websocket.close();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
reconnect() {
|
|
606
|
+
// 기존 연결 종료
|
|
607
|
+
this.isManualClose = true;
|
|
608
|
+
if (this.reconnectTimeout) {
|
|
609
|
+
clearTimeout(this.reconnectTimeout);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// close 이벤트 후에 재연결하도록 설정
|
|
613
|
+
const reconnectAfterClose = () => {
|
|
614
|
+
console.log('close websocket');
|
|
615
|
+
this.isManualClose = false;
|
|
616
|
+
this.currentRetryCount = 0;
|
|
617
|
+
this.connect();
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// 이미 닫혀있으면 바로 재연결
|
|
621
|
+
if (this.websocket.readyState === WebSocket.CLOSED || this.websocket.readyState === WebSocket.CLOSING) {
|
|
622
|
+
reconnectAfterClose();
|
|
623
|
+
} else {
|
|
624
|
+
// 연결이 닫힐 때까지 기다렸다가 재연결
|
|
625
|
+
this.websocket.addEventListener('close', reconnectAfterClose, { once: true });
|
|
626
|
+
this.websocket.close();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
getReadyState(): number {
|
|
631
|
+
return this.websocket.readyState;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
isConnectionOpen(): boolean {
|
|
635
|
+
return this.isConnected && this.websocket.readyState === WebSocket.OPEN;
|
|
636
|
+
}
|
|
637
|
+
}
|