@simplysm/service-server 13.0.99 → 14.0.1

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.
Files changed (83) hide show
  1. package/dist/auth/auth-token-payload.js +2 -1
  2. package/dist/auth/auth-token-payload.js.map +1 -6
  3. package/dist/auth/jwt-manager.js +21 -21
  4. package/dist/auth/jwt-manager.js.map +1 -6
  5. package/dist/core/define-service.d.ts +12 -12
  6. package/dist/core/define-service.d.ts.map +1 -1
  7. package/dist/core/define-service.js +77 -63
  8. package/dist/core/define-service.js.map +1 -6
  9. package/dist/core/service-executor.d.ts.map +1 -1
  10. package/dist/core/service-executor.js +42 -32
  11. package/dist/core/service-executor.js.map +1 -6
  12. package/dist/index.d.ts +0 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +11 -2
  15. package/dist/index.js.map +1 -6
  16. package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
  17. package/dist/legacy/v1-auto-update-handler.js +42 -35
  18. package/dist/legacy/v1-auto-update-handler.js.map +1 -6
  19. package/dist/protocol/protocol-wrapper.d.ts +9 -9
  20. package/dist/protocol/protocol-wrapper.js +64 -46
  21. package/dist/protocol/protocol-wrapper.js.map +1 -6
  22. package/dist/service-server.d.ts +2 -0
  23. package/dist/service-server.d.ts.map +1 -1
  24. package/dist/service-server.js +187 -165
  25. package/dist/service-server.js.map +1 -6
  26. package/dist/services/auto-update-service.js +35 -34
  27. package/dist/services/auto-update-service.js.map +1 -6
  28. package/dist/services/orm-service.js +114 -120
  29. package/dist/services/orm-service.js.map +1 -6
  30. package/dist/transport/http/http-request-handler.d.ts.map +1 -1
  31. package/dist/transport/http/http-request-handler.js +58 -46
  32. package/dist/transport/http/http-request-handler.js.map +1 -6
  33. package/dist/transport/http/static-file-handler.js +42 -39
  34. package/dist/transport/http/static-file-handler.js.map +1 -6
  35. package/dist/transport/http/upload-handler.d.ts.map +1 -1
  36. package/dist/transport/http/upload-handler.js +60 -55
  37. package/dist/transport/http/upload-handler.js.map +1 -6
  38. package/dist/transport/socket/service-socket.d.ts +13 -13
  39. package/dist/transport/socket/service-socket.js +132 -108
  40. package/dist/transport/socket/service-socket.js.map +1 -6
  41. package/dist/transport/socket/websocket-handler.d.ts +10 -10
  42. package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
  43. package/dist/transport/socket/websocket-handler.js +154 -139
  44. package/dist/transport/socket/websocket-handler.js.map +1 -6
  45. package/dist/types/server-options.d.ts +1 -1
  46. package/dist/types/server-options.d.ts.map +1 -1
  47. package/dist/types/server-options.js +2 -1
  48. package/dist/types/server-options.js.map +1 -6
  49. package/dist/utils/config-manager.js +48 -46
  50. package/dist/utils/config-manager.js.map +1 -6
  51. package/dist/workers/service-protocol.worker.js +8 -11
  52. package/dist/workers/service-protocol.worker.js.map +1 -6
  53. package/package.json +12 -14
  54. package/src/auth/jwt-manager.ts +2 -2
  55. package/src/core/define-service.ts +19 -19
  56. package/src/core/service-executor.ts +23 -17
  57. package/src/index.ts +10 -12
  58. package/src/legacy/v1-auto-update-handler.ts +10 -10
  59. package/src/protocol/protocol-wrapper.ts +16 -16
  60. package/src/service-server.ts +52 -39
  61. package/src/services/auto-update-service.ts +1 -1
  62. package/src/services/orm-service.ts +7 -7
  63. package/src/transport/http/http-request-handler.ts +16 -10
  64. package/src/transport/http/static-file-handler.ts +8 -8
  65. package/src/transport/http/upload-handler.ts +16 -9
  66. package/src/transport/socket/service-socket.ts +22 -22
  67. package/src/transport/socket/websocket-handler.ts +59 -60
  68. package/src/types/server-options.ts +1 -1
  69. package/src/utils/config-manager.ts +11 -11
  70. package/README.md +0 -163
  71. package/dist/services/smtp-client-service.d.ts +0 -8
  72. package/dist/services/smtp-client-service.d.ts.map +0 -1
  73. package/dist/services/smtp-client-service.js +0 -46
  74. package/dist/services/smtp-client-service.js.map +0 -6
  75. package/docs/auth.md +0 -59
  76. package/docs/core.md +0 -133
  77. package/docs/server.md +0 -126
  78. package/docs/services.md +0 -58
  79. package/docs/transport.md +0 -164
  80. package/src/services/smtp-client-service.ts +0 -59
  81. package/tests/define-service.spec.ts +0 -66
  82. package/tests/orm-service.spec.ts +0 -83
  83. package/tests/service-executor.spec.ts +0 -114
