connectbase-client 3.30.0 → 3.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  본 SDK 의 모든 주요 변경사항을 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 형식으로 기록합니다.
4
4
  버전은 [Semantic Versioning](https://semver.org/lang/ko/) 을 따릅니다.
5
5
 
6
+ ## [3.31.0] - 2026-06-05
7
+
8
+ ### Added — OAuth 리다이렉트 콜백 **부팅 자동 소비** 안전망
9
+
10
+ 소셜 로그인(구글 등) 리다이렉트 후 "로그인했는데 자꾸 로그인 페이지로 되돌아오는" 무한 루프를
11
+ SDK 레벨에서 차단한다. 여러 사용자가 동시에 보고한 회귀의 근본 안전망.
12
+
13
+ - **근본 원인**: 리다이렉트 방식은 콜백 URL(`?access_token=&refresh_token=&member_id=`)에서
14
+ 앱이 `cb.oauth.getCallbackResult()` 를 호출해야 토큰이 적재되고 cookie 가 부트스트랩된다.
15
+ 그런데 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 경로에 홈
16
+ `index.html` 을 서빙하므로, 앱이 콜백 처리 코드를 빠뜨리면 URL 토큰이 **영영 소비되지 않아**
17
+ 세션이 확립되지 않고 인증 가드가 다시 로그인으로 되돌린다.
18
+ - **변경**: `new ConnectBase()` 생성자가 토큰-in-URL 리다이렉트 콜백(팝업 제외)을 감지하면
19
+ `oauth.consumeRedirectCallbackOnBoot()` 를 boot-restore promise 로 등록해 **자동으로 토큰을
20
+ 적재 + cookie 부트스트랩**한다. 앱이 `getCallbackResult()` 를 호출하지 않아도 세션이 확립된다.
21
+ `prepareHeaders` 가 이 promise 를 await 하므로 첫 `getMe()`(인증 가드 등)가 세션 확립 후 발화.
22
+ - **하위호환·안전성**: 앱이 이후 `getCallbackResult()` 를 호출해도 같은 promise 를 공유해 **이중
23
+ bootstrap/rotation 을 방지**(boot 소비 실패 시에만 직접 적재로 폴백). 팝업(`window.opener`)·
24
+ code-only(`?code=`)·에러(`?error=`) 콜백은 제외 — 기존 동작 유지. `autoRestoreSession: false`
25
+ 면 자동 소비도 끈다. token rotation race(2026-05-16) 없음: 사전 re-issue 없이 URL 토큰을 직접
26
+ 소비(=getCallbackResult 리다이렉트 경로와 동일)하기 때문.
27
+
28
+ > 이미 배포된 앱도 CDN 번들(`connect-base.min.js`) / npm `latest` 갱신 시 코드 수정 없이 복구됩니다.
29
+
6
30
  ## [3.29.0] - 2026-06-02
7
31
 
8
32
  ### Added — `connectbase tunnel --token <고정값>` proxy_token 핀 (platform-issue 019e8623)
package/README.md CHANGED
@@ -1048,9 +1048,12 @@ HTTP response (for example, Discord Interactions requires a `200` with a
1048
1048
  JSON body within 3 seconds):
1049
1049
 
1050
1050
  ```javascript
1051
- export async function handler(event, ctx) {
1052
- // event.method / event.path / event.query / event.headers / event.body
1053
- // are populated for webhook invocations.
1051
+ export async function handler(rawBody, ctx) {
1052
+ // The first arg is the raw request body as a UTF-8 string (parse it yourself:
1053
+ // JSON.parse(rawBody) for JSON, new URLSearchParams(rawBody) for form-encoded).
1054
+ // ctx.method / ctx.path / ctx.query / ctx.headers / ctx.rawBody (Buffer) are
1055
+ // populated for webhook invocations. The first arg is the body itself (a
1056
+ // string), not a request object with a `.body` field.
1054
1057
  return {
1055
1058
  statusCode: 200,
1056
1059
  headers: { 'Content-Type': 'application/json' },
@@ -1,5 +1,5 @@
1
1
  "use strict";var ConnectBaseModule=(()=>{var ge=Object.defineProperty;var qe=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Le=Object.prototype.hasOwnProperty;var He=(a,e)=>{for(var t in e)ge(a,t,{get:e[t],enumerable:!0})},Be=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of De(e))!Le.call(a,r)&&r!==t&&ge(a,r,{get:()=>e[r],enumerable:!(s=qe(e,r))||s.enumerable});return a};var Fe=a=>Be(ge({},"__esModule",{value:!0}),a);var st={};He(st,{AIAPI:()=>O,AUTH_MEMBER_ID_TOKEN:()=>Je,AdsAPI:()=>M,ApiError:()=>g,AuthError:()=>k,ConnectBase:()=>pe,EndpointAPI:()=>q,GameAPI:()=>A,GameConfigAPI:()=>$,GameError:()=>m,GameRoom:()=>L,GameRoomTransport:()=>ue,GuestSessionConflictError:()=>E,NativeAPI:()=>U,SessionManager:()=>F,VideoProcessingError:()=>x,default:()=>tt,detectInAppBrowser:()=>D,escapeToExternalBrowser:()=>Z,isWebTransportSupported:()=>Pe,toCreateRoomWire:()=>H});var g=class extends Error{constructor(t,s,r,i){super(s);this.statusCode=t;this.name="ApiError",this.code=r,this.details=i}},k=class extends Error{constructor(e){super(e),this.name="AuthError"}},E=class extends Error{constructor(){super("signInAsGuestMember refused: an active non-guest session is present in this client. Pass { allowOverrideExistingSession: true } to explicitly replace it, or call signOut() first.");this.code="GUEST_SESSION_CONFLICT";this.name="GuestSessionConflictError"}},m=class extends Error{constructor(e){super(e.message||e.code||"GameError"),this.name="GameError",this.code=e.code||"UNKNOWN",this.phase=e.phase,this.feature=e.feature,this.roomId=e.roomId,this.scriptId=e.scriptId,this.originClientId=e.originClientId,this.requested=e.requested,this.available=e.available}};function I(a={}){let e=new AbortController,t=a.timeout??3e4,s=a.signal,r=null,i=null;return s&&(s.aborted?e.abort(s.reason):(i=()=>e.abort(s.reason),s.addEventListener("abort",i,{once:!0}))),t>0&&Number.isFinite(t)&&(r=setTimeout(()=>{e.abort(new DOMException(`Request timed out after ${t}ms`,"TimeoutError"))},t)),{signal:e.signal,cleanup:()=>{r!==null&&clearTimeout(r),s&&i&&s.removeEventListener("abort",i)}}}var N=class{constructor(e=20){this.buf=[];this.capacity=Math.min(Math.max(1,e),50)}push(e){this.buf.length>=this.capacity&&this.buf.shift(),this.buf.push(e)}snapshot(){return this.buf.slice()}clear(){this.buf=[]}};function _e(a){try{return(a.startsWith("http")?new URL(a):new URL(a,"http://x")).pathname||a}catch{let e=a.indexOf("?");return e>=0?a.slice(0,e):a}}var Re="cb_auth_tokens";function me(a){try{let e=a.split(".")[1];if(!e)return null;let t=e.replace(/-/g,"+").replace(/_/g,"/"),s=t+"=".repeat((4-t.length%4)%4),r=JSON.parse(atob(s));return r&&typeof r=="object"?r:null}catch{return null}}function Se(a){let e=me(a)?.role;return e==="User"||e==="AdminInvite"}var V=class{constructor(e){this.isRefreshing=!1;this.refreshPromise=null;this.refreshFailureCount=0;this.refreshLockedUntil=0;this.recentCalls=new N;this.bootRestorePromise=null;this.config={...e},this.storageKey=this.buildStorageKey(),this.warnIfUnsafePersistence(),this.restoreTokens()}setBootRestorePromise(e){this.bootRestorePromise=e.catch(()=>!1)}getRecentCalls(){return this.recentCalls.snapshot()}clearRecentCalls(){this.recentCalls.clear()}warnIfUnsafePersistence(){typeof window>"u"||(this.config.persistence==="localStorage"?console.warn(`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC refresh token \uC601\uAD6C \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') \uC744 \uC0AC\uC6A9\uD558\uBA74 \uC11C\uBC84\uAC00 \uBC1C\uAE09\uD55C HttpOnly cookie \uB85C\uB9CC refresh token \uC774 \uBCF4\uAD00\uB418\uC5B4 JS \uAC00 \uC811\uADFC\uD560 \uC218 \uC5C6\uACE0, \uC0C8\uB85C\uACE0\uCE68 \uD6C4\uC5D0\uB3C4 autoRestoreSession \uC73C\uB85C \uC790\uB3D9 \uBCF5\uAD6C\uB429\uB2C8\uB2E4.`):this.config.persistence==="sessionStorage"&&console.warn(`[connect-base-client] persistence="sessionStorage" \uB294 XSS \uC2DC \uD0ED \uC138\uC158 \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8\uAC12('none') + HttpOnly cookie \uD750\uB984\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.`))}updateConfig(e){this.config={...this.config,...e}}setTokens(e,t){this.config.accessToken=e,this.config.refreshToken=t,this.persistTokens()}clearTokens(){this.config.accessToken=void 0,this.config.refreshToken=void 0,this.removePersistedTokens()}async bootstrapRefreshCookie(){if(!(typeof window>"u")&&this.persistence==="none"&&this.config.refreshToken)try{await this.refreshAccessToken()}catch{}}get persistence(){return this.config.persistence??"none"}getStorage(){return typeof window>"u"?null:this.persistence==="localStorage"&&typeof localStorage<"u"?localStorage:this.persistence==="sessionStorage"&&typeof sessionStorage<"u"?sessionStorage:null}getPersistenceStorage(){return this.getStorage()}buildStorageKey(){let e=this.config.publicKey??this.config.secretKey;if(!e)return Re;let t=0;for(let s=0;s<e.length;s++)t=(t<<5)-t+e.charCodeAt(s),t=t&t;return`${Re}_${Math.abs(t).toString(36)}`}persistTokens(){if(this.persistence==="none")return;let e=this.getStorage();!e||!this.config.accessToken||!this.config.refreshToken||e.setItem(this.storageKey,JSON.stringify({accessToken:this.config.accessToken,refreshToken:this.config.refreshToken}))}restoreTokens(){if(this.persistence==="none"||this.config.accessToken&&this.config.refreshToken)return;let e=this.getStorage();if(!e)return;let t=e.getItem(this.storageKey);if(t)try{let{accessToken:s,refreshToken:r}=JSON.parse(t);s&&r&&(this.config.accessToken=s,this.config.refreshToken=r)}catch{e.removeItem(this.storageKey)}}removePersistedTokens(){if(this.persistence==="none")return;let e=this.getStorage();e&&e.removeItem(this.storageKey)}hasPublicKey(){return!!this.config.publicKey}getPublicKey(){return this.config.publicKey}hasSecretKey(){return!!this.config.secretKey}getSecretKey(){return this.config.secretKey}getCredential(){return this.config.publicKey??this.config.secretKey}getAccessToken(){return this.config.accessToken}hasJWT(){return!!this.config.accessToken}getBaseUrl(){return this.config.baseUrl}async refreshAccessToken(){if(this.isRefreshing)return this.refreshPromise;if(Date.now()<this.refreshLockedUntil){let t=new k("Token refresh locked due to repeated failures. Please login again.");throw this.emitError(t),this.config.onAuthError?.(t),t}if(this.isRefreshing=!0,!this.config.refreshToken&&typeof window>"u"){this.isRefreshing=!1,this.config.onTokenExpired?.();let t=new k("Refresh token is missing. Please login again.");throw this.emitError(t),this.config.onAuthError?.(t),t}return this.refreshPromise=(async()=>{let{signal:t,cleanup:s}=I({timeout:this.config.requestTimeoutMs??3e4}),r="transient";try{let i={"Content-Type":"application/json"};this.config.refreshToken&&(i.Authorization=`Bearer ${this.config.refreshToken}`);let n=this.getCredential();n&&(i["X-Public-Key"]=n);let o=await fetch(`${this.config.baseUrl}/v1/auth/re-issue`,{method:"POST",headers:i,credentials:"include",signal:t});if(!o.ok){let d=o.status,u;try{let p=await o.clone().json();p&&typeof p.error=="string"?u=p.error:p&&typeof p.code=="string"&&(u=p.code)}catch{}throw d>=500?new Error(`Token refresh failed (${d})`):(d===401||d===403||d===400&&(u==="invalid_grant"||u==="invalid_token")?r="permanent":r="client_bug",new Error(`Token refresh failed (${d}${u?` ${u}`:""})`))}let l=await o.json();if(!l||typeof l.access_token!="string")throw new Error("Token refresh response missing access_token");if(Se(l.access_token))return this.refreshFailureCount=0,this.refreshLockedUntil=0,null;if(this.config.appId){let d=me(l.access_token)?.app_id;if(typeof d=="string"&&d!==this.config.appId)return this.clearTokens(),this.refreshFailureCount=0,this.refreshLockedUntil=0,null}let c=typeof l.refresh_token=="string"&&l.refresh_token.length>0?l.refresh_token:this.config.refreshToken??"";return c?this.setTokens(l.access_token,c):(this.config.accessToken=l.access_token,this.persistTokens()),this.config.onTokenRefresh?.({accessToken:l.access_token,refreshToken:c}),this.refreshFailureCount=0,this.refreshLockedUntil=0,l.access_token}catch(i){let n=i instanceof Error?i.message:"Token refresh failed";this.refreshFailureCount++;let o=Math.min(500*2**Math.max(0,this.refreshFailureCount-1),3e4);if(this.refreshLockedUntil=Date.now()+o,r==="permanent"){this.clearTokens(),this.config.onTokenExpired?.();let c=new k(`${n}. Please login again.`);throw this.emitError(c),this.config.onAuthError?.(c),c}if(r==="client_bug"){let c=new k(`${n}. Client request invalid; tokens preserved.`);throw this.emitError(c),this.config.onAuthError?.(c),c}let l=new k(`${n}. Transient failure; tokens preserved, will retry after backoff.`);throw this.emitError(l),this.config.onTransientRefreshFailure?.(l),this.config.onAuthError?.(l),l}finally{s(),this.isRefreshing=!1,this.refreshPromise=null}})(),this.refreshPromise}async tryRestoreSessionFromCookie(){if(typeof window>"u")return!1;if(this.config.accessToken&&!this.isTokenExpired(this.config.accessToken))return!0;try{return!!await this.refreshAccessToken()}catch{return!1}}emitError(e){try{this.config.onError?.(e)}catch{}}isTokenExpired(e){let t=me(e);if(!t||typeof t.exp!="number")return!0;let s=Date.now()/1e3;return t.exp<s+300}async prepareHeaders(e){let t=new Headers;t.set("Content-Type","application/json");let s=this.getCredential();if(s&&t.set("X-Public-Key",s),!e?.skipAuth&&!this.config.accessToken&&this.config.publicKey&&typeof window<"u"&&this.bootRestorePromise)try{await this.bootRestorePromise}catch{}if(!e?.skipAuth&&this.config.accessToken){let r=this.config.accessToken;if(this.isTokenExpired(r)&&this.config.refreshToken){let i=await this.refreshAccessToken();i&&(r=i)}Se(r)||t.set("Authorization",`Bearer ${r}`)}else!e?.skipAuth&&this.config.publicKey&&this.config.secretKey&&!t.has("Authorization")&&t.set("Authorization",`Bearer ${this.config.secretKey}`);return e?.headers&&Object.entries(e.headers).forEach(([r,i])=>{t.set(r,i)}),t}async handleResponse(e){if(!e.ok){let t=await e.json().catch(()=>({message:e.statusText})),s=e.status===429?e.headers.get("Retry-After"):null,r;if(s){let c=Number.parseInt(s,10);if(Number.isFinite(c)&&c>=0)r=c;else{let d=Date.parse(s);Number.isFinite(d)&&(r=Math.max(0,Math.round((d-Date.now())/1e3)))}}let i=t.error;if(i&&typeof i=="object"&&"message"in i){let c={...i.details&&typeof i.details=="object"?i.details:{}};r!==void 0&&(c.retry_after_seconds=r);let d=new g(e.status,i.message||"Unknown error",i.code,Object.keys(c).length>0?c:i.details);throw this.emitError(d),d}let n=typeof i=="string"?i:t.message||"Unknown error",o=r!==void 0?{retry_after_seconds:r}:void 0,l=new g(e.status,n,void 0,o);throw this.emitError(l),l}return e.status===204||e.headers.get("content-length")==="0"?{}:e.json()}async doFetch(e,t,s){let r=Date.now(),i=0;try{let n=await this.tryFetchOnce(e,t,s);if(i=n.status,!n.ok&&n.status===401&&!s?.skipAuth&&this.config.publicKey&&typeof window<"u"&&await this.tryRestoreSessionFromCookie()){let l=await this.prepareHeaders(s);t.body instanceof FormData&&l.delete("Content-Type");let c=await this.tryFetchOnce(e,{...t,headers:l},s);return i=c.status,await this.handleResponse(c.response)}return await this.handleResponse(n.response)}finally{this.recentCalls.push({method:(t.method||"GET").toUpperCase(),path:_e(e),status:i,duration_ms:Date.now()-r,timestamp:new Date().toISOString()})}}async tryFetchOnce(e,t,s){let{signal:r,cleanup:i}=I({timeout:s?.timeout??this.config.requestTimeoutMs??3e4,signal:s?.signal});try{let n=await fetch(`${this.config.baseUrl}${e}`,{...t,credentials:"include",signal:r});return{response:n,ok:n.ok,status:n.status}}finally{i()}}async get(e,t){let s=await this.prepareHeaders(t);return this.doFetch(e,{method:"GET",headers:s},t)}async post(e,t,s){let r=await this.prepareHeaders(s);return t instanceof FormData&&r.delete("Content-Type"),this.doFetch(e,{method:"POST",headers:r,body:t instanceof FormData?t:JSON.stringify(t)},s)}async put(e,t,s){let r=await this.prepareHeaders(s);return this.doFetch(e,{method:"PUT",headers:r,body:JSON.stringify(t)},s)}async patch(e,t,s){let r=await this.prepareHeaders(s);return this.doFetch(e,{method:"PATCH",headers:r,body:JSON.stringify(t)},s)}async delete(e,t){let s=await this.prepareHeaders(t);return this.doFetch(e,{method:"DELETE",headers:s},t)}async fetchRaw(e,t){let s=await this.prepareHeaders(),r=new Headers(s);return t?.headers&&new Headers(t.headers).forEach((n,o)=>r.set(o,n)),fetch(`${this.config.baseUrl}${e}`,{...t,credentials:"include",headers:r})}};function Ne(a,e){switch(e){case"string":return typeof a=="string";case"number":return typeof a=="number"&&Number.isFinite(a);case"boolean":return typeof a=="boolean";case"array":return Array.isArray(a);case"object":return typeof a=="object"&&a!==null&&!Array.isArray(a);case"string-or-number":return typeof a=="string"||typeof a=="number"&&Number.isFinite(a)}}function R(a,e,t){if(typeof a!="object"||a===null||Array.isArray(a))throw new g(502,`[${t}] expected object, got ${typeof a}`,"SCHEMA_MISMATCH");let s=a;for(let[r,i]of Object.entries(e)){if(!(r in s)){if(i.optional)continue;throw new g(502,`[${t}] missing required field "${r}"`,"SCHEMA_MISMATCH")}let o=s[r];if(!(i.optional&&o==null)&&!Ne(o,i.type))throw new g(502,`[${t}] field "${r}" expected ${i.type}, got ${typeof o}`,"SCHEMA_MISMATCH")}return a}var ke="cb_guest_";function Ge(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 K=class{constructor(e){this.http=e;this.guestMemberLoginPromise=null;this.cachedGuestMemberTokenKey=null;this.analytics=null}_attachAnalytics(e){this.analytics=e}notifyVisitorTracker(e){if(this.analytics){this.analytics.setMemberId(e);return}typeof window>"u"||(e?typeof window.__cbSetMember=="function"&&window.__cbSetMember(e):typeof window.__cbClearMember=="function"&&window.__cbClearMember())}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),this.notifyVisitorTracker(t.member_id),t}async signInMember(e){let t=await this.http.post("/v1/public/app-members/signin",e,{skipAuth:!0});return R(t,{access_token:{type:"string"},refresh_token:{type:"string"},member_id:{type:"string-or-number"}},"auth.signInMember"),this.http.setTokens(t.access_token,t.refresh_token),this.notifyVisitorTracker(t.member_id),t}async signInAsGuestMember(e){if(this.guestMemberLoginPromise)return this.guestMemberLoginPromise;if(!e?.allowOverrideExistingSession&&this.hasActiveNonGuestSession())throw new E;this.guestMemberLoginPromise=this.executeGuestMemberLogin();try{return await this.guestMemberLoginPromise}finally{this.guestMemberLoginPromise=null}}hasActiveNonGuestSession(){let e=this.http.getAccessToken();if(!e||this.isTokenExpired(e))return!1;let t=this.getStoredGuestMemberTokens();return t?t.accessToken!==e:!0}async getMe(){let e=await this.http.get("/v1/public/app-members/me");return R(e,{member_id:{type:"string-or-number"}},"auth.getMe"),e}async updateCustomData(e){return this.http.patch("/v1/public/app-members/me/custom-data",e)}async adminUpdateMember(e,t){if(!this.http.getSecretKey())throw new Error("adminUpdateMember requires a secretKey. Initialize ConnectBase with both publicKey and secretKey.");return this.http.patch(`/v1/public/app-members/${encodeURIComponent(e)}`,t)}async signOut(){try{await this.http.post("/v1/auth/logout")}finally{this.http.clearTokens(),this.notifyVisitorTracker(null)}}clearGuestMemberTokens(){let e=this.http.getPersistenceStorage();e&&e.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 this.notifyVisitorTracker(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),this.notifyVisitorTracker(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),this.notifyVisitorTracker(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.getCredential();if(!e)this.cachedGuestMemberTokenKey=`${ke}default`;else{let t=Ge(e);this.cachedGuestMemberTokenKey=`${ke}${t}`}return this.cachedGuestMemberTokenKey}getStoredGuestMemberTokens(){let e=this.http.getPersistenceStorage();if(!e)return null;let t=e.getItem(this.getGuestMemberTokenKey());if(!t)return null;try{return JSON.parse(t)}catch{return null}}storeGuestMemberTokens(e,t,s){let r=this.http.getPersistenceStorage();r&&r.setItem(this.getGuestMemberTokenKey(),JSON.stringify({accessToken:e,refreshToken:t,memberId:s}))}};var W=class{constructor(e){this.realtimeWs=null;this.realtimeState="disconnected";this.realtimeHandlers=new Map;this.realtimeRetryCount=0;this.realtimeOptions=null;this.pendingRequests=new Map;this.pingInterval=null;this.realtimeOnStateChange=null;this.realtimeOnError=null;this.activeSubscriptions=new Map;this.http=e}getPublicPrefix(){return"/v1/public"}async getTables(){let e=this.getPublicPrefix();return(await this.http.get(`${e}/tables`)).tables}async getTable(e){let t=this.getPublicPrefix();return this.http.get(`${t}/tables/${e}`)}async createTable(e){let t=this.getPublicPrefix(),s={title:e.name,access_level:e.accessLevel??"Creator"};e.schema&&Object.keys(e.schema).length>0&&(s.schema=e.schema),await this.http.post(`${t}/tables`,s)}async updateTable(e,t){let s=this.getPublicPrefix(),r={};t.name!==void 0&&(r.title=t.name),t.schema!==void 0&&(r.schema=t.schema),t.accessLevel!==void 0&&(r.access_level=t.accessLevel),t.description!==void 0&&(r.description=t.description),await this.http.patch(`${s}/tables/${e}`,r)}async deleteTable(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/tables/${e}`)}async getValidationSchema(e){let t=this.getPublicPrefix();return(await this.http.get(`${t}/tables/${e}/validation-schema`)).validation_schema}async setValidationSchema(e,t){await this.http.put(`/v1/apps/${this.requireAppId()}/databases/tables/${e}/validation-schema`,{validation_schema:t})}async deleteValidationSchema(e){await this.http.delete(`/v1/apps/${this.requireAppId()}/databases/tables/${e}/validation-schema`)}requireAppId(){let e=this.http.config?.appId;if(!e)throw new Error("setValidationSchema/deleteValidationSchema \uB294 \uCF58\uC194 (JWT) \uC778\uC99D\uC774 \uD544\uC694\uD558\uBA70 ConnectBase config \uC5D0 appId \uAC00 \uC124\uC815\uB418\uC5B4\uC57C \uD569\uB2C8\uB2E4.");return e}async getColumns(e){let t=await this.getTable(e),s=t.schema??{},r=new Set(Array.isArray(s.$required)?s.$required:[]),i=[],n=0;for(let[o,l]of Object.entries(s)){if(o.startsWith("$")||l===void 0||Array.isArray(l))continue;let c="string",d=r.has(o),u,p,f;typeof l=="string"?c=l:(c=l.type,l.required===!0&&(d=!0),l.default!==void 0&&(u=l.default),l.description!==void 0&&(p=l.description),l.encrypted!==void 0&&(f=l.encrypted)),i.push({id:o,name:o,data_type:c,is_required:d,default_value:u,description:p,encrypted:f,order:n++,created_at:t.created_at})}return i}async createColumn(e,t){let s=this.getPublicPrefix();await this.http.post(`${s}/tables/${e}/columns`,t)}async updateColumn(e,t,s){let r=this.getPublicPrefix();await this.http.patch(`${r}/tables/${e}/columns/${t}`,s)}async deleteColumn(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/tables/${e}/columns/${t}`)}async getData(e,t){let s=this.getPublicPrefix();if(t?.where||t?.select||t?.exclude)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,select:t.select,exclude:t.exclude})}async getDataById(e,t){let s=this.getPublicPrefix();return this.http.get(`${s}/tables/${e}/data/${t}`)}async createData(e,t,s){let r=this.getPublicPrefix();if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return this.http.post(`${r}/tables/${e}/data`,t);let n=s?.autoCreate?"?auto_create=true":"";return this.http.post(`${r}/tables/name/${encodeURIComponent(e)}/data${n}`,t)}async updateData(e,t,s){let r=this.getPublicPrefix();return this.http.patch(`${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})}async aggregate(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/aggregate`,{table_id:e,pipeline:t})}async search(e,t,s,r){let i=this.getPublicPrefix();return this.http.post(`${i}/search`,{table_id:e,query:t,fields:s,options:r})}async autocomplete(e,t,s,r){let i=this.getPublicPrefix();return this.http.post(`${i}/autocomplete`,{table_id:e,query:t,field:s,...r})}async geoQuery(e,t,s,r){let i=this.getPublicPrefix();return this.http.post(`${i}/geo`,{table_id:e,field:t,query:s,...r})}async batch(e){let t=this.getPublicPrefix(),s=await this.http.post(`${t}/batch`,{operations:e});if(s&&s.success===!1){let r=s.results?.find(i=>i&&i.success===!1);throw new Error(r?.error||"batch operation failed")}return s}async transaction(e,t){let s=this.getPublicPrefix(),r=await this.http.post(`${s}/transactions`,{reads:e,writes:t});if(r&&r.success===!1)throw new Error(r.error||"transaction failed");return r}async getDataWithPopulate(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,select:t.select,exclude:t.exclude,populate:t.populate})}async listSecurityRules(e){return(await this.http.get(`/v1/apps/${e}/databases/security/rules`)).rules}async createSecurityRule(e,t){return this.http.post(`/v1/apps/${e}/databases/security/rules`,t)}async updateSecurityRule(e,t,s){return this.http.put(`/v1/apps/${e}/databases/security/rules/${t}`,s)}async deleteSecurityRule(e,t){await this.http.delete(`/v1/apps/${e}/databases/security/rules/${t}`)}async listIndexes(e,t){return(await this.http.get(`/v1/apps/${e}/databases/tables/${t}/indexes`)).indexes}async createIndex(e,t,s){return this.http.post(`/v1/apps/${e}/databases/tables/${t}/indexes`,s)}async deleteIndex(e,t,s){await this.http.delete(`/v1/apps/${e}/databases/tables/${t}/indexes/${s}`)}async analyzeIndexes(e,t){return this.http.get(`/v1/apps/${e}/databases/tables/${t}/indexes/analyze`)}async listSearchIndexes(e,t){return(await this.http.get(`/v1/apps/${e}/databases/tables/${t}/search-indexes`)).indexes}async createSearchIndex(e,t,s){return this.http.post(`/v1/apps/${e}/databases/tables/${t}/search-indexes`,s)}async deleteSearchIndex(e,t,s){await this.http.delete(`/v1/apps/${e}/databases/tables/${t}/search-indexes/${s}`)}async listGeoIndexes(e,t){return(await this.http.get(`/v1/apps/${e}/databases/tables/${t}/geo-indexes`)).indexes}async createGeoIndex(e,t,s){return this.http.post(`/v1/apps/${e}/databases/tables/${t}/geo-indexes`,s)}async deleteGeoIndex(e,t,s){await this.http.delete(`/v1/apps/${e}/databases/tables/${t}/geo-indexes/${s}`)}async listRelations(e,t){let s=t?`?source_table=${encodeURIComponent(t)}`:"";return(await this.http.get(`/v1/apps/${e}/databases/relations${s}`)).relations}async createRelation(e,t){return this.http.post(`/v1/apps/${e}/databases/relations`,t)}async deleteRelation(e,t){await this.http.delete(`/v1/apps/${e}/databases/relations/${t}`)}async listTriggers(e){return(await this.http.get(`/v1/apps/${e}/databases/triggers`)).triggers}async createTrigger(e,t){return this.http.post(`/v1/apps/${e}/databases/triggers`,t)}async updateTrigger(e,t,s){return this.http.put(`/v1/apps/${e}/databases/triggers/${t}`,s)}async deleteTrigger(e,t){await this.http.delete(`/v1/apps/${e}/databases/triggers/${t}`)}async setTTL(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/ttl`,t)}async getTTL(e,t){return this.http.get(`/v1/apps/${e}/lifecycle/ttl/${t}`)}async setRetentionPolicy(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/retention`,t)}async getRetentionPolicy(e,t){return this.http.get(`/v1/apps/${e}/lifecycle/retention/${t}`)}async setArchivePolicy(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/archive`,t)}async getArchivePolicy(e,t){return this.http.get(`/v1/apps/${e}/lifecycle/archive/${t}`)}async executeTTL(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/ttl/${t}/execute`,{})}async executeArchive(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/archive/${t}/execute`,{})}async executeRetention(e,t){return this.http.post(`/v1/apps/${e}/lifecycle/retention/${t}/execute`,{})}async listPolicies(e){return(await this.http.get(`/v1/apps/${e}/lifecycle`)).policies}async deletePolicy(e,t){await this.http.delete(`/v1/apps/${e}/lifecycle/${t}`)}async generateTypes(e){return this.http.get(`/v1/apps/${e}/types`)}async listBackups(e){return this.http.get(`/v1/apps/${e}/backups`)}async createBackup(e,t){return this.http.post(`/v1/apps/${e}/backups`,t)}async getBackup(e,t){return this.http.get(`/v1/apps/${e}/backups/${t}`)}async deleteBackup(e,t){await this.http.delete(`/v1/apps/${e}/backups/${t}`)}async restoreBackup(e,t,s){return this.http.post(`/v1/apps/${e}/backups/${t}/restore`,s||{backup_id:t})}async exportData(e,t){return this.http.post(`/v1/apps/${e}/data/export`,t||{format:"json"})}async importData(e,t){return this.http.post(`/v1/apps/${e}/data/import`,t)}async copyTable(e,t){return this.http.post(`/v1/apps/${e}/tables/copy`,t)}async migrateData(e,t){return this.http.post(`/v1/apps/${e}/tables/migrate`,t)}connectRealtime(e){return this.realtimeState==="connected"?Promise.resolve():this.realtimeState==="connecting"?Promise.reject(new Error("Already connecting")):(this.realtimeOptions=e,this.realtimeRetryCount=0,this.doRealtimeConnect())}disconnectRealtime(){this.realtimeOptions=null,this.setRealtimeState("disconnected"),this.realtimeRetryCount=0,this.stopRealtimePing(),this.realtimeWs&&(this.realtimeWs.close(),this.realtimeWs=null),this.pendingRequests.forEach(e=>{clearTimeout(e.timeout),e.reject(new Error("Connection closed"))}),this.pendingRequests.clear(),this.realtimeHandlers.clear(),this.activeSubscriptions.clear()}subscribe(e,t,s){if(this.realtimeState!=="connected")throw new Error("Not connected. Call connectRealtime() first.");let r=`csub_${Date.now()}_${Math.random().toString(36).substring(2,9)}`;this.activeSubscriptions.set(r,{tableId:e,options:s,handlers:t});let i=this.sendSubscribeRequest(e,t,s);return i.catch(n=>{this.activeSubscriptions.delete(r),t.onError?.(n instanceof Error?n:new Error(String(n)))}),{subscriptionId:r,unsubscribe:()=>{this.activeSubscriptions.delete(r),i.then(n=>{this.realtimeHandlers.delete(n),this.realtimeState==="connected"&&this.sendRealtimeMessage({type:"unsubscribe",request_id:this.generateRequestId(),subscription_id:n})}).catch(()=>{})},loadMore:(n,o)=>{this.realtimeState==="connected"&&i.then(l=>{let c={type:"snapshot_more",request_id:this.generateRequestId(),subscription_id:l,offset:n};o!==void 0&&(c.limit=o),this.sendRealtimeMessage(c)}).catch(()=>{})}}}isRealtimeConnected(){return this.realtimeState==="connected"}getRealtimeState(){return this.realtimeState}onRealtimeStateChange(e){return this.realtimeOnStateChange=e,()=>{this.realtimeOnStateChange=null}}onRealtimeError(e){return this.realtimeOnError=e,()=>{this.realtimeOnError=null}}setRealtimeState(e){this.realtimeState!==e&&(this.realtimeState=e,this.realtimeOnStateChange?.(e))}doRealtimeConnect(){if(!this.realtimeOptions)return Promise.reject(new Error("No realtime options"));this.setRealtimeState("connecting");let s=`${(this.realtimeOptions.dataServerUrl||this.http.getBaseUrl()).replace(/^http/,"ws")}/v1/database/realtime/ws?access_token=${encodeURIComponent(this.realtimeOptions.accessToken)}`;return new Promise((r,i)=>{try{this.realtimeWs=new WebSocket(s);let n=!1,o=setTimeout(()=>{n||(n=!0,this.realtimeWs&&(this.realtimeWs.close(),this.realtimeWs=null),this.setRealtimeState("disconnected"),i(new Error("Connection timeout")))},15e3);this.realtimeWs.onopen=()=>{n||(n=!0,clearTimeout(o)),this.setRealtimeState("connected"),this.realtimeRetryCount=0,this.startRealtimePing(),this.debugLog("Database realtime connected"),this.resubscribeAll(),r()},this.realtimeWs.onmessage=l=>{try{let c=JSON.parse(l.data);this.handleRealtimeMessage(c)}catch{this.debugLog("Failed to parse realtime message")}},this.realtimeWs.onclose=()=>{this.debugLog("Database realtime disconnected"),this.realtimeWs=null,this.stopRealtimePing(),n||(n=!0,clearTimeout(o),i(new Error("Connection closed during handshake"))),this.realtimeOptions&&this.realtimeState!=="disconnected"&&this.attemptRealtimeReconnect()},this.realtimeWs.onerror=()=>{this.debugLog("Database realtime error"),this.realtimeOnError?.(new Error("WebSocket connection error"))}}catch(n){this.setRealtimeState("disconnected"),i(n)}})}sendSubscribeRequest(e,t,s){let r=this.generateRequestId();this.realtimeHandlers.set(r,t);let i=s?.where?{filters:s.where.map(n=>({field:n.field,operator:n.operator,value:n.value}))}:void 0;return this.sendRealtimeMessage({type:"subscribe",request_id:r,table_id:e,doc_id:s?.docId,query:i,options:{include_self:s?.includeSelf??!1,include_metadata_changes:s?.includeMetadataChanges??!1}}),new Promise((n,o)=>{let l=setTimeout(()=>{this.pendingRequests.delete(r),this.realtimeHandlers.delete(r),o(new Error("Subscribe request timeout"))},3e4);this.pendingRequests.set(r,{resolve:c=>{let d=c,u=this.realtimeHandlers.get(r);u&&(this.realtimeHandlers.delete(r),this.realtimeHandlers.set(d,u)),n(d)},reject:o,timeout:l})})}resubscribeAll(){if(this.activeSubscriptions.size!==0){this.realtimeHandlers.clear(),this.pendingRequests.forEach(e=>clearTimeout(e.timeout)),this.pendingRequests.clear(),this.debugLog(`Resubscribing ${this.activeSubscriptions.size} subscriptions`);for(let[,e]of this.activeSubscriptions)this.sendSubscribeRequest(e.tableId,e.handlers,e.options).catch(t=>{e.handlers.onError?.(t instanceof Error?t:new Error(String(t)))})}}handleRealtimeMessage(e){switch(e.type){case"subscribed":{let s=e.request_id,r=e.subscription_id,i=this.pendingRequests.get(s);i&&(clearTimeout(i.timeout),i.resolve(r),this.pendingRequests.delete(s));break}case"snapshot":{let s=e.subscription_id,r=this.realtimeHandlers.get(s);if(r?.onSnapshot){let i=e.docs||[],n=e.has_more||!1,o=n?e.next_offset:void 0;r.onSnapshot(i,{totalCount:e.total_count||0,hasMore:n,nextOffset:o})}break}case"change":{let s=e.subscription_id,r=this.realtimeHandlers.get(s);if(r?.onChange){let i=e.changes||[];r.onChange(i)}break}case"presence":{let s=this.realtimeHandlers.get("__presence__");if(s?.onSnapshot){let r=e.states,i=Object.entries(r||{}).map(([n,o])=>({id:n,data:o,exists:!0}));s.onSnapshot(i,{totalCount:i.length,hasMore:!1})}break}case"error":{let s=e.request_id,r=e.message||"Unknown error";if(s){let i=this.pendingRequests.get(s);i&&(clearTimeout(i.timeout),i.reject(new Error(r)),this.pendingRequests.delete(s));let n=this.realtimeHandlers.get(s);n?.onError&&n.onError(new Error(r))}else this.realtimeOnError?.(new Error(r));break}case"pong":break;case"unsubscribed":case"presence_set_ack":case"presence_subscribed":case"typing_subscribed":break}}attemptRealtimeReconnect(){let e=this.realtimeOptions?.maxRetries??5,t=this.realtimeOptions?.retryInterval??1e3;if(this.realtimeRetryCount>=e){this.setRealtimeState("disconnected"),this.realtimeOnError?.(new Error("Realtime connection lost. Max retries exceeded."));return}this.setRealtimeState("connecting"),this.realtimeRetryCount++;let s=Math.min(t*Math.pow(2,this.realtimeRetryCount-1),3e4);this.debugLog(`Reconnecting in ${s}ms (attempt ${this.realtimeRetryCount}/${e})`),setTimeout(()=>{this.realtimeOptions&&this.doRealtimeConnect().catch(r=>{this.debugLog(`Reconnect failed: ${r}`)})},s)}startRealtimePing(){this.stopRealtimePing(),this.pingInterval=setInterval(()=>{this.realtimeState==="connected"&&this.realtimeWs?.readyState===WebSocket.OPEN&&this.sendRealtimeMessage({type:"ping",timestamp:Date.now()})},3e4)}stopRealtimePing(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}sendRealtimeMessage(e){this.realtimeWs?.readyState===WebSocket.OPEN&&this.realtimeWs.send(JSON.stringify(e))}generateRequestId(){return"req_"+Date.now()+"_"+Math.random().toString(36).substring(2,9)}debugLog(e){this.realtimeOptions?.debug&&console.log(`[DatabaseRealtime] ${e}`)}};var fe=new Set(["localhost","127.0.0.1","::1"]),Te=2048;function Ie(a,e={}){let t=e.context??"external URL";if(typeof a!="string"||a.length===0)throw new g(400,`${t}: empty URL`,"INVALID_PRESIGNED_URL");if(a.length>Te)throw new g(400,`${t}: URL exceeds ${Te} chars`,"INVALID_PRESIGNED_URL");let s;try{s=new URL(a)}catch{throw new g(400,`${t}: cannot parse URL`,"INVALID_PRESIGNED_URL")}let r=e.allowLocalhost&&fe.has(s.hostname);if(s.protocol!=="https:"&&!(r&&s.protocol==="http:"))throw new g(400,`${t}: scheme must be https (got ${s.protocol})`,"INVALID_PRESIGNED_URL");if(!e.allowLocalhost&&fe.has(s.hostname))throw new g(400,`${t}: localhost is not allowed in production`,"INVALID_PRESIGNED_URL");if(e.allowedHosts&&e.allowedHosts.length>0){let i=s.hostname;if(!e.allowedHosts.some(o=>o===i?!0:i.endsWith(`.${o}`)))throw new g(400,`${t}: host ${i} is not in allowlist`,"INVALID_PRESIGNED_URL")}return s}function Ce(){if(typeof window>"u")return!1;let a=window.location.hostname;return fe.has(a)}var j=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async uploadToPresigned(e,t,s){let r=Ie(e,{allowLocalhost:Ce(),context:"storage.presigned-url"}),{signal:i,cleanup:n}=I({timeout:s?.timeout,signal:s?.signal});try{let o=await fetch(r.toString(),{method:"PUT",body:t,headers:{"Content-Type":t.type||"application/octet-stream"},signal:i});if(!o.ok)throw new g(o.status,`Upload failed: ${o.statusText}`,"PRESIGNED_UPLOAD_FAILED")}finally{n()}}async getFiles(e,t){let s=this.getPublicPrefix(),r=t?`?parent_id=${encodeURIComponent(t)}`:"";return(await this.http.get(`${s}/storages/files/${e}/items${r}`)).files}async uploadFile(e,t,s){let r=this.getPublicPrefix(),i=await this.http.post(`${r}/storages/files/${e}/presigned-url`,{file_name:t.name,file_size:t.size,mime_type:t.type||"application/octet-stream",parent_id:s});await this.uploadToPresigned(i.upload_url,t);let n=await this.http.post(`${r}/storages/files/${e}/complete-upload`,{file_id:i.file_id});return{id:n.id,name:n.name,path:n.path,type:n.type,mime_type:n.mime_type,size:n.size,url:n.url,parent_id:n.parent_id,created_at:n.created_at}}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){if(this.http.hasPublicKey()&&!this.http.hasJWT())throw new Error("storage.moveFile requires JWT (console token) auth; not available with Public Key alone.");await this.http.post(`/v1/storages/files/${e}/items/${t}/move`,s)}async renameFile(e,t,s){if(this.http.hasPublicKey()&&!this.http.hasJWT())throw new Error("storage.renameFile requires JWT (console token) auth; not available with Public Key alone.");return this.http.patch(`/v1/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=t.startsWith("/")?t.slice(1):t,o=r?.overwrite!==!1,l=await this.http.post(`${i}/storages/files/${e}/presigned-url/path/${n}`,{file_name:s.name,file_size:s.size,mime_type:s.type||"application/octet-stream",overwrite:o});await this.uploadToPresigned(l.upload_url,s);let c=await this.http.post(`${i}/storages/files/${e}/complete-upload`,{file_id:l.file_id});return{id:c.id,name:c.name,path:c.path,type:c.type,mime_type:c.mime_type,size:c.size,url:c.url,parent_id:c.parent_id,created_at:c.created_at}}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}}async setPageMeta(e,t){let s=this.getPublicPrefix();return this.http.put(`${s}/storages/webs/${e}/page-metas`,t)}async batchSetPageMeta(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/storages/webs/${e}/page-metas/batch`,t)}async listPageMetas(e,t){let s=this.getPublicPrefix(),r=new URLSearchParams;t?.limit!=null&&r.set("limit",String(t.limit)),t?.offset!=null&&r.set("offset",String(t.offset));let i=r.toString();return this.http.get(`${s}/storages/webs/${e}/page-metas${i?`?${i}`:""}`)}async getPageMeta(e,t){let s=this.getPublicPrefix(),r=encodeURIComponent(t);return this.http.get(`${s}/storages/webs/${e}/page-metas/get?path=${r}`)}async deletePageMeta(e,t){let s=this.getPublicPrefix(),r=encodeURIComponent(t);await this.http.delete(`${s}/storages/webs/${e}/page-metas?path=${r}`)}async deleteAllPageMetas(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/storages/webs/${e}/page-metas/all`)}};var z=class{constructor(e){this.http=e}async getPublicKeys(e){return this.http.get(`/v1/apps/${e}/public-keys`)}async createPublicKey(e,t){return this.http.post(`/v1/apps/${e}/public-keys`,t)}async updatePublicKey(e,t,s){return this.http.patch(`/v1/apps/${e}/public-keys/${t}`,s)}async deletePublicKey(e,t){await this.http.delete(`/v1/apps/${e}/public-keys/${t}`)}};var J=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/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 invokeCrossApp(e,t,s,r,i){let n={};return r!==void 0&&(n.payload=r),i!==void 0&&(n.timeout=i),this.http.post(`/v1/integrations/providers/${e}/functions/${t}/invoke`,n,{skipAuth:!0,headers:{Authorization:`Bearer ${s}`}})}async call(e,t){let s=await this.invoke(e,t);if(!s.success)throw new g(500,s.error||"Function execution failed","FUNCTION_EXECUTION_FAILED");return s.result}getWebhookURL(e){return`${this.http.getBaseUrl().replace(/\/+$/,"")}/v1/public/functions/${encodeURIComponent(e)}/webhook`}};var Q=class{constructor(e,t){this.ws=null;this.state="disconnected";this._connectionId=null;this._appId=null;this.options={maxRetries:5,retryInterval:1e3,userId:"",accessToken:"",timeout:3e4,debug:!1};this.retryCount=0;this.pendingRequests=new Map;this.subscriptions=new Map;this.streamSessions=new Map;this.stateHandlers=[];this.errorHandlers=[];this.presenceHandlers=[];this.presenceSubscriptions=new Map;this.typingHandlers=new Map;this.readReceiptHandlers=new Map;this.connectPromise=null;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"){if(this.state==="connecting"&&this.connectPromise)return this.connectPromise;this.options={...this.options,...e},e.userId&&(this.userId=e.userId),this.connectPromise=this.doConnect();try{await this.connectPromise}finally{this.connectPromise=null}}}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(),this.streamSessions.forEach(e=>{e.handlers.onError&&e.handlers.onError(new Error("Connection closed"))}),this.streamSessions.clear(),this.presenceHandlers=[],this.presenceSubscriptions.clear(),this.typingHandlers.clear(),this.readReceiptHandlers.clear(),this._connectionId=null,this._appId=null,this.retryCount=0,this.connectPromise=null}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(l,c)=>{await this.sendMessage(e,l,c)},getHistory:async l=>this.getHistory(e,l??t.historyLimit),unsubscribe:async()=>{await this.unsubscribe(e)},onMessage:l=>(n.push(l),()=>{let c=n.indexOf(l);c>-1&&n.splice(c,1)})}}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();this.streamSessions.set(i,{handlers:t,requestId:r});try{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,mcp_group:s.mcpGroup},request_id:r})}catch(n){throw this.streamSessions.delete(i),n}return{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}isConnected(){return this.state==="connected"}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 setPresence(e,t={}){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId();await this.sendRequest({category:"",action:"presence_set",data:{status:e,device:t.device,metadata:t.metadata},request_id:s})}async setPresenceOnDisconnect(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId();await this.sendRequest({category:"",action:"presence_on_disconnect",data:{status:e,metadata:t},request_id:s})}async getPresence(e){if(this.state!=="connected")throw new Error("Not connected");let t=this.generateRequestId(),s=await this.sendRequest({category:"",action:"presence_get",data:{user_id:e},request_id:t});return{userId:s.user_id,status:s.status,lastSeen:s.last_seen,device:s.device,metadata:s.metadata}}async getPresenceMany(e){if(this.state!=="connected")throw new Error("Not connected");let t=this.generateRequestId(),s=await this.sendRequest({category:"",action:"presence_get_many",data:{user_ids:e},request_id:t}),r={};for(let[i,n]of Object.entries(s.users))r[i]={userId:n.user_id,status:n.status,lastSeen:n.last_seen,device:n.device,metadata:n.metadata};return{users:r}}async subscribePresence(e,t){if(this.state!=="connected")throw new Error("Not connected");if(!this.presenceSubscriptions.has(e)){let r=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:e},request_id:r}),this.presenceSubscriptions.set(e,[])}let s=this.presenceSubscriptions.get(e);return s.push(t),()=>{let r=s.indexOf(t);if(r>-1&&s.splice(r,1),s.length===0&&(this.presenceSubscriptions.delete(e),this.state==="connected")){let i=this.generateRequestId();this.sendRequest({category:"",action:"presence_unsubscribe",data:{user_id:e},request_id:i}).catch(n=>{this.log(`Failed to unsubscribe presence for ${e}: ${n}`)})}}}onPresenceChange(e){return this.presenceHandlers.push(e),()=>{let t=this.presenceHandlers.indexOf(e);t>-1&&this.presenceHandlers.splice(t,1)}}async startTyping(e){if(this.state!=="connected")throw new Error("Not connected");let t=this.generateRequestId();await this.sendRequest({category:"",action:"typing_start",data:{room_id:e},request_id:t})}async stopTyping(e){if(this.state!=="connected")throw new Error("Not connected");let t=this.generateRequestId();await this.sendRequest({category:"",action:"typing_stop",data:{room_id:e},request_id:t})}async onTypingChange(e,t){if(this.state!=="connected")throw new Error("Not connected");if(!this.typingHandlers.has(e)){let r=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:e},request_id:r}),this.typingHandlers.set(e,[])}let s=this.typingHandlers.get(e);return s.push(t),()=>{let r=s.indexOf(t);if(r>-1&&s.splice(r,1),s.length===0&&(this.typingHandlers.delete(e),this.state==="connected")){let i=this.generateRequestId();this.sendRequest({category:"",action:"typing_unsubscribe",data:{room_id:e},request_id:i}).catch(n=>{this.log(`Failed to unsubscribe typing for ${e}: ${n}`)})}}}async markRead(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId();await this.sendRequest({category:e,action:"mark_read",data:{message_ids:t},request_id:s})}onReadReceipt(e,t){this.readReceiptHandlers.has(e)||this.readReceiptHandlers.set(e,[]);let s=this.readReceiptHandlers.get(e);return s.push(t),()=>{let r=s.indexOf(t);r>-1&&s.splice(r,1)}}async doConnect(){return new Promise((e,t)=>{this.state="connecting",this.notifyStateChange(),this.log("Connecting...");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}`,this.log("Using accessToken authentication");else{let o=this.http.getPublicKey();if(!o){let l=new Error("API Key or accessToken is required for realtime connection");this.log("Connection failed: no API Key or accessToken"),t(l);return}r=`${s}/v1/realtime/auth?public_key=${encodeURIComponent(o)}&client_id=${this.clientId}`,this.log("Using API Key authentication")}this.userId&&(r+=`&user_id=${encodeURIComponent(this.userId)}`);let i=!1,n=setTimeout(()=>{i||(i=!0,this.log(`Connection timeout after ${this.options.timeout}ms`),this.ws&&(this.ws.close(),this.ws=null),this.state="disconnected",this.notifyStateChange(),t(new Error(`Connection timeout after ${this.options.timeout}ms`)))},this.options.timeout);try{this.log(`Connecting to ${s}`),this.ws=new WebSocket(r),this.ws.onopen=()=>{this.log("WebSocket opened, waiting for connected event...")},this.ws.onmessage=o=>{let l=o.data.split(`
2
- `).filter(c=>c.trim());for(let c of l)try{let d=JSON.parse(c);this.handleServerMessage(d,()=>{i||(i=!0,clearTimeout(n),this.log("Connected successfully"),e())})}catch(d){this.logError("Failed to parse message",{line:c,error:d})}},this.ws.onclose=o=>{this.log(`WebSocket closed: code=${o.code}, reason=${o.reason}`),!i&&this.state==="connecting"&&(i=!0,clearTimeout(n),t(new Error(`Connection closed: ${o.reason||"unknown reason"}`))),(this.state==="connected"||this.state==="connecting")&&this.handleDisconnect()},this.ws.onerror=o=>{this.log("WebSocket error occurred"),this.logError("WebSocket error",o),this.notifyError(new Error("WebSocket connection error")),!i&&this.state==="connecting"&&(i=!0,clearTimeout(n),t(new Error("Failed to connect")))}}catch(o){i=!0,clearTimeout(n),t(o)}})}log(e){this.options.debug&&console.log(`[Realtime] ${e}`)}logError(e,t){this.options.debug&&console.error(`[Realtime] ${e}`,t)}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_tool_call":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToolCall&&r.handlers.onToolCall(s.tool_name,s.arguments||{},s.index);break}case"stream_tool_result":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToolResult&&r.handlers.onToolResult(s.tool_name,s.success,s.duration_ms,s.index);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}case"presence":case"presence_status":{let s=e.data,r={userId:s.user_id,status:s.status,lastSeen:s.last_seen,device:s.device,metadata:s.metadata,eventType:s.event_type};this.presenceHandlers.forEach(n=>n(r));let i=this.presenceSubscriptions.get(s.user_id);if(i&&i.forEach(n=>n(r)),e.request_id){let n=this.pendingRequests.get(e.request_id);n&&(clearTimeout(n.timeout),n.resolve(e.data),this.pendingRequests.delete(e.request_id))}break}case"typing":{let s=e.data,r={roomId:s.room_id,users:s.users},i=this.typingHandlers.get(s.room_id);i&&i.forEach(n=>n(r));break}case"read_receipt":{let s=e.data,r={category:s.category,messageIds:s.message_ids,readerId:s.reader_id,readAt:s.read_at},i=this.readReceiptHandlers.get(s.category);i&&i.forEach(n=>n(r));break}}}handleDisconnect(){this.ws=null,this._connectionId=null,this.streamSessions.forEach(r=>{r.handlers.onError&&r.handlers.onError(new Error("Connection lost"))}),this.streamSessions.clear();let e=new Map;for(let[r,i]of this.subscriptions)e.set(r,[...i.handlers]);this.subscriptions.clear();let t=new Map;for(let[r,i]of this.presenceSubscriptions)t.set(r,[...i]);this.presenceSubscriptions.clear();let s=new Map;for(let[r,i]of this.typingHandlers)s.set(r,[...i]);if(this.typingHandlers.clear(),this.retryCount<this.options.maxRetries){this.state="reconnecting",this.notifyStateChange(),this.retryCount++;let r=Math.min(this.options.retryInterval*Math.pow(2,this.retryCount-1),3e4);setTimeout(async()=>{try{await this.doConnect(),this.log("Reconnected successfully, restoring subscriptions..."),await this.restoreSubscriptions(e,t,s)}catch(i){this.logError("Reconnect failed",i)}},r)}else this.state="disconnected",this.notifyStateChange(),this.notifyError(new Error("Connection lost. Max retries exceeded."))}async restoreSubscriptions(e,t,s){for(let[r,i]of e)try{this.log(`Restoring subscription: ${r}`);let n=this.generateRequestId(),o=await this.sendRequest({category:r,action:"subscribe",request_id:n}),l={category:o.category,persist:o.persist,historyCount:o.history_count,readReceipt:o.read_receipt};this.subscriptions.set(r,{info:l,handlers:i}),this.log(`Restored subscription: ${r}`)}catch(n){this.logError(`Failed to restore subscription for ${r}`,n),this.notifyError(new Error(`Failed to restore subscription: ${r}`))}for(let[r,i]of t)try{this.log(`Restoring presence subscription: ${r}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:r},request_id:n}),this.presenceSubscriptions.set(r,i),this.log(`Restored presence subscription: ${r}`)}catch(n){this.logError(`Failed to restore presence subscription for ${r}`,n)}for(let[r,i]of s)try{this.log(`Restoring typing subscription: ${r}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:r},request_id:n}),this.typingHandlers.set(r,i),this.log(`Restored typing subscription: ${r}`)}catch(n){this.logError(`Failed to restore typing subscription for ${r}`,n)}}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"))},this.options.timeout);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(){if(!this.appId)throw new Error("WebRTC getICEServers \uC5D0\uB294 appId \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId \uB97C \uC124\uC815\uD558\uC138\uC694.");let e=await this.http.get(`/v1/apps/${this.appId}/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.getPublicKey(),s=this.http.getAccessToken(),r="";if(s?r=`access_token=${encodeURIComponent(s)}`:t&&(r=`public_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 l of o)l.peer_id!==this.currentPeerId&&await this.createPeerConnection(l.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 validate(){if(!this.appId)throw new Error("WebRTC \uAC80\uC99D\uC5D0\uB294 appId\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId\uB97C \uC124\uC815\uD558\uC138\uC694.");return this.http.get(`/v1/apps/${this.appId}/validate`)}async getStats(e){return this.http.get(`/v1/apps/${e}/stats`)}async getRooms(e){return this.http.get(`/v1/apps/${e}/rooms`)}};var Y=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})}}};function Ee(){if(typeof window>"u")return null;let a=window.NativeBridge?.oauth;return!a||typeof a.signIn!="function"||typeof a.getCallbackScheme!="function"?null:a}function D(){if(typeof navigator>"u")return null;let a=navigator.userAgent||"";return/KAKAOTALK/i.test(a)?{type:"kakaotalk",ua:a}:/Line\//i.test(a)?{type:"line",ua:a}:/Instagram/i.test(a)?{type:"instagram",ua:a}:/FBAN|FBAV|FB_IAB/i.test(a)?{type:"facebook",ua:a}:/NAVER\(inapp/i.test(a)?{type:"naverapp",ua:a}:/MicroMessenger/i.test(a)?{type:"wechat",ua:a}:null}function Z(a){if(typeof window>"u")return!1;let e=D();if(!e)return!1;let t=a??window.location.href,s=encodeURIComponent(t);switch(e.type){case"kakaotalk":return window.location.href=`kakaotalk://web/openExternal?url=${s}`,!0;case"line":return window.location.href=`line://nv/openURL?url=${s}`,!0;default:return!1}}var ee=class{constructor(e){this.http=e}async getEnabledProviders(){return this.http.get("/v1/public/oauth/providers")}async signIn(e,t,s,r){return this.startCentralOAuth(e,t,s,r?.createIfNotExists?"signup":"signin")}async signUp(e,t,s){return this.startCentralOAuth(e,t,s,"signup")}async startCentralOAuth(e,t,s,r){let i=Ee();if(i){let c=i.getCallbackScheme(),d=new URLSearchParams({app_callback:c,intent:r});s&&d.append("state",s);let u=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${d.toString()}`),{url:p}=await i.signIn(u.authorization_url,c),f=new URL(p),b=new URL(t,window.location.href);f.searchParams.forEach((h,y)=>{b.searchParams.set(y,h)}),window.location.href=b.toString();return}let n=D();if(n&&(e==="google"||e==="apple")){if(Z())return;throw new Error(`\uD604\uC7AC ${n.type==="kakaotalk"?"\uCE74\uCE74\uC624\uD1A1":n.type==="line"?"\uB77C\uC778":"\uC778\uC571"} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB294 Google/Apple \uB85C\uADF8\uC778\uC774 \uC81C\uD55C\uB429\uB2C8\uB2E4. \uC6B0\uCE21 \uC0C1\uB2E8 \uBA54\uB274\uC5D0\uC11C "\uB2E4\uB978 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uAE30"\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`)}let o=new URLSearchParams({app_callback:t,intent:r});s&&o.append("state",s);let l=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${o.toString()}`);window.location.href=l.authorization_url}async signInWithPopup(e,t,s={}){let r=s.intent??"signin",i=Ee();if(i){let b=i.getCallbackScheme(),h=new URLSearchParams({app_callback:b,intent:r}),y=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${h.toString()}`),{url:_}=await i.signIn(y.authorization_url,b),w=new URL(_),S=w.searchParams.get("error");if(S)throw new Error(S);let T=w.searchParams.get("access_token")||"",v=w.searchParams.get("refresh_token")||"",C=w.searchParams.get("member_id")||"";if(!T||!v||!C)throw new Error("OAuth \uC751\uB2F5\uC5D0 \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");return this.http.setTokens(T,v),{access_token:T,refresh_token:v,member_id:C,is_new_member:w.searchParams.get("is_new_member")==="true"}}let n=D();if(n&&(e==="google"||e==="apple"))throw Z()?new Error("\uC678\uBD80 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC774\uB3D9\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."):new Error(`\uD604\uC7AC ${n.type==="kakaotalk"?"\uCE74\uCE74\uC624\uD1A1":n.type==="line"?"\uB77C\uC778":"\uC778\uC571"} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB294 Google/Apple \uB85C\uADF8\uC778\uC774 \uC81C\uD55C\uB429\uB2C8\uB2E4. \uC678\uBD80 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5B4\uC8FC\uC138\uC694.`);let o=new URLSearchParams;o.set("intent",r),t&&o.set("app_callback",t);let l=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${o.toString()}`),c=500,d=600,u=window.screenX+(window.outerWidth-c)/2,p=window.screenY+(window.outerHeight-d)/2,f=window.open(l.authorization_url,"oauth-popup",`width=${c},height=${d},left=${u},top=${p}`);if(!f)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((b,h)=>{let y=!1,_=()=>{y=!0,window.removeEventListener("message",w),clearInterval(S),clearTimeout(T)},w=async v=>{if(v.data?.type!=="oauth-callback"||y)return;if(_(),v.data.error){h(new Error(v.data.error));return}let C=v.data.email,he={member_id:v.data.member_id,access_token:v.data.access_token,refresh_token:v.data.refresh_token,is_new_member:v.data.is_new_member==="true"||v.data.is_new_member===!0,...typeof C=="string"&&C.length>0?{email:C}:{}};this.http.setTokens(he.access_token,he.refresh_token);try{await this.http.bootstrapRefreshCookie()}catch{}b(he)};window.addEventListener("message",w);let S=setInterval(()=>{try{f.closed&&(y||(_(),h(new Error("\uB85C\uADF8\uC778\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))))}catch{clearInterval(S)}},500),T=setTimeout(()=>{if(!y){_();try{f.close()}catch{}h(new Error("\uB85C\uADF8\uC778 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."))}},18e4)})}async getCallbackResult(){let e=new URLSearchParams(window.location.search),t=e.get("error");if(t){let l={error:t};return window.opener&&(window.opener.postMessage({type:"oauth-callback",...l},"*"),window.close()),l}let s=e.get("access_token"),r=e.get("refresh_token"),i=e.get("member_id");if(!s||!r||!i)return null;let n=e.get("email"),o={access_token:s,refresh_token:r,member_id:i,is_new_member:e.get("is_new_member")==="true",...n?{email:n}:{},state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...o},"*"),window.close(),o):(this.http.setTokens(s,r),await this.http.bootstrapRefreshCookie(),o)}async exchangeCodeFromCallback(){let e=new URLSearchParams(window.location.search),t=e.get("code");if(!t||e.get("error"))return null;let s=await this.http.post("/v1/auth/oauth/exchange",{code:t}),r={access_token:s.access_token,refresh_token:s.refresh_token,member_id:s.member_id,is_new_member:s.is_new_member,...s.email?{email:s.email}:{},state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...r},"*"),window.close(),r):(this.http.setTokens(r.access_token,r.refresh_token),await this.http.bootstrapRefreshCookie(),r)}};var te=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async prepare(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/prepare`,e)}async createCheckoutSession(e){let t=this.getPublicPrefix(),s=await this.http.post(`${t}/payments/checkout-session`,e);return R(s,{session_id:{type:"string"},session_url:{type:"string"}},"payment.createCheckoutSession"),s}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 se=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/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(),s=await this.http.post(`${t}/subscriptions`,e);return R(s,{id:{type:"string-or-number"},status:{type:"string"}},"subscription.create"),s}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 re=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async registerDevice(e){let t=this.getPublicPrefix(),s=await this.http.post(`${t}/push/devices`,e);return R(s,{device_token:{type:"string"}},"push.registerDevice"),s}async unregisterDevice(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${e}`)}async subscribeTopic(e,t){let s=this.getPublicPrefix(),r={topic_name:t};await this.http.post(`${s}/push/devices/${e}/topics/subscribe`,r)}async unsubscribeTopic(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/push/devices/${e}/topics/${t}/unsubscribe`)}async getVAPIDPublicKey(){let e=this.getPublicPrefix();return this.http.get(`${e}/push/vapid-public-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`,{...r,web_push_subscription:s})}async unregisterWebPush(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${encodeURIComponent(e)}`)}async sendToMembers(e,t,s){if(this.http.hasPublicKey())throw new Error("cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_) \uB610\uB294 \uCF58\uC194 JWT \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.");if(t.length===0)throw new Error("cb.push.sendToMembers(): memberIds \uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.");let r={target_type:"members",target_member_ids:t,title:s.title,body:s.body,...s.imageUrl!==void 0?{image_url:s.imageUrl}:{},...s.data!==void 0?{data:s.data}:{},...s.platforms!==void 0?{platforms:s.platforms}:{},...s.ttlSeconds!==void 0?{ttl:s.ttlSeconds}:{},...s.priority!==void 0?{priority:s.priority}:{},...s.clickAction!==void 0?{click_action:s.clickAction}:{},...s.scheduledAt!==void 0?{scheduled_at:s.scheduledAt}:{}};return this.http.post(`/v1/apps/${e}/push/send`,r)}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 xe=5*1024*1024,x=class extends Error{constructor(e,t){super(e),this.name="VideoProcessingError",this.video=t}},ie=class{constructor(e,t){this.http=e;this.storage={create:async e=>this.http.post("/v1/public/storages/videos",e),list:async()=>this.http.get("/v1/public/storages/videos"),get:async e=>this.http.get(`/v1/public/storages/videos/${e}`),update:async(e,t)=>this.http.put(`/v1/public/storages/videos/${e}`,t),delete:async e=>{await this.http.delete(`/v1/public/storages/videos/${e}`)},upload:async(e,t,s)=>{let r=await this.http.post(`/v1/public/storages/videos/${e}/uploads/init`,{filename:t.name,size:t.size,mime_type:t.type,title:s.title,description:s.description,visibility:s.visibility||"private",tags:s.tags}),i=r.chunk_size||xe,n=Math.ceil(t.size/i),o=0,l=Date.now(),c=0;for(let u=0;u<n;u++){let p=u*i,f=Math.min(p+i,t.size),b=t.slice(p,f),h=new FormData;h.append("chunk",b),h.append("chunk_index",String(u)),await this.http.post(`/v1/public/storages/videos/${e}/uploads/${r.session_id}/chunk`,h),o++;let y=Date.now(),_=(y-l)/1e3,w=f,S=w-c,T=_>0?S/_:0;l=y,c=w,s.onProgress&&s.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:T})}return(await this.http.post(`/v1/public/storages/videos/${e}/uploads/${r.session_id}/complete`,{})).video},listVideos:async(e,t)=>{let s=new URLSearchParams;t?.status&&s.set("status",t.status),t?.visibility&&s.set("visibility",t.visibility),t?.search&&s.set("search",t.search),t?.page&&s.set("page",String(t.page)),t?.limit&&s.set("limit",String(t.limit));let r=s.toString();return this.http.get(`/v1/public/storages/videos/${e}/videos${r?`?${r}`:""}`)},getVideo:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}`),deleteVideo:async(e,t)=>{await this.http.delete(`/v1/public/storages/videos/${e}/videos/${t}`)},getStreamUrl:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}/stream`),getTranscodeStatus:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}/transcode`)};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.hasPublicKey()?"/v1/public":"/v1"}async videoFetch(e,t,s){let r={},i=this.http.getPublicKey();i&&(r["X-Public-Key"]=i);let n=this.http.getAccessToken();n&&(r.Authorization=`Bearer ${n}`),s&&!(s instanceof FormData)&&(r["Content-Type"]="application/json");let{signal:o,cleanup:l}=I({timeout:3e4});try{let c=await fetch(`${this.videoBaseUrl}${t}`,{method:e,headers:r,body:s instanceof FormData?s:s?JSON.stringify(s):void 0,signal:o});if(!c.ok){let d=await c.json().catch(()=>({message:c.statusText})),u=d.error;if(u&&typeof u=="object"&&"message"in u)throw new g(c.status,u.message||"Unknown error",u.code,u.details);let p=typeof u=="string"?u:d.message||"Unknown error";throw new g(c.status,p)}return c.status===204||c.headers.get("content-length")==="0"?{}:await c.json()}finally{l()}}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||xe,n=Math.ceil(e.size/i),o=0,c=Date.now(),d=0;for(let p=0;p<n;p++){let f=p*i,b=Math.min(f+i,e.size),h=e.slice(f,b),y=new FormData;y.append("chunk",h),y.append("chunk_index",String(p)),await this.videoFetch("POST",`${s}/uploads/${r.session_id}/chunks`,y),o++;let _=Date.now(),w=(_-c)/1e3,S=b,T=S-d,v=w>0?T/w:0;c=_,d=S,t.onProgress&&t.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:v})}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 x("Video processing failed",o);if(t?.onProgress){let l=o.qualities.filter(d=>d.status==="ready").length,c=o.qualities.length||1;t.onProgress({phase:"processing",uploadedChunks:0,totalChunks:0,percentage:Math.round(l/c*100)})}await new Promise(l=>setTimeout(l,r))}throw new x("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 $=class{constructor(e,t){this.http=e,this.appId=t}async get(e){let t=this.resolveAppId(e);return this.http.get(`/v1/apps/${t}/game/config`)}async set(e,t){let s,r;return typeof e=="string"?(s=e,r=t??{}):(s=this.resolveAppId(),r=e),this.http.patch(`/v1/apps/${s}/game/config`,r)}async enable(e,t){return this.set(e,{[t]:!0})}async disable(e,t){return this.set(e,{[t]:!1})}resolveAppId(e){let t=e??this.appId;if(!t)throw new Error("appId not provided (pass it explicitly or set in client constructor)");return t}};function P(a){let e=a.data??a,t=s=>e[s];return new m({code:t("code")||a.code,message:t("message")||"Unknown error",phase:t("phase")||a.phase,feature:t("feature")||a.feature,roomId:t("room_id")||a.room_id,scriptId:t("script_id")||a.script_id,originClientId:t("origin_client_id")||a.origin_client_id,requested:t("requested")||a.requested,available:t("available")||a.available})}var $e=()=>{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"};function H(a){let e={};return a.roomId&&(e.room_id=a.roomId),a.categoryId&&(e.category_id=a.categoryId),typeof a.tickRate=="number"&&(e.tick_rate=a.tickRate),typeof a.maxPlayers=="number"&&(e.max_players=a.maxPlayers),a.scriptName&&(e.script_name=a.scriptName),a.metadata&&(e.metadata=a.metadata),e}var L=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._scriptName=null;this._scriptVersion=null;this._isConnected=!1;this.msgIdCounter=0;this.config={gameServerUrl:$e(),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}if(!this.config.appId){s(new Error("cb.game: appId is required to connect. Pass it to `new ConnectBase({ appId })` or per-client via `cb.game.createClient({ appId, clientId })`."));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=c=>{this._isConnected=!1,this.stopPingInterval(),this.handlers.onDisconnect?.(c),this.config.autoReconnect&&c.code!==1e3&&this.scheduleReconnect(e)},o=c=>{this.handlers.onError?.(c),s(new Error("WebSocket connection failed"))},l=c=>{this.handleMessage(c.data)};this.ws.addEventListener("open",i,{once:!0}),this.ws.addEventListener("close",n),this.ws.addEventListener("error",o,{once:!0}),this.ws.addEventListener("message",l)})}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,this._scriptName=null,this._scriptVersion=null}createRoom(e={}){return this.createRoomDetailed(e).then(t=>t.state)}createRoomDetailed(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;return this._roomId=n.room_id,this._state=n.initial_state,this._scriptName=n.script_name??null,this._scriptVersion=n.script_version??null,t({roomId:n.room_id,state:n.initial_state,scriptName:n.script_name,scriptVersion:n.script_version}),!0}else if(i.type==="error")return s(P(i)),!0;return!1};this.sendWithHandler("create_room",H(e),r,15e3,s)})}get scriptName(){return this._scriptName}get scriptVersion(){return this._scriptVersion}joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;return this._roomId=o.room_id,this._state=o.initial_state,this._scriptName=null,this._scriptVersion=null,s(o.initial_state),!0}else if(n.type==="error")return r(P(n)),!0;return!1};this.sendWithHandler("join_room",{room_id:e,metadata:t},i,15e3,r)})}leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>r.type==="room_left"?(this._roomId=null,this._state=null,this._scriptName=null,this._scriptVersion=null,e(),!0):r.type==="error"?(t(P(r)),!0):!1;this.sendWithHandler("leave_room",{},s,15e3,t)})}sendAction(e){if(!this._roomId)throw new m({code:"NOT_IN_ROOM",message:"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 m({code:"NOT_IN_ROOM",message:"Not in a room"});this.send("chat",{message:e})}requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>{if(r.type==="state"){let i=r.data;return this._state=i,e(i),!0}else if(r.type==="error")return t(P(r)),!0;return!1};this.sendWithHandler("get_state",{},s,15e3,t)})}listRooms(){return new Promise((e,t)=>{let s=r=>{if(r.type==="room_list"){let i=r.data;return e(i.rooms),!0}else if(r.type==="error")return t(P(r)),!0;return!1};this.sendWithHandler("list_rooms",{},s,15e3,t)})}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;return this.handlers.onPong?.(n),e(o),!0}else if(i.type==="error")return t(P(i)),!0;return!1};this.sendWithHandler("ping",{timestamp:s},r,15e3,t)})}buildConnectionUrl(e){let s=this.config.gameServerUrl.replace(/^http/,"ws"),r=new URLSearchParams;if(r.set("client_id",this.config.clientId),e&&r.set("room_id",e),this.config.publicKey&&r.set("public_key",this.config.publicKey),this.config.accessToken&&r.set("token",this.config.accessToken),!this.config.appId)throw new Error("cb.game: appId is required to build a game connection URL");return`${s}/v1/game/${this.config.appId}/ws?${r.toString()}`}send(e,t,s){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("WebSocket is not connected");this.ws.send(JSON.stringify({type:e,data:t,msg_id:s}))}sendWithHandler(e,t,s,r=15e3,i){let n=`${e}-${++this.msgIdCounter}`,o=null,l=()=>{this.ws?.removeEventListener("message",c),o&&(clearTimeout(o),o=null)},c=d=>{try{let u=JSON.parse(d.data);if(u.msg_id&&u.msg_id!==n)return;s(u)&&l()}catch{}};this.ws?.addEventListener("message",c),o=setTimeout(()=>{l();let d=new m({code:"TIMEOUT",message:`Request '${e}' timed out after ${r}ms`});i?.(d),this.handlers.onError?.(d)},r);try{this.send(e,t,n)}catch(d){l();let u=d instanceof Error?d:new Error(String(d));i?.(u)}}handleMessage(e){try{let t=JSON.parse(e);switch(t.type){case"delta":this.handleDelta(t);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t);break;case"chat":this.handlers.onChat?.({roomId:t.room_id||"",clientId:t.client_id||"",userId:t.user_id,message:t.message||"",serverTime:t.server_time||0});break;case"error":this.handlers.onError?.(P(t));break;default:this.handlers.onMessage?.(t);break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(!t)return;let s={fromVersion:t.from_version,toVersion:t.to_version,changes:t.changes.map(r=>({path:r.path,operation:r.operation,value:r.value,oldValue:r.old_value})),tick:t.tick};if(this._state){for(let r of s.changes)this.applyChange(r);this._state.version=s.toVersion}if(this.handlers.onAction){for(let r of s.changes)if(r.path.startsWith("actions.")&&r.operation==="set"&&r.value){let i=r.value;this.handlers.onAction({type:i.type||"",clientId:i.client_id||"",data:i.data,timestamp:i.timestamp||0})}}this.handlers.onDelta?.(s)}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){let t={clientId:e.player?.client_id||"",userId:e.player?.user_id,joinedAt:e.player?.joined_at||0,metadata:e.player?.metadata};e.event==="joined"?this.handlers.onPlayerJoined?.(t):e.event==="left"&&this.handlers.onPlayerLeft?.(t)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached"),this.handlers.onError?.(new m({code:"MAX_RECONNECT_ATTEMPTS",message:"Maximum reconnection attempts reached"}));return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++;let s=e||this._roomId;this.reconnectTimer=setTimeout(async()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);try{if(await this.connect(),s){console.log(`Rejoining room ${s}...`);try{await this.joinRoom(s),console.log(`Successfully rejoined room ${s}`)}catch(r){console.error(`Failed to rejoin room ${s}:`,r),this.handlers.onError?.(new m({code:"REJOIN_FAILED",message:`Failed to rejoin room: ${r}`,roomId:s}))}}}catch{}},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}},A=class{constructor(e,t,s){this.http=e,this.gameServerUrl=t||$e().replace(/^ws/,"http"),this.appId=s,this.config=new $(e,s)}createClient(e){return new L({...e,gameServerUrl:this.gameServerUrl.replace(/^http/,"ws"),appId:e.appId??this.appId,publicKey:this.http.getPublicKey(),accessToken:this.http.getAccessToken()})}async listRooms(e){let t=e||this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms`,{headers:this.getHeaders()});if(!s.ok)throw new g(s.status,`Failed to list rooms: ${s.statusText}`,"GAME_LIST_ROOMS_FAILED");return(await s.json()).rooms}async getRoom(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms/${e}`,{headers:this.getHeaders()});if(!s.ok)throw new g(s.status,`Failed to get room: ${s.statusText}`,"GAME_GET_ROOM_FAILED");return s.json()}async createRoom(e,t={}){throw new Error("cb.game.createRoom is not yet publicly available \u2014 use the admin console or request a backend public route.")}async deleteRoom(e){throw new Error("cb.game.deleteRoom is not yet publicly available \u2014 use the admin console or request a backend public route.")}async joinSpectator(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/rooms/${e}/spectators`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to join spectator: ${r.statusText}`);return r.json()}async leaveSpectator(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/rooms/${e}/spectators/${t}`,{method:"DELETE",headers:this.getHeaders()});if(!r.ok)throw new Error(`Failed to leave spectator: ${r.statusText}`)}async getSpectators(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms/${e}/spectators`,{headers:this.getHeaders()});if(!s.ok)throw new Error(`Failed to get spectators: ${s.statusText}`);return(await s.json()).spectators||[]}async joinVoiceChannel(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/rooms/${e}/join`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to join voice channel: ${r.statusText}`);return r.json()}async leaveVoiceChannel(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/rooms/${e}/leave`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to leave voice channel: ${r.statusText}`)}async setMute(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/mute`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:e,muted:t})});if(!r.ok)throw new Error(`Failed to set mute: ${r.statusText}`)}async listReplays(e){let t=this.appId||"",s=`${this.gameServerUrl}/v1/game/${t}/replays`;e&&(s+=`?room_id=${e}`);let r=await fetch(s,{headers:this.getHeaders()});if(r.status===404)throw new Error("cb.game.listReplays: replay storage is not configured on this server (REPLAY_STORAGE_PATH unset).");if(!r.ok)throw new Error(`Failed to list replays: ${r.statusText}`);return(await r.json()).replays||[]}async getReplay(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.getReplay: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to get replay: ${s.statusText}`);return s.json()}async downloadReplay(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}/download`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.downloadReplay: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to download replay: ${s.statusText}`);return s.arrayBuffer()}async getReplayHighlights(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}/highlights`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.getReplayHighlights: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to get highlights: ${s.statusText}`);return(await s.json()).highlights||[]}getHeaders(){let e={},t=this.http.getPublicKey();t&&(e["X-Public-Key"]=t);let s=this.http.getAccessToken();return s&&(e.Authorization=`Bearer ${s}`),e}async gameFetch(e,t,s,r="GAME_REQUEST_FAILED"){let i={method:e,headers:{...this.getHeaders()}};s!==void 0&&(i.headers["Content-Type"]="application/json",i.body=JSON.stringify(s));let n=await fetch(`${this.gameServerUrl}${t}`,i);if(!n.ok){let l=r,c=`${e} ${t} failed: ${n.statusText}`;try{let d=await n.json();d&&typeof d.error=="string"&&(c=d.error,l=d.error)}catch{}throw new g(n.status,c,l)}if(!(n.status===204||!(n.headers.get("content-type")||"").includes("application/json")))return n.json()}async enqueueMatch(e,t,s,r,i){return this.gameFetch("POST",`/v1/game/${e}/matchqueue/${t}/tickets`,{ticket_id:s,attributes:r,ttl_sec:i??0},"GAME_ENQUEUE_MATCH_FAILED")}async listMatchqueue(e,t){return this.gameFetch("GET",`/v1/game/${e}/matchqueue/${t}`,void 0,"GAME_LIST_MATCHQUEUE_FAILED")}async cancelMatch(e,t,s){await this.gameFetch("DELETE",`/v1/game/${e}/matchqueue/${t}/tickets/${s}`,void 0,"GAME_CANCEL_MATCH_FAILED")}async submitScore(e,t,s,r,i="set"){return this.gameFetch("POST",`/v1/game/${e}/leaderboards/${t}/scores`,{member:s,score:r,mode:i},"GAME_SUBMIT_SCORE_FAILED")}async getTopScores(e,t,s=10){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/top?n=${s}`,void 0,"GAME_GET_TOP_SCORES_FAILED")}async getMemberRank(e,t,s){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/members/${s}`,void 0,"GAME_GET_MEMBER_RANK_FAILED")}async getRankAround(e,t,s,r=5,i=5){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/around/${s}?above=${r}&below=${i}`,void 0,"GAME_GET_RANK_AROUND_FAILED")}async resetLeaderboard(e,t){await this.gameFetch("DELETE",`/v1/game/${e}/leaderboards/${t}`,void 0,"GAME_RESET_LEADERBOARD_FAILED")}async removeFromLeaderboard(e,t,s){await this.gameFetch("DELETE",`/v1/game/${e}/leaderboards/${t}/members/${s}`,void 0,"GAME_REMOVE_FROM_LEADERBOARD_FAILED")}async uploadScript(e,t,s){return this.gameFetch("POST",`/v1/game/${e}/scripts`,{name:t,code:s},"GAME_UPLOAD_SCRIPT_FAILED")}async listScripts(e){return this.gameFetch("GET",`/v1/game/${e}/scripts`,void 0,"GAME_LIST_SCRIPTS_FAILED")}async getScript(e,t){return this.gameFetch("GET",`/v1/game/${e}/scripts/${t}`,void 0,"GAME_GET_SCRIPT_FAILED")}async listScriptVersions(e,t){return this.gameFetch("GET",`/v1/game/${e}/scripts/${t}/versions`,void 0,"GAME_LIST_SCRIPT_VERSIONS_FAILED")}async activateScript(e,t,s){return this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/activate`,{version:s??0},"GAME_ACTIVATE_SCRIPT_FAILED")}async rollbackScript(e,t){return this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/rollback`,{},"GAME_ROLLBACK_SCRIPT_FAILED")}async deactivateScript(e,t){await this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/deactivate`,{},"GAME_DEACTIVATE_SCRIPT_FAILED")}async deleteScript(e,t){await this.gameFetch("DELETE",`/v1/game/${e}/scripts/${t}`,void 0,"GAME_DELETE_SCRIPT_FAILED")}};var M=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/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`)}async getAdMobReport(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/admob/reports${i?`?${i}`:""}`)}async getAdMobReportSummary(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/admob/reports/summary`)}};var U=class{constructor(){this.clipboard={writeText:async e=>{this.getPlatform()==="desktop"&&window.NativeBridge?.clipboard?await window.NativeBridge.clipboard.writeText(e):await navigator.clipboard.writeText(e)},readText:async()=>this.getPlatform()==="desktop"&&window.NativeBridge?.clipboard?window.NativeBridge.clipboard.readText():navigator.clipboard.readText(),writeHTML:async e=>{window.NativeBridge?.clipboard?.writeHTML?await window.NativeBridge.clipboard.writeHTML(e):await navigator.clipboard.writeText(e)},writeImage:async e=>{if(window.NativeBridge?.clipboard?.writeImage)await window.NativeBridge.clipboard.writeImage(e);else throw new Error("Image clipboard not supported on this platform")},readImage:async()=>window.NativeBridge?.clipboard?.readImage?window.NativeBridge.clipboard.readImage():null};this.filesystem={pickFile:async e=>{if(this.getPlatform()==="desktop"&&window.NativeBridge?.filesystem?.showOpenDialog){let s=await window.NativeBridge.filesystem.showOpenDialog({properties:e?.multiple?["openFile","multiSelections"]:["openFile"],filters:e?.filters});return s.canceled||!s.filePaths.length?null:s.filePaths.map(r=>new File([],r.split("/").pop()||"file"))}return new Promise(s=>{let r=document.createElement("input");r.type="file",e?.accept&&(r.accept=e.accept),e?.multiple&&(r.multiple=!0),r.onchange=()=>{s(r.files?Array.from(r.files):null)},r.click()})},saveFile:async(e,t,s)=>{let r=this.getPlatform();if(r==="desktop"&&window.NativeBridge?.filesystem){let l=await window.NativeBridge.filesystem.showSaveDialog?.({defaultPath:t,filters:s?.filters});if(l?.canceled||!l?.filePath)return!1;let c=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(l.filePath,c)).success}if(r==="mobile"&&window.NativeBridge?.filesystem){let l=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(t,l)).success}let i=e instanceof Blob?e:new Blob([e],{type:"text/plain"}),n=URL.createObjectURL(i),o=document.createElement("a");return o.href=n,o.download=t,o.click(),URL.revokeObjectURL(n),!0},readFile:async e=>{if(window.NativeBridge?.filesystem?.readFile){let t=await window.NativeBridge.filesystem.readFile(e);return t.success?t.content??null:null}return null},exists:async e=>window.NativeBridge?.filesystem?.exists?window.NativeBridge.filesystem.exists(e):!1};this.camera={takePicture:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.camera?window.NativeBridge.camera.takePicture(e):new Promise(s=>{let r=document.createElement("input");r.type="file",r.accept="image/*",r.capture="environment",r.onchange=async()=>{let i=r.files?.[0];if(!i){s(null);return}let n=new FileReader;n.onload=()=>{let o=new Image;o.onload=()=>{s({uri:URL.createObjectURL(i),base64:e?.base64?n.result.split(",")[1]:void 0,width:o.width,height:o.height})},o.src=n.result},n.readAsDataURL(i)},r.click()}),pickImage:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.camera?window.NativeBridge.camera.pickImage(e):new Promise(s=>{let r=document.createElement("input");r.type="file",r.accept="image/*",e?.multiple&&(r.multiple=!0),r.onchange=async()=>{let i=r.files;if(!i?.length){s(null);return}let n=[];for(let o of Array.from(i)){let l=await new Promise(c=>{let d=new FileReader;d.onload=()=>{let u=new Image;u.onload=()=>{c({uri:URL.createObjectURL(o),base64:e?.base64?d.result.split(",")[1]:void 0,width:u.width,height:u.height})},u.src=d.result},d.readAsDataURL(o)});n.push(l)}s(n)},r.click()})};this.location={getCurrentPosition:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.location?window.NativeBridge.location.getCurrentPosition(e):new Promise((s,r)=>{navigator.geolocation.getCurrentPosition(i=>{s({latitude:i.coords.latitude,longitude:i.coords.longitude,altitude:i.coords.altitude,accuracy:i.coords.accuracy,timestamp:i.timestamp})},r,{enableHighAccuracy:e?.accuracy==="high",timeout:1e4,maximumAge:0})})};this.notification={show:async e=>this.getPlatform()==="desktop"&&window.NativeBridge?.notification?(await window.NativeBridge.notification.show(e)).success:!("Notification"in window)||Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted"?!1:(new Notification(e.title,{body:e.body,icon:e.icon,silent:e.silent}),!0),requestPermission:async()=>this.getPlatform()==="mobile"&&window.NativeBridge?.push?(await window.NativeBridge.push.requestPermission()).granted:"Notification"in window?await Notification.requestPermission()==="granted":!1};this.shell={openExternal:async e=>this.getPlatform()==="desktop"&&window.NativeBridge?.shell?(await window.NativeBridge.shell.openExternal(e)).success:(window.open(e,"_blank"),!0)};this.window={minimize:async()=>{await window.NativeBridge?.window?.minimize()},maximize:async()=>{await window.NativeBridge?.window?.maximize()},unmaximize:async()=>{await window.NativeBridge?.window?.unmaximize()},close:async()=>{await window.NativeBridge?.window?.close()},isMaximized:async()=>await window.NativeBridge?.window?.isMaximized()??!1,setTitle:async e=>{window.NativeBridge?.window?await window.NativeBridge.window.setTitle(e):document.title=e},setFullScreen:async e=>{window.NativeBridge?.window?await window.NativeBridge.window.setFullScreen(e):document.documentElement.requestFullscreen&&(e?await document.documentElement.requestFullscreen():document.exitFullscreen&&await document.exitFullscreen())}};this.system={getInfo:async()=>window.NativeBridge?.system?window.NativeBridge.system.getInfo():null,getMemory:async()=>window.NativeBridge?.system?window.NativeBridge.system.getMemory():null};this.biometric={isAvailable:async()=>window.NativeBridge?.biometric?window.NativeBridge.biometric.isAvailable():null,authenticate:async e=>window.NativeBridge?.biometric?window.NativeBridge.biometric.authenticate(e):null};this.secureStore={setItem:async(e,t)=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.setItem(e,t)).success:(localStorage.setItem(e,t),!0),getItem:async e=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.getItem(e)).value:localStorage.getItem(e),deleteItem:async e=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.deleteItem(e)).success:(localStorage.removeItem(e),!0)};this.admob={showInterstitial:async()=>window.NativeBridge?.admob?(await window.NativeBridge.admob.showInterstitial()).shown:!1,showRewarded:async()=>window.NativeBridge?.admob?(await window.NativeBridge.admob.showRewarded()).rewarded:!1}}getPlatform(){if(typeof window>"u")return"web";let e=window.NativeBridge;return e?.platform==="electron"?"desktop":e?.platform==="react-native"||e?.platform==="ios"||e?.platform==="android"||e?.camera||window.ReactNativeWebView?"mobile":"web"}hasFeature(e){return typeof window>"u"?!1:!!window.NativeBridge?.[e]}get bridge(){if(!(typeof window>"u"))return window.NativeBridge}};async function Ae(a){if(typeof Blob<"u"&&a instanceof Blob){let s=await a.arrayBuffer(),r="name"in a?a.name:void 0;return{data:Me(new Uint8Array(s)),mimeType:a.type||"",defaultName:r}}let e=a;if(!e||e.data===void 0)throw new Error("addDocumentFromFile: file must be a Blob/File or { data, mimeType?, name? }");let t=e.data instanceof Uint8Array?e.data:new Uint8Array(e.data);return{data:Me(t),mimeType:e.mimeType??"",defaultName:e.name}}function Me(a){let e=globalThis;if(e.Buffer)return e.Buffer.from(a).toString("base64");let t=8192,s="";for(let r=0;r<a.length;r+=t){let i=a.subarray(r,Math.min(r+t,a.length));s+=String.fromCharCode.apply(null,Array.from(i))}return btoa(s)}var ne=class{constructor(e){this.http=e}async addDocument(e,t){return this.http.post(`/v1/public/knowledge-bases/${e}/documents`,t)}async updateDocument(e,t,s){return this.http.put(`/v1/public/knowledge-bases/${e}/documents/${t}`,s)}async updateDocumentFromFile(e,t,s,r){let{data:i,mimeType:n}=await Ae(s);return this.updateDocument(e,t,{name:r?.name,file_content:i,mime_type:n,metadata:r?.metadata})}async addDocumentFromFile(e,t,s){let{data:r,mimeType:i,defaultName:n}=await Ae(t),o=s?.name??n??"document";return this.addDocument(e,{name:o,source_type:"file",file_content:r,mime_type:i,metadata:s?.metadata})}async listDocuments(e){return this.http.get(`/v1/public/knowledge-bases/${e}/documents`)}async deleteDocument(e,t){await this.http.delete(`/v1/public/knowledge-bases/${e}/documents/${t}`)}async search(e,t){return this.http.post(`/v1/public/knowledge-bases/${e}/search`,t)}async searchGet(e,t,s){let r=new URLSearchParams({query:t});return s&&r.append("top_k",String(s)),this.http.get(`/v1/public/knowledge-bases/${e}/search?${r.toString()}`)}};var O=class{constructor(e){this.http=e}async chat(e){return this.http.post("/v1/public/ai/chat",e)}async chatStream(e,t,s){let r=s?.signal,i=o=>r?.aborted===!0||o instanceof DOMException&&o.name==="AbortError"||typeof o=="object"&&o!==null&&o.name==="AbortError",n;try{let o=await this.http.fetchRaw("/v1/public/ai/chat/stream",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:r});if(!o.ok){let d=await o.json().catch(()=>({error:"Stream request failed"}));t.onError?.(d.error||"Stream request failed");return}if(n=o.body?.getReader(),!n){t.onError?.("ReadableStream not supported");return}let l=new TextDecoder,c="";for(;;){let{done:d,value:u}=await n.read();if(d)break;c+=l.decode(u,{stream:!0});let p=c.split(`
2
+ `).filter(c=>c.trim());for(let c of l)try{let d=JSON.parse(c);this.handleServerMessage(d,()=>{i||(i=!0,clearTimeout(n),this.log("Connected successfully"),e())})}catch(d){this.logError("Failed to parse message",{line:c,error:d})}},this.ws.onclose=o=>{this.log(`WebSocket closed: code=${o.code}, reason=${o.reason}`),!i&&this.state==="connecting"&&(i=!0,clearTimeout(n),t(new Error(`Connection closed: ${o.reason||"unknown reason"}`))),(this.state==="connected"||this.state==="connecting")&&this.handleDisconnect()},this.ws.onerror=o=>{this.log("WebSocket error occurred"),this.logError("WebSocket error",o),this.notifyError(new Error("WebSocket connection error")),!i&&this.state==="connecting"&&(i=!0,clearTimeout(n),t(new Error("Failed to connect")))}}catch(o){i=!0,clearTimeout(n),t(o)}})}log(e){this.options.debug&&console.log(`[Realtime] ${e}`)}logError(e,t){this.options.debug&&console.error(`[Realtime] ${e}`,t)}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_tool_call":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToolCall&&r.handlers.onToolCall(s.tool_name,s.arguments||{},s.index);break}case"stream_tool_result":{let s=e.data,r=this.streamSessions.get(s.session_id);r?.handlers.onToolResult&&r.handlers.onToolResult(s.tool_name,s.success,s.duration_ms,s.index);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}case"presence":case"presence_status":{let s=e.data,r={userId:s.user_id,status:s.status,lastSeen:s.last_seen,device:s.device,metadata:s.metadata,eventType:s.event_type};this.presenceHandlers.forEach(n=>n(r));let i=this.presenceSubscriptions.get(s.user_id);if(i&&i.forEach(n=>n(r)),e.request_id){let n=this.pendingRequests.get(e.request_id);n&&(clearTimeout(n.timeout),n.resolve(e.data),this.pendingRequests.delete(e.request_id))}break}case"typing":{let s=e.data,r={roomId:s.room_id,users:s.users},i=this.typingHandlers.get(s.room_id);i&&i.forEach(n=>n(r));break}case"read_receipt":{let s=e.data,r={category:s.category,messageIds:s.message_ids,readerId:s.reader_id,readAt:s.read_at},i=this.readReceiptHandlers.get(s.category);i&&i.forEach(n=>n(r));break}}}handleDisconnect(){this.ws=null,this._connectionId=null,this.streamSessions.forEach(r=>{r.handlers.onError&&r.handlers.onError(new Error("Connection lost"))}),this.streamSessions.clear();let e=new Map;for(let[r,i]of this.subscriptions)e.set(r,[...i.handlers]);this.subscriptions.clear();let t=new Map;for(let[r,i]of this.presenceSubscriptions)t.set(r,[...i]);this.presenceSubscriptions.clear();let s=new Map;for(let[r,i]of this.typingHandlers)s.set(r,[...i]);if(this.typingHandlers.clear(),this.retryCount<this.options.maxRetries){this.state="reconnecting",this.notifyStateChange(),this.retryCount++;let r=Math.min(this.options.retryInterval*Math.pow(2,this.retryCount-1),3e4);setTimeout(async()=>{try{await this.doConnect(),this.log("Reconnected successfully, restoring subscriptions..."),await this.restoreSubscriptions(e,t,s)}catch(i){this.logError("Reconnect failed",i)}},r)}else this.state="disconnected",this.notifyStateChange(),this.notifyError(new Error("Connection lost. Max retries exceeded."))}async restoreSubscriptions(e,t,s){for(let[r,i]of e)try{this.log(`Restoring subscription: ${r}`);let n=this.generateRequestId(),o=await this.sendRequest({category:r,action:"subscribe",request_id:n}),l={category:o.category,persist:o.persist,historyCount:o.history_count,readReceipt:o.read_receipt};this.subscriptions.set(r,{info:l,handlers:i}),this.log(`Restored subscription: ${r}`)}catch(n){this.logError(`Failed to restore subscription for ${r}`,n),this.notifyError(new Error(`Failed to restore subscription: ${r}`))}for(let[r,i]of t)try{this.log(`Restoring presence subscription: ${r}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:r},request_id:n}),this.presenceSubscriptions.set(r,i),this.log(`Restored presence subscription: ${r}`)}catch(n){this.logError(`Failed to restore presence subscription for ${r}`,n)}for(let[r,i]of s)try{this.log(`Restoring typing subscription: ${r}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:r},request_id:n}),this.typingHandlers.set(r,i),this.log(`Restored typing subscription: ${r}`)}catch(n){this.logError(`Failed to restore typing subscription for ${r}`,n)}}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"))},this.options.timeout);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(){if(!this.appId)throw new Error("WebRTC getICEServers \uC5D0\uB294 appId \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId \uB97C \uC124\uC815\uD558\uC138\uC694.");let e=await this.http.get(`/v1/apps/${this.appId}/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.getPublicKey(),s=this.http.getAccessToken(),r="";if(s?r=`access_token=${encodeURIComponent(s)}`:t&&(r=`public_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 l of o)l.peer_id!==this.currentPeerId&&await this.createPeerConnection(l.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 validate(){if(!this.appId)throw new Error("WebRTC \uAC80\uC99D\uC5D0\uB294 appId\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. ConnectBase \uCD08\uAE30\uD654 \uC2DC appId\uB97C \uC124\uC815\uD558\uC138\uC694.");return this.http.get(`/v1/apps/${this.appId}/validate`)}async getStats(e){return this.http.get(`/v1/apps/${e}/stats`)}async getRooms(e){return this.http.get(`/v1/apps/${e}/rooms`)}};var Y=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})}}};function Ee(){if(typeof window>"u")return null;let a=window.NativeBridge?.oauth;return!a||typeof a.signIn!="function"||typeof a.getCallbackScheme!="function"?null:a}function D(){if(typeof navigator>"u")return null;let a=navigator.userAgent||"";return/KAKAOTALK/i.test(a)?{type:"kakaotalk",ua:a}:/Line\//i.test(a)?{type:"line",ua:a}:/Instagram/i.test(a)?{type:"instagram",ua:a}:/FBAN|FBAV|FB_IAB/i.test(a)?{type:"facebook",ua:a}:/NAVER\(inapp/i.test(a)?{type:"naverapp",ua:a}:/MicroMessenger/i.test(a)?{type:"wechat",ua:a}:null}function Z(a){if(typeof window>"u")return!1;let e=D();if(!e)return!1;let t=a??window.location.href,s=encodeURIComponent(t);switch(e.type){case"kakaotalk":return window.location.href=`kakaotalk://web/openExternal?url=${s}`,!0;case"line":return window.location.href=`line://nv/openURL?url=${s}`,!0;default:return!1}}var ee=class{constructor(e){this.http=e;this.bootConsumePromise=null}async getEnabledProviders(){return this.http.get("/v1/public/oauth/providers")}async signIn(e,t,s,r){return this.startCentralOAuth(e,t,s,r?.createIfNotExists?"signup":"signin")}async signUp(e,t,s){return this.startCentralOAuth(e,t,s,"signup")}async startCentralOAuth(e,t,s,r){let i=Ee();if(i){let c=i.getCallbackScheme(),d=new URLSearchParams({app_callback:c,intent:r});s&&d.append("state",s);let u=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${d.toString()}`),{url:p}=await i.signIn(u.authorization_url,c),f=new URL(p),b=new URL(t,window.location.href);f.searchParams.forEach((h,y)=>{b.searchParams.set(y,h)}),window.location.href=b.toString();return}let n=D();if(n&&(e==="google"||e==="apple")){if(Z())return;throw new Error(`\uD604\uC7AC ${n.type==="kakaotalk"?"\uCE74\uCE74\uC624\uD1A1":n.type==="line"?"\uB77C\uC778":"\uC778\uC571"} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB294 Google/Apple \uB85C\uADF8\uC778\uC774 \uC81C\uD55C\uB429\uB2C8\uB2E4. \uC6B0\uCE21 \uC0C1\uB2E8 \uBA54\uB274\uC5D0\uC11C "\uB2E4\uB978 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uAE30"\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`)}let o=new URLSearchParams({app_callback:t,intent:r});s&&o.append("state",s);let l=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${o.toString()}`);window.location.href=l.authorization_url}async signInWithPopup(e,t,s={}){let r=s.intent??"signin",i=Ee();if(i){let b=i.getCallbackScheme(),h=new URLSearchParams({app_callback:b,intent:r}),y=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${h.toString()}`),{url:_}=await i.signIn(y.authorization_url,b),w=new URL(_),S=w.searchParams.get("error");if(S)throw new Error(S);let T=w.searchParams.get("access_token")||"",v=w.searchParams.get("refresh_token")||"",C=w.searchParams.get("member_id")||"";if(!T||!v||!C)throw new Error("OAuth \uC751\uB2F5\uC5D0 \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");return this.http.setTokens(T,v),{access_token:T,refresh_token:v,member_id:C,is_new_member:w.searchParams.get("is_new_member")==="true"}}let n=D();if(n&&(e==="google"||e==="apple"))throw Z()?new Error("\uC678\uBD80 \uBE0C\uB77C\uC6B0\uC800\uB85C \uC774\uB3D9\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."):new Error(`\uD604\uC7AC ${n.type==="kakaotalk"?"\uCE74\uCE74\uC624\uD1A1":n.type==="line"?"\uB77C\uC778":"\uC778\uC571"} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB294 Google/Apple \uB85C\uADF8\uC778\uC774 \uC81C\uD55C\uB429\uB2C8\uB2E4. \uC678\uBD80 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5B4\uC8FC\uC138\uC694.`);let o=new URLSearchParams;o.set("intent",r),t&&o.set("app_callback",t);let l=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${o.toString()}`),c=500,d=600,u=window.screenX+(window.outerWidth-c)/2,p=window.screenY+(window.outerHeight-d)/2,f=window.open(l.authorization_url,"oauth-popup",`width=${c},height=${d},left=${u},top=${p}`);if(!f)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((b,h)=>{let y=!1,_=()=>{y=!0,window.removeEventListener("message",w),clearInterval(S),clearTimeout(T)},w=async v=>{if(v.data?.type!=="oauth-callback"||y)return;if(_(),v.data.error){h(new Error(v.data.error));return}let C=v.data.email,he={member_id:v.data.member_id,access_token:v.data.access_token,refresh_token:v.data.refresh_token,is_new_member:v.data.is_new_member==="true"||v.data.is_new_member===!0,...typeof C=="string"&&C.length>0?{email:C}:{}};this.http.setTokens(he.access_token,he.refresh_token);try{await this.http.bootstrapRefreshCookie()}catch{}b(he)};window.addEventListener("message",w);let S=setInterval(()=>{try{f.closed&&(y||(_(),h(new Error("\uB85C\uADF8\uC778\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))))}catch{clearInterval(S)}},500),T=setTimeout(()=>{if(!y){_();try{f.close()}catch{}h(new Error("\uB85C\uADF8\uC778 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."))}},18e4)})}async getCallbackResult(){let e=new URLSearchParams(window.location.search),t=e.get("error");if(t){let l={error:t};return window.opener&&(window.opener.postMessage({type:"oauth-callback",...l},"*"),window.close()),l}let s=e.get("access_token"),r=e.get("refresh_token"),i=e.get("member_id");if(!s||!r||!i)return null;let n=e.get("email"),o={access_token:s,refresh_token:r,member_id:i,is_new_member:e.get("is_new_member")==="true",...n?{email:n}:{},state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...o},"*"),window.close(),o):(this.bootConsumePromise&&await this.bootConsumePromise||(this.http.setTokens(s,r),await this.http.bootstrapRefreshCookie()),o)}consumeRedirectCallbackOnBoot(){return this.bootConsumePromise||(this.bootConsumePromise=this.doConsumeRedirectOnBoot()),this.bootConsumePromise}async doConsumeRedirectOnBoot(){try{if(typeof window>"u"||!window.location)return!1;let e=new URLSearchParams(window.location.search);if(e.get("error"))return!1;let t=e.get("access_token"),s=e.get("refresh_token"),r=e.get("member_id");return!t||!s||!r?!1:(this.http.setTokens(t,s),await this.http.bootstrapRefreshCookie(),!0)}catch{return!1}}async exchangeCodeFromCallback(){let e=new URLSearchParams(window.location.search),t=e.get("code");if(!t||e.get("error"))return null;let s=await this.http.post("/v1/auth/oauth/exchange",{code:t}),r={access_token:s.access_token,refresh_token:s.refresh_token,member_id:s.member_id,is_new_member:s.is_new_member,...s.email?{email:s.email}:{},state:e.get("state")||void 0};return window.opener?(window.opener.postMessage({type:"oauth-callback",...r},"*"),window.close(),r):(this.http.setTokens(r.access_token,r.refresh_token),await this.http.bootstrapRefreshCookie(),r)}};var te=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async prepare(e){let t=this.getPublicPrefix();return this.http.post(`${t}/payments/prepare`,e)}async createCheckoutSession(e){let t=this.getPublicPrefix(),s=await this.http.post(`${t}/payments/checkout-session`,e);return R(s,{session_id:{type:"string"},session_url:{type:"string"}},"payment.createCheckoutSession"),s}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 se=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/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(),s=await this.http.post(`${t}/subscriptions`,e);return R(s,{id:{type:"string-or-number"},status:{type:"string"}},"subscription.create"),s}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 re=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async registerDevice(e){let t=this.getPublicPrefix(),s=await this.http.post(`${t}/push/devices`,e);return R(s,{device_token:{type:"string"}},"push.registerDevice"),s}async unregisterDevice(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${e}`)}async subscribeTopic(e,t){let s=this.getPublicPrefix(),r={topic_name:t};await this.http.post(`${s}/push/devices/${e}/topics/subscribe`,r)}async unsubscribeTopic(e,t){let s=this.getPublicPrefix();await this.http.delete(`${s}/push/devices/${e}/topics/${t}/unsubscribe`)}async getVAPIDPublicKey(){let e=this.getPublicPrefix();return this.http.get(`${e}/push/vapid-public-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`,{...r,web_push_subscription:s})}async unregisterWebPush(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/push/devices/${encodeURIComponent(e)}`)}async sendToMembers(e,t,s){if(this.http.hasPublicKey())throw new Error("cb.push.sendToMembers() \uB294 User Secret Key(cb_sk_) \uB610\uB294 \uCF58\uC194 JWT \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.");if(t.length===0)throw new Error("cb.push.sendToMembers(): memberIds \uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.");let r={target_type:"members",target_member_ids:t,title:s.title,body:s.body,...s.imageUrl!==void 0?{image_url:s.imageUrl}:{},...s.data!==void 0?{data:s.data}:{},...s.platforms!==void 0?{platforms:s.platforms}:{},...s.ttlSeconds!==void 0?{ttl:s.ttlSeconds}:{},...s.priority!==void 0?{priority:s.priority}:{},...s.clickAction!==void 0?{click_action:s.clickAction}:{},...s.scheduledAt!==void 0?{scheduled_at:s.scheduledAt}:{}};return this.http.post(`/v1/apps/${e}/push/send`,r)}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 xe=5*1024*1024,x=class extends Error{constructor(e,t){super(e),this.name="VideoProcessingError",this.video=t}},ie=class{constructor(e,t){this.http=e;this.storage={create:async e=>this.http.post("/v1/public/storages/videos",e),list:async()=>this.http.get("/v1/public/storages/videos"),get:async e=>this.http.get(`/v1/public/storages/videos/${e}`),update:async(e,t)=>this.http.put(`/v1/public/storages/videos/${e}`,t),delete:async e=>{await this.http.delete(`/v1/public/storages/videos/${e}`)},upload:async(e,t,s)=>{let r=await this.http.post(`/v1/public/storages/videos/${e}/uploads/init`,{filename:t.name,size:t.size,mime_type:t.type,title:s.title,description:s.description,visibility:s.visibility||"private",tags:s.tags}),i=r.chunk_size||xe,n=Math.ceil(t.size/i),o=0,l=Date.now(),c=0;for(let u=0;u<n;u++){let p=u*i,f=Math.min(p+i,t.size),b=t.slice(p,f),h=new FormData;h.append("chunk",b),h.append("chunk_index",String(u)),await this.http.post(`/v1/public/storages/videos/${e}/uploads/${r.session_id}/chunk`,h),o++;let y=Date.now(),_=(y-l)/1e3,w=f,S=w-c,T=_>0?S/_:0;l=y,c=w,s.onProgress&&s.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:T})}return(await this.http.post(`/v1/public/storages/videos/${e}/uploads/${r.session_id}/complete`,{})).video},listVideos:async(e,t)=>{let s=new URLSearchParams;t?.status&&s.set("status",t.status),t?.visibility&&s.set("visibility",t.visibility),t?.search&&s.set("search",t.search),t?.page&&s.set("page",String(t.page)),t?.limit&&s.set("limit",String(t.limit));let r=s.toString();return this.http.get(`/v1/public/storages/videos/${e}/videos${r?`?${r}`:""}`)},getVideo:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}`),deleteVideo:async(e,t)=>{await this.http.delete(`/v1/public/storages/videos/${e}/videos/${t}`)},getStreamUrl:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}/stream`),getTranscodeStatus:async(e,t)=>this.http.get(`/v1/public/storages/videos/${e}/videos/${t}/transcode`)};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.hasPublicKey()?"/v1/public":"/v1"}async videoFetch(e,t,s){let r={},i=this.http.getPublicKey();i&&(r["X-Public-Key"]=i);let n=this.http.getAccessToken();n&&(r.Authorization=`Bearer ${n}`),s&&!(s instanceof FormData)&&(r["Content-Type"]="application/json");let{signal:o,cleanup:l}=I({timeout:3e4});try{let c=await fetch(`${this.videoBaseUrl}${t}`,{method:e,headers:r,body:s instanceof FormData?s:s?JSON.stringify(s):void 0,signal:o});if(!c.ok){let d=await c.json().catch(()=>({message:c.statusText})),u=d.error;if(u&&typeof u=="object"&&"message"in u)throw new g(c.status,u.message||"Unknown error",u.code,u.details);let p=typeof u=="string"?u:d.message||"Unknown error";throw new g(c.status,p)}return c.status===204||c.headers.get("content-length")==="0"?{}:await c.json()}finally{l()}}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||xe,n=Math.ceil(e.size/i),o=0,c=Date.now(),d=0;for(let p=0;p<n;p++){let f=p*i,b=Math.min(f+i,e.size),h=e.slice(f,b),y=new FormData;y.append("chunk",h),y.append("chunk_index",String(p)),await this.videoFetch("POST",`${s}/uploads/${r.session_id}/chunks`,y),o++;let _=Date.now(),w=(_-c)/1e3,S=b,T=S-d,v=w>0?T/w:0;c=_,d=S,t.onProgress&&t.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:v})}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 x("Video processing failed",o);if(t?.onProgress){let l=o.qualities.filter(d=>d.status==="ready").length,c=o.qualities.length||1;t.onProgress({phase:"processing",uploadedChunks:0,totalChunks:0,percentage:Math.round(l/c*100)})}await new Promise(l=>setTimeout(l,r))}throw new x("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 $=class{constructor(e,t){this.http=e,this.appId=t}async get(e){let t=this.resolveAppId(e);return this.http.get(`/v1/apps/${t}/game/config`)}async set(e,t){let s,r;return typeof e=="string"?(s=e,r=t??{}):(s=this.resolveAppId(),r=e),this.http.patch(`/v1/apps/${s}/game/config`,r)}async enable(e,t){return this.set(e,{[t]:!0})}async disable(e,t){return this.set(e,{[t]:!1})}resolveAppId(e){let t=e??this.appId;if(!t)throw new Error("appId not provided (pass it explicitly or set in client constructor)");return t}};function P(a){let e=a.data??a,t=s=>e[s];return new m({code:t("code")||a.code,message:t("message")||"Unknown error",phase:t("phase")||a.phase,feature:t("feature")||a.feature,roomId:t("room_id")||a.room_id,scriptId:t("script_id")||a.script_id,originClientId:t("origin_client_id")||a.origin_client_id,requested:t("requested")||a.requested,available:t("available")||a.available})}var $e=()=>{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"};function H(a){let e={};return a.roomId&&(e.room_id=a.roomId),a.categoryId&&(e.category_id=a.categoryId),typeof a.tickRate=="number"&&(e.tick_rate=a.tickRate),typeof a.maxPlayers=="number"&&(e.max_players=a.maxPlayers),a.scriptName&&(e.script_name=a.scriptName),a.metadata&&(e.metadata=a.metadata),e}var L=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._scriptName=null;this._scriptVersion=null;this._isConnected=!1;this.msgIdCounter=0;this.config={gameServerUrl:$e(),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}if(!this.config.appId){s(new Error("cb.game: appId is required to connect. Pass it to `new ConnectBase({ appId })` or per-client via `cb.game.createClient({ appId, clientId })`."));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=c=>{this._isConnected=!1,this.stopPingInterval(),this.handlers.onDisconnect?.(c),this.config.autoReconnect&&c.code!==1e3&&this.scheduleReconnect(e)},o=c=>{this.handlers.onError?.(c),s(new Error("WebSocket connection failed"))},l=c=>{this.handleMessage(c.data)};this.ws.addEventListener("open",i,{once:!0}),this.ws.addEventListener("close",n),this.ws.addEventListener("error",o,{once:!0}),this.ws.addEventListener("message",l)})}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,this._scriptName=null,this._scriptVersion=null}createRoom(e={}){return this.createRoomDetailed(e).then(t=>t.state)}createRoomDetailed(e={}){return new Promise((t,s)=>{let r=i=>{if(i.type==="room_created"){let n=i.data;return this._roomId=n.room_id,this._state=n.initial_state,this._scriptName=n.script_name??null,this._scriptVersion=n.script_version??null,t({roomId:n.room_id,state:n.initial_state,scriptName:n.script_name,scriptVersion:n.script_version}),!0}else if(i.type==="error")return s(P(i)),!0;return!1};this.sendWithHandler("create_room",H(e),r,15e3,s)})}get scriptName(){return this._scriptName}get scriptVersion(){return this._scriptVersion}joinRoom(e,t){return new Promise((s,r)=>{let i=n=>{if(n.type==="room_joined"){let o=n.data;return this._roomId=o.room_id,this._state=o.initial_state,this._scriptName=null,this._scriptVersion=null,s(o.initial_state),!0}else if(n.type==="error")return r(P(n)),!0;return!1};this.sendWithHandler("join_room",{room_id:e,metadata:t},i,15e3,r)})}leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>r.type==="room_left"?(this._roomId=null,this._state=null,this._scriptName=null,this._scriptVersion=null,e(),!0):r.type==="error"?(t(P(r)),!0):!1;this.sendWithHandler("leave_room",{},s,15e3,t)})}sendAction(e){if(!this._roomId)throw new m({code:"NOT_IN_ROOM",message:"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 m({code:"NOT_IN_ROOM",message:"Not in a room"});this.send("chat",{message:e})}requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>{if(r.type==="state"){let i=r.data;return this._state=i,e(i),!0}else if(r.type==="error")return t(P(r)),!0;return!1};this.sendWithHandler("get_state",{},s,15e3,t)})}listRooms(){return new Promise((e,t)=>{let s=r=>{if(r.type==="room_list"){let i=r.data;return e(i.rooms),!0}else if(r.type==="error")return t(P(r)),!0;return!1};this.sendWithHandler("list_rooms",{},s,15e3,t)})}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;return this.handlers.onPong?.(n),e(o),!0}else if(i.type==="error")return t(P(i)),!0;return!1};this.sendWithHandler("ping",{timestamp:s},r,15e3,t)})}buildConnectionUrl(e){let s=this.config.gameServerUrl.replace(/^http/,"ws"),r=new URLSearchParams;if(r.set("client_id",this.config.clientId),e&&r.set("room_id",e),this.config.publicKey&&r.set("public_key",this.config.publicKey),this.config.accessToken&&r.set("token",this.config.accessToken),!this.config.appId)throw new Error("cb.game: appId is required to build a game connection URL");return`${s}/v1/game/${this.config.appId}/ws?${r.toString()}`}send(e,t,s){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("WebSocket is not connected");this.ws.send(JSON.stringify({type:e,data:t,msg_id:s}))}sendWithHandler(e,t,s,r=15e3,i){let n=`${e}-${++this.msgIdCounter}`,o=null,l=()=>{this.ws?.removeEventListener("message",c),o&&(clearTimeout(o),o=null)},c=d=>{try{let u=JSON.parse(d.data);if(u.msg_id&&u.msg_id!==n)return;s(u)&&l()}catch{}};this.ws?.addEventListener("message",c),o=setTimeout(()=>{l();let d=new m({code:"TIMEOUT",message:`Request '${e}' timed out after ${r}ms`});i?.(d),this.handlers.onError?.(d)},r);try{this.send(e,t,n)}catch(d){l();let u=d instanceof Error?d:new Error(String(d));i?.(u)}}handleMessage(e){try{let t=JSON.parse(e);switch(t.type){case"delta":this.handleDelta(t);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t);break;case"chat":this.handlers.onChat?.({roomId:t.room_id||"",clientId:t.client_id||"",userId:t.user_id,message:t.message||"",serverTime:t.server_time||0});break;case"error":this.handlers.onError?.(P(t));break;default:this.handlers.onMessage?.(t);break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(!t)return;let s={fromVersion:t.from_version,toVersion:t.to_version,changes:t.changes.map(r=>({path:r.path,operation:r.operation,value:r.value,oldValue:r.old_value})),tick:t.tick};if(this._state){for(let r of s.changes)this.applyChange(r);this._state.version=s.toVersion}if(this.handlers.onAction){for(let r of s.changes)if(r.path.startsWith("actions.")&&r.operation==="set"&&r.value){let i=r.value;this.handlers.onAction({type:i.type||"",clientId:i.client_id||"",data:i.data,timestamp:i.timestamp||0})}}this.handlers.onDelta?.(s)}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){let t={clientId:e.player?.client_id||"",userId:e.player?.user_id,joinedAt:e.player?.joined_at||0,metadata:e.player?.metadata};e.event==="joined"?this.handlers.onPlayerJoined?.(t):e.event==="left"&&this.handlers.onPlayerLeft?.(t)}scheduleReconnect(e){if(this.reconnectAttempts>=(this.config.maxReconnectAttempts??5)){console.error("Max reconnect attempts reached"),this.handlers.onError?.(new m({code:"MAX_RECONNECT_ATTEMPTS",message:"Maximum reconnection attempts reached"}));return}let t=Math.min((this.config.reconnectInterval??1e3)*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++;let s=e||this._roomId;this.reconnectTimer=setTimeout(async()=>{console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);try{if(await this.connect(),s){console.log(`Rejoining room ${s}...`);try{await this.joinRoom(s),console.log(`Successfully rejoined room ${s}`)}catch(r){console.error(`Failed to rejoin room ${s}:`,r),this.handlers.onError?.(new m({code:"REJOIN_FAILED",message:`Failed to rejoin room: ${r}`,roomId:s}))}}}catch{}},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}},A=class{constructor(e,t,s){this.http=e,this.gameServerUrl=t||$e().replace(/^ws/,"http"),this.appId=s,this.config=new $(e,s)}createClient(e){return new L({...e,gameServerUrl:this.gameServerUrl.replace(/^http/,"ws"),appId:e.appId??this.appId,publicKey:this.http.getPublicKey(),accessToken:this.http.getAccessToken()})}async listRooms(e){let t=e||this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms`,{headers:this.getHeaders()});if(!s.ok)throw new g(s.status,`Failed to list rooms: ${s.statusText}`,"GAME_LIST_ROOMS_FAILED");return(await s.json()).rooms}async getRoom(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms/${e}`,{headers:this.getHeaders()});if(!s.ok)throw new g(s.status,`Failed to get room: ${s.statusText}`,"GAME_GET_ROOM_FAILED");return s.json()}async createRoom(e,t={}){throw new Error("cb.game.createRoom is not yet publicly available \u2014 use the admin console or request a backend public route.")}async deleteRoom(e){throw new Error("cb.game.deleteRoom is not yet publicly available \u2014 use the admin console or request a backend public route.")}async joinSpectator(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/rooms/${e}/spectators`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to join spectator: ${r.statusText}`);return r.json()}async leaveSpectator(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/rooms/${e}/spectators/${t}`,{method:"DELETE",headers:this.getHeaders()});if(!r.ok)throw new Error(`Failed to leave spectator: ${r.statusText}`)}async getSpectators(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/rooms/${e}/spectators`,{headers:this.getHeaders()});if(!s.ok)throw new Error(`Failed to get spectators: ${s.statusText}`);return(await s.json()).spectators||[]}async joinVoiceChannel(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/rooms/${e}/join`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to join voice channel: ${r.statusText}`);return r.json()}async leaveVoiceChannel(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/rooms/${e}/leave`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:t})});if(!r.ok)throw new Error(`Failed to leave voice channel: ${r.statusText}`)}async setMute(e,t){let s=this.appId||"",r=await fetch(`${this.gameServerUrl}/v1/game/${s}/voice/mute`,{method:"POST",headers:{...this.getHeaders(),"Content-Type":"application/json"},body:JSON.stringify({player_id:e,muted:t})});if(!r.ok)throw new Error(`Failed to set mute: ${r.statusText}`)}async listReplays(e){let t=this.appId||"",s=`${this.gameServerUrl}/v1/game/${t}/replays`;e&&(s+=`?room_id=${e}`);let r=await fetch(s,{headers:this.getHeaders()});if(r.status===404)throw new Error("cb.game.listReplays: replay storage is not configured on this server (REPLAY_STORAGE_PATH unset).");if(!r.ok)throw new Error(`Failed to list replays: ${r.statusText}`);return(await r.json()).replays||[]}async getReplay(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.getReplay: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to get replay: ${s.statusText}`);return s.json()}async downloadReplay(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}/download`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.downloadReplay: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to download replay: ${s.statusText}`);return s.arrayBuffer()}async getReplayHighlights(e){let t=this.appId||"",s=await fetch(`${this.gameServerUrl}/v1/game/${t}/replays/${e}/highlights`,{headers:this.getHeaders()});if(s.status===404)throw new Error("cb.game.getReplayHighlights: replay not found or replay storage unconfigured.");if(!s.ok)throw new Error(`Failed to get highlights: ${s.statusText}`);return(await s.json()).highlights||[]}getHeaders(){let e={},t=this.http.getPublicKey();t&&(e["X-Public-Key"]=t);let s=this.http.getAccessToken();return s&&(e.Authorization=`Bearer ${s}`),e}async gameFetch(e,t,s,r="GAME_REQUEST_FAILED"){let i={method:e,headers:{...this.getHeaders()}};s!==void 0&&(i.headers["Content-Type"]="application/json",i.body=JSON.stringify(s));let n=await fetch(`${this.gameServerUrl}${t}`,i);if(!n.ok){let l=r,c=`${e} ${t} failed: ${n.statusText}`;try{let d=await n.json();d&&typeof d.error=="string"&&(c=d.error,l=d.error)}catch{}throw new g(n.status,c,l)}if(!(n.status===204||!(n.headers.get("content-type")||"").includes("application/json")))return n.json()}async enqueueMatch(e,t,s,r,i){return this.gameFetch("POST",`/v1/game/${e}/matchqueue/${t}/tickets`,{ticket_id:s,attributes:r,ttl_sec:i??0},"GAME_ENQUEUE_MATCH_FAILED")}async listMatchqueue(e,t){return this.gameFetch("GET",`/v1/game/${e}/matchqueue/${t}`,void 0,"GAME_LIST_MATCHQUEUE_FAILED")}async cancelMatch(e,t,s){await this.gameFetch("DELETE",`/v1/game/${e}/matchqueue/${t}/tickets/${s}`,void 0,"GAME_CANCEL_MATCH_FAILED")}async submitScore(e,t,s,r,i="set"){return this.gameFetch("POST",`/v1/game/${e}/leaderboards/${t}/scores`,{member:s,score:r,mode:i},"GAME_SUBMIT_SCORE_FAILED")}async getTopScores(e,t,s=10){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/top?n=${s}`,void 0,"GAME_GET_TOP_SCORES_FAILED")}async getMemberRank(e,t,s){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/members/${s}`,void 0,"GAME_GET_MEMBER_RANK_FAILED")}async getRankAround(e,t,s,r=5,i=5){return this.gameFetch("GET",`/v1/game/${e}/leaderboards/${t}/around/${s}?above=${r}&below=${i}`,void 0,"GAME_GET_RANK_AROUND_FAILED")}async resetLeaderboard(e,t){await this.gameFetch("DELETE",`/v1/game/${e}/leaderboards/${t}`,void 0,"GAME_RESET_LEADERBOARD_FAILED")}async removeFromLeaderboard(e,t,s){await this.gameFetch("DELETE",`/v1/game/${e}/leaderboards/${t}/members/${s}`,void 0,"GAME_REMOVE_FROM_LEADERBOARD_FAILED")}async uploadScript(e,t,s){return this.gameFetch("POST",`/v1/game/${e}/scripts`,{name:t,code:s},"GAME_UPLOAD_SCRIPT_FAILED")}async listScripts(e){return this.gameFetch("GET",`/v1/game/${e}/scripts`,void 0,"GAME_LIST_SCRIPTS_FAILED")}async getScript(e,t){return this.gameFetch("GET",`/v1/game/${e}/scripts/${t}`,void 0,"GAME_GET_SCRIPT_FAILED")}async listScriptVersions(e,t){return this.gameFetch("GET",`/v1/game/${e}/scripts/${t}/versions`,void 0,"GAME_LIST_SCRIPT_VERSIONS_FAILED")}async activateScript(e,t,s){return this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/activate`,{version:s??0},"GAME_ACTIVATE_SCRIPT_FAILED")}async rollbackScript(e,t){return this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/rollback`,{},"GAME_ROLLBACK_SCRIPT_FAILED")}async deactivateScript(e,t){await this.gameFetch("POST",`/v1/game/${e}/scripts/${t}/deactivate`,{},"GAME_DEACTIVATE_SCRIPT_FAILED")}async deleteScript(e,t){await this.gameFetch("DELETE",`/v1/game/${e}/scripts/${t}`,void 0,"GAME_DELETE_SCRIPT_FAILED")}};var M=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/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`)}async getAdMobReport(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/admob/reports${i?`?${i}`:""}`)}async getAdMobReportSummary(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/admob/reports/summary`)}};var U=class{constructor(){this.clipboard={writeText:async e=>{this.getPlatform()==="desktop"&&window.NativeBridge?.clipboard?await window.NativeBridge.clipboard.writeText(e):await navigator.clipboard.writeText(e)},readText:async()=>this.getPlatform()==="desktop"&&window.NativeBridge?.clipboard?window.NativeBridge.clipboard.readText():navigator.clipboard.readText(),writeHTML:async e=>{window.NativeBridge?.clipboard?.writeHTML?await window.NativeBridge.clipboard.writeHTML(e):await navigator.clipboard.writeText(e)},writeImage:async e=>{if(window.NativeBridge?.clipboard?.writeImage)await window.NativeBridge.clipboard.writeImage(e);else throw new Error("Image clipboard not supported on this platform")},readImage:async()=>window.NativeBridge?.clipboard?.readImage?window.NativeBridge.clipboard.readImage():null};this.filesystem={pickFile:async e=>{if(this.getPlatform()==="desktop"&&window.NativeBridge?.filesystem?.showOpenDialog){let s=await window.NativeBridge.filesystem.showOpenDialog({properties:e?.multiple?["openFile","multiSelections"]:["openFile"],filters:e?.filters});return s.canceled||!s.filePaths.length?null:s.filePaths.map(r=>new File([],r.split("/").pop()||"file"))}return new Promise(s=>{let r=document.createElement("input");r.type="file",e?.accept&&(r.accept=e.accept),e?.multiple&&(r.multiple=!0),r.onchange=()=>{s(r.files?Array.from(r.files):null)},r.click()})},saveFile:async(e,t,s)=>{let r=this.getPlatform();if(r==="desktop"&&window.NativeBridge?.filesystem){let l=await window.NativeBridge.filesystem.showSaveDialog?.({defaultPath:t,filters:s?.filters});if(l?.canceled||!l?.filePath)return!1;let c=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(l.filePath,c)).success}if(r==="mobile"&&window.NativeBridge?.filesystem){let l=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(t,l)).success}let i=e instanceof Blob?e:new Blob([e],{type:"text/plain"}),n=URL.createObjectURL(i),o=document.createElement("a");return o.href=n,o.download=t,o.click(),URL.revokeObjectURL(n),!0},readFile:async e=>{if(window.NativeBridge?.filesystem?.readFile){let t=await window.NativeBridge.filesystem.readFile(e);return t.success?t.content??null:null}return null},exists:async e=>window.NativeBridge?.filesystem?.exists?window.NativeBridge.filesystem.exists(e):!1};this.camera={takePicture:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.camera?window.NativeBridge.camera.takePicture(e):new Promise(s=>{let r=document.createElement("input");r.type="file",r.accept="image/*",r.capture="environment",r.onchange=async()=>{let i=r.files?.[0];if(!i){s(null);return}let n=new FileReader;n.onload=()=>{let o=new Image;o.onload=()=>{s({uri:URL.createObjectURL(i),base64:e?.base64?n.result.split(",")[1]:void 0,width:o.width,height:o.height})},o.src=n.result},n.readAsDataURL(i)},r.click()}),pickImage:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.camera?window.NativeBridge.camera.pickImage(e):new Promise(s=>{let r=document.createElement("input");r.type="file",r.accept="image/*",e?.multiple&&(r.multiple=!0),r.onchange=async()=>{let i=r.files;if(!i?.length){s(null);return}let n=[];for(let o of Array.from(i)){let l=await new Promise(c=>{let d=new FileReader;d.onload=()=>{let u=new Image;u.onload=()=>{c({uri:URL.createObjectURL(o),base64:e?.base64?d.result.split(",")[1]:void 0,width:u.width,height:u.height})},u.src=d.result},d.readAsDataURL(o)});n.push(l)}s(n)},r.click()})};this.location={getCurrentPosition:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.location?window.NativeBridge.location.getCurrentPosition(e):new Promise((s,r)=>{navigator.geolocation.getCurrentPosition(i=>{s({latitude:i.coords.latitude,longitude:i.coords.longitude,altitude:i.coords.altitude,accuracy:i.coords.accuracy,timestamp:i.timestamp})},r,{enableHighAccuracy:e?.accuracy==="high",timeout:1e4,maximumAge:0})})};this.notification={show:async e=>this.getPlatform()==="desktop"&&window.NativeBridge?.notification?(await window.NativeBridge.notification.show(e)).success:!("Notification"in window)||Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted"?!1:(new Notification(e.title,{body:e.body,icon:e.icon,silent:e.silent}),!0),requestPermission:async()=>this.getPlatform()==="mobile"&&window.NativeBridge?.push?(await window.NativeBridge.push.requestPermission()).granted:"Notification"in window?await Notification.requestPermission()==="granted":!1};this.shell={openExternal:async e=>this.getPlatform()==="desktop"&&window.NativeBridge?.shell?(await window.NativeBridge.shell.openExternal(e)).success:(window.open(e,"_blank"),!0)};this.window={minimize:async()=>{await window.NativeBridge?.window?.minimize()},maximize:async()=>{await window.NativeBridge?.window?.maximize()},unmaximize:async()=>{await window.NativeBridge?.window?.unmaximize()},close:async()=>{await window.NativeBridge?.window?.close()},isMaximized:async()=>await window.NativeBridge?.window?.isMaximized()??!1,setTitle:async e=>{window.NativeBridge?.window?await window.NativeBridge.window.setTitle(e):document.title=e},setFullScreen:async e=>{window.NativeBridge?.window?await window.NativeBridge.window.setFullScreen(e):document.documentElement.requestFullscreen&&(e?await document.documentElement.requestFullscreen():document.exitFullscreen&&await document.exitFullscreen())}};this.system={getInfo:async()=>window.NativeBridge?.system?window.NativeBridge.system.getInfo():null,getMemory:async()=>window.NativeBridge?.system?window.NativeBridge.system.getMemory():null};this.biometric={isAvailable:async()=>window.NativeBridge?.biometric?window.NativeBridge.biometric.isAvailable():null,authenticate:async e=>window.NativeBridge?.biometric?window.NativeBridge.biometric.authenticate(e):null};this.secureStore={setItem:async(e,t)=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.setItem(e,t)).success:(localStorage.setItem(e,t),!0),getItem:async e=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.getItem(e)).value:localStorage.getItem(e),deleteItem:async e=>window.NativeBridge?.secureStore?(await window.NativeBridge.secureStore.deleteItem(e)).success:(localStorage.removeItem(e),!0)};this.admob={showInterstitial:async()=>window.NativeBridge?.admob?(await window.NativeBridge.admob.showInterstitial()).shown:!1,showRewarded:async()=>window.NativeBridge?.admob?(await window.NativeBridge.admob.showRewarded()).rewarded:!1}}getPlatform(){if(typeof window>"u")return"web";let e=window.NativeBridge;return e?.platform==="electron"?"desktop":e?.platform==="react-native"||e?.platform==="ios"||e?.platform==="android"||e?.camera||window.ReactNativeWebView?"mobile":"web"}hasFeature(e){return typeof window>"u"?!1:!!window.NativeBridge?.[e]}get bridge(){if(!(typeof window>"u"))return window.NativeBridge}};async function Ae(a){if(typeof Blob<"u"&&a instanceof Blob){let s=await a.arrayBuffer(),r="name"in a?a.name:void 0;return{data:Me(new Uint8Array(s)),mimeType:a.type||"",defaultName:r}}let e=a;if(!e||e.data===void 0)throw new Error("addDocumentFromFile: file must be a Blob/File or { data, mimeType?, name? }");let t=e.data instanceof Uint8Array?e.data:new Uint8Array(e.data);return{data:Me(t),mimeType:e.mimeType??"",defaultName:e.name}}function Me(a){let e=globalThis;if(e.Buffer)return e.Buffer.from(a).toString("base64");let t=8192,s="";for(let r=0;r<a.length;r+=t){let i=a.subarray(r,Math.min(r+t,a.length));s+=String.fromCharCode.apply(null,Array.from(i))}return btoa(s)}var ne=class{constructor(e){this.http=e}async addDocument(e,t){return this.http.post(`/v1/public/knowledge-bases/${e}/documents`,t)}async updateDocument(e,t,s){return this.http.put(`/v1/public/knowledge-bases/${e}/documents/${t}`,s)}async updateDocumentFromFile(e,t,s,r){let{data:i,mimeType:n}=await Ae(s);return this.updateDocument(e,t,{name:r?.name,file_content:i,mime_type:n,metadata:r?.metadata})}async addDocumentFromFile(e,t,s){let{data:r,mimeType:i,defaultName:n}=await Ae(t),o=s?.name??n??"document";return this.addDocument(e,{name:o,source_type:"file",file_content:r,mime_type:i,metadata:s?.metadata})}async listDocuments(e){return this.http.get(`/v1/public/knowledge-bases/${e}/documents`)}async deleteDocument(e,t){await this.http.delete(`/v1/public/knowledge-bases/${e}/documents/${t}`)}async search(e,t){return this.http.post(`/v1/public/knowledge-bases/${e}/search`,t)}async searchGet(e,t,s){let r=new URLSearchParams({query:t});return s&&r.append("top_k",String(s)),this.http.get(`/v1/public/knowledge-bases/${e}/search?${r.toString()}`)}};var O=class{constructor(e){this.http=e}async chat(e){return this.http.post("/v1/public/ai/chat",e)}async chatStream(e,t,s){let r=s?.signal,i=o=>r?.aborted===!0||o instanceof DOMException&&o.name==="AbortError"||typeof o=="object"&&o!==null&&o.name==="AbortError",n;try{let o=await this.http.fetchRaw("/v1/public/ai/chat/stream",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:r});if(!o.ok){let d=await o.json().catch(()=>({error:"Stream request failed"}));t.onError?.(d.error||"Stream request failed");return}if(n=o.body?.getReader(),!n){t.onError?.("ReadableStream not supported");return}let l=new TextDecoder,c="";for(;;){let{done:d,value:u}=await n.read();if(d)break;c+=l.decode(u,{stream:!0});let p=c.split(`
3
3
  `);c=p.pop()||"";for(let f of p){if(!f.startsWith("data: "))continue;let b=f.slice(6).trim();if(b==="[DONE]"){t.onDone?.();return}try{let h=JSON.parse(b);if(h.error){t.onError?.(h.message||h.error||"stream error");return}if(h.type==="sources"&&h.sources){t.onSources?.(h.sources);continue}if(h.type==="tool_start"||h.type==="tool_end"){t.onToolEvent?.({type:h.type,name:h.name,toolCallId:h.toolCallId,arguments:h.arguments,result:h.result,success:h.success,durationMs:h.durationMs});continue}if(h.type==="searching"){h.searching&&t.onSearching?.(h.searching);continue}if(h.type==="heartbeat")continue;if(h.reasoning&&t.onReasoning?.(h.reasoning),h.content&&t.onToken?.(h.content),h.done){t.onDone?.();return}}catch{}}}}catch(o){if(i(o)){t.onAbort?.();return}throw o}finally{if(n)try{await n.cancel()}catch{}}}};var q=class{constructor(e){this.http=e}async call(e,t){if(!e)throw new Error("EndpointAPI.call: label required");if(!t.path||!t.path.startsWith("/"))throw new Error(`EndpointAPI.call: path must start with '/', got ${JSON.stringify(t.path)}`);let s=this.url(e,t.path),r=new Headers(t.headers??{});if(!r.has("X-Public-Key")){let i=this.http.getPublicKey();if(!i)throw new Error("EndpointAPI.call: publicKey not configured. Pass `publicKey` to ConnectBase constructor.");r.set("X-Public-Key",i)}return fetch(s,{method:t.method??"GET",headers:r,body:t.body,signal:t.signal,redirect:"follow"})}url(e,t){if(!e)throw new Error("EndpointAPI.url: label required");if(!t||!t.startsWith("/"))throw new Error(`EndpointAPI.url: path must start with '/', got ${JSON.stringify(t)}`);return`${this.http.getBaseUrl().replace(/\/+$/,"")}/v1/proxy/${encodeURIComponent(e)}${t}`}async connectWebSocket(e,t={}){if(!e)throw new Error("EndpointAPI.connectWebSocket: label required");let s=t.path??"/";if(!s.startsWith("/"))throw new Error(`EndpointAPI.connectWebSocket: path must start with '/', got ${JSON.stringify(s)}`);let r=this.http.getBaseUrl().replace(/\/+$/,""),i=`${r}/v1/proxy/${encodeURIComponent(e)}/ws-ticket`,n=new Headers,o=this.http.getPublicKey();if(!o)throw new Error("EndpointAPI.connectWebSocket: publicKey not configured. Pass `publicKey` to ConnectBase constructor.");n.set("X-Public-Key",o);let l=await fetch(i,{method:"POST",headers:n,signal:t.signal});if(!l.ok){let p=await l.text().catch(()=>"");throw new Error(`EndpointAPI.connectWebSocket: ticket issuance failed (${l.status}) ${p.slice(0,200)}`)}let{ticket:c}=await l.json();if(!c)throw new Error("EndpointAPI.connectWebSocket: server returned empty ticket");let d=new URL(`${r}/v1/proxy/${encodeURIComponent(e)}${s}`);if(d.protocol=d.protocol==="https:"?"wss:":"ws:",t.query)for(let[p,f]of Object.entries(t.query))d.searchParams.set(p,f);d.searchParams.set("ticket",c);let u=t.protocols?new WebSocket(d.toString(),t.protocols):new WebSocket(d.toString());if(t.signal){let p=()=>{try{u.close(1e3,"client aborted")}catch{}};t.signal.aborted?p():t.signal.addEventListener("abort",p,{once:!0})}return u}async pollUntil(e,t,s,r={}){let i=r.intervalMs??1500,n=r.timeoutMs??5*6e4,o=r.parse??"json",l=Date.now();for(;;){if(r.signal?.aborted)throw new DOMException("aborted","AbortError");if(Date.now()-l>n)throw new Error(`EndpointAPI.pollUntil: timeout after ${n}ms (label=${e}, path=${t.path})`);let c;try{c=await this.call(e,{...t,signal:r.signal})}catch(p){if(p.name==="AbortError")throw p;await be(i,r.signal);continue}if(c.status>=500){await Ve(c),await be(i,r.signal);continue}if(c.status>=400){let p=await c.text().catch(()=>"");throw new Error(`EndpointAPI.pollUntil: ${c.status} ${c.statusText} (label=${e}, path=${t.path})${p?` \u2014 ${p.slice(0,200)}`:""}`)}let d;o==="text"?d=await c.text():o==="none"?d=void 0:d=await c.json().catch(()=>{});let u=await s(d,c);if(u!==void 0)return u;await be(i,r.signal)}}};async function be(a,e){if(e?.aborted)throw new DOMException("aborted","AbortError");return new Promise((t,s)=>{let r=setTimeout(()=>{e?.removeEventListener("abort",i),t()},a),i=()=>{clearTimeout(r),s(new DOMException("aborted","AbortError"))};e?.addEventListener("abort",i,{once:!0})})}async function Ve(a){try{await a.text()}catch{}}var oe=class{constructor(e){this.http=e}async publish(e,t){return this.http.post(`/v1/public/queues/${e}/messages`,t)}async publishBatch(e,t){return this.http.post(`/v1/public/queues/${e}/messages/batch`,t)}async consume(e,t){let s=new URLSearchParams;t?.max_messages&&s.set("max_messages",String(t.max_messages)),t?.visibility_timeout&&s.set("visibility_timeout",String(t.visibility_timeout)),t?.auto_ack!==void 0&&s.set("auto_ack",String(t.auto_ack));let r=s.toString(),i=await this.http.get(`/v1/public/queues/${e}/messages${r?`?${r}`:""}`);return R(i,{messages:{type:"array"}},"queue.consume"),i}async ack(e,t,s){let r={message_ids:t,ack_token:s};return this.http.post(`/v1/public/queues/${e}/messages/ack`,r)}async nack(e,t,s){return this.http.post(`/v1/public/queues/${e}/messages/${t}/nack`,s||{})}async getInfo(e){return this.http.get(`/v1/public/queues/${e}`)}};var Ke=1800*1e3,ye="__cb_session",ve="__cb_visitor_uid",ae="__cb_last_activity";function B(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let e=Math.random()*16|0;return(a==="x"?e:e&3|8).toString(16)})}var F=class{constructor(){this._sessionId=null;this._visitorUid=null;this._lastActivity=0;this._isNewSession=!1}get sessionId(){return this.ensureSession(),this._sessionId}get visitorUid(){return this._visitorUid||(this._visitorUid=this.loadOrCreateVisitorUid()),this._visitorUid}get isNewSession(){return this._isNewSession}touch(){this._lastActivity=Date.now(),this._isNewSession=!1;try{typeof sessionStorage<"u"&&sessionStorage.setItem(ae,String(this._lastActivity))}catch{}}reset(){this._sessionId=null,this._isNewSession=!1;try{typeof sessionStorage<"u"&&(sessionStorage.removeItem(ye),sessionStorage.removeItem(ae))}catch{}}regenerateVisitorUid(){let e=B();this._visitorUid=e;try{typeof localStorage<"u"&&localStorage.setItem(ve,e)}catch{}return this.reset(),e}ensureSession(){let e=Date.now();if(!this._sessionId)try{if(typeof sessionStorage<"u"){this._sessionId=sessionStorage.getItem(ye);let t=sessionStorage.getItem(ae);this._lastActivity=t?parseInt(t,10):0}}catch{}if(!this._sessionId||this._lastActivity>0&&e-this._lastActivity>Ke){this._sessionId=B(),this._isNewSession=!0,this._lastActivity=e;try{typeof sessionStorage<"u"&&(sessionStorage.setItem(ye,this._sessionId),sessionStorage.setItem(ae,String(e)))}catch{}}}loadOrCreateVisitorUid(){try{if(typeof localStorage<"u"){let e=localStorage.getItem(ve);if(e)return e;let t=B();return localStorage.setItem(ve,t),t}}catch{}return B()}};function Ue(a){if(!a)return"";let e=new URLSearchParams;a.start_date!==void 0&&e.set("start_date",String(a.start_date)),a.end_date!==void 0&&e.set("end_date",String(a.end_date)),a.limit!==void 0&&e.set("limit",String(a.limit));let t=e.toString();return t?`?${t}`:""}function We(){if(typeof window>"u")return{};try{let a=new URLSearchParams(window.location.search),e={};for(let t of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let s=a.get(t);s&&(e[t]=s)}return e}catch{return{}}}var ce=class{constructor(e){this.storageWebId=null;this.memberId=null;this.eventQueue=[];this.batchTimer=null;this.isInitialized=!1;this.heartbeatTimer=null;this.visibilityHandler=null;this.unloadHeartbeatHandler=null;this.popstateHandler=null;this.beforeUnloadHandler=null;this.origPushState=null;this.origReplaceState=null;this.heatmapClickHandler=null;this.heatmapScrollHandler=null;this.utm={};this.handleHeatmapClick=e=>{let t=e.clientX/window.innerWidth*100,s=e.clientY/window.innerHeight*100;this.recordHeatmapEvent("click",t,s)};this.heatmapQueue=[];this.http=e,this.config={trackPageViews:!0,trackEvents:!0,trackSessions:!0,heatmap:!1,recording:!1,batchSize:10,flushInterval:5e3,respectDoNotTrack:!0,debug:!1},this.consent={analytics:!0,heatmap:!1,recording:!1},this.session=new F}init(e,t){if(this.isInitialized){this.log("Analytics already initialized");return}if(typeof window>"u"){this.log("Analytics only works in browser environment");return}if(t?.respectDoNotTrack!==!1&&this.isDNT()){this.log("Do Not Track enabled, analytics disabled");return}this.storageWebId=e,Object.assign(this.config,t),this.isInitialized=!0,this.utm=We(),this.config.trackSessions&&(this.trackSessionStart(),this.startHeartbeat()),this.config.trackPageViews&&(this.trackPageView(),this.setupAutoPageView()),this.startBatchTimer(),this.beforeUnloadHandler=()=>this.flushSync(),window.addEventListener("beforeunload",this.beforeUnloadHandler),this.log("Analytics initialized",{storageWebId:e})}destroy(){this.isInitialized&&(this.stopBatchTimer(),this.stopHeartbeat(),this.removeAutoPageView(),this.removeHeatmapListeners(),this.beforeUnloadHandler&&(window.removeEventListener("beforeunload",this.beforeUnloadHandler),this.beforeUnloadHandler=null),this.flush(),this.isInitialized=!1,this.log("Analytics destroyed"))}setConsent(e){Object.assign(this.consent,e),this.log("Consent updated",e),e.analytics===!1&&this.isInitialized&&this.destroy()}getConsent(){return{...this.consent}}trackPageView(e){if(!this.canTrack())return;this.session.touch();let t=this.createBaseEvent("page_view");t.page_path=e||window.location.pathname,t.page_url=window.location.href,t.page_title=document.title,t.referrer=document.referrer||void 0,t.screen_width=window.screen.width,t.screen_height=window.screen.height,Object.assign(t,this.utm),this.enqueue(t)}trackEvent(e,t){if(!this.canTrack()||!this.config.trackEvents)return;this.session.touch();let s=this.createBaseEvent("event");s.event_name=e,s.event_properties=t,s.page_path=window.location.pathname,s.page_url=window.location.href,this.enqueue(s)}identify(e){this.memberId&&this.memberId!==e&&this.reset(),this.setMemberId(e),this.linkMemberSilent(e)}reset(){this.setMemberId(null),this.eventQueue=[],this.heatmapQueue=[];let e=this.session.regenerateVisitorUid();this.log("Analytics reset",{newVisitorUid:e})}setMemberId(e){this.memberId=e||null,typeof window<"u"&&(e?typeof window.__cbSetMember=="function"&&window.__cbSetMember(e):typeof window.__cbClearMember=="function"&&window.__cbClearMember()),this.log("Member id set",{memberId:e})}linkMemberSilent(e,t=!1){if(!this.storageWebId)return;let s=this.http.hasPublicKey()?"/v1/public":"/v1";this.http.post(`${s}/storages/web/${this.storageWebId}/visitors/link-member`,{visitor_uid:this.session.visitorUid,app_member_id:e}).then(r=>{r&&(this.log("link-member response",r),!t&&r.success===!1&&r.code==="VISITOR_LINKED_TO_OTHER_MEMBER"&&(this.log("user-switch detected \u2014 regenerating visitor_uid"),this.eventQueue=[],this.heatmapQueue=[],this.session.regenerateVisitorUid(),this.linkMemberSilent(e,!0)))}).catch(r=>{this.log("link-member silent fail",r)})}getMemberId(){return this.memberId}enableHeatmap(e){if(!this.canTrack()||!this.consent.heatmap)return;let t=e?.click??!0,s=e?.scroll??!0;if(t&&(this.heatmapClickHandler=this.handleHeatmapClick,document.addEventListener("click",this.heatmapClickHandler)),s){let r=null,i=0;this.heatmapScrollHandler=()=>{let n=Math.round((window.scrollY+window.innerHeight)/document.documentElement.scrollHeight*100);i=Math.max(i,n),r&&clearTimeout(r),r=setTimeout(()=>{this.recordHeatmapEvent("scroll",50,i*(window.innerHeight/100),i)},500)},window.addEventListener("scroll",this.heatmapScrollHandler,{passive:!0})}this.log("Heatmap enabled",e)}enableHeartbeat(){if(!this.canTrack()||this.heartbeatTimer)return;let e=()=>{if(!this.canTrack())return;let t=this.http.hasPublicKey()?"/v1/public":"/v1";this.http.post(`${t}/storages/web/${this.storageWebId}/sessions/heartbeat`,{visitor_uid:this.session.visitorUid,session_id:this.session.sessionId}).catch(()=>{})};this.heartbeatTimer=setInterval(e,3e4),document.addEventListener("visibilitychange",()=>{document.hidden?this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null):this.heartbeatTimer||(this.heartbeatTimer=setInterval(e,3e4),e())}),window.addEventListener("beforeunload",()=>{if(typeof navigator<"u"&&navigator.sendBeacon&&this.storageWebId){let t=this.http.hasPublicKey()?"/v1/public":"/v1",s=`${this.http.getBaseUrl()}${t}/storages/web/${this.storageWebId}/sessions/heartbeat`;navigator.sendBeacon(s,JSON.stringify({visitor_uid:this.session.visitorUid,session_id:this.session.sessionId}))}}),e(),this.log("Heartbeat enabled (30s interval)")}async flush(){await this.flushQueue()}async getPopularPages(e,t){let s=this.requireServerSideStorageId(e,"getPopularPages"),r=Ue(t);return this.http.get(`/v1/storages/web/${s}/popular-pages${r}`)}async getNavigationFlow(e,t){let s=this.requireServerSideStorageId(e,"getNavigationFlow"),r=Ue(t);return this.http.get(`/v1/storages/web/${s}/navigation/flow${r}`)}async getVisitors(e,t){let s=this.requireServerSideStorageId(e,"getVisitors"),r=new URLSearchParams;t?.limit!==void 0&&r.set("limit",String(t.limit)),t?.offset!==void 0&&r.set("offset",String(t.offset)),t?.sort_by&&r.set("sort_by",t.sort_by);let i=r.toString()?`?${r.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitors${i}`)}async getVisitorGroups(e,t){let s=this.requireServerSideStorageId(e,"getVisitorGroups"),r=new URLSearchParams;t?.limit!==void 0&&r.set("limit",String(t.limit)),t?.offset!==void 0&&r.set("offset",String(t.offset)),t?.sort_by&&r.set("sort_by",t.sort_by);let i=r.toString()?`?${r.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitor-groups${i}`)}async getVisitorByMember(e,t){let s=this.requireServerSideStorageId(e,"getVisitorByMember");return this.http.get(`/v1/storages/web/${s}/members/${t}/visitor`)}async mergeVisitors(e,t){let s=this.requireServerSideStorageId(e,"mergeVisitors");return this.http.post(`/v1/storages/web/${s}/visitors/merge`,t)}requireServerSideStorageId(e,t){if(this.http.hasPublicKey())throw new Error(`cb.analytics.${t}() \uB294 \uCF58\uC194 JWT \uB610\uB294 User Secret Key(cb_sk_) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 SDK \uC758 Public Key(cb_pk_) \uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.`);let s=e??this.storageWebId;if(!s)throw new Error(`cb.analytics.${t}() \uD638\uCD9C \uC2DC storageWebId \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (init \uB610\uB294 \uC778\uC790\uB85C \uC804\uB2EC).`);return s}getSession(){return this.session}canTrack(){return!(!this.isInitialized||!this.storageWebId||!this.consent.analytics)}isDNT(){return typeof navigator>"u"?!1:navigator.doNotTrack==="1"||navigator.globalPrivacyControl===!0}createBaseEvent(e){return{type:e,event_id:B(),timestamp:new Date().toISOString(),session_id:this.session.sessionId,visitor_uid:this.session.visitorUid}}enqueue(e){this.eventQueue.push(e),this.eventQueue.length>=this.config.batchSize&&this.flushQueue()}async flushQueue(){if(this.eventQueue.length===0||!this.storageWebId)return;let e=this.eventQueue.splice(0),t=this.http.hasPublicKey()?"/v1/public":"/v1";try{await this.http.post(`${t}/storages/web/${this.storageWebId}/visitors/batch`,{visitor_uid:this.session.visitorUid,...this.memberId?{app_member_id:this.memberId}:{},events:e.map(s=>({event_id:s.event_id,timestamp:s.timestamp,page_path:s.page_path||"",page_url:s.page_url||"",page_title:s.page_title||"",referrer:s.referrer||"",user_agent:typeof navigator<"u"?navigator.userAgent:"",screen_width:s.screen_width||0,screen_height:s.screen_height||0,session_id:s.session_id,session_start:s.type==="session_start",is_page_view:s.type==="page_view",event_name:s.event_name,event_properties:s.event_properties,utm_source:s.utm_source,utm_medium:s.utm_medium,utm_campaign:s.utm_campaign,utm_content:s.utm_content,utm_term:s.utm_term}))}),this.log(`Flushed ${e.length} events`)}catch(s){this.eventQueue.length<this.config.batchSize*3&&this.eventQueue.unshift(...e),this.log("Flush failed",s)}}flushSync(){if(this.eventQueue.length===0||!this.storageWebId||typeof navigator>"u"||!navigator.sendBeacon)return;let e=this.eventQueue.splice(0),t=this.http.hasPublicKey()?"/v1/public":"/v1",r=`${this.http.getBaseUrl()}${t}/storages/web/${this.storageWebId}/visitors/batch`,i=typeof navigator<"u"?navigator.userAgent:"",n=JSON.stringify({visitor_uid:this.session.visitorUid,...this.memberId?{app_member_id:this.memberId}:{},events:e.map(o=>({event_id:o.event_id,timestamp:o.timestamp,page_path:o.page_path||"",page_url:o.page_url||"",page_title:o.page_title||"",referrer:o.referrer||"",user_agent:i,screen_width:o.screen_width||0,screen_height:o.screen_height||0,session_id:o.session_id,session_start:o.type==="session_start",is_page_view:o.type==="page_view",event_name:o.event_name,event_properties:o.event_properties,utm_source:o.utm_source,utm_medium:o.utm_medium,utm_campaign:o.utm_campaign,utm_content:o.utm_content,utm_term:o.utm_term}))});try{navigator.sendBeacon(r,new Blob([n],{type:"application/json"}))}catch{}}trackSessionStart(){let e=this.createBaseEvent("session_start");e.page_path=window.location.pathname,e.page_url=window.location.href,e.referrer=document.referrer||void 0,e.screen_width=window.screen.width,e.screen_height=window.screen.height,Object.assign(e,this.utm),this.enqueue(e)}startBatchTimer(){this.batchTimer=setInterval(()=>this.flushQueue(),this.config.flushInterval)}stopBatchTimer(){this.batchTimer&&(clearInterval(this.batchTimer),this.batchTimer=null)}startHeartbeat(){this.heartbeatTimer=setInterval(()=>{this.canTrack()&&this.sendHeartbeat()},30*1e3),typeof document<"u"&&(this.visibilityHandler=()=>{document.visibilityState==="hidden"?(this.sendHeartbeatBeacon(),this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)):document.visibilityState==="visible"&&(this.heartbeatTimer||(this.sendHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.canTrack()&&this.sendHeartbeat()},30*1e3)))},document.addEventListener("visibilitychange",this.visibilityHandler)),typeof window<"u"&&(this.unloadHeartbeatHandler=()=>this.sendHeartbeatBeacon(),window.addEventListener("beforeunload",this.unloadHeartbeatHandler))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),typeof document<"u"&&this.visibilityHandler&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),typeof window<"u"&&this.unloadHeartbeatHandler&&(window.removeEventListener("beforeunload",this.unloadHeartbeatHandler),this.unloadHeartbeatHandler=null)}sendHeartbeat(){let e=this.createBaseEvent("heartbeat");if(e.page_path=window.location.pathname,this.enqueue(e),this.storageWebId){let t=this.http.hasPublicKey()?"/v1/public":"/v1";this.http.post(`${t}/storages/web/${this.storageWebId}/sessions/heartbeat`,{visitor_uid:this.session.visitorUid,session_id:this.session.sessionId}).catch(()=>{})}}sendHeartbeatBeacon(){if(!this.storageWebId||typeof navigator>"u"||!navigator.sendBeacon)return;let e=this.http.hasPublicKey()?"/v1/public":"/v1",s=`${this.http.getBaseUrl()}${e}/storages/web/${this.storageWebId}/sessions/heartbeat`,r=JSON.stringify({visitor_uid:this.session.visitorUid,session_id:this.session.sessionId});try{navigator.sendBeacon(s,new Blob([r],{type:"application/json"}))}catch{}}setupAutoPageView(){this.popstateHandler=()=>this.trackPageView(),window.addEventListener("popstate",this.popstateHandler),this.origPushState=history.pushState.bind(history),this.origReplaceState=history.replaceState.bind(history);let e=this;history.pushState=function(...t){e.origPushState(...t),e.trackPageView()},history.replaceState=function(...t){e.origReplaceState(...t),e.trackPageView()}}removeAutoPageView(){this.popstateHandler&&(window.removeEventListener("popstate",this.popstateHandler),this.popstateHandler=null),this.origPushState&&(history.pushState=this.origPushState,this.origPushState=null),this.origReplaceState&&(history.replaceState=this.origReplaceState,this.origReplaceState=null)}removeHeatmapListeners(){this.heatmapClickHandler&&(document.removeEventListener("click",this.heatmapClickHandler),this.heatmapClickHandler=null),this.heatmapScrollHandler&&(window.removeEventListener("scroll",this.heatmapScrollHandler),this.heatmapScrollHandler=null)}recordHeatmapEvent(e,t,s,r){if(!this.canTrack()||!this.storageWebId)return;let i=this.http.hasPublicKey()?"/v1/public":"/v1";if(this.heatmapQueue.push({page_path:window.location.pathname,event_type:e,x_percent:Math.round(t*100)/100,y_percent:Math.round(s*100)/100,viewport_width:window.innerWidth,viewport_height:window.innerHeight,scroll_depth_percent:r,session_id:this.session.sessionId}),this.heatmapQueue.length>=50){let n=this.heatmapQueue.splice(0);this.http.post(`${i}/storages/web/${this.storageWebId}/heatmap/batch`,{visitor_uid:this.session.visitorUid,events:n}).catch(()=>{})}}log(...e){this.config.debug&&console.log("[Analytics]",...e)}};var le=class{constructor(e){this.http=e}async reportIssue(e){let t={title:e.title,body:e.body,category:e.category,metadata:e.metadata};return e.anonymousEmail&&(t.anonymous_email=e.anonymousEmail),e.recaptchaToken&&(t.recaptcha_token=e.recaptchaToken),this.http.post("/v1/public/reports",t)}async reportIssueOnBehalfOfUser(e){if(!e.targetAppId)throw new Error("targetAppId is required");if(!e.accessToken)throw new Error("accessToken is required");let t={title:e.title,body:e.body};e.category&&(t.category=e.category),e.metadata&&(t.metadata=e.metadata);let s=`${this.http.getBaseUrl()}/v1/integrations/providers/${encodeURIComponent(e.targetAppId)}/issues/by-user`,r=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.accessToken}`},body:JSON.stringify(t)});if(!r.ok){let i;try{i=await r.json()}catch{i={error:r.statusText}}let n=i??{},o=n.error_description??n.error??`HTTP ${r.status}`;throw new Error(`reportIssueOnBehalfOfUser failed (${r.status}): ${o}`)}return await r.json()}async reportPlatformBug(e){let t=e.attachAutomaticContext!==!1,s={title:e.title,body:e.body,category:e.category??"other",severity:e.severity??"medium",metadata:e.metadata};if(e.reporterEmail&&(s.reporter_email=e.reporterEmail),e.recaptchaToken&&(s.recaptcha_token=e.recaptchaToken),t){s.sdk_version=e.sdkVersion??je(),s.sdk_platform=e.sdkPlatform??ze(),s.environment=e.environment??"unknown",e.error?s.stack_trace=Oe(e.error.stack??String(e.error)):e.stackTrace&&(s.stack_trace=Oe(e.stackTrace));let r=e.recentApiCalls??this.http.getRecentCalls();r.length>0&&(s.recent_api_calls=r)}return this.http.post("/v1/public/platform-issues",s)}getRecentApiCalls(){return this.http.getRecentCalls()}clearRecentApiCalls(){this.http.clearRecentCalls()}async listPlatformIssueReplies(e){return(await this.http.get(`/v1/public/platform-issues/${e}/comments`)).comments}async replyToPlatformIssue(e,t){return this.http.post(`/v1/public/platform-issues/${e}/comments`,{body:t})}async getPlatformIssue(e){return this.http.get(`/v1/public/platform-issues/${e}`)}async listMyPlatformIssues(e={}){let t=new URLSearchParams;for(let r of e.status??[])t.append("status",r);for(let r of e.severity??[])t.append("severity",r);for(let r of e.category??[])t.append("category",r);e.sinceUpdatedAt&&t.set("since_updated_at",e.sinceUpdatedAt),e.cursor&&t.set("cursor",e.cursor),typeof e.limit=="number"&&t.set("limit",String(e.limit));let s=t.toString();return this.http.get(`/v1/public/platform-issues${s?"?"+s:""}`)}};function je(){return typeof __SDK_VERSION__<"u"&&__SDK_VERSION__?__SDK_VERSION__:"unknown"}function ze(){return typeof window<"u"&&typeof document<"u"?"web":"node"}function Oe(a){let e=a;e=e.replace(/(\(|\s|^)(?:file:\/\/)?\/[^\s)]+\/([^\s/)]+)(:\d+:\d+)?/g,"$1$2$3"),e=e.replace(/(https?:\/\/[^\s)?]+)\?[^\s)]*/g,"$1");let t=32*1024;return e.length>t&&(e=e.slice(0,t)+`
4
- \u2026[truncated]`),e}var Je="$auth.member_id";var we=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.publicKey&&s.set("public_key",this.config.publicKey),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 l=s.slice(4,4+o);s=s.slice(4+o),this.onMessage(l)}}}catch{}}disconnect(){this.transport&&(this.transport.close(),this.transport=null,this.writer=null)}send(e,t=!0){if(!this.transport)throw new m({code:"NOT_CONNECTED",message:"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}},de=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(l){t(l);return}let r=()=>{e()},i=()=>{this.onClose()},n=l=>{let c=new Error("WebSocket error");this.onError(c),t(c)},o=l=>{l.data instanceof ArrayBuffer?this.onMessage(new Uint8Array(l.data)):typeof l.data=="string"&&this.onMessage(new TextEncoder().encode(l.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;s.set("client_id",this.config.clientId),this.config.publicKey&&s.set("public_key",this.config.publicKey),this.config.accessToken&&s.set("token",this.config.accessToken);let r=this.config.appId||"";return`${t}/v1/game/${r}/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 m({code:"NOT_CONNECTED",message:"Not connected"});typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function Pe(){return typeof WebTransport<"u"}var ue=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._scriptName=null;this._scriptVersion=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 scriptName(){return this._scriptName}get scriptVersion(){return this._scriptVersion}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")&&Pe(),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?.(new m({code:"CONNECTION_ERROR",message:o.message}))};if(s)try{this.transport=new we(this.config,r,i,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new de(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new de(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(await this.createRoomDetailed(e)).state}async createRoomDetailed(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,this._scriptName=n.script_name??null,this._scriptVersion=n.script_version??null,t({roomId:n.room_id,state:n.initial_state,scriptName:n.script_name,scriptVersion:n.script_version})}else i.type==="error"&&s(P(i))};this.sendWithHandler("create_room",H(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,this._scriptName=null,this._scriptVersion=null,s(o.initial_state)}else n.type==="error"&&r(P(n))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,this._scriptName=null,this._scriptVersion=null,e()):r.type==="error"&&t(P(r))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new m({code:"NOT_IN_ROOM",message:"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 m({code:"NOT_IN_ROOM",message:"Not in a room"});this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"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(P(r))};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(P(i))};this.sendWithHandler("ping",{timestamp:s},r)})}send(e,t){if(!this.transport?.isConnected())throw new m({code:"NOT_CONNECTED",message:"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);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t);break;case"chat":this.handlers.onChat?.({roomId:t.room_id||"",clientId:t.client_id||"",userId:t.user_id,message:t.message||"",serverTime:t.server_time||0});break;case"error":this.handlers.onError?.(P(t));break;case"room_stale":this.handlers.onRoomStale?this.handlers.onRoomStale({reason:t.reason||"unknown",roomId:t.room_id||"",scriptId:t.script_id,scriptVersion:t.script_version,serverTime:t.server_time||0}):console.warn("[connect-base game] room_stale received but onRoomStale handler not set:",{reason:t.reason,roomId:t.room_id,scriptVersion:t.script_version});break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(!t)return;let s={fromVersion:t.from_version,toVersion:t.to_version,changes:t.changes.map(r=>({path:r.path,operation:r.operation,value:r.value,oldValue:r.old_value})),tick:t.tick};if(this._state){for(let r of s.changes)this.applyChange(r);this._state.version=s.toVersion}if(this.handlers.onAction){for(let r of s.changes)if(r.path.startsWith("actions.")&&r.operation==="set"&&r.value){let i=r.value;this.handlers.onAction({type:i.type||"",clientId:i.client_id||"",data:i.data,timestamp:i.timestamp||0})}}this.handlers.onDelta?.(s)}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){let t={clientId:e.player?.client_id||"",userId:e.player?.user_id,joinedAt:e.player?.joined_at||0,metadata:e.player?.metadata};e.event==="joined"?this.handlers.onPlayerJoined?.(t):e.event==="left"&&this.handlers.onPlayerLeft?.(t)}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 Qe="https://api.connectbase.world",Xe="https://socket.connectbase.world",Ye="https://webrtc.connectbase.world",Ze="https://video.connectbase.world",et="https://game.connectbase.world",pe=class{constructor(e={}){let t=i=>{if(!(typeof process>"u"||!process.env))return process.env[i]},s={baseUrl:e.baseUrl||t("CB_BASE_URL")||Qe,publicKey:e.publicKey,secretKey:e.secretKey,appId:e.appId,persistence:e.persistence,autoRestoreSession:e.autoRestoreSession,requestTimeoutMs:e.requestTimeoutMs,onError:e.onError,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError,onTokenExpired:e.onTokenExpired,onTransientRefreshFailure:e.onTransientRefreshFailure};this.http=new V(s),this.auth=new K(this.http),this.database=new W(this.http),this.storage=new j(this.http),this.publicKey=new z(this.http),this.functions=new J(this.http),this.realtime=new Q(this.http,e.socketUrl||t("CB_SOCKET_URL")||Xe),this.webrtc=new X(this.http,e.webrtcUrl||t("CB_WEBRTC_URL")||Ye,e.appId),this.errorTracker=new Y(this.http,e.errorTracker),this.oauth=new ee(this.http),this.payment=new te(this.http),this.subscription=new se(this.http),this.push=new re(this.http),this.video=new ie(this.http,e.videoUrl||t("CB_VIDEO_URL")||Ze),this.game=new A(this.http,e.gameUrl||t("CB_GAME_URL")||et,e.appId),this.ads=new M(this.http),this.native=new U,this.knowledge=new ne(this.http),this.ai=new O(this.http),this.queue=new oe(this.http),this.analytics=new ce(this.http),this.endpoint=new q(this.http),this.support=new le(this.http),this.auth._attachAnalytics(this.analytics),(e.autoRestoreSession??!0)&&typeof window<"u"&&!this.isOAuthCallbackUrl()&&this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie())}isOAuthCallbackUrl(){if(typeof window>"u"||!window.location)return!1;let e=new URLSearchParams(window.location.search);return!!(e.has("access_token")&&e.has("refresh_token")||e.has("code")&&e.has("state")||e.has("error")&&e.has("state"))}async restoreSession(){return this.http.tryRestoreSessionFromCookie()}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},tt=pe;return Fe(st);})();
4
+ \u2026[truncated]`),e}var Je="$auth.member_id";var we=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.publicKey&&s.set("public_key",this.config.publicKey),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 l=s.slice(4,4+o);s=s.slice(4+o),this.onMessage(l)}}}catch{}}disconnect(){this.transport&&(this.transport.close(),this.transport=null,this.writer=null)}send(e,t=!0){if(!this.transport)throw new m({code:"NOT_CONNECTED",message:"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}},de=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(l){t(l);return}let r=()=>{e()},i=()=>{this.onClose()},n=l=>{let c=new Error("WebSocket error");this.onError(c),t(c)},o=l=>{l.data instanceof ArrayBuffer?this.onMessage(new Uint8Array(l.data)):typeof l.data=="string"&&this.onMessage(new TextEncoder().encode(l.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;s.set("client_id",this.config.clientId),this.config.publicKey&&s.set("public_key",this.config.publicKey),this.config.accessToken&&s.set("token",this.config.accessToken);let r=this.config.appId||"";return`${t}/v1/game/${r}/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 m({code:"NOT_CONNECTED",message:"Not connected"});typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function Pe(){return typeof WebTransport<"u"}var ue=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._scriptName=null;this._scriptVersion=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 scriptName(){return this._scriptName}get scriptVersion(){return this._scriptVersion}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")&&Pe(),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?.(new m({code:"CONNECTION_ERROR",message:o.message}))};if(s)try{this.transport=new we(this.config,r,i,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new de(this.config,r,i,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new de(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(await this.createRoomDetailed(e)).state}async createRoomDetailed(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,this._scriptName=n.script_name??null,this._scriptVersion=n.script_version??null,t({roomId:n.room_id,state:n.initial_state,scriptName:n.script_name,scriptVersion:n.script_version})}else i.type==="error"&&s(P(i))};this.sendWithHandler("create_room",H(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,this._scriptName=null,this._scriptVersion=null,s(o.initial_state)}else n.type==="error"&&r(P(n))};this.sendWithHandler("join_room",{room_id:e,metadata:t},i)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"Not in a room"}));return}let s=r=>{r.type==="room_left"?(this._roomId=null,this._state=null,this._scriptName=null,this._scriptVersion=null,e()):r.type==="error"&&t(P(r))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new m({code:"NOT_IN_ROOM",message:"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 m({code:"NOT_IN_ROOM",message:"Not in a room"});this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new m({code:"NOT_IN_ROOM",message:"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(P(r))};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(P(i))};this.sendWithHandler("ping",{timestamp:s},r)})}send(e,t){if(!this.transport?.isConnected())throw new m({code:"NOT_CONNECTED",message:"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);break;case"state":this._state=t.data,this.handlers.onStateUpdate?.(this._state);break;case"player_event":this.handlePlayerEvent(t);break;case"chat":this.handlers.onChat?.({roomId:t.room_id||"",clientId:t.client_id||"",userId:t.user_id,message:t.message||"",serverTime:t.server_time||0});break;case"error":this.handlers.onError?.(P(t));break;case"room_stale":this.handlers.onRoomStale?this.handlers.onRoomStale({reason:t.reason||"unknown",roomId:t.room_id||"",scriptId:t.script_id,scriptVersion:t.script_version,serverTime:t.server_time||0}):console.warn("[connect-base game] room_stale received but onRoomStale handler not set:",{reason:t.reason,roomId:t.room_id,scriptVersion:t.script_version});break}}catch{console.error("Failed to parse game message:",e)}}handleDelta(e){let t=e.delta;if(!t)return;let s={fromVersion:t.from_version,toVersion:t.to_version,changes:t.changes.map(r=>({path:r.path,operation:r.operation,value:r.value,oldValue:r.old_value})),tick:t.tick};if(this._state){for(let r of s.changes)this.applyChange(r);this._state.version=s.toVersion}if(this.handlers.onAction){for(let r of s.changes)if(r.path.startsWith("actions.")&&r.operation==="set"&&r.value){let i=r.value;this.handlers.onAction({type:i.type||"",clientId:i.client_id||"",data:i.data,timestamp:i.timestamp||0})}}this.handlers.onDelta?.(s)}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){let t={clientId:e.player?.client_id||"",userId:e.player?.user_id,joinedAt:e.player?.joined_at||0,metadata:e.player?.metadata};e.event==="joined"?this.handlers.onPlayerJoined?.(t):e.event==="left"&&this.handlers.onPlayerLeft?.(t)}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 Qe="https://api.connectbase.world",Xe="https://socket.connectbase.world",Ye="https://webrtc.connectbase.world",Ze="https://video.connectbase.world",et="https://game.connectbase.world",pe=class{constructor(e={}){let t=i=>{if(!(typeof process>"u"||!process.env))return process.env[i]},s={baseUrl:e.baseUrl||t("CB_BASE_URL")||Qe,publicKey:e.publicKey,secretKey:e.secretKey,appId:e.appId,persistence:e.persistence,autoRestoreSession:e.autoRestoreSession,requestTimeoutMs:e.requestTimeoutMs,onError:e.onError,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError,onTokenExpired:e.onTokenExpired,onTransientRefreshFailure:e.onTransientRefreshFailure};this.http=new V(s),this.auth=new K(this.http),this.database=new W(this.http),this.storage=new j(this.http),this.publicKey=new z(this.http),this.functions=new J(this.http),this.realtime=new Q(this.http,e.socketUrl||t("CB_SOCKET_URL")||Xe),this.webrtc=new X(this.http,e.webrtcUrl||t("CB_WEBRTC_URL")||Ye,e.appId),this.errorTracker=new Y(this.http,e.errorTracker),this.oauth=new ee(this.http),this.payment=new te(this.http),this.subscription=new se(this.http),this.push=new re(this.http),this.video=new ie(this.http,e.videoUrl||t("CB_VIDEO_URL")||Ze),this.game=new A(this.http,e.gameUrl||t("CB_GAME_URL")||et,e.appId),this.ads=new M(this.http),this.native=new U,this.knowledge=new ne(this.http),this.ai=new O(this.http),this.queue=new oe(this.http),this.analytics=new ce(this.http),this.endpoint=new q(this.http),this.support=new le(this.http),this.auth._attachAnalytics(this.analytics),(e.autoRestoreSession??!0)&&typeof window<"u"&&(this.isOAuthRedirectCallbackUrl()?this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot()):this.isOAuthCallbackUrl()||this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie()))}isOAuthCallbackUrl(){if(typeof window>"u"||!window.location)return!1;let e=new URLSearchParams(window.location.search);return!!(e.has("access_token")&&e.has("refresh_token")||e.has("code")&&e.has("state")||e.has("error")&&e.has("state"))}isOAuthRedirectCallbackUrl(){if(typeof window>"u"||!window.location||window.opener)return!1;let e=new URLSearchParams(window.location.search);return e.has("access_token")&&e.has("refresh_token")}async restoreSession(){return this.http.tryRestoreSessionFromCookie()}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},tt=pe;return Fe(st);})();
5
5
  var ConnectBase = ConnectBaseModule.default || ConnectBaseModule.ConnectBase;
