connectbase-client 6.8.0 → 6.10.0

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,48 @@
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
+ ## [6.10.0] - 2026-10-30
7
+
8
+ ### Added — 실시간 메시지의 `fromConn`(보낸 연결의 서버 ID)
9
+
10
+ `RealtimeMessage.fromConn` 은 서버가 연결마다 정하는 ID 입니다. 클라이언트가 고를 수 없고, 공개 키(익명) 연결이 보낸
11
+ 메시지에도 실립니다. `from`(client_id)은 연결할 때 아무 값이나(남의 값도) 고를 수 있어 익명 관객 수처럼 "연결 하나 =
12
+ 한 사람" 으로 세거나 연결 단위로 메시지 양을 제한할 때 쓸 수 없었습니다.
13
+
14
+ ```ts
15
+ room.onMessage((msg) => {
16
+ audience.add(msg.fromMember ?? msg.fromConn) // 로그인 회원은 멤버, 익명은 연결 단위
17
+ })
18
+ ```
19
+
20
+ 다시 연결하면 값이 바뀌고, 실시간 메시지에만 있습니다(히스토리에는 없음). 2026-10-30 이후 서버가 필요합니다.
21
+
22
+ ## [6.9.0] - 2026-10-30
23
+
24
+ ### Added — 확장자 없는 파일 배포: `deploy --include-extensionless`
25
+
26
+ 무작위 이름의 데이터 파일처럼 확장자 없이 서빙해야 하는 파일을 웹 스토리지에 배포할 수 있습니다. 켜면 확장자 없는
27
+ 파일을 바이너리로 올리고(한도를 넘는 큰 파일은 presigned 직접 업로드), 사이트는 `application/octet-stream` 으로
28
+ 바이트 그대로 서빙합니다(추적 스크립트 주입, gzip 없음, Range 지원, 캐시 1시간).
29
+
30
+ ```bash
31
+ npx connectbase-client deploy ./dist --include-extensionless
32
+ ```
33
+
34
+ `.connectbaserc` 에 `"includeExtensionless": true` 를 넣어도 같습니다. 기본값은 지금처럼 제외입니다 — `deploy` 의
35
+ 기본 디렉터리가 `.` 이라 프로젝트 루트를 배포하면 Dockerfile, LICENSE, 키 파일(id_rsa, kubeconfig 등) 같은 확장자
36
+ 없는 파일이 공개될 수 있어서입니다. 켜도 숨김 파일과 폴더(`.env`, `.ssh/` 등)는 여전히 제외되고, `_redirects`,
37
+ `_headers` 는 지금처럼 텍스트 사이트 설정 파일로 올라갑니다. 확장자 없는 파일이 제외되면 제외 목록 경고에 켜는 방법이
38
+ 함께 나옵니다.
39
+
40
+ 서빙은 2026-10-30 이후 서버(manifest 항목의 `binary` 표시)가 필요합니다. 그 전 배포에 들어 있던 확장자 없는 경로는
41
+ 지금처럼 HTML(SPA 라우트)로 다룹니다.
42
+
43
+ ### Added — `.mvx` 허용
44
+
45
+ `.mvx`(사이트 자체 형식의 3D/자료 파일)를 바이너리 허용 확장자로 추가했습니다. `application/octet-stream`, 캐시
46
+ 1시간으로 서빙되며, 큰 파일은 presigned 경로로 자동 오프로드됩니다.
47
+
6
48
  ## [6.8.0] - 2026-10-23
7
49
 
8
50
  ### Added — 서버 코드에서 토픽 만들기: `cb.push.createTopic(appId, name, options?)`
package/README.md CHANGED
@@ -294,7 +294,8 @@ In CI, read the Secret Key from a repository secret:
294
294
  ### Requirements
295
295
 
296
296
  - `index.html` must exist in the root of the deploy directory
