connectbase-client 3.45.0 → 3.47.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.js CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  GameRoom: () => GameRoom,
34
34
  GameRoomTransport: () => GameRoomTransport,
35
35
  NativeAPI: () => NativeAPI,
36
+ RolesAPI: () => RolesAPI,
36
37
  SessionManager: () => SessionManager,
37
38
  VideoProcessingError: () => VideoProcessingError,
38
39
  default: () => index_default,
@@ -5448,6 +5449,38 @@ var PaymentAPI = class {
5448
5449
  const prefix = this.getPublicPrefix();
5449
5450
  return this.http.get(`${prefix}/payments/orders/${orderId}`);
5450
5451
  }
5452
+ /**
5453
+ * 앱의 결제 내역 목록을 조회한다 (필터/페이지네이션 지원).
5454
+ *
5455
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["payment:read"]) 또는 콘솔 JWT
5456
+ * 인증이 필요하다. 재무 데이터이므로 Public Key(cb_pk_) 단독 브라우저 SDK 인스턴스로는 호출할 수
5457
+ * 없다. (다른 payment 메서드와 달리 /v1/public 이 아니라 앱 스코프 dual-auth 라우트를 쓴다.)
5458
+ *
5459
+ * @example
5460
+ * ```typescript
5461
+ * // 함수(service_role, management_scopes: ["payment:read"]) 안에서
5462
+ * const { payments, total } = await ctx.cbAdmin.payment.list(ctx.appId, { status: 'done', limit: 20 })
5463
+ * ```
5464
+ */
5465
+ async list(appId, options) {
5466
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
5467
+ throw new Error(
5468
+ 'cb.payment.list() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["payment:read"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
5469
+ );
5470
+ }
5471
+ const params = new URLSearchParams();
5472
+ if (options?.status) params.set("status", options.status);
5473
+ if (options?.startDate) params.set("start_date", options.startDate);
5474
+ if (options?.endDate) params.set("end_date", options.endDate);
5475
+ if (options?.limit !== void 0)
5476
+ params.set("limit", String(options.limit));
5477
+ if (options?.offset !== void 0)
5478
+ params.set("offset", String(options.offset));
5479
+ const qs = params.toString();
5480
+ return this.http.get(
5481
+ `/v1/apps/${appId}/payments${qs ? `?${qs}` : ""}`
5482
+ );
5483
+ }
5451
5484
  };
5452
5485
 
5453
5486
  // src/api/subscription.ts
@@ -6049,6 +6082,28 @@ var PushAPI = class {
6049
6082
  };
6050
6083
  return this.http.post(`/v1/apps/${appId}/push/send-to-topic`, body);
6051
6084
  }
6085
+ /**
6086
+ * 앱의 푸시 통계(도달/오픈율/클릭율, 디바이스/메시지/토픽 수)를 조회한다.
6087
+ *
6088
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["push:read"]) 또는 콘솔 JWT
6089
+ * 인증이 필요하다. sendToMembers/sendToTopic 과 동일한 위임 가드 — cb_pk_ 단독(브라우저) SDK
6090
+ * 인스턴스는 차단하고, 위임 Bearer(ctx.cbAdmin) 인스턴스는 허용한다.
6091
+ *
6092
+ * @example
6093
+ * ```typescript
6094
+ * // 함수(service_role, management_scopes: ["push:read"]) 안에서
6095
+ * const stats = await ctx.cbAdmin.push.getStats(ctx.appId)
6096
+ * console.log(stats.open_rate, stats.click_rate)
6097
+ * ```
6098
+ */
6099
+ async getStats(appId) {
6100
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6101
+ throw new Error(
6102
+ 'cb.push.getStats() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["push:read"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
6103
+ );
6104
+ }
6105
+ return this.http.get(`/v1/apps/${appId}/push/stats`);
6106
+ }
6052
6107
  // ============ Helper Methods ============
