@simplysm/service-client 13.0.69 → 13.0.70

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.
@@ -21,7 +21,7 @@ interface ServiceClientEvents {
21
21
  }
22
22
 
23
23
  export class ServiceClient extends EventEmitter<ServiceClientEvents> {
24
- // 모듈들
24
+ // Modules
25
25
  private readonly _socket: SocketProvider;
26
26
  private readonly _transport: ServiceTransport;
27
27
  private readonly _eventClient: EventClient;
@@ -29,7 +29,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
29
29
 
30
30
  private _authToken?: string;
31
31
 
32
- // 상태 접근자
32
+ // State accessors
33
33
  get connected() {
34
34
  return this._socket.connected;
35
35
  }
@@ -47,7 +47,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
47
47
  const wsProtocol = options.ssl ? "wss" : "ws";
48
48
  const wsUrl = `${wsProtocol}://${options.host}:${options.port}/ws`;
49
49
 
50
- // 모듈 초기화
50
+ // Initialize modules
51
51
  this._socket = createSocketProvider(wsUrl, this.name, this.options.maxReconnectCount ?? 10);
52
52
  const protocol = createServiceProtocol();
53
53
  const protocolWrapper = createClientProtocolWrapper(protocol);
@@ -55,19 +55,19 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
55
55
  this._eventClient = createEventClient(this._transport);
56
56
  this._fileClient = createFileClient(this.hostUrl, this.name);
57
57
 
58
- // 이벤트 바인딩
58
+ // Event bindings
59
59
  this._socket.on("state", async (state) => {
60
60
  this.emit("state", state);
61
61
 
62
- // 재연결 이벤트 리스너 자동 복구
62
+ // Auto-recover event listeners on reconnect
63
63
  if (state === "connected") {
64
64
  try {
65
65
  if (this._authToken != null) {
66
- await this.auth(this._authToken); // 재인증
66
+ await this.auth(this._authToken); // Re-authenticate
67
67
  }
68
68
  await this._eventClient.reRegisterAll();
69
69
  } catch (err) {
70
- logger.error("이벤트 리스너 복구 실패", err);
70
+ logger.error("Failed to recover event listeners", err);
71
71
  }
72
72
  }
73
73
  });
@@ -77,7 +77,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
77
77
  });
78
78
  }
79
79
 
