connectbase-client 3.35.1 → 3.36.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
@@ -184,7 +184,7 @@ var HttpClient = class {
184
184
  this.refreshLockedUntil = 0;
185
185
  /**
186
186
  * 최근 API 호출 breadcrumb (PII strip 후 저장). platform_issue 발행 시 자동 첨부 가능.
187
- * `client.support.getRecentCalls()` 로 외부 노출.
187
+ * `client.support.getRecentApiCalls()` 로 외부 노출.
188
188
  */
189
189
  this.recentCalls = new RecentCallsBuffer();
190
190
  /**
@@ -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) {
@@ -6795,7 +6813,8 @@ var VideoAPI = class {
6795
6813
  */
6796
6814
  async postComment(videoId, content, parentId) {
6797
6815
  const prefix = this.getPublicPrefix();
6798
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/comments`, {
6816
+ return this.videoFetch("POST", `${prefix}/comments`, {
6817
+ video_id: videoId,
6799
6818
  content,
6800
6819
  parent_id: parentId
6801
6820
  });
@@ -6862,31 +6881,31 @@ var VideoAPI = class {
6862
6881
  const prefix = this.getPublicPrefix();
6863
6882
  const response = await this.videoFetch(
6864
6883
  "GET",
6865
- `${prefix}/channels/${channelId}/memberships/tiers`
6884
+ `${prefix}/channels/${channelId}/membership/tiers`
6866
6885
  );
6867
6886
  return response.tiers;
6868
6887
  }
6869
6888
  /**
6870
6889
  * Join a membership tier
6890
+ *
6891
+ * @param _channelId 현재 서버에서 무시됩니다 (tier 가 채널을 함의). API 안정성을 위해 시그니처만 유지.
6892
+ * @param tierId 가입할 멤버십 티어 ID
6871
6893
  */
6872
- async joinMembership(channelId, tierId) {
6894
+ async joinMembership(_channelId, tierId) {
6873
6895
  const prefix = this.getPublicPrefix();
6874
- return this.videoFetch(
6875
- "POST",
6876
- `${prefix}/channels/${channelId}/memberships/${tierId}/join`,
6877
- {}
6878
- );
6896
+ return this.videoFetch("POST", `${prefix}/membership/join`, {
6897
+ tier_id: tierId
6898
+ });
6879
6899
  }
6880
6900
  /**
6881
6901
  * Cancel membership
6902
+ *
6903
+ * @param _channelId 현재 서버에서 무시됩니다. API 안정성을 위해 시그니처만 유지.
6904
+ * @param membershipId 취소할 멤버십 ID
6882
6905
  */
6883
- async cancelMembership(channelId, membershipId) {
6906
+ async cancelMembership(_channelId, membershipId) {
6884
6907
  const prefix = this.getPublicPrefix();
6885
- await this.videoFetch(
6886
- "POST",
6887
- `${prefix}/channels/${channelId}/memberships/${membershipId}/cancel`,
6888
- {}
6889
- );
6908
+ await this.videoFetch("POST", `${prefix}/membership/${membershipId}/cancel`, {});
6890
6909
  }
6891
6910
  // ========== Super Chat Operations ==========
6892
6911
  /**
@@ -6914,13 +6933,15 @@ var VideoAPI = class {
6914
6933
  // ========== Recommendation Operations ==========
6915
6934
  /**
6916
6935
  * Get recommended videos
6936
+ *
6937
+ * @deprecated Use getHomeFeed() — 동일한 개인화 피드(`/recommendations/feed`)를 반환합니다.
6917
6938
  */
6918
6939
  async getRecommendations(limit) {
6919
6940
  const prefix = this.getPublicPrefix();
6920
6941
  const params = limit ? `?limit=${limit}` : "";
6921
6942
  const response = await this.videoFetch(
6922
6943
  "GET",
6923
- `${prefix}/recommendations${params}`
6944
+ `${prefix}/recommendations/feed${params}`
6924
6945
  );
6925
6946
  return response.videos;
6926
6947
  }
@@ -6932,7 +6953,7 @@ var VideoAPI = class {
6932
6953
  const params = limit ? `?limit=${limit}` : "";
6933
6954
  const response = await this.videoFetch(
6934
6955
  "GET",
6935
- `${prefix}/recommendations/home${params}`
6956
+ `${prefix}/recommendations/feed${params}`
6936
6957
  );
6937
6958
  return response.videos;
6938
6959
  }
@@ -6944,7 +6965,7 @@ var VideoAPI = class {
6944
6965
  const params = limit ? `?limit=${limit}` : "";
6945
6966
  const response = await this.videoFetch(
6946
6967
  "GET",
6947
- `${prefix}/recommendations/related/${videoId}${params}`
6968
+ `${prefix}/videos/${videoId}/related${params}`
6948
6969
  );
6949
6970
  return response.videos;
6950
6971
  }
@@ -6965,9 +6986,17 @@ var VideoAPI = class {
6965
6986
  */
6966
6987
  async submitFeedback(videoId, feedback) {
6967
6988
  const prefix = this.getPublicPrefix();
6968
- await this.videoFetch("POST", `${prefix}/recommendations/feedback`, {
6989
+ if (feedback === "not_interested") {
6990
+ await this.videoFetch(
6991
+ "POST",
6992
+ `${prefix}/recommendations/not-interested/${videoId}`,
6993
+ {}
6994
+ );
6995
+ return;
6996
+ }
6997
+ await this.videoFetch("POST", `${prefix}/recommendations/interactions`, {
6969
6998
  video_id: videoId,
6970
- feedback
6999
+ type: "like"
6971
7000
  });
6972
7001
  }
6973
7002
  };
@@ -7009,7 +7038,7 @@ var GameConfigAPI = class {
7009
7038
  /**
7010
7039
  * 단일 기능 활성화 — set() 의 편의 wrapper.
7011
7040
  *
7012
- * @example await cb.game.config.enable(appId, "matchqueue")
7041
+ * @example await cb.game.config.enable(appId, "matchqueue_enabled")
7013
7042
  */
7014
7043
  async enable(appId, feature) {
7015
7044
  return this.set(appId, { [feature]: true });
@@ -7667,7 +7696,7 @@ var GameAPI = class {
7667
7696
  const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectators`, {
7668
7697
  method: "POST",
7669
7698
  headers: { ...this.getHeaders(), "Content-Type": "application/json" },
7670
- body: JSON.stringify({ player_id: playerId })
7699
+ body: JSON.stringify({ user_id: playerId })
7671
7700
  });
7672
7701
  if (!response.ok) throw new Error(`Failed to join spectator: ${response.statusText}`);
7673
7702
  return response.json();
@@ -9120,6 +9149,9 @@ var EndpointAPI = class {
9120
9149
  * predicate 는 같은 Response 를 한 번만 읽을 수 있으므로, 헬퍼 내부에서
9121
9150
  * `res.clone().json()` 형태로 안전하게 파싱한 뒤 호출자에게 전달.
9122
9151
  *
9152
+ * predicate 의 `body` 는 `unknown` 으로 전달되므로, 호출자가 자신의 응답
9153
+ * 스키마로 narrowing 한 뒤 사용한다 (런타임 검증 책임은 호출자에게 있음).
9154
+ *
9123
9155
  * @example ComfyUI 결과 폴링
9124
9156
  * ```typescript
9125
9157
  * type Hist = Record<string, { outputs: Record<string, { images?: { filename: string }[] }> }>
@@ -9127,7 +9159,8 @@ var EndpointAPI = class {
9127
9159
  * const filename = await cb.endpoint.pollUntil<string>(
9128
9160
  * "comfyui-main",
9129
9161
  * { path: `/history/${promptId}` },
9130
- * (data: Hist) => {
9162
+ * (body) => {
9163
+ * const data = body as Hist
9131
9164
  * const entry = data[promptId]
9132
9165
  * if (!entry) return undefined // 아직 큐에 있음
9133
9166
  * for (const out of Object.values(entry.outputs)) {
@@ -10989,11 +11022,11 @@ var GameRoomTransport = class {
10989
11022
  });
10990
11023
  }
10991
11024
  // Private methods
10992
- send(type, data) {
11025
+ send(type, data, msgId) {
10993
11026
  if (!this.transport?.isConnected()) {
10994
11027
  throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
10995
11028
  }
10996
- const message = JSON.stringify({ type, data });
11029
+ const message = JSON.stringify({ type, data, msg_id: msgId });
10997
11030
  this.transport.send(message, true);
10998
11031
  }
10999
11032
  sendWithHandler(type, data, handler) {
@@ -11002,14 +11035,14 @@ var GameRoomTransport = class {
11002
11035
  setTimeout(() => {
11003
11036
  this.pendingHandlers.delete(id);
11004
11037
  }, 1e4);
11005
- this.send(type, { ...data, _msg_id: id });
11038
+ this.send(type, data, id);
11006
11039
  }
11007
11040
  handleMessage(data) {
11008
11041
  try {
11009
11042
  const msg = JSON.parse(data);
11010
- if (msg._msg_id && this.pendingHandlers.has(msg._msg_id)) {
11011
- const handler = this.pendingHandlers.get(msg._msg_id);
11012
- this.pendingHandlers.delete(msg._msg_id);
11043
+ if (msg.msg_id && this.pendingHandlers.has(msg.msg_id)) {
11044
+ const handler = this.pendingHandlers.get(msg.msg_id);
11045
+ this.pendingHandlers.delete(msg.msg_id);
11013
11046
  handler(msg);
11014
11047
  return;
11015
11048
  }
package/dist/index.mjs CHANGED
@@ -138,7 +138,7 @@ var HttpClient = class {
138
138
  this.refreshLockedUntil = 0;
139
139
  /**
140
140
  * 최근 API 호출 breadcrumb (PII strip 후 저장). platform_issue 발행 시 자동 첨부 가능.
141
- * `client.support.getRecentCalls()` 로 외부 노출.
141
+ * `client.support.getRecentApiCalls()` 로 외부 노출.
142
142
  */
143
143
  this.recentCalls = new RecentCallsBuffer();
144
144
  /**
@@ -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) {
@@ -6749,7 +6767,8 @@ var VideoAPI = class {
6749
6767
  */
6750
6768
  async postComment(videoId, content, parentId) {
6751
6769
  const prefix = this.getPublicPrefix();
6752
- return this.videoFetch("POST", `${prefix}/videos/${videoId}/comments`, {
6770
+ return this.videoFetch("POST", `${prefix}/comments`, {
6771
+ video_id: videoId,
6753
6772
  content,
6754
6773
  parent_id: parentId
6755
6774
  });
@@ -6816,31 +6835,31 @@ var VideoAPI = class {
6816
6835
  const prefix = this.getPublicPrefix();
6817
6836
  const response = await this.videoFetch(
6818
6837
  "GET",
6819
- `${prefix}/channels/${channelId}/memberships/tiers`
6838
+ `${prefix}/channels/${channelId}/membership/tiers`
6820
6839
  );
6821
6840
  return response.tiers;
6822
6841
  }
6823
6842
  /**
6824
6843
  * Join a membership tier
6844
+ *
6845
+ * @param _channelId 현재 서버에서 무시됩니다 (tier 가 채널을 함의). API 안정성을 위해 시그니처만 유지.
6846
+ * @param tierId 가입할 멤버십 티어 ID
6825
6847
  */
6826
- async joinMembership(channelId, tierId) {
6848
+ async joinMembership(_channelId, tierId) {
6827
6849
  const prefix = this.getPublicPrefix();
6828
- return this.videoFetch(
6829
- "POST",
6830
- `${prefix}/channels/${channelId}/memberships/${tierId}/join`,
6831
- {}
6832
- );
6850
+ return this.videoFetch("POST", `${prefix}/membership/join`, {
6851
+ tier_id: tierId
6852
+ });
6833
6853
  }
6834
6854
  /**
6835
6855
  * Cancel membership
6856
+ *
6857
+ * @param _channelId 현재 서버에서 무시됩니다. API 안정성을 위해 시그니처만 유지.
6858
+ * @param membershipId 취소할 멤버십 ID
6836
6859
  */
6837
- async cancelMembership(channelId, membershipId) {
6860
+ async cancelMembership(_channelId, membershipId) {
6838
6861
  const prefix = this.getPublicPrefix();
6839
- await this.videoFetch(
6840
- "POST",
6841
- `${prefix}/channels/${channelId}/memberships/${membershipId}/cancel`,
6842
- {}
6843
- );
6862
+ await this.videoFetch("POST", `${prefix}/membership/${membershipId}/cancel`, {});
6844
6863
  }
6845
6864
  // ========== Super Chat Operations ==========
6846
6865
  /**
@@ -6868,13 +6887,15 @@ var VideoAPI = class {
6868
6887
  // ========== Recommendation Operations ==========
6869
6888
  /**
6870
6889
  * Get recommended videos
6890
+ *
6891
+ * @deprecated Use getHomeFeed() — 동일한 개인화 피드(`/recommendations/feed`)를 반환합니다.
6871
6892
  */
6872
6893
  async getRecommendations(limit) {
6873
6894
  const prefix = this.getPublicPrefix();
6874
6895
  const params = limit ? `?limit=${limit}` : "";
6875
6896
  const response = await this.videoFetch(
6876
6897
  "GET",
6877
- `${prefix}/recommendations${params}`
6898
+ `${prefix}/recommendations/feed${params}`
6878
6899
  );
6879
6900
  return response.videos;
6880
6901
  }
@@ -6886,7 +6907,7 @@ var VideoAPI = class {
6886
6907
  const params = limit ? `?limit=${limit}` : "";
6887
6908
  const response = await this.videoFetch(
6888
6909
  "GET",
6889
- `${prefix}/recommendations/home${params}`
6910
+ `${prefix}/recommendations/feed${params}`
6890
6911
  );
6891
6912
  return response.videos;
6892
6913
  }
@@ -6898,7 +6919,7 @@ var VideoAPI = class {
6898
6919
  const params = limit ? `?limit=${limit}` : "";
6899
6920
  const response = await this.videoFetch(
6900
6921
  "GET",
6901
- `${prefix}/recommendations/related/${videoId}${params}`
6922
+ `${prefix}/videos/${videoId}/related${params}`
6902
6923
  );
6903
6924
  return response.videos;
6904
6925
  }
@@ -6919,9 +6940,17 @@ var VideoAPI = class {
6919
6940
  */
6920
6941
  async submitFeedback(videoId, feedback) {
6921
6942
  const prefix = this.getPublicPrefix();
6922
- await this.videoFetch("POST", `${prefix}/recommendations/feedback`, {
6943
+ if (feedback === "not_interested") {
6944
+ await this.videoFetch(
6945
+ "POST",
6946
+ `${prefix}/recommendations/not-interested/${videoId}`,
6947
+ {}
6948
+ );
6949
+ return;
6950
+ }
6951
+ await this.videoFetch("POST", `${prefix}/recommendations/interactions`, {
6923
6952
  video_id: videoId,
6924
- feedback
6953
+ type: "like"
6925
6954
  });
6926
6955
  }
6927
6956
  };
@@ -6963,7 +6992,7 @@ var GameConfigAPI = class {
6963
6992
  /**
6964
6993
  * 단일 기능 활성화 — set() 의 편의 wrapper.
6965
6994
  *
6966
- * @example await cb.game.config.enable(appId, "matchqueue")
6995
+ * @example await cb.game.config.enable(appId, "matchqueue_enabled")
6967
6996
  */
6968
6997
  async enable(appId, feature) {
6969
6998
  return this.set(appId, { [feature]: true });
@@ -7621,7 +7650,7 @@ var GameAPI = class {
7621
7650
  const response = await fetch(`${this.gameServerUrl}/v1/game/${id}/rooms/${roomId}/spectators`, {
7622
7651
  method: "POST",
7623
7652
  headers: { ...this.getHeaders(), "Content-Type": "application/json" },
7624
- body: JSON.stringify({ player_id: playerId })
7653
+ body: JSON.stringify({ user_id: playerId })
7625
7654
  });
7626
7655
  if (!response.ok) throw new Error(`Failed to join spectator: ${response.statusText}`);
7627
7656
  return response.json();
@@ -9074,6 +9103,9 @@ var EndpointAPI = class {
9074
9103
  * predicate 는 같은 Response 를 한 번만 읽을 수 있으므로, 헬퍼 내부에서
9075
9104
  * `res.clone().json()` 형태로 안전하게 파싱한 뒤 호출자에게 전달.
9076
9105
  *
9106
+ * predicate 의 `body` 는 `unknown` 으로 전달되므로, 호출자가 자신의 응답
9107
+ * 스키마로 narrowing 한 뒤 사용한다 (런타임 검증 책임은 호출자에게 있음).
9108
+ *
9077
9109
  * @example ComfyUI 결과 폴링
9078
9110
  * ```typescript
9079
9111
  * type Hist = Record<string, { outputs: Record<string, { images?: { filename: string }[] }> }>
@@ -9081,7 +9113,8 @@ var EndpointAPI = class {
9081
9113
  * const filename = await cb.endpoint.pollUntil<string>(
9082
9114
  * "comfyui-main",
9083
9115
  * { path: `/history/${promptId}` },
9084
- * (data: Hist) => {
9116
+ * (body) => {
9117
+ * const data = body as Hist
9085
9118
  * const entry = data[promptId]
9086
9119
  * if (!entry) return undefined // 아직 큐에 있음
9087
9120
  * for (const out of Object.values(entry.outputs)) {
@@ -10943,11 +10976,11 @@ var GameRoomTransport = class {
10943
10976
  });
10944
10977
  }
10945
10978
  // Private methods
10946
- send(type, data) {
10979
+ send(type, data, msgId) {
10947
10980
  if (!this.transport?.isConnected()) {
10948
10981
  throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
10949
10982
  }
10950
- const message = JSON.stringify({ type, data });
10983
+ const message = JSON.stringify({ type, data, msg_id: msgId });
10951
10984
  this.transport.send(message, true);
10952
10985
  }
10953
10986
  sendWithHandler(type, data, handler) {
@@ -10956,14 +10989,14 @@ var GameRoomTransport = class {
10956
10989
  setTimeout(() => {
10957
10990
  this.pendingHandlers.delete(id);
10958
10991
  }, 1e4);
10959
- this.send(type, { ...data, _msg_id: id });
10992
+ this.send(type, data, id);
10960
10993
  }
10961
10994
  handleMessage(data) {
10962
10995
  try {
10963
10996
  const msg = JSON.parse(data);
10964
- if (msg._msg_id && this.pendingHandlers.has(msg._msg_id)) {
10965
- const handler = this.pendingHandlers.get(msg._msg_id);
10966
- this.pendingHandlers.delete(msg._msg_id);
10997
+ if (msg.msg_id && this.pendingHandlers.has(msg.msg_id)) {
10998
+ const handler = this.pendingHandlers.get(msg.msg_id);
10999
+ this.pendingHandlers.delete(msg.msg_id);
10967
11000
  handler(msg);
10968
11001
  return;
10969
11002
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.35.1",
3
+ "version": "3.36.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",