connectbase-client 3.35.2 → 3.37.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
@@ -5517,11 +5517,23 @@ var PaymentAPI = class {
5517
5517
  * // Mount PaymentElement, then:
5518
5518
  * // stripe.confirmPayment({ elements, redirect: 'if_required' })
5519
5519
  * }
5520
+ *
5521
+ * // PayApp 결제 플로우 (위젯 불필요 — 호스팅 결제창으로 redirect)
5522
+ * // 결제 결과는 서버의 feedbackurl webhook 으로 확정되며, 사용자는 success_url 로 복귀한다.
5523
+ * if (result.payment_provider === 'payapp' && result.payapp_pay_url) {
5524
+ * window.location.href = result.payapp_pay_url
5525
+ * }
5520
5526
  * ```
5527
+ *
5528
+ * @remarks
5529
+ * PayApp 은 `customer_phone` 이 필수입니다(결제요청 수신 번호). 미지정 시 서버가 거부합니다.
5521
5530
  */
5522
5531
  async prepare(data) {
5523
5532
  const prefix = this.getPublicPrefix();
5524
- return this.http.post(`${prefix}/payments/prepare`, data);
5533
+ return this.http.post(
5534
+ `${prefix}/payments/prepare`,
5535
+ data
5536
+ );
5525
5537
  }
5526
5538
  /**
5527
5539
  * Stripe-hosted 결제 페이지 세션 생성 (client_secret 미노출 플로우).
@@ -5585,7 +5597,10 @@ var PaymentAPI = class {
5585
5597
  */
5586
5598
  async confirm(data) {
5587
5599
  const prefix = this.getPublicPrefix();
5588
- return this.http.post(`${prefix}/payments/confirm`, data);
5600
+ return this.http.post(
5601
+ `${prefix}/payments/confirm`,
5602
+ data
5603
+ );
5589
5604
  }
5590
5605
  /**
5591
5606
  * 결제 취소
@@ -6139,20 +6154,22 @@ var PushAPI = class {
6139
6154
  }
6140
6155
  // ============ Server-side Send (Functions / cb_sk_ 전용) ============
6141
6156
  /**
6142
- * 회원 ID 목록으로 푸시 발송 — Functions / cb_sk_ 인증 환경 전용.
6157
+ * 회원 ID 목록으로 푸시 발송 — Functions / 서버사이드 인증 환경 전용.
6143
6158
  *
6144
- * 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT 또는 User Secret Key
6145
- * (`cb_sk_`) 받는다. 브라우저 SDK 의 Public Key(`cb_pk_`) 인스턴스에서는 호출
6146
- * 자체가 차단되며, 권한 누설을 막기 위해 명확한 에러를 던진다.
6159
+ * 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT, User Secret Key(`cb_sk_`),
6160
+ * 또는 service_role 토큰(`ctx.cbAdmin`) 받는다. 브라우저 SDK 의 Public Key(`cb_pk_`)
6161
+ * **단독** 인스턴스에서는 호출 자체가 차단되며(권한 누설 방지), 명확한 에러를 던진다.
6162
+ * 위임 Bearer 토큰을 가진 인스턴스(`ctx.cbAdmin`/`ctx.cb`)는 cb_pk_ 가 앱 식별용으로만
6163
+ * 실리므로 허용된다.
6147
6164
  *
6148
- * @param appId 발송 대상 앱 ID (cb_sk_ 환경은 user 단위 권한이므로 명시 전달).
6165
+ * @param appId 발송 대상 앱 ID.
6149
6166
  * @param memberIds 받는 회원 ID 배열 (UUID).
6150
6167
  * @param payload 푸시 내용/옵션.
6151
6168
  *
6152
6169
  * @example
6153
6170
  * ```ts