6053
6108
  /**
6054
6109
  * 브라우저 고유 ID 생성 (localStorage에 저장)
@@ -6109,6 +6164,72 @@ var PushAPI = class {
6109
6164
  }
6110
6165
  };
6111
6166
 
6167
+ // src/api/roles.ts
6168
+ var RolesAPI = class {
6169
+ constructor(http) {
6170
+ this.http = http;
6171
+ }
6172
+ ensureServerAuth(method) {
6173
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6174
+ throw new Error(
6175
+ `cb.roles.${method}() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["role:read" | "role:manage"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.`
6176
+ );
6177
+ }
6178
+ }
6179
+ /** 앱의 역할 목록을 조회한다. (management_scope: `role:read`) */
6180
+ async list(appId) {
6181
+ this.ensureServerAuth("list");
6182
+ return this.http.get(`/v1/apps/${appId}/app-roles`);
6183
+ }
6184
+ /** 역할 상세(권한/할당 사용자 포함)를 조회한다. (management_scope: `role:read`) */
6185
+ async get(appId, roleId) {
6186
+ this.ensureServerAuth("get");
6187
+ return this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6188
+ }
6189
+ /** 역할을 생성한다. (management_scope: `role:manage`) */
6190
+ async create(appId, payload) {
6191
+ this.ensureServerAuth("create");
6192
+ return this.http.post(`/v1/apps/${appId}/app-roles`, {
6193
+ role_title: payload.title,
6194
+ role_description: payload.description,
6195
+ selected_permission_ids: payload.permissionIds ?? []
6196
+ });
6197
+ }
6198
+ /**
6199
+ * 역할을 수정한다 — **전체 동기화(replace)**. permissionIds / userIds 는 전달값으로 대체된다.
6200
+ * (management_scope: `role:manage`)
6201
+ */
6202
+ async update(appId, roleId, payload) {
6203
+ this.ensureServerAuth("update");
6204
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6205
+ role_title: payload.title,
6206
+ role_description: payload.description,
6207
+ selected_permission_ids: payload.permissionIds,
6208
+ selected_user_ids: payload.userIds
6209
+ });
6210
+ }
6211
+ /**
6212
+ * 역할에 사용자를 할당한다 (제목/설명/권한 보존). EditRole 이 전체 동기화라, 현재 상세를 읽어
6213
+ * 나머지 필드를 유지한 채 사용자 목록만 교체해 PUT 한다. userIds 는 "이 역할을 가질 사용자
6214
+ * 전체"다 (추가가 아니라 동기화). (management_scope: `role:manage`)
6215
+ */
6216
+ async assign(appId, roleId, userIds) {
6217
+ this.ensureServerAuth("assign");
6218
+ const detail = await this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6219
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6220
+ role_title: detail.role_name,
6221
+ role_description: detail.role_description,
6222
+ selected_permission_ids: detail.permission_item.map((p) => p.id),
6223
+ selected_user_ids: userIds
6224
+ });
6225
+ }
6226
+ /** 역할을 삭제한다. (management_scope: `role:manage`) */
6227
+ async delete(appId, roleId) {
6228
+ this.ensureServerAuth("delete");
6229
+ await this.http.delete(`/v1/apps/${appId}/app-roles/${roleId}`);
6230
+ }
6231
+ };
6232
+
6112
6233
  // src/api/video.ts
6113
6234
  var DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
