@simplysm/service-server 13.0.100 → 14.0.4
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 +79 -133
- package/dist/auth/auth-token-payload.js +2 -1
- package/dist/auth/auth-token-payload.js.map +1 -6
- package/dist/auth/jwt-manager.js +21 -21
- package/dist/auth/jwt-manager.js.map +1 -6
- package/dist/core/define-service.d.ts +12 -12
- package/dist/core/define-service.d.ts.map +1 -1
- package/dist/core/define-service.js +77 -63
- package/dist/core/define-service.js.map +1 -6
- package/dist/core/service-executor.d.ts.map +1 -1
- package/dist/core/service-executor.js +42 -32
- package/dist/core/service-executor.js.map +1 -6
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -6
- package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
- package/dist/legacy/v1-auto-update-handler.js +42 -35
- package/dist/legacy/v1-auto-update-handler.js.map +1 -6
- package/dist/protocol/protocol-wrapper.d.ts +9 -9
- package/dist/protocol/protocol-wrapper.js +64 -46
- package/dist/protocol/protocol-wrapper.js.map +1 -6
- package/dist/service-server.d.ts +2 -1
- package/dist/service-server.d.ts.map +1 -1
- package/dist/service-server.js +183 -165
- package/dist/service-server.js.map +1 -6
- package/dist/services/auto-update-service.js +35 -34
- package/dist/services/auto-update-service.js.map +1 -6
- package/dist/services/orm-service.js +114 -120
- package/dist/services/orm-service.js.map +1 -6
- package/dist/transport/http/http-request-handler.d.ts.map +1 -1
- package/dist/transport/http/http-request-handler.js +58 -46
- package/dist/transport/http/http-request-handler.js.map +1 -6
- package/dist/transport/http/static-file-handler.js +42 -39
- package/dist/transport/http/static-file-handler.js.map +1 -6
- package/dist/transport/http/upload-handler.d.ts.map +1 -1
- package/dist/transport/http/upload-handler.js +60 -55
- package/dist/transport/http/upload-handler.js.map +1 -6
- package/dist/transport/socket/service-socket.d.ts +13 -13
- package/dist/transport/socket/service-socket.js +132 -108
- package/dist/transport/socket/service-socket.js.map +1 -6
- package/dist/transport/socket/websocket-handler.d.ts +9 -13
- package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
- package/dist/transport/socket/websocket-handler.js +148 -139
- package/dist/transport/socket/websocket-handler.js.map +1 -6
- package/dist/types/server-options.d.ts +1 -1
- package/dist/types/server-options.d.ts.map +1 -1
- package/dist/types/server-options.js +2 -1
- package/dist/types/server-options.js.map +1 -6
- package/dist/utils/config-manager.js +48 -46
- package/dist/utils/config-manager.js.map +1 -6
- package/dist/workers/service-protocol.worker.js +8 -11
- package/dist/workers/service-protocol.worker.js.map +1 -6
- package/docs/auth.md +28 -16
- package/docs/core.md +113 -54
- package/docs/legacy.md +21 -0
- package/docs/main.md +81 -0
- package/docs/protocol.md +31 -0
- package/docs/services.md +49 -44
- package/docs/transport.md +81 -76
- package/docs/types.md +31 -0
- package/docs/utilities.md +27 -0
- package/package.json +12 -13
- package/src/auth/jwt-manager.ts +2 -2
- package/src/core/define-service.ts +19 -19
- package/src/core/service-executor.ts +23 -17
- package/src/index.ts +10 -12
- package/src/legacy/v1-auto-update-handler.ts +10 -10
- package/src/protocol/protocol-wrapper.ts +16 -16
- package/src/service-server.ts +51 -43
- package/src/services/auto-update-service.ts +1 -1
- package/src/services/orm-service.ts +7 -7
- package/src/transport/http/http-request-handler.ts +16 -10
- package/src/transport/http/static-file-handler.ts +8 -8
- package/src/transport/http/upload-handler.ts +16 -9
- package/src/transport/socket/service-socket.ts +22 -22
- package/src/transport/socket/websocket-handler.ts +50 -70
- package/src/types/server-options.ts +1 -1
- package/src/utils/config-manager.ts +11 -11
- package/dist/services/smtp-client-service.d.ts +0 -8
- package/dist/services/smtp-client-service.d.ts.map +0 -1
- package/dist/services/smtp-client-service.js +0 -46
- package/dist/services/smtp-client-service.js.map +0 -6
- package/docs/server.md +0 -126
- package/src/services/smtp-client-service.ts +0 -59
- package/tests/define-service.spec.ts +0 -66
- package/tests/orm-service.spec.ts +0 -83
- package/tests/service-executor.spec.ts +0 -114
|
@@ -15,10 +15,10 @@ import type {
|
|
|
15
15
|
const logger = consola.withTag("service-server:ServiceSocket");
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* 서비스 소켓 인터페이스
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* 프로토콜 인코딩/디코딩, ping/pong 연결 유지,
|
|
21
|
+
* 이벤트 리스너 추적이 포함된 단일 WebSocket 연결을 관리한다.
|
|
22
22
|
*/
|
|
23
23
|
export interface ServiceSocket {
|
|
24
24
|
readonly connectedAtDateTime: DateTime;
|
|
@@ -27,37 +27,37 @@ export interface ServiceSocket {
|
|
|
27
27
|
authTokenPayload?: AuthTokenPayload;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* WebSocket 연결을 닫는다
|
|
31
31
|
*/
|
|
32
32
|
close(): void;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* 클라이언트에 메시지를 전송한다
|
|
36
36
|
*/
|
|
37
37
|
send(uuid: string, msg: ServiceServerMessage): Promise<number>;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* key/name/info로 이벤트 리스너를 등록한다
|
|
41
41
|
*/
|
|
42
42
|
addListener(key: string, eventName: string, info: unknown): void;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* key로 이벤트 리스너를 제거한다
|
|
46
46
|
*/
|
|
47
47
|
removeListener(key: string): void;
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* 특정 이벤트 이름에 해당하는 모든 이벤트 리스너를 조회한다
|
|
51
51
|
*/
|
|
52
52
|
getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* 이 소켓의 리스너에 존재하는 대상 키를 필터링한다
|
|
56
56
|
*/
|
|
57
57
|
filterEventTargetKeys(targetKeys: string[]): string[];
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
60
|
+
* 이벤트 핸들러를 등록한다
|
|
61
61
|
*/
|
|
62
62
|
on(event: "error", handler: (err: Error) => void): void;
|
|
63
63
|
on(event: "close", handler: (code: number) => void): void;
|
|
@@ -65,10 +65,10 @@ export interface ServiceSocket {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* 서비스 소켓 인스턴스를 생성한다
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* 프로토콜 인코딩/디코딩, ping/pong 연결 유지,
|
|
71
|
+
* 이벤트 리스너 추적이 포함된 단일 WebSocket 연결을 관리한다.
|
|
72
72
|
*/
|
|
73
73
|
export function createServiceSocket(
|
|
74
74
|
socket: WebSocket,
|
|
@@ -77,10 +77,10 @@ export function createServiceSocket(
|
|
|
77
77
|
connReq: FastifyRequest,
|
|
78
78
|
): ServiceSocket {
|
|
79
79
|
// -------------------------------------------------------------------
|
|
80
|
-
//
|
|
80
|
+
// 상태
|
|
81
81
|
// -------------------------------------------------------------------
|
|
82
82
|
|
|
83
|
-
const PING_INTERVAL = 5000; //
|
|
83
|
+
const PING_INTERVAL = 5000; // 5초마다 ping 전송
|
|
84
84
|
const PONG_PACKET = new Uint8Array([0x02]);
|
|
85
85
|
|
|
86
86
|
const protocol = createServerProtocolWrapper();
|
|
@@ -97,7 +97,7 @@ export function createServiceSocket(
|
|
|
97
97
|
const connectedAtDateTime = new DateTime();
|
|
98
98
|
|
|
99
99
|
// -------------------------------------------------------------------
|
|
100
|
-
//
|
|
100
|
+
// 헬퍼
|
|
101
101
|
// -------------------------------------------------------------------
|
|
102
102
|
|
|
103
103
|
async function sendInternal(uuid: string, msg: ServiceServerRawMessage): Promise<number> {
|
|
@@ -121,11 +121,11 @@ export function createServiceSocket(
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// -------------------------------------------------------------------
|
|
124
|
-
//
|
|
124
|
+
// 이벤트 핸들러
|
|
125
125
|
// -------------------------------------------------------------------
|
|
126
126
|
|
|
127
127
|
function onError(err: Error): void {
|
|
128
|
-
logger.error("WebSocket
|
|
128
|
+
logger.error("WebSocket 클라이언트 에러", err);
|
|
129
129
|
emitEvent("error", err);
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -137,7 +137,7 @@ export function createServiceSocket(
|
|
|
137
137
|
|
|
138
138
|
async function onMessage(msgBuffer: Bytes): Promise<void> {
|
|
139
139
|
try {
|
|
140
|
-
//
|
|
140
|
+
// ping에 대한 pong 응답 처리
|
|
141
141
|
if (msgBuffer.length === 1 && msgBuffer[0] === 0x01) {
|
|
142
142
|
if (socket.readyState === WebSocket.OPEN) {
|
|
143
143
|
socket.send(PONG_PACKET);
|
|
@@ -159,12 +159,12 @@ export function createServiceSocket(
|
|
|
159
159
|
emitEvent("message", { uuid: decodeResult.uuid, msg });
|
|
160
160
|
}
|
|
161
161
|
} catch (err) {
|
|
162
|
-
logger.error("
|
|
162
|
+
logger.error("WebSocket 메시지 처리 중 에러 발생", err);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// -------------------------------------------------------------------
|
|
167
|
-
//
|
|
167
|
+
// 설정
|
|
168
168
|
// -------------------------------------------------------------------
|
|
169
169
|
|
|
170
170
|
socket.on("close", onClose);
|
|
@@ -186,7 +186,7 @@ export function createServiceSocket(
|
|
|
186
186
|
}, PING_INTERVAL);
|
|
187
187
|
|
|
188
188
|
// -------------------------------------------------------------------
|
|
189
|
-
//
|
|
189
|
+
// 공개 API
|
|
190
190
|
// -------------------------------------------------------------------
|
|
191
191
|
|
|
192
192
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebSocket } from "ws";
|
|
2
|
-
import { Uuid } from "@simplysm/core-common";
|
|
2
|
+
import { Uuid, env } from "@simplysm/core-common";
|
|
3
3
|
import type { ServiceEventDef, ServiceClientMessage } from "@simplysm/service-common";
|
|
4
4
|
import { createServiceSocket, type ServiceSocket } from "./service-socket";
|
|
5
5
|
import { verifyJwt } from "../../auth/jwt-manager";
|
|
@@ -9,29 +9,24 @@ import consola from "consola";
|
|
|
9
9
|
const logger = consola.withTag("service-server:WebSocketHandler");
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* WebSocket
|
|
12
|
+
* WebSocket 핸들러 인터페이스
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
|
|
15
|
+
* 이벤트 브로드캐스팅을 처리한다.
|
|
16
16
|
*/
|
|
17
17
|
export interface WebSocketHandler {
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* 새 WebSocket 연결을 추가한다
|
|
20
20
|
*/
|
|
21
21
|
addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* 모든 활성 연결을 닫는다
|
|
25
25
|
*/
|
|
26
26
|
closeAll(): void;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*/
|
|
31
|
-
broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Emit event to matching clients
|
|
29
|
+
* 매칭되는 클라이언트에 이벤트를 발생시킨다
|
|
35
30
|
*/
|
|
36
31
|
emit<TInfo, TData>(
|
|
37
32
|
eventDef: ServiceEventDef<TInfo, TData>,
|
|
@@ -41,10 +36,10 @@ export interface WebSocketHandler {
|
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
/**
|
|
44
|
-
*
|
|
39
|
+
* WebSocket 핸들러 인스턴스를 생성한다
|
|
45
40
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
41
|
+
* 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
|
|
42
|
+
* 이벤트 브로드캐스팅을 처리한다.
|
|
48
43
|
*/
|
|
49
44
|
export function createWebSocketHandler(
|
|
50
45
|
runMethod: (def: {
|
|
@@ -56,13 +51,13 @@ export function createWebSocketHandler(
|
|
|
56
51
|
jwtSecret: string | undefined,
|
|
57
52
|
): WebSocketHandler {
|
|
58
53
|
// -------------------------------------------------------------------
|
|
59
|
-
//
|
|
54
|
+
// 상태
|
|
60
55
|
// -------------------------------------------------------------------
|
|
61
56
|
|
|
62
57
|
const socketMap = new Map<string, ServiceSocket>();
|
|
63
58
|
|
|
64
59
|
// -------------------------------------------------------------------
|
|
65
|
-
//
|
|
60
|
+
// 헬퍼
|
|
66
61
|
// -------------------------------------------------------------------
|
|
67
62
|
|
|
68
63
|
async function processRequest(
|
|
@@ -72,7 +67,9 @@ export function createWebSocketHandler(
|
|
|
72
67
|
): Promise<number> {
|
|
73
68
|
try {
|
|
74
69
|
if (message.name.includes(".") && Array.isArray(message.body)) {
|
|
75
|
-
const
|
|
70
|
+
const dotIndex = message.name.indexOf(".");
|
|
71
|
+
const serviceName = message.name.substring(0, dotIndex);
|
|
72
|
+
const methodName = message.name.substring(dotIndex + 1);
|
|
76
73
|
|
|
77
74
|
const result = await runMethod({
|
|
78
75
|
serviceName,
|
|
@@ -99,35 +96,34 @@ export function createWebSocketHandler(
|
|
|
99
96
|
} else if (message.name === "evt:emit") {
|
|
100
97
|
const { keys, data } = message.body as { keys: string[]; data: unknown };
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
keys: targetKeys,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
99
|
+
await Promise.allSettled(
|
|
100
|
+
Array.from(socketMap.values()).map(async (subSock) => {
|
|
101
|
+
const targetKeys = subSock.filterEventTargetKeys(keys);
|
|
102
|
+
if (targetKeys.length > 0) {
|
|
103
|
+
await subSock.send(uuid, {
|
|
104
|
+
name: "evt:on",
|
|
105
|
+
body: { keys: targetKeys, data },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
114
110
|
|
|
115
111
|
return await serviceSocket.send(uuid, { name: "response" });
|
|
116
112
|
} else if (message.name === "auth") {
|
|
117
|
-
if (jwtSecret == null) throw new Error("JWT Secret
|
|
113
|
+
if (jwtSecret == null) throw new Error("JWT Secret이 정의되지 않았습니다.");
|
|
118
114
|
|
|
119
115
|
const token = message.body;
|
|
120
116
|
serviceSocket.authTokenPayload = await verifyJwt(jwtSecret, token);
|
|
121
117
|
return await serviceSocket.send(uuid, { name: "response" });
|
|
122
118
|
} else {
|
|
123
|
-
const err = new Error("
|
|
119
|
+
const err = new Error("유효하지 않은 요청입니다.");
|
|
124
120
|
|
|
125
121
|
return await serviceSocket.send(uuid, {
|
|
126
122
|
name: "error",
|
|
127
123
|
body: {
|
|
128
124
|
name: err.name,
|
|
129
125
|
message: err.message,
|
|
130
|
-
stack: err.stack,
|
|
126
|
+
...(env.DEV ? { stack: err.stack } : {}),
|
|
131
127
|
code: "BAD_MESSAGE",
|
|
132
128
|
},
|
|
133
129
|
});
|
|
@@ -136,7 +132,7 @@ export function createWebSocketHandler(
|
|
|
136
132
|
const error =
|
|
137
133
|
err instanceof Error
|
|
138
134
|
? err
|
|
139
|
-
: new Error(typeof err === "string" ? err : "
|
|
135
|
+
: new Error(typeof err === "string" ? err : "알 수 없는 에러가 발생했습니다.");
|
|
140
136
|
|
|
141
137
|
return serviceSocket.send(uuid, {
|
|
142
138
|
name: "error",
|
|
@@ -144,14 +140,14 @@ export function createWebSocketHandler(
|
|
|
144
140
|
name: error.name,
|
|
145
141
|
message: error.message,
|
|
146
142
|
code: "INTERNAL_ERROR",
|
|
147
|
-
stack: error.stack,
|
|
143
|
+
...(env.DEV ? { stack: error.stack } : {}),
|
|
148
144
|
},
|
|
149
145
|
});
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
148
|
|
|
153
149
|
// -------------------------------------------------------------------
|
|
154
|
-
//
|
|
150
|
+
// 공개 API
|
|
155
151
|
// -------------------------------------------------------------------
|
|
156
152
|
|
|
157
153
|
return {
|
|
@@ -164,7 +160,7 @@ export function createWebSocketHandler(
|
|
|
164
160
|
try {
|
|
165
161
|
const serviceSocket = createServiceSocket(socket, clientId, clientName, connReq);
|
|
166
162
|
|
|
167
|
-
//
|
|
163
|
+
// 기존 연결 해제
|
|
168
164
|
const prevServiceSocket = socketMap.get(clientId);
|
|
169
165
|
if (prevServiceSocket != null) {
|
|
170
166
|
prevServiceSocket.close();
|
|
@@ -172,32 +168,32 @@ export function createWebSocketHandler(
|
|
|
172
168
|
const connectionDateTimeText =
|
|
173
169
|
prevServiceSocket.connectedAtDateTime.toFormatString("yyyy:MM:dd HH:mm:ss.fff");
|
|
174
170
|
logger.debug(
|
|
175
|
-
|
|
171
|
+
`이전 클라이언트 연결 해제됨: ${clientId}: ${connectionDateTimeText}`,
|
|
176
172
|
);
|
|
177
173
|
}
|
|
178
174
|
|
|
179
175
|
socketMap.set(clientId, serviceSocket);
|
|
180
176
|
|
|
181
177
|
serviceSocket.on("close", (code) => {
|
|
182
|
-
logger.debug(
|
|
178
|
+
logger.debug(`클라이언트 연결 해제됨: (code: ${code})`);
|
|
183
179
|
|
|
184
180
|
if (socketMap.get(clientId) !== serviceSocket) return;
|
|
185
181
|
socketMap.delete(clientId);
|
|
186
182
|
});
|
|
187
183
|
|
|
188
184
|
serviceSocket.on("message", async ({ uuid, msg }) => {
|
|
189
|
-
logger.debug("
|
|
185
|
+
logger.debug("요청 수신됨", msg);
|
|
190
186
|
const sentSize = await processRequest(serviceSocket, uuid, msg);
|
|
191
|
-
logger.debug(
|
|
187
|
+
logger.debug(`응답 전송됨 (크기: ${sentSize})`);
|
|
192
188
|
});
|
|
193
189
|
|
|
194
|
-
logger.debug("
|
|
190
|
+
logger.debug("클라이언트 연결됨", {
|
|
195
191
|
clientId,
|
|
196
192
|
remoteAddress: connReq.socket.remoteAddress,
|
|
197
193
|
socketSize: socketMap.size,
|
|
198
194
|
});
|
|
199
195
|
} catch (err) {
|
|
200
|
-
logger.error("
|
|
196
|
+
logger.error("연결 처리 중 에러 발생", err);
|
|
201
197
|
socket.terminate();
|
|
202
198
|
}
|
|
203
199
|
},
|
|
@@ -208,21 +204,6 @@ export function createWebSocketHandler(
|
|
|
208
204
|
}
|
|
209
205
|
},
|
|
210
206
|
|
|
211
|
-
async broadcastReload(
|
|
212
|
-
clientName: string | undefined,
|
|
213
|
-
changedFileSet: Set<string>,
|
|
214
|
-
): Promise<void> {
|
|
215
|
-
for (const serviceSocket of socketMap.values()) {
|
|
216
|
-
await serviceSocket.send(Uuid.generate().toString(), {
|
|
217
|
-
name: "reload",
|
|
218
|
-
body: {
|
|
219
|
-
clientName,
|
|
220
|
-
changedFileSet,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
|
|
226
207
|
async emit<TInfo, TData>(
|
|
227
208
|
eventDef: ServiceEventDef<TInfo, TData>,
|
|
228
209
|
infoSelector: (item: TInfo) => boolean,
|
|
@@ -234,18 +215,17 @@ export function createWebSocketHandler(
|
|
|
234
215
|
.filter((item) => infoSelector(item.info as TInfo))
|
|
235
216
|
.map((item) => item.key);
|
|
236
217
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
keys: subTargetKeys,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
218
|
+
await Promise.allSettled(
|
|
219
|
+
Array.from(socketMap.values()).map(async (subSock) => {
|
|
220
|
+
const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
|
|
221
|
+
if (subTargetKeys.length > 0) {
|
|
222
|
+
await subSock.send(Uuid.generate().toString(), {
|
|
223
|
+
name: "evt:on",
|
|
224
|
+
body: { keys: subTargetKeys, data },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
249
229
|
},
|
|
250
230
|
};
|
|
251
231
|
}
|
|
@@ -5,12 +5,12 @@ import consola from "consola";
|
|
|
5
5
|
|
|
6
6
|
const logger = consola.withTag("service-server:ConfigManager");
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// 값: 설정 객체, 키: 파일 경로
|
|
9
9
|
const _cache = new LazyGcMap<string, unknown>({
|
|
10
|
-
gcInterval: 10 * 60 * 1000, //
|
|
11
|
-
expireTime: 60 * 60 * 1000, //
|
|
10
|
+
gcInterval: 10 * 60 * 1000, // 10분마다
|
|
11
|
+
expireTime: 60 * 60 * 1000, // 1시간 후 만료
|
|
12
12
|
onExpire: async (filePath) => {
|
|
13
|
-
logger.debug(
|
|
13
|
+
logger.debug(`설정 캐시 만료 및 워처 해제됨: ${path.basename(filePath)}`);
|
|
14
14
|
await closeWatcher(filePath);
|
|
15
15
|
},
|
|
16
16
|
});
|
|
@@ -18,18 +18,18 @@ const _cache = new LazyGcMap<string, unknown>({
|
|
|
18
18
|
const _watchers = new Map<string, FsWatcher>();
|
|
19
19
|
|
|
20
20
|
export async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined> {
|
|
21
|
-
// 1.
|
|
21
|
+
// 1. 캐시 히트 (시간 자동 갱신)
|
|
22
22
|
if (_cache.has(filePath)) {
|
|
23
23
|
return _cache.get(filePath) as TConfig;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (!(await fsx.exists(filePath))) return undefined;
|
|
27
27
|
|
|
28
|
-
// 2.
|
|
28
|
+
// 2. 로드 및 캐시
|
|
29
29
|
const config = await fsx.readJson(filePath);
|
|
30
30
|
_cache.set(filePath, config);
|
|
31
31
|
|
|
32
|
-
// 3.
|
|
32
|
+
// 3. 워처 등록
|
|
33
33
|
if (!_watchers.has(filePath)) {
|
|
34
34
|
try {
|
|
35
35
|
const watcher = await FsWatcher.watch([filePath]);
|
|
@@ -39,20 +39,20 @@ export async function getConfig<TConfig>(filePath: string): Promise<TConfig | un
|
|
|
39
39
|
if (!(await fsx.exists(filePath))) {
|
|
40
40
|
_cache.delete(filePath);
|
|
41
41
|
await closeWatcher(filePath);
|
|
42
|
-
logger.debug(
|
|
42
|
+
logger.debug(`설정 파일 삭제됨: ${path.basename(filePath)}`);
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
47
|
const newConfig = await fsx.readJson(filePath);
|
|
48
48
|
_cache.set(filePath, newConfig);
|
|
49
|
-
logger.debug(
|
|
49
|
+
logger.debug(`설정 파일 실시간 리로드됨: ${path.basename(filePath)}`);
|
|
50
50
|
} catch (err) {
|
|
51
|
-
logger.warn(
|
|
51
|
+
logger.warn(`설정 파일 리로드 실패: ${filePath}`, err);
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
54
|
} catch (err) {
|
|
55
|
-
logger.error(
|
|
55
|
+
logger.error(`워치 실패: ${filePath}`, err);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type ServiceMethods } from "../core/define-service";
|
|
2
|
-
import type { SmtpClientSendByDefaultOption, SmtpClientSendOption } from "@simplysm/service-common";
|
|
3
|
-
export declare const SmtpClientService: import("..").ServiceDefinition<{
|
|
4
|
-
send(options: SmtpClientSendOption): Promise<string>;
|
|
5
|
-
sendByConfig(configName: string, options: SmtpClientSendByDefaultOption): Promise<string>;
|
|
6
|
-
}>;
|
|
7
|
-
export type SmtpClientServiceType = ServiceMethods<typeof SmtpClientService>;
|
|
8
|
-
//# sourceMappingURL=smtp-client-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"smtp-client-service.d.ts","sourceRoot":"","sources":["..\\..\\src\\services\\smtp-client-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAEV,6BAA6B,EAC7B,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAElC,eAAO,MAAM,iBAAiB;kBACR,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;6BA6B3B,MAAM,WAAW,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC;EAkB9F,CAAC;AAEJ,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import nodemailer from "nodemailer";
|
|
2
|
-
import { defineService } from "../core/define-service.js";
|
|
3
|
-
const SmtpClientService = defineService("SmtpClient", (ctx) => ({
|
|
4
|
-
async send(options) {
|
|
5
|
-
return new Promise((resolve, reject) => {
|
|
6
|
-
const transport = nodemailer.createTransport({
|
|
7
|
-
host: options.host,
|
|
8
|
-
port: options.port,
|
|
9
|
-
secure: options.secure,
|
|
10
|
-
auth: options.user != null ? {
|
|
11
|
-
user: options.user,
|
|
12
|
-
pass: options.pass
|
|
13
|
-
} : void 0,
|
|
14
|
-
tls: {
|
|
15
|
-
rejectUnauthorized: false
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
transport.sendMail(options, (err, info) => {
|
|
19
|
-
if (err) {
|
|
20
|
-
reject(err);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
resolve(info.messageId);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
},
|
|
27
|
-
async sendByConfig(configName, options) {
|
|
28
|
-
const config = (await ctx.getConfig("smtp"))[configName];
|
|
29
|
-
if (config == null) {
|
|
30
|
-
throw new Error(`SMTP config not found: ${configName}`);
|
|
31
|
-
}
|
|
32
|
-
return this.send({
|
|
33
|
-
user: config.user,
|
|
34
|
-
pass: config.pass,
|
|
35
|
-
host: config.host,
|
|
36
|
-
port: config.port,
|
|
37
|
-
secure: config.secure,
|
|
38
|
-
from: `"${config.senderName}" <${config.senderEmail ?? config.user}>`,
|
|
39
|
-
...options
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}));
|
|
43
|
-
export {
|
|
44
|
-
SmtpClientService
|
|
45
|
-
};
|
|
46
|
-
//# sourceMappingURL=smtp-client-service.js.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/services/smtp-client-service.ts"],
|
|
4
|
-
"mappings": "AAAA,OAAO,gBAAgB;AACvB,SAAS,qBAA0C;AAO5C,MAAM,oBAAoB,cAAc,cAAc,CAAC,SAAS;AAAA,EACrE,MAAM,KAAK,SAAgD;AACzD,WAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,YAAM,YAAY,WAAW,gBAAgB;AAAA,QAC3C,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,QAAQ,QAAQ;AAAA,QAChB,MACE,QAAQ,QAAQ,OACZ;AAAA,UACE,MAAM,QAAQ;AAAA,UACd,MAAM,QAAQ;AAAA,QAChB,IACA;AAAA,QACN,KAAK;AAAA,UACH,oBAAoB;AAAA,QACtB;AAAA,MACF,CAAC;AAED,gBAAU,SAAS,SAAuC,CAAC,KAAK,SAAS;AACvE,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AAEA,gBAAQ,KAAK,SAAS;AAAA,MACxB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,YAAoB,SAAyD;AAC9F,UAAM,UACJ,MAAM,IAAI,UAAgE,MAAM,GAChF,UAAU;AACZ,QAAI,UAAU,MAAM;AAClB,YAAM,IAAI,MAAM,0BAA0B,UAAU,EAAE;AAAA,IACxD;AAEA,WAAO,KAAK,KAAK;AAAA,MACf,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf,MAAM,IAAI,OAAO,UAAU,MAAM,OAAO,eAAe,OAAO,IAAI;AAAA,MAClE,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACF,EAAE;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
package/docs/server.md
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# Server
|
|
2
|
-
|
|
3
|
-
## `ServiceServerOptions`
|
|
4
|
-
|
|
5
|
-
Configuration options for `ServiceServer`.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
interface ServiceServerOptions {
|
|
9
|
-
rootPath: string;
|
|
10
|
-
port: number;
|
|
11
|
-
ssl?: {
|
|
12
|
-
pfxBytes: Uint8Array;
|
|
13
|
-
passphrase: string;
|
|
14
|
-
};
|
|
15
|
-
auth?: {
|
|
16
|
-
jwtSecret: string;
|
|
17
|
-
};
|
|
18
|
-
services: ServiceDefinition[];
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
| Field | Type | Description |
|
|
23
|
-
|-------|------|-------------|
|
|
24
|
-
| `rootPath` | `string` | Root path for static files, uploads, and config |
|
|
25
|
-
| `port` | `number` | Server port |
|
|
26
|
-
| `ssl` | `object` | SSL/TLS config (PFX certificate) |
|
|
27
|
-
| `ssl.pfxBytes` | `Uint8Array` | PFX certificate bytes |
|
|
28
|
-
| `ssl.passphrase` | `string` | Certificate passphrase |
|
|
29
|
-
| `auth` | `object` | Authentication config |
|
|
30
|
-
| `auth.jwtSecret` | `string` | JWT signing secret |
|
|
31
|
-
| `services` | `ServiceDefinition[]` | Service definitions to register |
|
|
32
|
-
|
|
33
|
-
## `ServiceServer`
|
|
34
|
-
|
|
35
|
-
Fastify-based service server with WebSocket support, JWT authentication, and built-in services. Extends `EventEmitter`.
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{
|
|
39
|
-
ready: void;
|
|
40
|
-
close: void;
|
|
41
|
-
}> {
|
|
42
|
-
isOpen: boolean;
|
|
43
|
-
readonly fastify: FastifyInstance;
|
|
44
|
-
readonly options: ServiceServerOptions;
|
|
45
|
-
|
|
46
|
-
constructor(options: ServiceServerOptions);
|
|
47
|
-
|
|
48
|
-
listen(): Promise<void>;
|
|
49
|
-
close(): Promise<void>;
|
|
50
|
-
broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
|
|
51
|
-
emitEvent<TInfo, TData>(
|
|
52
|
-
eventDef: ServiceEventDef<TInfo, TData>,
|
|
53
|
-
infoSelector: (item: TInfo) => boolean,
|
|
54
|
-
data: TData,
|
|
55
|
-
): Promise<void>;
|
|
56
|
-
signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
|
|
57
|
-
verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
| Property | Type | Description |
|
|
62
|
-
|----------|------|-------------|
|
|
63
|
-
| `isOpen` | `boolean` | Whether server is listening |
|
|
64
|
-
| `fastify` | `FastifyInstance` | Underlying Fastify instance |
|
|
65
|
-
| `options` | `ServiceServerOptions` | Server options |
|
|
66
|
-
|
|
67
|
-
| Method | Description |
|
|
68
|
-
|--------|-------------|
|
|
69
|
-
| `listen()` | Start the server (registers plugins, routes, WebSocket, graceful shutdown) |
|
|
70
|
-
| `close()` | Stop the server (close all connections) |
|
|
71
|
-
| `broadcastReload()` | Broadcast reload to all connected clients |
|
|
72
|
-
| `emitEvent()` | Emit event to matching clients |
|
|
73
|
-
| `signAuthToken()` | Sign a JWT token |
|
|
74
|
-
| `verifyAuthToken()` | Verify and decode a JWT token |
|
|
75
|
-
|
|
76
|
-
### Registered Routes
|
|
77
|
-
|
|
78
|
-
| Route | Method | Description |
|
|
79
|
-
|-------|--------|-------------|
|
|
80
|
-
| `/api/:service/:method` | GET/POST | HTTP service method endpoint |
|
|
81
|
-
| `/upload` | POST | File upload endpoint (multipart) |
|
|
82
|
-
| `/ws` | WebSocket | WebSocket service endpoint (Protocol V2) |
|
|
83
|
-
| `/` | WebSocket | WebSocket endpoint (V1 legacy + V2) |
|
|
84
|
-
| `/*` | GET | Static file serving from `www/` |
|
|
85
|
-
|
|
86
|
-
### Registered Plugins
|
|
87
|
-
|
|
88
|
-
- `@fastify/websocket` - WebSocket support
|
|
89
|
-
- `@fastify/helmet` - Security headers (CSP, HSTS)
|
|
90
|
-
- `@fastify/multipart` - File upload
|
|
91
|
-
- `@fastify/static` - Static file serving
|
|
92
|
-
- `@fastify/cors` - CORS support
|
|
93
|
-
|
|
94
|
-
## `createServiceServer`
|
|
95
|
-
|
|
96
|
-
Factory function to create a `ServiceServer` instance.
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
function createServiceServer<TAuthInfo = unknown>(
|
|
100
|
-
options: ServiceServerOptions,
|
|
101
|
-
): ServiceServer<TAuthInfo>;
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Utils
|
|
105
|
-
|
|
106
|
-
### `getConfig`
|
|
107
|
-
|
|
108
|
-
Read JSON configuration from file with caching and live-reload via file watcher. Cache expires after 1 hour.
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Legacy
|
|
115
|
-
|
|
116
|
-
### `handleV1Connection`
|
|
117
|
-
|
|
118
|
-
V1 legacy client handler. Only auto-update is supported; all other requests return an upgrade-required error.
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
function handleV1Connection(
|
|
122
|
-
socket: WebSocket,
|
|
123
|
-
autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
|
|
124
|
-
clientNameSetter?: (clientName: string | undefined) => void,
|
|
125
|
-
): void;
|
|
126
|
-
```
|