connectbase-client 3.24.0 → 3.25.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
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.25.1] - 2026-05-27
7
+
8
+ ### Fixed — 3.25.0 회귀 복구 + heartbeat 헬퍼 정리
9
+
10
+ 3.25.0 이 NativeBridge / inApp 우회 / deploy timeout 을 추가하면서, 같은 시기 3.24.0 에 들어가 있던
11
+ incremental deploy(manifest 기반 +/− diff) 와 `signUp` / `startCentralOAuth` 흐름을 통째로 누락한 채
12
+ release 됐다. 본 패치는 두 라인을 머지해 3.24.0 의 동작을 회복하면서 3.25.0 의 신규 기능을 모두 유지한다.
13
+
14
+ - **CLI deploy** — `fullDeploy` 만 호출되던 회귀를 닫고 `incrementalDeploy` + `tryFetchManifest` + `computeDeployDiff` + `handleDeployResponse` 복원.
15
+ - Dev 는 항상 전량(`/deploy/dev`), Prod 는 manifest 기반 `+upsert/-delete` 전송, 변경 0 이면 업로드 skip.
16
+ - 409 revision conflict 자동 1회 재시도.
17
+ - 두 경로 모두 `computeDeployTimeout(totalBytes, override)` 결과를 `makeRequest` 의 `timeoutMs` 로 전달.
18
+ - `startUploadHeartbeat()` 헬퍼로 분리해 incremental / full 양쪽에서 TTY heartbeat 표시 (CI 환경은 무음).
19
+ - `VERSION` 을 빌드된 `package.json` 에서 읽도록 복원 (`getPackageVersion()`).
20
+ - **OAuth** — `signUp(provider, callbackUrl, state)` 와 `startCentralOAuth(intent)` 가 다시 export.
21
+ - `signIn` / `signUp` 모두 `startCentralOAuth` 를 거치며, 내부에서 ① NativeBridge 감지 ②
22
+ 3rd-party 인앱 브라우저(`detectInAppBrowser` + `escapeToExternalBrowser`) 처리 ③ 일반 웹 redirect
23
+ 순으로 분기. 3.25.0 에서 `signIn` 만 inApp 처리를 받던 비대칭을 해소.
24
+ - `signInWithPopup` 도 같은 3-way 분기 + `options.intent` 유지.
25
+ - **Exports** — `detectInAppBrowser`, `escapeToExternalBrowser` 외에 3.24.0 의 `TokenPersistence` 타입과
26
+ `GameError` 도 함께 re-export (3.25.0 에서 export 라인이 충돌 머지로 일부 빠져있던 회귀).
27
+
28
+ ### Notes — 누락된 CHANGELOG 보강
29
+
30
+ 3.24.0 / 3.25.0 은 CHANGELOG 엔트리 없이 publish 됐다. 향후 retro 엔트리를 보강할 수 있으나, 본 릴리스는
31
+ 3.25.0 → 3.25.1 의 diff 만 다룬다.
32
+
6
33
  ## [3.23.0] - 2026-05-26
7
34
 
8
35
  ### Fixed — OAuth 표준 흐름 페이지 리로드 후 세션 유실 (platform-issue 019e638d)
package/dist/cli.js CHANGED
@@ -131,7 +131,7 @@ function getPackageVersion() {
131
131
  }
132
132
  } catch {
133
133
  }
134
- return "0.15.1";
134
+ return "3.25.1";
135
135
  }
136
136
  var VERSION = getPackageVersion();
137
137
  var DEFAULT_BASE_URL = "https://api.connectbase.world";
@@ -280,7 +280,8 @@ function collectFiles(dir, baseDir = dir) {
280
280
  }
281
281
  return files;
282
282
  }
283
- async function makeRequest(url, method, headers, body) {
283
+ var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
284
+ async function makeRequest(url, method, headers, body, reqOpts = {}) {
284
285
  return new Promise((resolve2, reject) => {
285
286
  const parsedUrl = new URL(url);
286
287
  const isHttps = parsedUrl.protocol === "https:";
@@ -313,8 +314,16 @@ async function makeRequest(url, method, headers, body) {
313
314
  }
314
315
  });
315
316
  });