297
- - Supported file types: `.html`, `.css`, `.js`, `.json`, `.svg`, `.png`, `.jpg`, `.gif`, `.webp`, `.woff`, `.woff2`, `.ttf`, `.mp3`, `.mp4`, `.pdf`, `.wasm`, 3D assets (`.glb`, `.gltf`, `.bin`), browser ML models (`.onnx`, `.tflite`, `.task`, `.safetensors`), etc.
297
+ - Supported file types: `.html`, `.css`, `.js`, `.json`, `.svg`, `.png`, `.jpg`, `.gif`, `.webp`, `.woff`, `.woff2`, `.ttf`, `.mp3`, `.mp4`, `.pdf`, `.wasm`, 3D/data assets (`.glb`, `.gltf`, `.bin`, `.mvx`), browser ML models (`.onnx`, `.tflite`, `.task`, `.safetensors`), etc.
298
+ - Files without an extension (e.g. randomly named data files) are skipped by default so that a project-root deploy does not publish files like `Dockerfile` or `id_rsa`. Pass `--include-extensionless` (or set `"includeExtensionless": true` in `.connectbaserc`) to deploy them as binary; they are served as `application/octet-stream` byte for byte (requires 6.9.0+).
298
299
  - Files with other extensions are skipped, and `deploy` prints the skipped list as a warning. Hidden files and folders (`.env`, `.git/`) are skipped silently.
299
300
  - Binary files over the per-file limit (~6 MB) are uploaded directly to object storage via presigned URLs.
300
301
 