6114
6235
  var VideoProcessingError = class extends Error {
@@ -8061,6 +8182,8 @@ var AdsAPI = class {
8061
8182
  // src/api/native.ts
8062
8183
  var NativeAPI = class {
8063
8184
  constructor() {
8185
+ /** 웹 Web Speech API 인스턴스 (stop() 용) */
8186
+ this._webSpeechRecognition = null;
8064
8187
  /**
8065
8188
  * 크로스 플랫폼 클립보드 API
8066
8189
  */
@@ -8487,6 +8610,109 @@ var NativeAPI = class {
8487
8610
  return false;
8488
8611
  }
8489
8612
  };
8613
+ /**
8614
+ * 크로스 플랫폼 음성 인식(STT) API
8615
+ *
8616
+ * - 모바일(패키징 앱): 네이티브 STT 브릿지(expo-speech-recognition — iOS SFSpeechRecognizer / Android SpeechRecognizer)
8617
+ * - 웹/데스크톱: Web Speech API(webkitSpeechRecognition)
8618
+ *
8619
+ * iOS WKWebView 는 Web Speech API 를 지원하지 않으므로, 패키징 앱에서는 speech 네이티브 기능을 켜면
8620
+ * 자동으로 네이티브 브릿지 경로를 사용한다.
8621
+ *
8622
+ * @example
8623
+ * ```typescript
8624
+ * if (await cb.native.speech.isAvailable()) {
8625
+ * const result = await cb.native.speech.recognize({
8626
+ * lang: 'ko-KR',
8627
+ * onPartial: (text) => console.log('부분 결과:', text),
8628
+ * })
8629
+ * console.log('최종:', result.transcript)
8630
+ * }
8631
+ * ```
8632
+ */
8633
+ this.speech = {
8634
+ /**
8635
+ * 음성 인식 지원 여부
8636
+ */
8637
+ isAvailable: async () => {
8638
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8639
+ const r = await window.NativeBridge.speech.isAvailable();
8640
+ return r.available;
8641
+ }
8642
+ if (typeof window === "undefined") return false;
8643
+ return "SpeechRecognition" in window || "webkitSpeechRecognition" in window;
8644
+ },
8645
+ /**
8646
+ * 음성 인식 시작 → 최종 transcript 반환.
8647
+ * `options.onPartial` 로 중간 결과를 실시간 수신할 수 있다.
8648
+ */
8649
+ recognize: async (options) => {
8650
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8651
+ let partialHandler;
8652
+ if (options?.onPartial) {
8653
+ partialHandler = (e) => {
8654
+ const detail = e.detail;
8655
+ if (detail?.transcript) options.onPartial?.(detail.transcript);
8656
+ };
8657
+ window.addEventListener("nativeSpeechPartial", partialHandler);
8658
+ }
8659
+ try {
8660
+ return await window.NativeBridge.speech.recognize(options);
8661
+ } finally {
8662
+ if (partialHandler) window.removeEventListener("nativeSpeechPartial", partialHandler);
8663
+ }
8664
+ }
8665
+ const SR = window;
8666
+ const Ctor = SR.SpeechRecognition || SR.webkitSpeechRecognition;
8667
+ if (!Ctor) {
8668
+ throw new Error("\uC774 \uD50C\uB7AB\uD3FC\uC5D0\uC11C\uB294 \uC74C\uC131 \uC778\uC2DD\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD328\uD0A4\uC9D5 \uC571\uC5D0\uC11C\uB294 speech \uB124\uC774\uD2F0\uBE0C \uAE30\uB2A5\uC744 \uD65C\uC131\uD654\uD558\uC138\uC694.");
8669
+ }
8670
+ return new Promise((resolve, reject) => {
8671
+ const recognition = new Ctor();
8672
+ this._webSpeechRecognition = recognition;
8673
+ recognition.lang = options?.lang ?? "en-US";
8674
+ recognition.interimResults = options?.interim ?? true;
8675
+ recognition.continuous = options?.continuous ?? false;
8676
+ let finalTranscript = "";
8677
+ recognition.onresult = (event) => {
8678
+ let interim = "";
8679
+ for (let i = event.resultIndex; i < event.results.length; i++) {
8680
+ const res = event.results[i];
8681
+ if (res.isFinal) {
8682
+ finalTranscript += res[0].transcript;
8683
+ } else {
8684
+ interim += res[0].transcript;
8685
+ }
8686
+ }
8687
+ if (interim && options?.onPartial) options.onPartial(interim);
8688
+ };
8689
+ recognition.onerror = (event) => {
8690
+ this._webSpeechRecognition = null;
8691
+ reject(new Error(event?.error || "\uC74C\uC131 \uC778\uC2DD \uC624\uB958"));
8692
+ };
8693
+ recognition.onend = () => {
8694
+ this._webSpeechRecognition = null;
8695
+ resolve({ transcript: finalTranscript.trim(), isFinal: true });
8696
+ };
8697
+ recognition.start();
8698
+ });
8699
+ },
8700
+ /**
8701
+ * 진행 중인 음성 인식 중지
8702
+ */
8703
+ stop: async () => {
8704
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8705
+ await window.NativeBridge.speech.stop();
8706
+ return;
8707
+ }
8708
+ if (this._webSpeechRecognition) {
8709
+ try {
8710
+ this._webSpeechRecognition.stop();
8711
+ } catch {
8712
+ }
8713
+ }
8714
+ }
8715
+ };
8490
8716
  }
