connectbase-client 0.3.1 → 0.3.3
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/README.md +24 -8
- package/dist/connect-base.umd.js +2 -2
- package/dist/index.d.mts +155 -2
- package/dist/index.d.ts +155 -2
- package/dist/index.js +156 -5
- package/dist/index.mjs +155 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -430,17 +430,33 @@ await cb.database.from('users').delete().where({ id: 'user-1' })
|
|
|
430
430
|
### Storage
|
|
431
431
|
|
|
432
432
|
```typescript
|
|
433
|
-
//
|
|
434
|
-
const
|
|
433
|
+
// 파일 업로드 (UUID 기반 URL - 매번 변경됨)
|
|
434
|
+
const result = await cb.storage.uploadFile('storage-id', file)
|
|
435
|
+
console.log(result.url)
|
|
436
|
+
|
|
437
|
+
// 특정 폴더에 업로드
|
|
438
|
+
const result = await cb.storage.uploadFile('storage-id', file, 'folder-id')
|
|
439
|
+
|
|
440
|
+
// 경로 기반 업로드 (고정 URL - Firebase Storage 스타일)
|
|
441
|
+
// 같은 경로에 다시 업로드하면 URL이 유지된 채로 파일만 교체
|
|
442
|
+
const result = await cb.storage.uploadByPath(
|
|
443
|
+
'storage-id',
|
|
444
|
+
'/profiles/user123/avatar.png',
|
|
445
|
+
file
|
|
446
|
+
)
|
|
447
|
+
console.log(result.url) // 항상 동일한 URL
|
|
448
|
+
|
|
449
|
+
// 경로로 파일 조회
|
|
450
|
+
const file = await cb.storage.getByPath('storage-id', '/profiles/user123/avatar.png')
|
|
435
451
|
|
|
436
|
-
//
|
|
437
|
-
const
|
|
452
|
+
// 경로로 URL만 가져오기 (없으면 null)
|
|
453
|
+
const url = await cb.storage.getUrlByPath('storage-id', '/profiles/user123/avatar.png')
|
|
438
454
|
|
|
439
|
-
//
|
|
440
|
-
await cb.storage.
|
|
455
|
+
// 파일 목록 조회
|
|
456
|
+
const files = await cb.storage.getFiles('storage-id')
|
|
441
457
|
|
|
442
|
-
//
|
|
443
|
-
|
|
458
|
+
// 파일 삭제
|
|
459
|
+
await cb.storage.deleteFile('storage-id', 'file-id')
|
|
444
460
|
```
|
|
445
461
|
|
|
446
462
|
### Realtime
|
package/dist/connect-base.umd.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var ConnectBaseModule=(()=>{var H=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var Z=(a,e)=>{for(var t in e)H(a,t,{get:e[t],enumerable:!0})},ee=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of X(e))!Y.call(a,r)&&r!==t&&H(a,r,{get:()=>e[r],enumerable:!(s=Q(e,r))||s.enumerable});return a};var te=a=>ee(H({},"__esModule",{value:!0}),a);var le={};Z(le,{ApiError:()=>d,AuthError:()=>u,ConnectBase:()=>D,GameAPI:()=>y,GameRoom:()=>b,GameRoomTransport:()=>q,VideoProcessingError:()=>f,default:()=>he,isWebTransportSupported:()=>K});var d=class extends Error{constructor(t,s){super(s);this.statusCode=t;this.name="ApiError"}},u=class extends Error{constructor(e){super(e),this.name="AuthError"}};var P=class{constructor(e){this.isRefreshing=!1;this.refreshPromise=null;this.config=e}updateConfig(e){this.config={...this.config,...e}}setTokens(e,t){this.config.accessToken=e,this.config.refreshToken=t}clearTokens(){this.config.accessToken=void 0,this.config.refreshToken=void 0}hasApiKey(){return!!this.config.apiKey}getApiKey(){return this.config.apiKey}getAccessToken(){return this.config.accessToken}getBaseUrl(){return this.config.baseUrl}async refreshAccessToken(){if(this.isRefreshing)return this.refreshPromise;if(this.isRefreshing=!0,!this.config.refreshToken){this.isRefreshing=!1;let e=new u("Refresh token is missing. Please login again.");throw this.config.onAuthError?.(e),e}return this.refreshPromise=(async()=>{try{let e=await fetch(`${this.config.baseUrl}/v1/auth/re-issue`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.refreshToken}`}});if(!e.ok)throw new Error("Token refresh failed");let t=await e.json();return this.config.accessToken=t.access_token,this.config.refreshToken=t.refresh_token,this.config.onTokenRefresh?.({accessToken:t.access_token,refreshToken:t.refresh_token}),t.access_token}catch{this.clearTokens();let e=new u("Token refresh failed. Please login again.");throw this.config.onAuthError?.(e),e}finally{this.isRefreshing=!1,this.refreshPromise=null}})(),this.refreshPromise}isTokenExpired(e){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s+300}catch{return!0}}async prepareHeaders(e){let t=new Headers;if(t.set("Content-Type","application/json"),this.config.apiKey&&t.set("X-API-Key",this.config.apiKey),!e?.skipAuth&&this.config.accessToken){let s=this.config.accessToken;if(this.isTokenExpired(s)&&this.config.refreshToken){let r=await this.refreshAccessToken();r&&(s=r)}t.set("Authorization",`Bearer ${s}`)}return e?.headers&&Object.entries(e.headers).forEach(([s,r])=>{t.set(s,r)}),t}async handleResponse(e){if(!e.ok){let t=await e.json().catch(()=>({message:e.statusText}));throw new d(e.status,t.message||t.error||"Unknown error")}return e.status===204||e.headers.get("content-length")==="0"?{}:e.json()}async get(e,t){let s=await this.prepareHeaders(t),r=await fetch(`${this.config.baseUrl}${e}`,{method:"GET",headers:s});return this.handleResponse(r)}async post(e,t,s){let r=await this.prepareHeaders(s);t instanceof FormData&&r.delete("Content-Type");let i=await fetch(`${this.config.baseUrl}${e}`,{method:"POST",headers:r,body:t instanceof FormData?t:JSON.stringify(t)});return this.handleResponse(i)}async put(e,t,s){let r=await this.prepareHeaders(s),i=await fetch(`${this.config.baseUrl}${e}`,{method:"PUT",headers:r,body:JSON.stringify(t)});return this.handleResponse(i)}async patch(e,t,s){let r=await this.prepareHeaders(s),i=await fetch(`${this.config.baseUrl}${e}`,{method:"PATCH",headers:r,body:JSON.stringify(t)});return this.handleResponse(i)}async delete(e,t){let s=await this.prepareHeaders(t),r=await fetch(`${this.config.baseUrl}${e}`,{method:"DELETE",headers:s});return this.handleResponse(r)}};var N="cb_guest_";function g(a){typeof window>"u"||(a?typeof window.__cbSetMember=="function"&&window.__cbSetMember(a):typeof window.__cbClearMember=="function"&&window.__cbClearMember())}function se(a){let e=0;for(let t=0;t<a.length;t++){let s=a.charCodeAt(t);e=(e<<5)-e+s,e=e&e}return Math.abs(e).toString(36)}var S=class{constructor(e){this.http=e;this.guestMemberLoginPromise=null;this.cachedGuestMemberTokenKey=null}async getAuthSettings(){return this.http.get("/v1/public/auth-settings",{skipAuth:!0})}async signUpMember(e){let t=await this.http.post("/v1/public/app-members/signup",e,{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),g(t.member_id),t}async signInMember(e){let t=await this.http.post("/v1/public/app-members/signin",e,{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),g(t.member_id),t}async signInAsGuestMember(){if(this.guestMemberLoginPromise)return this.guestMemberLoginPromise;this.guestMemberLoginPromise=this.executeGuestMemberLogin();try{return await this.guestMemberLoginPromise}finally{this.guestMemberLoginPromise=null}}async signOut(){try{await this.http.post("/v1/auth/logout")}finally{this.http.clearTokens(),g(null)}}clearGuestMemberTokens(){typeof localStorage>"u"||localStorage.removeItem(this.getGuestMemberTokenKey())}async executeGuestMemberLogin(){let e=this.getStoredGuestMemberTokens();if(e){if(!this.isTokenExpired(e.accessToken))try{this.http.setTokens(e.accessToken,e.refreshToken);let s=await this.http.get("/v1/public/app-members/me");if(s.is_active)return g(s.member_id),{member_id:s.member_id,access_token:e.accessToken,refresh_token:e.refreshToken};this.clearGuestMemberTokens()}catch{this.http.clearTokens()}if(e.refreshToken&&!this.isTokenExpired(e.refreshToken))try{let s=await this.http.post("/v1/auth/re-issue",{},{headers:{Authorization:`Bearer ${e.refreshToken}`},skipAuth:!0});return this.http.setTokens(s.access_token,s.refresh_token),this.storeGuestMemberTokens(s.access_token,s.refresh_token,e.memberId),g(e.memberId),{member_id:e.memberId,access_token:s.access_token,refresh_token:s.refresh_token}}catch{this.clearGuestMemberTokens()}else this.clearGuestMemberTokens()}let t=await this.http.post("/v1/public/app-members",{},{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),this.storeGuestMemberTokens(t.access_token,t.refresh_token,t.member_id),g(t.member_id),t}isTokenExpired(e){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s}catch{return!0}}getGuestMemberTokenKey(){if(this.cachedGuestMemberTokenKey)return this.cachedGuestMemberTokenKey;let e=this.http.getApiKey();if(!e)this.cachedGuestMemberTokenKey=`${N}default`;else{let t=se(e);this.cachedGuestMemberTokenKey=`${N}${t}`}return this.cachedGuestMemberTokenKey}getStoredGuestMemberTokens(){if(typeof localStorage>"u")return null;let e=localStorage.getItem(this.getGuestMemberTokenKey());if(!e)return null;try{return JSON.parse(e)}catch{return null}}storeGuestMemberTokens(e,t,s){typeof localStorage>"u"||localStorage.setItem(this.getGuestMemberTokenKey(),JSON.stringify({accessToken:e,refreshToken:t,memberId:s}))}};var R=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async getTables(e){return(await this.http.get(`/v1/databases/json-databases/${e}/tables`)).tables}async createTable(e,t){return this.http.post(`/v1/databases/json-databases/${e}/tables`,t)}async deleteTable(e,t){await this.http.delete(`/v1/databases/json-databases/${e}/tables/${t}`)}async getColumns(e){return(await this.http.get(`/v1/tables/${e}/columns`)).columns}async createColumn(e,t){return this.http.post(`/v1/tables/${e}/columns`,t)}async updateColumn(e,t,s){return this.http.patch(`/v1/tables/${e}/columns/${t}`,s)}async deleteColumn(e,t){await this.http.delete(`/v1/tables/${e}/columns/${t}`)}async getData(e,t){let s=this.getPublicPrefix();if(t?.where)return this.queryData(e,t);let r=new URLSearchParams;t?.limit&&r.append("limit",t.limit.toString()),t?.offset&&r.append("offset",t.offset.toString());let i=r.toString(),n=i?`${s}/tables/${e}/data?${i}`:`${s}/tables/${e}/data`;return this.http.get(n)}async queryData(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/query`,{where:t.where,order_by:t.orderBy,order_direction:t.orderDirection,limit:t.limit,offset:t.offset})}async getDataById(e,t){let s=this.getPublicPrefix();return this.http.get(`${s}/tables/${e}/data/${t}`)}async createData(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data`,t)}async updateData(e,t,s){let r=this.getPublicPrefix();return this.http.put(`${r}/tables/${e}/data/${t}`,s)}async deleteData(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/tables/${e}/data/${t}`)}async createMany(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/bulk`,{data:t.map(r=>r.data)})}async deleteWhere(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/delete-where`,{where:t})}};var T=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async getFiles(e){let t=this.getPublicPrefix();return(await this.http.get(`${t}/storages/files/${e}/items`)).files}async uploadFile(e,t,s){let r=this.getPublicPrefix(),i=new FormData;return i.append("file",t),s&&i.append("parent_id",s),this.http.post(`${r}/storages/files/${e}/upload`,i)}async uploadFiles(e,t,s){let r=[];for(let i of t){let n=await this.uploadFile(e,i,s);r.push(n)}return r}async createFolder(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/storages/files/${e}/folders`,t)}async deleteFile(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/storages/files/${e}/items/${t}`)}async moveFile(e,t,s){let r=this.getPublicPrefix();await this.http.post(`${r}/storages/files/${e}/items/${t}/move`,s)}async renameFile(e,t,s){let r=this.getPublicPrefix();return this.http.patch(`${r}/storages/files/${e}/items/${t}/rename`,s)}getFileUrl(e){return e.url||null}isImageFile(e){return e.mime_type?.startsWith("image/")||!1}};var C=class{constructor(e){this.http=e}async getApiKeys(e){return this.http.get(`/v1/apps/${e}/api-keys`)}async createApiKey(e,t){return this.http.post(`/v1/apps/${e}/api-keys`,t)}async updateApiKey(e,t,s){return this.http.patch(`/v1/apps/${e}/api-keys/${t}`,s)}async deleteApiKey(e,t){await this.http.delete(`/v1/apps/${e}/api-keys/${t}`)}};var _=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async invoke(e,t,s){let r=this.getPublicPrefix(),i={};return t!==void 0&&(i.payload=t),s!==void 0&&(i.timeout=s),this.http.post(`${r}/functions/${e}/invoke`,i)}async call(e,t){let s=await this.invoke(e,t);if(!s.success)throw new Error(s.error||"Function execution failed");return s.result}};var k=class{constructor(e,t){this.ws=null;this.state="disconnected";this._connectionId=null;this._appId=null;this.options={maxRetries:5,retryInterval:1e3,userId:"",accessToken:""};this.retryCount=0;this.pendingRequests=new Map;this.subscriptions=new Map;this.streamSessions=new Map;this.stateHandlers=[];this.errorHandlers=[];this.http=e,this.socketUrl=t,this.clientId=this.generateClientId()}get connectionId(){return this._connectionId}get appId(){return this._appId}async connect(e={}){if(!(this.state==="connected"||this.state==="connecting"))return this.options={...this.options,...e},e.userId&&(this.userId=e.userId),this.doConnect()}disconnect(){this.state="disconnected",this.notifyStateChange(),this.ws&&(this.ws.close(),this.ws=null),this.pendingRequests.forEach(e=>{clearTimeout(e.timeout),e.reject(new Error("Connection closed"))}),this.pendingRequests.clear(),this.subscriptions.clear()}async subscribe(e,t={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let s=this.generateRequestId(),r=await this.sendRequest({category:e,action:"subscribe",request_id:s}),i={category:r.category,persist:r.persist,historyCount:r.history_count,readReceipt:r.read_receipt},n=[];return this.subscriptions.set(e,{info:i,handlers:n}),{info:i,send:async(c,h)=>{await this.sendMessage(e,c,h)},getHistory:async c=>this.getHistory(e,c??t.historyLimit),unsubscribe:async()=>{await this.unsubscribe(e)},onMessage:c=>{n.push(c)}}}async unsubscribe(e){if(this.state!=="connected")return;let t=this.generateRequestId();await this.sendRequest({category:e,action:"unsubscribe",request_id:t}),this.subscriptions.delete(e)}async sendMessage(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected");let i=s.includeSelf!==!1,n=this.generateRequestId();await this.sendRequest({category:e,action:"send",data:{data:t,broadcast:i},request_id:n})}async getHistory(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId(),r=await this.sendRequest({category:e,action:"history",data:t?{limit:t}:void 0,request_id:s});return{category:r.category,messages:r.messages.map(i=>({id:i.id,category:i.category,from:i.from,data:i.data,sentAt:i.sent_at})),total:r.total}}async stream(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let r=this.generateRequestId(),i=s.sessionId||this.generateRequestId();return this.streamSessions.set(i,{handlers:t,requestId:r}),this.sendRaw({category:"",action:"stream",data:{provider:s.provider,model:s.model,messages:e,system:s.system,temperature:s.temperature,max_tokens:s.maxTokens,session_id:i,metadata:s.metadata},request_id:r}),{sessionId:i,stop:async()=>{await this.stopStream(i)}}}async stopStream(e){if(this.state!=="connected")return;let t=this.generateRequestId();this.sendRaw({category:"",action:"stream_stop",data:{session_id:e},request_id:t}),this.streamSessions.delete(e)}getState(){return this.state}onStateChange(e){return this.stateHandlers.push(e),()=>{let t=this.stateHandlers.indexOf(e);t>-1&&this.stateHandlers.splice(t,1)}}onError(e){return this.errorHandlers.push(e),()=>{let t=this.errorHandlers.indexOf(e);t>-1&&this.errorHandlers.splice(t,1)}}async doConnect(){return new Promise((e,t)=>{this.state="connecting",this.notifyStateChange();let s=this.socketUrl.replace(/^http/,"ws"),r;if(this.options.accessToken)r=`${s}/v1/realtime/auth?access_token=${encodeURIComponent(this.options.accessToken)}&client_id=${this.clientId}`;else{let i=this.http.getApiKey();if(!i){t(new Error("API Key or accessToken is required for realtime connection"));return}r=`${s}/v1/realtime/auth?api_key=${encodeURIComponent(i)}&client_id=${this.clientId}`}this.userId&&(r+=`&user_id=${encodeURIComponent(this.userId)}`);try{this.ws=new WebSocket(r),this.ws.onopen=()=>{},this.ws.onmessage=i=>{let n=i.data.split(`
|
|
2
|
-
`).filter(o=>o.trim());for(let o of n)try{let c=JSON.parse(o);this.handleServerMessage(c,e)}catch(c){console.error("[Realtime] Failed to parse message:",o,c)}},this.ws.onclose=()=>{(this.state==="connected"||this.state==="connecting")&&this.handleDisconnect()},this.ws.onerror=i=>{console.error("[Realtime] WebSocket error:",i),this.notifyError(new Error("WebSocket connection error")),this.state==="connecting"&&t(new Error("Failed to connect"))}}catch(i){t(i)}})}handleServerMessage(e,t){switch(e.event){case"connected":{let s=e.data;this._connectionId=s.connection_id,this._appId=s.app_id,this.state="connected",this.retryCount=0,this.notifyStateChange(),t&&t();break}case"subscribed":case"unsubscribed":case"sent":case"result":case"history":{if(e.request_id){let s=this.pendingRequests.get(e.request_id);s&&(clearTimeout(s.timeout),s.resolve(e.data),this.pendingRequests.delete(e.request_id))}break}case"message":{let s=e.data,r=this.subscriptions.get(s.category);if(r){let i={id:s.id,category:s.category,from:s.from,data:s.data,sentAt:s.sent_at};r.handlers.forEach(n=>n(i))}break}case"error":{if(e.request_id){let s=this.pendingRequests.get(e.request_id);s&&(clearTimeout(s.timeout),s.reject(new Error(e.error||"Unknown error")),this.pendingRequests.delete(e.request_id))}else this.notifyError(new Error(e.error||"Unknown error"));break}case"pong":break;case"stream_token":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToken&&r.handlers.onToken(s.token,s.index);break}case"stream_done":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onDone&&r.handlers.onDone({sessionId:s.session_id,fullText:s.full_text,totalTokens:s.total_tokens,promptTokens:s.prompt_tokens,duration:s.duration_ms}),this.streamSessions.delete(s.session_id);break}case"stream_error":{let s=e.data;if(e.request_id){for(let[r,i]of this.streamSessions)if(i.requestId===e.request_id){i.handlers.onError&&i.handlers.onError(new Error(s.message)),this.streamSessions.delete(r);break}}break}}}handleDisconnect(){this.ws=null,this._connectionId=null,this.retryCount<this.options.maxRetries?(this.state="reconnecting",this.notifyStateChange(),this.retryCount++,setTimeout(()=>{this.doConnect().catch(e=>{console.error("[Realtime] Reconnect failed:",e)})},this.options.retryInterval*this.retryCount)):(this.state="disconnected",this.notifyStateChange(),this.notifyError(new Error("Connection lost. Max retries exceeded.")))}sendRequest(e){return new Promise((t,s)=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN){s(new Error("Not connected"));return}let r=setTimeout(()=>{this.pendingRequests.delete(e.request_id),s(new Error("Request timeout"))},3e4);this.pendingRequests.set(e.request_id,{resolve:t,reject:s,timeout:r}),this.ws.send(JSON.stringify(e))})}sendRaw(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");this.ws.send(JSON.stringify(e))}notifyStateChange(){this.stateHandlers.forEach(e=>e(this.state))}notifyError(e){this.errorHandlers.forEach(t=>t(e))}generateClientId(){return"cb_"+Math.random().toString(36).substring(2,15)}generateRequestId(){return"req_"+Date.now()+"_"+Math.random().toString(36).substring(2,9)}};var E=class{constructor(e,t){this.ws=null;this.state="disconnected";this.stateListeners=[];this.errorListeners=[];this.peerJoinedListeners=[];this.peerLeftListeners=[];this.remoteStreamListeners=[];this.reconnectAttempts=0;this.maxReconnectAttempts=5;this.reconnectTimeout=null;this.currentRoomId=null;this.currentPeerId=null;this.currentUserId=null;this.isBroadcaster=!1;this.localStream=null;this.channelType="interactive";this.peerConnections=new Map;this.remoteStreams=new Map;this.iceServers=[];this.http=e,this.webrtcUrl=t}async getICEServers(){let e=await this.http.get("/v1/ice-servers");return this.iceServers=e.ice_servers,e.ice_servers}async connect(e){if(this.state==="connected"||this.state==="connecting")throw new Error("\uC774\uBBF8 \uC5F0\uACB0\uB418\uC5B4 \uC788\uAC70\uB098 \uC5F0\uACB0 \uC911\uC785\uB2C8\uB2E4");if(this.setState("connecting"),this.currentRoomId=e.roomId,this.currentUserId=e.userId||null,this.isBroadcaster=e.isBroadcaster||!1,this.localStream=e.localStream||null,this.iceServers.length===0)try{await this.getICEServers()}catch{this.iceServers=[{urls:"stun:stun.l.google.com:19302"}]}return this.connectWebSocket()}connectWebSocket(){return new Promise((e,t)=>{let s=this.buildWebSocketUrl();this.ws=new WebSocket(s);let r=setTimeout(()=>{this.state==="connecting"&&(this.ws?.close(),t(new Error("\uC5F0\uACB0 \uC2DC\uAC04 \uCD08\uACFC")))},1e4);this.ws.onopen=()=>{clearTimeout(r),this.reconnectAttempts=0,this.sendSignaling({type:"join",room_id:this.currentRoomId,data:{user_id:this.currentUserId,is_broadcaster:this.isBroadcaster}})},this.ws.onmessage=async i=>{try{let n=JSON.parse(i.data);await this.handleSignalingMessage(n,e,t)}catch(n){console.error("Failed to parse signaling message:",n)}},this.ws.onerror=i=>{clearTimeout(r),console.error("WebSocket error:",i),this.emitError(new Error("WebSocket \uC5F0\uACB0 \uC624\uB958"))},this.ws.onclose=i=>{clearTimeout(r),this.state==="connecting"&&t(new Error("\uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4")),this.handleDisconnect(i)}})}buildWebSocketUrl(){let e=this.webrtcUrl.replace("https://","wss://").replace("http://","ws://"),t=this.http.getApiKey(),s=this.http.getAccessToken(),r="";return s?r=`access_token=${encodeURIComponent(s)}`:t&&(r=`api_key=${encodeURIComponent(t)}`),`${e}/v1/signaling?${r}`}async handleSignalingMessage(e,t,s){switch(e.type){case"joined":if(this.setState("connected"),this.currentPeerId=e.peer_id||null,e.data&&typeof e.data=="object"){let n=e.data;n.channel_type&&(this.channelType=n.channel_type);let o=n.peers||[];for(let c of o)c.peer_id!==this.currentPeerId&&await this.createPeerConnection(c.peer_id,!0)}t?.();break;case"peer_joined":if(e.peer_id&&e.peer_id!==this.currentPeerId){let n={peer_id:e.peer_id,...typeof e.data=="object"?e.data:{}};this.emitPeerJoined(e.peer_id,n),await this.createPeerConnection(e.peer_id,!1)}break;case"peer_left":e.peer_id&&(this.closePeerConnection(e.peer_id),this.emitPeerLeft(e.peer_id));break;case"offer":e.peer_id&&e.sdp&&await this.handleOffer(e.peer_id,e.sdp);break;case"answer":e.peer_id&&e.sdp&&await this.handleAnswer(e.peer_id,e.sdp);break;case"ice_candidate":e.peer_id&&e.candidate&&await this.handleICECandidate(e.peer_id,e.candidate);break;case"error":let r=typeof e.data=="string"?e.data:"Unknown error",i=new Error(r);this.emitError(i),s?.(i);break}}async createPeerConnection(e,t){this.closePeerConnection(e);let s={iceServers:this.iceServers.map(i=>({urls:i.urls,username:i.username,credential:i.credential}))},r=new RTCPeerConnection(s);if(this.peerConnections.set(e,r),this.localStream&&this.localStream.getTracks().forEach(i=>{r.addTrack(i,this.localStream)}),r.onicecandidate=i=>{i.candidate&&this.sendSignaling({type:"ice_candidate",target_id:e,candidate:i.candidate.toJSON()})},r.ontrack=i=>{let[n]=i.streams;n&&(this.remoteStreams.set(e,n),this.emitRemoteStream(e,n))},r.onconnectionstatechange=()=>{r.connectionState==="failed"&&(console.warn(`Peer connection failed: ${e}`),this.closePeerConnection(e))},t){let i=await r.createOffer();await r.setLocalDescription(i),this.sendSignaling({type:"offer",target_id:e,sdp:i.sdp})}return r}async handleOffer(e,t){let s=this.peerConnections.get(e);s||(s=await this.createPeerConnection(e,!1)),await s.setRemoteDescription(new RTCSessionDescription({type:"offer",sdp:t}));let r=await s.createAnswer();await s.setLocalDescription(r),this.sendSignaling({type:"answer",target_id:e,sdp:r.sdp})}async handleAnswer(e,t){let s=this.peerConnections.get(e);s&&await s.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:t}))}async handleICECandidate(e,t){let s=this.peerConnections.get(e);if(s)try{await s.addIceCandidate(new RTCIceCandidate(t))}catch(r){console.warn("Failed to add ICE candidate:",r)}}closePeerConnection(e){let t=this.peerConnections.get(e);t&&(t.close(),this.peerConnections.delete(e)),this.remoteStreams.delete(e)}sendSignaling(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleDisconnect(e){let t=this.state==="connected";this.setState("disconnected"),this.peerConnections.forEach((s,r)=>{s.close(),this.emitPeerLeft(r)}),this.peerConnections.clear(),this.remoteStreams.clear(),t&&e.code!==1e3&&this.reconnectAttempts<this.maxReconnectAttempts&&this.attemptReconnect()}attemptReconnect(){this.reconnectAttempts++,this.setState("reconnecting");let e=Math.min(5e3*Math.pow(2,this.reconnectAttempts-1),3e4);this.reconnectTimeout=setTimeout(async()=>{try{await this.connectWebSocket()}catch{this.reconnectAttempts<this.maxReconnectAttempts?this.attemptReconnect():(this.setState("failed"),this.emitError(new Error("\uC7AC\uC5F0\uACB0 \uC2E4\uD328: \uCD5C\uB300 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC")))}},e)}disconnect(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.sendSignaling({type:"leave"}),this.ws.close(1e3,"User disconnected")),this.peerConnections.forEach(e=>e.close()),this.peerConnections.clear(),this.remoteStreams.clear(),this.ws=null,this.currentRoomId=null,this.currentPeerId=null,this.localStream=null,this.setState("disconnected")}getState(){return this.state}getRoomId(){return this.currentRoomId}getPeerId(){return this.currentPeerId}getChannelType(){return this.channelType}getRemoteStream(e){return this.remoteStreams.get(e)}getAllRemoteStreams(){return new Map(this.remoteStreams)}replaceLocalStream(e){this.localStream=e,this.peerConnections.forEach(t=>{let s=t.getSenders();e.getTracks().forEach(r=>{let i=s.find(n=>n.track?.kind===r.kind);i?i.replaceTrack(r):t.addTrack(r,e)})})}setAudioEnabled(e){this.localStream&&this.localStream.getAudioTracks().forEach(t=>{t.enabled=e})}setVideoEnabled(e){this.localStream&&this.localStream.getVideoTracks().forEach(t=>{t.enabled=e})}onStateChange(e){return this.stateListeners.push(e),()=>{this.stateListeners=this.stateListeners.filter(t=>t!==e)}}onError(e){return this.errorListeners.push(e),()=>{this.errorListeners=this.errorListeners.filter(t=>t!==e)}}onPeerJoined(e){return this.peerJoinedListeners.push(e),()=>{this.peerJoinedListeners=this.peerJoinedListeners.filter(t=>t!==e)}}onPeerLeft(e){return this.peerLeftListeners.push(e),()=>{this.peerLeftListeners=this.peerLeftListeners.filter(t=>t!==e)}}onRemoteStream(e){return this.remoteStreamListeners.push(e),()=>{this.remoteStreamListeners=this.remoteStreamListeners.filter(t=>t!==e)}}setState(e){this.state!==e&&(this.state=e,this.stateListeners.forEach(t=>t(e)))}emitError(e){this.errorListeners.forEach(t=>t(e))}emitPeerJoined(e,t){this.peerJoinedListeners.forEach(s=>s(e,t))}emitPeerLeft(e){this.peerLeftListeners.forEach(t=>t(e))}emitRemoteStream(e,t){this.remoteStreamListeners.forEach(s=>s(e,t))}async getStats(e){return this.http.get(`/v1/apps/${e}/webrtc/stats`)}async getRooms(e){return this.http.get(`/v1/apps/${e}/webrtc/rooms`)}};var x=class{constructor(e,t={}){this.storageWebId=null;this.errorQueue=[];this.batchTimer=null;this.isInitialized=!1;this.originalOnError=null;this.originalOnUnhandledRejection=null;this.http=e,this.config={autoCapture:t.autoCapture??!0,captureTypes:t.captureTypes??["error","unhandledrejection"],batchInterval:t.batchInterval??5e3,maxBatchSize:t.maxBatchSize??10,beforeSend:t.beforeSend??(s=>s),debug:t.debug??!1}}init(e){if(this.isInitialized){this.log("ErrorTracker already initialized");return}if(typeof window>"u"){this.log("ErrorTracker only works in browser environment");return}this.storageWebId=e,this.isInitialized=!0,this.config.autoCapture&&this.setupAutoCapture(),this.startBatchTimer(),this.log("ErrorTracker initialized",{storageWebId:e})}destroy(){this.stopBatchTimer(),this.removeAutoCapture(),this.flushQueue(),this.isInitialized=!1,this.log("ErrorTracker destroyed")}async captureError(e,t){let s=this.createErrorReport(e,t);s&&this.queueError(s)}async captureMessage(e,t){let s={message:e,error_type:"custom",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t};this.queueError(s)}async flush(){await this.flushQueue()}log(...e){this.config.debug&&console.log("[ErrorTracker]",...e)}setupAutoCapture(){typeof window>"u"||(this.config.captureTypes.includes("error")&&(this.originalOnError=window.onerror,window.onerror=(e,t,s,r,i)=>(this.handleGlobalError(e,t,s,r,i),this.originalOnError?this.originalOnError(e,t,s,r,i):!1)),this.config.captureTypes.includes("unhandledrejection")&&(this.originalOnUnhandledRejection=window.onunhandledrejection,window.onunhandledrejection=e=>{this.handleUnhandledRejection(e),this.originalOnUnhandledRejection&&this.originalOnUnhandledRejection(e)}),this.log("Auto capture enabled",{types:this.config.captureTypes}))}removeAutoCapture(){typeof window>"u"||(this.originalOnError!==null&&(window.onerror=this.originalOnError),this.originalOnUnhandledRejection!==null&&(window.onunhandledrejection=this.originalOnUnhandledRejection))}handleGlobalError(e,t,s,r,i){let n={message:typeof e=="string"?e:e.type||"Unknown error",source:t||void 0,lineno:s||void 0,colno:r||void 0,stack:i?.stack,error_type:"error",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0};this.queueError(n)}handleUnhandledRejection(e){let t=e.reason,s="Unhandled Promise Rejection",r;t instanceof Error?(s=t.message,r=t.stack):typeof t=="string"?s=t:t&&typeof t=="object"&&(s=JSON.stringify(t));let i={message:s,stack:r,error_type:"unhandledrejection",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0};this.queueError(i)}createErrorReport(e,t){let s;e instanceof Error?s={message:e.message,stack:e.stack,error_type:"error",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t}:s={message:e,error_type:"custom",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t};let r=this.config.beforeSend(s);return r===!1||r===null?(this.log("Error filtered out by beforeSend"),null):r}queueError(e){this.errorQueue.push(e),this.log("Error queued",{message:e.message,queueSize:this.errorQueue.length}),this.errorQueue.length>=this.config.maxBatchSize&&this.flushQueue()}startBatchTimer(){this.batchTimer||(this.batchTimer=setInterval(()=>{this.flushQueue()},this.config.batchInterval))}stopBatchTimer(){this.batchTimer&&(clearInterval(this.batchTimer),this.batchTimer=null)}async flushQueue(){if(!this.storageWebId||this.errorQueue.length===0)return;let e=[...this.errorQueue];this.errorQueue=[];try{e.length===1?await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/report`,e[0]):await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/batch`,{errors:e,user_agent:typeof navigator<"u"?navigator.userAgent:void 0}),this.log("Errors sent",{count:e.length})}catch(t){let s=this.config.maxBatchSize-this.errorQueue.length;s>0&&this.errorQueue.unshift(...e.slice(0,s)),this.log("Failed to send errors, re-queued",{error:t})}}};var $=class{constructor(e){this.http=e}async getEnabledProviders(){return this.http.get("/v1/public/oauth/providers")}async signIn(e,t,s){let r=new URLSearchParams({app_callback:t});s&&r.append("state",s);let i=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${r.toString()}`);window.location.href=i.authorization_url}async signInWithPopup(e,t){let s=new URLSearchParams({app_callback:t}),r=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${s.toString()}`),i=500,n=600,o=window.screenX+(window.outerWidth-i)/2,c=window.screenY+(window.outerHeight-n)/2,h=window.open(r.authorization_url,"oauth-popup",`width=${i},height=${n},left=${o},top=${c}`);if(!h)throw new Error("\uD31D\uC5C5\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD31D\uC5C5 \uCC28\uB2E8\uC744 \uD574\uC81C\uD574\uC8FC\uC138\uC694.");return new Promise((m,F)=>{let p=async l=>{if(l.data?.type!=="oauth-callback")return;if(window.removeEventListener("message",p),l.data.error){F(new Error(l.data.error));return}let v={member_id:l.data.member_id,access_token:l.data.access_token,refresh_token:l.data.refresh_token,is_new_member:l.data.is_new_member==="true"||l.data.is_new_member===!0};this.http.setTokens(v.access_token,v.refresh_token),m(v)};window.addEventListener("message",p);let w=setInterval(()=>{h.closed&&(clearInterval(w),window.removeEventListener("message",p),F(new Error("\uB85C\uADF8\uC778\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.")))},500)})}getCallbackResult(){let e=new URLSearchParams(window.location.search),t=e.get("error");if(t){let o={error:t};return window.opener&&(window.opener.postMessage({type:"oauth-callback",...o},"*"),window.close()),o}let s=e.get("access_token"),r=e.get("refresh_token"),i=e.get("member_id");if(!s||!r||!i)return null;let n={access_token:s,refresh_token:r,member_id:i,is_new_member:e.get("is_new_member")==="true",state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...n},"*"),window.close(),n):(this.http.setTokens(s,r),n)}};var I=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async prepare(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/prepare`,e)}async confirm(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/confirm`,e)}async cancel(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/payments/${e}/cancel`,t)}async getByOrderId(e){let t=this.getPublicPrefix();return this.http.get(`${t}/payments/orders/${e}`)}};var M=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async issueBillingKey(){let e=this.getPublicPrefix();return this.http.post(`${e}/subscriptions/billing-keys/issue`,{})}async confirmBillingKey(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/billing-keys/confirm`,e)}async listBillingKeys(e){let t=this.getPublicPrefix(),s=e?`?customer_id=${e}`:"";return this.http.get(`${t}/subscriptions/billing-keys${s}`)}async getBillingKey(e){let t=this.getPublicPrefix();return this.http.get(`${t}/subscriptions/billing-keys/${e}`)}async updateBillingKey(e,t){let s=this.getPublicPrefix();return this.http.patch(`${s}/subscriptions/billing-keys/${e}`,t)}async deleteBillingKey(e){let t=this.getPublicPrefix();return this.http.delete(`${t}/subscriptions/billing-keys/${e}`)}async create(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions`,e)}async list(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.status&&s.set("status",e.status),e?.limit&&s.set("limit",String(e.limit)),e?.offset&&s.set("offset",String(e.offset));let r=s.toString();return this.http.get(`${t}/subscriptions${r?"?"+r:""}`)}async get(e){let t=this.getPublicPrefix();return this.http.get(`${t}/subscriptions/${e}`)}async update(e,t){let s=this.getPublicPrefix();return this.http.patch(`${s}/subscriptions/${e}`,t)}async pause(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/subscriptions/${e}/pause`,t||{})}async resume(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/${e}/resume`,{})}async cancel(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/subscriptions/${e}/cancel`,t)}async listPayments(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;t?.status&&r.set("status",t.status),t?.limit&&r.set("limit",String(t.limit)),t?.offset&&r.set("offset",String(t.offset));let i=r.toString();return this.http.get(`${s}/subscriptions/${e}/payments${i?"?"+i:""}`)}async chargeWithBillingKey(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/charge`,e)}};var A=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async registerDevice(e){let t=this.getPublicPrefix();return this.http.post(`${t}/push/devices`,e)}async unregisterDevice(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${e}`)}async getDevices(){let e=this.getPublicPrefix();return(await this.http.get(`${e}/push/devices`)).devices||[]}async subscribeTopic(e){let t=this.getPublicPrefix(),s={topic_name:e};await this.http.post(`${t}/push/topics/subscribe`,s)}async unsubscribeTopic(e){let t=this.getPublicPrefix(),s={topic_name:e};await this.http.post(`${t}/push/topics/unsubscribe`,s)}async getSubscribedTopics(){let e=this.getPublicPrefix();return(await this.http.get(`${e}/push/topics/subscribed`)).topics||[]}async getVAPIDPublicKey(){let e=this.getPublicPrefix();return this.http.get(`${e}/push/vapid-key`)}async registerWebPush(e){let t=this.getPublicPrefix(),s;if("toJSON"in e){let i=e.toJSON();s={endpoint:i.endpoint||"",expirationTime:i.expirationTime,keys:{p256dh:i.keys?.p256dh||"",auth:i.keys?.auth||""}}}else s=e;let r={device_token:s.endpoint,platform:"web",device_id:this.generateDeviceId(),device_name:this.getBrowserName(),os_version:this.getOSInfo()};return this.http.post(`${t}/push/devices/web`,{...r,web_push_subscription:s})}async unregisterWebPush(){let e=this.getPublicPrefix();await this.http.delete(`${e}/push/devices/web`)}generateDeviceId(){if(typeof window>"u"||typeof localStorage>"u")return`device_${Date.now()}_${Math.random().toString(36).substr(2,9)}`;let e="cb_push_device_id",t=localStorage.getItem(e);return t||(t=`web_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,localStorage.setItem(e,t)),t}getBrowserName(){if(typeof navigator>"u")return"Unknown Browser";let e=navigator.userAgent;return e.includes("Chrome")&&!e.includes("Edg")?"Chrome":e.includes("Safari")&&!e.includes("Chrome")?"Safari":e.includes("Firefox")?"Firefox":e.includes("Edg")?"Edge":e.includes("Opera")||e.includes("OPR")?"Opera":"Unknown Browser"}getOSInfo(){if(typeof navigator>"u")return"Unknown OS";let e=navigator.userAgent;return e.includes("Windows")?"Windows":e.includes("Mac OS")?"macOS":e.includes("Linux")?"Linux":e.includes("Android")?"Android":e.includes("iOS")||e.includes("iPhone")||e.includes("iPad")?"iOS":"Unknown OS"}};var re=5*1024*1024,f=class extends Error{constructor(e,t){super(e),this.name="VideoProcessingError",this.video=t}},U=class{constructor(e,t){this.http=e;this.videoBaseUrl=t||this.getDefaultVideoUrl()}getDefaultVideoUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"http://localhost:8089"}return"https://video.connectbase.world"}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async videoFetch(e,t,s){let r={},i=this.http.getApiKey();i&&(r["X-API-Key"]=i);let n=this.http.getAccessToken();n&&(r.Authorization=`Bearer ${n}`),s&&!(s instanceof FormData)&&(r["Content-Type"]="application/json");let o=await fetch(`${this.videoBaseUrl}${t}`,{method:e,headers:r,body:s instanceof FormData?s:s?JSON.stringify(s):void 0});if(!o.ok){let c=await o.json().catch(()=>({message:o.statusText}));throw new d(o.status,c.message||"Unknown error")}return o.status===204||o.headers.get("content-length")==="0"?{}:o.json()}async upload(e,t){let s=this.getPublicPrefix(),r=await this.videoFetch("POST",`${s}/uploads`,{filename:e.name,size:e.size,mime_type:e.type,title:t.title,description:t.description,visibility:t.visibility||"private",tags:t.tags,channel_id:t.channel_id}),i=r.chunk_size||re,n=Math.ceil(e.size/i),o=0,h=Date.now(),m=0;for(let p=0;p<n;p++){let w=p*i,l=Math.min(w+i,e.size),v=e.slice(w,l),L=new FormData;L.append("chunk",v),L.append("chunk_index",String(p)),await this.videoFetch("POST",`${s}/uploads/${r.session_id}/chunks`,L),o++;let W=Date.now(),B=(W-h)/1e3,V=l,J=V-m,z=B>0?J/B:0;h=W,m=V,t.onProgress&&t.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:z})}return(await this.videoFetch("POST",`${s}/uploads/${r.session_id}/complete`,{})).video}async waitForReady(e,t){let s=t?.timeout||18e5,r=t?.interval||5e3,i=Date.now(),n=this.getPublicPrefix();for(;Date.now()-i<s;){let o=await this.videoFetch("GET",`${n}/videos/${e}`);if(o.status==="ready")return o;if(o.status==="failed")throw new f("Video processing failed",o);if(t?.onProgress){let c=o.qualities.filter(m=>m.status==="ready").length,h=o.qualities.length||1;t.onProgress({phase:"processing",uploadedChunks:0,totalChunks:0,percentage:Math.round(c/h*100)})}await new Promise(c=>setTimeout(c,r))}throw new f("Timeout waiting for video to be ready")}async list(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.status&&s.set("status",e.status),e?.visibility&&s.set("visibility",e.visibility),e?.search&&s.set("search",e.search),e?.channel_id&&s.set("channel_id",e.channel_id),e?.page&&s.set("page",String(e.page)),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/videos${r?`?${r}`:""}`)}async get(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/videos/${e}`)}async update(e,t){let s=this.getPublicPrefix();return this.videoFetch("PATCH",`${s}/videos/${e}`,t)}async delete(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/videos/${e}`)}async getStreamUrl(e,t){let s=this.getPublicPrefix(),r=t?`?quality=${t}`:"";return this.videoFetch("GET",`${s}/videos/${e}/stream-url${r}`)}async getThumbnails(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/videos/${e}/thumbnails`)).thumbnails}async getTranscodeStatus(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/videos/${e}/transcode/status`)}async retryTranscode(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/videos/${e}/transcode/retry`,{})}async createChannel(e){let t=this.getPublicPrefix();return this.videoFetch("POST",`${t}/channels`,e)}async getChannel(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/channels/${e}`)}async getChannelByHandle(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/channels/handle/${e}`)}async updateChannel(e,t){let s=this.getPublicPrefix();return this.videoFetch("PATCH",`${s}/channels/${e}`,t)}async subscribeChannel(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/channels/${e}/subscribe`,{})}async unsubscribeChannel(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/channels/${e}/subscribe`)}async createPlaylist(e,t){let s=this.getPublicPrefix();return this.videoFetch("POST",`${s}/channels/${e}/playlists`,t)}async getPlaylists(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/channels/${e}/playlists`)).playlists}async getPlaylistItems(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/playlists/${e}/items`)).items}async addToPlaylist(e,t,s){let r=this.getPublicPrefix();return this.videoFetch("POST",`${r}/playlists/${e}/items`,{video_id:t,position:s})}async removeFromPlaylist(e,t){let s=this.getPublicPrefix();await this.videoFetch("DELETE",`${s}/playlists/${e}/items/${t}`)}async getShortsFeed(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.cursor&&s.set("cursor",e.cursor),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/shorts${r?`?${r}`:""}`)}async getTrendingShorts(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return this.videoFetch("GET",`${t}/shorts/trending${s}`)}async getShorts(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/shorts/${e}`)}async getComments(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;t?.cursor&&r.set("cursor",t.cursor),t?.limit&&r.set("limit",String(t.limit)),t?.sort&&r.set("sort",t.sort);let i=r.toString();return this.videoFetch("GET",`${s}/videos/${e}/comments${i?`?${i}`:""}`)}async postComment(e,t,s){let r=this.getPublicPrefix();return this.videoFetch("POST",`${r}/videos/${e}/comments`,{content:t,parent_id:s})}async deleteComment(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/comments/${e}`)}async likeVideo(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/videos/${e}/like`,{})}async unlikeVideo(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/videos/${e}/like`)}async getWatchHistory(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.cursor&&s.set("cursor",e.cursor),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/watch-history${r?`?${r}`:""}`)}async clearWatchHistory(){let e=this.getPublicPrefix();await this.videoFetch("DELETE",`${e}/watch-history`)}async reportWatchProgress(e,t,s){let r=this.getPublicPrefix();await this.videoFetch("POST",`${r}/videos/${e}/watch-progress`,{position:t,duration:s})}async getMembershipTiers(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/channels/${e}/memberships/tiers`)).tiers}async joinMembership(e,t){let s=this.getPublicPrefix();return this.videoFetch("POST",`${s}/channels/${e}/memberships/${t}/join`,{})}async cancelMembership(e,t){let s=this.getPublicPrefix();await this.videoFetch("POST",`${s}/channels/${e}/memberships/${t}/cancel`,{})}async sendSuperChat(e,t,s,r){let i=this.getPublicPrefix();return this.videoFetch("POST",`${i}/videos/${e}/super-chats`,{amount:t,message:s,currency:r||"USD"})}async getSuperChats(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/videos/${e}/super-chats`)).super_chats}async getRecommendations(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations${s}`)).videos}async getHomeFeed(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations/home${s}`)).videos}async getRelatedVideos(e,t){let s=this.getPublicPrefix(),r=t?`?limit=${t}`:"";return(await this.videoFetch("GET",`${s}/recommendations/related/${e}${r}`)).videos}async getTrendingVideos(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations/trending${s}`)).videos}async submitFeedback(e,t){let s=this.getPublicPrefix();await this.videoFetch("POST",`${s}/recommendations/feedback`,{video_id:e,feedback:t})}};var j=()=>{if(typeof window<"u"){let a=window.location.hostname;if(a==="localhost"||a==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"},b=class{constructor(e){this.ws=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this.config={gameServerUrl:j(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,...e}}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}on(e,t){return this.handlers[e]=t,this}connect(e){return new Promise((t,s)=>{if(this.ws?.readyState===WebSocket.OPEN){t();return}let r=this.buildConnectionUrl(e);this.ws=new WebSocket(r);let i=()=>{this._isConnected=!0,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),t()},n=h=>{this._isConnected=!1,this.stopPingInterval(),this.handlers.onDisconnect?.(h),this.config.autoReconnect&&h.code!==1e3&&this.scheduleReconnect(e)},o=h=>{this.handlers.onError?.(h),s(new Error("WebSocket connection failed"))},c=h=>{this.handleMessage(h.data)};this.ws.addEventListener("open",i,{once:!0}),this.ws.addEventListener("close",n),this.ws.addEventListener("error",o,{once:!0}),this.ws.addEventListener("message",c)})}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null),this._isConnected=!1,this._roomId=null}createRoom(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else i.type==="error"&&s(new Error(i.data.message))};this.sendWithHandler("create_room",e,r)})}joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&r(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,e()):r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e){if(!this._roomId)throw new Error("Not in a room");this.send("action",{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++})}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{if(r.type==="state"){let i=r.data;this._state=i,e(i)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("get_state",{},s)})}listRooms(){return new Promise((e,t)=>{let s=r=>{if(r.type==="room_list"){let i=r.data;e(i.rooms)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("list_rooms",{},s)})}ping(){return new Promise((e,t)=>{let s=Date.now(),r=i=>{if(i.type==="pong"){let n=i.data,o=Date.now()-n.clientTimestamp;this.handlers.onPong?.(n),e(o)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("ping",{timestamp:s},r)})}buildConnectionUrl(e){let s=this.config.gameServerUrl.replace(/^http/,"ws"),r=new URLSearchParams;return r.set("client_id",this.config.clientId),e&&r.set("room_id",e),this.config.apiKey&&r.set("api_key",this.config.apiKey),this.config.accessToken&&r.set("token",this.config.accessToken),`${s}/v1/game/ws?${r.toString()}`}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("WebSocket is not connected");this.ws.send(JSON.stringify({type:e,data:t}))}sendWithHandler(e,t,s){let r=i=>{try{let n=JSON.parse(i.data);s(n),this.ws?.removeEventListener("message",r)}catch{}};this.ws?.addEventListener("message",r),this.send(e,t)}handleMessage(e){try{let t=JSON.parse(e);switch(t.type){case"delta":this.handleDelta(t.data);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t.data);break;case"chat":this.handlers.onChat?.(t.data);break;case"error":this.handlers.onError?.(t.data);break;default:break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(this._state){for(let s of t.changes)this.applyChange(s);this._state.version=t.toVersion}this.handlers.onDelta?.(t)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let i=0;i<t.length-1;i++){let n=t[i];n in s||(s[n]={}),s=s[n]}let r=t[t.length-1];e.operation==="delete"?delete s[r]:s[r]=e.value}handlePlayerEvent(e){e.event==="joined"?this.handlers.onPlayerJoined?.(e.player):e.event==="left"&&this.handlers.onPlayerLeft?.(e.player)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached");return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`),this.connect(e||this._roomId||void 0).catch(()=>{})},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}},y=class{constructor(e,t){this.http=e,this.gameServerUrl=t||j().replace(/^ws/,"http")}createClient(e){return new b({...e,gameServerUrl:this.gameServerUrl.replace(/^http/,"ws"),apiKey:this.http.getApiKey(),accessToken:this.http.getAccessToken()})}async listRooms(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms${e?`?app_id=${e}`:""}`,{headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to list rooms: ${t.statusText}`);return(await t.json()).rooms}async getRoom(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms/${e}`,{headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to get room: ${t.statusText}`);return t.json()}async createRoom(e,t={}){let s=await fetch(`${this.gameServerUrl}/v1/game/rooms`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({app_id:e,category_id:t.categoryId,room_id:t.roomId,tick_rate:t.tickRate,max_players:t.maxPlayers,metadata:t.metadata})});if(!s.ok)throw new Error(`Failed to create room: ${s.statusText}`);return s.json()}async deleteRoom(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms/${e}`,{method:"DELETE",headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to delete room: ${t.statusText}`)}getHeaders(){let e={},t=this.http.getApiKey();t&&(e["X-API-Key"]=t);let s=this.http.getAccessToken();return s&&(e.Authorization=`Bearer ${s}`),e}};var O=class{constructor(e,t,s,r){this.type="webtransport";this.transport=null;this.writer=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=r}async connect(){let e=this.buildUrl();this.transport=new WebTransport(e),await this.transport.ready,this.config.useUnreliableDatagrams!==!1&&this.readDatagrams();let t=await this.transport.createBidirectionalStream();this.writer=t.writable.getWriter(),this.readStream(t.readable),this.transport.closed.then(()=>{this.onClose()}).catch(s=>{this.onError(s)})}buildUrl(){let t=(this.config.gameServerUrl||"https://game.connectbase.world").replace(/^ws/,"http").replace(/^http:/,"https:"),s=new URLSearchParams;return s.set("client_id",this.config.clientId),this.config.apiKey&&s.set("api_key",this.config.apiKey),this.config.accessToken&&s.set("token",this.config.accessToken),`${t}/v1/game/webtransport?${s.toString()}`}async readDatagrams(){if(!this.transport)return;let e=this.transport.datagrams.readable.getReader();try{for(;;){let{value:t,done:s}=await e.read();if(s)break;this.onMessage(t)}}catch{}}async readStream(e){let t=e.getReader(),s=new Uint8Array(0);try{for(;;){let{value:r,done:i}=await t.read();if(i)break;let n=new Uint8Array(s.length+r.length);for(n.set(s),n.set(r,s.length),s=n;s.length>=4;){let o=new DataView(s.buffer).getUint32(0,!0);if(s.length<4+o)break;let c=s.slice(4,4+o);s=s.slice(4+o),this.onMessage(c)}}}catch{}}disconnect(){this.transport&&(this.transport.close(),this.transport=null,this.writer=null)}send(e,t=!0){if(!this.transport)throw new Error("Not connected");let s=typeof e=="string"?new TextEncoder().encode(e):e;if(t){if(this.writer){let r=new Uint8Array(4);new DataView(r.buffer).setUint32(0,s.length,!0);let i=new Uint8Array(4+s.length);i.set(r),i.set(s,4),this.writer.write(i)}}else{let r=this.config.maxDatagramSize||1200;s.length<=r?this.transport.datagrams.writable.getWriter().write(s):(console.warn("Datagram too large, falling back to reliable stream"),this.send(e,!0))}}isConnected(){return this.transport!==null}},G=class{constructor(e,t,s,r){this.type="websocket";this.ws=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=r}connect(){return new Promise((e,t)=>{let s=this.buildUrl();try{this.ws=new WebSocket(s),this.ws.binaryType="arraybuffer"}catch(c){t(c);return}let r=()=>{e()},i=()=>{this.onClose()},n=c=>{let h=new Error("WebSocket error");this.onError(h),t(h)},o=c=>{c.data instanceof ArrayBuffer?this.onMessage(new Uint8Array(c.data)):typeof c.data=="string"&&this.onMessage(new TextEncoder().encode(c.data))};this.ws.addEventListener("open",r,{once:!0}),this.ws.addEventListener("close",i),this.ws.addEventListener("error",n,{once:!0}),this.ws.addEventListener("message",o)})}buildUrl(){let t=(this.config.gameServerUrl||"wss://game.connectbase.world").replace(/^http/,"ws"),s=new URLSearchParams;return s.set("client_id",this.config.clientId),this.config.apiKey&&s.set("api_key",this.config.apiKey),this.config.accessToken&&s.set("token",this.config.accessToken),`${t}/v1/game/ws?${s.toString()}`}disconnect(){this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null)}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function K(){return typeof WebTransport<"u"}var q=class{constructor(e){this.transport=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this._connectionStatus="disconnected";this._lastError=null;this._latency=0;this._transportType="websocket";this.decoder=new TextDecoder;this.pendingHandlers=new Map;this.messageId=0;this.config={gameServerUrl:this.getDefaultGameServerUrl(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,connectionTimeout:1e4,transport:"auto",useUnreliableDatagrams:!0,...e}}getDefaultGameServerUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"}get transportType(){return this._transportType}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}get connectionState(){return{status:this._connectionStatus,transport:this._transportType==="auto"?null:this._transportType,roomId:this._roomId,latency:this._latency,reconnectAttempt:this.reconnectAttempts,lastError:this._lastError||void 0}}get latency(){return this._latency}on(e,t){return this.handlers[e]=t,this}async connect(e){if(this.transport?.isConnected())return;this._connectionStatus=this.reconnectAttempts>0?"reconnecting":"connecting";let t=this.config.transport||"auto",s=(t==="webtransport"||t==="auto")&&K(),r=o=>{this.handleMessage(this.decoder.decode(o))},i=()=>{this._isConnected=!1,this._connectionStatus="disconnected",this.stopPingInterval(),this.handlers.onDisconnect?.(new CloseEvent("close")),this.config.autoReconnect&&(this._connectionStatus="reconnecting",this.scheduleReconnect(e))},n=o=>{this._connectionStatus="error",this._lastError=o,this.handlers.onError?.(o)};if(s)try{this.transport=new O(this.config,r,i,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new G(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new G(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket";this._isConnected=!0,this._connectionStatus="connected",this._lastError=null,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),e&&await this.joinRoom(e)}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.transport&&(this.transport.disconnect(),this.transport=null),this._isConnected=!1,this._connectionStatus="disconnected",this._roomId=null,this._state=null}async createRoom(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else i.type==="error"&&s(new Error(i.data.message))};this.sendWithHandler("create_room",e,r)})}async joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&r(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,e()):r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new Error("Not in a room");let s=JSON.stringify({type:"action",data:{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++}}),r=t||this._transportType!=="webtransport";this.transport?.send(s,r)}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{if(r.type==="state"){let i=r.data;this._state=i,e(i)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("get_state",{},s)})}async ping(){return new Promise((e,t)=>{let s=Date.now(),r=i=>{if(i.type==="pong"){let n=i.data,o=Date.now()-n.clientTimestamp;this._latency=o,this.handlers.onPong?.(n),e(o)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("ping",{timestamp:s},r)})}send(e,t){if(!this.transport?.isConnected())throw new Error("Not connected");let s=JSON.stringify({type:e,data:t});this.transport.send(s,!0)}sendWithHandler(e,t,s){let r=`msg_${this.messageId++}`;this.pendingHandlers.set(r,s),setTimeout(()=>{this.pendingHandlers.delete(r)},1e4),this.send(e,{...t,_msg_id:r})}handleMessage(e){try{let t=JSON.parse(e);if(t._msg_id&&this.pendingHandlers.has(t._msg_id)){let s=this.pendingHandlers.get(t._msg_id);this.pendingHandlers.delete(t._msg_id),s(t);return}switch(t.type){case"delta":this.handleDelta(t.data);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t.data);break;case"chat":this.handlers.onChat?.(t.data);break;case"error":this.handlers.onError?.(t.data);break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(this._state){for(let s of t.changes)this.applyChange(s);this._state.version=t.toVersion}this.handlers.onDelta?.(t)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let i=0;i<t.length-1;i++){let n=t[i];n in s||(s[n]={}),s=s[n]}let r=t[t.length-1];e.operation==="delete"?delete s[r]:s[r]=e.value}handlePlayerEvent(e){e.event==="joined"?this.handlers.onPlayerJoined?.(e.player):e.event==="left"&&this.handlers.onPlayerLeft?.(e.player)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached");return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`),this.connect(e||this._roomId||void 0).catch(()=>{})},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}};var ie="https://api.connectbase.world",ne="https://socket.connectbase.world",oe="https://webrtc.connectbase.world",ae="https://video.connectbase.world",ce="https://game.connectbase.world",D=class{constructor(e={}){let t={baseUrl:e.baseUrl||ie,apiKey:e.apiKey,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError};this.http=new P(t),this.auth=new S(this.http),this.database=new R(this.http),this.storage=new T(this.http),this.apiKey=new C(this.http),this.functions=new _(this.http),this.realtime=new k(this.http,e.socketUrl||ne),this.webrtc=new E(this.http,e.webrtcUrl||oe),this.errorTracker=new x(this.http,e.errorTracker),this.oauth=new $(this.http),this.payment=new I(this.http),this.subscription=new M(this.http),this.push=new A(this.http),this.video=new U(this.http,e.videoUrl||ae),this.game=new y(this.http,e.gameUrl||ce)}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},he=D;return te(le);})();
|
|
1
|
+
"use strict";var ConnectBaseModule=(()=>{var O=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(a,e)=>{for(var t in e)O(a,t,{get:e[t],enumerable:!0})},te=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Y(e))!Z.call(a,r)&&r!==t&&O(a,r,{get:()=>e[r],enumerable:!(s=X(e,r))||s.enumerable});return a};var se=a=>te(O({},"__esModule",{value:!0}),a);var pe={};ee(pe,{AdsAPI:()=>v,ApiError:()=>d,AuthError:()=>u,ConnectBase:()=>F,GameAPI:()=>y,GameRoom:()=>P,GameRoomTransport:()=>D,VideoProcessingError:()=>f,default:()=>le,isWebTransportSupported:()=>W});var d=class extends Error{constructor(t,s){super(s);this.statusCode=t;this.name="ApiError"}},u=class extends Error{constructor(e){super(e),this.name="AuthError"}};var S=class{constructor(e){this.isRefreshing=!1;this.refreshPromise=null;this.config=e}updateConfig(e){this.config={...this.config,...e}}setTokens(e,t){this.config.accessToken=e,this.config.refreshToken=t}clearTokens(){this.config.accessToken=void 0,this.config.refreshToken=void 0}hasApiKey(){return!!this.config.apiKey}getApiKey(){return this.config.apiKey}getAccessToken(){return this.config.accessToken}getBaseUrl(){return this.config.baseUrl}async refreshAccessToken(){if(this.isRefreshing)return this.refreshPromise;if(this.isRefreshing=!0,!this.config.refreshToken){this.isRefreshing=!1;let e=new u("Refresh token is missing. Please login again.");throw this.config.onAuthError?.(e),e}return this.refreshPromise=(async()=>{try{let e=await fetch(`${this.config.baseUrl}/v1/auth/re-issue`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.refreshToken}`}});if(!e.ok)throw new Error("Token refresh failed");let t=await e.json();return this.config.accessToken=t.access_token,this.config.refreshToken=t.refresh_token,this.config.onTokenRefresh?.({accessToken:t.access_token,refreshToken:t.refresh_token}),t.access_token}catch{this.clearTokens();let e=new u("Token refresh failed. Please login again.");throw this.config.onAuthError?.(e),e}finally{this.isRefreshing=!1,this.refreshPromise=null}})(),this.refreshPromise}isTokenExpired(e){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s+300}catch{return!0}}async prepareHeaders(e){let t=new Headers;if(t.set("Content-Type","application/json"),this.config.apiKey&&t.set("X-API-Key",this.config.apiKey),!e?.skipAuth&&this.config.accessToken){let s=this.config.accessToken;if(this.isTokenExpired(s)&&this.config.refreshToken){let r=await this.refreshAccessToken();r&&(s=r)}t.set("Authorization",`Bearer ${s}`)}return e?.headers&&Object.entries(e.headers).forEach(([s,r])=>{t.set(s,r)}),t}async handleResponse(e){if(!e.ok){let t=await e.json().catch(()=>({message:e.statusText}));throw new d(e.status,t.message||t.error||"Unknown error")}return e.status===204||e.headers.get("content-length")==="0"?{}:e.json()}async get(e,t){let s=await this.prepareHeaders(t),r=await fetch(`${this.config.baseUrl}${e}`,{method:"GET",headers:s});return this.handleResponse(r)}async post(e,t,s){let r=await this.prepareHeaders(s);t instanceof FormData&&r.delete("Content-Type");let i=await fetch(`${this.config.baseUrl}${e}`,{method:"POST",headers:r,body:t instanceof FormData?t:JSON.stringify(t)});return this.handleResponse(i)}async put(e,t,s){let r=await this.prepareHeaders(s),i=await fetch(`${this.config.baseUrl}${e}`,{method:"PUT",headers:r,body:JSON.stringify(t)});return this.handleResponse(i)}async patch(e,t,s){let r=await this.prepareHeaders(s),i=await fetch(`${this.config.baseUrl}${e}`,{method:"PATCH",headers:r,body:JSON.stringify(t)});return this.handleResponse(i)}async delete(e,t){let s=await this.prepareHeaders(t),r=await fetch(`${this.config.baseUrl}${e}`,{method:"DELETE",headers:s});return this.handleResponse(r)}};var j="cb_guest_";function g(a){typeof window>"u"||(a?typeof window.__cbSetMember=="function"&&window.__cbSetMember(a):typeof window.__cbClearMember=="function"&&window.__cbClearMember())}function re(a){let e=0;for(let t=0;t<a.length;t++){let s=a.charCodeAt(t);e=(e<<5)-e+s,e=e&e}return Math.abs(e).toString(36)}var R=class{constructor(e){this.http=e;this.guestMemberLoginPromise=null;this.cachedGuestMemberTokenKey=null}async getAuthSettings(){return this.http.get("/v1/public/auth-settings",{skipAuth:!0})}async signUpMember(e){let t=await this.http.post("/v1/public/app-members/signup",e,{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),g(t.member_id),t}async signInMember(e){let t=await this.http.post("/v1/public/app-members/signin",e,{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),g(t.member_id),t}async signInAsGuestMember(){if(this.guestMemberLoginPromise)return this.guestMemberLoginPromise;this.guestMemberLoginPromise=this.executeGuestMemberLogin();try{return await this.guestMemberLoginPromise}finally{this.guestMemberLoginPromise=null}}async signOut(){try{await this.http.post("/v1/auth/logout")}finally{this.http.clearTokens(),g(null)}}clearGuestMemberTokens(){typeof localStorage>"u"||localStorage.removeItem(this.getGuestMemberTokenKey())}async executeGuestMemberLogin(){let e=this.getStoredGuestMemberTokens();if(e){if(!this.isTokenExpired(e.accessToken))try{this.http.setTokens(e.accessToken,e.refreshToken);let s=await this.http.get("/v1/public/app-members/me");if(s.is_active)return g(s.member_id),{member_id:s.member_id,access_token:e.accessToken,refresh_token:e.refreshToken};this.clearGuestMemberTokens()}catch{this.http.clearTokens()}if(e.refreshToken&&!this.isTokenExpired(e.refreshToken))try{let s=await this.http.post("/v1/auth/re-issue",{},{headers:{Authorization:`Bearer ${e.refreshToken}`},skipAuth:!0});return this.http.setTokens(s.access_token,s.refresh_token),this.storeGuestMemberTokens(s.access_token,s.refresh_token,e.memberId),g(e.memberId),{member_id:e.memberId,access_token:s.access_token,refresh_token:s.refresh_token}}catch{this.clearGuestMemberTokens()}else this.clearGuestMemberTokens()}let t=await this.http.post("/v1/public/app-members",{},{skipAuth:!0});return this.http.setTokens(t.access_token,t.refresh_token),this.storeGuestMemberTokens(t.access_token,t.refresh_token,t.member_id),g(t.member_id),t}isTokenExpired(e){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s}catch{return!0}}getGuestMemberTokenKey(){if(this.cachedGuestMemberTokenKey)return this.cachedGuestMemberTokenKey;let e=this.http.getApiKey();if(!e)this.cachedGuestMemberTokenKey=`${j}default`;else{let t=re(e);this.cachedGuestMemberTokenKey=`${j}${t}`}return this.cachedGuestMemberTokenKey}getStoredGuestMemberTokens(){if(typeof localStorage>"u")return null;let e=localStorage.getItem(this.getGuestMemberTokenKey());if(!e)return null;try{return JSON.parse(e)}catch{return null}}storeGuestMemberTokens(e,t,s){typeof localStorage>"u"||localStorage.setItem(this.getGuestMemberTokenKey(),JSON.stringify({accessToken:e,refreshToken:t,memberId:s}))}};var T=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async getTables(e){return(await this.http.get(`/v1/databases/json-databases/${e}/tables`)).tables}async createTable(e,t){return this.http.post(`/v1/databases/json-databases/${e}/tables`,t)}async deleteTable(e,t){await this.http.delete(`/v1/databases/json-databases/${e}/tables/${t}`)}async getColumns(e){return(await this.http.get(`/v1/tables/${e}/columns`)).columns}async createColumn(e,t){return this.http.post(`/v1/tables/${e}/columns`,t)}async updateColumn(e,t,s){return this.http.patch(`/v1/tables/${e}/columns/${t}`,s)}async deleteColumn(e,t){await this.http.delete(`/v1/tables/${e}/columns/${t}`)}async getData(e,t){let s=this.getPublicPrefix();if(t?.where)return this.queryData(e,t);let r=new URLSearchParams;t?.limit&&r.append("limit",t.limit.toString()),t?.offset&&r.append("offset",t.offset.toString());let i=r.toString(),n=i?`${s}/tables/${e}/data?${i}`:`${s}/tables/${e}/data`;return this.http.get(n)}async queryData(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/query`,{where:t.where,order_by:t.orderBy,order_direction:t.orderDirection,limit:t.limit,offset:t.offset})}async getDataById(e,t){let s=this.getPublicPrefix();return this.http.get(`${s}/tables/${e}/data/${t}`)}async createData(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data`,t)}async updateData(e,t,s){let r=this.getPublicPrefix();return this.http.put(`${r}/tables/${e}/data/${t}`,s)}async deleteData(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/tables/${e}/data/${t}`)}async createMany(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/bulk`,{data:t.map(r=>r.data)})}async deleteWhere(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/tables/${e}/data/delete-where`,{where:t})}};var C=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async getFiles(e){let t=this.getPublicPrefix();return(await this.http.get(`${t}/storages/files/${e}/items`)).files}async uploadFile(e,t,s){let r=this.getPublicPrefix(),i=new FormData;return i.append("file",t),s&&i.append("parent_id",s),this.http.post(`${r}/storages/files/${e}/upload`,i)}async uploadFiles(e,t,s){let r=[];for(let i of t){let n=await this.uploadFile(e,i,s);r.push(n)}return r}async createFolder(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/storages/files/${e}/folders`,t)}async deleteFile(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/storages/files/${e}/items/${t}`)}async moveFile(e,t,s){let r=this.getPublicPrefix();await this.http.post(`${r}/storages/files/${e}/items/${t}/move`,s)}async renameFile(e,t,s){let r=this.getPublicPrefix();return this.http.patch(`${r}/storages/files/${e}/items/${t}/rename`,s)}getFileUrl(e){return e.url||null}isImageFile(e){return e.mime_type?.startsWith("image/")||!1}async uploadByPath(e,t,s,r){let i=this.getPublicPrefix(),n=new FormData;n.append("file",s);let o=r?.overwrite!==!1;n.append("overwrite",o.toString());let c=t.startsWith("/")?t.slice(1):t;return this.http.post(`${i}/storages/files/${e}/path/${c}`,n)}async getByPath(e,t){let s=this.getPublicPrefix(),r=t.startsWith("/")?t.slice(1):t;return this.http.get(`${s}/storages/files/${e}/path/${r}`)}async getUrlByPath(e,t){try{return(await this.getByPath(e,t)).url||null}catch{return null}}};var _=class{constructor(e){this.http=e}async getApiKeys(e){return this.http.get(`/v1/apps/${e}/api-keys`)}async createApiKey(e,t){return this.http.post(`/v1/apps/${e}/api-keys`,t)}async updateApiKey(e,t,s){return this.http.patch(`/v1/apps/${e}/api-keys/${t}`,s)}async deleteApiKey(e,t){await this.http.delete(`/v1/apps/${e}/api-keys/${t}`)}};var k=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async invoke(e,t,s){let r=this.getPublicPrefix(),i={};return t!==void 0&&(i.payload=t),s!==void 0&&(i.timeout=s),this.http.post(`${r}/functions/${e}/invoke`,i)}async call(e,t){let s=await this.invoke(e,t);if(!s.success)throw new Error(s.error||"Function execution failed");return s.result}};var E=class{constructor(e,t){this.ws=null;this.state="disconnected";this._connectionId=null;this._appId=null;this.options={maxRetries:5,retryInterval:1e3,userId:"",accessToken:""};this.retryCount=0;this.pendingRequests=new Map;this.subscriptions=new Map;this.streamSessions=new Map;this.stateHandlers=[];this.errorHandlers=[];this.http=e,this.socketUrl=t,this.clientId=this.generateClientId()}get connectionId(){return this._connectionId}get appId(){return this._appId}async connect(e={}){if(!(this.state==="connected"||this.state==="connecting"))return this.options={...this.options,...e},e.userId&&(this.userId=e.userId),this.doConnect()}disconnect(){this.state="disconnected",this.notifyStateChange(),this.ws&&(this.ws.close(),this.ws=null),this.pendingRequests.forEach(e=>{clearTimeout(e.timeout),e.reject(new Error("Connection closed"))}),this.pendingRequests.clear(),this.subscriptions.clear()}async subscribe(e,t={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let s=this.generateRequestId(),r=await this.sendRequest({category:e,action:"subscribe",request_id:s}),i={category:r.category,persist:r.persist,historyCount:r.history_count,readReceipt:r.read_receipt},n=[];return this.subscriptions.set(e,{info:i,handlers:n}),{info:i,send:async(c,h)=>{await this.sendMessage(e,c,h)},getHistory:async c=>this.getHistory(e,c??t.historyLimit),unsubscribe:async()=>{await this.unsubscribe(e)},onMessage:c=>{n.push(c)}}}async unsubscribe(e){if(this.state!=="connected")return;let t=this.generateRequestId();await this.sendRequest({category:e,action:"unsubscribe",request_id:t}),this.subscriptions.delete(e)}async sendMessage(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected");let i=s.includeSelf!==!1,n=this.generateRequestId();await this.sendRequest({category:e,action:"send",data:{data:t,broadcast:i},request_id:n})}async getHistory(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId(),r=await this.sendRequest({category:e,action:"history",data:t?{limit:t}:void 0,request_id:s});return{category:r.category,messages:r.messages.map(i=>({id:i.id,category:i.category,from:i.from,data:i.data,sentAt:i.sent_at})),total:r.total}}async stream(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let r=this.generateRequestId(),i=s.sessionId||this.generateRequestId();return this.streamSessions.set(i,{handlers:t,requestId:r}),this.sendRaw({category:"",action:"stream",data:{provider:s.provider,model:s.model,messages:e,system:s.system,temperature:s.temperature,max_tokens:s.maxTokens,session_id:i,metadata:s.metadata},request_id:r}),{sessionId:i,stop:async()=>{await this.stopStream(i)}}}async stopStream(e){if(this.state!=="connected")return;let t=this.generateRequestId();this.sendRaw({category:"",action:"stream_stop",data:{session_id:e},request_id:t}),this.streamSessions.delete(e)}getState(){return this.state}onStateChange(e){return this.stateHandlers.push(e),()=>{let t=this.stateHandlers.indexOf(e);t>-1&&this.stateHandlers.splice(t,1)}}onError(e){return this.errorHandlers.push(e),()=>{let t=this.errorHandlers.indexOf(e);t>-1&&this.errorHandlers.splice(t,1)}}async doConnect(){return new Promise((e,t)=>{this.state="connecting",this.notifyStateChange();let s=this.socketUrl.replace(/^http/,"ws"),r;if(this.options.accessToken)r=`${s}/v1/realtime/auth?access_token=${encodeURIComponent(this.options.accessToken)}&client_id=${this.clientId}`;else{let i=this.http.getApiKey();if(!i){t(new Error("API Key or accessToken is required for realtime connection"));return}r=`${s}/v1/realtime/auth?api_key=${encodeURIComponent(i)}&client_id=${this.clientId}`}this.userId&&(r+=`&user_id=${encodeURIComponent(this.userId)}`);try{this.ws=new WebSocket(r),this.ws.onopen=()=>{},this.ws.onmessage=i=>{let n=i.data.split(`
|
|
2
|
+
`).filter(o=>o.trim());for(let o of n)try{let c=JSON.parse(o);this.handleServerMessage(c,e)}catch(c){console.error("[Realtime] Failed to parse message:",o,c)}},this.ws.onclose=()=>{(this.state==="connected"||this.state==="connecting")&&this.handleDisconnect()},this.ws.onerror=i=>{console.error("[Realtime] WebSocket error:",i),this.notifyError(new Error("WebSocket connection error")),this.state==="connecting"&&t(new Error("Failed to connect"))}}catch(i){t(i)}})}handleServerMessage(e,t){switch(e.event){case"connected":{let s=e.data;this._connectionId=s.connection_id,this._appId=s.app_id,this.state="connected",this.retryCount=0,this.notifyStateChange(),t&&t();break}case"subscribed":case"unsubscribed":case"sent":case"result":case"history":{if(e.request_id){let s=this.pendingRequests.get(e.request_id);s&&(clearTimeout(s.timeout),s.resolve(e.data),this.pendingRequests.delete(e.request_id))}break}case"message":{let s=e.data,r=this.subscriptions.get(s.category);if(r){let i={id:s.id,category:s.category,from:s.from,data:s.data,sentAt:s.sent_at};r.handlers.forEach(n=>n(i))}break}case"error":{if(e.request_id){let s=this.pendingRequests.get(e.request_id);s&&(clearTimeout(s.timeout),s.reject(new Error(e.error||"Unknown error")),this.pendingRequests.delete(e.request_id))}else this.notifyError(new Error(e.error||"Unknown error"));break}case"pong":break;case"stream_token":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToken&&r.handlers.onToken(s.token,s.index);break}case"stream_done":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onDone&&r.handlers.onDone({sessionId:s.session_id,fullText:s.full_text,totalTokens:s.total_tokens,promptTokens:s.prompt_tokens,duration:s.duration_ms}),this.streamSessions.delete(s.session_id);break}case"stream_error":{let s=e.data;if(e.request_id){for(let[r,i]of this.streamSessions)if(i.requestId===e.request_id){i.handlers.onError&&i.handlers.onError(new Error(s.message)),this.streamSessions.delete(r);break}}break}}}handleDisconnect(){this.ws=null,this._connectionId=null,this.retryCount<this.options.maxRetries?(this.state="reconnecting",this.notifyStateChange(),this.retryCount++,setTimeout(()=>{this.doConnect().catch(e=>{console.error("[Realtime] Reconnect failed:",e)})},this.options.retryInterval*this.retryCount)):(this.state="disconnected",this.notifyStateChange(),this.notifyError(new Error("Connection lost. Max retries exceeded.")))}sendRequest(e){return new Promise((t,s)=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN){s(new Error("Not connected"));return}let r=setTimeout(()=>{this.pendingRequests.delete(e.request_id),s(new Error("Request timeout"))},3e4);this.pendingRequests.set(e.request_id,{resolve:t,reject:s,timeout:r}),this.ws.send(JSON.stringify(e))})}sendRaw(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");this.ws.send(JSON.stringify(e))}notifyStateChange(){this.stateHandlers.forEach(e=>e(this.state))}notifyError(e){this.errorHandlers.forEach(t=>t(e))}generateClientId(){return"cb_"+Math.random().toString(36).substring(2,15)}generateRequestId(){return"req_"+Date.now()+"_"+Math.random().toString(36).substring(2,9)}};var x=class{constructor(e,t,s){this.ws=null;this.state="disconnected";this.stateListeners=[];this.errorListeners=[];this.peerJoinedListeners=[];this.peerLeftListeners=[];this.remoteStreamListeners=[];this.reconnectAttempts=0;this.maxReconnectAttempts=5;this.reconnectTimeout=null;this.currentRoomId=null;this.currentPeerId=null;this.currentUserId=null;this.isBroadcaster=!1;this.localStream=null;this.channelType="interactive";this.peerConnections=new Map;this.remoteStreams=new Map;this.iceServers=[];this.http=e,this.webrtcUrl=t,this.appId=s}async getICEServers(){let e=await this.http.get("/v1/ice-servers");return this.iceServers=e.ice_servers,e.ice_servers}async connect(e){if(this.state==="connected"||this.state==="connecting")throw new Error("\uC774\uBBF8 \uC5F0\uACB0\uB418\uC5B4 \uC788\uAC70\uB098 \uC5F0\uACB0 \uC911\uC785\uB2C8\uB2E4");if(this.setState("connecting"),this.currentRoomId=e.roomId,this.currentUserId=e.userId||null,this.isBroadcaster=e.isBroadcaster||!1,this.localStream=e.localStream||null,this.iceServers.length===0)try{await this.getICEServers()}catch{this.iceServers=[{urls:"stun:stun.l.google.com:19302"}]}return this.connectWebSocket()}connectWebSocket(){return new Promise((e,t)=>{let s=this.buildWebSocketUrl();this.ws=new WebSocket(s);let r=setTimeout(()=>{this.state==="connecting"&&(this.ws?.close(),t(new Error("\uC5F0\uACB0 \uC2DC\uAC04 \uCD08\uACFC")))},1e4);this.ws.onopen=()=>{clearTimeout(r),this.reconnectAttempts=0,this.sendSignaling({type:"join",room_id:this.currentRoomId,data:{user_id:this.currentUserId,is_broadcaster:this.isBroadcaster}})},this.ws.onmessage=async i=>{try{let n=JSON.parse(i.data);await this.handleSignalingMessage(n,e,t)}catch(n){console.error("Failed to parse signaling message:",n)}},this.ws.onerror=i=>{clearTimeout(r),console.error("WebSocket error:",i),this.emitError(new Error("WebSocket \uC5F0\uACB0 \uC624\uB958"))},this.ws.onclose=i=>{clearTimeout(r),this.state==="connecting"&&t(new Error("\uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4")),this.handleDisconnect(i)}})}buildWebSocketUrl(){let e=this.webrtcUrl.replace("https://","wss://").replace("http://","ws://"),t=this.http.getApiKey(),s=this.http.getAccessToken(),r="";if(s?r=`access_token=${encodeURIComponent(s)}`:t&&(r=`api_key=${encodeURIComponent(t)}`),!this.appId)throw new Error("WebRTC \uC5F0\uACB0\uC5D0\uB294 appId\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId\uB97C \uC124\uC815\uD558\uC138\uC694.");return`${e}/v1/apps/${this.appId}/signaling?${r}`}async handleSignalingMessage(e,t,s){switch(e.type){case"joined":if(this.setState("connected"),this.currentPeerId=e.peer_id||null,e.data&&typeof e.data=="object"){let n=e.data;n.channel_type&&(this.channelType=n.channel_type);let o=n.peers||[];for(let c of o)c.peer_id!==this.currentPeerId&&await this.createPeerConnection(c.peer_id,!0)}t?.();break;case"peer_joined":if(e.peer_id&&e.peer_id!==this.currentPeerId){let n={peer_id:e.peer_id,...typeof e.data=="object"?e.data:{}};this.emitPeerJoined(e.peer_id,n),await this.createPeerConnection(e.peer_id,!1)}break;case"peer_left":e.peer_id&&(this.closePeerConnection(e.peer_id),this.emitPeerLeft(e.peer_id));break;case"offer":e.peer_id&&e.sdp&&await this.handleOffer(e.peer_id,e.sdp);break;case"answer":e.peer_id&&e.sdp&&await this.handleAnswer(e.peer_id,e.sdp);break;case"ice_candidate":e.peer_id&&e.candidate&&await this.handleICECandidate(e.peer_id,e.candidate);break;case"error":let r=typeof e.data=="string"?e.data:"Unknown error",i=new Error(r);this.emitError(i),s?.(i);break}}async createPeerConnection(e,t){this.closePeerConnection(e);let s={iceServers:this.iceServers.map(i=>({urls:i.urls,username:i.username,credential:i.credential}))},r=new RTCPeerConnection(s);if(this.peerConnections.set(e,r),this.localStream&&this.localStream.getTracks().forEach(i=>{r.addTrack(i,this.localStream)}),r.onicecandidate=i=>{i.candidate&&this.sendSignaling({type:"ice_candidate",target_id:e,candidate:i.candidate.toJSON()})},r.ontrack=i=>{let[n]=i.streams;n&&(this.remoteStreams.set(e,n),this.emitRemoteStream(e,n))},r.onconnectionstatechange=()=>{r.connectionState==="failed"&&(console.warn(`Peer connection failed: ${e}`),this.closePeerConnection(e))},t){let i=await r.createOffer();await r.setLocalDescription(i),this.sendSignaling({type:"offer",target_id:e,sdp:i.sdp})}return r}async handleOffer(e,t){let s=this.peerConnections.get(e);s||(s=await this.createPeerConnection(e,!1)),await s.setRemoteDescription(new RTCSessionDescription({type:"offer",sdp:t}));let r=await s.createAnswer();await s.setLocalDescription(r),this.sendSignaling({type:"answer",target_id:e,sdp:r.sdp})}async handleAnswer(e,t){let s=this.peerConnections.get(e);s&&await s.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:t}))}async handleICECandidate(e,t){let s=this.peerConnections.get(e);if(s)try{await s.addIceCandidate(new RTCIceCandidate(t))}catch(r){console.warn("Failed to add ICE candidate:",r)}}closePeerConnection(e){let t=this.peerConnections.get(e);t&&(t.close(),this.peerConnections.delete(e)),this.remoteStreams.delete(e)}sendSignaling(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}handleDisconnect(e){let t=this.state==="connected";this.setState("disconnected"),this.peerConnections.forEach((s,r)=>{s.close(),this.emitPeerLeft(r)}),this.peerConnections.clear(),this.remoteStreams.clear(),t&&e.code!==1e3&&this.reconnectAttempts<this.maxReconnectAttempts&&this.attemptReconnect()}attemptReconnect(){this.reconnectAttempts++,this.setState("reconnecting");let e=Math.min(5e3*Math.pow(2,this.reconnectAttempts-1),3e4);this.reconnectTimeout=setTimeout(async()=>{try{await this.connectWebSocket()}catch{this.reconnectAttempts<this.maxReconnectAttempts?this.attemptReconnect():(this.setState("failed"),this.emitError(new Error("\uC7AC\uC5F0\uACB0 \uC2E4\uD328: \uCD5C\uB300 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC")))}},e)}disconnect(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.sendSignaling({type:"leave"}),this.ws.close(1e3,"User disconnected")),this.peerConnections.forEach(e=>e.close()),this.peerConnections.clear(),this.remoteStreams.clear(),this.ws=null,this.currentRoomId=null,this.currentPeerId=null,this.localStream=null,this.setState("disconnected")}getState(){return this.state}getRoomId(){return this.currentRoomId}getPeerId(){return this.currentPeerId}getChannelType(){return this.channelType}getRemoteStream(e){return this.remoteStreams.get(e)}getAllRemoteStreams(){return new Map(this.remoteStreams)}replaceLocalStream(e){this.localStream=e,this.peerConnections.forEach(t=>{let s=t.getSenders();e.getTracks().forEach(r=>{let i=s.find(n=>n.track?.kind===r.kind);i?i.replaceTrack(r):t.addTrack(r,e)})})}setAudioEnabled(e){this.localStream&&this.localStream.getAudioTracks().forEach(t=>{t.enabled=e})}setVideoEnabled(e){this.localStream&&this.localStream.getVideoTracks().forEach(t=>{t.enabled=e})}onStateChange(e){return this.stateListeners.push(e),()=>{this.stateListeners=this.stateListeners.filter(t=>t!==e)}}onError(e){return this.errorListeners.push(e),()=>{this.errorListeners=this.errorListeners.filter(t=>t!==e)}}onPeerJoined(e){return this.peerJoinedListeners.push(e),()=>{this.peerJoinedListeners=this.peerJoinedListeners.filter(t=>t!==e)}}onPeerLeft(e){return this.peerLeftListeners.push(e),()=>{this.peerLeftListeners=this.peerLeftListeners.filter(t=>t!==e)}}onRemoteStream(e){return this.remoteStreamListeners.push(e),()=>{this.remoteStreamListeners=this.remoteStreamListeners.filter(t=>t!==e)}}setState(e){this.state!==e&&(this.state=e,this.stateListeners.forEach(t=>t(e)))}emitError(e){this.errorListeners.forEach(t=>t(e))}emitPeerJoined(e,t){this.peerJoinedListeners.forEach(s=>s(e,t))}emitPeerLeft(e){this.peerLeftListeners.forEach(t=>t(e))}emitRemoteStream(e,t){this.remoteStreamListeners.forEach(s=>s(e,t))}async getStats(e){return this.http.get(`/v1/apps/${e}/webrtc/stats`)}async getRooms(e){return this.http.get(`/v1/apps/${e}/webrtc/rooms`)}};var $=class{constructor(e,t={}){this.storageWebId=null;this.errorQueue=[];this.batchTimer=null;this.isInitialized=!1;this.originalOnError=null;this.originalOnUnhandledRejection=null;this.http=e,this.config={autoCapture:t.autoCapture??!0,captureTypes:t.captureTypes??["error","unhandledrejection"],batchInterval:t.batchInterval??5e3,maxBatchSize:t.maxBatchSize??10,beforeSend:t.beforeSend??(s=>s),debug:t.debug??!1}}init(e){if(this.isInitialized){this.log("ErrorTracker already initialized");return}if(typeof window>"u"){this.log("ErrorTracker only works in browser environment");return}this.storageWebId=e,this.isInitialized=!0,this.config.autoCapture&&this.setupAutoCapture(),this.startBatchTimer(),this.log("ErrorTracker initialized",{storageWebId:e})}destroy(){this.stopBatchTimer(),this.removeAutoCapture(),this.flushQueue(),this.isInitialized=!1,this.log("ErrorTracker destroyed")}async captureError(e,t){let s=this.createErrorReport(e,t);s&&this.queueError(s)}async captureMessage(e,t){let s={message:e,error_type:"custom",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t};this.queueError(s)}async flush(){await this.flushQueue()}log(...e){this.config.debug&&console.log("[ErrorTracker]",...e)}setupAutoCapture(){typeof window>"u"||(this.config.captureTypes.includes("error")&&(this.originalOnError=window.onerror,window.onerror=(e,t,s,r,i)=>(this.handleGlobalError(e,t,s,r,i),this.originalOnError?this.originalOnError(e,t,s,r,i):!1)),this.config.captureTypes.includes("unhandledrejection")&&(this.originalOnUnhandledRejection=window.onunhandledrejection,window.onunhandledrejection=e=>{this.handleUnhandledRejection(e),this.originalOnUnhandledRejection&&this.originalOnUnhandledRejection(e)}),this.log("Auto capture enabled",{types:this.config.captureTypes}))}removeAutoCapture(){typeof window>"u"||(this.originalOnError!==null&&(window.onerror=this.originalOnError),this.originalOnUnhandledRejection!==null&&(window.onunhandledrejection=this.originalOnUnhandledRejection))}handleGlobalError(e,t,s,r,i){let n={message:typeof e=="string"?e:e.type||"Unknown error",source:t||void 0,lineno:s||void 0,colno:r||void 0,stack:i?.stack,error_type:"error",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0};this.queueError(n)}handleUnhandledRejection(e){let t=e.reason,s="Unhandled Promise Rejection",r;t instanceof Error?(s=t.message,r=t.stack):typeof t=="string"?s=t:t&&typeof t=="object"&&(s=JSON.stringify(t));let i={message:s,stack:r,error_type:"unhandledrejection",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0};this.queueError(i)}createErrorReport(e,t){let s;e instanceof Error?s={message:e.message,stack:e.stack,error_type:"error",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t}:s={message:e,error_type:"custom",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0,...t};let r=this.config.beforeSend(s);return r===!1||r===null?(this.log("Error filtered out by beforeSend"),null):r}queueError(e){this.errorQueue.push(e),this.log("Error queued",{message:e.message,queueSize:this.errorQueue.length}),this.errorQueue.length>=this.config.maxBatchSize&&this.flushQueue()}startBatchTimer(){this.batchTimer||(this.batchTimer=setInterval(()=>{this.flushQueue()},this.config.batchInterval))}stopBatchTimer(){this.batchTimer&&(clearInterval(this.batchTimer),this.batchTimer=null)}async flushQueue(){if(!this.storageWebId||this.errorQueue.length===0)return;let e=[...this.errorQueue];this.errorQueue=[];try{e.length===1?await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/report`,e[0]):await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/batch`,{errors:e,user_agent:typeof navigator<"u"?navigator.userAgent:void 0}),this.log("Errors sent",{count:e.length})}catch(t){let s=this.config.maxBatchSize-this.errorQueue.length;s>0&&this.errorQueue.unshift(...e.slice(0,s)),this.log("Failed to send errors, re-queued",{error:t})}}};var I=class{constructor(e){this.http=e}async getEnabledProviders(){return this.http.get("/v1/public/oauth/providers")}async signIn(e,t,s){let r=new URLSearchParams({app_callback:t});s&&r.append("state",s);let i=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${r.toString()}`);window.location.href=i.authorization_url}async signInWithPopup(e,t){let s=new URLSearchParams({app_callback:t}),r=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${s.toString()}`),i=500,n=600,o=window.screenX+(window.outerWidth-i)/2,c=window.screenY+(window.outerHeight-n)/2,h=window.open(r.authorization_url,"oauth-popup",`width=${i},height=${n},left=${o},top=${c}`);if(!h)throw new Error("\uD31D\uC5C5\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD31D\uC5C5 \uCC28\uB2E8\uC744 \uD574\uC81C\uD574\uC8FC\uC138\uC694.");return new Promise((m,L)=>{let p=async l=>{if(l.data?.type!=="oauth-callback")return;if(window.removeEventListener("message",p),l.data.error){L(new Error(l.data.error));return}let b={member_id:l.data.member_id,access_token:l.data.access_token,refresh_token:l.data.refresh_token,is_new_member:l.data.is_new_member==="true"||l.data.is_new_member===!0};this.http.setTokens(b.access_token,b.refresh_token),m(b)};window.addEventListener("message",p);let w=setInterval(()=>{h.closed&&(clearInterval(w),window.removeEventListener("message",p),L(new Error("\uB85C\uADF8\uC778\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.")))},500)})}getCallbackResult(){let e=new URLSearchParams(window.location.search),t=e.get("error");if(t){let o={error:t};return window.opener&&(window.opener.postMessage({type:"oauth-callback",...o},"*"),window.close()),o}let s=e.get("access_token"),r=e.get("refresh_token"),i=e.get("member_id");if(!s||!r||!i)return null;let n={access_token:s,refresh_token:r,member_id:i,is_new_member:e.get("is_new_member")==="true",state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...n},"*"),window.close(),n):(this.http.setTokens(s,r),n)}};var A=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async prepare(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/prepare`,e)}async confirm(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/confirm`,e)}async cancel(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/payments/cancel`,{payment_id:e,...t})}async getByOrderId(e){let t=this.getPublicPrefix();return this.http.get(`${t}/payments/orders/${e}`)}};var M=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async issueBillingKey(){let e=this.getPublicPrefix();return this.http.post(`${e}/subscriptions/billing-keys`,{})}async confirmBillingKey(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/billing-keys/confirm`,e)}async listBillingKeys(e){let t=this.getPublicPrefix(),s=e?`?customer_id=${e}`:"";return this.http.get(`${t}/subscriptions/billing-keys${s}`)}async getBillingKey(e){let t=this.getPublicPrefix();return this.http.get(`${t}/subscriptions/billing-keys/${e}`)}async updateBillingKey(e,t){let s=this.getPublicPrefix();return this.http.patch(`${s}/subscriptions/billing-keys/${e}`,t)}async deleteBillingKey(e){let t=this.getPublicPrefix();return this.http.delete(`${t}/subscriptions/billing-keys/${e}`)}async create(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions`,e)}async list(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.status&&s.set("status",e.status),e?.limit&&s.set("limit",String(e.limit)),e?.offset&&s.set("offset",String(e.offset));let r=s.toString();return this.http.get(`${t}/subscriptions${r?"?"+r:""}`)}async get(e){let t=this.getPublicPrefix();return this.http.get(`${t}/subscriptions/${e}`)}async update(e,t){let s=this.getPublicPrefix();return this.http.patch(`${s}/subscriptions/${e}`,t)}async pause(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/subscriptions/${e}/pause`,t||{})}async resume(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/${e}/resume`,{})}async cancel(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/subscriptions/${e}/cancel`,t)}async listPayments(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;t?.status&&r.set("status",t.status),t?.limit&&r.set("limit",String(t.limit)),t?.offset&&r.set("offset",String(t.offset));let i=r.toString();return this.http.get(`${s}/subscriptions/${e}/payments${i?"?"+i:""}`)}async chargeWithBillingKey(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/charge`,e)}};var U=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async registerDevice(e){let t=this.getPublicPrefix();return this.http.post(`${t}/push/devices`,e)}async unregisterDevice(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${e}`)}async getDevices(){let e=this.getPublicPrefix();return(await this.http.get(`${e}/push/devices`)).devices||[]}async subscribeTopic(e){let t=this.getPublicPrefix(),s={topic_name:e};await this.http.post(`${t}/push/topics/subscribe`,s)}async unsubscribeTopic(e){let t=this.getPublicPrefix(),s={topic_name:e};await this.http.post(`${t}/push/topics/unsubscribe`,s)}async getSubscribedTopics(){let e=this.getPublicPrefix();return(await this.http.get(`${e}/push/topics/subscribed`)).topics||[]}async getVAPIDPublicKey(){let e=this.getPublicPrefix();return this.http.get(`${e}/push/vapid-key`)}async registerWebPush(e){let t=this.getPublicPrefix(),s;if("toJSON"in e){let i=e.toJSON();s={endpoint:i.endpoint||"",expirationTime:i.expirationTime,keys:{p256dh:i.keys?.p256dh||"",auth:i.keys?.auth||""}}}else s=e;let r={device_token:s.endpoint,platform:"web",device_id:this.generateDeviceId(),device_name:this.getBrowserName(),os_version:this.getOSInfo()};return this.http.post(`${t}/push/devices/web`,{...r,web_push_subscription:s})}async unregisterWebPush(){let e=this.getPublicPrefix();await this.http.delete(`${e}/push/devices/web`)}generateDeviceId(){if(typeof window>"u"||typeof localStorage>"u")return`device_${Date.now()}_${Math.random().toString(36).substr(2,9)}`;let e="cb_push_device_id",t=localStorage.getItem(e);return t||(t=`web_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,localStorage.setItem(e,t)),t}getBrowserName(){if(typeof navigator>"u")return"Unknown Browser";let e=navigator.userAgent;return e.includes("Chrome")&&!e.includes("Edg")?"Chrome":e.includes("Safari")&&!e.includes("Chrome")?"Safari":e.includes("Firefox")?"Firefox":e.includes("Edg")?"Edge":e.includes("Opera")||e.includes("OPR")?"Opera":"Unknown Browser"}getOSInfo(){if(typeof navigator>"u")return"Unknown OS";let e=navigator.userAgent;return e.includes("Windows")?"Windows":e.includes("Mac OS")?"macOS":e.includes("Linux")?"Linux":e.includes("Android")?"Android":e.includes("iOS")||e.includes("iPhone")||e.includes("iPad")?"iOS":"Unknown OS"}};var ie=5*1024*1024,f=class extends Error{constructor(e,t){super(e),this.name="VideoProcessingError",this.video=t}},G=class{constructor(e,t){this.http=e;this.videoBaseUrl=t||this.getDefaultVideoUrl()}getDefaultVideoUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"http://localhost:8089"}return"https://video.connectbase.world"}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async videoFetch(e,t,s){let r={},i=this.http.getApiKey();i&&(r["X-API-Key"]=i);let n=this.http.getAccessToken();n&&(r.Authorization=`Bearer ${n}`),s&&!(s instanceof FormData)&&(r["Content-Type"]="application/json");let o=await fetch(`${this.videoBaseUrl}${t}`,{method:e,headers:r,body:s instanceof FormData?s:s?JSON.stringify(s):void 0});if(!o.ok){let c=await o.json().catch(()=>({message:o.statusText}));throw new d(o.status,c.message||"Unknown error")}return o.status===204||o.headers.get("content-length")==="0"?{}:o.json()}async upload(e,t){let s=this.getPublicPrefix(),r=await this.videoFetch("POST",`${s}/uploads`,{filename:e.name,size:e.size,mime_type:e.type,title:t.title,description:t.description,visibility:t.visibility||"private",tags:t.tags,channel_id:t.channel_id}),i=r.chunk_size||ie,n=Math.ceil(e.size/i),o=0,h=Date.now(),m=0;for(let p=0;p<n;p++){let w=p*i,l=Math.min(w+i,e.size),b=e.slice(w,l),H=new FormData;H.append("chunk",b),H.append("chunk_index",String(p)),await this.videoFetch("POST",`${s}/uploads/${r.session_id}/chunks`,H),o++;let B=Date.now(),V=(B-h)/1e3,N=l,z=N-m,Q=V>0?z/V:0;h=B,m=N,t.onProgress&&t.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:Q})}return(await this.videoFetch("POST",`${s}/uploads/${r.session_id}/complete`,{})).video}async waitForReady(e,t){let s=t?.timeout||18e5,r=t?.interval||5e3,i=Date.now(),n=this.getPublicPrefix();for(;Date.now()-i<s;){let o=await this.videoFetch("GET",`${n}/videos/${e}`);if(o.status==="ready")return o;if(o.status==="failed")throw new f("Video processing failed",o);if(t?.onProgress){let c=o.qualities.filter(m=>m.status==="ready").length,h=o.qualities.length||1;t.onProgress({phase:"processing",uploadedChunks:0,totalChunks:0,percentage:Math.round(c/h*100)})}await new Promise(c=>setTimeout(c,r))}throw new f("Timeout waiting for video to be ready")}async list(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.status&&s.set("status",e.status),e?.visibility&&s.set("visibility",e.visibility),e?.search&&s.set("search",e.search),e?.channel_id&&s.set("channel_id",e.channel_id),e?.page&&s.set("page",String(e.page)),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/videos${r?`?${r}`:""}`)}async get(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/videos/${e}`)}async update(e,t){let s=this.getPublicPrefix();return this.videoFetch("PATCH",`${s}/videos/${e}`,t)}async delete(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/videos/${e}`)}async getStreamUrl(e,t){let s=this.getPublicPrefix(),r=t?`?quality=${t}`:"";return this.videoFetch("GET",`${s}/videos/${e}/stream-url${r}`)}async getThumbnails(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/videos/${e}/thumbnails`)).thumbnails}async getTranscodeStatus(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/videos/${e}/transcode/status`)}async retryTranscode(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/videos/${e}/transcode/retry`,{})}async createChannel(e){let t=this.getPublicPrefix();return this.videoFetch("POST",`${t}/channels`,e)}async getChannel(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/channels/${e}`)}async getChannelByHandle(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/channels/handle/${e}`)}async updateChannel(e,t){let s=this.getPublicPrefix();return this.videoFetch("PATCH",`${s}/channels/${e}`,t)}async subscribeChannel(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/channels/${e}/subscribe`,{})}async unsubscribeChannel(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/channels/${e}/subscribe`)}async createPlaylist(e,t){let s=this.getPublicPrefix();return this.videoFetch("POST",`${s}/channels/${e}/playlists`,t)}async getPlaylists(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/channels/${e}/playlists`)).playlists}async getPlaylistItems(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/playlists/${e}/items`)).items}async addToPlaylist(e,t,s){let r=this.getPublicPrefix();return this.videoFetch("POST",`${r}/playlists/${e}/items`,{video_id:t,position:s})}async removeFromPlaylist(e,t){let s=this.getPublicPrefix();await this.videoFetch("DELETE",`${s}/playlists/${e}/items/${t}`)}async getShortsFeed(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.cursor&&s.set("cursor",e.cursor),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/shorts${r?`?${r}`:""}`)}async getTrendingShorts(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return this.videoFetch("GET",`${t}/shorts/trending${s}`)}async getShorts(e){let t=this.getPublicPrefix();return this.videoFetch("GET",`${t}/shorts/${e}`)}async getComments(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;t?.cursor&&r.set("cursor",t.cursor),t?.limit&&r.set("limit",String(t.limit)),t?.sort&&r.set("sort",t.sort);let i=r.toString();return this.videoFetch("GET",`${s}/videos/${e}/comments${i?`?${i}`:""}`)}async postComment(e,t,s){let r=this.getPublicPrefix();return this.videoFetch("POST",`${r}/videos/${e}/comments`,{content:t,parent_id:s})}async deleteComment(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/comments/${e}`)}async likeVideo(e){let t=this.getPublicPrefix();await this.videoFetch("POST",`${t}/videos/${e}/like`,{})}async unlikeVideo(e){let t=this.getPublicPrefix();await this.videoFetch("DELETE",`${t}/videos/${e}/like`)}async getWatchHistory(e){let t=this.getPublicPrefix(),s=new URLSearchParams;e?.cursor&&s.set("cursor",e.cursor),e?.limit&&s.set("limit",String(e.limit));let r=s.toString();return this.videoFetch("GET",`${t}/watch-history${r?`?${r}`:""}`)}async clearWatchHistory(){let e=this.getPublicPrefix();await this.videoFetch("DELETE",`${e}/watch-history`)}async reportWatchProgress(e,t,s){let r=this.getPublicPrefix();await this.videoFetch("POST",`${r}/videos/${e}/watch-progress`,{position:t,duration:s})}async getMembershipTiers(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/channels/${e}/memberships/tiers`)).tiers}async joinMembership(e,t){let s=this.getPublicPrefix();return this.videoFetch("POST",`${s}/channels/${e}/memberships/${t}/join`,{})}async cancelMembership(e,t){let s=this.getPublicPrefix();await this.videoFetch("POST",`${s}/channels/${e}/memberships/${t}/cancel`,{})}async sendSuperChat(e,t,s,r){let i=this.getPublicPrefix();return this.videoFetch("POST",`${i}/videos/${e}/super-chats`,{amount:t,message:s,currency:r||"USD"})}async getSuperChats(e){let t=this.getPublicPrefix();return(await this.videoFetch("GET",`${t}/videos/${e}/super-chats`)).super_chats}async getRecommendations(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations${s}`)).videos}async getHomeFeed(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations/home${s}`)).videos}async getRelatedVideos(e,t){let s=this.getPublicPrefix(),r=t?`?limit=${t}`:"";return(await this.videoFetch("GET",`${s}/recommendations/related/${e}${r}`)).videos}async getTrendingVideos(e){let t=this.getPublicPrefix(),s=e?`?limit=${e}`:"";return(await this.videoFetch("GET",`${t}/recommendations/trending${s}`)).videos}async submitFeedback(e,t){let s=this.getPublicPrefix();await this.videoFetch("POST",`${s}/recommendations/feedback`,{video_id:e,feedback:t})}};var J=()=>{if(typeof window<"u"){let a=window.location.hostname;if(a==="localhost"||a==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"},P=class{constructor(e){this.ws=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this.config={gameServerUrl:J(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,...e}}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}on(e,t){return this.handlers[e]=t,this}connect(e){return new Promise((t,s)=>{if(this.ws?.readyState===WebSocket.OPEN){t();return}let r=this.buildConnectionUrl(e);this.ws=new WebSocket(r);let i=()=>{this._isConnected=!0,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),t()},n=h=>{this._isConnected=!1,this.stopPingInterval(),this.handlers.onDisconnect?.(h),this.config.autoReconnect&&h.code!==1e3&&this.scheduleReconnect(e)},o=h=>{this.handlers.onError?.(h),s(new Error("WebSocket connection failed"))},c=h=>{this.handleMessage(h.data)};this.ws.addEventListener("open",i,{once:!0}),this.ws.addEventListener("close",n),this.ws.addEventListener("error",o,{once:!0}),this.ws.addEventListener("message",c)})}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null),this._isConnected=!1,this._roomId=null}createRoom(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else i.type==="error"&&s(new Error(i.data.message))};this.sendWithHandler("create_room",e,r)})}joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&r(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,e()):r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e){if(!this._roomId)throw new Error("Not in a room");this.send("action",{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++})}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{if(r.type==="state"){let i=r.data;this._state=i,e(i)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("get_state",{},s)})}listRooms(){return new Promise((e,t)=>{let s=r=>{if(r.type==="room_list"){let i=r.data;e(i.rooms)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("list_rooms",{},s)})}ping(){return new Promise((e,t)=>{let s=Date.now(),r=i=>{if(i.type==="pong"){let n=i.data,o=Date.now()-n.clientTimestamp;this.handlers.onPong?.(n),e(o)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("ping",{timestamp:s},r)})}buildConnectionUrl(e){let s=this.config.gameServerUrl.replace(/^http/,"ws"),r=new URLSearchParams;return r.set("client_id",this.config.clientId),e&&r.set("room_id",e),this.config.apiKey&&r.set("api_key",this.config.apiKey),this.config.accessToken&&r.set("token",this.config.accessToken),`${s}/v1/game/ws?${r.toString()}`}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("WebSocket is not connected");this.ws.send(JSON.stringify({type:e,data:t}))}sendWithHandler(e,t,s){let r=i=>{try{let n=JSON.parse(i.data);s(n),this.ws?.removeEventListener("message",r)}catch{}};this.ws?.addEventListener("message",r),this.send(e,t)}handleMessage(e){try{let t=JSON.parse(e);switch(t.type){case"delta":this.handleDelta(t.data);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t.data);break;case"chat":this.handlers.onChat?.(t.data);break;case"error":this.handlers.onError?.(t.data);break;default:break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(this._state){for(let s of t.changes)this.applyChange(s);this._state.version=t.toVersion}this.handlers.onDelta?.(t)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let i=0;i<t.length-1;i++){let n=t[i];n in s||(s[n]={}),s=s[n]}let r=t[t.length-1];e.operation==="delete"?delete s[r]:s[r]=e.value}handlePlayerEvent(e){e.event==="joined"?this.handlers.onPlayerJoined?.(e.player):e.event==="left"&&this.handlers.onPlayerLeft?.(e.player)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached");return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`),this.connect(e||this._roomId||void 0).catch(()=>{})},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}},y=class{constructor(e,t){this.http=e,this.gameServerUrl=t||J().replace(/^ws/,"http")}createClient(e){return new P({...e,gameServerUrl:this.gameServerUrl.replace(/^http/,"ws"),apiKey:this.http.getApiKey(),accessToken:this.http.getAccessToken()})}async listRooms(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms${e?`?app_id=${e}`:""}`,{headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to list rooms: ${t.statusText}`);return(await t.json()).rooms}async getRoom(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms/${e}`,{headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to get room: ${t.statusText}`);return t.json()}async createRoom(e,t={}){let s=await fetch(`${this.gameServerUrl}/v1/game/rooms`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({app_id:e,category_id:t.categoryId,room_id:t.roomId,tick_rate:t.tickRate,max_players:t.maxPlayers,metadata:t.metadata})});if(!s.ok)throw new Error(`Failed to create room: ${s.statusText}`);return s.json()}async deleteRoom(e){let t=await fetch(`${this.gameServerUrl}/v1/game/rooms/${e}`,{method:"DELETE",headers:this.getHeaders()});if(!t.ok)throw new Error(`Failed to delete room: ${t.statusText}`)}getHeaders(){let e={},t=this.http.getApiKey();t&&(e["X-API-Key"]=t);let s=this.http.getAccessToken();return s&&(e.Authorization=`Bearer ${s}`),e}};var v=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasApiKey()?"/v1/public":"/v1"}async getConnectionStatus(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/connection`)}async getReport(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;e&&r.set("start",e),t&&r.set("end",t);let i=r.toString();return this.http.get(`${s}/ads/reports${i?`?${i}`:""}`)}async getReportSummary(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/reports/summary`)}};var K=class{constructor(e,t,s,r){this.type="webtransport";this.transport=null;this.writer=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=r}async connect(){let e=this.buildUrl();this.transport=new WebTransport(e),await this.transport.ready,this.config.useUnreliableDatagrams!==!1&&this.readDatagrams();let t=await this.transport.createBidirectionalStream();this.writer=t.writable.getWriter(),this.readStream(t.readable),this.transport.closed.then(()=>{this.onClose()}).catch(s=>{this.onError(s)})}buildUrl(){let t=(this.config.gameServerUrl||"https://game.connectbase.world").replace(/^ws/,"http").replace(/^http:/,"https:"),s=new URLSearchParams;return s.set("client_id",this.config.clientId),this.config.apiKey&&s.set("api_key",this.config.apiKey),this.config.accessToken&&s.set("token",this.config.accessToken),`${t}/v1/game/webtransport?${s.toString()}`}async readDatagrams(){if(!this.transport)return;let e=this.transport.datagrams.readable.getReader();try{for(;;){let{value:t,done:s}=await e.read();if(s)break;this.onMessage(t)}}catch{}}async readStream(e){let t=e.getReader(),s=new Uint8Array(0);try{for(;;){let{value:r,done:i}=await t.read();if(i)break;let n=new Uint8Array(s.length+r.length);for(n.set(s),n.set(r,s.length),s=n;s.length>=4;){let o=new DataView(s.buffer).getUint32(0,!0);if(s.length<4+o)break;let c=s.slice(4,4+o);s=s.slice(4+o),this.onMessage(c)}}}catch{}}disconnect(){this.transport&&(this.transport.close(),this.transport=null,this.writer=null)}send(e,t=!0){if(!this.transport)throw new Error("Not connected");let s=typeof e=="string"?new TextEncoder().encode(e):e;if(t){if(this.writer){let r=new Uint8Array(4);new DataView(r.buffer).setUint32(0,s.length,!0);let i=new Uint8Array(4+s.length);i.set(r),i.set(s,4),this.writer.write(i)}}else{let r=this.config.maxDatagramSize||1200;s.length<=r?this.transport.datagrams.writable.getWriter().write(s):(console.warn("Datagram too large, falling back to reliable stream"),this.send(e,!0))}}isConnected(){return this.transport!==null}},q=class{constructor(e,t,s,r){this.type="websocket";this.ws=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=r}connect(){return new Promise((e,t)=>{let s=this.buildUrl();try{this.ws=new WebSocket(s),this.ws.binaryType="arraybuffer"}catch(c){t(c);return}let r=()=>{e()},i=()=>{this.onClose()},n=c=>{let h=new Error("WebSocket error");this.onError(h),t(h)},o=c=>{c.data instanceof ArrayBuffer?this.onMessage(new Uint8Array(c.data)):typeof c.data=="string"&&this.onMessage(new TextEncoder().encode(c.data))};this.ws.addEventListener("open",r,{once:!0}),this.ws.addEventListener("close",i),this.ws.addEventListener("error",n,{once:!0}),this.ws.addEventListener("message",o)})}buildUrl(){let t=(this.config.gameServerUrl||"wss://game.connectbase.world").replace(/^http/,"ws"),s=new URLSearchParams;return s.set("client_id",this.config.clientId),this.config.apiKey&&s.set("api_key",this.config.apiKey),this.config.accessToken&&s.set("token",this.config.accessToken),`${t}/v1/game/ws?${s.toString()}`}disconnect(){this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null)}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function W(){return typeof WebTransport<"u"}var D=class{constructor(e){this.transport=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this._connectionStatus="disconnected";this._lastError=null;this._latency=0;this._transportType="websocket";this.decoder=new TextDecoder;this.pendingHandlers=new Map;this.messageId=0;this.config={gameServerUrl:this.getDefaultGameServerUrl(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,connectionTimeout:1e4,transport:"auto",useUnreliableDatagrams:!0,...e}}getDefaultGameServerUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"}get transportType(){return this._transportType}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}get connectionState(){return{status:this._connectionStatus,transport:this._transportType==="auto"?null:this._transportType,roomId:this._roomId,latency:this._latency,reconnectAttempt:this.reconnectAttempts,lastError:this._lastError||void 0}}get latency(){return this._latency}on(e,t){return this.handlers[e]=t,this}async connect(e){if(this.transport?.isConnected())return;this._connectionStatus=this.reconnectAttempts>0?"reconnecting":"connecting";let t=this.config.transport||"auto",s=(t==="webtransport"||t==="auto")&&W(),r=o=>{this.handleMessage(this.decoder.decode(o))},i=()=>{this._isConnected=!1,this._connectionStatus="disconnected",this.stopPingInterval(),this.handlers.onDisconnect?.(new CloseEvent("close")),this.config.autoReconnect&&(this._connectionStatus="reconnecting",this.scheduleReconnect(e))},n=o=>{this._connectionStatus="error",this._lastError=o,this.handlers.onError?.(o)};if(s)try{this.transport=new K(this.config,r,i,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new q(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new q(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket";this._isConnected=!0,this._connectionStatus="connected",this._lastError=null,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),e&&await this.joinRoom(e)}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.transport&&(this.transport.disconnect(),this.transport=null),this._isConnected=!1,this._connectionStatus="disconnected",this._roomId=null,this._state=null}async createRoom(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else i.type==="error"&&s(new Error(i.data.message))};this.sendWithHandler("create_room",e,r)})}async joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&r(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,e()):r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new Error("Not in a room");let s=JSON.stringify({type:"action",data:{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++}}),r=t||this._transportType!=="webtransport";this.transport?.send(s,r)}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=r=>{if(r.type==="state"){let i=r.data;this._state=i,e(i)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("get_state",{},s)})}async ping(){return new Promise((e,t)=>{let s=Date.now(),r=i=>{if(i.type==="pong"){let n=i.data,o=Date.now()-n.clientTimestamp;this._latency=o,this.handlers.onPong?.(n),e(o)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("ping",{timestamp:s},r)})}send(e,t){if(!this.transport?.isConnected())throw new Error("Not connected");let s=JSON.stringify({type:e,data:t});this.transport.send(s,!0)}sendWithHandler(e,t,s){let r=`msg_${this.messageId++}`;this.pendingHandlers.set(r,s),setTimeout(()=>{this.pendingHandlers.delete(r)},1e4),this.send(e,{...t,_msg_id:r})}handleMessage(e){try{let t=JSON.parse(e);if(t._msg_id&&this.pendingHandlers.has(t._msg_id)){let s=this.pendingHandlers.get(t._msg_id);this.pendingHandlers.delete(t._msg_id),s(t);return}switch(t.type){case"delta":this.handleDelta(t.data);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t.data);break;case"chat":this.handlers.onChat?.(t.data);break;case"error":this.handlers.onError?.(t.data);break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(this._state){for(let s of t.changes)this.applyChange(s);this._state.version=t.toVersion}this.handlers.onDelta?.(t)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let i=0;i<t.length-1;i++){let n=t[i];n in s||(s[n]={}),s=s[n]}let r=t[t.length-1];e.operation==="delete"?delete s[r]:s[r]=e.value}handlePlayerEvent(e){e.event==="joined"?this.handlers.onPlayerJoined?.(e.player):e.event==="left"&&this.handlers.onPlayerLeft?.(e.player)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached");return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`),this.connect(e||this._roomId||void 0).catch(()=>{})},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}};var ne="https://api.connectbase.world",oe="https://socket.connectbase.world",ae="https://webrtc.connectbase.world",ce="https://video.connectbase.world",he="https://game.connectbase.world",F=class{constructor(e={}){let t={baseUrl:e.baseUrl||ne,apiKey:e.apiKey,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError};this.http=new S(t),this.auth=new R(this.http),this.database=new T(this.http),this.storage=new C(this.http),this.apiKey=new _(this.http),this.functions=new k(this.http),this.realtime=new E(this.http,e.socketUrl||oe),this.webrtc=new x(this.http,e.webrtcUrl||ae,e.appId),this.errorTracker=new $(this.http,e.errorTracker),this.oauth=new I(this.http),this.payment=new A(this.http),this.subscription=new M(this.http),this.push=new U(this.http),this.video=new G(this.http,e.videoUrl||ce),this.game=new y(this.http,e.gameUrl||he),this.ads=new v(this.http)}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},le=F;return se(pe);})();
|
|
3
3
|
var ConnectBase = ConnectBaseModule.default || ConnectBaseModule.ConnectBase;
|
package/dist/index.d.mts
CHANGED
|
@@ -392,6 +392,28 @@ interface RenameFileResponse {
|
|
|
392
392
|
interface FetchFilesResponse {
|
|
393
393
|
files: FileItem[];
|
|
394
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* 경로 기반 업로드 옵션
|
|
397
|
+
*/
|
|
398
|
+
interface UploadByPathOptions {
|
|
399
|
+
/** true면 기존 파일 덮어쓰기 (기본값: true) */
|
|
400
|
+
overwrite?: boolean;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 경로로 파일 조회 응답
|
|
404
|
+
*/
|
|
405
|
+
interface GetFileByPathResponse {
|
|
406
|
+
id: string;
|
|
407
|
+
name: string;
|
|
408
|
+
path: string;
|
|
409
|
+
type: string;
|
|
410
|
+
mime_type: string;
|
|
411
|
+
size: number;
|
|
412
|
+
url: string;
|
|
413
|
+
parent_id?: string;
|
|
414
|
+
created_at: string;
|
|
415
|
+
updated_at: string;
|
|
416
|
+
}
|
|
395
417
|
|
|
396
418
|
declare class StorageAPI {
|
|
397
419
|
private http;
|
|
@@ -436,6 +458,51 @@ declare class StorageAPI {
|
|
|
436
458
|
* 이미지 파일 여부 확인
|
|
437
459
|
*/
|
|
438
460
|
isImageFile(file: FileItem): boolean;
|
|
461
|
+
/**
|
|
462
|
+
* 경로 기반 파일 업로드 (Firebase Storage 스타일)
|
|
463
|
+
*
|
|
464
|
+
* 같은 경로에 파일이 이미 존재하면 덮어쓰기합니다.
|
|
465
|
+
* URL이 변경되지 않아 고정 URL이 필요한 경우에 유용합니다.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* // 프로필 이미지 업로드 (항상 같은 URL 유지)
|
|
470
|
+
* const result = await storage.uploadByPath(
|
|
471
|
+
* storageId,
|
|
472
|
+
* '/profiles/user123/avatar.png',
|
|
473
|
+
* file
|
|
474
|
+
* )
|
|
475
|
+
* console.log(result.url) // 항상 동일한 URL
|
|
476
|
+
*
|
|
477
|
+
* // 같은 경로로 다시 업로드하면 파일만 교체
|
|
478
|
+
* await storage.uploadByPath(storageId, '/profiles/user123/avatar.png', newFile)
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
uploadByPath(storageId: string, path: string, file: File, options?: UploadByPathOptions): Promise<UploadFileResponse>;
|
|
482
|
+
/**
|
|
483
|
+
* 경로로 파일 조회
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```ts
|
|
487
|
+
* const file = await storage.getByPath(storageId, '/profiles/user123/avatar.png')
|
|
488
|
+
* console.log(file.url) // 파일의 공개 URL
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
getByPath(storageId: string, path: string): Promise<GetFileByPathResponse>;
|
|
492
|
+
/**
|
|
493
|
+
* 경로로 파일 URL 가져오기
|
|
494
|
+
*
|
|
495
|
+
* 파일이 존재하지 않으면 null 반환
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* const url = await storage.getUrlByPath(storageId, '/profiles/user123/avatar.png')
|
|
500
|
+
* if (url) {
|
|
501
|
+
* console.log('이미지 URL:', url)
|
|
502
|
+
* }
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
getUrlByPath(storageId: string, path: string): Promise<string | null>;
|
|
439
506
|
}
|
|
440
507
|
|
|
441
508
|
/**
|
|
@@ -1028,6 +1095,7 @@ interface JoinRoomResponse {
|
|
|
1028
1095
|
declare class WebRTCAPI {
|
|
1029
1096
|
private http;
|
|
1030
1097
|
private webrtcUrl;
|
|
1098
|
+
private appId?;
|
|
1031
1099
|
private ws;
|
|
1032
1100
|
private state;
|
|
1033
1101
|
private stateListeners;
|
|
@@ -1047,7 +1115,7 @@ declare class WebRTCAPI {
|
|
|
1047
1115
|
private peerConnections;
|
|
1048
1116
|
private remoteStreams;
|
|
1049
1117
|
private iceServers;
|
|
1050
|
-
constructor(http: HttpClient, webrtcUrl: string);
|
|
1118
|
+
constructor(http: HttpClient, webrtcUrl: string, appId?: string);
|
|
1051
1119
|
/**
|
|
1052
1120
|
* ICE 서버 목록 조회
|
|
1053
1121
|
*/
|
|
@@ -2996,6 +3064,83 @@ declare class GameAPI {
|
|
|
2996
3064
|
private getHeaders;
|
|
2997
3065
|
}
|
|
2998
3066
|
|
|
3067
|
+
interface GoogleConnectionStatus {
|
|
3068
|
+
is_connected: boolean;
|
|
3069
|
+
email?: string;
|
|
3070
|
+
adsense_account_id?: string;
|
|
3071
|
+
}
|
|
3072
|
+
interface AdReportSummary {
|
|
3073
|
+
total_earnings: number;
|
|
3074
|
+
total_impressions: number;
|
|
3075
|
+
total_clicks: number;
|
|
3076
|
+
ctr: number;
|
|
3077
|
+
cpc: number;
|
|
3078
|
+
rpm: number;
|
|
3079
|
+
}
|
|
3080
|
+
interface DailyReport {
|
|
3081
|
+
date: string;
|
|
3082
|
+
earnings: number;
|
|
3083
|
+
impressions: number;
|
|
3084
|
+
clicks: number;
|
|
3085
|
+
ctr: number;
|
|
3086
|
+
}
|
|
3087
|
+
interface AdReportResponse {
|
|
3088
|
+
summary: AdReportSummary;
|
|
3089
|
+
daily: DailyReport[];
|
|
3090
|
+
}
|
|
3091
|
+
declare class AdsAPI {
|
|
3092
|
+
private http;
|
|
3093
|
+
constructor(http: HttpClient);
|
|
3094
|
+
/**
|
|
3095
|
+
* API Key 인증 시 /v1/public 접두사 반환
|
|
3096
|
+
*/
|
|
3097
|
+
private getPublicPrefix;
|
|
3098
|
+
/**
|
|
3099
|
+
* AdSense 연결 상태 확인
|
|
3100
|
+
*
|
|
3101
|
+
* @returns 연결 상태, 이메일, AdSense 계정 ID
|
|
3102
|
+
*
|
|
3103
|
+
* @example
|
|
3104
|
+
* ```typescript
|
|
3105
|
+
* const status = await cb.ads.getConnectionStatus()
|
|
3106
|
+
* if (status.is_connected) {
|
|
3107
|
+
* console.log('연결됨:', status.email)
|
|
3108
|
+
* }
|
|
3109
|
+
* ```
|
|
3110
|
+
*/
|
|
3111
|
+
getConnectionStatus(): Promise<GoogleConnectionStatus>;
|
|
3112
|
+
/**
|
|
3113
|
+
* AdSense 리포트 조회 (일별 데이터 + 요약)
|
|
3114
|
+
*
|
|
3115
|
+
* @param startDate - 시작일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3116
|
+
* @param endDate - 종료일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3117
|
+
* @returns 요약 + 일별 데이터
|
|
3118
|
+
*
|
|
3119
|
+
* @example
|
|
3120
|
+
* ```typescript
|
|
3121
|
+
* const report = await cb.ads.getReport('2025-01-01', '2025-01-31')
|
|
3122
|
+
* console.log('총 수익:', report.summary.total_earnings)
|
|
3123
|
+
* report.daily.forEach(day => {
|
|
3124
|
+
* console.log(`${day.date}: $${day.earnings}`)
|
|
3125
|
+
* })
|
|
3126
|
+
* ```
|
|
3127
|
+
*/
|
|
3128
|
+
getReport(startDate?: string, endDate?: string): Promise<AdReportResponse>;
|
|
3129
|
+
/**
|
|
3130
|
+
* 최근 30일 AdSense 요약 조회
|
|
3131
|
+
*
|
|
3132
|
+
* @returns 30일 합산 수익, 노출, 클릭, CTR, CPC, RPM
|
|
3133
|
+
*
|
|
3134
|
+
* @example
|
|
3135
|
+
* ```typescript
|
|
3136
|
+
* const summary = await cb.ads.getReportSummary()
|
|
3137
|
+
* console.log(`총 수익: $${summary.total_earnings}`)
|
|
3138
|
+
* console.log(`RPM: $${summary.rpm}`)
|
|
3139
|
+
* ```
|
|
3140
|
+
*/
|
|
3141
|
+
getReportSummary(): Promise<AdReportSummary>;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
2999
3144
|
/**
|
|
3000
3145
|
* WebTransport-based Game Client
|
|
3001
3146
|
*
|
|
@@ -3159,6 +3304,10 @@ interface ConnectBaseConfig {
|
|
|
3159
3304
|
* @example 'https://game.connectbase.world'
|
|
3160
3305
|
*/
|
|
3161
3306
|
gameUrl?: string;
|
|
3307
|
+
/**
|
|
3308
|
+
* 앱 ID (콘솔에서 확인, WebRTC 등 일부 기능에 필요)
|
|
3309
|
+
*/
|
|
3310
|
+
appId?: string;
|
|
3162
3311
|
/**
|
|
3163
3312
|
* API Key (콘솔에서 발급)
|
|
3164
3313
|
*/
|
|
@@ -3271,6 +3420,10 @@ declare class ConnectBase {
|
|
|
3271
3420
|
* 게임 API (실시간 멀티플레이어)
|
|
3272
3421
|
*/
|
|
3273
3422
|
readonly game: GameAPI;
|
|
3423
|
+
/**
|
|
3424
|
+
* 광고 API (Google AdSense)
|
|
3425
|
+
*/
|
|
3426
|
+
readonly ads: AdsAPI;
|
|
3274
3427
|
constructor(config?: ConnectBaseConfig);
|
|
3275
3428
|
/**
|
|
3276
3429
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -3286,4 +3439,4 @@ declare class ConnectBase {
|
|
|
3286
3439
|
updateConfig(config: Partial<ConnectBaseConfig>): void;
|
|
3287
3440
|
}
|
|
3288
3441
|
|
|
3289
|
-
export { ApiError, type ApiKeyItem, type AppStatsResponse, AuthError, type AuthSettingsResponse, type BillingCycle, type BillingKeyResponse, type BulkCreateResponse, type BulkError, type CancelPaymentRequest, type CancelPaymentResponse, type CancelSubscriptionRequest, type CategoryInfo, type Channel, type ChannelMembership, type ChargeWithBillingKeyRequest, type ChargeWithBillingKeyResponse, type ChatMessage, type ClientMessage, type ColumnSchema, type CommentListResponse, type ConfirmBillingKeyRequest, type ConfirmPaymentRequest, type ConfirmPaymentResponse, ConnectBase, type ConnectBaseConfig, type ConnectedData, type ConnectionState, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateChannelRequest, type CreateColumnRequest, type CreateDataRequest, type CreateFolderRequest, type CreateFolderResponse, type CreatePlaylistRequest, type CreateSubscriptionRequest, type CreateTableRequest, type DataItem, type DataType, type DeleteWhereResponse, type DeviceInfo, type EnabledProviderInfo, type EnabledProvidersResponse, type ErrorHandler, type ErrorMessage, type ErrorReport, type ErrorTrackerConfig, type ErrorType, type FetchApiKeysResponse, type FetchDataResponse, type FetchFilesResponse, type FileItem, GameAPI, type GameAction, type GameClientConfig, type GameConnectionState, type GameConnectionStatus, type GameDelta, type GameEventHandlers, type GamePlayer, GameRoom, type GameRoomConfig, type GameRoomInfo, GameRoomTransport, type GameServerMessage, type GameServerMessageType, type GameState, type GameTransportConfig, type GetAuthorizationURLResponse, type GuestMemberSignInResponse, type HistoryResponse, type ICEServer, type ICEServersResponse, type InitUploadResponse, type InvokeFunctionRequest, type InvokeFunctionResponse, type IssueBillingKeyRequest, type IssueBillingKeyResponse, type JoinRoomRequest, type JoinRoomResponse, type ListBillingKeysResponse, type ListSubscriptionPaymentsRequest, type ListSubscriptionPaymentsResponse, type ListSubscriptionsRequest, type ListSubscriptionsResponse, type MemberSignInRequest, type MemberSignInResponse, type MemberSignUpRequest, type MemberSignUpResponse, type MembershipTier, type MessageHandler, type MoveFileRequest, type OAuthCallbackResponse, type OAuthProvider, type PauseSubscriptionRequest, type PaymentDetail, type PaymentStatus, type PeerInfo, type PlayerEvent, type Playlist, type PlaylistItem, type PongMessage, type PreparePaymentRequest, type PreparePaymentResponse, type PushPlatform, type QualityProgress, type QueryOptions, type RealtimeConnectOptions, type RealtimeMessage, type RegisterDeviceRequest, type RenameFileRequest, type RenameFileResponse, type RoomInfo, type RoomStats, type RoomsResponse, type SendOptions, type ServerMessage, type Shorts, type ShortsListResponse, type SignalingMessage, type SignalingMessageType, type StateChange, type StateChangeHandler, type StreamDoneCallback, type StreamDoneData, type StreamErrorCallback, type StreamHandlers, type StreamMessage, type StreamOptions, type StreamSession, type StreamTokenCallback, type StreamURLResponse, type SubscribeOptions, type SubscribeTopicRequest, type SubscribedData, type Subscription, type SubscriptionPaymentResponse, type SubscriptionPaymentStatus, type SubscriptionResponse, type SubscriptionStatus, type SuperChat, type TableSchema, type TranscodeStatus, type TransportType, type UpdateApiKeyRequest, type UpdateApiKeyResponse, type UpdateBillingKeyRequest, type UpdateChannelRequest, type UpdateColumnRequest, type UpdateDataRequest, type UpdateSubscriptionRequest, type UpdateVideoRequest, type UploadFileResponse, type UploadOptions, type UploadProgress, type VAPIDPublicKeyResponse, type Video, type VideoComment, type VideoListOptions, type VideoListResponse, VideoProcessingError, type VideoQuality, type VideoStatus, type VideoVisibility, type WaitOptions, type WatchHistoryItem, type WebPushSubscription, type WebRTCConnectOptions, type WebRTCConnectionState, type WebRTCMode, type WhereCondition, type WhereOperator, ConnectBase as default, isWebTransportSupported };
|
|
3442
|
+
export { type AdReportResponse, type AdReportSummary, AdsAPI, ApiError, type ApiKeyItem, type AppStatsResponse, AuthError, type AuthSettingsResponse, type BillingCycle, type BillingKeyResponse, type BulkCreateResponse, type BulkError, type CancelPaymentRequest, type CancelPaymentResponse, type CancelSubscriptionRequest, type CategoryInfo, type Channel, type ChannelMembership, type ChargeWithBillingKeyRequest, type ChargeWithBillingKeyResponse, type ChatMessage, type ClientMessage, type ColumnSchema, type CommentListResponse, type ConfirmBillingKeyRequest, type ConfirmPaymentRequest, type ConfirmPaymentResponse, ConnectBase, type ConnectBaseConfig, type ConnectedData, type ConnectionState, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateChannelRequest, type CreateColumnRequest, type CreateDataRequest, type CreateFolderRequest, type CreateFolderResponse, type CreatePlaylistRequest, type CreateSubscriptionRequest, type CreateTableRequest, type DailyReport, type DataItem, type DataType, type DeleteWhereResponse, type DeviceInfo, type EnabledProviderInfo, type EnabledProvidersResponse, type ErrorHandler, type ErrorMessage, type ErrorReport, type ErrorTrackerConfig, type ErrorType, type FetchApiKeysResponse, type FetchDataResponse, type FetchFilesResponse, type FileItem, GameAPI, type GameAction, type GameClientConfig, type GameConnectionState, type GameConnectionStatus, type GameDelta, type GameEventHandlers, type GamePlayer, GameRoom, type GameRoomConfig, type GameRoomInfo, GameRoomTransport, type GameServerMessage, type GameServerMessageType, type GameState, type GameTransportConfig, type GetAuthorizationURLResponse, type GetFileByPathResponse, type GoogleConnectionStatus, type GuestMemberSignInResponse, type HistoryResponse, type ICEServer, type ICEServersResponse, type InitUploadResponse, type InvokeFunctionRequest, type InvokeFunctionResponse, type IssueBillingKeyRequest, type IssueBillingKeyResponse, type JoinRoomRequest, type JoinRoomResponse, type ListBillingKeysResponse, type ListSubscriptionPaymentsRequest, type ListSubscriptionPaymentsResponse, type ListSubscriptionsRequest, type ListSubscriptionsResponse, type MemberSignInRequest, type MemberSignInResponse, type MemberSignUpRequest, type MemberSignUpResponse, type MembershipTier, type MessageHandler, type MoveFileRequest, type OAuthCallbackResponse, type OAuthProvider, type PauseSubscriptionRequest, type PaymentDetail, type PaymentStatus, type PeerInfo, type PlayerEvent, type Playlist, type PlaylistItem, type PongMessage, type PreparePaymentRequest, type PreparePaymentResponse, type PushPlatform, type QualityProgress, type QueryOptions, type RealtimeConnectOptions, type RealtimeMessage, type RegisterDeviceRequest, type RenameFileRequest, type RenameFileResponse, type RoomInfo, type RoomStats, type RoomsResponse, type SendOptions, type ServerMessage, type Shorts, type ShortsListResponse, type SignalingMessage, type SignalingMessageType, type StateChange, type StateChangeHandler, type StreamDoneCallback, type StreamDoneData, type StreamErrorCallback, type StreamHandlers, type StreamMessage, type StreamOptions, type StreamSession, type StreamTokenCallback, type StreamURLResponse, type SubscribeOptions, type SubscribeTopicRequest, type SubscribedData, type Subscription, type SubscriptionPaymentResponse, type SubscriptionPaymentStatus, type SubscriptionResponse, type SubscriptionStatus, type SuperChat, type TableSchema, type TranscodeStatus, type TransportType, type UpdateApiKeyRequest, type UpdateApiKeyResponse, type UpdateBillingKeyRequest, type UpdateChannelRequest, type UpdateColumnRequest, type UpdateDataRequest, type UpdateSubscriptionRequest, type UpdateVideoRequest, type UploadByPathOptions, type UploadFileResponse, type UploadOptions, type UploadProgress, type VAPIDPublicKeyResponse, type Video, type VideoComment, type VideoListOptions, type VideoListResponse, VideoProcessingError, type VideoQuality, type VideoStatus, type VideoVisibility, type WaitOptions, type WatchHistoryItem, type WebPushSubscription, type WebRTCConnectOptions, type WebRTCConnectionState, type WebRTCMode, type WhereCondition, type WhereOperator, ConnectBase as default, isWebTransportSupported };
|
package/dist/index.d.ts
CHANGED
|
@@ -392,6 +392,28 @@ interface RenameFileResponse {
|
|
|
392
392
|
interface FetchFilesResponse {
|
|
393
393
|
files: FileItem[];
|
|
394
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* 경로 기반 업로드 옵션
|
|
397
|
+
*/
|
|
398
|
+
interface UploadByPathOptions {
|
|
399
|
+
/** true면 기존 파일 덮어쓰기 (기본값: true) */
|
|
400
|
+
overwrite?: boolean;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 경로로 파일 조회 응답
|
|
404
|
+
*/
|
|
405
|
+
interface GetFileByPathResponse {
|
|
406
|
+
id: string;
|
|
407
|
+
name: string;
|
|
408
|
+
path: string;
|
|
409
|
+
type: string;
|
|
410
|
+
mime_type: string;
|
|
411
|
+
size: number;
|
|
412
|
+
url: string;
|
|
413
|
+
parent_id?: string;
|
|
414
|
+
created_at: string;
|
|
415
|
+
updated_at: string;
|
|
416
|
+
}
|
|
395
417
|
|
|
396
418
|
declare class StorageAPI {
|
|
397
419
|
private http;
|
|
@@ -436,6 +458,51 @@ declare class StorageAPI {
|
|
|
436
458
|
* 이미지 파일 여부 확인
|
|
437
459
|
*/
|
|
438
460
|
isImageFile(file: FileItem): boolean;
|
|
461
|
+
/**
|
|
462
|
+
* 경로 기반 파일 업로드 (Firebase Storage 스타일)
|
|
463
|
+
*
|
|
464
|
+
* 같은 경로에 파일이 이미 존재하면 덮어쓰기합니다.
|
|
465
|
+
* URL이 변경되지 않아 고정 URL이 필요한 경우에 유용합니다.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* // 프로필 이미지 업로드 (항상 같은 URL 유지)
|
|
470
|
+
* const result = await storage.uploadByPath(
|
|
471
|
+
* storageId,
|
|
472
|
+
* '/profiles/user123/avatar.png',
|
|
473
|
+
* file
|
|
474
|
+
* )
|
|
475
|
+
* console.log(result.url) // 항상 동일한 URL
|
|
476
|
+
*
|
|
477
|
+
* // 같은 경로로 다시 업로드하면 파일만 교체
|
|
478
|
+
* await storage.uploadByPath(storageId, '/profiles/user123/avatar.png', newFile)
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
uploadByPath(storageId: string, path: string, file: File, options?: UploadByPathOptions): Promise<UploadFileResponse>;
|
|
482
|
+
/**
|
|
483
|
+
* 경로로 파일 조회
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```ts
|
|
487
|
+
* const file = await storage.getByPath(storageId, '/profiles/user123/avatar.png')
|
|
488
|
+
* console.log(file.url) // 파일의 공개 URL
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
getByPath(storageId: string, path: string): Promise<GetFileByPathResponse>;
|
|
492
|
+
/**
|
|
493
|
+
* 경로로 파일 URL 가져오기
|
|
494
|
+
*
|
|
495
|
+
* 파일이 존재하지 않으면 null 반환
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* const url = await storage.getUrlByPath(storageId, '/profiles/user123/avatar.png')
|
|
500
|
+
* if (url) {
|
|
501
|
+
* console.log('이미지 URL:', url)
|
|
502
|
+
* }
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
getUrlByPath(storageId: string, path: string): Promise<string | null>;
|
|
439
506
|
}
|
|
440
507
|
|
|
441
508
|
/**
|
|
@@ -1028,6 +1095,7 @@ interface JoinRoomResponse {
|
|
|
1028
1095
|
declare class WebRTCAPI {
|
|
1029
1096
|
private http;
|
|
1030
1097
|
private webrtcUrl;
|
|
1098
|
+
private appId?;
|
|
1031
1099
|
private ws;
|
|
1032
1100
|
private state;
|
|
1033
1101
|
private stateListeners;
|
|
@@ -1047,7 +1115,7 @@ declare class WebRTCAPI {
|
|
|
1047
1115
|
private peerConnections;
|
|
1048
1116
|
private remoteStreams;
|
|
1049
1117
|
private iceServers;
|
|
1050
|
-
constructor(http: HttpClient, webrtcUrl: string);
|
|
1118
|
+
constructor(http: HttpClient, webrtcUrl: string, appId?: string);
|
|
1051
1119
|
/**
|
|
1052
1120
|
* ICE 서버 목록 조회
|
|
1053
1121
|
*/
|
|
@@ -2996,6 +3064,83 @@ declare class GameAPI {
|
|
|
2996
3064
|
private getHeaders;
|
|
2997
3065
|
}
|
|
2998
3066
|
|
|
3067
|
+
interface GoogleConnectionStatus {
|
|
3068
|
+
is_connected: boolean;
|
|
3069
|
+
email?: string;
|
|
3070
|
+
adsense_account_id?: string;
|
|
3071
|
+
}
|
|
3072
|
+
interface AdReportSummary {
|
|
3073
|
+
total_earnings: number;
|
|
3074
|
+
total_impressions: number;
|
|
3075
|
+
total_clicks: number;
|
|
3076
|
+
ctr: number;
|
|
3077
|
+
cpc: number;
|
|
3078
|
+
rpm: number;
|
|
3079
|
+
}
|
|
3080
|
+
interface DailyReport {
|
|
3081
|
+
date: string;
|
|
3082
|
+
earnings: number;
|
|
3083
|
+
impressions: number;
|
|
3084
|
+
clicks: number;
|
|
3085
|
+
ctr: number;
|
|
3086
|
+
}
|
|
3087
|
+
interface AdReportResponse {
|
|
3088
|
+
summary: AdReportSummary;
|
|
3089
|
+
daily: DailyReport[];
|
|
3090
|
+
}
|
|
3091
|
+
declare class AdsAPI {
|
|
3092
|
+
private http;
|
|
3093
|
+
constructor(http: HttpClient);
|
|
3094
|
+
/**
|
|
3095
|
+
* API Key 인증 시 /v1/public 접두사 반환
|
|
3096
|
+
*/
|
|
3097
|
+
private getPublicPrefix;
|
|
3098
|
+
/**
|
|
3099
|
+
* AdSense 연결 상태 확인
|
|
3100
|
+
*
|
|
3101
|
+
* @returns 연결 상태, 이메일, AdSense 계정 ID
|
|
3102
|
+
*
|
|
3103
|
+
* @example
|
|
3104
|
+
* ```typescript
|
|
3105
|
+
* const status = await cb.ads.getConnectionStatus()
|
|
3106
|
+
* if (status.is_connected) {
|
|
3107
|
+
* console.log('연결됨:', status.email)
|
|
3108
|
+
* }
|
|
3109
|
+
* ```
|
|
3110
|
+
*/
|
|
3111
|
+
getConnectionStatus(): Promise<GoogleConnectionStatus>;
|
|
3112
|
+
/**
|
|
3113
|
+
* AdSense 리포트 조회 (일별 데이터 + 요약)
|
|
3114
|
+
*
|
|
3115
|
+
* @param startDate - 시작일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3116
|
+
* @param endDate - 종료일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3117
|
+
* @returns 요약 + 일별 데이터
|
|
3118
|
+
*
|
|
3119
|
+
* @example
|
|
3120
|
+
* ```typescript
|
|
3121
|
+
* const report = await cb.ads.getReport('2025-01-01', '2025-01-31')
|
|
3122
|
+
* console.log('총 수익:', report.summary.total_earnings)
|
|
3123
|
+
* report.daily.forEach(day => {
|
|
3124
|
+
* console.log(`${day.date}: $${day.earnings}`)
|
|
3125
|
+
* })
|
|
3126
|
+
* ```
|
|
3127
|
+
*/
|
|
3128
|
+
getReport(startDate?: string, endDate?: string): Promise<AdReportResponse>;
|
|
3129
|
+
/**
|
|
3130
|
+
* 최근 30일 AdSense 요약 조회
|
|
3131
|
+
*
|
|
3132
|
+
* @returns 30일 합산 수익, 노출, 클릭, CTR, CPC, RPM
|
|
3133
|
+
*
|
|
3134
|
+
* @example
|
|
3135
|
+
* ```typescript
|
|
3136
|
+
* const summary = await cb.ads.getReportSummary()
|
|
3137
|
+
* console.log(`총 수익: $${summary.total_earnings}`)
|
|
3138
|
+
* console.log(`RPM: $${summary.rpm}`)
|
|
3139
|
+
* ```
|
|
3140
|
+
*/
|
|
3141
|
+
getReportSummary(): Promise<AdReportSummary>;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
2999
3144
|
/**
|
|
3000
3145
|
* WebTransport-based Game Client
|
|
3001
3146
|
*
|
|
@@ -3159,6 +3304,10 @@ interface ConnectBaseConfig {
|
|
|
3159
3304
|
* @example 'https://game.connectbase.world'
|
|
3160
3305
|
*/
|
|
3161
3306
|
gameUrl?: string;
|
|
3307
|
+
/**
|
|
3308
|
+
* 앱 ID (콘솔에서 확인, WebRTC 등 일부 기능에 필요)
|
|
3309
|
+
*/
|
|
3310
|
+
appId?: string;
|
|
3162
3311
|
/**
|
|
3163
3312
|
* API Key (콘솔에서 발급)
|
|
3164
3313
|
*/
|
|
@@ -3271,6 +3420,10 @@ declare class ConnectBase {
|
|
|
3271
3420
|
* 게임 API (실시간 멀티플레이어)
|
|
3272
3421
|
*/
|
|
3273
3422
|
readonly game: GameAPI;
|
|
3423
|
+
/**
|
|
3424
|
+
* 광고 API (Google AdSense)
|
|
3425
|
+
*/
|
|
3426
|
+
readonly ads: AdsAPI;
|
|
3274
3427
|
constructor(config?: ConnectBaseConfig);
|
|
3275
3428
|
/**
|
|
3276
3429
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -3286,4 +3439,4 @@ declare class ConnectBase {
|
|
|
3286
3439
|
updateConfig(config: Partial<ConnectBaseConfig>): void;
|
|
3287
3440
|
}
|
|
3288
3441
|
|
|
3289
|
-
export { ApiError, type ApiKeyItem, type AppStatsResponse, AuthError, type AuthSettingsResponse, type BillingCycle, type BillingKeyResponse, type BulkCreateResponse, type BulkError, type CancelPaymentRequest, type CancelPaymentResponse, type CancelSubscriptionRequest, type CategoryInfo, type Channel, type ChannelMembership, type ChargeWithBillingKeyRequest, type ChargeWithBillingKeyResponse, type ChatMessage, type ClientMessage, type ColumnSchema, type CommentListResponse, type ConfirmBillingKeyRequest, type ConfirmPaymentRequest, type ConfirmPaymentResponse, ConnectBase, type ConnectBaseConfig, type ConnectedData, type ConnectionState, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateChannelRequest, type CreateColumnRequest, type CreateDataRequest, type CreateFolderRequest, type CreateFolderResponse, type CreatePlaylistRequest, type CreateSubscriptionRequest, type CreateTableRequest, type DataItem, type DataType, type DeleteWhereResponse, type DeviceInfo, type EnabledProviderInfo, type EnabledProvidersResponse, type ErrorHandler, type ErrorMessage, type ErrorReport, type ErrorTrackerConfig, type ErrorType, type FetchApiKeysResponse, type FetchDataResponse, type FetchFilesResponse, type FileItem, GameAPI, type GameAction, type GameClientConfig, type GameConnectionState, type GameConnectionStatus, type GameDelta, type GameEventHandlers, type GamePlayer, GameRoom, type GameRoomConfig, type GameRoomInfo, GameRoomTransport, type GameServerMessage, type GameServerMessageType, type GameState, type GameTransportConfig, type GetAuthorizationURLResponse, type GuestMemberSignInResponse, type HistoryResponse, type ICEServer, type ICEServersResponse, type InitUploadResponse, type InvokeFunctionRequest, type InvokeFunctionResponse, type IssueBillingKeyRequest, type IssueBillingKeyResponse, type JoinRoomRequest, type JoinRoomResponse, type ListBillingKeysResponse, type ListSubscriptionPaymentsRequest, type ListSubscriptionPaymentsResponse, type ListSubscriptionsRequest, type ListSubscriptionsResponse, type MemberSignInRequest, type MemberSignInResponse, type MemberSignUpRequest, type MemberSignUpResponse, type MembershipTier, type MessageHandler, type MoveFileRequest, type OAuthCallbackResponse, type OAuthProvider, type PauseSubscriptionRequest, type PaymentDetail, type PaymentStatus, type PeerInfo, type PlayerEvent, type Playlist, type PlaylistItem, type PongMessage, type PreparePaymentRequest, type PreparePaymentResponse, type PushPlatform, type QualityProgress, type QueryOptions, type RealtimeConnectOptions, type RealtimeMessage, type RegisterDeviceRequest, type RenameFileRequest, type RenameFileResponse, type RoomInfo, type RoomStats, type RoomsResponse, type SendOptions, type ServerMessage, type Shorts, type ShortsListResponse, type SignalingMessage, type SignalingMessageType, type StateChange, type StateChangeHandler, type StreamDoneCallback, type StreamDoneData, type StreamErrorCallback, type StreamHandlers, type StreamMessage, type StreamOptions, type StreamSession, type StreamTokenCallback, type StreamURLResponse, type SubscribeOptions, type SubscribeTopicRequest, type SubscribedData, type Subscription, type SubscriptionPaymentResponse, type SubscriptionPaymentStatus, type SubscriptionResponse, type SubscriptionStatus, type SuperChat, type TableSchema, type TranscodeStatus, type TransportType, type UpdateApiKeyRequest, type UpdateApiKeyResponse, type UpdateBillingKeyRequest, type UpdateChannelRequest, type UpdateColumnRequest, type UpdateDataRequest, type UpdateSubscriptionRequest, type UpdateVideoRequest, type UploadFileResponse, type UploadOptions, type UploadProgress, type VAPIDPublicKeyResponse, type Video, type VideoComment, type VideoListOptions, type VideoListResponse, VideoProcessingError, type VideoQuality, type VideoStatus, type VideoVisibility, type WaitOptions, type WatchHistoryItem, type WebPushSubscription, type WebRTCConnectOptions, type WebRTCConnectionState, type WebRTCMode, type WhereCondition, type WhereOperator, ConnectBase as default, isWebTransportSupported };
|
|
3442
|
+
export { type AdReportResponse, type AdReportSummary, AdsAPI, ApiError, type ApiKeyItem, type AppStatsResponse, AuthError, type AuthSettingsResponse, type BillingCycle, type BillingKeyResponse, type BulkCreateResponse, type BulkError, type CancelPaymentRequest, type CancelPaymentResponse, type CancelSubscriptionRequest, type CategoryInfo, type Channel, type ChannelMembership, type ChargeWithBillingKeyRequest, type ChargeWithBillingKeyResponse, type ChatMessage, type ClientMessage, type ColumnSchema, type CommentListResponse, type ConfirmBillingKeyRequest, type ConfirmPaymentRequest, type ConfirmPaymentResponse, ConnectBase, type ConnectBaseConfig, type ConnectedData, type ConnectionState, type CreateApiKeyRequest, type CreateApiKeyResponse, type CreateChannelRequest, type CreateColumnRequest, type CreateDataRequest, type CreateFolderRequest, type CreateFolderResponse, type CreatePlaylistRequest, type CreateSubscriptionRequest, type CreateTableRequest, type DailyReport, type DataItem, type DataType, type DeleteWhereResponse, type DeviceInfo, type EnabledProviderInfo, type EnabledProvidersResponse, type ErrorHandler, type ErrorMessage, type ErrorReport, type ErrorTrackerConfig, type ErrorType, type FetchApiKeysResponse, type FetchDataResponse, type FetchFilesResponse, type FileItem, GameAPI, type GameAction, type GameClientConfig, type GameConnectionState, type GameConnectionStatus, type GameDelta, type GameEventHandlers, type GamePlayer, GameRoom, type GameRoomConfig, type GameRoomInfo, GameRoomTransport, type GameServerMessage, type GameServerMessageType, type GameState, type GameTransportConfig, type GetAuthorizationURLResponse, type GetFileByPathResponse, type GoogleConnectionStatus, type GuestMemberSignInResponse, type HistoryResponse, type ICEServer, type ICEServersResponse, type InitUploadResponse, type InvokeFunctionRequest, type InvokeFunctionResponse, type IssueBillingKeyRequest, type IssueBillingKeyResponse, type JoinRoomRequest, type JoinRoomResponse, type ListBillingKeysResponse, type ListSubscriptionPaymentsRequest, type ListSubscriptionPaymentsResponse, type ListSubscriptionsRequest, type ListSubscriptionsResponse, type MemberSignInRequest, type MemberSignInResponse, type MemberSignUpRequest, type MemberSignUpResponse, type MembershipTier, type MessageHandler, type MoveFileRequest, type OAuthCallbackResponse, type OAuthProvider, type PauseSubscriptionRequest, type PaymentDetail, type PaymentStatus, type PeerInfo, type PlayerEvent, type Playlist, type PlaylistItem, type PongMessage, type PreparePaymentRequest, type PreparePaymentResponse, type PushPlatform, type QualityProgress, type QueryOptions, type RealtimeConnectOptions, type RealtimeMessage, type RegisterDeviceRequest, type RenameFileRequest, type RenameFileResponse, type RoomInfo, type RoomStats, type RoomsResponse, type SendOptions, type ServerMessage, type Shorts, type ShortsListResponse, type SignalingMessage, type SignalingMessageType, type StateChange, type StateChangeHandler, type StreamDoneCallback, type StreamDoneData, type StreamErrorCallback, type StreamHandlers, type StreamMessage, type StreamOptions, type StreamSession, type StreamTokenCallback, type StreamURLResponse, type SubscribeOptions, type SubscribeTopicRequest, type SubscribedData, type Subscription, type SubscriptionPaymentResponse, type SubscriptionPaymentStatus, type SubscriptionResponse, type SubscriptionStatus, type SuperChat, type TableSchema, type TranscodeStatus, type TransportType, type UpdateApiKeyRequest, type UpdateApiKeyResponse, type UpdateBillingKeyRequest, type UpdateChannelRequest, type UpdateColumnRequest, type UpdateDataRequest, type UpdateSubscriptionRequest, type UpdateVideoRequest, type UploadByPathOptions, type UploadFileResponse, type UploadOptions, type UploadProgress, type VAPIDPublicKeyResponse, type Video, type VideoComment, type VideoListOptions, type VideoListResponse, VideoProcessingError, type VideoQuality, type VideoStatus, type VideoVisibility, type WaitOptions, type WatchHistoryItem, type WebPushSubscription, type WebRTCConnectOptions, type WebRTCConnectionState, type WebRTCMode, type WhereCondition, type WhereOperator, ConnectBase as default, isWebTransportSupported };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AdsAPI: () => AdsAPI,
|
|
23
24
|
ApiError: () => ApiError,
|
|
24
25
|
AuthError: () => AuthError,
|
|
25
26
|
ConnectBase: () => ConnectBase,
|
|
@@ -707,6 +708,75 @@ var StorageAPI = class {
|
|
|
707
708
|
isImageFile(file) {
|
|
708
709
|
return file.mime_type?.startsWith("image/") || false;
|
|
709
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* 경로 기반 파일 업로드 (Firebase Storage 스타일)
|
|
713
|
+
*
|
|
714
|
+
* 같은 경로에 파일이 이미 존재하면 덮어쓰기합니다.
|
|
715
|
+
* URL이 변경되지 않아 고정 URL이 필요한 경우에 유용합니다.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* // 프로필 이미지 업로드 (항상 같은 URL 유지)
|
|
720
|
+
* const result = await storage.uploadByPath(
|
|
721
|
+
* storageId,
|
|
722
|
+
* '/profiles/user123/avatar.png',
|
|
723
|
+
* file
|
|
724
|
+
* )
|
|
725
|
+
* console.log(result.url) // 항상 동일한 URL
|
|
726
|
+
*
|
|
727
|
+
* // 같은 경로로 다시 업로드하면 파일만 교체
|
|
728
|
+
* await storage.uploadByPath(storageId, '/profiles/user123/avatar.png', newFile)
|
|
729
|
+
* ```
|
|
730
|
+
*/
|
|
731
|
+
async uploadByPath(storageId, path, file, options) {
|
|
732
|
+
const prefix = this.getPublicPrefix();
|
|
733
|
+
const formData = new FormData();
|
|
734
|
+
formData.append("file", file);
|
|
735
|
+
const overwrite = options?.overwrite !== false;
|
|
736
|
+
formData.append("overwrite", overwrite.toString());
|
|
737
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
738
|
+
return this.http.post(
|
|
739
|
+
`${prefix}/storages/files/${storageId}/path/${cleanPath}`,
|
|
740
|
+
formData
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 경로로 파일 조회
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* const file = await storage.getByPath(storageId, '/profiles/user123/avatar.png')
|
|
749
|
+
* console.log(file.url) // 파일의 공개 URL
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
async getByPath(storageId, path) {
|
|
753
|
+
const prefix = this.getPublicPrefix();
|
|
754
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
755
|
+
return this.http.get(
|
|
756
|
+
`${prefix}/storages/files/${storageId}/path/${cleanPath}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* 경로로 파일 URL 가져오기
|
|
761
|
+
*
|
|
762
|
+
* 파일이 존재하지 않으면 null 반환
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* ```ts
|
|
766
|
+
* const url = await storage.getUrlByPath(storageId, '/profiles/user123/avatar.png')
|
|
767
|
+
* if (url) {
|
|
768
|
+
* console.log('이미지 URL:', url)
|
|
769
|
+
* }
|
|
770
|
+
* ```
|
|
771
|
+
*/
|
|
772
|
+
async getUrlByPath(storageId, path) {
|
|
773
|
+
try {
|
|
774
|
+
const file = await this.getByPath(storageId, path);
|
|
775
|
+
return file.url || null;
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
710
780
|
};
|
|
711
781
|
|
|
712
782
|
// src/api/api-key.ts
|
|
@@ -1296,7 +1366,7 @@ var RealtimeAPI = class {
|
|
|
1296
1366
|
|
|
1297
1367
|
// src/api/webrtc.ts
|
|
1298
1368
|
var WebRTCAPI = class {
|
|
1299
|
-
constructor(http, webrtcUrl) {
|
|
1369
|
+
constructor(http, webrtcUrl, appId) {
|
|
1300
1370
|
this.ws = null;
|
|
1301
1371
|
this.state = "disconnected";
|
|
1302
1372
|
this.stateListeners = [];
|
|
@@ -1320,6 +1390,7 @@ var WebRTCAPI = class {
|
|
|
1320
1390
|
this.iceServers = [];
|
|
1321
1391
|
this.http = http;
|
|
1322
1392
|
this.webrtcUrl = webrtcUrl;
|
|
1393
|
+
this.appId = appId;
|
|
1323
1394
|
}
|
|
1324
1395
|
/**
|
|
1325
1396
|
* ICE 서버 목록 조회
|
|
@@ -1421,7 +1492,10 @@ var WebRTCAPI = class {
|
|
|
1421
1492
|
} else if (apiKey) {
|
|
1422
1493
|
authParam = `api_key=${encodeURIComponent(apiKey)}`;
|
|
1423
1494
|
}
|
|
1424
|
-
|
|
1495
|
+
if (!this.appId) {
|
|
1496
|
+
throw new Error("WebRTC \uC5F0\uACB0\uC5D0\uB294 appId\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId\uB97C \uC124\uC815\uD558\uC138\uC694.");
|
|
1497
|
+
}
|
|
1498
|
+
return `${wsBase}/v1/apps/${this.appId}/signaling?${authParam}`;
|
|
1425
1499
|
}
|
|
1426
1500
|
async handleSignalingMessage(msg, resolve, reject) {
|
|
1427
1501
|
switch (msg.type) {
|
|
@@ -2280,7 +2354,10 @@ var PaymentAPI = class {
|
|
|
2280
2354
|
*/
|
|
2281
2355
|
async cancel(paymentId, data) {
|
|
2282
2356
|
const prefix = this.getPublicPrefix();
|
|
2283
|
-
return this.http.post(`${prefix}/payments
|
|
2357
|
+
return this.http.post(`${prefix}/payments/cancel`, {
|
|
2358
|
+
payment_id: paymentId,
|
|
2359
|
+
...data
|
|
2360
|
+
});
|
|
2284
2361
|
}
|
|
2285
2362
|
/**
|
|
2286
2363
|
* 주문 ID로 결제 조회
|
|
@@ -2337,7 +2414,7 @@ var SubscriptionAPI = class {
|
|
|
2337
2414
|
*/
|
|
2338
2415
|
async issueBillingKey() {
|
|
2339
2416
|
const prefix = this.getPublicPrefix();
|
|
2340
|
-
return this.http.post(`${prefix}/subscriptions/billing-keys
|
|
2417
|
+
return this.http.post(`${prefix}/subscriptions/billing-keys`, {});
|
|
2341
2418
|
}
|
|
2342
2419
|
/**
|
|
2343
2420
|
* 빌링키 등록 확인
|
|
@@ -3848,6 +3925,78 @@ var GameAPI = class {
|
|
|
3848
3925
|
}
|
|
3849
3926
|
};
|
|
3850
3927
|
|
|
3928
|
+
// src/api/ads.ts
|
|
3929
|
+
var AdsAPI = class {
|
|
3930
|
+
constructor(http) {
|
|
3931
|
+
this.http = http;
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* API Key 인증 시 /v1/public 접두사 반환
|
|
3935
|
+
*/
|
|
3936
|
+
getPublicPrefix() {
|
|
3937
|
+
return this.http.hasApiKey() ? "/v1/public" : "/v1";
|
|
3938
|
+
}
|
|
3939
|
+
/**
|
|
3940
|
+
* AdSense 연결 상태 확인
|
|
3941
|
+
*
|
|
3942
|
+
* @returns 연결 상태, 이메일, AdSense 계정 ID
|
|
3943
|
+
*
|
|
3944
|
+
* @example
|
|
3945
|
+
* ```typescript
|
|
3946
|
+
* const status = await cb.ads.getConnectionStatus()
|
|
3947
|
+
* if (status.is_connected) {
|
|
3948
|
+
* console.log('연결됨:', status.email)
|
|
3949
|
+
* }
|
|
3950
|
+
* ```
|
|
3951
|
+
*/
|
|
3952
|
+
async getConnectionStatus() {
|
|
3953
|
+
const prefix = this.getPublicPrefix();
|
|
3954
|
+
return this.http.get(`${prefix}/ads/connection`);
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* AdSense 리포트 조회 (일별 데이터 + 요약)
|
|
3958
|
+
*
|
|
3959
|
+
* @param startDate - 시작일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3960
|
+
* @param endDate - 종료일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3961
|
+
* @returns 요약 + 일별 데이터
|
|
3962
|
+
*
|
|
3963
|
+
* @example
|
|
3964
|
+
* ```typescript
|
|
3965
|
+
* const report = await cb.ads.getReport('2025-01-01', '2025-01-31')
|
|
3966
|
+
* console.log('총 수익:', report.summary.total_earnings)
|
|
3967
|
+
* report.daily.forEach(day => {
|
|
3968
|
+
* console.log(`${day.date}: $${day.earnings}`)
|
|
3969
|
+
* })
|
|
3970
|
+
* ```
|
|
3971
|
+
*/
|
|
3972
|
+
async getReport(startDate, endDate) {
|
|
3973
|
+
const prefix = this.getPublicPrefix();
|
|
3974
|
+
const params = new URLSearchParams();
|
|
3975
|
+
if (startDate) params.set("start", startDate);
|
|
3976
|
+
if (endDate) params.set("end", endDate);
|
|
3977
|
+
const query = params.toString();
|
|
3978
|
+
return this.http.get(
|
|
3979
|
+
`${prefix}/ads/reports${query ? `?${query}` : ""}`
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
/**
|
|
3983
|
+
* 최근 30일 AdSense 요약 조회
|
|
3984
|
+
*
|
|
3985
|
+
* @returns 30일 합산 수익, 노출, 클릭, CTR, CPC, RPM
|
|
3986
|
+
*
|
|
3987
|
+
* @example
|
|
3988
|
+
* ```typescript
|
|
3989
|
+
* const summary = await cb.ads.getReportSummary()
|
|
3990
|
+
* console.log(`총 수익: $${summary.total_earnings}`)
|
|
3991
|
+
* console.log(`RPM: $${summary.rpm}`)
|
|
3992
|
+
* ```
|
|
3993
|
+
*/
|
|
3994
|
+
async getReportSummary() {
|
|
3995
|
+
const prefix = this.getPublicPrefix();
|
|
3996
|
+
return this.http.get(`${prefix}/ads/reports/summary`);
|
|
3997
|
+
}
|
|
3998
|
+
};
|
|
3999
|
+
|
|
3851
4000
|
// src/api/game-transport.ts
|
|
3852
4001
|
var WebTransportTransport = class {
|
|
3853
4002
|
constructor(config, onMessage, onClose, onError) {
|
|
@@ -4471,7 +4620,7 @@ var ConnectBase = class {
|
|
|
4471
4620
|
this.apiKey = new ApiKeyAPI(this.http);
|
|
4472
4621
|
this.functions = new FunctionsAPI(this.http);
|
|
4473
4622
|
this.realtime = new RealtimeAPI(this.http, config.socketUrl || DEFAULT_SOCKET_URL);
|
|
4474
|
-
this.webrtc = new WebRTCAPI(this.http, config.webrtcUrl || DEFAULT_WEBRTC_URL);
|
|
4623
|
+
this.webrtc = new WebRTCAPI(this.http, config.webrtcUrl || DEFAULT_WEBRTC_URL, config.appId);
|
|
4475
4624
|
this.errorTracker = new ErrorTrackerAPI(this.http, config.errorTracker);
|
|
4476
4625
|
this.oauth = new OAuthAPI(this.http);
|
|
4477
4626
|
this.payment = new PaymentAPI(this.http);
|
|
@@ -4479,6 +4628,7 @@ var ConnectBase = class {
|
|
|
4479
4628
|
this.push = new PushAPI(this.http);
|
|
4480
4629
|
this.video = new VideoAPI(this.http, config.videoUrl || DEFAULT_VIDEO_URL);
|
|
4481
4630
|
this.game = new GameAPI(this.http, config.gameUrl || DEFAULT_GAME_URL);
|
|
4631
|
+
this.ads = new AdsAPI(this.http);
|
|
4482
4632
|
}
|
|
4483
4633
|
/**
|
|
4484
4634
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -4502,6 +4652,7 @@ var ConnectBase = class {
|
|
|
4502
4652
|
var index_default = ConnectBase;
|
|
4503
4653
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4504
4654
|
0 && (module.exports = {
|
|
4655
|
+
AdsAPI,
|
|
4505
4656
|
ApiError,
|
|
4506
4657
|
AuthError,
|
|
4507
4658
|
ConnectBase,
|
package/dist/index.mjs
CHANGED
|
@@ -673,6 +673,75 @@ var StorageAPI = class {
|
|
|
673
673
|
isImageFile(file) {
|
|
674
674
|
return file.mime_type?.startsWith("image/") || false;
|
|
675
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* 경로 기반 파일 업로드 (Firebase Storage 스타일)
|
|
678
|
+
*
|
|
679
|
+
* 같은 경로에 파일이 이미 존재하면 덮어쓰기합니다.
|
|
680
|
+
* URL이 변경되지 않아 고정 URL이 필요한 경우에 유용합니다.
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* ```ts
|
|
684
|
+
* // 프로필 이미지 업로드 (항상 같은 URL 유지)
|
|
685
|
+
* const result = await storage.uploadByPath(
|
|
686
|
+
* storageId,
|
|
687
|
+
* '/profiles/user123/avatar.png',
|
|
688
|
+
* file
|
|
689
|
+
* )
|
|
690
|
+
* console.log(result.url) // 항상 동일한 URL
|
|
691
|
+
*
|
|
692
|
+
* // 같은 경로로 다시 업로드하면 파일만 교체
|
|
693
|
+
* await storage.uploadByPath(storageId, '/profiles/user123/avatar.png', newFile)
|
|
694
|
+
* ```
|
|
695
|
+
*/
|
|
696
|
+
async uploadByPath(storageId, path, file, options) {
|
|
697
|
+
const prefix = this.getPublicPrefix();
|
|
698
|
+
const formData = new FormData();
|
|
699
|
+
formData.append("file", file);
|
|
700
|
+
const overwrite = options?.overwrite !== false;
|
|
701
|
+
formData.append("overwrite", overwrite.toString());
|
|
702
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
703
|
+
return this.http.post(
|
|
704
|
+
`${prefix}/storages/files/${storageId}/path/${cleanPath}`,
|
|
705
|
+
formData
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* 경로로 파일 조회
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* const file = await storage.getByPath(storageId, '/profiles/user123/avatar.png')
|
|
714
|
+
* console.log(file.url) // 파일의 공개 URL
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
async getByPath(storageId, path) {
|
|
718
|
+
const prefix = this.getPublicPrefix();
|
|
719
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
720
|
+
return this.http.get(
|
|
721
|
+
`${prefix}/storages/files/${storageId}/path/${cleanPath}`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* 경로로 파일 URL 가져오기
|
|
726
|
+
*
|
|
727
|
+
* 파일이 존재하지 않으면 null 반환
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```ts
|
|
731
|
+
* const url = await storage.getUrlByPath(storageId, '/profiles/user123/avatar.png')
|
|
732
|
+
* if (url) {
|
|
733
|
+
* console.log('이미지 URL:', url)
|
|
734
|
+
* }
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
async getUrlByPath(storageId, path) {
|
|
738
|
+
try {
|
|
739
|
+
const file = await this.getByPath(storageId, path);
|
|
740
|
+
return file.url || null;
|
|
741
|
+
} catch {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
676
745
|
};
|
|
677
746
|
|
|
678
747
|
// src/api/api-key.ts
|
|
@@ -1262,7 +1331,7 @@ var RealtimeAPI = class {
|
|
|
1262
1331
|
|
|
1263
1332
|
// src/api/webrtc.ts
|
|
1264
1333
|
var WebRTCAPI = class {
|
|
1265
|
-
constructor(http, webrtcUrl) {
|
|
1334
|
+
constructor(http, webrtcUrl, appId) {
|
|
1266
1335
|
this.ws = null;
|
|
1267
1336
|
this.state = "disconnected";
|
|
1268
1337
|
this.stateListeners = [];
|
|
@@ -1286,6 +1355,7 @@ var WebRTCAPI = class {
|
|
|
1286
1355
|
this.iceServers = [];
|
|
1287
1356
|
this.http = http;
|
|
1288
1357
|
this.webrtcUrl = webrtcUrl;
|
|
1358
|
+
this.appId = appId;
|
|
1289
1359
|
}
|
|
1290
1360
|
/**
|
|
1291
1361
|
* ICE 서버 목록 조회
|
|
@@ -1387,7 +1457,10 @@ var WebRTCAPI = class {
|
|
|
1387
1457
|
} else if (apiKey) {
|
|
1388
1458
|
authParam = `api_key=${encodeURIComponent(apiKey)}`;
|
|
1389
1459
|
}
|
|
1390
|
-
|
|
1460
|
+
if (!this.appId) {
|
|
1461
|
+
throw new Error("WebRTC \uC5F0\uACB0\uC5D0\uB294 appId\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId\uB97C \uC124\uC815\uD558\uC138\uC694.");
|
|
1462
|
+
}
|
|
1463
|
+
return `${wsBase}/v1/apps/${this.appId}/signaling?${authParam}`;
|
|
1391
1464
|
}
|
|
1392
1465
|
async handleSignalingMessage(msg, resolve, reject) {
|
|
1393
1466
|
switch (msg.type) {
|
|
@@ -2246,7 +2319,10 @@ var PaymentAPI = class {
|
|
|
2246
2319
|
*/
|
|
2247
2320
|
async cancel(paymentId, data) {
|
|
2248
2321
|
const prefix = this.getPublicPrefix();
|
|
2249
|
-
return this.http.post(`${prefix}/payments
|
|
2322
|
+
return this.http.post(`${prefix}/payments/cancel`, {
|
|
2323
|
+
payment_id: paymentId,
|
|
2324
|
+
...data
|
|
2325
|
+
});
|
|
2250
2326
|
}
|
|
2251
2327
|
/**
|
|
2252
2328
|
* 주문 ID로 결제 조회
|
|
@@ -2303,7 +2379,7 @@ var SubscriptionAPI = class {
|
|
|
2303
2379
|
*/
|
|
2304
2380
|
async issueBillingKey() {
|
|
2305
2381
|
const prefix = this.getPublicPrefix();
|
|
2306
|
-
return this.http.post(`${prefix}/subscriptions/billing-keys
|
|
2382
|
+
return this.http.post(`${prefix}/subscriptions/billing-keys`, {});
|
|
2307
2383
|
}
|
|
2308
2384
|
/**
|
|
2309
2385
|
* 빌링키 등록 확인
|
|
@@ -3814,6 +3890,78 @@ var GameAPI = class {
|
|
|
3814
3890
|
}
|
|
3815
3891
|
};
|
|
3816
3892
|
|
|
3893
|
+
// src/api/ads.ts
|
|
3894
|
+
var AdsAPI = class {
|
|
3895
|
+
constructor(http) {
|
|
3896
|
+
this.http = http;
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* API Key 인증 시 /v1/public 접두사 반환
|
|
3900
|
+
*/
|
|
3901
|
+
getPublicPrefix() {
|
|
3902
|
+
return this.http.hasApiKey() ? "/v1/public" : "/v1";
|
|
3903
|
+
}
|
|
3904
|
+
/**
|
|
3905
|
+
* AdSense 연결 상태 확인
|
|
3906
|
+
*
|
|
3907
|
+
* @returns 연결 상태, 이메일, AdSense 계정 ID
|
|
3908
|
+
*
|
|
3909
|
+
* @example
|
|
3910
|
+
* ```typescript
|
|
3911
|
+
* const status = await cb.ads.getConnectionStatus()
|
|
3912
|
+
* if (status.is_connected) {
|
|
3913
|
+
* console.log('연결됨:', status.email)
|
|
3914
|
+
* }
|
|
3915
|
+
* ```
|
|
3916
|
+
*/
|
|
3917
|
+
async getConnectionStatus() {
|
|
3918
|
+
const prefix = this.getPublicPrefix();
|
|
3919
|
+
return this.http.get(`${prefix}/ads/connection`);
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* AdSense 리포트 조회 (일별 데이터 + 요약)
|
|
3923
|
+
*
|
|
3924
|
+
* @param startDate - 시작일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3925
|
+
* @param endDate - 종료일 (YYYY-MM-DD), 미지정 시 최근 30일
|
|
3926
|
+
* @returns 요약 + 일별 데이터
|
|
3927
|
+
*
|
|
3928
|
+
* @example
|
|
3929
|
+
* ```typescript
|
|
3930
|
+
* const report = await cb.ads.getReport('2025-01-01', '2025-01-31')
|
|
3931
|
+
* console.log('총 수익:', report.summary.total_earnings)
|
|
3932
|
+
* report.daily.forEach(day => {
|
|
3933
|
+
* console.log(`${day.date}: $${day.earnings}`)
|
|
3934
|
+
* })
|
|
3935
|
+
* ```
|
|
3936
|
+
*/
|
|
3937
|
+
async getReport(startDate, endDate) {
|
|
3938
|
+
const prefix = this.getPublicPrefix();
|
|
3939
|
+
const params = new URLSearchParams();
|
|
3940
|
+
if (startDate) params.set("start", startDate);
|
|
3941
|
+
if (endDate) params.set("end", endDate);
|
|
3942
|
+
const query = params.toString();
|
|
3943
|
+
return this.http.get(
|
|
3944
|
+
`${prefix}/ads/reports${query ? `?${query}` : ""}`
|
|
3945
|
+
);
|
|
3946
|
+
}
|
|
3947
|
+
/**
|
|
3948
|
+
* 최근 30일 AdSense 요약 조회
|
|
3949
|
+
*
|
|
3950
|
+
* @returns 30일 합산 수익, 노출, 클릭, CTR, CPC, RPM
|
|
3951
|
+
*
|
|
3952
|
+
* @example
|
|
3953
|
+
* ```typescript
|
|
3954
|
+
* const summary = await cb.ads.getReportSummary()
|
|
3955
|
+
* console.log(`총 수익: $${summary.total_earnings}`)
|
|
3956
|
+
* console.log(`RPM: $${summary.rpm}`)
|
|
3957
|
+
* ```
|
|
3958
|
+
*/
|
|
3959
|
+
async getReportSummary() {
|
|
3960
|
+
const prefix = this.getPublicPrefix();
|
|
3961
|
+
return this.http.get(`${prefix}/ads/reports/summary`);
|
|
3962
|
+
}
|
|
3963
|
+
};
|
|
3964
|
+
|
|
3817
3965
|
// src/api/game-transport.ts
|
|
3818
3966
|
var WebTransportTransport = class {
|
|
3819
3967
|
constructor(config, onMessage, onClose, onError) {
|
|
@@ -4437,7 +4585,7 @@ var ConnectBase = class {
|
|
|
4437
4585
|
this.apiKey = new ApiKeyAPI(this.http);
|
|
4438
4586
|
this.functions = new FunctionsAPI(this.http);
|
|
4439
4587
|
this.realtime = new RealtimeAPI(this.http, config.socketUrl || DEFAULT_SOCKET_URL);
|
|
4440
|
-
this.webrtc = new WebRTCAPI(this.http, config.webrtcUrl || DEFAULT_WEBRTC_URL);
|
|
4588
|
+
this.webrtc = new WebRTCAPI(this.http, config.webrtcUrl || DEFAULT_WEBRTC_URL, config.appId);
|
|
4441
4589
|
this.errorTracker = new ErrorTrackerAPI(this.http, config.errorTracker);
|
|
4442
4590
|
this.oauth = new OAuthAPI(this.http);
|
|
4443
4591
|
this.payment = new PaymentAPI(this.http);
|
|
@@ -4445,6 +4593,7 @@ var ConnectBase = class {
|
|
|
4445
4593
|
this.push = new PushAPI(this.http);
|
|
4446
4594
|
this.video = new VideoAPI(this.http, config.videoUrl || DEFAULT_VIDEO_URL);
|
|
4447
4595
|
this.game = new GameAPI(this.http, config.gameUrl || DEFAULT_GAME_URL);
|
|
4596
|
+
this.ads = new AdsAPI(this.http);
|
|
4448
4597
|
}
|
|
4449
4598
|
/**
|
|
4450
4599
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -4467,6 +4616,7 @@ var ConnectBase = class {
|
|
|
4467
4616
|
};
|
|
4468
4617
|
var index_default = ConnectBase;
|
|
4469
4618
|
export {
|
|
4619
|
+
AdsAPI,
|
|
4470
4620
|
ApiError,
|
|
4471
4621
|
AuthError,
|
|
4472
4622
|
ConnectBase,
|