package/dist/cli.js CHANGED
@@ -43,6 +43,7 @@ __export(cli_exports, {
43
43
  guessMimeType: () => guessMimeType,
44
44
  isAllowedDeployExt: () => isAllowedDeployExt,
45
45
  isBinaryDeployExt: () => isBinaryDeployExt,
46
+ isExtensionlessDataFile: () => isExtensionlessDataFile,
46
47
  makeRequest: () => makeRequest,
47
48
  normalizeRelativePath: () => normalizeRelativePath,
48
49
  packageManagerCommands: () => packageManagerCommands,
@@ -156,7 +157,7 @@ function handleTunnelError(msg, appId, localPort) {
156
157
  }
157
158
 
158
159
  // src/cli.ts
159
- var VERSION = "6.8.0" ? "6.8.0" : getPackageVersion();
160
+ var VERSION = "6.10.0" ? "6.10.0" : getPackageVersion();
160
161
  var DEFAULT_BASE_URL = "https://api.connectbase.world";
161
162
  var colors = {
162
163
  reset: "\x1B[0m",
@@ -218,6 +219,8 @@ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
218
219
  ".glb",
219
220
  ".gltf",
220
221
  ".bin",
222
+ // 사이트 자체 형식의 3D/자료 파일(바이너리, 2026-10-30). 브라우저는 fetch 로만 읽는다.
223
+ ".mvx",
221
224
  // 브라우저 추론용 ML 모델 (platform-issue 01a0f660). 전부 바이너리다.
222
225
  // .onnx — onnxruntime-web
223
226
  // .tflite — TensorFlow Lite (tfjs-tflite 등)
@@ -253,6 +256,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
253
256
  ".wasm",
254
257
  ".glb",
255
258
  ".bin",
259
+ ".mvx",
256
260
  ".onnx",
257
261
  ".tflite",
258
262
  ".task",
@@ -283,6 +287,7 @@ var MIME_BY_EXT = {
283
287
  ".glb": "model/gltf-binary",
284
288
  ".gltf": "model/gltf+json",
285
289
  ".bin": "application/octet-stream",
290
+ ".mvx": "application/octet-stream",
286
291
  // ML 모델은 등록된 표준 MIME 이 없다. 로더(onnxruntime-web/MediaPipe/transformers.js)는
287
292
  // fetch → ArrayBuffer 로 읽으므로 octet-stream 이면 충분하다.
288
293
  ".onnx": "application/octet-stream",
@@ -305,6 +310,10 @@ function isAllowedDeployExt(filePath) {
305
310
  function isBinaryDeployExt(filePath) {
306
311
  return BINARY_EXTENSIONS.has(path2.extname(filePath).toLowerCase());
307
312
  }
313
+ function isExtensionlessDataFile(filePath) {
314
+ const base = path2.basename(filePath);
315
+ return path2.extname(base) === "" && !ALLOWED_EXTENSIONLESS_FILES.has(base);
316
+ }
308
317
  var WEB_FILE_MAX_STORED_BYTES = 6 * 1024 * 1024 - 64 * 1024;
309
318
  function sha256Hex(value) {
310
319
  return crypto.createHash("sha256").update(value, "utf8").digest("hex");
@@ -365,19 +374,21 @@ function loadConfig() {
365
374
  if (rcContent.storageId) config.storageId = rcContent.storageId;
366
375
  if (rcContent.baseUrl) config.baseUrl = rcContent.baseUrl;
367
376
  if (rcContent.deployDir) config.deployDir = rcContent.deployDir;
377
+ if (rcContent.includeExtensionless === true)
378
+ config.includeExtensionless = true;
368
379
  } catch {
369
380
  warn(".connectbaserc \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
370
381
  }
371
382
  }
372
383
  return config;
373
384
  }
374
- function collectDeployFiles(dir) {
385
+ function collectDeployFiles(dir, options = {}) {
375
386
  const skipped = [];
376
- const files = collectFiles(dir, dir, skipped);
387
+ const files = collectFiles(dir, dir, skipped, options);
377
388
  skipped.sort();
378
389
  return { files, skipped };
379
390
  }
380
- function collectFiles(dir, baseDir, skipped) {
391
+ function collectFiles(dir, baseDir, skipped, options) {
381
392
  const files = [];
382
393
  const entries = fs2.readdirSync(dir, { withFileTypes: true });
383
394
  for (const entry of entries) {
@@ -385,18 +396,19 @@ function collectFiles(dir, baseDir, skipped) {
385
396
  const relativePath = path2.relative(baseDir, fullPath);
386
397
  if (entry.isDirectory()) {
387
398
  if (!entry.name.startsWith(".")) {
388
- files.push(...collectFiles(fullPath, baseDir, skipped));
399
+ files.push(...collectFiles(fullPath, baseDir, skipped, options));
389
400
  }
390
401
  } else if (entry.isFile()) {
391
402
  const ext = path2.extname(entry.name).toLowerCase();
392
403
  if (entry.name.startsWith(".")) {
393
404
  continue;
394
405
  }
395
- if (!isAllowedDeployExt(entry.name)) {
406
+ const extensionlessData = options.includeExtensionless === true && isExtensionlessDataFile(entry.name);
407
+ if (!extensionlessData && !isAllowedDeployExt(entry.name)) {
396
408
  skipped.push(normalizeRelativePath(relativePath));
397
409
  continue;
398
410
  }
399
- const isBinary = BINARY_EXTENSIONS.has(ext);
411
+ const isBinary = extensionlessData || BINARY_EXTENSIONS.has(ext);
400
412
  let content;
401
413
  if (isBinary) {
402
414
  content = fs2.readFileSync(fullPath).toString("base64");
@@ -438,6 +450,11 @@ function formatSkippedFilesReport(skipped, limit = SKIPPED_FILES_LIST_LIMIT) {
438
450
  lines.push(
439
451
  " \uD544\uC694\uD55C \uD30C\uC77C\uC774\uBA74 \uD5C8\uC6A9 \uD655\uC7A5\uC790\uB85C \uBC14\uAFB8\uAC70\uB098, \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(cb.storage)\uC5D0 \uC62C\uB824 URL \uB85C \uCC38\uC870\uD558\uC138\uC694."
440
452
  );
453
+ if (skipped.some((p) => isExtensionlessDataFile(p))) {
454
+ lines.push(
455
+ ' \uD655\uC7A5\uC790 \uC5C6\uB294 \uD30C\uC77C\uC740 --include-extensionless (\uB610\uB294 .connectbaserc \uC758 "includeExtensionless": true) \uB85C \uBC14\uC774\uB108\uB9AC(application/octet-stream)\uB85C \uBC30\uD3EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.'
456
+ );
457
+ }
441
458
  return lines;
442
459
  }
443
460
  function reportSkippedFiles(skipped) {
@@ -766,8 +783,18 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
766
783
  }
767
784
  info(`\uBC30\uD3EC \uC900\uBE44 \uC911... ${dir}`);
768
785
  process.stdout.write(`${colors.dim}\uD30C\uC77C \uC218\uC9D1 \uC911...${colors.reset}`);
769
- const { files, skipped } = collectDeployFiles(dir);
786
+ const { files, skipped } = collectDeployFiles(dir, {
787
+ includeExtensionless: deployOpts.includeExtensionless
788
+ });
770
789
  process.stdout.write(`\r${colors.dim} \r${colors.reset}`);
790
+ if (deployOpts.includeExtensionless) {
791
+ const extensionless = files.filter((f) => isExtensionlessDataFile(f.path));
792
+ if (extensionless.length > 0) {
793
+ info(
794
+ `\uD655\uC7A5\uC790 \uC5C6\uB294 \uD30C\uC77C ${extensionless.length}\uAC1C\uB97C \uBC14\uC774\uB108\uB9AC(application/octet-stream)\uB85C \uD3EC\uD568\uD569\uB2C8\uB2E4 (--include-extensionless)`
795
+ );
796
+ }
797
+ }
771
798
  if (files.length === 0) {
772
799
  reportSkippedFiles(skipped);
773
800
  error("\uBC30\uD3EC\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
@@ -3670,6 +3697,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
3670
3697
  --label <name> Endpoint binding \uC790\uB3D9 \uB4F1\uB85D (tunnel \uC804\uC6A9) \u2014 SDK \uC758 cb.endpoint.call(label) \uD638\uCD9C \uAC00\uB2A5
3671
3698
  --description <text> Endpoint binding \uC124\uBA85 (--label \uB3D9\uBC18 \uC2DC\uB9CC)
3672
3699
  -d, --dev QA \uD658\uACBD\uC5D0\uB9CC \uBC30\uD3EC (deploy \uC804\uC6A9, deprecated \u2014 QA \uBCC4\uCE6D)
3700
+ --include-extensionless \uD655\uC7A5\uC790 \uC5C6\uB294 \uD30C\uC77C\uB3C4 \uBC14\uC774\uB108\uB9AC(application/octet-stream)\uB85C \uBC30\uD3EC (deploy \uC804\uC6A9, \uAE30\uBCF8: \uC81C\uC678)
3673
3701
  -y, --yes \uBE44\uB300\uD654\uD615 \uCD08\uAE30\uD654 \u2014 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uAE30\uBCF8\uAC12/\uD50C\uB798\uADF8\uB85C \uC9C4\uD589 (init \uC804\uC6A9, CI/AI \uC5D0\uC774\uC804\uD2B8\uC6A9)
3674
3702
  -a, --app <id> \uAE30\uC874 \uC571 ID \uC9C0\uC815 (init \uC804\uC6A9)
3675
3703
  --secret-key <key> Secret Key (cb_sk_) \uC9C0\uC815 (init/mcp)
@@ -3727,7 +3755,8 @@ ${colors.yellow}\uC124\uC815 \uD30C\uC77C (.connectbaserc):${colors.reset}
3727
3755
  "publicKey": "cb_pk_...",
3728
3756
  "secretKey": "cb_sk_...",
3729
3757
  "storageId": "your-storage-id",
3730
- "deployDir": "./dist"
3758
+ "deployDir": "./dist",
3759
+ "includeExtensionless": false ${colors.dim}// true \uBA74 \uD655\uC7A5\uC790 \uC5C6\uB294 \uD30C\uC77C\uB3C4 \uBC30\uD3EC (= --include-extensionless)${colors.reset}
3731
3760
  }
3732
3761
  `);
3733
3762
  }
@@ -3771,6 +3800,8 @@ function parseArgs(args) {
3771
3800
  result.options.showToken = "true";
3772
3801
  } else if (arg === "-d" || arg === "--dev") {
3773
3802
  result.options.dev = "true";
3803
+ } else if (arg === "--include-extensionless") {
3804
+ result.options.includeExtensionless = "true";
3774
3805
  } else if (arg === "--check") {
3775
3806
  result.options.check = "true";
3776
3807
  } else if (arg === "--skip-docs") {
@@ -3845,7 +3876,9 @@ async function main() {
3845
3876
  process.exit(1);
3846
3877
  }
3847
3878
  const isDev = parsed.options.dev === "true";
3848
- const deployOpts = {};
3879
+ const deployOpts = {
3880
+ includeExtensionless: parsed.options.includeExtensionless === "true" || fileConfig.includeExtensionless === true
3881
+ };
3849
3882
  if (parsed.options.timeout) {
3850
3883
  const t = parseInt(parsed.options.timeout, 10);
3851
3884
  if (!Number.isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
@@ -3960,6 +3993,7 @@ main().catch((err) => {
3960
3993
  guessMimeType,
3961
3994
  isAllowedDeployExt,
3962
3995
  isBinaryDeployExt,
3996
+ isExtensionlessDataFile,
3963
3997
  makeRequest,
3964
3998
  normalizeRelativePath,
3965
3999
  packageManagerCommands,