8491
8717
  /**
8492
8718
  * 현재 플랫폼 감지
@@ -11092,6 +11318,7 @@ var ConnectBase = class {
11092
11318
  this.payment = new PaymentAPI(this.http);
11093
11319
  this.subscription = new SubscriptionAPI(this.http);
11094
11320
  this.push = new PushAPI(this.http);
11321
+ this.roles = new RolesAPI(this.http);
11095
11322
  this.video = new VideoAPI(this.http, config.videoUrl || env("CB_VIDEO_URL") || DEFAULT_VIDEO_URL);
11096
11323
  this.game = new GameAPI(this.http, config.gameUrl || env("CB_GAME_URL") || DEFAULT_GAME_URL, config.appId);
11097
11324
  this.ads = new AdsAPI(this.http);
@@ -11187,6 +11414,7 @@ var index_default = ConnectBase;
11187
11414
  GameRoom,
11188
11415
  GameRoomTransport,
11189
11416
  NativeAPI,
11417
+ RolesAPI,
11190
11418
  SessionManager,
11191
11419
  VideoProcessingError,
11192
11420
  detectInAppBrowser,
package/dist/index.mjs CHANGED
@@ -5403,6 +5403,38 @@ var PaymentAPI = class {
5403
5403
  const prefix = this.getPublicPrefix();
5404
5404
  return this.http.get(`${prefix}/payments/orders/${orderId}`);
5405
5405
  }
5406
+ /**
5407
+ * 앱의 결제 내역 목록을 조회한다 (필터/페이지네이션 지원).
5408
+ *
5409
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["payment:read"]) 또는 콘솔 JWT
5410
+ * 인증이 필요하다. 재무 데이터이므로 Public Key(cb_pk_) 단독 브라우저 SDK 인스턴스로는 호출할 수
5411
+ * 없다. (다른 payment 메서드와 달리 /v1/public 이 아니라 앱 스코프 dual-auth 라우트를 쓴다.)
5412
+ *
5413
+ * @example
5414
+ * ```typescript
5415
+ * // 함수(service_role, management_scopes: ["payment:read"]) 안에서
5416
+ * const { payments, total } = await ctx.cbAdmin.payment.list(ctx.appId, { status: 'done', limit: 20 })
5417
+ * ```
5418
+ */
5419
+ async list(appId, options) {
5420
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
5421
+ throw new Error(
5422
+ 'cb.payment.list() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["payment:read"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
5423
+ );
5424
+ }
5425
+ const params = new URLSearchParams();
5426
+ if (options?.status) params.set("status", options.status);
5427
+ if (options?.startDate) params.set("start_date", options.startDate);
5428
+ if (options?.endDate) params.set("end_date", options.endDate);
5429
+ if (options?.limit !== void 0)
5430
+ params.set("limit", String(options.limit));
5431
+ if (options?.offset !== void 0)
5432
+ params.set("offset", String(options.offset));
5433
+ const qs = params.toString();
5434
+ return this.http.get(
5435
+ `/v1/apps/${appId}/payments${qs ? `?${qs}` : ""}`
5436
+ );
5437
+ }
5406
5438
  };
