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/CHANGELOG.md +29 -0
- package/README.md +15 -9
- package/dist/connect-base.umd.js +2 -2
- package/dist/index.d.mts +62 -25
- package/dist/index.d.ts +62 -25
- package/dist/index.js +75 -42
- package/dist/index.mjs +75 -42
- package/package.json +1 -1
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.
|
|
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(
|
|
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(
|
|
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 /
|
|
6157
|
+
* 회원 ID 목록으로 푸시 발송 — Functions / 서버사이드 인증 환경 전용.
|
|
6143
6158
|
*
|
|
6144
|
-
* 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT
|
|
6145
|
-
* (`
|
|
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
|
|
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 (
|
|
6155
|
-
* const result = await
|
|
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_) \
|
|
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 {
|
|
6619
|
-
* videoElement.src =
|
|
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}/
|
|
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}/
|
|
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(
|
|
6894
|
+
async joinMembership(_channelId, tierId) {
|
|
6873
6895
|
const prefix = this.getPublicPrefix();
|
|
6874
|
-
return this.videoFetch(
|
|
6875
|
-
|
|
6876
|
-
|
|
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(
|
|
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/
|
|
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}/
|
|
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
|
-
|
|
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
|
-
|
|
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, "
|
|
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({
|
|
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
|
-
* (
|
|
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,
|
|
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.
|
|
11011
|
-
const handler = this.pendingHandlers.get(msg.
|
|
11012
|
-
this.pendingHandlers.delete(msg.
|
|
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.
|
|
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(
|
|
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(
|
|
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 /
|
|
6111
|
+
* 회원 ID 목록으로 푸시 발송 — Functions / 서버사이드 인증 환경 전용.
|
|
6097
6112
|
*
|
|
6098
|
-
* 백엔드 라우트 `POST /v1/apps/:appID/push/send` 는 콘솔 JWT
|
|
6099
|
-
* (`
|
|
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
|
|
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 (
|
|
6109
|
-
* const result = await
|
|
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_) \
|
|
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 {
|
|
6573
|
-
* videoElement.src =
|
|
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}/
|
|
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}/
|
|
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(
|
|
6848
|
+
async joinMembership(_channelId, tierId) {
|
|
6827
6849
|
const prefix = this.getPublicPrefix();
|
|
6828
|
-
return this.videoFetch(
|
|
6829
|
-
|
|
6830
|
-
|
|
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(
|
|
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/
|
|
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}/
|
|
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
|
-
|
|
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
|
-
|
|
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, "
|
|
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({
|
|
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
|
-
* (
|
|
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,
|
|
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.
|
|
10965
|
-
const handler = this.pendingHandlers.get(msg.
|
|
10966
|
-
this.pendingHandlers.delete(msg.
|
|
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
|
}
|