316
- req.setTimeout(6e4, () => {
317
- req.destroy(new Error("\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (60\uCD08)"));
317
+ req.on("socket", (socket) => {
318
+ socket.setKeepAlive(true, 3e4);
319
+ });
320
+ const timeoutMs = reqOpts.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
321
+ req.setTimeout(timeoutMs, () => {
322
+ req.destroy(
323
+ new Error(
324
+ `\uC694\uCCAD \uC2DC\uAC04 \uCD08\uACFC (${Math.round(timeoutMs / 1e3)}\uCD08). \uD070 \uC0AC\uC774\uD2B8\uB294 --timeout <\uCD08> \uB610\uB294 CONNECTBASE_DEPLOY_TIMEOUT \uD658\uACBD\uBCC0\uC218\uB85C \uB298\uB9AC\uC138\uC694.`
325
+ )
326
+ );
318
327
  });
319
328
  req.on("error", reject);
320
329
  if (bodyBuffer) {
@@ -323,7 +332,17 @@ async function makeRequest(url, method, headers, body) {
323
332
  req.end();
324
333
  });
325
334
  }
326
- async function deploy(directory, config, isDev = false) {
335
+ var DEPLOY_TIMEOUT_BASE_MS = 5 * 6e4;
336
+ var DEPLOY_TIMEOUT_PER_10MB_MS = 6e4;
337
+ var DEPLOY_TIMEOUT_MIN_MS = 10 * 6e4;
338
+ function computeDeployTimeout(totalBytes, override) {
339
+ if (override && override > 0) return override;
340
+ const envOverride = process.env.CONNECTBASE_DEPLOY_TIMEOUT ? parseInt(process.env.CONNECTBASE_DEPLOY_TIMEOUT, 10) * 1e3 : 0;
341
+ if (envOverride > 0) return envOverride;
342
+ const sizeBased = Math.ceil(totalBytes / (10 * 1024 * 1024)) * DEPLOY_TIMEOUT_PER_10MB_MS;
343
+ return Math.max(DEPLOY_TIMEOUT_MIN_MS, DEPLOY_TIMEOUT_BASE_MS + sizeBased);
344
+ }
345
+ async function deploy(directory, config, isDev = false, deployOpts = {}) {
327
346
  const dir = path2.resolve(directory);
328
347
  if (!fs2.existsSync(dir)) {
329
348
  error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
@@ -363,14 +382,16 @@ async function deploy(directory, config, isDev = false) {
363
382
  const envLabel = isDev ? "Dev" : "Prod";
364
383
  const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
365
384
  const headers = { "X-Public-Key": config.publicKey };
385
+ const timeoutMs = computeDeployTimeout(totalSize, deployOpts.timeoutMs);
386
+ log(`${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`);
366
387
  if (isDev) {
367
- await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy/dev");
388
+ await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy/dev", timeoutMs);
368
389
  return;
369
390
  }
370
391
  try {
371
392
  const manifest = await tryFetchManifest(baseStorageUrl, headers);
372
393
  if (!manifest) {
373
- await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy");
394
+ await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy", timeoutMs);
374
395
  return;
375
396
  }
376
397
  const diff = computeDeployDiff(files, manifest);
@@ -384,7 +405,7 @@ ${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.res
384
405
  info(`\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`);
385
406
  const uploadSize = diff.upsert.reduce((s, f) => s + f.content.length, 0);
386
407
  info(`\uC5C5\uB85C\uB4DC \uD06C\uAE30: ${(uploadSize / 1024).toFixed(1)} KB`);
387
- await incrementalDeploy(baseStorageUrl, headers, diff, manifest.revision, envLabel);
408
+ await incrementalDeploy(baseStorageUrl, headers, diff, manifest.revision, envLabel, timeoutMs);
388
409
  } catch (err) {
389
410
  process.stdout.write("\r \r");
390
411
  error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
@@ -428,15 +449,25 @@ function computeDeployDiff(local, manifest) {
428
449
  }
429
450
  return { upsert, delete: toDelete };
430
451
  }
431
- async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel) {
452
+ function startUploadHeartbeat(envLabel, suffix = "\uBC30\uD3EC \uC911") {
453
+ if (!process.stdout.isTTY) return null;
454
+ const startedAt = Date.now();
455
+ return setInterval(() => {
456
+ const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
457
+ process.stdout.write(`\r${colors.blue}\u27F3${colors.reset} ${envLabel} ${suffix}... ${colors.dim}(${elapsed}s)${colors.reset}`);
458
+ }, 1e3);
459
+ }
460
+ async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel, timeoutMs) {
432
461
  process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`);
462
+ let heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC911");
433
463
  const body = JSON.stringify({
434
464
  upsert: diff.upsert,
435
465
  delete: diff.delete,
436
466
  base_revision: baseRevision
437
467
  });
438
- const res = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body);
439
- process.stdout.write("\r \r");
468
+ const res = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body, { timeoutMs });
469
+ if (heartbeat) clearInterval(heartbeat);
470
+ process.stdout.write("\r \r");
440
471
  if (res.status === 409) {
441
472
  warn("\uC11C\uBC84 revision \uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. manifest \uC7AC\uC870\uD68C \uD6C4 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4.");
442
473
  const manifest = await tryFetchManifest(baseStorageUrl, headers);
@@ -450,15 +481,18 @@ async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, en
450
481
  base_revision: manifest.revision
451
482
  });
452
483
  process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`);
453
- const res2 = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body2);
454
- process.stdout.write("\r \r");
484
+ heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4");
485
+ const res2 = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body2, { timeoutMs });
486
+ if (heartbeat) clearInterval(heartbeat);
487
+ process.stdout.write("\r \r");
455
488
  handleDeployResponse(res2, envLabel);
456
489
  return;
457
490
  }
458
491
  handleDeployResponse(res, envLabel);
459
492
  }
460
- async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint) {
493
+ async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint, timeoutMs) {
461
494
  process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911...`);
495
+ const heartbeat = startUploadHeartbeat(envLabel);
462
496
  const body = JSON.stringify({
463
497
  files: files.map((f) => ({
464
498
  path: f.path,
@@ -466,8 +500,9 @@ async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint) {
466
500
  is_binary: f.isBinary
467
501
  }))
468
502
  });
469
- const res = await makeRequest(`${baseStorageUrl}/${endpoint}`, "POST", headers, body);
470
- process.stdout.write("\r \r");
503
+ const res = await makeRequest(`${baseStorageUrl}/${endpoint}`, "POST", headers, body, { timeoutMs });
504
+ if (heartbeat) clearInterval(heartbeat);
505
+ process.stdout.write("\r \r");
471
506
  handleDeployResponse(res, envLabel);
472
507
  }
473
508
  function handleDeployResponse(response, envLabel) {
@@ -2308,7 +2343,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
2308
2343
  -s, --storage <id> \uC2A4\uD1A0\uB9AC\uC9C0 ID
2309
2344
  -k, --public-key <key> API Key
2310
2345
  -u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
2311
- -t, --timeout <sec> \uD130\uB110 \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel \uC804\uC6A9)
2346
+ -t, --timeout <sec> \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel/deploy \uACF5\uD1B5. deploy \uBBF8\uC9C0\uC815 \uC2DC \uD30C\uC77C \uD06C\uAE30\uC5D0 \uB530\uB77C \uC790\uB3D9 \uC0B0\uC815)
2312
2347
  --max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
2313
2348
  --force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
2314
2349
  --public proxy_token \uAC80\uC99D \uBE44\uD65C\uC131\uD654 \u2014 \uC6F9\uD6C5/\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C\uC6A9 (tunnel \uC804\uC6A9)
@@ -2466,7 +2501,13 @@ async function main() {
2466
2501
  process.exit(1);
2467
2502
  }
2468
2503
  const isDev = parsed.options.dev === "true";
2469
- await deploy(directory, config, isDev);
2504
+ const deployOpts = {};
2505
+ if (parsed.options.timeout) {
2506
+ const t = parseInt(parsed.options.timeout, 10);
2507
+ if (!isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
2508
+ else warn(`--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`);
2509
+ }
2510
+ await deploy(directory, config, isDev, deployOpts);
2470
2511
  } else if (parsed.command === "tunnel") {
2471
2512
  const portStr = parsed.args[0];
2472
2513
  if (!portStr) {