80
- // 타입 안전성을 위한 Proxy 생성 메소드
80
+ // Proxy creation method for type safety
81
81
  getService<TService>(serviceName: string): RemoteService<TService> {
82
82
  return new Proxy({} as RemoteService<TService>, {
83
83
  get: (_target, prop) => {
@@ -131,7 +131,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
131
131
  info: TInfo,
132
132
  cb: (data: TData) => PromiseLike<void>,
133
133
  ): Promise<string> {
134
- if (!this.connected) throw new Error("서버와 연결되어있지 않습니다.");
134
+ if (!this.connected) throw new Error("Not connected to the server.");
135
135
  return this._eventClient.addListener(eventDef, info, cb);
136
136
  }
137
137
 
@@ -150,7 +150,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
150
150
  async uploadFile(files: File[] | FileList | { name: string; data: BlobPart }[]) {
151
151
  if (this._authToken == null) {
152
152
  throw new Error(
153
- "인증 토큰이 없습니다. 파일 업로드를 위해서는 먼저 auth() 호출하여 인증해야 합니다.",
153
+ "No authentication token found. Call auth() to authenticate before uploading files.",
154
154
  );
155
155
  }
156
156
  return this._fileClient.upload(files, this._authToken);
@@ -161,11 +161,11 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
161
161
  }
162
162
  }
163
163
 
164
- // TService의 모든 메소드 반환형을 Promise로 감싸주는 타입 변환기
164
+ // Type transformer that wraps all method return types of TService with Promise
165
165
  export type RemoteService<TService> = {
166
166
  [K in keyof TService]: TService[K] extends (...args: infer P) => infer R
167
167
  ? (...args: P) => Promise<Awaited<R>>
168
- : never; // 함수가 아닌 프로퍼티는 안씀
168
+ : never; // Non-function properties are excluded
169
169
  };
170
170
 
171
171
  export function createServiceClient(name: string, options: ServiceConnectionConfig): ServiceClient {
@@ -41,12 +41,12 @@ export function createServiceTransport(
41
41
  }
42
42
  >();
43
43
 
44
- // 응답 progress totalSize 저장 (complete 100% emit용)
44
+ // Store response progress totalSize (for emitting 100% on complete)
45
45
  const responseProgressTotalSize = new Map<string, number>();
46
46
 
47
47
  socket.on("message", onMessage);
48
48
 
49
- // 소켓이 끊기면 대기 중인 모든 요청을 에러 처리하여 메모리 해제
49
+ // When socket disconnects, reject all pending requests to free memory
50
50
  socket.on("state", (state) => {
51
51
  if (state === "closed" || state === "reconnecting") {
52
52
  cancelAllRequests("Socket connection lost");
@@ -56,16 +56,16 @@ export function createServiceTransport(
56
56
  async function send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown> {
57
57
  const uuid = Uuid.new().toString();
58
58
 
59
- // 응답 대기 시작 (요청 보내기 전에 리스너를 먼저 등록해야 안전함)
59
+ // Start awaiting response (register listener before sending request for safety)
60
60
  const responsePromise = new Promise((resolve, reject) => {
61
61
  pendingRequests.set(uuid, { resolve, reject, progress });
62
62
  });
63
63
 
64
- // 요청 전송
64
+ // Send request
65
65
  try {
66
66
  const { chunks, totalSize } = await protocol.encode(uuid, message);
67
67
 
68
- // 진행률 초기화
68
+ // Initialize progress
69
69
  if (chunks.length > 1) {
70
70
  progress?.request?.({
71
71
  uuid,
@@ -74,18 +74,18 @@ export function createServiceTransport(
74
74
  });
75
75
  }
76
76
 
77
- // 전송
77
+ // Send
78
78
  for (const chunk of chunks) {
79
79
  await socket.send(chunk);
80
80
  }
81
81
  } catch (err) {
82
- // 전송 실패 즉시 정리
82
+ // Clean up immediately on send failure
83
83
  pendingRequests.get(uuid)?.reject(err as Error);
84
84
  pendingRequests.delete(uuid);
85
85
  throw err;
86
86
  }
87
87
 
88
- // 응답 결과 반환
88
+ // Return response result
89
89
  return responsePromise;
90
90
  }
91
91
 
@@ -96,7 +96,7 @@ export function createServiceTransport(
96
96
 
97
97
  try {
98
98
  if (decoded.type === "progress") {
99
- // totalSize 기억 (complete 100% emit용)
99
+ // Remember totalSize (for emitting 100% on complete)
100
100
  responseProgressTotalSize.set(decoded.uuid, decoded.totalSize);
101
101
 
102
102
  listenerInfo?.progress?.response?.({
@@ -113,7 +113,7 @@ export function createServiceTransport(
113
113
  completedSize: body.completedSize,
114
114
  });
115
115
  } else if (decoded.message.name === "response") {
116
- // split된 메시지였으면 100% progress emit
116
+ // Emit 100% progress if it was a split message
117
117
  const totalSize = responseProgressTotalSize.get(decoded.uuid);
118
118
  if (totalSize != null) {
119
119
  responseProgressTotalSize.delete(decoded.uuid);
@@ -124,15 +124,15 @@ export function createServiceTransport(
124
124
  });
125
125
  }
126
126
 
127
- // 응답을 받았으므로 Map에서 제거
127
+ // Remove from Map since response received
128
128
  pendingRequests.delete(decoded.uuid);
129
129
 
130
130
  listenerInfo?.resolve(decoded.message.body as ServiceResponseMessage);
131
131
  } else if (decoded.message.name === "error") {
132
- // progress totalSize 정리
132
+ // Clean up progress totalSize
133
133
  responseProgressTotalSize.delete(decoded.uuid);
134
134
 
135
- // 에러를 받았으므로 Map에서 제거
135
+ // Remove from Map since error received
136
136
  pendingRequests.delete(decoded.uuid);
137
137
 
138
138
  listenerInfo?.reject(toError(decoded.message.body));
@@ -145,7 +145,7 @@ export function createServiceTransport(
145
145
  const body = decoded.message.body as { keys: string[]; data: unknown };
146
146
  emitter.emit("event", { keys: body.keys, data: body.data });
147
147
  } else {
148
- throw new Error("요청이 되었습니다.");
148
+ throw new Error("Invalid message received from server.");
149
149
  }
150
150
  }
151
151
  } catch (err) {
@@ -153,7 +153,7 @@ export function createServiceTransport(
153
153
  }
154
154
  }
155
155
 
156
- // 모든 대기 요청 취소 처리
156
+ // Cancel all pending requests
157
157
  function cancelAllRequests(reason: string): void {
158
158
  for (const listenerInfo of pendingRequests.values()) {
159
159
  listenerInfo.reject(new Error(`Request canceled: ${reason}`));
@@ -30,15 +30,15 @@ export function createSocketProvider(
30
30
  clientName: string,
31
31
  maxReconnectCount: number,
32
32
  ): SocketProvider {
33
- // 설정상수
34
- const HEARTBEAT_TIMEOUT = 30000; // 30초간 아무런 메시지가 없으면 끊김으로 간주
35
- const HEARTBEAT_INTERVAL = 5000; // 5초마다 전송
36
- const RECONNECT_DELAY = 3000; // 3초마다 재연결 시도
33
+ // Configuration constants
34
+ const HEARTBEAT_TIMEOUT = 30000; // Consider disconnected if no message for 30s
35
+ const HEARTBEAT_INTERVAL = 5000; // Send ping every 5s
36
+ const RECONNECT_DELAY = 3000; // Retry reconnect every 3s
37
37
 
38
- // 1바이트 버퍼 미리 생성 (메모리 절약)
38
+ // Pre-allocate 1-byte buffer (saves memory)
39
39
  const PING_PACKET = new Uint8Array([0x01]);
40
40
 
41
- // 상태
41
+ // State
42
42
  let ws: WebSocket | undefined;
43
43
  let isManualClose = false;
44
44
  let reconnectCount = 0;
@@ -58,10 +58,10 @@ export function createSocketProvider(
58
58
  try {
59
59
  await createSocket();
60
60
  startHeartbeat();
61
- reconnectCount = 0; // 연결 성공 카운트 초기화
61
+ reconnectCount = 0; // Reset count on successful connection
62
62
  emitter.emit("state", "connected");
63
63
  } catch (err) {
64
- // 최초 연결 실패는 에러를 던짐 (호출자가 있게)
64
+ // Throw on initial connection failure (so the caller can handle it)
65
65
  throw err;
66
66
  }
67
67
  }
@@ -72,7 +72,7 @@ export function createSocketProvider(
72
72
  const currentWs = ws;
73
73
  if (currentWs != null) {
74
74
  currentWs.close();
75
- // 완전히 닫힐 때까지 대기 (Graceful Shutdown)
75
+ // Wait until fully closed (graceful shutdown)
76
76
  await waitUntil(() => currentWs.readyState === WebSocket.CLOSED, 100, 30).catch(() => {});
77
77
  }
78
78
  emitter.emit("state", "closed");
@@ -82,11 +82,11 @@ export function createSocketProvider(
82
82
  try {
83
83
  await waitUntil(() => isConnected(), undefined, 50);
84
84
  } catch {
85
- throw new Error("서버와 연결되어있지 않습니다. 인터넷 연결을 확인하세요.");
85
+ throw new Error("Not connected to the server. Please check your internet connection.");
86
86
  }
87
87
  const currentWs = ws;
88
88
  if (currentWs == null) {
89
- throw new Error("WebSocket 연결되어있지 않습니다.");
89
+ throw new Error("WebSocket is not connected.");
90
90
  }
91
91
  currentWs.send(data);
92
92
  }
@@ -109,7 +109,7 @@ export function createSocketProvider(
109
109
  };
110
110
 
111
111
  newWs.onerror = (event: Event) => {
112
- // 연결 에러 발생 시 reject
112
+ // Reject on error during connection
113
113
  if (!isConnected()) {
114
114
  const errorEvent = event as ErrorEvent;
115
115
  const msg = errorEvent.message;
@@ -118,21 +118,21 @@ export function createSocketProvider(
118
118
  };
119
119
  });
120
120
 
121
- // 시점에서 ws 항상 할당되어 있음 (ws.onopen에서 할당)
121
+ // At this point ws is always assigned (assigned in ws.onopen)
122
122
  const currentWs = ws;
123
123
  if (currentWs == null) {
124
- throw new Error("WebSocket 초기화 실패");
124
+ throw new Error("WebSocket initialization failed");
125
125
  }
126
126
 
127
127
  currentWs.onmessage = (event) => {
128
- lastHeartbeatTime = Date.now(); // 하트비트 갱신
128
+ lastHeartbeatTime = Date.now(); // Update heartbeat
129
129
 
130
130
  const data = event.data as ArrayBuffer;
131
131
  const bytes = new Uint8Array(data);
132
132
 
133
- // Raw Ping/Pong 처리 (가장 먼저 체크)
134
- // 1바이트이고 바이트가 0x02(Pong)이면 무시
135
- // (하트비트 타임스탬프만 갱신하고 끝냄)
133
+ // Raw Ping/Pong handling (checked first)
134
+ // If 1 byte and first byte is 0x02 (Pong), ignore
135
+ // (only heartbeat timestamp was updated, nothing else to do)
136
136
  if (bytes.length === 1 && bytes[0] === 0x02) return;
137
137
 
138
138
  emitter.emit("message", bytes);
@@ -147,11 +147,11 @@ export function createSocketProvider(
147
147
  }
148
148
 
149
149
  async function tryReconnect(): Promise<void> {
150
- // 루프 기반 재연결 (재귀 대신 사용하여 스택 안전성 확보)
150
+ // Loop-based reconnect (used instead of recursion for stack safety)
151
151
  while (reconnectCount < maxReconnectCount) {
152
152
  reconnectCount++;
153
153
  emitter.emit("state", "reconnecting");
154
- logger.warn("WebSocket 연결 끊김. 재연결 시도...", {
154
+ logger.warn("WebSocket disconnected. Attempting reconnect...", {
155
155
  reconnectCount,
156
156
  maxReconnectCount,
157
157
  });
@@ -162,16 +162,16 @@ export function createSocketProvider(
162
162
  await createSocket();
163
163
  startHeartbeat();
164
164
  reconnectCount = 0;
165
- emitter.emit("state", "connected"); // 재연결 성공 알림
166
- logger.info("WebSocket 재연결 성공");
167
- return; // 재연결 성공 종료
165
+ emitter.emit("state", "connected"); // Notify reconnect success
166
+ logger.info("WebSocket reconnected successfully");
167
+ return; // Exit on successful reconnect
168
168
  } catch {
169
- // 실패 루프 계속
169
+ // Continue loop on failure
170
170
  }
171
171
  }
172
172
 
173
- // 최대 재시도 횟수 초과
174
- logger.error("재연결 시도 횟수 초과. 연결을 포기합니다.");
173
+ // Max retry count exceeded
174
+ logger.error("Reconnect retry limit exceeded. Giving up.");
175
175
  emitter.emit("state", "closed");
176
176
  }
177
177
 
@@ -180,32 +180,32 @@ export function createSocketProvider(
180
180
  lastHeartbeatTime = Date.now();
181
181
 
182
182
  heartbeatTimer = setInterval(() => {
183
- // 타임아웃 체크
183
+ // Timeout check
184
184
  if (Date.now() - lastHeartbeatTime > HEARTBEAT_TIMEOUT) {
185
185
  logger.warn("Heartbeat Timeout. Connection lost.");
186
186
 
187
- // 타임아웃이 발생했으므로 즉시 타이머를 멈춰서 반복 실행을 막습니다.
187
+ // Stop the timer immediately on timeout to prevent repeated execution.
188
188
  stopHeartbeat();
189
189
 
190
- // 소켓이 닫히기를 기다리지 말고(onclose 미발생 대비), 강제로 정리 재연결합니다.
190
+ // Don't wait for socket close (onclose may not fire); force cleanup and reconnect.
191
191
  if (ws != null) {
192
192
  const tempWs = ws;
193
- ws = undefined; // 연결 상태 끊김으로 간주
193
+ ws = undefined; // Consider connection as disconnected
194
194
 
195
- // 기존 소켓의 이벤트 핸들러 제거
196
- // 뒤늦게 발생한 onclose에 따른 중복 재연결 방지
195
+ // Remove event handlers from old socket
196
+ // Prevent duplicate reconnect from late onclose events
197
197
  tempWs.onclose = null;
198
198
  tempWs.onerror = null;
199
199
  tempWs.onmessage = null;
200
200
 
201
- // 소켓 닫기 시도 (에러 무시)
201
+ // Attempt to close socket (ignore errors)
202
202
  try {
203
203
  tempWs.close();
204
204
  } catch {
205
205
  // ignore
206
206
  }
207
207
 
208
- // 수동 종료가 아니라면 재연결 로직 강제 실행
208
+ // Force reconnect logic if not a manual close
209
209
  if (!isManualClose) {
210
210
  void tryReconnect();
211
211
  }
@@ -213,7 +213,7 @@ export function createSocketProvider(
213
213
  return;
214
214
  }
215
215
 
216
- // ping 전송
216
+ // Send ping
217
217
  const currentWs = ws;
218
218
  if (isConnected() && currentWs != null) {
219
219
  try {
@@ -2,6 +2,6 @@ export interface ServiceConnectionConfig {
2
2
  port: number;
3
3
  host: string;
4
4
  ssl?: boolean;
5
- /** 0 입력시 reconnect안함. 바로 끊김 */
5
+ /** Set to 0 to disable reconnect; disconnects immediately */
6
6
  maxReconnectCount?: number;
7
7
  }
@@ -17,34 +17,34 @@ self.onmessage = (event: MessageEvent) => {
17
17
  let transferList: Transferable[] = [];
18
18
 
19
19
  if (type === "encode") {
20
- // [Main -> Worker] 인코딩 요청 (data: { uuid, message })
21
- // message 이미 Plain Object 넘어옴 (Structured Clone)
20
+ // [Main -> Worker] Encode request (data: { uuid, message })
21
+ // message is already a Plain Object (via Structured Clone)
22
22
  const { uuid, message } = data as {
23
23
  uuid: string;
24
24
  message: Parameters<typeof protocol.encode>[1];
25
25
  };
26
26
  const { chunks } = protocol.encode(uuid, message);
27
27
 
28
- // Buffer[] 전송 가능하므로 결과로 반환
28
+ // Buffer[] is transferable, return as result
29
29
  result = chunks;
30
- // 결과물 청크들의 내부 ArrayBuffer를 소유권 이전 목록에 추가
30
+ // Add internal ArrayBuffers of result chunks to transfer list
31
31
  transferList = chunks.map((chunk) => chunk.buffer as ArrayBuffer);
32
32
  } else {
33
- // [Main -> Worker] 디코딩 요청 (data: Uint8Array)
34
- // data Uint8Array로 넘어옴
33
+ // [Main -> Worker] Decode request (data: Uint8Array)
34
+ // data arrives as Uint8Array
35
35
  const bytes = new Uint8Array(data as ArrayBuffer);
36
36
  const decodeResult = protocol.decode(bytes);
37
37
 
38
- // 결과물(객체)을 전송 가능한 형태로 변환 (Zero-Copy 준비)
38
+ // Convert result object to transferable form (prepare for zero-copy)
39
39
  const encoded = transferableEncode(decodeResult);
40
40
  result = encoded.result;
41
41
  transferList = encoded.transferList;
42
42
  }
43
43
 
44
- // [Worker -> Main] 성공 응답
44
+ // [Worker -> Main] Success response
45
45
  self.postMessage({ id, type: "success", result }, { transfer: transferList });
46
46
  } catch (err) {
47
- // [Worker -> Main] 에러 응답
47
+ // [Worker -> Main] Error response
48
48
  self.postMessage({
49
49
  id,
50
50
  type: "error",