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 +42 -0
- package/README.md +2 -1
- package/dist/cli.js +44 -10
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +7 -2
- package/dist/index.mjs +7 -2
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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,
|