5407
5439
 
5408
5440
  // src/api/subscription.ts
@@ -6004,6 +6036,28 @@ var PushAPI = class {
6004
6036
  };
6005
6037
  return this.http.post(`/v1/apps/${appId}/push/send-to-topic`, body);
6006
6038
  }
6039
+ /**
6040
+ * 앱의 푸시 통계(도달/오픈율/클릭율, 디바이스/메시지/토픽 수)를 조회한다.
6041
+ *
6042
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["push:read"]) 또는 콘솔 JWT
6043
+ * 인증이 필요하다. sendToMembers/sendToTopic 과 동일한 위임 가드 — cb_pk_ 단독(브라우저) SDK
6044
+ * 인스턴스는 차단하고, 위임 Bearer(ctx.cbAdmin) 인스턴스는 허용한다.
6045
+ *
6046
+ * @example
6047
+ * ```typescript
6048
+ * // 함수(service_role, management_scopes: ["push:read"]) 안에서
6049
+ * const stats = await ctx.cbAdmin.push.getStats(ctx.appId)
6050
+ * console.log(stats.open_rate, stats.click_rate)
6051
+ * ```
6052
+ */
6053
+ async getStats(appId) {
6054
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6055
+ throw new Error(
6056
+ 'cb.push.getStats() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["push:read"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
6057
+ );
6058
+ }
6059
+ return this.http.get(`/v1/apps/${appId}/push/stats`);
6060
+ }
6007
6061
  // ============ Helper Methods ============
6008
6062
  /**
6009
6063
  * 브라우저 고유 ID 생성 (localStorage에 저장)
@@ -6064,6 +6118,72 @@ var PushAPI = class {
6064
6118
  }
6065
6119
  };
6066
6120
 
6121
+ // src/api/roles.ts
6122
+ var RolesAPI = class {
6123
+ constructor(http) {
6124
+ this.http = http;
6125
+ }
6126
+ ensureServerAuth(method) {
6127
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6128
+ throw new Error(
6129
+ `cb.roles.${method}() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["role:read" | "role:manage"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.`
6130
+ );
6131
+ }
6132
+ }
6133
+ /** 앱의 역할 목록을 조회한다. (management_scope: `role:read`) */
6134
+ async list(appId) {
6135
+ this.ensureServerAuth("list");
6136
+ return this.http.get(`/v1/apps/${appId}/app-roles`);
6137
+ }
6138
+ /** 역할 상세(권한/할당 사용자 포함)를 조회한다. (management_scope: `role:read`) */
6139
+ async get(appId, roleId) {
6140
+ this.ensureServerAuth("get");
6141
+ return this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6142
+ }
6143
+ /** 역할을 생성한다. (management_scope: `role:manage`) */
6144
+ async create(appId, payload) {
6145
+ this.ensureServerAuth("create");
6146
+ return this.http.post(`/v1/apps/${appId}/app-roles`, {
6147
+ role_title: payload.title,
6148
+ role_description: payload.description,
6149
+ selected_permission_ids: payload.permissionIds ?? []
6150
+ });
6151
+ }
6152
+ /**
6153
+ * 역할을 수정한다 — **전체 동기화(replace)**. permissionIds / userIds 는 전달값으로 대체된다.
6154
+ * (management_scope: `role:manage`)
6155
+ */
6156
+ async update(appId, roleId, payload) {
6157
+ this.ensureServerAuth("update");
6158
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6159
+ role_title: payload.title,
6160
+ role_description: payload.description,
6161
+ selected_permission_ids: payload.permissionIds,
6162
+ selected_user_ids: payload.userIds
6163
+ });
6164
+ }
6165
+ /**
6166
+ * 역할에 사용자를 할당한다 (제목/설명/권한 보존). EditRole 이 전체 동기화라, 현재 상세를 읽어
6167
+ * 나머지 필드를 유지한 채 사용자 목록만 교체해 PUT 한다. userIds 는 "이 역할을 가질 사용자
6168
+ * 전체"다 (추가가 아니라 동기화). (management_scope: `role:manage`)
6169
+ */
6170
+ async assign(appId, roleId, userIds) {
6171
+ this.ensureServerAuth("assign");
6172
+ const detail = await this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6173
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6174
+ role_title: detail.role_name,
6175
+ role_description: detail.role_description,
6176
+ selected_permission_ids: detail.permission_item.map((p) => p.id),
6177
+ selected_user_ids: userIds
6178
+ });
6179
+ }
6180
+ /** 역할을 삭제한다. (management_scope: `role:manage`) */
6181
+ async delete(appId, roleId) {
6182
+ this.ensureServerAuth("delete");
6183
+ await this.http.delete(`/v1/apps/${appId}/app-roles/${roleId}`);
6184
+ }
6185
+ };
6186
+
6067
6187
  // src/api/video.ts
