connectbase-client 3.38.0 → 3.39.2

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/CHANGELOG.md CHANGED
@@ -3,6 +3,60 @@
3
3
  본 SDK 의 모든 주요 변경사항을 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 형식으로 기록합니다.
4
4
  버전은 [Semantic Versioning](https://semver.org/lang/ko/) 을 따릅니다.
5
5
 
6
+ ## [3.39.2] - 2026-06-26
7
+
8
+ ### Performance — 터널 프록시 응답 지연 제거 (TCP_NODELAY / keep-alive)
9
+
10
+ `connectbase tunnel` 프록시 경유가 직접 호출 대비 **~2.4초 고정 지연**을 추가하던 문제를
11
+ 수정했다(플랫폼 이슈 019f01c3 — 실시간 TTS 음성에서 "첫 음성까지 ~3.5초"). 원인은 응답
12
+ 본문을 tunnel-server 로 되돌리는 영구 WebSocket 소켓에 Nagle 알고리즘이 켜져 있던 것이다.
13
+
14
+ - **TCP_NODELAY (핵심)**: tunnel-server 로의 영구 WS 소켓에 `setNoDelay(true)` 적용. 프록시
15
+ 응답 본문은 CLI→tunnel-server 방향으로 CLI 가 송신하는데, 업스트림(사이드카)이 본문을
16
+ 여러 작은 청크로 흘리면 각 WS 프레임 write 가 직전 세그먼트의 ACK 를 기다리며 묶였다.
17
+ tunnel-server 가 지리적으로 먼 경우(예: 한국↔싱가포르 ~45ms RTT) 이 Nagle×delayed-ACK
18
+ 상호작용이 프레임당 수십 ms 스톨을 만들고, 청크 수에 비례해 고정 수초로 누적됐다.
19
+ - **로컬 keep-alive**: 사이드카(로컬 업스트림) HTTP 호출에 keep-alive Agent 적용 — 요청마다
20
+ TCP 핸드셰이크를 새로 맺지 않고 소켓을 재사용한다(순차 호출 시 이득).
21
+ - **로컬·업스트림 WS 소켓 NoDelay**: 사이드카 HTTP/WS 소켓에도 `setNoDelay(true)` 적용
22
+ (인터랙티브 WS — noVNC·실시간 프레임 — 프레임당 지연 감소).
23
+
24
+ 연계: core-server 도 프록시 forward 를 공개 도메인(`tunnel.connectbase.world`, 클러스터
25
+ 밖 ingress 헤어핀)이 아니라 tunnel-server 내부 ClusterIP 로 직결하도록 함께 수정됐다.
26
+
27
+ ## [3.39.1] - 2026-06-26
28
+
29
+ ### Fixed — 터널 인증 4xx 를 재시도하지 않고 즉시 원인 표시
30
+
31
+ `connectbase tunnel` 이 인증 단계에서 받은 **클라이언트 오류(4xx)** 를 일시 장애처럼 10회
32
+ 재시도하던 것을 수정했다. 서버가 4xx 로 명확히 거부하면 재시도 없이 즉시 원인을 출력하고 종료한다.
33
+
34
+ - **403 (앱 접근 거부)**: `app_id` 오설정(공개키 UUID·storageId 입력)이 대부분 — 서버 메시지와
35
+ 함께 "`app_id` 가 콘솔의 앱/프로젝트 ID 인지 확인" 안내를 출력하고 즉시 종료.
36
+ - **그 외 4xx (400/404 등)**: 설정 문제라 재시도해도 동일하게 실패 → 즉시 종료.
37
+ - **5xx · 429 · 네트워크 오류**: 기존대로 지수 백오프 재시도(일시적 장애).
38
+ - 서버 응답 본문의 `{"error":"..."}` 메시지를 파싱해 표시.
39
+
40
+ 연계: tunnel-server 가 core 인증 4xx(403/401)를 503 "auth service unavailable" 로 뭉개던
41
+ 서버측 문제도 함께 수정됐다(설정 오류가 서비스 장애로 오진단되던 것 해소).
42
+
43
+ ## [3.39.0] - 2026-06-25
44
+
45
+ ### Removed (BREAKING) — 게스트/익명 로그인 제거
46
+
47
+ 게스트 멤버 로그인과 익명 플랫폼 로그인을 제거했다. 웹 클라이언트 코드 실수(예: 렌더마다
48
+ 게스트 로그인 호출)로 게스트 멤버가 대량 생성되는 문제를 차단하기 위함이다.
49
+
50
+ - **제거**: `cb.auth.signInAsGuestMember()`, `cb.auth.clearGuestMemberTokens()`,
51
+ `SignInAsGuestMemberOptions`, `GuestMemberSignInResponse`, `GuestSessionConflictError`.
52
+ - **제거**: `AuthSettingsResponse.allow_guest_login` 필드 (백엔드 `/v1/public/auth-settings`
53
+ 가 더 이상 반환하지 않음). 백엔드 엔드포인트 `POST /v1/public/app-members`(게스트 생성),
54
+ `POST /v1/auth/signin/anonymous`(익명) 도 함께 제거됐다.
55
+ - **보존**: 기존 게스트 멤버 데이터는 그대로 유지되며, `getMe()` 의 `is_guest` /
56
+ `auth_provider` 로 계속 식별 가능하다.
57
+ - **마이그레이션**: 비로그인/익명 사용자가 필요하면 정식 회원가입(`signUpMember`) 또는 소셜
58
+ 로그인을 사용할 것.
59
+
6
60
  ## [3.38.0] - 2026-06-25
7
61
 
8
62
  ### Added — `cb.push.sendToTopic` (토픽 구독자 전체 발송, service_role 지원)
package/README.md CHANGED
@@ -107,7 +107,7 @@ try {
107
107
  ## Features
108
108
 
109
109
  - **Real-time Game Server**: WebSocket-based multiplayer game state synchronization
110
- - **Authentication**: ID/Password, guest login, and OAuth social login support
110
+ - **Authentication**: ID/Password and OAuth social login support
111
111
  - **Database**: JSON-based NoSQL database with real-time queries
112
112
  - **Storage**: File storage with CDN support
113
113
  - **Push Notifications**: Cross-platform push notification support
@@ -525,9 +525,6 @@ const result = await cb.auth.signInMember({
525
525
  password: 'password123'
526
526
  })
527
527
 
528
- // Guest login
529
- const guest = await cb.auth.signInAsGuestMember()
530
-
531
528
  // OAuth login (redirect - recommended)
532
529
  await cb.oauth.signIn('google', 'https://myapp.com/oauth/callback')
533
530
 
package/dist/cli.js CHANGED
@@ -1933,6 +1933,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1933
1933
  req.on("upgrade", (_res, sock, _head) => {
1934
1934
  socket = sock;
1935
1935
  reconnectAttempts = 0;
1936
+ sock.setNoDelay(true);
1936
1937
  const parser = new WsFrameParser({
1937
1938
  onMessage: (data) => {
1938
1939
  try {
@@ -1988,16 +1989,32 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
1988
1989
  let body = "";
1989
1990
  res.on("data", (chunk) => body += chunk.toString());
1990
1991
  res.on("end", () => {
1991
- if (res.statusCode === 401) {
1992
- error("\uC778\uC99D \uC2E4\uD328: API Key\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
1992
+ const status = res.statusCode ?? 0;
1993
+ let serverMsg = body;
1994
+ try {
1995
+ const j = JSON.parse(body);
1996
+ if (j && typeof j.error === "string") serverMsg = j.error;
1997
+ } catch {
1998
+ }
1999
+ if (status === 401) {
2000
+ error("\uC778\uC99D \uC2E4\uD328: Secret Key\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
1993
2001
  shouldReconnect = false;
1994
2002
  process.exit(1);
1995
- } else if (res.statusCode === 402) {
2003
+ } else if (status === 402) {
1996
2004
  error("\uD560\uB2F9\uB7C9 \uCD08\uACFC: \uD50C\uB79C\uC744 \uC5C5\uADF8\uB808\uC774\uB4DC\uD558\uC138\uC694");
1997
2005
  shouldReconnect = false;
1998
2006
  process.exit(1);
2007
+ } else if (status === 403) {
2008
+ error(`\uC811\uADFC \uAC70\uBD80: ${serverMsg}`);
2009
+ error("\u2192 app_id \uAC00 \uCF58\uC194\uC758 \uC571/\uD504\uB85C\uC81D\uD2B8 ID \uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694 (\uACF5\uAC1C\uD0A4 UUID \uB098 storageId \uAC00 \uC544\uB2D9\uB2C8\uB2E4).");
2010
+ shouldReconnect = false;
2011
+ process.exit(1);
2012
+ } else if (status >= 400 && status < 500 && status !== 429) {
2013
+ error(`\uD130\uB110 \uC5F0\uACB0 \uAC70\uBD80 (${status}): ${serverMsg}`);
2014
+ shouldReconnect = false;
2015
+ process.exit(1);
1999
2016
  } else {
2000
- error(`\uD130\uB110 \uC11C\uBC84 \uC751\uB2F5 \uC624\uB958 (${res.statusCode}): ${body}`);
2017
+ error(`\uD130\uB110 \uC11C\uBC84 \uC751\uB2F5 \uC624\uB958 (${status}): ${serverMsg}`);
2001
2018
  if (shouldReconnect) {
2002
2019
  scheduleReconnect();
2003
2020
  }
@@ -2017,6 +2034,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2017
2034
  setTimeout(connect, delay);
2018
2035
  }
2019
2036
  const streams = /* @__PURE__ */ new Map();
2037
+ const localAgent = new http.Agent({ keepAlive: true, maxSockets: 64 });
2020
2038
  function routeStreamData(streamId, payload) {
2021
2039
  const f = streams.get(streamId);
2022
2040
  if (!f) return;
@@ -2050,7 +2068,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2050
2068
  let started = false;
2051
2069
  let cancelled = false;
2052
2070
  const localReq = http.request(
2053
- { hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders },
2071
+ { hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders, agent: localAgent },
2054
2072
  (res) => {
2055
2073
  const respHeaders = {};
2056
2074
  for (const [k, v] of Object.entries(res.headers)) {
@@ -2082,6 +2100,12 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2082
2100
  });
2083
2101
  }
2084
2102
  );
2103
+ localReq.on("socket", (s) => {
2104
+ try {
2105
+ s.setNoDelay(true);
2106
+ } catch {
2107
+ }
2108
+ });
2085
2109
  localReq.on("error", (err) => {
2086
2110
  warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
2087
2111
  if (!started) {
@@ -2140,6 +2164,10 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
2140
2164
  });
2141
2165
  req.on("upgrade", (res, sk) => {
2142
2166
  upstream = sk;
2167
+ try {
2168
+ upstream.setNoDelay(true);
2169
+ } catch {
2170
+ }
2143
2171
  const respHeaders = {};
2144
2172
  for (const [k, v] of Object.entries(res.headers)) {
2145
2173
  if (v == null) continue;