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 +54 -0
- package/README.md +1 -4
- package/dist/cli.js +33 -5
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +4 -102
- package/dist/index.d.ts +4 -102
- package/dist/index.js +1 -204
- package/dist/index.mjs +1 -203
- package/package.json +1 -1
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
|
|
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
|
-
|
|
1992
|
-
|
|
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 (
|
|
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 (${
|
|
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;
|