@@ -15,7 +15,7 @@ import consola from "consola";
15
15
 
16
16
  const logger = consola.withTag("service-server:OrmService");
17
17
 
18
- // Static state needs to live outside the factory (shared across calls)
18
+ // 정적 상태는 팩토리 외부에 존재해야 (호출 공유)
19
19
  const socketConns = new WeakMap<ServiceSocket, Map<number, DbConn>>();
20
20
 
21
21
  export const OrmService = defineService(
@@ -24,7 +24,7 @@ export const OrmService = defineService(
24
24
  const sock = (): ServiceSocket => {
25
25
  const socket = ctx.socket;
26
26
  if (socket == null) {
27
- throw new Error("WebSocket connection is required. ORM service cannot be used over HTTP.");
27
+ throw new Error("WebSocket 연결이 필요합니다. ORM 서비스는 HTTP로 사용할 없습니다.");
28
28
  }
29
29
  return socket;
30
30
  };
@@ -34,7 +34,7 @@ export const OrmService = defineService(
34
34
  opt.configName
35
35
  ];
36
36
  if (config == null) {
37
- throw new Error(`ORM configuration not found: ${opt.configName}`);
37
+ throw new Error(`ORM 설정을 찾을 없습니다: ${opt.configName}`);
38
38
  }
39
39
  return { ...config, ...opt.config } as DbConnConfig;
40
40
  };
@@ -43,7 +43,7 @@ export const OrmService = defineService(
43
43
  const myConns = socketConns.get(sock());
44
44
  const conn = myConns?.get(connId);
45
45
  if (conn == null) {
46
- throw new Error("Not connected to database. (Invalid Connection ID)");
46
+ throw new Error("데이터베이스에 연결되지 않았습니다. (유효하지 않은 연결 ID)");
47
47
  }
48
48
  return conn;
49
49
  };
@@ -71,7 +71,7 @@ export const OrmService = defineService(
71
71
  sock().on("close", async () => {
72
72
  if (myConns == null) return;
73
73
 
74
- logger.debug("Socket close detected: cleaning up all open DB connections.");
74
+ logger.debug("소켓 종료 감지: 열린 모든 DB 연결을 정리합니다.");
75
75
  const conns = Array.from(myConns.values());
76
76
 
77
77
  await Promise.all(
@@ -81,7 +81,7 @@ export const OrmService = defineService(
81
81
  await conn.close();
82
82
  }
83
83
  } catch (err) {
84
- logger.warn("Error ignored during forced DB connection close", err);
84
+ logger.warn("강제 DB 연결 종료 에러 무시됨", err);
85
85
  }
86
86
  }),
87
87
  );
@@ -110,7 +110,7 @@ export const OrmService = defineService(
110
110
  const conn = getConn(connId);
111
111
  await conn.close();
112
112
  } catch (err) {
113
- logger.warn("Error ignored during DB connection close", err);
113
+ logger.warn("DB 연결 종료 에러 무시됨", err);
114
114
  }
115
115
  },
116
116
 