6154
- * // ConnectBase Function (Node.js, secrets.CB_SECRET_KEY 주입)
6155
- * const result = await cb.push.sendToMembers(APP_ID, [memberId], {
6171
+ * // ConnectBase Function (service_role=true) — 시크릿 키 없이 ctx.cbAdmin 으로 발송
6172
+ * const result = await ctx.cbAdmin.push.sendToMembers(ctx.appId, [memberId], {
6156
6173
  * title: '새 알림',
6157
6174
  * body: '확인해보세요',
6158
6175
  * data: { route: '/notifications' },
@@ -6161,9 +6178,9 @@ var PushAPI = class {
6161
6178
  * ```
6162
6179
  */
6163
6180
  async sendToMembers(appId, memberIds, payload) {
6164
- if (this.http.hasPublicKey()) {
6181
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6165
6182
  throw new Error(
6166
- "cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_) \uB610\uB294 \uCF58\uC194 JWT \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) 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."
6183
+ "cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_), \uCF58\uC194 JWT, \uB610\uB294 service_role(ctx.cbAdmin) \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."
6167
6184
  );
6168
6185
  }
6169
6186
  if (memberIds.length === 0) {
@@ -6615,8 +6632,9 @@ var VideoAPI = class {
6615
6632
  *
6616
6633
  * @example
6617
6634
  * ```ts
6618
- * const { url, expires_at } = await cb.video.getStreamUrl(videoId, '720p')
6619
- * videoElement.src = url
6635
+ * const { master_playlist, qualities, expires_at } = await cb.video.getStreamUrl(videoId)
6636
+ * videoElement.src = master_playlist // 적응형 비트레이트(HLS.js 등으로 재생)
6637
+ * // 특정 화질 고정이 필요하면 qualities['720p'] 사용
6620
6638
  * ```
6621
6639
  */
6622
6640
  async getStreamUrl(videoId, quality) {
@@ -6702,31 +6720,37 @@ var VideoAPI = class {
6702
6720
  }
6703
6721
  // ========== Playlist Operations ==========
6704
6722
  /**
6705
- * Create a playlist
6723
+ * Create a playlist for a channel.
6724
+ * 백엔드는 `POST /v1/public/playlists` 에 body 로 `channel_id` 를 받는다 (AppMember 인증).
6706
6725
  */
6707
6726
  async createPlaylist(channelId, data) {
6708
6727
  const prefix = this.getPublicPrefix();
6709
- return this.videoFetch("POST", `${prefix}/channels/${channelId}/playlists`, data);
6728
+ return this.videoFetch("POST", `${prefix}/playlists`, {
6729
+ ...data,
6730
+ channel_id: channelId
6731
+ });
6710
6732
  }
6711
6733
  /**
6712
- * Get playlists for a channel
6734
+ * Get a channel's public playlists.
6735
+ * 백엔드는 `GET /v1/public/playlists/public?channel_id=` 로 공개 플레이리스트를 반환한다.
6713
6736
  */
6714
6737
  async getPlaylists(channelId) {
6715
6738
  const prefix = this.getPublicPrefix();
6716
6739
  const response = await this.videoFetch(
6717
6740
  "GET",
6718
- `${prefix}/channels/${channelId}/playlists`
6741
+ `${prefix}/playlists/public?channel_id=${encodeURIComponent(channelId)}`
6719
6742
  );
6720
6743
  return response.playlists;
6721
6744
  }
6722
6745
  /**
6723
- * Get playlist items
6746
+ * Get playlist items.
6747
+ * 백엔드는 별도 items 라우트가 없고 `GET /v1/public/playlists/:id` 응답에 items 를 포함한다.
6724
6748
  */
6725
6749
  async getPlaylistItems(playlistId) {
6726
6750
  const prefix = this.getPublicPrefix();
6727
6751
  const response = await this.videoFetch(
6728
6752
  "GET",
6729
- `${prefix}/playlists/${playlistId}/items`
6753
+ `${prefix}/playlists/${playlistId}`
6730
6754
  );
6731
6755
  return response.items;
6732
6756
  }
@@ -6795,7 +6819,8 @@ var VideoAPI = class {
6795
6819
  */
6796
6820
  async postComment(videoId, content, parentId) {
6797
6821
  const prefix = this.getPublicPrefix();
6798
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/comments`, {
6822
+ return this.videoFetch("POST", `${prefix}/comments`, {
6823
+ video_id: videoId,
6799
6824
  content,
6800
6825
  parent_id: parentId
6801
6826
  });
@@ -6862,42 +6887,55 @@ var VideoAPI = class {
6862
6887
  const prefix = this.getPublicPrefix();
6863
6888
  const response = await this.videoFetch(
6864
6889
  "GET",
6865
- `${prefix}/channels/${channelId}/memberships/tiers`
6890
+ `${prefix}/channels/${channelId}/membership/tiers`
6866
6891
  );
6867
6892
  return response.tiers;
6868
6893
  }
6869
6894
  /**
6870
6895
  * Join a membership tier
6896
+ *
6897
+ * @param _channelId 현재 서버에서 무시됩니다 (tier 가 채널을 함의). API 안정성을 위해 시그니처만 유지.
6898
+ * @param tierId 가입할 멤버십 티어 ID
6871
6899
  */
6872
- async joinMembership(channelId, tierId) {
6900
+ async joinMembership(_channelId, tierId) {
6873
6901
  const prefix = this.getPublicPrefix();
6874
- return this.videoFetch(
6875
- "POST",
6876
- `${prefix}/channels/${channelId}/memberships/${tierId}/join`,
6877
- {}
6878
- );
6902
+ return this.videoFetch("POST", `${prefix}/membership/join`, {
6903
+ tier_id: tierId
6904
+ });
6879
6905
  }
6880
6906
  /**
6881
6907
  * Cancel membership
6908
+ *
6909
+ * @param _channelId 현재 서버에서 무시됩니다. API 안정성을 위해 시그니처만 유지.
6910
+ * @param membershipId 취소할 멤버십 ID
6882
6911
  */
6883
- async cancelMembership(channelId, membershipId) {
6912
+ async cancelMembership(_channelId, membershipId) {
6884
6913
  const prefix = this.getPublicPrefix();
6885
- await this.videoFetch(
6886
- "POST",
6887
- `${prefix}/channels/${channelId}/memberships/${membershipId}/cancel`,
6888
- {}
6889
- );
6914
+ await this.videoFetch("POST", `${prefix}/membership/${membershipId}/cancel`, {});
6890
6915
  }
6891
6916
  // ========== Super Chat Operations ==========
6892
6917
  /**
6893
- * Send a super chat
6918
+ * Send a super chat to a channel.
6919
+ *
6920
+ * 백엔드는 `POST /v1/public/super-chats` 에 `channel_id`/`type`/`amount` 를 요구하고,
6921
+ * `video_id` 또는 `live_id` 중 하나로 대상을 지정한다 (AppMember 인증 필수).
6922
+ * 응답의 `client_secret` 으로 결제(Stripe)를 확정해야 슈퍼챗이 노출된다.
6923
+ *
6924
+ * @param channelId 슈퍼챗을 받을 채널 ID
6925
+ * @param amount 금액(최소 단위 정수)
6926
+ * @param options videoId 또는 liveId 중 하나로 대상 지정 + 종류/메시지/통화
6894
6927
  */
6895
- async sendSuperChat(videoId, amount, message, currency) {
6928
+ async sendSuperChat(channelId, amount, options) {
6896
6929
  const prefix = this.getPublicPrefix();
6897
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/super-chats`, {
6930
+ return this.videoFetch("POST", `${prefix}/super-chats`, {
6931
+ channel_id: channelId,
6932
+ type: options?.type ?? "super_thanks",
6898
6933
  amount,
6899
- message,
6900
- currency: currency || "USD"
6934
+ video_id: options?.videoId,
6935
+ live_id: options?.liveId,
6936
+ currency: options?.currency ?? "USD",
6937
+ message: options?.message,
6938
+ sticker_id: options?.stickerId
6901
6939
  });
6902
6940
  }
6903
6941
  /**
@@ -6914,13 +6952,15 @@ var VideoAPI = class {
6914
6952
  // ========== Recommendation Operations ==========
6915
6953
  /**
6916
6954
  * Get recommended videos
6955
+ *
6956
+ * @deprecated Use getHomeFeed() — 동일한 개인화 피드(`/recommendations/feed`)를 반환합니다.
6917
6957
  */
6918
6958
  async getRecommendations(limit) {
6919
6959
  const prefix = this.getPublicPrefix();
6920
6960
  const params = limit ? `?limit=${limit}` : "";
6921
6961
  const response = await this.videoFetch(
6922
6962
  "GET",
6923
- `${prefix}/recommendations${params}`
6963
+ `${prefix}/recommendations/feed${params}`
6924
6964
  );
6925
6965
  return response.videos;
6926
6966
  }
@@ -6932,7 +6972,7 @@ var VideoAPI = class {
6932
6972
  const params = limit ? `?limit=${limit}` : "";
6933
6973
  const response = await this.videoFetch(
6934
6974
  "GET",
6935
- `${prefix}/recommendations/home${params}`
6975
+ `${prefix}/recommendations/feed${params}`
6936
6976
  );
6937
6977
  return response.videos;
6938
6978
  }
@@ -6944,7 +6984,7 @@ var VideoAPI = class {
6944
6984
  const params = limit ? `?limit=${limit}` : "";
6945
6985
  const response = await this.videoFetch(
6946
6986
  "GET",
6947
- `${prefix}/recommendations/related/${videoId}${params}`
6987
+ `${prefix}/videos/${videoId}/related${params}`
6948
6988
  );
6949
6989
  return response.videos;
6950
6990
  }
@@ -6965,9 +7005,17 @@ var VideoAPI = class {
6965
7005
  */
6966
7006
  async submitFeedback(videoId, feedback) {
6967
7007
  const prefix = this.getPublicPrefix();
6968
- await this.videoFetch("POST", `${prefix}/recommendations/feedback`, {
7008
+ if (feedback === "not_interested") {
7009
+ await this.videoFetch(
7010
+ "POST",
7011
+ `${prefix}/recommendations/not-interested/${videoId}`,
7012
+ {}
7013
+ );
7014
+ return;
7015
+ }
7016
+ await this.videoFetch("POST", `${prefix}/recommendations/interactions`, {
6969
7017
  video_id: videoId,
6970
- feedback
7018
+ type: "like"
6971
7019
  });
6972
7020
  }
6973
7021
  };
@@ -7009,7 +7057,7 @@ var GameConfigAPI = class {
7009
7057
  /**
7010
7058
  * 단일 기능 활성화 — set() 의 편의 wrapper.
7011
7059
  *
7012
- * @example await cb.game.config.enable(appId, "matchqueue")
7060
+ * @example await cb.game.config.enable(appId, "matchqueue_enabled")
7013
7061
  */
7014
7062
  async enable(appId, feature) {
7015
7063
  return this.set(appId, { [feature]: true });
@@ -7667,7 +7715,7 @@ var GameAPI = class {
7667
7715
  const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectators`, {
7668
7716
  method: "POST",
7669
7717
  headers: { ...this.getHeaders(), "Content-Type": "application/json" },
7670
- body: JSON.stringify({ player_id: playerId })
7718
+ body: JSON.stringify({ user_id: playerId })
7671
7719
  });
7672
7720
  if (!response.ok) throw new Error(`Failed to join spectator: ${response.statusText}`);
7673
7721
  return response.json();
@@ -10993,11 +11041,11 @@ var GameRoomTransport = class {
10993
11041
  });
10994
11042
  }
10995
11043
  // Private methods
10996
- send(type, data) {
11044
+ send(type, data, msgId) {
10997
11045
  if (!this.transport?.isConnected()) {
10998
11046
  throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
10999
11047
  }
11000
- const message = JSON.stringify({ type, data });
11048
+ const message = JSON.stringify({ type, data, msg_id: msgId });
11001
11049
  this.transport.send(message, true);
11002
11050
  }
11003
11051
  sendWithHandler(type, data, handler) {
@@ -11006,14 +11054,14 @@ var GameRoomTransport = class {
11006
11054
  setTimeout(() => {
11007
11055
  this.pendingHandlers.delete(id);
11008
11056
  }, 1e4);
11009
- this.send(type, { ...data, _msg_id: id });
11057
+ this.send(type, data, id);
11010
11058
  }
11011
11059
  handleMessage(data) {
11012
11060
  try {
11013
11061
  const msg = JSON.parse(data);
11014
- if (msg._msg_id && this.pendingHandlers.has(msg._msg_id)) {
11015
- const handler = this.pendingHandlers.get(msg._msg_id);
11016
- this.pendingHandlers.delete(msg._msg_id);
11062
+ if (msg.msg_id && this.pendingHandlers.has(msg.msg_id)) {
11063
+ const handler = this.pendingHandlers.get(msg.msg_id);
11064
+ this.pendingHandlers.delete(msg.msg_id);
11017
11065
  handler(msg);
11018
11066
  return;
11019
11067
  }
package/dist/index.mjs CHANGED
@@ -5471,11 +5471,23 @@ var PaymentAPI = class {
5471
5471
  * // Mount PaymentElement, then:
5472
5472
  * // stripe.confirmPayment({ elements, redirect: 'if_required' })
5473
5473
  * }
5474
+ *
5475
+ * // PayApp 결제 플로우 (위젯 불필요 — 호스팅 결제창으로 redirect)
5476
+ * // 결제 결과는 서버의 feedbackurl webhook 으로 확정되며, 사용자는 success_url 로 복귀한다.
5477
+ * if (result.payment_provider === 'payapp' && result.payapp_pay_url) {
5478
+ * window.location.href = result.payapp_pay_url
5479
+ * }
5474
5480
  * ```
5481
+ *
5482
+ * @remarks
5483
+ * PayApp 은 `customer_phone` 이 필수입니다(결제요청 수신 번호). 미지정 시 서버가 거부합니다.
5475
5484
  */
5476
5485
  async prepare(data) {
5477
5486
  const prefix = this.getPublicPrefix();
5478
- return this.http.post(`${prefix}/payments/prepare`, data);
5487
+ return this.http.post(
5488
+ `${prefix}/payments/prepare`,
5489
+ data
5490
+ );
5479
5491
  }
5480
5492
  /**
5481
5493
  * Stripe-hosted 결제 페이지 세션 생성 (client_secret 미노출 플로우).
@@ -5539,7 +5551,10 @@ var PaymentAPI = class {
5539
5551
  */
5540
5552
  async confirm(data) {
5541
5553
  const prefix = this.getPublicPrefix();
5542
- return this.http.post(`${prefix}/payments/confirm`, data);
5554
+ return this.http.post(
5555
+ `${prefix}/payments/confirm`,
5556
+ data
5557
+ );
5543
5558
  }
5544
5559
  /**
5545
5560
  * 결제 취소
@@ -6093,20 +6108,22 @@ var PushAPI = class {
6093
6108
  }
6094
6109
  // ============ Server-side Send (Functions / cb_sk_ 전용) ============
6095
6110
  /**
6096
- * 회원 ID 목록으로 푸시 발송 — Functions / cb_sk_ 인증 환경 전용.
6111
+ * 회원 ID 목록으로 푸시 발송 — Functions / 서버사이드 인증 환경 전용.
6097
6112
  *
6098
- * 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT 또는 User Secret Key
6099
- * (`cb_sk_`) 받는다. 브라우저 SDK 의 Public Key(`cb_pk_`) 인스턴스에서는 호출
6100
- * 자체가 차단되며, 권한 누설을 막기 위해 명확한 에러를 던진다.
6113
+ * 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT, User Secret Key(`cb_sk_`),
6114
+ * 또는 service_role 토큰(`ctx.cbAdmin`) 받는다. 브라우저 SDK 의 Public Key(`cb_pk_`)
6115
+ * **단독** 인스턴스에서는 호출 자체가 차단되며(권한 누설 방지), 명확한 에러를 던진다.
6116
+ * 위임 Bearer 토큰을 가진 인스턴스(`ctx.cbAdmin`/`ctx.cb`)는 cb_pk_ 가 앱 식별용으로만
6117
+ * 실리므로 허용된다.
6101
6118
  *
6102
- * @param appId 발송 대상 앱 ID (cb_sk_ 환경은 user 단위 권한이므로 명시 전달).
6119
+ * @param appId 발송 대상 앱 ID.
6103
6120
  * @param memberIds 받는 회원 ID 배열 (UUID).
6104
6121
  * @param payload 푸시 내용/옵션.
6105
6122
  *
6106
6123
  * @example
6107
6124
  * ```ts
6108
- * // ConnectBase Function (Node.js, secrets.CB_SECRET_KEY 주입)
6109
- * const result = await cb.push.sendToMembers(APP_ID, [memberId], {
6125
+ * // ConnectBase Function (service_role=true) — 시크릿 키 없이 ctx.cbAdmin 으로 발송
6126
+ * const result = await ctx.cbAdmin.push.sendToMembers(ctx.appId, [memberId], {
6110
6127
  * title: '새 알림',
6111
6128
  * body: '확인해보세요',
6112
6129
  * data: { route: '/notifications' },
@@ -6115,9 +6132,9 @@ var PushAPI = class {
6115
6132
  * ```
6116
6133
  */
6117
6134
  async sendToMembers(appId, memberIds, payload) {
6118
- if (this.http.hasPublicKey()) {
6135
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6119
6136
  throw new Error(
6120
- "cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_) \uB610\uB294 \uCF58\uC194 JWT \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) 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."
6137
+ "cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_), \uCF58\uC194 JWT, \uB610\uB294 service_role(ctx.cbAdmin) \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."
6121
6138
  );
6122
6139
  }
6123
6140
  if (memberIds.length === 0) {
@@ -6569,8 +6586,9 @@ var VideoAPI = class {
6569
6586
  *
6570
6587
  * @example
6571
6588
  * ```ts
6572
- * const { url, expires_at } = await cb.video.getStreamUrl(videoId, '720p')
6573
- * videoElement.src = url
6589
+ * const { master_playlist, qualities, expires_at } = await cb.video.getStreamUrl(videoId)
6590
+ * videoElement.src = master_playlist // 적응형 비트레이트(HLS.js 등으로 재생)
6591
+ * // 특정 화질 고정이 필요하면 qualities['720p'] 사용
6574
6592
  * ```
6575
6593
  */
6576
6594
  async getStreamUrl(videoId, quality) {
@@ -6656,31 +6674,37 @@ var VideoAPI = class {
6656
6674
  }
6657
6675
  // ========== Playlist Operations ==========
6658
6676
  /**
6659
- * Create a playlist
6677
+ * Create a playlist for a channel.
6678
+ * 백엔드는 `POST /v1/public/playlists` 에 body 로 `channel_id` 를 받는다 (AppMember 인증).
6660
6679
  */
6661
6680
  async createPlaylist(channelId, data) {
6662
6681
  const prefix = this.getPublicPrefix();
6663
- return this.videoFetch("POST", `${prefix}/channels/${channelId}/playlists`, data);
6682
+ return this.videoFetch("POST", `${prefix}/playlists`, {
6683
+ ...data,
6684
+ channel_id: channelId
6685
+ });
6664
6686
  }
6665
6687
  /**
6666
- * Get playlists for a channel
6688
+ * Get a channel's public playlists.
6689
+ * 백엔드는 `GET /v1/public/playlists/public?channel_id=` 로 공개 플레이리스트를 반환한다.
6667
6690
  */
6668
6691
  async getPlaylists(channelId) {
6669
6692
  const prefix = this.getPublicPrefix();
6670
6693
  const response = await this.videoFetch(
6671
6694
  "GET",
6672
- `${prefix}/channels/${channelId}/playlists`
6695
+ `${prefix}/playlists/public?channel_id=${encodeURIComponent(channelId)}`
6673
6696
  );
6674
6697
  return response.playlists;
6675
6698
  }
6676
6699
  /**
6677
- * Get playlist items
6700
+ * Get playlist items.
6701
+ * 백엔드는 별도 items 라우트가 없고 `GET /v1/public/playlists/:id` 응답에 items 를 포함한다.
6678
6702
  */
6679
6703
  async getPlaylistItems(playlistId) {
6680
6704
  const prefix = this.getPublicPrefix();
6681
6705
  const response = await this.videoFetch(
6682
6706
  "GET",
6683
- `${prefix}/playlists/${playlistId}/items`
6707
+ `${prefix}/playlists/${playlistId}`
6684
6708
  );
6685
6709
  return response.items;
6686
6710
  }
@@ -6749,7 +6773,8 @@ var VideoAPI = class {
6749
6773
  */
6750
6774
  async postComment(videoId, content, parentId) {
6751
6775
  const prefix = this.getPublicPrefix();
6752
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/comments`, {
6776
+ return this.videoFetch("POST", `${prefix}/comments`, {
6777
+ video_id: videoId,
6753
6778
  content,
6754
6779
  parent_id: parentId
6755
6780
  });
@@ -6816,42 +6841,55 @@ var VideoAPI = class {
6816
6841
  const prefix = this.getPublicPrefix();
6817
6842
  const response = await this.videoFetch(
6818
6843
  "GET",
6819
- `${prefix}/channels/${channelId}/memberships/tiers`
6844
+ `${prefix}/channels/${channelId}/membership/tiers`
6820
6845
  );
6821
6846
  return response.tiers;
6822
6847
  }
6823
6848
  /**
6824
6849
  * Join a membership tier
6850
+ *
6851
+ * @param _channelId 현재 서버에서 무시됩니다 (tier 가 채널을 함의). API 안정성을 위해 시그니처만 유지.
6852
+ * @param tierId 가입할 멤버십 티어 ID
6825
6853
  */
6826
- async joinMembership(channelId, tierId) {
6854
+ async joinMembership(_channelId, tierId) {
6827
6855
  const prefix = this.getPublicPrefix();
6828
- return this.videoFetch(
6829
- "POST",
6830
- `${prefix}/channels/${channelId}/memberships/${tierId}/join`,
6831
- {}
6832
- );
6856
+ return this.videoFetch("POST", `${prefix}/membership/join`, {
6857
+ tier_id: tierId
6858
+ });
6833
6859
  }
6834
6860
  /**
6835
6861
  * Cancel membership
6862
+ *
6863
+ * @param _channelId 현재 서버에서 무시됩니다. API 안정성을 위해 시그니처만 유지.
6864
+ * @param membershipId 취소할 멤버십 ID
6836
6865
  */
6837
- async cancelMembership(channelId, membershipId) {
6866
+ async cancelMembership(_channelId, membershipId) {
6838
6867
  const prefix = this.getPublicPrefix();
6839
- await this.videoFetch(
6840
- "POST",
6841
- `${prefix}/channels/${channelId}/memberships/${membershipId}/cancel`,
6842
- {}
6843
- );
6868
+ await this.videoFetch("POST", `${prefix}/membership/${membershipId}/cancel`, {});
6844
6869
  }
6845
6870
  // ========== Super Chat Operations ==========
6846
6871
  /**
6847
- * Send a super chat
6872
+ * Send a super chat to a channel.
6873
+ *
6874
+ * 백엔드는 `POST /v1/public/super-chats` 에 `channel_id`/`type`/`amount` 를 요구하고,
6875
+ * `video_id` 또는 `live_id` 중 하나로 대상을 지정한다 (AppMember 인증 필수).
6876
+ * 응답의 `client_secret` 으로 결제(Stripe)를 확정해야 슈퍼챗이 노출된다.
6877
+ *
6878
+ * @param channelId 슈퍼챗을 받을 채널 ID
6879
+ * @param amount 금액(최소 단위 정수)
6880
+ * @param options videoId 또는 liveId 중 하나로 대상 지정 + 종류/메시지/통화
6848
6881
  */
6849
- async sendSuperChat(videoId, amount, message, currency) {
6882
+ async sendSuperChat(channelId, amount, options) {
6850
6883
  const prefix = this.getPublicPrefix();
6851
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/super-chats`, {
6884
+ return this.videoFetch("POST", `${prefix}/super-chats`, {
6885
+ channel_id: channelId,
6886
+ type: options?.type ?? "super_thanks",
6852
6887
  amount,
6853
- message,
6854
- currency: currency || "USD"
6888
+ video_id: options?.videoId,
6889
+ live_id: options?.liveId,
6890
+ currency: options?.currency ?? "USD",
6891
+ message: options?.message,
6892
+ sticker_id: options?.stickerId
6855
6893
  });
6856
6894
  }
6857
6895
  /**
@@ -6868,13 +6906,15 @@ var VideoAPI = class {
6868
6906
  // ========== Recommendation Operations ==========
6869
6907
  /**
6870
6908
  * Get recommended videos
6909
+ *
6910
+ * @deprecated Use getHomeFeed() — 동일한 개인화 피드(`/recommendations/feed`)를 반환합니다.
6871
6911
  */
6872
6912
  async getRecommendations(limit) {
6873
6913
  const prefix = this.getPublicPrefix();
6874
6914
  const params = limit ? `?limit=${limit}` : "";
6875
6915
  const response = await this.videoFetch(
6876
6916
  "GET",
6877
- `${prefix}/recommendations${params}`
6917
+ `${prefix}/recommendations/feed${params}`
6878
6918
  );
6879
6919
  return response.videos;
6880
6920
  }
@@ -6886,7 +6926,7 @@ var VideoAPI = class {
6886
6926
  const params = limit ? `?limit=${limit}` : "";
6887
6927
  const response = await this.videoFetch(
6888
6928
  "GET",
6889
- `${prefix}/recommendations/home${params}`
6929
+ `${prefix}/recommendations/feed${params}`
6890
6930
  );
6891
6931
  return response.videos;
6892
6932
  }
@@ -6898,7 +6938,7 @@ var VideoAPI = class {
6898
6938
  const params = limit ? `?limit=${limit}` : "";
6899
6939
  const response = await this.videoFetch(
6900
6940
  "GET",
6901
- `${prefix}/recommendations/related/${videoId}${params}`
6941
+ `${prefix}/videos/${videoId}/related${params}`
6902
6942
  );
6903
6943
  return response.videos;
6904
6944
  }
@@ -6919,9 +6959,17 @@ var VideoAPI = class {
6919
6959
  */
6920
6960
  async submitFeedback(videoId, feedback) {
6921
6961
  const prefix = this.getPublicPrefix();
6922
- await this.videoFetch("POST", `${prefix}/recommendations/feedback`, {
6962
+ if (feedback === "not_interested") {
6963
+ await this.videoFetch(
6964
+ "POST",
6965
+ `${prefix}/recommendations/not-interested/${videoId}`,
6966
+ {}
6967
+ );
6968
+ return;
6969
+ }
6970
+ await this.videoFetch("POST", `${prefix}/recommendations/interactions`, {
6923
6971
  video_id: videoId,
6924
- feedback
6972
+ type: "like"
6925
6973
  });
6926
6974
  }
6927
6975
  };
@@ -6963,7 +7011,7 @@ var GameConfigAPI = class {
6963
7011
  /**
6964
7012
  * 단일 기능 활성화 — set() 의 편의 wrapper.
6965
7013
  *
6966
- * @example await cb.game.config.enable(appId, "matchqueue")
7014
+ * @example await cb.game.config.enable(appId, "matchqueue_enabled")
6967
7015
  */
6968
7016
  async enable(appId, feature) {
6969
7017
  return this.set(appId, { [feature]: true });
@@ -7621,7 +7669,7 @@ var GameAPI = class {
7621
7669
  const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectators`, {
7622
7670
  method: "POST",
7623
7671
  headers: { ...this.getHeaders(), "Content-Type": "application/json" },
7624
- body: JSON.stringify({ player_id: playerId })
7672
+ body: JSON.stringify({ user_id: playerId })
7625
7673
  });
7626
7674
  if (!response.ok) throw new Error(`Failed to join spectator: ${response.statusText}`);
7627
7675
  return response.json();
@@ -10947,11 +10995,11 @@ var GameRoomTransport = class {
10947
10995
  });
10948
10996
  }
10949
10997
  // Private methods
10950
- send(type, data) {
10998
+ send(type, data, msgId) {
10951
10999
  if (!this.transport?.isConnected()) {
10952
11000
  throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
10953
11001
  }
10954
- const message = JSON.stringify({ type, data });
11002
+ const message = JSON.stringify({ type, data, msg_id: msgId });
10955
11003
  this.transport.send(message, true);
10956
11004
  }
10957
11005
  sendWithHandler(type, data, handler) {
@@ -10960,14 +11008,14 @@ var GameRoomTransport = class {
10960
11008
  setTimeout(() => {
10961
11009
  this.pendingHandlers.delete(id);
10962
11010
  }, 1e4);
10963
- this.send(type, { ...data, _msg_id: id });
11011
+ this.send(type, data, id);
10964
11012
  }
10965
11013
  handleMessage(data) {
10966
11014
  try {
10967
11015
  const msg = JSON.parse(data);
10968
- if (msg._msg_id && this.pendingHandlers.has(msg._msg_id)) {
10969
- const handler = this.pendingHandlers.get(msg._msg_id);
10970
- this.pendingHandlers.delete(msg._msg_id);
11016
+ if (msg.msg_id && this.pendingHandlers.has(msg.msg_id)) {
11017
+ const handler = this.pendingHandlers.get(msg.msg_id);
11018
+ this.pendingHandlers.delete(msg.msg_id);
10971
11019
  handler(msg);
10972
11020
  return;
10973
11021
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.35.2",
3
+ "version": "3.37.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",