connectbase-client 3.49.0 → 3.50.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/dist/index.mjs CHANGED
@@ -2251,18 +2251,27 @@ var StorageAPI = class {
2251
2251
  * Presigned URL 로 직접 업로드. 호출 전에 URL 스킴(https)을 검증해
2252
2252
  * 서버 응답을 그대로 신뢰하는 SSRF/오용 경로를 차단.
2253
2253
  * 타임아웃과 외부 signal 을 모두 지원한다.
2254
+ *
2255
+ * `onProgress` 가 주어지고 `XMLHttpRequest` 를 쓸 수 있는(브라우저) 환경이면
2256
+ * XHR 의 `upload.onprogress` 로 바이트 단위 진행률을 보고한다. 그 외에는
2257
+ * fetch 로 업로드하고 시작(0%)/완료(100%)만 통지한다(Node/Edge 호환).
2254
2258
  */
2255
2259
  async uploadToPresigned(rawUrl, file, options) {
2256
2260
  const parsed = validateExternalUrl(rawUrl, {
2257
2261
  allowLocalhost: isLocalhostOrigin(),
2258
2262
  context: "storage.presigned-url"
2259
2263
  });
2264
+ const url = parsed.toString();
2265
+ if (options?.onProgress && typeof XMLHttpRequest !== "undefined") {
2266
+ return this.putViaXhr(url, file, options.onProgress, options);
2267
+ }
2260
2268
  const { signal, cleanup } = createTimeoutController({
2261
2269
  timeout: options?.timeout,
2262
2270
  signal: options?.signal
2263
2271
  });
2264
2272
  try {
2265
- const response = await fetch(parsed.toString(), {
2273
+ options?.onProgress?.({ loaded: 0, total: file.size, percentage: 0 });
2274
+ const response = await fetch(url, {
2266
2275
  method: "PUT",
2267
2276
  body: file,
2268
2277
  headers: {
@@ -2277,10 +2286,79 @@ var StorageAPI = class {
2277
2286
  "PRESIGNED_UPLOAD_FAILED"
2278
2287
  );
2279
2288
  }
2289
+ options?.onProgress?.({ loaded: file.size, total: file.size, percentage: 100 });
2280
2290
  } finally {
2281
2291
  cleanup();
2282
2292
  }
2283
2293
  }
2294
+ /**
2295
+ * XMLHttpRequest 기반 PUT 업로드. `upload.onprogress` 로 바이트 진행률을
2296
+ * 보고하며, 외부 signal(취소)과 타임아웃을 모두 지원한다. URL 은 호출 전에
2297
+ * 이미 검증된 값을 받는다(uploadToPresigned 에서 스킴 검증 수행).
2298
+ */
2299
+ putViaXhr(url, file, onProgress, options) {
2300
+ return new Promise((resolve, reject) => {
2301
+ const external = options?.signal;
2302
+ if (external?.aborted) {
2303
+ reject(external.reason ?? new DOMException("Aborted", "AbortError"));
2304
+ return;
2305
+ }
2306
+ const xhr = new XMLHttpRequest();
2307
+ xhr.open("PUT", url, true);
2308
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
2309
+ const timeout = options?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
2310
+ if (timeout > 0 && Number.isFinite(timeout)) {
2311
+ xhr.timeout = timeout;
2312
+ }
2313
+ let abortListener = null;
2314
+ const cleanup = () => {
2315
+ if (external && abortListener) {
2316
+ external.removeEventListener("abort", abortListener);
2317
+ }
2318
+ };
2319
+ if (external) {
2320
+ abortListener = () => xhr.abort();
2321
+ external.addEventListener("abort", abortListener, { once: true });
2322
+ }
2323
+ xhr.upload.onprogress = (event) => {
2324
+ if (!event.lengthComputable) return;
2325
+ const total = event.total || file.size;
2326
+ onProgress({
2327
+ loaded: event.loaded,
2328
+ total,
2329
+ percentage: total > 0 ? Math.round(event.loaded / total * 100) : 0
2330
+ });
2331
+ };
2332
+ xhr.onload = () => {
2333
+ cleanup();
2334
+ if (xhr.status >= 200 && xhr.status < 300) {
2335
+ onProgress({ loaded: file.size, total: file.size, percentage: 100 });
2336
+ resolve();
2337
+ } else {
2338
+ reject(
2339
+ new ApiError(
2340
+ xhr.status,
2341
+ `Upload failed: ${xhr.statusText || "unknown error"}`,
2342
+ "PRESIGNED_UPLOAD_FAILED"
2343
+ )
2344
+ );
2345
+ }
2346
+ };
2347
+ xhr.onerror = () => {
2348
+ cleanup();
2349
+ reject(new ApiError(0, "Upload failed: network error", "PRESIGNED_UPLOAD_FAILED"));
2350
+ };
2351
+ xhr.ontimeout = () => {
2352
+ cleanup();
2353
+ reject(new DOMException(`Request timed out after ${timeout}ms`, "TimeoutError"));
2354
+ };
2355
+ xhr.onabort = () => {
2356
+ cleanup();
2357
+ reject(external?.reason ?? new DOMException("Aborted", "AbortError"));
2358
+ };
2359
+ xhr.send(file);
2360
+ });
2361
+ }
2284
2362
  /**
2285
2363
  * 파일 목록 조회
2286
2364
  *
@@ -2310,14 +2388,28 @@ var StorageAPI = class {
2310
2388
  *
2311
2389
  * 서버를 거치지 않고 Object Storage에 직접 업로드합니다.
2312
2390
  *
2391
+ * 3번째 인자는 부모 폴더 ID(문자열) 또는 옵션 객체를 받습니다. 옵션으로
2392
+ * `onProgress` 콜백을 넘기면 업로드 진행률(%)을 실시간으로 받을 수 있습니다
2393
+ * (브라우저 환경). 기존 `uploadFile(id, file, 'folder-id')` 호출과 하위 호환됩니다.
2394
+ *
2313
2395
  * @example
2314
2396
  * ```ts
2315
2397
  * const fileInput = document.querySelector('input[type="file"]')
2316
2398
  * const result = await cb.storage.uploadFile('storage-id', fileInput.files[0])
2317
2399
  * console.log(result.url) // 업로드된 파일 URL
2400
+ *
2401
+ * // 특정 폴더에 업로드 (기존 방식 그대로)
2402
+ * await cb.storage.uploadFile('storage-id', file, 'folder-id')
2403
+ *
2404
+ * // 진행률 표시 + 특정 폴더
2405
+ * await cb.storage.uploadFile('storage-id', file, {
2406
+ * parentId: 'folder-id',
2407
+ * onProgress: (p) => console.log(`${p.percentage}%`), // { loaded, total, percentage }
2408
+ * })
2318
2409
  * ```
2319
2410
  */
2320
- async uploadFile(storageId, file, parentId) {
2411
+ async uploadFile(storageId, file, parentIdOrOptions) {
2412
+ const options = typeof parentIdOrOptions === "string" ? { parentId: parentIdOrOptions } : parentIdOrOptions ?? {};
2321
2413
  const prefix = this.getPublicPrefix();
2322
2414
  const presignedResponse = await this.http.post(
2323
2415
  `${prefix}/storages/files/${storageId}/presigned-url`,
@@ -2325,10 +2417,14 @@ var StorageAPI = class {
2325
2417
  file_name: file.name,
2326
2418
  file_size: file.size,
2327
2419
  mime_type: file.type || "application/octet-stream",
2328
- parent_id: parentId
2420
+ parent_id: options.parentId
2329
2421
  }
2330
2422
  );
2331
- await this.uploadToPresigned(presignedResponse.upload_url, file);
2423
+ await this.uploadToPresigned(presignedResponse.upload_url, file, {
2424
+ onProgress: options.onProgress,
2425
+ signal: options.signal,
2426
+ timeout: options.timeout
2427
+ });
2332
2428
  const completeResponse = await this.http.post(
2333
2429
  `${prefix}/storages/files/${storageId}/complete-upload`,
2334
2430
  { file_id: presignedResponse.file_id }
@@ -2347,11 +2443,14 @@ var StorageAPI = class {
2347
2443
  }
2348
2444
  /**
2349
2445
  * 여러 파일 업로드
2446
+ *
2447
+ * `onProgress` 를 옵션으로 넘기면 **파일별로** 진행률 콜백이 호출됩니다
2448
+ * (각 파일마다 0→100%). 전체 진행률이 필요하면 완료된 파일 수를 직접 집계하세요.
2350
2449
  */
2351
- async uploadFiles(storageId, files, parentId) {
2450
+ async uploadFiles(storageId, files, parentIdOrOptions) {
2352
2451
  const results = [];
2353
2452
  for (const file of files) {
2354
- const result = await this.uploadFile(storageId, file, parentId);
2453
+ const result = await this.uploadFile(storageId, file, parentIdOrOptions);
2355
2454
  results.push(result);
2356
2455
  }
2357
2456
  return results;
@@ -2450,7 +2549,11 @@ var StorageAPI = class {
2450
2549
  overwrite
2451
2550
  }
2452
2551
  );
2453
- await this.uploadToPresigned(presignedResponse.upload_url, file);
2552
+ await this.uploadToPresigned(presignedResponse.upload_url, file, {
2553
+ onProgress: options?.onProgress,
2554
+ signal: options?.signal,
2555
+ timeout: options?.timeout
2556
+ });
2454
2557
  const completeResponse = await this.http.post(
2455
2558
  `${prefix}/storages/files/${storageId}/complete-upload`,
2456
2559
  { file_id: presignedResponse.file_id }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.49.0",
3
+ "version": "3.50.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",