package/dist/index.d.mts CHANGED
@@ -3847,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3847
3847
  */
3848
3848
  declare class OAuthAPI {
3849
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3850
3856
  constructor(http: HttpClient);
3851
3857
  /**
3852
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3971,6 +3977,28 @@ declare class OAuthAPI {
3971
3977
  state?: string;
3972
3978
  error?: string;
3973
3979
  } | null>;
3980
+ /**
3981
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
3982
+ *
3983
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
3984
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
3985
+ * 적재해 세션을 확립한다.
3986
+ *
3987
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
3988
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
3989
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
3990
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
3991
+ *
3992
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
3993
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
3994
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
3995
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
3996
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
3997
+ *
3998
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
3999
+ */
4000
+ consumeRedirectCallbackOnBoot(): Promise<boolean>;
4001
+ private doConsumeRedirectOnBoot;
3974
4002
  /**
3975
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3976
4004
  *
@@ -8812,9 +8840,18 @@ declare class ConnectBase {
8812
8840
  *
8813
8841
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8814
8842
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8815
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8843
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8816
8844
  */
8817
8845
  private isOAuthCallbackUrl;
8846
+ /**
8847
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8848
+ *
8849
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8850
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8851
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8852
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8853
+ */
8854
+ private isOAuthRedirectCallbackUrl;
8818
8855
  /**
8819
8856
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8820
8857
  *
package/dist/index.d.ts CHANGED
@@ -3847,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3847
3847
  */
3848
3848
  declare class OAuthAPI {
3849
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3850
3856
  constructor(http: HttpClient);
3851
3857
  /**
3852
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3971,6 +3977,28 @@ declare class OAuthAPI {
3971
3977
  state?: string;
3972
3978
  error?: string;
3973
3979
  } | null>;
3980
+ /**
3981
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
3982
+ *
3983
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
3984
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
3985
+ * 적재해 세션을 확립한다.
3986
+ *
3987
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
3988
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
3989
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
3990
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
3991
+ *
3992
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
3993
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
3994
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
3995
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
3996
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
3997
+ *
3998
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
3999
+ */
4000
+ consumeRedirectCallbackOnBoot(): Promise<boolean>;
4001
+ private doConsumeRedirectOnBoot;
3974
4002
  /**
3975
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3976
4004
  *
@@ -8812,9 +8840,18 @@ declare class ConnectBase {
8812
8840
  *
8813
8841
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8814
8842
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8815
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8843
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8816
8844
  */
8817
8845
  private isOAuthCallbackUrl;
8846
+ /**
8847
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8848
+ *
8849
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8850
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8851
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8852
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8853
+ */
8854
+ private isOAuthRedirectCallbackUrl;
8818
8855
  /**
8819
8856
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8820
8857
  *
package/dist/index.js CHANGED
@@ -4810,6 +4810,12 @@ function escapeToExternalBrowser(currentUrl) {
4810
4810
  var OAuthAPI = class {
4811
4811
  constructor(http) {
4812
4812
  this.http = http;
4813
+ /**
4814
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4815
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4816
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4817
+ */
4818
+ this.bootConsumePromise = null;
4813
4819
  }
4814
4820
  /**
4815
4821
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5104,10 +5110,58 @@ var OAuthAPI = class {
5104
5110
  window.close();
5105
5111
  return result;
5106
5112
  }
5113
+ if (this.bootConsumePromise) {
5114
+ const consumed = await this.bootConsumePromise;
5115
+ if (consumed) {
5116
+ return result;
5117
+ }
5118
+ }
5107
5119
  this.http.setTokens(accessToken, refreshToken);
5108
5120
  await this.http.bootstrapRefreshCookie();
5109
5121
  return result;
5110
5122
  }
5123
+ /**
5124
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5125
+ *
5126
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5127
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5128
+ * 적재해 세션을 확립한다.
5129
+ *
5130
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5131
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5132
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5133
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5134
+ *
5135
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5136
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5137
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5138
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5139
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5140
+ *
5141
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5142
+ */
5143
+ consumeRedirectCallbackOnBoot() {
5144
+ if (!this.bootConsumePromise) {
5145
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5146
+ }
5147
+ return this.bootConsumePromise;
5148
+ }
5149
+ async doConsumeRedirectOnBoot() {
5150
+ try {
5151
+ if (typeof window === "undefined" || !window.location) return false;
5152
+ const params = new URLSearchParams(window.location.search);
5153
+ if (params.get("error")) return false;
5154
+ const accessToken = params.get("access_token");
5155
+ const refreshToken = params.get("refresh_token");
5156
+ const memberId = params.get("member_id");
5157
+ if (!accessToken || !refreshToken || !memberId) return false;
5158
+ this.http.setTokens(accessToken, refreshToken);
5159
+ await this.http.bootstrapRefreshCookie();
5160
+ return true;
5161
+ } catch {
5162
+ return false;
5163
+ }
5164
+ }
5111
5165
  /**
5112
5166
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5113
5167
  *
@@ -10870,8 +10924,12 @@ var ConnectBase = class {
10870
10924
  this.support = new SupportAPI(this.http);
10871
10925
  this.auth._attachAnalytics(this.analytics);
10872
10926
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10873
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10874
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10927
+ if (shouldAutoRestore && typeof window !== "undefined") {
10928
+ if (this.isOAuthRedirectCallbackUrl()) {
10929
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10930
+ } else if (!this.isOAuthCallbackUrl()) {
10931
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10932
+ }
10875
10933
  }
10876
10934
  }
10877
10935
  /**
@@ -10879,7 +10937,7 @@ var ConnectBase = class {
10879
10937
  *
10880
10938
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10881
10939
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10882
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10940
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10883
10941
  */
10884
10942
  isOAuthCallbackUrl() {
10885
10943
  if (typeof window === "undefined" || !window.location) return false;
@@ -10889,6 +10947,20 @@ var ConnectBase = class {
10889
10947
  if (params.has("error") && params.has("state")) return true;
10890
10948
  return false;
10891
10949
  }
10950
+ /**
10951
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10952
+ *
10953
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10954
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10955
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10956
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10957
+ */
10958
+ isOAuthRedirectCallbackUrl() {
10959
+ if (typeof window === "undefined" || !window.location) return false;
10960
+ if (window.opener) return false;
10961
+ const params = new URLSearchParams(window.location.search);
10962
+ return params.has("access_token") && params.has("refresh_token");
10963
+ }
10892
10964
  /**
10893
10965
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10894
10966
  *
package/dist/index.mjs CHANGED
@@ -4764,6 +4764,12 @@ function escapeToExternalBrowser(currentUrl) {
4764
4764
  var OAuthAPI = class {
4765
4765
  constructor(http) {
4766
4766
  this.http = http;
4767
+ /**
4768
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4769
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4770
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4771
+ */
4772
+ this.bootConsumePromise = null;
4767
4773
  }
4768
4774
  /**
4769
4775
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5058,10 +5064,58 @@ var OAuthAPI = class {
5058
5064
  window.close();
5059
5065
  return result;
5060
5066
  }
5067
+ if (this.bootConsumePromise) {
5068
+ const consumed = await this.bootConsumePromise;
5069
+ if (consumed) {
5070
+ return result;
5071
+ }
5072
+ }
5061
5073
  this.http.setTokens(accessToken, refreshToken);
5062
5074
  await this.http.bootstrapRefreshCookie();
5063
5075
  return result;
5064
5076
  }
5077
+ /**
5078
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5079
+ *
5080
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5081
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5082
+ * 적재해 세션을 확립한다.
5083
+ *
5084
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5085
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5086
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5087
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5088
+ *
5089
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5090
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5091
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5092
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5093
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5094
+ *
5095
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5096
+ */
5097
+ consumeRedirectCallbackOnBoot() {
5098
+ if (!this.bootConsumePromise) {
5099
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5100
+ }
5101
+ return this.bootConsumePromise;
5102
+ }
5103
+ async doConsumeRedirectOnBoot() {
5104
+ try {
5105
+ if (typeof window === "undefined" || !window.location) return false;
5106
+ const params = new URLSearchParams(window.location.search);
5107
+ if (params.get("error")) return false;
5108
+ const accessToken = params.get("access_token");
5109
+ const refreshToken = params.get("refresh_token");
5110
+ const memberId = params.get("member_id");
5111
+ if (!accessToken || !refreshToken || !memberId) return false;
5112
+ this.http.setTokens(accessToken, refreshToken);
5113
+ await this.http.bootstrapRefreshCookie();
5114
+ return true;
5115
+ } catch {
5116
+ return false;
5117
+ }
5118
+ }
5065
5119
  /**
5066
5120
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5067
5121
  *
@@ -10824,8 +10878,12 @@ var ConnectBase = class {
10824
10878
  this.support = new SupportAPI(this.http);
10825
10879
  this.auth._attachAnalytics(this.analytics);
10826
10880
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10827
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10828
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10881
+ if (shouldAutoRestore && typeof window !== "undefined") {
10882
+ if (this.isOAuthRedirectCallbackUrl()) {
10883
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10884
+ } else if (!this.isOAuthCallbackUrl()) {
10885
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10886
+ }
10829
10887
  }
10830
10888
  }
10831
10889
  /**
@@ -10833,7 +10891,7 @@ var ConnectBase = class {
10833
10891
  *
10834
10892
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10835
10893
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10836
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10894
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10837
10895
  */
10838
10896
  isOAuthCallbackUrl() {
10839
10897
  if (typeof window === "undefined" || !window.location) return false;
@@ -10843,6 +10901,20 @@ var ConnectBase = class {
10843
10901
  if (params.has("error") && params.has("state")) return true;
10844
10902
  return false;
10845
10903
  }
10904
+ /**
10905
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10906
+ *
10907
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10908
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10909
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10910
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10911
+ */
10912
+ isOAuthRedirectCallbackUrl() {
10913
+ if (typeof window === "undefined" || !window.location) return false;
10914
+ if (window.opener) return false;
10915
+ const params = new URLSearchParams(window.location.search);
10916
+ return params.has("access_token") && params.has("refresh_token");
10917
+ }
10846
10918
  /**
10847
10919
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10848
10920
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.30.0",
3
+ "version": "3.31.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",