connectbase-client 6.12.0 → 6.13.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
@@ -12709,32 +12709,41 @@ var SubscriptionAPI = class {
12709
12709
  * 구독 생성
12710
12710
  * 정기결제 구독을 생성합니다.
12711
12711
  *
12712
- * @param data - 구독 정보
12713
- * @returns 생성된 구독 정보
12712
+ * **금액과 요금제는 서버가 정합니다.** 브라우저에서는 콘솔 가격표의 정기 상품 `product_id` 만 보내고, 서버가
12713
+ * 금액, 통화, 주기, 플랜명, 무료 체험, MoR PG 가격 ref 를 상품에서 정합니다. 금액을 직접 보내는 형태(레거시)는
12714
+ * 결제 설정의 금액 출처가 `client` 인 앱이거나 서버(시크릿 키, service_role 함수)에서 부를 때만 받습니다.
12715
+ *
12716
+ * @param data - 가격표 정기 상품(`product_id`) 또는 금액과 요금제, 빌링키, 고객 정보 등
12717
+ * @returns 생성된 구독 정보 (`product_id` 포함)
12714
12718
  *
12715
12719
  * @example
12716
12720
  * ```typescript
12721
+ * // 가격표 구독 (권장) — 금액, 주기, 체험 기간은 콘솔 가격표의 premium_month 가 정한다
12717
12722
  * const subscription = await client.subscription.create({
12718
12723
  * billing_key_id: 'bk_xxxxx',
12719
- * plan_name: '프리미엄 플랜',
12720
- * amount: 9900,
12721
- * billing_cycle: 'monthly',
12722
- * billing_day: 15, // 매월 15일 결제
12723
- * trial_days: 7 // 7일 무료 체험
12724
+ * product_id: 'premium_month',
12724
12725
  * })
12725
12726
  *
12726
- * // MoR(dodo/paddle) 구독 + 할인 코드
12727
+ * // MoR(dodo/paddle) 구독 + 할인 코드 — PG 가격 ref 도 상품의 provider_price_refs 에서 서버가 고른다
12727
12728
  * const promo = await client.subscription.create({
12729
+ * product_id: 'premium_month',
12730
+ * discount_code: 'LAUNCH50', // 첫 결제에 적용 (몇 주기까지인지는 PG 쿠폰 설정이 결정)
12731
+ * })
12732
+ *
12733
+ * // 금액 직접 지정 (레거시 앱, 서버 호출) — 가격표 전용 앱에서 브라우저가 보내면 403 client_amount_not_allowed
12734
+ * const legacy = await client.subscription.create({
12735
+ * billing_key_id: 'bk_xxxxx',
12728
12736
  * plan_name: '프리미엄 플랜',
12729
12737
  * amount: 9900,
12730
12738
  * billing_cycle: 'monthly',
12731
- * provider_price_ref: 'pdt_xxxxx', // PG 카탈로그의 recurring 상품
12732
- * discount_code: 'LAUNCH50', // 첫 결제에 적용 (몇 주기까지인지는 PG 쿠폰 설정이 결정)
12733
- * trial_days: 14, // Dodo 만 지원 — Paddle 은 400 trial_days_unsupported
12734
12739
  * })
12735
12740
  * ```
12736
12741
  *
12737
12742
  * @remarks
12743
+ * 권한은 구독의 `product_id` 로 지급합니다(`metadata` 는 클라이언트 값). 가격표 전용 앱의 브라우저 호출은
12744
+ * `start_now: false` 와 `billing_day` 를 쓸 수 없습니다(403 `client_schedule_not_allowed`) — 둘 다 한 번 낸 돈으로 쓰는
12745
+ * 기간을 늘립니다. 무료 체험은 상품의 `trial_days` 가 정합니다.
12746
+ *
12738
12747
  * 할인 코드(`discount_code`)와 `trial_days` 의 프로바이더 지원 범위가 다릅니다:
12739
12748
  * - **Dodo**: 할인 코드 O, 결제창 쿠폰 입력 토글 O, 구독별 체험 일수 O
12740
12749
  * - **Paddle**: 할인 코드 O(코드→내부 ID 자동 해소), 쿠폰 입력칸은 항상 노출(숨김 불가),
@@ -12806,9 +12815,9 @@ var SubscriptionAPI = class {
12806
12815
  *
12807
12816
  * @example
12808
12817
  * ```typescript
12818
+ * // 서버(시크릿 키, service_role 함수)에서 다른 가격표 상품으로 바꾸기 — 다음 청구부터 새 금액
12809
12819
  * await client.subscription.update(subscriptionId, {
12810
- * plan_name: '엔터프라이즈 플랜',
12811
- * amount: 29900
12820
+ * product_id: 'enterprise_month',
12812
12821
  * })
12813
12822
  *
12814
12823
  * // 다음 결제일을 특정 날짜로 옮기기 (기간권·보상)
@@ -12994,11 +13003,10 @@ var SubscriptionAPI = class {
12994
13003
  *
12995
13004
  * @example
12996
13005
  * ```typescript
13006
+ * // 가격표 정기 상품으로 바꾼다 — 새 PG 가격 ref 와 플랜명은 서버가 상품에서 정한다
12997
13007
  * await client.subscription.changePlan(subscriptionId, {
12998
- * provider_price_ref: 'pri_family_monthly', // Dodo 는 'pdt_*'
12999
- * plan_name: '가족 플랜',
13000
- * amount: 24900,
13001
- * proration_mode: 'prorated_immediately',
13008
+ * product_id: 'family_month',
13009
+ * proration_mode: 'prorated_immediately', // 브라우저는 지금 정산하는 모드만
13002
13010
  * })
13003
13011
  * ```
13004
13012
  */
@@ -13031,7 +13039,7 @@ var SubscriptionAPI = class {
13031
13039
  * @example
13032
13040
  * ```typescript
13033
13041
  * const preview = await client.subscription.previewChangePlan(subscriptionId, {
13034
- * provider_price_ref: 'pri_family_monthly',
13042
+ * product_id: 'family_month',
13035
13043
  * })
13036
13044
  * // 통화를 함께 포맷한다 — 금액만 쓰면 다통화에서 거짓 금액이 된다
13037
13045
  * format(preview.immediate_charge_amount, preview.currency)
@@ -13085,10 +13093,11 @@ var SubscriptionAPI = class {
13085
13093
  *
13086
13094
  * @example
13087
13095
  * ```typescript
13096
+ * // 가격표 단건 상품으로 청구 — 금액과 상품명은 서버가 정한다
13088
13097
  * const result = await client.subscription.chargeWithBillingKey({
13089
13098
  * billing_key_id: 'bk_xxxxx',
13090
- * amount: 15000,
13091
- * order_name: '추가 결제'
13099
+ * product_id: 'gem_100',
13100
+ * quantity: 2,
13092
13101
  * })
13093
13102
  *
13094
13103
  * if (result.status === 'done') {
@@ -13322,8 +13331,8 @@ var SupportAPI = class {
13322
13331
  }
13323
13332
  };
13324
13333
  function detectSdkVersion() {
13325
- if ("6.12.0") {
13326
- return "6.12.0";
13334
+ if ("6.13.0") {
13335
+ return "6.13.0";
13327
13336
  }
13328
13337
  return "unknown";
13329
13338
  }
@@ -14217,7 +14226,14 @@ var WebRTCAPI = class _WebRTCAPI {
14217
14226
  this.currentRoomId = null;
14218
14227
  this.currentPeerId = null;
14219
14228
  this.currentUserId = null;
14220
- this.isBroadcaster = false;
14229
+ /** connect 가 요청한 방송자 역할 (switchRoom 이 이어받는다) */
14230
+ this.broadcasterRequested = false;
14231
+ /** 서버가 정한 방송자 역할 (`joined` 의 is_broadcaster) */
14232
+ this.broadcasterGranted = false;
14233
+ /** 비공개 룸 허용 목록 요청 (이 연결에서만 쓴다) */
14234
+ this.allowedMemberIds = null;
14235
+ this.privateRoom = false;
14236
+ this.warningListeners = [];
14221
14237
  this.localStream = null;
14222
14238
  this.channelType = "interactive";
14223
14239
  // 피어 연결 관리
@@ -14271,7 +14287,10 @@ var WebRTCAPI = class _WebRTCAPI {
14271
14287
  this.setState("connecting");
14272
14288
  this.currentRoomId = options.roomId;
14273
14289
  this.currentUserId = options.userId || null;
14274
- this.isBroadcaster = options.isBroadcaster || false;
14290
+ this.broadcasterRequested = options.isBroadcaster || false;
14291
+ this.broadcasterGranted = false;
14292
+ this.allowedMemberIds = options.allowedMemberIds ? [...options.allowedMemberIds] : null;
14293
+ this.privateRoom = false;
14275
14294
  this.localStream = options.localStream || null;
14276
14295
  if (this.iceServers.length === 0) {
14277
14296
  try {
@@ -14303,13 +14322,16 @@ var WebRTCAPI = class _WebRTCAPI {
14303
14322
  return;
14304
14323
  }
14305
14324
  this.reconnectAttempts = 0;
14325
+ const joinData = {
14326
+ is_broadcaster: this.broadcasterRequested
14327
+ };
14328
+ if (this.allowedMemberIds) {
14329
+ joinData.allowed_member_ids = this.allowedMemberIds;
14330
+ }
14306
14331
  this.sendSignaling({
14307
14332
  type: "join",
14308
14333
  room_id: this.currentRoomId,
14309
- data: {
14310
- user_id: this.currentUserId,
14311
- is_broadcaster: this.isBroadcaster
14312
- }
14334
+ data: joinData
14313
14335
  });
14314
14336
  };
14315
14337
  ws.onmessage = async (event) => {
@@ -14377,6 +14399,8 @@ var WebRTCAPI = class _WebRTCAPI {
14377
14399
  if (data.channel_type) {
14378
14400
  this.channelType = data.channel_type;
14379
14401
  }
14402
+ this.broadcasterGranted = data.is_broadcaster === true;
14403
+ this.privateRoom = data.private === true;
14380
14404
  const peers = data.peers || [];
14381
14405
  for (const peer of peers) {
14382
14406
  if (peer.peer_id !== this.currentPeerId) {
@@ -14417,9 +14441,16 @@ var WebRTCAPI = class _WebRTCAPI {
14417
14441
  await this.handleICECandidate(msg.peer_id, msg.candidate);
14418
14442
  }
14419
14443
  break;
14444
+ case "warning": {
14445
+ const data = msg.data && typeof msg.data === "object" ? msg.data : {};
14446
+ this.emitWarning({
14447
+ code: typeof data.code === "string" ? data.code : "unknown",
14448
+ message: typeof data.message === "string" ? data.message : ""
14449
+ });
14450
+ break;
14451
+ }
14420
14452
  case "error": {
14421
- const errorMsg = typeof msg.data === "string" ? msg.data : "Unknown error";
14422
- const error = new Error(errorMsg);
14453
+ const error = new Error(_WebRTCAPI.signalingErrorMessage(msg.data));
14423
14454
  this.emitError(error);
14424
14455
  reject?.(error);
14425
14456
  break;
@@ -14517,6 +14548,19 @@ var WebRTCAPI = class _WebRTCAPI {
14517
14548
  }
14518
14549
  this.remoteStreams.delete(remotePeerId);
14519
14550
  }
14551
+ /**
14552
+ * 서버 error 메시지의 data 에서 안내 문구를 꺼낸다. 대부분은 문자열이지만 할당량, 동시접속 초과는
14553
+ * `{ error, message, ... }` 객체로 온다.
14554
+ */
14555
+ static signalingErrorMessage(data) {
14556
+ if (typeof data === "string" && data) return data;
14557
+ if (data && typeof data === "object") {
14558
+ const obj = data;
14559
+ if (typeof obj.message === "string" && obj.message) return obj.message;
14560
+ if (typeof obj.error === "string" && obj.error) return obj.error;
14561
+ }
14562
+ return "Unknown error";
14563
+ }
14520
14564
  sendSignaling(msg) {
14521
14565
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
14522
14566
  this.ws.send(JSON.stringify(msg));
@@ -14582,6 +14626,8 @@ var WebRTCAPI = class _WebRTCAPI {
14582
14626
  this.currentRoomId = null;
14583
14627
  this.currentPeerId = null;
14584
14628
  this.localStream = null;
14629
+ this.broadcasterGranted = false;
14630
+ this.privateRoom = false;
14585
14631
  this.setState("disconnected");
14586
14632
  }
14587
14633
  /**
@@ -14599,9 +14645,15 @@ var WebRTCAPI = class _WebRTCAPI {
14599
14645
  async switchRoom(roomId, options = {}) {
14600
14646
  const localStream = options.localStream ?? this.localStream ?? void 0;
14601
14647
  const userId = options.userId ?? this.currentUserId ?? void 0;
14602
- const isBroadcaster = options.isBroadcaster ?? this.isBroadcaster;
14648
+ const isBroadcaster = options.isBroadcaster ?? this.broadcasterRequested;
14603
14649
  this.disconnect();
14604
- await this.connect({ roomId, userId, isBroadcaster, localStream });
14650
+ await this.connect({
14651
+ roomId,
14652
+ userId,
14653
+ isBroadcaster,
14654
+ localStream,
14655
+ allowedMemberIds: options.allowedMemberIds
14656
+ });
14605
14657
  }
14606
14658
  /**
14607
14659
  * 소켓, 룸, 이벤트 리스너, 피어 연결을 **독립적으로** 갖는 새 WebRTC 세션을 만든다.
@@ -14654,6 +14706,19 @@ var WebRTCAPI = class _WebRTCAPI {
14654
14706
  getChannelType() {
14655
14707
  return this.channelType;
14656
14708
  }
14709
+ /**
14710
+ * 서버가 정한 방송자 역할인지 조회한다. 요청(`isBroadcaster: true`)과 다를 수 있다 — 익명 연결이나
14711
+ * 채널의 방송자 역할이 없는 회원은 시청자로 참가한다(`onWarning` 으로 이유가 온다).
14712
+ */
14713
+ isBroadcaster() {
14714
+ return this.broadcasterGranted;
14715
+ }
14716
+ /**
14717
+ * 지금 룸이 회원 허용 목록이 있는 비공개 룸인지 조회한다.
14718
+ */
14719
+ isPrivateRoom() {
14720
+ return this.privateRoom;
14721
+ }
14657
14722
  /**
14658
14723
  * 원격 스트림 조회
14659
14724
  */
@@ -14724,6 +14789,24 @@ var WebRTCAPI = class _WebRTCAPI {
14724
14789
  this.errorListeners = this.errorListeners.filter((cb) => cb !== callback);
14725
14790
  };
14726
14791
  }
14792
+ /**
14793
+ * 서버가 join 요청을 바꿨다는 알림 이벤트 (예: 방송자 요청을 시청자로 낮춤)
14794
+ *
14795
+ * @example
14796
+ * ```typescript
14797
+ * cb.webrtc.onWarning(({ code, message }) => {
14798
+ * if (code === 'broadcaster_role_required') showToast(message)
14799
+ * })
14800
+ * ```
14801
+ */
14802
+ onWarning(callback) {
14803
+ this.warningListeners.push(callback);
14804
+ return () => {
14805
+ this.warningListeners = this.warningListeners.filter(
14806
+ (cb) => cb !== callback
14807
+ );
14808
+ };
14809
+ }
14727
14810
  /**
14728
14811
  * 피어 참가 이벤트
14729
14812
  */
@@ -14769,6 +14852,9 @@ var WebRTCAPI = class _WebRTCAPI {
14769
14852
  emitError(error) {
14770
14853
  this.errorListeners.forEach((cb) => cb(error));
14771
14854
  }
14855
+ emitWarning(warning) {
14856
+ this.warningListeners.forEach((cb) => cb(warning));
14857
+ }
14772
14858
  emitPeerJoined(peerId, info) {
14773
14859
  this.peerJoinedListeners.forEach((cb) => cb(peerId, info));
14774
14860
  }
package/dist/index.mjs CHANGED
@@ -12657,32 +12657,41 @@ var SubscriptionAPI = class {
12657
12657
  * 구독 생성
12658
12658
  * 정기결제 구독을 생성합니다.
12659
12659
  *
12660
- * @param data - 구독 정보
12661
- * @returns 생성된 구독 정보
12660
+ * **금액과 요금제는 서버가 정합니다.** 브라우저에서는 콘솔 가격표의 정기 상품 `product_id` 만 보내고, 서버가
12661
+ * 금액, 통화, 주기, 플랜명, 무료 체험, MoR PG 가격 ref 를 상품에서 정합니다. 금액을 직접 보내는 형태(레거시)는
12662
+ * 결제 설정의 금액 출처가 `client` 인 앱이거나 서버(시크릿 키, service_role 함수)에서 부를 때만 받습니다.
12663
+ *
12664
+ * @param data - 가격표 정기 상품(`product_id`) 또는 금액과 요금제, 빌링키, 고객 정보 등
12665
+ * @returns 생성된 구독 정보 (`product_id` 포함)
12662
12666
  *
12663
12667
  * @example
12664
12668
  * ```typescript
12669
+ * // 가격표 구독 (권장) — 금액, 주기, 체험 기간은 콘솔 가격표의 premium_month 가 정한다
12665
12670
  * const subscription = await client.subscription.create({
12666
12671
  * billing_key_id: 'bk_xxxxx',
12667
- * plan_name: '프리미엄 플랜',
12668
- * amount: 9900,
12669
- * billing_cycle: 'monthly',
12670
- * billing_day: 15, // 매월 15일 결제
12671
- * trial_days: 7 // 7일 무료 체험
12672
+ * product_id: 'premium_month',
12672
12673
  * })
12673
12674
  *
12674
- * // MoR(dodo/paddle) 구독 + 할인 코드
12675
+ * // MoR(dodo/paddle) 구독 + 할인 코드 — PG 가격 ref 도 상품의 provider_price_refs 에서 서버가 고른다
12675
12676
  * const promo = await client.subscription.create({
12677
+ * product_id: 'premium_month',
12678
+ * discount_code: 'LAUNCH50', // 첫 결제에 적용 (몇 주기까지인지는 PG 쿠폰 설정이 결정)
12679
+ * })
12680
+ *
12681
+ * // 금액 직접 지정 (레거시 앱, 서버 호출) — 가격표 전용 앱에서 브라우저가 보내면 403 client_amount_not_allowed
12682
+ * const legacy = await client.subscription.create({
12683
+ * billing_key_id: 'bk_xxxxx',
12676
12684
  * plan_name: '프리미엄 플랜',
12677
12685
  * amount: 9900,
12678
12686
  * billing_cycle: 'monthly',
12679
- * provider_price_ref: 'pdt_xxxxx', // PG 카탈로그의 recurring 상품
12680
- * discount_code: 'LAUNCH50', // 첫 결제에 적용 (몇 주기까지인지는 PG 쿠폰 설정이 결정)
12681
- * trial_days: 14, // Dodo 만 지원 — Paddle 은 400 trial_days_unsupported
12682
12687
  * })
12683
12688
  * ```
12684
12689
  *
12685
12690
  * @remarks
12691
+ * 권한은 구독의 `product_id` 로 지급합니다(`metadata` 는 클라이언트 값). 가격표 전용 앱의 브라우저 호출은
12692
+ * `start_now: false` 와 `billing_day` 를 쓸 수 없습니다(403 `client_schedule_not_allowed`) — 둘 다 한 번 낸 돈으로 쓰는
12693
+ * 기간을 늘립니다. 무료 체험은 상품의 `trial_days` 가 정합니다.
12694
+ *
12686
12695
  * 할인 코드(`discount_code`)와 `trial_days` 의 프로바이더 지원 범위가 다릅니다:
12687
12696
  * - **Dodo**: 할인 코드 O, 결제창 쿠폰 입력 토글 O, 구독별 체험 일수 O
12688
12697
  * - **Paddle**: 할인 코드 O(코드→내부 ID 자동 해소), 쿠폰 입력칸은 항상 노출(숨김 불가),
@@ -12754,9 +12763,9 @@ var SubscriptionAPI = class {
12754
12763
  *
12755
12764
  * @example
12756
12765
  * ```typescript
12766
+ * // 서버(시크릿 키, service_role 함수)에서 다른 가격표 상품으로 바꾸기 — 다음 청구부터 새 금액
12757
12767
  * await client.subscription.update(subscriptionId, {
12758
- * plan_name: '엔터프라이즈 플랜',
12759
- * amount: 29900
12768
+ * product_id: 'enterprise_month',
12760
12769
  * })
12761
12770
  *
12762
12771
  * // 다음 결제일을 특정 날짜로 옮기기 (기간권·보상)
@@ -12942,11 +12951,10 @@ var SubscriptionAPI = class {
12942
12951
  *
12943
12952
  * @example
12944
12953
  * ```typescript
12954
+ * // 가격표 정기 상품으로 바꾼다 — 새 PG 가격 ref 와 플랜명은 서버가 상품에서 정한다
12945
12955
  * await client.subscription.changePlan(subscriptionId, {
12946
- * provider_price_ref: 'pri_family_monthly', // Dodo 는 'pdt_*'
12947
- * plan_name: '가족 플랜',
12948
- * amount: 24900,
12949
- * proration_mode: 'prorated_immediately',
12956
+ * product_id: 'family_month',
12957
+ * proration_mode: 'prorated_immediately', // 브라우저는 지금 정산하는 모드만
12950
12958
  * })
12951
12959
  * ```
12952
12960
  */
@@ -12979,7 +12987,7 @@ var SubscriptionAPI = class {
12979
12987
  * @example
12980
12988
  * ```typescript
12981
12989
  * const preview = await client.subscription.previewChangePlan(subscriptionId, {
12982
- * provider_price_ref: 'pri_family_monthly',
12990
+ * product_id: 'family_month',
12983
12991
  * })
12984
12992
  * // 통화를 함께 포맷한다 — 금액만 쓰면 다통화에서 거짓 금액이 된다
12985
12993
  * format(preview.immediate_charge_amount, preview.currency)
@@ -13033,10 +13041,11 @@ var SubscriptionAPI = class {
13033
13041
  *
13034
13042
  * @example
13035
13043
  * ```typescript
13044
+ * // 가격표 단건 상품으로 청구 — 금액과 상품명은 서버가 정한다
13036
13045
  * const result = await client.subscription.chargeWithBillingKey({
13037
13046
  * billing_key_id: 'bk_xxxxx',
13038
- * amount: 15000,
13039
- * order_name: '추가 결제'
13047
+ * product_id: 'gem_100',
13048
+ * quantity: 2,
13040
13049
  * })
13041
13050
  *
13042
13051
  * if (result.status === 'done') {
@@ -13270,8 +13279,8 @@ var SupportAPI = class {
13270
13279
  }
13271
13280
  };
13272
13281
  function detectSdkVersion() {
13273
- if ("6.12.0") {
13274
- return "6.12.0";
13282
+ if ("6.13.0") {
13283
+ return "6.13.0";
13275
13284
  }
13276
13285
  return "unknown";
13277
13286
  }
@@ -14165,7 +14174,14 @@ var WebRTCAPI = class _WebRTCAPI {
14165
14174
  this.currentRoomId = null;
14166
14175
  this.currentPeerId = null;
14167
14176
  this.currentUserId = null;
14168
- this.isBroadcaster = false;
14177
+ /** connect 가 요청한 방송자 역할 (switchRoom 이 이어받는다) */
14178
+ this.broadcasterRequested = false;
14179
+ /** 서버가 정한 방송자 역할 (`joined` 의 is_broadcaster) */
14180
+ this.broadcasterGranted = false;
14181
+ /** 비공개 룸 허용 목록 요청 (이 연결에서만 쓴다) */
14182
+ this.allowedMemberIds = null;
14183
+ this.privateRoom = false;
14184
+ this.warningListeners = [];
14169
14185
  this.localStream = null;
14170
14186
  this.channelType = "interactive";
14171
14187
  // 피어 연결 관리
@@ -14219,7 +14235,10 @@ var WebRTCAPI = class _WebRTCAPI {
14219
14235
  this.setState("connecting");
14220
14236
  this.currentRoomId = options.roomId;
14221
14237
  this.currentUserId = options.userId || null;
14222
- this.isBroadcaster = options.isBroadcaster || false;
14238
+ this.broadcasterRequested = options.isBroadcaster || false;
14239
+ this.broadcasterGranted = false;
14240
+ this.allowedMemberIds = options.allowedMemberIds ? [...options.allowedMemberIds] : null;
14241
+ this.privateRoom = false;
14223
14242
  this.localStream = options.localStream || null;
14224
14243
  if (this.iceServers.length === 0) {
14225
14244
  try {
@@ -14251,13 +14270,16 @@ var WebRTCAPI = class _WebRTCAPI {
14251
14270
  return;
14252
14271
  }
14253
14272
  this.reconnectAttempts = 0;
14273
+ const joinData = {
14274
+ is_broadcaster: this.broadcasterRequested
14275
+ };
14276
+ if (this.allowedMemberIds) {
14277
+ joinData.allowed_member_ids = this.allowedMemberIds;
14278
+ }
14254
14279
  this.sendSignaling({
14255
14280
  type: "join",
14256
14281
  room_id: this.currentRoomId,
14257
- data: {
14258
- user_id: this.currentUserId,
14259
- is_broadcaster: this.isBroadcaster
14260
- }
14282
+ data: joinData
14261
14283
  });
14262
14284
  };
14263
14285
  ws.onmessage = async (event) => {
@@ -14325,6 +14347,8 @@ var WebRTCAPI = class _WebRTCAPI {
14325
14347
  if (data.channel_type) {
14326
14348
  this.channelType = data.channel_type;
14327
14349
  }
14350
+ this.broadcasterGranted = data.is_broadcaster === true;
14351
+ this.privateRoom = data.private === true;
14328
14352
  const peers = data.peers || [];
14329
14353
  for (const peer of peers) {
14330
14354
  if (peer.peer_id !== this.currentPeerId) {
@@ -14365,9 +14389,16 @@ var WebRTCAPI = class _WebRTCAPI {
14365
14389
  await this.handleICECandidate(msg.peer_id, msg.candidate);
14366
14390
  }
14367
14391
  break;
14392
+ case "warning": {
14393
+ const data = msg.data && typeof msg.data === "object" ? msg.data : {};
14394
+ this.emitWarning({
14395
+ code: typeof data.code === "string" ? data.code : "unknown",
14396
+ message: typeof data.message === "string" ? data.message : ""
14397
+ });
14398
+ break;
14399
+ }
14368
14400
  case "error": {
14369
- const errorMsg = typeof msg.data === "string" ? msg.data : "Unknown error";
14370
- const error = new Error(errorMsg);
14401
+ const error = new Error(_WebRTCAPI.signalingErrorMessage(msg.data));
14371
14402
  this.emitError(error);
14372
14403
  reject?.(error);
14373
14404
  break;
@@ -14465,6 +14496,19 @@ var WebRTCAPI = class _WebRTCAPI {
14465
14496
  }
14466
14497
  this.remoteStreams.delete(remotePeerId);
14467
14498
  }
14499
+ /**
14500
+ * 서버 error 메시지의 data 에서 안내 문구를 꺼낸다. 대부분은 문자열이지만 할당량, 동시접속 초과는
14501
+ * `{ error, message, ... }` 객체로 온다.
14502
+ */
14503
+ static signalingErrorMessage(data) {
14504
+ if (typeof data === "string" && data) return data;
14505
+ if (data && typeof data === "object") {
14506
+ const obj = data;
14507
+ if (typeof obj.message === "string" && obj.message) return obj.message;
14508
+ if (typeof obj.error === "string" && obj.error) return obj.error;
14509
+ }
14510
+ return "Unknown error";
14511
+ }
14468
14512
  sendSignaling(msg) {
14469
14513
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
14470
14514
  this.ws.send(JSON.stringify(msg));
@@ -14530,6 +14574,8 @@ var WebRTCAPI = class _WebRTCAPI {
14530
14574
  this.currentRoomId = null;
14531
14575
  this.currentPeerId = null;
14532
14576
  this.localStream = null;
14577
+ this.broadcasterGranted = false;
14578
+ this.privateRoom = false;
14533
14579
  this.setState("disconnected");
14534
14580
  }
14535
14581
  /**
@@ -14547,9 +14593,15 @@ var WebRTCAPI = class _WebRTCAPI {
14547
14593
  async switchRoom(roomId, options = {}) {
14548
14594
  const localStream = options.localStream ?? this.localStream ?? void 0;
14549
14595
  const userId = options.userId ?? this.currentUserId ?? void 0;
14550
- const isBroadcaster = options.isBroadcaster ?? this.isBroadcaster;
14596
+ const isBroadcaster = options.isBroadcaster ?? this.broadcasterRequested;
14551
14597
  this.disconnect();
14552
- await this.connect({ roomId, userId, isBroadcaster, localStream });
14598
+ await this.connect({
14599
+ roomId,
14600
+ userId,
14601
+ isBroadcaster,
14602
+ localStream,
14603
+ allowedMemberIds: options.allowedMemberIds
14604
+ });
14553
14605
  }
14554
14606
  /**
14555
14607
  * 소켓, 룸, 이벤트 리스너, 피어 연결을 **독립적으로** 갖는 새 WebRTC 세션을 만든다.
@@ -14602,6 +14654,19 @@ var WebRTCAPI = class _WebRTCAPI {
14602
14654
  getChannelType() {
14603
14655
  return this.channelType;
14604
14656
  }
14657
+ /**
14658
+ * 서버가 정한 방송자 역할인지 조회한다. 요청(`isBroadcaster: true`)과 다를 수 있다 — 익명 연결이나
14659
+ * 채널의 방송자 역할이 없는 회원은 시청자로 참가한다(`onWarning` 으로 이유가 온다).
14660
+ */
14661
+ isBroadcaster() {
14662
+ return this.broadcasterGranted;
14663
+ }
14664
+ /**
14665
+ * 지금 룸이 회원 허용 목록이 있는 비공개 룸인지 조회한다.
14666
+ */
14667
+ isPrivateRoom() {
14668
+ return this.privateRoom;
14669
+ }
14605
14670
  /**
14606
14671
  * 원격 스트림 조회
14607
14672
  */
@@ -14672,6 +14737,24 @@ var WebRTCAPI = class _WebRTCAPI {
14672
14737
  this.errorListeners = this.errorListeners.filter((cb) => cb !== callback);
14673
14738
  };
14674
14739
  }
14740
+ /**
14741
+ * 서버가 join 요청을 바꿨다는 알림 이벤트 (예: 방송자 요청을 시청자로 낮춤)
14742
+ *
14743
+ * @example
14744
+ * ```typescript
14745
+ * cb.webrtc.onWarning(({ code, message }) => {
14746
+ * if (code === 'broadcaster_role_required') showToast(message)
14747
+ * })
14748
+ * ```
14749
+ */
14750
+ onWarning(callback) {
14751
+ this.warningListeners.push(callback);
14752
+ return () => {
14753
+ this.warningListeners = this.warningListeners.filter(
14754
+ (cb) => cb !== callback
14755
+ );
14756
+ };
14757
+ }
14675
14758
  /**
14676
14759
  * 피어 참가 이벤트
14677
14760
  */
@@ -14717,6 +14800,9 @@ var WebRTCAPI = class _WebRTCAPI {
14717
14800
  emitError(error) {
14718
14801
  this.errorListeners.forEach((cb) => cb(error));
14719
14802
  }
14803
+ emitWarning(warning) {
14804
+ this.warningListeners.forEach((cb) => cb(warning));
14805
+ }
14720
14806
  emitPeerJoined(peerId, info) {
14721
14807
  this.peerJoinedListeners.forEach((cb) => cb(peerId, info));
14722
14808
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "6.12.0",
3
+ "version": "6.13.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",