@@ -16,49 +16,55 @@ export async function handleHttpRequest<TAuthInfo = unknown>(
16
16
  ): Promise<void> {
17
17
  const { service, method } = req.params as { service: string; method: string };
18
18
 
19
- // ClientName header
19
+ // ClientName 헤더
20
20
  const clientName = req.headers["x-sd-client-name"] as string | undefined;
21
- if (clientName == null) throw new Error("ClientName header is required");
21
+ if (clientName == null) throw new Error("ClientName 헤더가 필요합니다");
22
22
 
23
- // Parse and verify Authorization header
23
+ // Authorization 헤더 파싱 검증
24
24
  let authTokenPayload: AuthTokenPayload<TAuthInfo> | undefined;
25
25
  try {
26
26
  const authHeader = req.headers.authorization;
27
27
  if (authHeader != null) {
28
- if (jwtSecret == null) throw new Error("JWT Secret is not defined.");
28
+ if (jwtSecret == null) throw new Error("JWT Secret 정의되지 않았습니다.");
29
29
 
30
30
  const token = authHeader.split(" ")[1]; // "Bearer <token>"
31
31
  authTokenPayload = await verifyJwt<TAuthInfo>(jwtSecret, token);
32
32
  }
33
33
  } catch (err) {
34
34
  reply.status(401).send({
35
- error: "Unauthorized",
35
+ error: "인증 실패",
36
36
  message: err instanceof Error ? err.message : String(err),
37
37
  });
38
38
  return;
39
39
  }
40
40
 
41
- // Parse parameters
41
+ // 매개변수 파싱
42
42
  let params: unknown[] | undefined;
43
43
  if (req.method === "GET") {
44
44
  const query = req.query as { json?: string };
45
45
  if (typeof query.json !== "string") {
46
- throw new Error("JSON query parameter required");
46
+ throw new Error("JSON 쿼리 파라미터가 필요합니다");
47
47
  }
48
48
  params = json.parse(query.json);
49
49
  } else if (req.method === "POST") {
50
50
  if (!Array.isArray(req.body)) {
51
51
  reply.status(400).send({
52
- error: "Bad Request",
53
- message: "Request body must be an array.",
52
+ error: "잘못된 요청",
53
+ message: "요청 본문은 배열이어야 합니다.",
54
54
  });
55
55
  return;
56
56
  }
57
57
 
58
58
  params = req.body as unknown[];
59
+ } else {
60
+ reply.status(405).send({
61
+ error: "Method Not Allowed",
62
+ message: `${req.method} 메서드는 지원하지 않습니다.`,
63
+ });
64
+ return;
59
65
  }
60
66
 
61
- // Execute service and send response
67
+ // 서비스 실행 응답 전송
62
68
  if (params != null) {
63
69
  const serviceResult = await runMethod({
64
70
  serviceName: service,
@@ -14,12 +14,12 @@ export async function handleStaticFile(
14
14
  let targetFilePath = path.resolve(rootPath, "www", urlPath);
15
15
  const allowedRootPath = path.resolve(rootPath, "www");
16
16
 
17
- // Security guard for targetPath (prevent path traversal)
17
+ // targetPath 보안 가드 (경로 탐색 공격 방지)
18
18
  if (targetFilePath !== allowedRootPath && !pathx.isChildPath(targetFilePath, allowedRootPath)) {
19
- throw new Error("Access denied");
19
+ throw new Error("접근이 거부되었습니다");
20
20
  }
21
21
 
22
- // Redirect with trailing slash for directories (standard web server behavior)
22
+ // 디렉토리에 대해 슬래시를 추가하여 리다이렉트 (표준 서버 동작)
23
23
  if ((await fsx.exists(targetFilePath)) && (await fsx.stat(targetFilePath)).isDirectory()) {
24
24
  if (!urlPath.endsWith("/")) {
25
25
  const urlObj = new URL(req.raw.url!, "http://localhost");
@@ -29,15 +29,15 @@ export async function handleStaticFile(
29
29
  targetFilePath = path.resolve(targetFilePath, "index.html");
30
30
  }
31
31
 
32
- // Permission check (hidden files, etc.)
32
+ // 권한 확인 (숨김 파일 )
33
33
  if (path.basename(targetFilePath).startsWith(".")) {
34
- const errorMessage = "You do not have permission to access this file.";
34
+ const errorMessage = " 파일에 접근할 권한이 없습니다.";
35
35
  responseErrorHtml(reply, 403, errorMessage);
36
36
  logger.warn(`[403] ${errorMessage} (${targetFilePath})`);
37
37
  return;
38
38
  }
39
39
 
40
- // Send file
40
+ // 파일 전송
41
41
  const filename = path.basename(targetFilePath);
42
42
  const directory = path.dirname(targetFilePath);
43
43
 
@@ -46,11 +46,11 @@ export async function handleStaticFile(
46
46
  } catch (err: unknown) {
47
47
  const error = err as { code?: string };
48
48
  if (error.code === "ENOENT") {
49
- const errorMessage = "File not found.";
49
+ const errorMessage = "파일을 찾을 수 없습니다.";
50
50
  responseErrorHtml(reply, 404, errorMessage);
51
51
  logger.warn(`[404] ${errorMessage} (${targetFilePath})`);
52
52
  } else {
53
- const errorMessage = "An error occurred while sending the file.";
53
+ const errorMessage = "파일 전송 에러가 발생했습니다.";
54
54
  responseErrorHtml(reply, 500, errorMessage);
55
55
  logger.error(`[500] ${errorMessage}`, err);
56
56
  }
@@ -17,24 +17,24 @@ export async function handleUpload(
17
17
  jwtSecret: string | undefined,
18
18
  ): Promise<void> {
19
19
  if (!req.isMultipart()) {
20
- reply.status(400).send("Multipart request expected");
20
+ reply.status(400).send("Multipart 요청이 필요합니다");
21
21
  return;
22
22
  }
23
23
 
24
- // Auth check
24
+ // 인증 확인
25
25
  try {
26
26
  const authHeader = req.headers.authorization;
27
27
  if (authHeader == null) {
28
- throw new Error("Authentication token is missing.");
28
+ throw new Error("인증 토큰이 누락되었습니다.");
29
29
  }
30
30
  if (jwtSecret == null) {
31
- throw new Error("JWT Secret is not defined.");
31
+ throw new Error("JWT Secret 정의되지 않았습니다.");
32
32
  }
33
33
  const token = authHeader.split(" ")[1];
34
34
  await verifyJwt(jwtSecret, token);
35
35
  } catch (err) {
36
36
  reply.status(401).send({
37
- error: "Unauthorized",
37
+ error: "인증 실패",
38
38
  message: err instanceof Error ? err.message : String(err),
39
39
  });
40
40
  return;
@@ -58,7 +58,7 @@ export async function handleUpload(
58
58
  await pipeline(part.file, createWriteStream(currentSavePath));
59
59
 
60
60
  if (part.file.truncated) {
61
- throw new Error(`File limit exceeded: ${originalFilename}`);
61
+ throw new Error(`파일 크기 제한 초과: ${originalFilename}`);
62
62
  }
63
63
 
64
64
  const stats = await fsx.stat(currentSavePath);
@@ -75,13 +75,20 @@ export async function handleUpload(
75
75
 
76
76
  reply.send(result);
77
77
  } catch (err) {
78
- logger.error("Upload Error", err);
78
+ logger.error("업로드 에러", err);
79
79
 
80
80
  if (currentSavePath != null) {
81
81
  await fsx.rm(currentSavePath).catch(() => {});
82
- logger.warn(`Incomplete file deleted: ${currentSavePath}`);
82
+ logger.warn(`불완전한 파일 삭제됨: ${currentSavePath}`);
83
83
  }
84
84
 
85
- reply.code(500).send("Upload Failed");
85
+ // 이미 저장된 파일 정리
86
+ for (const savedFile of result) {
87
+ const savedPath = path.resolve(rootPath, "www", savedFile.path);
88
+ await fsx.rm(savedPath).catch(() => {});
89
+ logger.warn(`이미 저장된 파일 삭제됨: ${savedPath}`);
90
+ }
91
+
92
+ reply.code(500).send("업로드 실패");
86
93
  }
87
94
  }
@@ -15,10 +15,10 @@ import type {
15
15
  const logger = consola.withTag("service-server:ServiceSocket");
16
16
 
17
17
  /**
18
- * Service socket interface
18
+ * 서비스 소켓 인터페이스
19
19
  *
20
- * Manages a single WebSocket connection with protocol encoding/decoding,
21
- * ping/pong keep-alive, and event listener tracking.
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
- * Close the WebSocket connection
30
+ * WebSocket 연결을 닫는다
31
31
  */
32
32
  close(): void;
33
33
 
34
34
  /**
35
- * Send a message to the client
35
+ * 클라이언트에 메시지를 전송한다
36
36
  */
37
37
  send(uuid: string, msg: ServiceServerMessage): Promise<number>;
38
38
 
39
39
  /**
40
- * Register an event listener with key/name/info
40
+ * key/name/info로 이벤트 리스너를 등록한다
41
41
  */
42
42
  addListener(key: string, eventName: string, info: unknown): void;
43
43
 
44
44
  /**
45
- * Remove an event listener by key
45
+ * key로 이벤트 리스너를 제거한다
46
46
  */
47
47
  removeListener(key: string): void;
48
48
 
49
49
  /**
50
- * Get all event listeners for a specific event name
50
+ * 특정 이벤트 이름에 해당하는 모든 이벤트 리스너를 조회한다
51
51
  */
52
52
  getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
53
53
 
54
54
  /**
55
- * Filter target keys that exist in this socket's listeners
55
+ * 소켓의 리스너에 존재하는 대상 키를 필터링한다
56
56
  */
57
57
  filterEventTargetKeys(targetKeys: string[]): string[];
58
58
 
59
59
  /**
60
- * Register event handlers
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
- * Create a service socket instance
68
+ * 서비스 소켓 인스턴스를 생성한다
69
69
  *
70
- * Manages a single WebSocket connection with protocol encoding/decoding,
71
- * ping/pong keep-alive, and event listener tracking.
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
- // State
80
+ // 상태
81
81
  // -------------------------------------------------------------------
82
82
 
83
- const PING_INTERVAL = 5000; // Send ping every 5s
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
- // Helpers
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
- // Event Handlers
124
+ // 이벤트 핸들러
125
125
  // -------------------------------------------------------------------
126
126
 
127
127
  function onError(err: Error): void {
128
- logger.error("WebSocket client error", err);
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
- // Handle pong response to ping
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("Error processing WebSocket message", err);
162
+ logger.error("WebSocket 메시지 처리 중 에러 발생", err);
163
163
  }
164
164
  }
165
165
 
166
166
  // -------------------------------------------------------------------
167
- // Setup
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
- // Public API
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,29 @@ import consola from "consola";
9
9
  const logger = consola.withTag("service-server:WebSocketHandler");
10
10
 
11
11
  /**
12
- * WebSocket handler interface
12
+ * WebSocket 핸들러 인터페이스
13
13
  *
14
- * Manages multiple WebSocket connections, routes messages to services,
15
- * and handles event broadcasting.
14
+ * 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
15
+ * 이벤트 브로드캐스팅을 처리한다.
16
16
  */
17
17
  export interface WebSocketHandler {
18
18
  /**
19
- * Add a new WebSocket connection
19
+ * WebSocket 연결을 추가한다
20
20
  */
21
21
  addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
22
22
 
23
23
  /**
24
- * Close all active connections
24
+ * 모든 활성 연결을 닫는다
25
25
  */
26
26
  closeAll(): void;
27
27
 
28
28
  /**
29
- * Broadcast reload message to all clients
29
+ * 모든 클라이언트에 리로드 메시지를 브로드캐스트한다
30
30
  */
31
31
  broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
32
32
 
33
33
  /**
34
- * Emit event to matching clients
34
+ * 매칭되는 클라이언트에 이벤트를 발생시킨다
35
35
  */
36
36
  emit<TInfo, TData>(
37
37
  eventDef: ServiceEventDef<TInfo, TData>,
@@ -41,10 +41,10 @@ export interface WebSocketHandler {
41
41
  }
42
42
 
43
43
  /**
44
- * Create a WebSocket handler instance
44
+ * WebSocket 핸들러 인스턴스를 생성한다
45
45
  *
46
- * Manages multiple WebSocket connections, routes messages to services,
47
- * and handles event broadcasting.
46
+ * 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
47
+ * 이벤트 브로드캐스팅을 처리한다.
48
48
  */
49
49
  export function createWebSocketHandler(
50
50
  runMethod: (def: {
@@ -56,13 +56,13 @@ export function createWebSocketHandler(
56
56
  jwtSecret: string | undefined,
57
57
  ): WebSocketHandler {
58
58
  // -------------------------------------------------------------------
59
- // State
59
+ // 상태
60
60
  // -------------------------------------------------------------------
61
61
 
62
62
  const socketMap = new Map<string, ServiceSocket>();
63
63
 
64
64
  // -------------------------------------------------------------------
65
- // Helpers
65
+ // 헬퍼
66
66
  // -------------------------------------------------------------------
67
67
 
68
68
  async function processRequest(
@@ -72,7 +72,9 @@ export function createWebSocketHandler(
72
72
  ): Promise<number> {
73
73
  try {
74
74
  if (message.name.includes(".") && Array.isArray(message.body)) {
75
- const [serviceName, methodName] = message.name.split(".");
75
+ const dotIndex = message.name.indexOf(".");
76
+ const serviceName = message.name.substring(0, dotIndex);
77
+ const methodName = message.name.substring(dotIndex + 1);
76
78
 
77
79
  const result = await runMethod({
78
80
  serviceName,
@@ -99,35 +101,34 @@ export function createWebSocketHandler(
99
101
  } else if (message.name === "evt:emit") {
100
102
  const { keys, data } = message.body as { keys: string[]; data: unknown };
101
103
 
102
- for (const subSock of socketMap.values()) {
103
- const targetKeys = subSock.filterEventTargetKeys(keys);
104
- if (targetKeys.length > 0) {
105
- await subSock.send(uuid, {
106
- name: "evt:on",
107
- body: {
108
- keys: targetKeys,
109
- data,
110
- },
111
- });
112
- }
113
- }
104
+ await Promise.allSettled(
105
+ Array.from(socketMap.values()).map(async (subSock) => {
106
+ const targetKeys = subSock.filterEventTargetKeys(keys);
107
+ if (targetKeys.length > 0) {
108
+ await subSock.send(uuid, {
109
+ name: "evt:on",
110
+ body: { keys: targetKeys, data },
111
+ });
112
+ }
113
+ }),
114
+ );
114
115
 
115
116
  return await serviceSocket.send(uuid, { name: "response" });
116
117
  } else if (message.name === "auth") {
117
- if (jwtSecret == null) throw new Error("JWT Secret is not defined.");
118
+ if (jwtSecret == null) throw new Error("JWT Secret 정의되지 않았습니다.");
118
119
 
119
120
  const token = message.body;
120
121
  serviceSocket.authTokenPayload = await verifyJwt(jwtSecret, token);
121
122
  return await serviceSocket.send(uuid, { name: "response" });
122
123
  } else {
123
- const err = new Error("Invalid request.");
124
+ const err = new Error("유효하지 않은 요청입니다.");
124
125
 
125
126
  return await serviceSocket.send(uuid, {
126
127
  name: "error",
127
128
  body: {
128
129
  name: err.name,
129
130
  message: err.message,
130
- stack: err.stack,
131
+ ...(env.DEV ? { stack: err.stack } : {}),
131
132
  code: "BAD_MESSAGE",
132
133
  },
133
134
  });
@@ -136,7 +137,7 @@ export function createWebSocketHandler(
136
137
  const error =
137
138
  err instanceof Error
138
139
  ? err
139
- : new Error(typeof err === "string" ? err : "An unknown error has occurred.");
140
+ : new Error(typeof err === "string" ? err : " 없는 에러가 발생했습니다.");
140
141
 
141
142
  return serviceSocket.send(uuid, {
142
143
  name: "error",
@@ -144,14 +145,14 @@ export function createWebSocketHandler(
144
145
  name: error.name,
145
146
  message: error.message,
146
147
  code: "INTERNAL_ERROR",
147
- stack: error.stack,
148
+ ...(env.DEV ? { stack: error.stack } : {}),
148
149
  },
149
150
  });
150
151
  }
151
152
  }
152
153
 
153
154
  // -------------------------------------------------------------------
154
- // Public API
155
+ // 공개 API
155
156
  // -------------------------------------------------------------------
156
157
 
157
158
  return {
@@ -164,7 +165,7 @@ export function createWebSocketHandler(
164
165
  try {
165
166
  const serviceSocket = createServiceSocket(socket, clientId, clientName, connReq);
166
167
 
167
- // Disconnect existing connection
168
+ // 기존 연결 해제
168
169
  const prevServiceSocket = socketMap.get(clientId);
169
170
  if (prevServiceSocket != null) {
170
171
  prevServiceSocket.close();
@@ -172,32 +173,32 @@ export function createWebSocketHandler(
172
173
  const connectionDateTimeText =
173
174
  prevServiceSocket.connectedAtDateTime.toFormatString("yyyy:MM:dd HH:mm:ss.fff");
174
175
  logger.debug(
175
- `Disconnected previous client connection: ${clientId}: ${connectionDateTimeText}`,
176
+ `이전 클라이언트 연결 해제됨: ${clientId}: ${connectionDateTimeText}`,
176
177
  );
177
178
  }
178
179
 
179
180
  socketMap.set(clientId, serviceSocket);
180
181
 
181
182
  serviceSocket.on("close", (code) => {
182
- logger.debug(`Client disconnected: (code: ${code})`);
183
+ logger.debug(`클라이언트 연결 해제됨: (code: ${code})`);
183
184
 
184
185
  if (socketMap.get(clientId) !== serviceSocket) return;
185
186
  socketMap.delete(clientId);
186
187
  });
187
188
 
188
189
  serviceSocket.on("message", async ({ uuid, msg }) => {
189
- logger.debug("Request received", msg);
190
+ logger.debug("요청 수신됨", msg);
190
191
  const sentSize = await processRequest(serviceSocket, uuid, msg);
191
- logger.debug(`Response sent (size: ${sentSize})`);
192
+ logger.debug(`응답 전송됨 (크기: ${sentSize})`);
192
193
  });
193
194
 
194
- logger.debug("Client connected", {
195
+ logger.debug("클라이언트 연결됨", {
195
196
  clientId,
196
197
  remoteAddress: connReq.socket.remoteAddress,
197
198
  socketSize: socketMap.size,
198
199
  });
199
200
  } catch (err) {
200
- logger.error("Error handling connection", err);
201
+ logger.error("연결 처리 중 에러 발생", err);
201
202
  socket.terminate();
202
203
  }
203
204
  },
@@ -212,15 +213,14 @@ export function createWebSocketHandler(
212
213
  clientName: string | undefined,
213
214
  changedFileSet: Set<string>,
214
215
  ): 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
- }
216
+ await Promise.allSettled(
217
+ Array.from(socketMap.values()).map((serviceSocket) =>
218
+ serviceSocket.send(Uuid.generate().toString(), {
219
+ name: "reload",
220
+ body: { clientName, changedFileSet },
221
+ }),
222
+ ),
223
+ );
224
224
  },
225
225
 
226
226
  async emit<TInfo, TData>(
@@ -234,18 +234,17 @@ export function createWebSocketHandler(
234
234
  .filter((item) => infoSelector(item.info as TInfo))
235
235
  .map((item) => item.key);
236
236
 
237
- for (const subSock of socketMap.values()) {
238
- const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
239
- if (subTargetKeys.length > 0) {
240
- await subSock.send(Uuid.generate().toString(), {
241
- name: "evt:on",
242
- body: {
243
- keys: subTargetKeys,
244
- data,
245
- },
246
- });
247
- }
248
- }
237
+ await Promise.allSettled(
238
+ Array.from(socketMap.values()).map(async (subSock) => {
239
+ const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
240
+ if (subTargetKeys.length > 0) {
241
+ await subSock.send(Uuid.generate().toString(), {
242
+ name: "evt:on",
243
+ body: { keys: subTargetKeys, data },
244
+ });
245
+ }
246
+ }),
247
+ );
249
248
  },
250
249
  };
251
250
  }
@@ -9,6 +9,6 @@ export interface ServiceServerOptions {
9
9
  };
10
10
  auth?: {
11
11
  jwtSecret: string;
12
- };
12
+ } | false;
13
13
  services: ServiceDefinition[];
14
14
  }