6068
6188
  var DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
6069
6189
  var VideoProcessingError = class extends Error {
@@ -8016,6 +8136,8 @@ var AdsAPI = class {
8016
8136
  // src/api/native.ts
8017
8137
  var NativeAPI = class {
8018
8138
  constructor() {
8139
+ /** 웹 Web Speech API 인스턴스 (stop() 용) */
8140
+ this._webSpeechRecognition = null;
8019
8141
  /**
8020
8142
  * 크로스 플랫폼 클립보드 API
8021
8143
  */
@@ -8442,6 +8564,109 @@ var NativeAPI = class {
8442
8564
  return false;
8443
8565
  }
8444
8566
  };
8567
+ /**
8568
+ * 크로스 플랫폼 음성 인식(STT) API
8569
+ *
8570
+ * - 모바일(패키징 앱): 네이티브 STT 브릿지(expo-speech-recognition — iOS SFSpeechRecognizer / Android SpeechRecognizer)
8571
+ * - 웹/데스크톱: Web Speech API(webkitSpeechRecognition)
8572
+ *
8573
+ * iOS WKWebView 는 Web Speech API 를 지원하지 않으므로, 패키징 앱에서는 speech 네이티브 기능을 켜면
8574
+ * 자동으로 네이티브 브릿지 경로를 사용한다.
8575
+ *
8576
+ * @example
8577
+ * ```typescript
8578
+ * if (await cb.native.speech.isAvailable()) {
8579
+ * const result = await cb.native.speech.recognize({
8580
+ * lang: 'ko-KR',
8581
+ * onPartial: (text) => console.log('부분 결과:', text),
8582
+ * })
8583
+ * console.log('최종:', result.transcript)
8584
+ * }
8585
+ * ```
8586
+ */
8587
+ this.speech = {
8588
+ /**
8589
+ * 음성 인식 지원 여부
8590
+ */
8591
+ isAvailable: async () => {
8592
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8593
+ const r = await window.NativeBridge.speech.isAvailable();
8594
+ return r.available;
8595
+ }
8596
+ if (typeof window === "undefined") return false;
8597
+ return "SpeechRecognition" in window || "webkitSpeechRecognition" in window;
8598
+ },
8599
+ /**
8600
+ * 음성 인식 시작 → 최종 transcript 반환.
8601
+ * `options.onPartial` 로 중간 결과를 실시간 수신할 수 있다.
8602
+ */
8603
+ recognize: async (options) => {
8604
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8605
+ let partialHandler;
8606
+ if (options?.onPartial) {
8607
+ partialHandler = (e) => {
8608
+ const detail = e.detail;
8609
+ if (detail?.transcript) options.onPartial?.(detail.transcript);
8610
+ };
8611
+ window.addEventListener("nativeSpeechPartial", partialHandler);
8612
+ }
8613
+ try {
8614
+ return await window.NativeBridge.speech.recognize(options);
8615
+ } finally {
8616
+ if (partialHandler) window.removeEventListener("nativeSpeechPartial", partialHandler);
8617
+ }
8618
+ }
8619
+ const SR = window;
8620
+ const Ctor = SR.SpeechRecognition || SR.webkitSpeechRecognition;
8621
+ if (!Ctor) {
8622
+ throw new Error("\uC774 \uD50C\uB7AB\uD3FC\uC5D0\uC11C\uB294 \uC74C\uC131 \uC778\uC2DD\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD328\uD0A4\uC9D5 \uC571\uC5D0\uC11C\uB294 speech \uB124\uC774\uD2F0\uBE0C \uAE30\uB2A5\uC744 \uD65C\uC131\uD654\uD558\uC138\uC694.");
8623
+ }
8624
+ return new Promise((resolve, reject) => {
8625
+ const recognition = new Ctor();
8626
+ this._webSpeechRecognition = recognition;
8627
+ recognition.lang = options?.lang ?? "en-US";
8628
+ recognition.interimResults = options?.interim ?? true;
8629
+ recognition.continuous = options?.continuous ?? false;
8630
+ let finalTranscript = "";
8631
+ recognition.onresult = (event) => {
8632
+ let interim = "";
8633
+ for (let i = event.resultIndex; i < event.results.length; i++) {
8634
+ const res = event.results[i];
8635
+ if (res.isFinal) {
8636
+ finalTranscript += res[0].transcript;
8637
+ } else {
8638
+ interim += res[0].transcript;
8639
+ }
8640
+ }
8641
+ if (interim && options?.onPartial) options.onPartial(interim);
8642
+ };
8643
+ recognition.onerror = (event) => {
8644
+ this._webSpeechRecognition = null;
8645
+ reject(new Error(event?.error || "\uC74C\uC131 \uC778\uC2DD \uC624\uB958"));
8646
+ };
8647
+ recognition.onend = () => {
8648
+ this._webSpeechRecognition = null;
8649
+ resolve({ transcript: finalTranscript.trim(), isFinal: true });
8650
+ };
8651
+ recognition.start();
8652
+ });
8653
+ },
8654
+ /**
8655
+ * 진행 중인 음성 인식 중지
8656
+ */
8657
+ stop: async () => {
8658
+ if (this.getPlatform() === "mobile" && window.NativeBridge?.speech) {
8659
+ await window.NativeBridge.speech.stop();
8660
+ return;
8661
+ }
8662
+ if (this._webSpeechRecognition) {
8663
+ try {
8664
+ this._webSpeechRecognition.stop();
8665
+ } catch {
8666
+ }
8667
+ }
8668
+ }
8669
+ };
8445
8670
  }
8446
8671
  /**
8447
8672
  * 현재 플랫폼 감지
@@ -11047,6 +11272,7 @@ var ConnectBase = class {
11047
11272
  this.payment = new PaymentAPI(this.http);
11048
11273
  this.subscription = new SubscriptionAPI(this.http);
11049
11274
  this.push = new PushAPI(this.http);
11275
+ this.roles = new RolesAPI(this.http);
11050
11276
  this.video = new VideoAPI(this.http, config.videoUrl || env("CB_VIDEO_URL") || DEFAULT_VIDEO_URL);
11051
11277
  this.game = new GameAPI(this.http, config.gameUrl || env("CB_GAME_URL") || DEFAULT_GAME_URL, config.appId);
11052
11278
  this.ads = new AdsAPI(this.http);
@@ -11141,6 +11367,7 @@ export {
11141
11367
  GameRoom,
11142
11368
  GameRoomTransport,
11143
11369
  NativeAPI,
11370
+ RolesAPI,
11144
11371
  SessionManager,
11145
11372
  VideoProcessingError,
11146
11373
  index_default as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.45.0",
3
+ "version": "3.47.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",