connectbase-client 3.7.2 → 3.8.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 +168 -0
- package/dist/connect-base.umd.js +2 -2
- package/dist/index.d.mts +47 -8
- package/dist/index.d.ts +47 -8
- package/dist/index.js +62 -9
- package/dist/index.mjs +62 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,142 @@
|
|
|
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.8.0] - 2026-05-07
|
|
7
|
+
|
|
8
|
+
### Added — HttpOnly cookie 기반 refresh token 흐름 (XSS 면역 default 세션)
|
|
9
|
+
|
|
10
|
+
3.7.x 의 `persistence` 콘솔 경고가 권고하던 "HttpOnly cookie + 기본값('none')" 흐름을
|
|
11
|
+
실제로 동작하도록 SDK + 백엔드를 함께 구현했습니다.
|
|
12
|
+
|
|
13
|
+
- `persistence: 'none'` (기본값) 으로도 **새로고침 후 자동 복구** 가능. refresh token 은
|
|
14
|
+
서버 HttpOnly cookie 로만 보관되어 JS 가 접근할 수 없습니다 (XSS 시 탈취 불가).
|
|
15
|
+
- `localStorage` / `sessionStorage` 옵션은 여전히 사용 가능하지만 위험 경고가 유지됩니다.
|
|
16
|
+
|
|
17
|
+
**SDK 변경:**
|
|
18
|
+
|
|
19
|
+
- 모든 ConnectBase API fetch 호출에 `credentials: 'include'` 적용 — HttpOnly refresh cookie 가
|
|
20
|
+
자동 첨부됩니다 (`api.connectbase.world` host-only cookie 기준).
|
|
21
|
+
- `/v1/auth/re-issue` 호출이 cookie 만으로 동작 — 메모리에 refresh token 이 없어도 cookie 가
|
|
22
|
+
있으면 access token 회복.
|
|
23
|
+
- `ConnectBase` 옵션에 `autoRestoreSession?: boolean` 추가 (브라우저 기본 true). 인스턴스 생성
|
|
24
|
+
시 자동으로 cookie 기반 세션 복구를 시도하며, 미로그인/cookie 만료 시 silent 실패.
|
|
25
|
+
- `cb.restoreSession(): Promise<boolean>` 메서드로 명시적 await 도 가능.
|
|
26
|
+
- `persistence` 콘솔 경고를 갱신: 위험은 그대로 표시하되 'none' + HttpOnly cookie 흐름이
|
|
27
|
+
실제로 작동함을 안내.
|
|
28
|
+
|
|
29
|
+
**백엔드 변경 (core-server):**
|
|
30
|
+
|
|
31
|
+
- `pkg/util/cookie` 에 `CrossSite` / `HostOnly` 옵션 추가 (SameSite=None + Secure + host-only).
|
|
32
|
+
- 새 공용 헬퍼 `core-server/app/util/auth_cookie/` — platform 용 `refresh_token` 과 AppMember/OAuth
|
|
33
|
+
용 `cb_member_refresh_token` 두 종류를 분리해 충돌 방지.
|
|
34
|
+
- `/v1/public/app-members/{signin,signup,signout}` + `CreateGuestMember` + OAuth callback/exchange
|
|
35
|
+
엔드포인트가 refresh token 을 HttpOnly cookie 로 발급.
|
|
36
|
+
- `/v1/auth/re-issue` 가 입력 우선순위 `[member cookie → user cookie → Authorization Bearer]`
|
|
37
|
+
로 처리하며, cookie 흐름은 sliding (재호출 시 cookie 만료 7일 연장).
|
|
38
|
+
- 응답 body 의 `refresh_token` 은 하위호환을 위해 그대로 유지 (구버전 SDK / Node.js / 게임 SDK
|
|
39
|
+
호환). 신규 SDK 는 cookie 만 신뢰합니다.
|
|
40
|
+
|
|
41
|
+
**마이그레이션:**
|
|
42
|
+
|
|
43
|
+
기존 `persistence: 'sessionStorage'` 또는 `'localStorage'` 사용 중이었다면 옵션을 제거하기만
|
|
44
|
+
하면 됩니다 (default 가 안전 흐름):
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// before (3.7.x)
|
|
48
|
+
new ConnectBase({ publicKey, persistence: 'sessionStorage' })
|
|
49
|
+
|
|
50
|
+
// after (3.8.0)
|
|
51
|
+
new ConnectBase({ publicKey }) // persistence: 'none' + autoRestoreSession: true (기본)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
다른 origin 에서 호출하는 경우 (앱 도메인 ≠ `api.connectbase.world`) CORS 화이트리스트에 등록되어
|
|
55
|
+
있어야 합니다 — 콘솔의 커스텀 도메인 등록 흐름이 그대로 적용됩니다.
|
|
56
|
+
|
|
57
|
+
## [3.7.2] - 2026-05-01
|
|
58
|
+
|
|
59
|
+
### Fixed — `cb.endpoint.connectWebSocket()` 메시지 깨짐 + permessage-deflate 누설
|
|
60
|
+
|
|
61
|
+
3.7.0/3.7.1 의 endpoint WS pass-through 가 handshake 는 101 정상이지만 모든
|
|
62
|
+
메시지가 binary frame 으로 도착하고 payload 가 origin (ComfyUI/aiohttp) 의
|
|
63
|
+
raw WS frame bytes (RSV1=1 압축 frame 포함) 라 client 의 native WebSocket
|
|
64
|
+
이 디코드 불가하던 문제를 수정했습니다. 두 개의 별개 버그가 합쳐진 회귀:
|
|
65
|
+
|
|
66
|
+
1. **CLI 가 WS frame parse/encode 안 함** — 기존 `startWSStream` 이
|
|
67
|
+
`socket.on('data')` 의 raw bytes 를 그대로 v2 binary frame payload 로
|
|
68
|
+
forward 해서 client 에 frame header (0x81/0x82/0xc1) 가 섞인 상태로 도달.
|
|
69
|
+
2. **`Sec-WebSocket-Extensions` 가 upstream 까지 forward 됨** — 브라우저 native
|
|
70
|
+
WebSocket 이 default 로 `permessage-deflate` 를 광고하고, sanitize 가 hop-by-hop
|
|
71
|
+
만 strip 해서 upstream (aiohttp default) 이 accept → compressed frame 송신
|
|
72
|
+
시작. CLI 는 deflate context 가 없어 디코드 불가.
|
|
73
|
+
|
|
74
|
+
**Fix (CLI):**
|
|
75
|
+
|
|
76
|
+
- `UpstreamWsFrameParser`: incoming WS frame 을 parse 해서 payload 만 추출.
|
|
77
|
+
TEXT/BINARY/CONTINUATION/PING/CLOSE 처리, fragmented frame 은 누적, RSV1
|
|
78
|
+
(compression) frame 은 protocol error 로 거부.
|
|
79
|
+
- `buildClientFrame` / `createUpstreamTextFrame`: outgoing payload 를 RFC 6455
|
|
80
|
+
§5.3 client masking 적용한 masked WS frame 으로 encode 후 upstream 에 write.
|
|
81
|
+
- 로컬 upstream 요청에서 `Sec-WebSocket-Extensions` 헤더 strip — proxy chain 이
|
|
82
|
+
deflate context 를 end-to-end 로 carry 하지 못하므로 compression 자체를 비활성.
|
|
83
|
+
|
|
84
|
+
**회귀 영향 범위:** 3.7.0 의 모든 `cb.endpoint.connectWebSocket()` 사용자.
|
|
85
|
+
ComfyUI / vLLM 등 default-on compression origin 영향. 사용자가 3.7.2 로 업그레이드
|
|
86
|
+
+ 터널 재시작 시 즉시 정상화. text/binary 구분은 client 측에서 항상 binary
|
|
87
|
+
(ArrayBuffer) 로 도달 — JSON 은 `TextDecoder.decode` 로 string 변환 필요.
|
|
88
|
+
Opcode propagation 은 별도 follow-up.
|
|
89
|
+
|
|
90
|
+
**회귀 가드:** `UpstreamWsFrameParser` 단위 테스트 (TEXT payload 추출 / fragmented
|
|
91
|
+
누적 / RSV1 reject / TCP coalescing / split-across-chunks / PING callback) +
|
|
92
|
+
`buildClientFrame` masking round-trip + `WSStreamForwarder` e2e (real local HTTP
|
|
93
|
+
server 로 `Sec-WebSocket-Extensions` 미도달 검증).
|
|
94
|
+
|
|
95
|
+
## [3.7.1] - 2026-05-01
|
|
96
|
+
|
|
97
|
+
### Fixed — `cb.endpoint.connectWebSocket()` 가 502 로 떨어지던 회귀
|
|
98
|
+
|
|
99
|
+
3.7.0 의 endpoint WS pass-through 가 client 단에서 항상 CF 502 page 로 떨어지던
|
|
100
|
+
문제를 수정했습니다. Root cause 는 CLI 가 로컬 upstream HTTP 요청에 WebSocket
|
|
101
|
+
upgrade 헤더 (`Connection: Upgrade` + `Upgrade: websocket`) 를 누락한 것:
|
|
102
|
+
upstream (ComfyUI 등) 이 일반 GET 으로 인식하고 400 Bad Request 반환 → core-server
|
|
103
|
+
ReverseProxy 가 비-101 body 를 relay 하려다 `net/http: abort Handler` panic →
|
|
104
|
+
CF 가 corrupted response 를 자체 502 page 로 substitute.
|
|
105
|
+
|
|
106
|
+
**Primary fix (CLI):**
|
|
107
|
+
|
|
108
|
+
- `cli.ts` `startWSStream` 과 `tunnel-v2.ts` `WSStreamForwarder` 가 upstream
|
|
109
|
+
요청에 `Connection: Upgrade` + `Upgrade: websocket` 명시적으로 set. tunnel-server
|
|
110
|
+
의 `sanitizeRequestHeaders` 가 RFC 7230 hop-by-hop 으로 strip 하므로 CLI →
|
|
111
|
+
upstream 새 hop 에서 다시 추가 필요.
|
|
112
|
+
|
|
113
|
+
**Defensive fix (core-server, 자동 deploy):**
|
|
114
|
+
|
|
115
|
+
- `ForwardWebSocket` `ModifyResponse` 에서 비-101 upstream 응답 시 ErrorHandler
|
|
116
|
+
경로로 라우팅 — ReverseProxy 가 비-101 body 를 relay 하려다 panic 하는 path
|
|
117
|
+
차단. Upstream 이 정당한 사유로 비-101 (rate limit / auth 실패 등) 반환할 때도
|
|
118
|
+
깨끗한 502 + 진단 메시지가 client 까지 도달.
|
|
119
|
+
|
|
120
|
+
**회귀 영향 범위:** 3.7.0 의 모든 `cb.endpoint.connectWebSocket()` 사용자.
|
|
121
|
+
사용자가 3.7.1 로 업그레이드 + 터널 재시작 시 정상 동작.
|
|
122
|
+
|
|
123
|
+
**회귀 가드:** `tunnel-v2.test.ts` 가 `WSStreamForwarder` 의 upgrade 헤더 송신을
|
|
124
|
+
real local HTTP server 로 e2e 검증. Go 측 `proxy_service_test.go
|
|
125
|
+
TestForwardWebSocket_Non101UpstreamReturnsCleanError` 가 panic 없는 깨끗한 502
|
|
126
|
+
반환을 락인.
|
|
127
|
+
|
|
128
|
+
## [3.7.0] - 2026-05-01
|
|
129
|
+
|
|
130
|
+
### Added — `cb.endpoint.connectWebSocket()` 네이티브 WebSocket 지원
|
|
131
|
+
|
|
132
|
+
Endpoint Proxy v2 (Phase 5) 가 출하되어, SDK 사용자가 `cb.endpoint.connectWebSocket()`
|
|
133
|
+
한 줄로 ComfyUI / vLLM / 일반 모델 서버의 WebSocket 엔드포인트를 직접 연결할 수
|
|
134
|
+
있습니다. CLI 도 v2 endpoint (`/v2/tunnel/connect`) 를 사용하도록 전환.
|
|
135
|
+
|
|
136
|
+
> **요구사항:** 백엔드 v2 endpoint 가 먼저 배포돼 있어야 동작합니다. 프로덕션
|
|
137
|
+
> 배포 완료 후 SDK 업그레이드를 권장합니다.
|
|
138
|
+
|
|
139
|
+
> **알려진 이슈:** 3.7.0 / 3.7.1 에서 WS frame leakage / 502 회귀가 발견되어
|
|
140
|
+
> **3.7.2 이상 사용 권장**. 자세한 내용은 3.7.1 / 3.7.2 항목 참고.
|
|
141
|
+
|
|
6
142
|
## [3.6.0] - 2026-05-01
|
|
7
143
|
|
|
8
144
|
### Added — `cb.realtime.stream()` 멀티모달 메시지 (Vision) 지원
|
|
@@ -168,6 +304,38 @@ await cb.knowledge.search(kbId, {
|
|
|
168
304
|
|
|
169
305
|
자세한 사용법: [docs/knowledge-base/USER_ISOLATION.md](https://github.com/connectbase-world/connectbase/blob/release/docs/knowledge-base/USER_ISOLATION.md)
|
|
170
306
|
|
|
307
|
+
## [3.4.0] - 2026-04-30
|
|
308
|
+
|
|
309
|
+
### Added — `cb.analytics.reset()` 사용자 전환 오염 방지 helper
|
|
310
|
+
|
|
311
|
+
같은 브라우저에서 다른 사용자로 로그인할 때 방문자 (`visitor_uid`) 데이터가 이전
|
|
312
|
+
사용자의 식별자에 묶인 채로 남아 데이터가 오염되던 문제를 SDK 측에서 끊어 낼 수
|
|
313
|
+
있는 helper 를 추가했습니다.
|
|
314
|
+
|
|
315
|
+
- **`cb.analytics.reset()`** — 로그아웃 시 호출. `visitor_uid` 를 새로 발급하고
|
|
316
|
+
in-flight 큐를 비웁니다.
|
|
317
|
+
- **자동 reset (1) — `identify(memberId)` 가 다른 멤버 감지** — 직전 세션과 다른
|
|
318
|
+
`memberId` 가 들어오면 SDK 가 자동으로 `reset()` → identify 순으로 처리.
|
|
319
|
+
- **자동 reset (2) — `linkMemberSilent` conflict 안전망** — 백엔드가 `409
|
|
320
|
+
VISITOR_LINKED_TO_OTHER_MEMBER` 응답을 주면 SDK 가 자동으로 reset 후 1회 재시도.
|
|
321
|
+
명시적 `reset()` 호출을 누락한 경우의 fallback.
|
|
322
|
+
|
|
323
|
+
### Changed — BatchEvent 멱등성
|
|
324
|
+
|
|
325
|
+
- 모든 `BatchEvent` 에 `event_id` (UUID) 자동 부여. 백엔드가 `(visitor_id,
|
|
326
|
+
event_id)` UNIQUE 인덱스 + `OnConflict DoNothing` 으로 at-least-once 재전송 시
|
|
327
|
+
중복 INSERT 차단. SDK 사용자 입장에서 추가 작업 없음.
|
|
328
|
+
|
|
329
|
+
### Fixed — 첫 방문 봇 오판 방지
|
|
330
|
+
|
|
331
|
+
- `flushSync` (`navigator.sendBeacon`) 호출에 `user_agent` 를 첨부 — 첫 방문 직후
|
|
332
|
+
`pagehide` 로 flush 될 때 백엔드가 UA 누락으로 봇으로 분류해 카운트가 누락되던
|
|
333
|
+
문제 해결.
|
|
334
|
+
|
|
335
|
+
**서버 측 동시 변경:** `LinkMember` idempotent 처리, `WebPageView.event_id` 컬럼 +
|
|
336
|
+
UNIQUE 인덱스, `link-member` 옵셔널 토큰 검증 (Authorization Bearer 가 있으면
|
|
337
|
+
AppMember 토큰의 `member_id` 와 body 일치 검증, 없으면 BC 보존 + 경고 로그).
|
|
338
|
+
|
|
171
339
|
## [3.3.1] - 2026-04-30
|
|
172
340
|
|
|
173
341
|
### Fixed — Docs
|
package/dist/connect-base.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var ConnectBaseModule=(()=>{var ie=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var Se=(c,e)=>{for(var t in e)ie(c,t,{get:e[t],enumerable:!0})},Re=(c,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of we(e))!Pe.call(c,i)&&i!==t&&ie(c,i,{get:()=>e[i],enumerable:!(s=ve(e,i))||s.enumerable});return c};var _e=c=>Re(ie({},"__esModule",{value:!0}),c);var Le={};Se(Le,{AIAPI:()=>x,AUTH_MEMBER_ID_TOKEN:()=>xe,AdsAPI:()=>C,ApiError:()=>h,AuthError:()=>P,ConnectBase:()=>se,EndpointAPI:()=>$,GameAPI:()=>I,GameConfigAPI:()=>k,GameRoom:()=>M,GameRoomTransport:()=>te,NativeAPI:()=>E,SessionManager:()=>q,VideoProcessingError:()=>T,default:()=>De,isWebTransportSupported:()=>le});var h=class extends Error{constructor(t,s,i,r){super(s);this.statusCode=t;this.name="ApiError",this.code=i,this.details=r}},P=class extends Error{constructor(e){super(e),this.name="AuthError"}};function _(c={}){let e=new AbortController,t=c.timeout??3e4,s=c.signal,i=null,r=null;return s&&(s.aborted?e.abort(s.reason):(r=()=>e.abort(s.reason),s.addEventListener("abort",r,{once:!0}))),t>0&&Number.isFinite(t)&&(i=setTimeout(()=>{e.abort(new DOMException(`Request timed out after ${t}ms`,"TimeoutError"))},t)),{signal:e.signal,cleanup:()=>{i!==null&&clearTimeout(i),s&&r&&s.removeEventListener("abort",r)}}}var de="cb_auth_tokens",D=class{constructor(e){this.isRefreshing=!1;this.refreshPromise=null;this.refreshFailureCount=0;this.refreshLockedUntil=0;this.config={...e},this.storageKey=this.buildStorageKey(),this.warnIfUnsafePersistence(),this.restoreTokens()}warnIfUnsafePersistence(){typeof window>"u"||(this.config.persistence==="localStorage"?console.warn(`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC \uD1A0\uD070 \uC601\uAD6C \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\uB2C8\uB2E4. refresh token \uC740 \uC11C\uBC84 HttpOnly \uCFE0\uD0A4\uB85C \uBC1C\uAE09\uBC1B\uACE0 \uAE30\uBCF8\uAC12('none')\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`):this.config.persistence==="sessionStorage"&&console.warn('[connect-base-client] persistence="sessionStorage" \uB294 XSS \uC2DC \uD604\uC7AC \uD0ED \uC138\uC158 \uD0C8\uCDE8 \uC704\uD5D8\uC774 \uC788\uC2B5\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()}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 de;let t=0;for(let s=0;s<e.length;s++)t=(t<<5)-t+e.charCodeAt(s),t=t&t;return`${de}_${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:i}=JSON.parse(t);s&&i&&(this.config.accessToken=s,this.config.refreshToken=i)}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 P("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){this.isRefreshing=!1,this.config.onTokenExpired?.();let t=new P("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}=_({timeout:this.config.requestTimeoutMs??3e4});try{let i=await fetch(`${this.config.baseUrl}/v1/auth/re-issue`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.refreshToken}`},signal:t});if(!i.ok)throw new Error("Token refresh failed");let r=await i.json();return this.setTokens(r.access_token,r.refresh_token),this.config.onTokenRefresh?.({accessToken:r.access_token,refreshToken:r.refresh_token}),this.refreshFailureCount=0,this.refreshLockedUntil=0,r.access_token}catch{this.refreshFailureCount++;let i=Math.min(500*2**Math.max(0,this.refreshFailureCount-1),3e4);this.refreshLockedUntil=Date.now()+i,this.clearTokens(),this.config.onTokenExpired?.();let r=new P("Token refresh failed. Please login again.");throw this.emitError(r),this.config.onAuthError?.(r),r}finally{s(),this.isRefreshing=!1,this.refreshPromise=null}})(),this.refreshPromise}emitError(e){try{this.config.onError?.(e)}catch{}}isTokenExpired(e){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s+300}catch{return!0}}async prepareHeaders(e){let t=new Headers;t.set("Content-Type","application/json");let s=this.getCredential();if(s&&t.set("X-Public-Key",s),!e?.skipAuth&&this.config.accessToken){let i=this.config.accessToken;if(this.isTokenExpired(i)&&this.config.refreshToken){let r=await this.refreshAccessToken();r&&(i=r)}t.set("Authorization",`Bearer ${i}`)}return e?.headers&&Object.entries(e.headers).forEach(([i,r])=>{t.set(i,r)}),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,i;if(s){let a=Number.parseInt(s,10);if(Number.isFinite(a)&&a>=0)i=a;else{let d=Date.parse(s);Number.isFinite(d)&&(i=Math.max(0,Math.round((d-Date.now())/1e3)))}}let r=t.error;if(r&&typeof r=="object"&&"message"in r){let a={...r.details&&typeof r.details=="object"?r.details:{}};i!==void 0&&(a.retry_after_seconds=i);let d=new h(e.status,r.message||"Unknown error",r.code,Object.keys(a).length>0?a:r.details);throw this.emitError(d),d}let n=typeof r=="string"?r:t.message||"Unknown error",o=i!==void 0?{retry_after_seconds:i}:void 0,l=new h(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{signal:i,cleanup:r}=_({timeout:s?.timeout??this.config.requestTimeoutMs??3e4,signal:s?.signal});try{let n=await fetch(`${this.config.baseUrl}${e}`,{...t,signal:i});return await this.handleResponse(n)}finally{r()}}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 i=await this.prepareHeaders(s);return t instanceof FormData&&i.delete("Content-Type"),this.doFetch(e,{method:"POST",headers:i,body:t instanceof FormData?t:JSON.stringify(t)},s)}async put(e,t,s){let i=await this.prepareHeaders(s);return this.doFetch(e,{method:"PUT",headers:i,body:JSON.stringify(t)},s)}async patch(e,t,s){let i=await this.prepareHeaders(s);return this.doFetch(e,{method:"PATCH",headers:i,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(),i=new Headers(s);return t?.headers&&new Headers(t.headers).forEach((n,o)=>i.set(o,n)),fetch(`${this.config.baseUrl}${e}`,{...t,headers:i})}};function Te(c,e){switch(e){case"string":return typeof c=="string";case"number":return typeof c=="number"&&Number.isFinite(c);case"boolean":return typeof c=="boolean";case"array":return Array.isArray(c);case"object":return typeof c=="object"&&c!==null&&!Array.isArray(c);case"string-or-number":return typeof c=="string"||typeof c=="number"&&Number.isFinite(c)}}function f(c,e,t){if(typeof c!="object"||c===null||Array.isArray(c))throw new h(502,`[${t}] expected object, got ${typeof c}`,"SCHEMA_MISMATCH");let s=c;for(let[i,r]of Object.entries(e)){if(!(i in s)){if(r.optional)continue;throw new h(502,`[${t}] missing required field "${i}"`,"SCHEMA_MISMATCH")}let o=s[i];if(!(r.optional&&o==null)&&!Te(o,r.type))throw new h(502,`[${t}] field "${i}" expected ${r.type}, got ${typeof o}`,"SCHEMA_MISMATCH")}return c}var pe="cb_guest_";function ke(c){let e=0;for(let t=0;t<c.length;t++){let s=c.charCodeAt(t);e=(e<<5)-e+s,e=e&e}return Math.abs(e).toString(36)}var L=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 f(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(){if(this.guestMemberLoginPromise)return this.guestMemberLoginPromise;this.guestMemberLoginPromise=this.executeGuestMemberLogin();try{return await this.guestMemberLoginPromise}finally{this.guestMemberLoginPromise=null}}async getMe(){let e=await this.http.get("/v1/public/app-members/me");return f(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 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=`${pe}default`;else{let t=ke(e);this.cachedGuestMemberTokenKey=`${pe}${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 i=this.http.getPersistenceStorage();i&&i.setItem(this.getGuestMemberTokenKey(),JSON.stringify({accessToken:e,refreshToken:t,memberId:s}))}};var H=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(),i={};t.name!==void 0&&(i.title=t.name),t.schema!==void 0&&(i.schema=t.schema),t.accessLevel!==void 0&&(i.access_level=t.accessLevel),t.description!==void 0&&(i.description=t.description),await this.http.patch(`${s}/tables/${e}`,i)}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??{},i=new Set(Array.isArray(s.$required)?s.$required:[]),r=[],n=0;for(let[o,l]of Object.entries(s)){if(o.startsWith("$")||l===void 0||Array.isArray(l))continue;let a="string",d=i.has(o),u,p,g;typeof l=="string"?a=l:(a=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&&(g=l.encrypted)),r.push({id:o,name:o,data_type:a,is_required:d,default_value:u,description:p,encrypted:g,order:n++,created_at:t.created_at})}return r}async createColumn(e,t){let s=this.getPublicPrefix();await this.http.post(`${s}/tables/${e}/columns`,t)}async updateColumn(e,t,s){let i=this.getPublicPrefix();await this.http.patch(`${i}/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 i=new URLSearchParams;t?.limit&&i.append("limit",t.limit.toString()),t?.offset&&i.append("offset",t.offset.toString());let r=i.toString(),n=r?`${s}/tables/${e}/data?${r}`:`${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 i=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(`${i}/tables/${e}/data`,t);let n=s?.autoCreate?"?auto_create=true":"";return this.http.post(`${i}/tables/name/${encodeURIComponent(e)}/data${n}`,t)}async updateData(e,t,s){let i=this.getPublicPrefix();return this.http.patch(`${i}/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(i=>i.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,i){let r=this.getPublicPrefix();return this.http.post(`${r}/search`,{table_id:e,query:t,fields:s,options:i})}async autocomplete(e,t,s,i){let r=this.getPublicPrefix();return this.http.post(`${r}/autocomplete`,{table_id:e,query:t,field:s,...i})}async geoQuery(e,t,s,i){let r=this.getPublicPrefix();return this.http.post(`${r}/geo`,{table_id:e,field:t,query:s,...i})}async batch(e){let t=this.getPublicPrefix();return this.http.post(`${t}/batch`,{operations:e})}async transaction(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/transactions`,{reads:e,writes:t})}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 i=`csub_${Date.now()}_${Math.random().toString(36).substring(2,9)}`;this.activeSubscriptions.set(i,{tableId:e,options:s,handlers:t});let r=this.sendSubscribeRequest(e,t,s);return r.catch(n=>{this.activeSubscriptions.delete(i),t.onError?.(n instanceof Error?n:new Error(String(n)))}),{subscriptionId:i,unsubscribe:()=>{this.activeSubscriptions.delete(i),r.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"&&r.then(l=>{let a={type:"snapshot_more",request_id:this.generateRequestId(),subscription_id:l,offset:n};o!==void 0&&(a.limit=o),this.sendRealtimeMessage(a)}).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((i,r)=>{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"),r(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(),i()},this.realtimeWs.onmessage=l=>{try{let a=JSON.parse(l.data);this.handleRealtimeMessage(a)}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),r(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"),r(n)}})}sendSubscribeRequest(e,t,s){let i=this.generateRequestId();this.realtimeHandlers.set(i,t);let r=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:i,table_id:e,doc_id:s?.docId,query:r,options:{include_self:s?.includeSelf??!1,include_metadata_changes:s?.includeMetadataChanges??!1}}),new Promise((n,o)=>{let l=setTimeout(()=>{this.pendingRequests.delete(i),this.realtimeHandlers.delete(i),o(new Error("Subscribe request timeout"))},3e4);this.pendingRequests.set(i,{resolve:a=>{let d=a,u=this.realtimeHandlers.get(i);u&&(this.realtimeHandlers.delete(i),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,i=e.subscription_id,r=this.pendingRequests.get(s);r&&(clearTimeout(r.timeout),r.resolve(i),this.pendingRequests.delete(s));break}case"snapshot":{let s=e.subscription_id,i=this.realtimeHandlers.get(s);if(i?.onSnapshot){let r=e.docs||[],n=e.has_more||!1,o=n?e.next_offset:void 0;i.onSnapshot(r,{totalCount:e.total_count||0,hasMore:n,nextOffset:o})}break}case"change":{let s=e.subscription_id,i=this.realtimeHandlers.get(s);if(i?.onChange){let r=e.changes||[];i.onChange(r)}break}case"presence":{let s=this.realtimeHandlers.get("__presence__");if(s?.onSnapshot){let i=e.states,r=Object.entries(i||{}).map(([n,o])=>({id:n,data:o,exists:!0}));s.onSnapshot(r,{totalCount:r.length,hasMore:!1})}break}case"error":{let s=e.request_id,i=e.message||"Unknown error";if(s){let r=this.pendingRequests.get(s);r&&(clearTimeout(r.timeout),r.reject(new Error(i)),this.pendingRequests.delete(s));let n=this.realtimeHandlers.get(s);n?.onError&&n.onError(new Error(i))}else this.realtimeOnError?.(new Error(i));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(i=>{this.debugLog(`Reconnect failed: ${i}`)})},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 re=new Set(["localhost","127.0.0.1","::1"]),ue=2048;function he(c,e={}){let t=e.context??"external URL";if(typeof c!="string"||c.length===0)throw new h(400,`${t}: empty URL`,"INVALID_PRESIGNED_URL");if(c.length>ue)throw new h(400,`${t}: URL exceeds ${ue} chars`,"INVALID_PRESIGNED_URL");let s;try{s=new URL(c)}catch{throw new h(400,`${t}: cannot parse URL`,"INVALID_PRESIGNED_URL")}let i=e.allowLocalhost&&re.has(s.hostname);if(s.protocol!=="https:"&&!(i&&s.protocol==="http:"))throw new h(400,`${t}: scheme must be https (got ${s.protocol})`,"INVALID_PRESIGNED_URL");if(!e.allowLocalhost&&re.has(s.hostname))throw new h(400,`${t}: localhost is not allowed in production`,"INVALID_PRESIGNED_URL");if(e.allowedHosts&&e.allowedHosts.length>0){let r=s.hostname;if(!e.allowedHosts.some(o=>o===r?!0:r.endsWith(`.${o}`)))throw new h(400,`${t}: host ${r} is not in allowlist`,"INVALID_PRESIGNED_URL")}return s}function ge(){if(typeof window>"u")return!1;let c=window.location.hostname;return re.has(c)}var O=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async uploadToPresigned(e,t,s){let i=he(e,{allowLocalhost:ge(),context:"storage.presigned-url"}),{signal:r,cleanup:n}=_({timeout:s?.timeout,signal:s?.signal});try{let o=await fetch(i.toString(),{method:"PUT",body:t,headers:{"Content-Type":t.type||"application/octet-stream"},signal:r});if(!o.ok)throw new h(o.status,`Upload failed: ${o.statusText}`,"PRESIGNED_UPLOAD_FAILED")}finally{n()}}async getFiles(e,t){let s=this.getPublicPrefix(),i=t?`?parent_id=${encodeURIComponent(t)}`:"";return(await this.http.get(`${s}/storages/files/${e}/items${i}`)).files}async uploadFile(e,t,s){let i=this.getPublicPrefix(),r=await this.http.post(`${i}/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(r.upload_url,t);let n=await this.http.post(`${i}/storages/files/${e}/complete-upload`,{file_id:r.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 i=[];for(let r of t){let n=await this.uploadFile(e,r,s);i.push(n)}return i}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,i){let r=this.getPublicPrefix(),n=t.startsWith("/")?t.slice(1):t,o=i?.overwrite!==!1,l=await this.http.post(`${r}/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 a=await this.http.post(`${r}/storages/files/${e}/complete-upload`,{file_id:l.file_id});return{id:a.id,name:a.name,path:a.path,type:a.type,mime_type:a.mime_type,size:a.size,url:a.url,parent_id:a.parent_id,created_at:a.created_at}}async getByPath(e,t){let s=this.getPublicPrefix(),i=t.startsWith("/")?t.slice(1):t;return this.http.get(`${s}/storages/files/${e}/path/${i}`)}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(),i=new URLSearchParams;t?.limit!=null&&i.set("limit",String(t.limit)),t?.offset!=null&&i.set("offset",String(t.offset));let r=i.toString();return this.http.get(`${s}/storages/webs/${e}/page-metas${r?`?${r}`:""}`)}async getPageMeta(e,t){let s=this.getPublicPrefix(),i=encodeURIComponent(t);return this.http.get(`${s}/storages/webs/${e}/page-metas/get?path=${i}`)}async deletePageMeta(e,t){let s=this.getPublicPrefix(),i=encodeURIComponent(t);await this.http.delete(`${s}/storages/webs/${e}/page-metas?path=${i}`)}async deleteAllPageMetas(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/storages/webs/${e}/page-metas/all`)}};var B=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 G=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async invoke(e,t,s){let i=this.getPublicPrefix(),r={};return t!==void 0&&(r.payload=t),s!==void 0&&(r.timeout=s),this.http.post(`${i}/functions/${e}/invoke`,r)}async call(e,t){let s=await this.invoke(e,t);if(!s.success)throw new h(500,s.error||"Function execution failed","FUNCTION_EXECUTION_FAILED");return s.result}};var F=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(),i=await this.sendRequest({category:e,action:"subscribe",request_id:s}),r={category:i.category,persist:i.persist,historyCount:i.history_count,readReceipt:i.read_receipt},n=[];return this.subscriptions.set(e,{info:r,handlers:n}),{info:r,send:async(l,a)=>{await this.sendMessage(e,l,a)},getHistory:async l=>this.getHistory(e,l??t.historyLimit),unsubscribe:async()=>{await this.unsubscribe(e)},onMessage:l=>(n.push(l),()=>{let a=n.indexOf(l);a>-1&&n.splice(a,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 r=s.includeSelf!==!1,n=this.generateRequestId();await this.sendRequest({category:e,action:"send",data:{data:t,broadcast:r},request_id:n})}async getHistory(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId(),i=await this.sendRequest({category:e,action:"history",data:t?{limit:t}:void 0,request_id:s});return{category:i.category,messages:i.messages.map(r=>({id:r.id,category:r.category,from:r.from,data:r.data,sentAt:r.sent_at})),total:i.total}}async stream(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let i=this.generateRequestId(),r=s.sessionId||this.generateRequestId();this.streamSessions.set(r,{handlers:t,requestId:i});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:r,metadata:s.metadata,mcp_group:s.mcpGroup},request_id:i})}catch(n){throw this.streamSessions.delete(r),n}return{sessionId:r,stop:async()=>{await this.stopStream(r)}}}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}),i={};for(let[r,n]of Object.entries(s.users))i[r]={userId:n.user_id,status:n.status,lastSeen:n.last_seen,device:n.device,metadata:n.metadata};return{users:i}}async subscribePresence(e,t){if(this.state!=="connected")throw new Error("Not connected");if(!this.presenceSubscriptions.has(e)){let i=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:e},request_id:i}),this.presenceSubscriptions.set(e,[])}let s=this.presenceSubscriptions.get(e);return s.push(t),()=>{let i=s.indexOf(t);if(i>-1&&s.splice(i,1),s.length===0&&(this.presenceSubscriptions.delete(e),this.state==="connected")){let r=this.generateRequestId();this.sendRequest({category:"",action:"presence_unsubscribe",data:{user_id:e},request_id:r}).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 i=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:e},request_id:i}),this.typingHandlers.set(e,[])}let s=this.typingHandlers.get(e);return s.push(t),()=>{let i=s.indexOf(t);if(i>-1&&s.splice(i,1),s.length===0&&(this.typingHandlers.delete(e),this.state==="connected")){let r=this.generateRequestId();this.sendRequest({category:"",action:"typing_unsubscribe",data:{room_id:e},request_id:r}).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 i=s.indexOf(t);i>-1&&s.splice(i,1)}}async doConnect(){return new Promise((e,t)=>{this.state="connecting",this.notifyStateChange(),this.log("Connecting...");let s=this.socketUrl.replace(/^http/,"ws"),i;if(this.options.accessToken)i=`${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}i=`${s}/v1/realtime/auth?public_key=${encodeURIComponent(o)}&client_id=${this.clientId}`,this.log("Using API Key authentication")}this.userId&&(i+=`&user_id=${encodeURIComponent(this.userId)}`);let r=!1,n=setTimeout(()=>{r||(r=!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(i),this.ws.onopen=()=>{this.log("WebSocket opened, waiting for connected event...")},this.ws.onmessage=o=>{let l=o.data.split(`
|
|
1
|
+
"use strict";var ConnectBaseModule=(()=>{var ie=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var Se=(c,e)=>{for(var t in e)ie(c,t,{get:e[t],enumerable:!0})},Re=(c,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of we(e))!Pe.call(c,i)&&i!==t&&ie(c,i,{get:()=>e[i],enumerable:!(s=ve(e,i))||s.enumerable});return c};var _e=c=>Re(ie({},"__esModule",{value:!0}),c);var He={};Se(He,{AIAPI:()=>x,AUTH_MEMBER_ID_TOKEN:()=>xe,AdsAPI:()=>C,ApiError:()=>h,AuthError:()=>P,ConnectBase:()=>se,EndpointAPI:()=>$,GameAPI:()=>I,GameConfigAPI:()=>k,GameRoom:()=>M,GameRoomTransport:()=>te,NativeAPI:()=>E,SessionManager:()=>q,VideoProcessingError:()=>T,default:()=>De,isWebTransportSupported:()=>le});var h=class extends Error{constructor(t,s,i,r){super(s);this.statusCode=t;this.name="ApiError",this.code=i,this.details=r}},P=class extends Error{constructor(e){super(e),this.name="AuthError"}};function _(c={}){let e=new AbortController,t=c.timeout??3e4,s=c.signal,i=null,r=null;return s&&(s.aborted?e.abort(s.reason):(r=()=>e.abort(s.reason),s.addEventListener("abort",r,{once:!0}))),t>0&&Number.isFinite(t)&&(i=setTimeout(()=>{e.abort(new DOMException(`Request timed out after ${t}ms`,"TimeoutError"))},t)),{signal:e.signal,cleanup:()=>{i!==null&&clearTimeout(i),s&&r&&s.removeEventListener("abort",r)}}}var de="cb_auth_tokens",D=class{constructor(e){this.isRefreshing=!1;this.refreshPromise=null;this.refreshFailureCount=0;this.refreshLockedUntil=0;this.config={...e},this.storageKey=this.buildStorageKey(),this.warnIfUnsafePersistence(),this.restoreTokens()}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()}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 de;let t=0;for(let s=0;s<e.length;s++)t=(t<<5)-t+e.charCodeAt(s),t=t&t;return`${de}_${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:i}=JSON.parse(t);s&&i&&(this.config.accessToken=s,this.config.refreshToken=i)}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 P("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 P("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}=_({timeout:this.config.requestTimeoutMs??3e4});try{let i={"Content-Type":"application/json"};this.config.refreshToken&&(i.Authorization=`Bearer ${this.config.refreshToken}`);let r=await fetch(`${this.config.baseUrl}/v1/auth/re-issue`,{method:"POST",headers:i,credentials:"include",signal:t});if(!r.ok)throw new Error("Token refresh failed");let n=await r.json();if(!n||typeof n.access_token!="string")throw new Error("Token refresh response missing access_token");let o=typeof n.refresh_token=="string"&&n.refresh_token.length>0?n.refresh_token:this.config.refreshToken??"";return o?this.setTokens(n.access_token,o):(this.config.accessToken=n.access_token,this.persistTokens()),this.config.onTokenRefresh?.({accessToken:n.access_token,refreshToken:o}),this.refreshFailureCount=0,this.refreshLockedUntil=0,n.access_token}catch{this.refreshFailureCount++;let i=Math.min(500*2**Math.max(0,this.refreshFailureCount-1),3e4);this.refreshLockedUntil=Date.now()+i,this.clearTokens(),this.config.onTokenExpired?.();let r=new P("Token refresh failed. Please login again.");throw this.emitError(r),this.config.onAuthError?.(r),r}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){try{let t=JSON.parse(atob(e.split(".")[1])),s=Date.now()/1e3;return t.exp<s+300}catch{return!0}}async prepareHeaders(e){let t=new Headers;t.set("Content-Type","application/json");let s=this.getCredential();if(s&&t.set("X-Public-Key",s),!e?.skipAuth&&this.config.accessToken){let i=this.config.accessToken;if(this.isTokenExpired(i)&&this.config.refreshToken){let r=await this.refreshAccessToken();r&&(i=r)}t.set("Authorization",`Bearer ${i}`)}return e?.headers&&Object.entries(e.headers).forEach(([i,r])=>{t.set(i,r)}),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,i;if(s){let a=Number.parseInt(s,10);if(Number.isFinite(a)&&a>=0)i=a;else{let d=Date.parse(s);Number.isFinite(d)&&(i=Math.max(0,Math.round((d-Date.now())/1e3)))}}let r=t.error;if(r&&typeof r=="object"&&"message"in r){let a={...r.details&&typeof r.details=="object"?r.details:{}};i!==void 0&&(a.retry_after_seconds=i);let d=new h(e.status,r.message||"Unknown error",r.code,Object.keys(a).length>0?a:r.details);throw this.emitError(d),d}let n=typeof r=="string"?r:t.message||"Unknown error",o=i!==void 0?{retry_after_seconds:i}:void 0,l=new h(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{signal:i,cleanup:r}=_({timeout:s?.timeout??this.config.requestTimeoutMs??3e4,signal:s?.signal});try{let n=await fetch(`${this.config.baseUrl}${e}`,{...t,credentials:"include",signal:i});return await this.handleResponse(n)}finally{r()}}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 i=await this.prepareHeaders(s);return t instanceof FormData&&i.delete("Content-Type"),this.doFetch(e,{method:"POST",headers:i,body:t instanceof FormData?t:JSON.stringify(t)},s)}async put(e,t,s){let i=await this.prepareHeaders(s);return this.doFetch(e,{method:"PUT",headers:i,body:JSON.stringify(t)},s)}async patch(e,t,s){let i=await this.prepareHeaders(s);return this.doFetch(e,{method:"PATCH",headers:i,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(),i=new Headers(s);return t?.headers&&new Headers(t.headers).forEach((n,o)=>i.set(o,n)),fetch(`${this.config.baseUrl}${e}`,{...t,credentials:"include",headers:i})}};function Te(c,e){switch(e){case"string":return typeof c=="string";case"number":return typeof c=="number"&&Number.isFinite(c);case"boolean":return typeof c=="boolean";case"array":return Array.isArray(c);case"object":return typeof c=="object"&&c!==null&&!Array.isArray(c);case"string-or-number":return typeof c=="string"||typeof c=="number"&&Number.isFinite(c)}}function f(c,e,t){if(typeof c!="object"||c===null||Array.isArray(c))throw new h(502,`[${t}] expected object, got ${typeof c}`,"SCHEMA_MISMATCH");let s=c;for(let[i,r]of Object.entries(e)){if(!(i in s)){if(r.optional)continue;throw new h(502,`[${t}] missing required field "${i}"`,"SCHEMA_MISMATCH")}let o=s[i];if(!(r.optional&&o==null)&&!Te(o,r.type))throw new h(502,`[${t}] field "${i}" expected ${r.type}, got ${typeof o}`,"SCHEMA_MISMATCH")}return c}var pe="cb_guest_";function ke(c){let e=0;for(let t=0;t<c.length;t++){let s=c.charCodeAt(t);e=(e<<5)-e+s,e=e&e}return Math.abs(e).toString(36)}var H=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 f(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(){if(this.guestMemberLoginPromise)return this.guestMemberLoginPromise;this.guestMemberLoginPromise=this.executeGuestMemberLogin();try{return await this.guestMemberLoginPromise}finally{this.guestMemberLoginPromise=null}}async getMe(){let e=await this.http.get("/v1/public/app-members/me");return f(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 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=`${pe}default`;else{let t=ke(e);this.cachedGuestMemberTokenKey=`${pe}${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 i=this.http.getPersistenceStorage();i&&i.setItem(this.getGuestMemberTokenKey(),JSON.stringify({accessToken:e,refreshToken:t,memberId:s}))}};var L=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(),i={};t.name!==void 0&&(i.title=t.name),t.schema!==void 0&&(i.schema=t.schema),t.accessLevel!==void 0&&(i.access_level=t.accessLevel),t.description!==void 0&&(i.description=t.description),await this.http.patch(`${s}/tables/${e}`,i)}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??{},i=new Set(Array.isArray(s.$required)?s.$required:[]),r=[],n=0;for(let[o,l]of Object.entries(s)){if(o.startsWith("$")||l===void 0||Array.isArray(l))continue;let a="string",d=i.has(o),u,p,g;typeof l=="string"?a=l:(a=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&&(g=l.encrypted)),r.push({id:o,name:o,data_type:a,is_required:d,default_value:u,description:p,encrypted:g,order:n++,created_at:t.created_at})}return r}async createColumn(e,t){let s=this.getPublicPrefix();await this.http.post(`${s}/tables/${e}/columns`,t)}async updateColumn(e,t,s){let i=this.getPublicPrefix();await this.http.patch(`${i}/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 i=new URLSearchParams;t?.limit&&i.append("limit",t.limit.toString()),t?.offset&&i.append("offset",t.offset.toString());let r=i.toString(),n=r?`${s}/tables/${e}/data?${r}`:`${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 i=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(`${i}/tables/${e}/data`,t);let n=s?.autoCreate?"?auto_create=true":"";return this.http.post(`${i}/tables/name/${encodeURIComponent(e)}/data${n}`,t)}async updateData(e,t,s){let i=this.getPublicPrefix();return this.http.patch(`${i}/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(i=>i.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,i){let r=this.getPublicPrefix();return this.http.post(`${r}/search`,{table_id:e,query:t,fields:s,options:i})}async autocomplete(e,t,s,i){let r=this.getPublicPrefix();return this.http.post(`${r}/autocomplete`,{table_id:e,query:t,field:s,...i})}async geoQuery(e,t,s,i){let r=this.getPublicPrefix();return this.http.post(`${r}/geo`,{table_id:e,field:t,query:s,...i})}async batch(e){let t=this.getPublicPrefix();return this.http.post(`${t}/batch`,{operations:e})}async transaction(e,t){let s=this.getPublicPrefix();return this.http.post(`${s}/transactions`,{reads:e,writes:t})}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 i=`csub_${Date.now()}_${Math.random().toString(36).substring(2,9)}`;this.activeSubscriptions.set(i,{tableId:e,options:s,handlers:t});let r=this.sendSubscribeRequest(e,t,s);return r.catch(n=>{this.activeSubscriptions.delete(i),t.onError?.(n instanceof Error?n:new Error(String(n)))}),{subscriptionId:i,unsubscribe:()=>{this.activeSubscriptions.delete(i),r.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"&&r.then(l=>{let a={type:"snapshot_more",request_id:this.generateRequestId(),subscription_id:l,offset:n};o!==void 0&&(a.limit=o),this.sendRealtimeMessage(a)}).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((i,r)=>{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"),r(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(),i()},this.realtimeWs.onmessage=l=>{try{let a=JSON.parse(l.data);this.handleRealtimeMessage(a)}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),r(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"),r(n)}})}sendSubscribeRequest(e,t,s){let i=this.generateRequestId();this.realtimeHandlers.set(i,t);let r=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:i,table_id:e,doc_id:s?.docId,query:r,options:{include_self:s?.includeSelf??!1,include_metadata_changes:s?.includeMetadataChanges??!1}}),new Promise((n,o)=>{let l=setTimeout(()=>{this.pendingRequests.delete(i),this.realtimeHandlers.delete(i),o(new Error("Subscribe request timeout"))},3e4);this.pendingRequests.set(i,{resolve:a=>{let d=a,u=this.realtimeHandlers.get(i);u&&(this.realtimeHandlers.delete(i),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,i=e.subscription_id,r=this.pendingRequests.get(s);r&&(clearTimeout(r.timeout),r.resolve(i),this.pendingRequests.delete(s));break}case"snapshot":{let s=e.subscription_id,i=this.realtimeHandlers.get(s);if(i?.onSnapshot){let r=e.docs||[],n=e.has_more||!1,o=n?e.next_offset:void 0;i.onSnapshot(r,{totalCount:e.total_count||0,hasMore:n,nextOffset:o})}break}case"change":{let s=e.subscription_id,i=this.realtimeHandlers.get(s);if(i?.onChange){let r=e.changes||[];i.onChange(r)}break}case"presence":{let s=this.realtimeHandlers.get("__presence__");if(s?.onSnapshot){let i=e.states,r=Object.entries(i||{}).map(([n,o])=>({id:n,data:o,exists:!0}));s.onSnapshot(r,{totalCount:r.length,hasMore:!1})}break}case"error":{let s=e.request_id,i=e.message||"Unknown error";if(s){let r=this.pendingRequests.get(s);r&&(clearTimeout(r.timeout),r.reject(new Error(i)),this.pendingRequests.delete(s));let n=this.realtimeHandlers.get(s);n?.onError&&n.onError(new Error(i))}else this.realtimeOnError?.(new Error(i));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(i=>{this.debugLog(`Reconnect failed: ${i}`)})},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 re=new Set(["localhost","127.0.0.1","::1"]),ue=2048;function he(c,e={}){let t=e.context??"external URL";if(typeof c!="string"||c.length===0)throw new h(400,`${t}: empty URL`,"INVALID_PRESIGNED_URL");if(c.length>ue)throw new h(400,`${t}: URL exceeds ${ue} chars`,"INVALID_PRESIGNED_URL");let s;try{s=new URL(c)}catch{throw new h(400,`${t}: cannot parse URL`,"INVALID_PRESIGNED_URL")}let i=e.allowLocalhost&&re.has(s.hostname);if(s.protocol!=="https:"&&!(i&&s.protocol==="http:"))throw new h(400,`${t}: scheme must be https (got ${s.protocol})`,"INVALID_PRESIGNED_URL");if(!e.allowLocalhost&&re.has(s.hostname))throw new h(400,`${t}: localhost is not allowed in production`,"INVALID_PRESIGNED_URL");if(e.allowedHosts&&e.allowedHosts.length>0){let r=s.hostname;if(!e.allowedHosts.some(o=>o===r?!0:r.endsWith(`.${o}`)))throw new h(400,`${t}: host ${r} is not in allowlist`,"INVALID_PRESIGNED_URL")}return s}function ge(){if(typeof window>"u")return!1;let c=window.location.hostname;return re.has(c)}var O=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async uploadToPresigned(e,t,s){let i=he(e,{allowLocalhost:ge(),context:"storage.presigned-url"}),{signal:r,cleanup:n}=_({timeout:s?.timeout,signal:s?.signal});try{let o=await fetch(i.toString(),{method:"PUT",body:t,headers:{"Content-Type":t.type||"application/octet-stream"},signal:r});if(!o.ok)throw new h(o.status,`Upload failed: ${o.statusText}`,"PRESIGNED_UPLOAD_FAILED")}finally{n()}}async getFiles(e,t){let s=this.getPublicPrefix(),i=t?`?parent_id=${encodeURIComponent(t)}`:"";return(await this.http.get(`${s}/storages/files/${e}/items${i}`)).files}async uploadFile(e,t,s){let i=this.getPublicPrefix(),r=await this.http.post(`${i}/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(r.upload_url,t);let n=await this.http.post(`${i}/storages/files/${e}/complete-upload`,{file_id:r.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 i=[];for(let r of t){let n=await this.uploadFile(e,r,s);i.push(n)}return i}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,i){let r=this.getPublicPrefix(),n=t.startsWith("/")?t.slice(1):t,o=i?.overwrite!==!1,l=await this.http.post(`${r}/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 a=await this.http.post(`${r}/storages/files/${e}/complete-upload`,{file_id:l.file_id});return{id:a.id,name:a.name,path:a.path,type:a.type,mime_type:a.mime_type,size:a.size,url:a.url,parent_id:a.parent_id,created_at:a.created_at}}async getByPath(e,t){let s=this.getPublicPrefix(),i=t.startsWith("/")?t.slice(1):t;return this.http.get(`${s}/storages/files/${e}/path/${i}`)}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(),i=new URLSearchParams;t?.limit!=null&&i.set("limit",String(t.limit)),t?.offset!=null&&i.set("offset",String(t.offset));let r=i.toString();return this.http.get(`${s}/storages/webs/${e}/page-metas${r?`?${r}`:""}`)}async getPageMeta(e,t){let s=this.getPublicPrefix(),i=encodeURIComponent(t);return this.http.get(`${s}/storages/webs/${e}/page-metas/get?path=${i}`)}async deletePageMeta(e,t){let s=this.getPublicPrefix(),i=encodeURIComponent(t);await this.http.delete(`${s}/storages/webs/${e}/page-metas?path=${i}`)}async deleteAllPageMetas(e){let t=this.getPublicPrefix();await this.http.delete(`${t}/storages/webs/${e}/page-metas/all`)}};var B=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 G=class{constructor(e){this.http=e}getPublicPrefix(){return this.http.hasPublicKey()?"/v1/public":"/v1"}async invoke(e,t,s){let i=this.getPublicPrefix(),r={};return t!==void 0&&(r.payload=t),s!==void 0&&(r.timeout=s),this.http.post(`${i}/functions/${e}/invoke`,r)}async call(e,t){let s=await this.invoke(e,t);if(!s.success)throw new h(500,s.error||"Function execution failed","FUNCTION_EXECUTION_FAILED");return s.result}};var F=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(),i=await this.sendRequest({category:e,action:"subscribe",request_id:s}),r={category:i.category,persist:i.persist,historyCount:i.history_count,readReceipt:i.read_receipt},n=[];return this.subscriptions.set(e,{info:r,handlers:n}),{info:r,send:async(l,a)=>{await this.sendMessage(e,l,a)},getHistory:async l=>this.getHistory(e,l??t.historyLimit),unsubscribe:async()=>{await this.unsubscribe(e)},onMessage:l=>(n.push(l),()=>{let a=n.indexOf(l);a>-1&&n.splice(a,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 r=s.includeSelf!==!1,n=this.generateRequestId();await this.sendRequest({category:e,action:"send",data:{data:t,broadcast:r},request_id:n})}async getHistory(e,t){if(this.state!=="connected")throw new Error("Not connected");let s=this.generateRequestId(),i=await this.sendRequest({category:e,action:"history",data:t?{limit:t}:void 0,request_id:s});return{category:i.category,messages:i.messages.map(r=>({id:r.id,category:r.category,from:r.from,data:r.data,sentAt:r.sent_at})),total:i.total}}async stream(e,t,s={}){if(this.state!=="connected")throw new Error("Not connected. Call connect() first.");let i=this.generateRequestId(),r=s.sessionId||this.generateRequestId();this.streamSessions.set(r,{handlers:t,requestId:i});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:r,metadata:s.metadata,mcp_group:s.mcpGroup},request_id:i})}catch(n){throw this.streamSessions.delete(r),n}return{sessionId:r,stop:async()=>{await this.stopStream(r)}}}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}),i={};for(let[r,n]of Object.entries(s.users))i[r]={userId:n.user_id,status:n.status,lastSeen:n.last_seen,device:n.device,metadata:n.metadata};return{users:i}}async subscribePresence(e,t){if(this.state!=="connected")throw new Error("Not connected");if(!this.presenceSubscriptions.has(e)){let i=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:e},request_id:i}),this.presenceSubscriptions.set(e,[])}let s=this.presenceSubscriptions.get(e);return s.push(t),()=>{let i=s.indexOf(t);if(i>-1&&s.splice(i,1),s.length===0&&(this.presenceSubscriptions.delete(e),this.state==="connected")){let r=this.generateRequestId();this.sendRequest({category:"",action:"presence_unsubscribe",data:{user_id:e},request_id:r}).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 i=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:e},request_id:i}),this.typingHandlers.set(e,[])}let s=this.typingHandlers.get(e);return s.push(t),()=>{let i=s.indexOf(t);if(i>-1&&s.splice(i,1),s.length===0&&(this.typingHandlers.delete(e),this.state==="connected")){let r=this.generateRequestId();this.sendRequest({category:"",action:"typing_unsubscribe",data:{room_id:e},request_id:r}).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 i=s.indexOf(t);i>-1&&s.splice(i,1)}}async doConnect(){return new Promise((e,t)=>{this.state="connecting",this.notifyStateChange(),this.log("Connecting...");let s=this.socketUrl.replace(/^http/,"ws"),i;if(this.options.accessToken)i=`${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}i=`${s}/v1/realtime/auth?public_key=${encodeURIComponent(o)}&client_id=${this.clientId}`,this.log("Using API Key authentication")}this.userId&&(i+=`&user_id=${encodeURIComponent(this.userId)}`);let r=!1,n=setTimeout(()=>{r||(r=!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(i),this.ws.onopen=()=>{this.log("WebSocket opened, waiting for connected event...")},this.ws.onmessage=o=>{let l=o.data.split(`
|
|
2
2
|
`).filter(a=>a.trim());for(let a of l)try{let d=JSON.parse(a);this.handleServerMessage(d,()=>{r||(r=!0,clearTimeout(n),this.log("Connected successfully"),e())})}catch(d){this.logError("Failed to parse message",{line:a,error:d})}},this.ws.onclose=o=>{this.log(`WebSocket closed: code=${o.code}, reason=${o.reason}`),!r&&this.state==="connecting"&&(r=!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")),!r&&this.state==="connecting"&&(r=!0,clearTimeout(n),t(new Error("Failed to connect")))}}catch(o){r=!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,i=this.subscriptions.get(s.category);if(i){let r={id:s.id,category:s.category,from:s.from,data:s.data,sentAt:s.sent_at};i.handlers.forEach(n=>n(r))}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,i=this.streamSessions.get(s.session_id);i?.handlers.onToken&&i.handlers.onToken(s.token,s.index);break}case"stream_done":{let s=e.data,i=this.streamSessions.get(s.session_id);i?.handlers.onDone&&i.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,i=this.streamSessions.get(s.session_id);i?.handlers.onToolCall&&i.handlers.onToolCall(s.tool_name,s.arguments||{},s.index);break}case"stream_tool_result":{let s=e.data,i=this.streamSessions.get(s.session_id);i?.handlers.onToolResult&&i.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[i,r]of this.streamSessions)if(r.requestId===e.request_id){r.handlers.onError&&r.handlers.onError(new Error(s.message)),this.streamSessions.delete(i);break}}break}case"presence":case"presence_status":{let s=e.data,i={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(i));let r=this.presenceSubscriptions.get(s.user_id);if(r&&r.forEach(n=>n(i)),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,i={roomId:s.room_id,users:s.users},r=this.typingHandlers.get(s.room_id);r&&r.forEach(n=>n(i));break}case"read_receipt":{let s=e.data,i={category:s.category,messageIds:s.message_ids,readerId:s.reader_id,readAt:s.read_at},r=this.readReceiptHandlers.get(s.category);r&&r.forEach(n=>n(i));break}}}handleDisconnect(){this.ws=null,this._connectionId=null,this.streamSessions.forEach(i=>{i.handlers.onError&&i.handlers.onError(new Error("Connection lost"))}),this.streamSessions.clear();let e=new Map;for(let[i,r]of this.subscriptions)e.set(i,[...r.handlers]);this.subscriptions.clear();let t=new Map;for(let[i,r]of this.presenceSubscriptions)t.set(i,[...r]);this.presenceSubscriptions.clear();let s=new Map;for(let[i,r]of this.typingHandlers)s.set(i,[...r]);if(this.typingHandlers.clear(),this.retryCount<this.options.maxRetries){this.state="reconnecting",this.notifyStateChange(),this.retryCount++;let i=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(r){this.logError("Reconnect failed",r)}},i)}else this.state="disconnected",this.notifyStateChange(),this.notifyError(new Error("Connection lost. Max retries exceeded."))}async restoreSubscriptions(e,t,s){for(let[i,r]of e)try{this.log(`Restoring subscription: ${i}`);let n=this.generateRequestId(),o=await this.sendRequest({category:i,action:"subscribe",request_id:n}),l={category:o.category,persist:o.persist,historyCount:o.history_count,readReceipt:o.read_receipt};this.subscriptions.set(i,{info:l,handlers:r}),this.log(`Restored subscription: ${i}`)}catch(n){this.logError(`Failed to restore subscription for ${i}`,n),this.notifyError(new Error(`Failed to restore subscription: ${i}`))}for(let[i,r]of t)try{this.log(`Restoring presence subscription: ${i}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"presence_subscribe",data:{user_id:i},request_id:n}),this.presenceSubscriptions.set(i,r),this.log(`Restored presence subscription: ${i}`)}catch(n){this.logError(`Failed to restore presence subscription for ${i}`,n)}for(let[i,r]of s)try{this.log(`Restoring typing subscription: ${i}`);let n=this.generateRequestId();await this.sendRequest({category:"",action:"typing_subscribe",data:{room_id:i},request_id:n}),this.typingHandlers.set(i,r),this.log(`Restored typing subscription: ${i}`)}catch(n){this.logError(`Failed to restore typing subscription for ${i}`,n)}}sendRequest(e){return new Promise((t,s)=>{if(!this.ws||this.ws.readyState!==WebSocket.OPEN){s(new Error("Not connected"));return}let i=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:i}),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 N=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 i=setTimeout(()=>{this.state==="connecting"&&(this.ws?.close(),t(new Error("\uC5F0\uACB0 \uC2DC\uAC04 \uCD08\uACFC")))},1e4);this.ws.onopen=()=>{clearTimeout(i),this.reconnectAttempts=0,this.sendSignaling({type:"join",room_id:this.currentRoomId,data:{user_id:this.currentUserId,is_broadcaster:this.isBroadcaster}})},this.ws.onmessage=async r=>{try{let n=JSON.parse(r.data);await this.handleSignalingMessage(n,e,t)}catch(n){console.error("Failed to parse signaling message:",n)}},this.ws.onerror=r=>{clearTimeout(i),console.error("WebSocket error:",r),this.emitError(new Error("WebSocket \uC5F0\uACB0 \uC624\uB958"))},this.ws.onclose=r=>{clearTimeout(i),this.state==="connecting"&&t(new Error("\uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4")),this.handleDisconnect(r)}})}buildWebSocketUrl(){let e=this.webrtcUrl.replace("https://","wss://").replace("http://","ws://"),t=this.http.getPublicKey(),s=this.http.getAccessToken(),i="";if(s?i=`access_token=${encodeURIComponent(s)}`:t&&(i=`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?${i}`}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 i=typeof e.data=="string"?e.data:"Unknown error",r=new Error(i);this.emitError(r),s?.(r);break}}async createPeerConnection(e,t){this.closePeerConnection(e);let s={iceServers:this.iceServers.map(r=>({urls:r.urls,username:r.username,credential:r.credential}))},i=new RTCPeerConnection(s);if(this.peerConnections.set(e,i),this.localStream&&this.localStream.getTracks().forEach(r=>{i.addTrack(r,this.localStream)}),i.onicecandidate=r=>{r.candidate&&this.sendSignaling({type:"ice_candidate",target_id:e,candidate:r.candidate.toJSON()})},i.ontrack=r=>{let[n]=r.streams;n&&(this.remoteStreams.set(e,n),this.emitRemoteStream(e,n))},i.onconnectionstatechange=()=>{i.connectionState==="failed"&&(console.warn(`Peer connection failed: ${e}`),this.closePeerConnection(e))},t){let r=await i.createOffer();await i.setLocalDescription(r),this.sendSignaling({type:"offer",target_id:e,sdp:r.sdp})}return i}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 i=await s.createAnswer();await s.setLocalDescription(i),this.sendSignaling({type:"answer",target_id:e,sdp:i.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(i){console.warn("Failed to add ICE candidate:",i)}}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,i)=>{s.close(),this.emitPeerLeft(i)}),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(i=>{let r=s.find(n=>n.track?.kind===i.kind);r?r.replaceTrack(i):t.addTrack(i,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 W=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,i,r)=>(this.handleGlobalError(e,t,s,i,r),this.originalOnError?this.originalOnError(e,t,s,i,r):!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,i,r){let n={message:typeof e=="string"?e:e.type||"Unknown error",source:t||void 0,lineno:s||void 0,colno:i||void 0,stack:r?.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",i;t instanceof Error?(s=t.message,i=t.stack):typeof t=="string"?s=t:t&&typeof t=="object"&&(s=JSON.stringify(t));let r={message:s,stack:i,error_type:"unhandledrejection",url:typeof window<"u"?window.location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0};this.queueError(r)}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 i=this.config.beforeSend(s);return i===!1||i===null?(this.log("Error filtered out by beforeSend"),null):i}queueError(e){this.errorQueue.push(e),this.log("Error queued",{message:e.message,queueSize:this.errorQueue.length}),this.errorQueue.length>=this.config.maxBatchSize&&this.flushQueue()}startBatchTimer(){this.batchTimer||(this.batchTimer=setInterval(()=>{this.flushQueue()},this.config.batchInterval))}stopBatchTimer(){this.batchTimer&&(clearInterval(this.batchTimer),this.batchTimer=null)}async flushQueue(){if(!this.storageWebId||this.errorQueue.length===0)return;let e=[...this.errorQueue];this.errorQueue=[];try{e.length===1?await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/report`,e[0]):await this.http.post(`/v1/public/storages/web/${this.storageWebId}/errors/batch`,{errors:e,user_agent:typeof navigator<"u"?navigator.userAgent:void 0}),this.log("Errors sent",{count:e.length})}catch(t){let s=this.config.maxBatchSize-this.errorQueue.length;s>0&&this.errorQueue.unshift(...e.slice(0,s)),this.log("Failed to send errors, re-queued",{error:t})}}};var K=class{constructor(e){this.http=e}async getEnabledProviders(){return this.http.get("/v1/public/oauth/providers")}async signIn(e,t,s){let i=new URLSearchParams({app_callback:t});s&&i.append("state",s);let r=await this.http.get(`/v1/public/oauth/${e}/authorize/central?${i.toString()}`);window.location.href=r.authorization_url}async signInWithPopup(e,t){let s=new URLSearchParams;t&&s.set("app_callback",t);let i=s.toString(),r=await this.http.get(`/v1/public/oauth/${e}/authorize/central${i?"?"+i:""}`),n=500,o=600,l=window.screenX+(window.outerWidth-n)/2,a=window.screenY+(window.outerHeight-o)/2,d=window.open(r.authorization_url,"oauth-popup",`width=${n},height=${o},left=${l},top=${a}`);if(!d)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((u,p)=>{let g=!1,y=()=>{g=!0,window.removeEventListener("message",v),clearInterval(b),clearTimeout(S)},v=async m=>{if(m.data?.type!=="oauth-callback"||g)return;if(y(),m.data.error){p(new Error(m.data.error));return}let w=m.data.email,R={member_id:m.data.member_id,access_token:m.data.access_token,refresh_token:m.data.refresh_token,is_new_member:m.data.is_new_member==="true"||m.data.is_new_member===!0,...typeof w=="string"&&w.length>0?{email:w}:{}};this.http.setTokens(R.access_token,R.refresh_token),u(R)};window.addEventListener("message",v);let b=setInterval(()=>{try{d.closed&&(g||(y(),p(new Error("\uB85C\uADF8\uC778\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))))}catch{clearInterval(b)}},500),S=setTimeout(()=>{if(!g){y();try{d.close()}catch{}p(new Error("\uB85C\uADF8\uC778 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."))}},18e4)})}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"),i=e.get("refresh_token"),r=e.get("member_id");if(!s||!i||!r)return null;let n=e.get("email"),o={access_token:s,refresh_token:i,member_id:r,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,i),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}),i={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",...i},"*"),window.close(),i):(this.http.setTokens(i.access_token,i.refresh_token),i)}};var V=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 f(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 j=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 f(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 i=s.toString();return this.http.get(`${t}/subscriptions${i?"?"+i:""}`)}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(),i=new URLSearchParams;t?.status&&i.set("status",t.status),t?.limit&&i.set("limit",String(t.limit)),t?.offset&&i.set("offset",String(t.offset));let r=i.toString();return this.http.get(`${s}/subscriptions/${e}/payments${r?"?"+r:""}`)}async chargeWithBillingKey(e){let t=this.getPublicPrefix();return this.http.post(`${t}/subscriptions/charge`,e)}};var z=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 f(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(),i={topic_name:t};await this.http.post(`${s}/push/devices/${e}/topics/subscribe`,i)}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 r=e.toJSON();s={endpoint:r.endpoint||"",expirationTime:r.expirationTime,keys:{p256dh:r.keys?.p256dh||"",auth:r.keys?.auth||""}}}else s=e;let i={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`,{...i,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 i={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`,i)}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 me=5*1024*1024,T=class extends Error{constructor(e,t){super(e),this.name="VideoProcessingError",this.video=t}},J=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 i=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}),r=i.chunk_size||me,n=Math.ceil(t.size/r),o=0,l=Date.now(),a=0;for(let u=0;u<n;u++){let p=u*r,g=Math.min(p+r,t.size),y=t.slice(p,g),v=new FormData;v.append("chunk",y),v.append("chunk_index",String(u)),await this.http.post(`/v1/public/storages/videos/${e}/uploads/${i.session_id}/chunk`,v),o++;let b=Date.now(),S=(b-l)/1e3,m=g,w=m-a,R=S>0?w/S:0;l=b,a=m,s.onProgress&&s.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:R})}return(await this.http.post(`/v1/public/storages/videos/${e}/uploads/${i.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 i=s.toString();return this.http.get(`/v1/public/storages/videos/${e}/videos${i?`?${i}`:""}`)},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 i={},r=this.http.getPublicKey();r&&(i["X-Public-Key"]=r);let n=this.http.getAccessToken();n&&(i.Authorization=`Bearer ${n}`),s&&!(s instanceof FormData)&&(i["Content-Type"]="application/json");let{signal:o,cleanup:l}=_({timeout:3e4});try{let a=await fetch(`${this.videoBaseUrl}${t}`,{method:e,headers:i,body:s instanceof FormData?s:s?JSON.stringify(s):void 0,signal:o});if(!a.ok){let d=await a.json().catch(()=>({message:a.statusText})),u=d.error;if(u&&typeof u=="object"&&"message"in u)throw new h(a.status,u.message||"Unknown error",u.code,u.details);let p=typeof u=="string"?u:d.message||"Unknown error";throw new h(a.status,p)}return a.status===204||a.headers.get("content-length")==="0"?{}:await a.json()}finally{l()}}async upload(e,t){let s=this.getPublicPrefix(),i=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}),r=i.chunk_size||me,n=Math.ceil(e.size/r),o=0,a=Date.now(),d=0;for(let p=0;p<n;p++){let g=p*r,y=Math.min(g+r,e.size),v=e.slice(g,y),b=new FormData;b.append("chunk",v),b.append("chunk_index",String(p)),await this.videoFetch("POST",`${s}/uploads/${i.session_id}/chunks`,b),o++;let S=Date.now(),m=(S-a)/1e3,w=y,R=w-d,ye=m>0?R/m:0;a=S,d=w,t.onProgress&&t.onProgress({phase:"uploading",uploadedChunks:o,totalChunks:n,percentage:Math.round(o/n*100),currentSpeed:ye})}return(await this.videoFetch("POST",`${s}/uploads/${i.session_id}/complete`,{})).video}async waitForReady(e,t){let s=t?.timeout||18e5,i=t?.interval||5e3,r=Date.now(),n=this.getPublicPrefix();for(;Date.now()-r<s;){let o=await this.videoFetch("GET",`${n}/videos/${e}`);if(o.status==="ready")return o;if(o.status==="failed")throw new T("Video processing failed",o);if(t?.onProgress){let l=o.qualities.filter(d=>d.status==="ready").length,a=o.qualities.length||1;t.onProgress({phase:"processing",uploadedChunks:0,totalChunks:0,percentage:Math.round(l/a*100)})}await new Promise(l=>setTimeout(l,i))}throw new T("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 i=s.toString();return this.videoFetch("GET",`${t}/videos${i?`?${i}`:""}`)}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(),i=t?`?quality=${t}`:"";return this.videoFetch("GET",`${s}/videos/${e}/stream-url${i}`)}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 i=this.getPublicPrefix();return this.videoFetch("POST",`${i}/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 i=s.toString();return this.videoFetch("GET",`${t}/shorts${i?`?${i}`:""}`)}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(),i=new URLSearchParams;t?.cursor&&i.set("cursor",t.cursor),t?.limit&&i.set("limit",String(t.limit)),t?.sort&&i.set("sort",t.sort);let r=i.toString();return this.videoFetch("GET",`${s}/videos/${e}/comments${r?`?${r}`:""}`)}async postComment(e,t,s){let i=this.getPublicPrefix();return this.videoFetch("POST",`${i}/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 i=s.toString();return this.videoFetch("GET",`${t}/watch-history${i?`?${i}`:""}`)}async clearWatchHistory(){let e=this.getPublicPrefix();await this.videoFetch("DELETE",`${e}/watch-history`)}async reportWatchProgress(e,t,s){let i=this.getPublicPrefix();await this.videoFetch("POST",`${i}/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,i){let r=this.getPublicPrefix();return this.videoFetch("POST",`${r}/videos/${e}/super-chats`,{amount:t,message:s,currency:i||"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(),i=t?`?limit=${t}`:"";return(await this.videoFetch("GET",`${s}/recommendations/related/${e}${i}`)).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 k=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,i;return typeof e=="string"?(s=e,i=t??{}):(s=this.resolveAppId(),i=e),this.http.patch(`/v1/apps/${s}/game/config`,i)}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}};var fe=()=>{if(typeof window<"u"){let c=window.location.hostname;if(c==="localhost"||c==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"},M=class{constructor(e){this.ws=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this.msgIdCounter=0;this.config={gameServerUrl:fe(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,...e}}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}on(e,t){return this.handlers[e]=t,this}connect(e){return new Promise((t,s)=>{if(this.ws?.readyState===WebSocket.OPEN){t();return}let i=this.buildConnectionUrl(e);this.ws=new WebSocket(i);let r=()=>{this._isConnected=!0,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),t()},n=a=>{this._isConnected=!1,this.stopPingInterval(),this.handlers.onDisconnect?.(a),this.config.autoReconnect&&a.code!==1e3&&this.scheduleReconnect(e)},o=a=>{this.handlers.onError?.(a),s(new Error("WebSocket connection failed"))},l=a=>{this.handleMessage(a.data)};this.ws.addEventListener("open",r,{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}createRoom(e={}){return new Promise((t,s)=>{let i=r=>{if(r.type==="room_created"){let n=r.data;return this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state),!0}else if(r.type==="error")return s(new Error(r.data.message)),!0;return!1};this.sendWithHandler("create_room",e,i,15e3,s)})}joinRoom(e,t){return new Promise((s,i)=>{let r=n=>{if(n.type==="room_joined"){let o=n.data;return this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state),!0}else if(n.type==="error")return i(new Error(n.data.message)),!0;return!1};this.sendWithHandler("join_room",{room_id:e,metadata:t},r,15e3,i)})}leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>i.type==="room_left"?(this._roomId=null,this._state=null,e(),!0):i.type==="error"?(t(new Error(i.data.message)),!0):!1;this.sendWithHandler("leave_room",{},s,15e3,t)})}sendAction(e){if(!this._roomId)throw new Error("Not in a room");this.send("action",{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++})}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>{if(i.type==="state"){let r=i.data;return this._state=r,e(r),!0}else if(i.type==="error")return t(new Error(i.data.message)),!0;return!1};this.sendWithHandler("get_state",{},s,15e3,t)})}listRooms(){return new Promise((e,t)=>{let s=i=>{if(i.type==="room_list"){let r=i.data;return e(r.rooms),!0}else if(i.type==="error")return t(new Error(i.data.message)),!0;return!1};this.sendWithHandler("list_rooms",{},s,15e3,t)})}ping(){return new Promise((e,t)=>{let s=Date.now(),i=r=>{if(r.type==="pong"){let n=r.data,o=Date.now()-n.clientTimestamp;return this.handlers.onPong?.(n),e(o),!0}else if(r.type==="error")return t(new Error(r.data.message)),!0;return!1};this.sendWithHandler("ping",{timestamp:s},i,15e3,t)})}buildConnectionUrl(e){let s=this.config.gameServerUrl.replace(/^http/,"ws"),i=new URLSearchParams;i.set("client_id",this.config.clientId),e&&i.set("room_id",e),this.config.publicKey&&i.set("public_key",this.config.publicKey),this.config.accessToken&&i.set("token",this.config.accessToken);let r=this.config.appId||"";return`${s}/v1/game/${r}/ws?${i.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,i=15e3,r){let n=`${e}-${++this.msgIdCounter}`,o=null,l=()=>{this.ws?.removeEventListener("message",a),o&&(clearTimeout(o),o=null)},a=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",a),o=setTimeout(()=>{l();let d=new Error(`Request '${e}' timed out after ${i}ms`);r?.(d),this.handlers.onError?.({code:"TIMEOUT",message:d.message})},i);try{this.send(e,t,n)}catch(d){l();let u=d instanceof Error?d:new Error(String(d));r?.(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?.({code:t.code||"UNKNOWN",message:t.message||"Unknown error"});break;default: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(i=>({path:i.path,operation:i.operation,value:i.value,oldValue:i.old_value})),tick:t.tick};if(this._state){for(let i of s.changes)this.applyChange(i);this._state.version=s.toVersion}if(this.handlers.onAction){for(let i of s.changes)if(i.path.startsWith("actions.")&&i.operation==="set"&&i.value){let r=i.value;this.handlers.onAction({type:r.type||"",clientId:r.client_id||"",data:r.data,timestamp:r.timestamp||0})}}this.handlers.onDelta?.(s)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let r=0;r<t.length-1;r++){let n=t[r];n in s||(s[n]={}),s=s[n]}let i=t[t.length-1];e.operation==="delete"?delete s[i]:s[i]=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?.({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(i){console.error(`Failed to rejoin room ${s}:`,i),this.handlers.onError?.({code:"REJOIN_FAILED",message:`Failed to rejoin room: ${i}`})}}}catch{}},t)}startPingInterval(){this.pingInterval=setInterval(()=>{this.ping().catch(()=>{})},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}},I=class{constructor(e,t,s){this.http=e,this.gameServerUrl=t||fe().replace(/^ws/,"http"),this.appId=s,this.config=new k(e,s)}createClient(e){return new M({...e,gameServerUrl:this.gameServerUrl.replace(/^http/,"ws"),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 h(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 h(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||"",i=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(!i.ok)throw new Error(`Failed to join spectator: ${i.statusText}`);return i.json()}async leaveSpectator(e,t){let s=this.appId||"",i=await fetch(`${this.gameServerUrl}/v1/game/${s}/rooms/${e}/spectators/${t}`,{method:"DELETE",headers:this.getHeaders()});if(!i.ok)throw new Error(`Failed to leave spectator: ${i.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||"",i=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(!i.ok)throw new Error(`Failed to join voice channel: ${i.statusText}`);return i.json()}async leaveVoiceChannel(e,t){let s=this.appId||"",i=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(!i.ok)throw new Error(`Failed to leave voice channel: ${i.statusText}`)}async setMute(e,t){let s=this.appId||"",i=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(!i.ok)throw new Error(`Failed to set mute: ${i.statusText}`)}async listReplays(e){let t=this.appId||"",s=`${this.gameServerUrl}/v1/game/${t}/replays`;e&&(s+=`?room_id=${e}`);let i=await fetch(s,{headers:this.getHeaders()});if(i.status===404)throw new Error("cb.game.listReplays: replay storage is not configured on this server (REPLAY_STORAGE_PATH unset).");if(!i.ok)throw new Error(`Failed to list replays: ${i.statusText}`);return(await i.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 enqueueMatch(e,t,s,i,r){return this.http.post(`/v1/game/${e}/matchqueue/${t}/tickets`,{ticket_id:s,attributes:i,ttl_sec:r??0})}async listMatchqueue(e,t){return this.http.get(`/v1/game/${e}/matchqueue/${t}`)}async cancelMatch(e,t,s){await this.http.delete(`/v1/game/${e}/matchqueue/${t}/tickets/${s}`)}async submitScore(e,t,s,i,r="set"){return this.http.post(`/v1/game/${e}/leaderboards/${t}/scores`,{member:s,score:i,mode:r})}async getTopScores(e,t,s=10){return this.http.get(`/v1/game/${e}/leaderboards/${t}/top?n=${s}`)}async getMemberRank(e,t,s){return this.http.get(`/v1/game/${e}/leaderboards/${t}/members/${s}`)}async getRankAround(e,t,s,i=5,r=5){return this.http.get(`/v1/game/${e}/leaderboards/${t}/around/${s}?above=${i}&below=${r}`)}async resetLeaderboard(e,t){await this.http.delete(`/v1/game/${e}/leaderboards/${t}`)}async removeFromLeaderboard(e,t,s){await this.http.delete(`/v1/game/${e}/leaderboards/${t}/members/${s}`)}async uploadScript(e,t,s){return this.http.post(`/v1/game/${e}/scripts`,{name:t,code:s})}async listScripts(e){return this.http.get(`/v1/game/${e}/scripts`)}async getScript(e,t){return this.http.get(`/v1/game/${e}/scripts/${t}`)}async listScriptVersions(e,t){return this.http.get(`/v1/game/${e}/scripts/${t}/versions`)}async activateScript(e,t,s){return this.http.post(`/v1/game/${e}/scripts/${t}/activate`,{version:s??0})}async rollbackScript(e,t){return this.http.post(`/v1/game/${e}/scripts/${t}/rollback`,{})}async disableScript(e,t){await this.http.delete(`/v1/game/${e}/scripts/${t}`)}};var C=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(),i=new URLSearchParams;e&&i.set("start",e),t&&i.set("end",t);let r=i.toString();return this.http.get(`${s}/ads/reports${r?`?${r}`:""}`)}async getReportSummary(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/reports/summary`)}async getAdMobReport(e,t){let s=this.getPublicPrefix(),i=new URLSearchParams;e&&i.set("start",e),t&&i.set("end",t);let r=i.toString();return this.http.get(`${s}/ads/admob/reports${r?`?${r}`:""}`)}async getAdMobReportSummary(){let e=this.getPublicPrefix();return this.http.get(`${e}/ads/admob/reports/summary`)}};var E=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(i=>new File([],i.split("/").pop()||"file"))}return new Promise(s=>{let i=document.createElement("input");i.type="file",e?.accept&&(i.accept=e.accept),e?.multiple&&(i.multiple=!0),i.onchange=()=>{s(i.files?Array.from(i.files):null)},i.click()})},saveFile:async(e,t,s)=>{let i=this.getPlatform();if(i==="desktop"&&window.NativeBridge?.filesystem){let l=await window.NativeBridge.filesystem.showSaveDialog?.({defaultPath:t,filters:s?.filters});if(l?.canceled||!l?.filePath)return!1;let a=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(l.filePath,a)).success}if(i==="mobile"&&window.NativeBridge?.filesystem){let l=e instanceof Blob?await e.text():e;return(await window.NativeBridge.filesystem.writeFile(t,l)).success}let r=e instanceof Blob?e:new Blob([e],{type:"text/plain"}),n=URL.createObjectURL(r),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 i=document.createElement("input");i.type="file",i.accept="image/*",i.capture="environment",i.onchange=async()=>{let r=i.files?.[0];if(!r){s(null);return}let n=new FileReader;n.onload=()=>{let o=new Image;o.onload=()=>{s({uri:URL.createObjectURL(r),base64:e?.base64?n.result.split(",")[1]:void 0,width:o.width,height:o.height})},o.src=n.result},n.readAsDataURL(r)},i.click()}),pickImage:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.camera?window.NativeBridge.camera.pickImage(e):new Promise(s=>{let i=document.createElement("input");i.type="file",i.accept="image/*",e?.multiple&&(i.multiple=!0),i.onchange=async()=>{let r=i.files;if(!r?.length){s(null);return}let n=[];for(let o of Array.from(r)){let l=await new Promise(a=>{let d=new FileReader;d.onload=()=>{let u=new Image;u.onload=()=>{a({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)},i.click()})};this.location={getCurrentPosition:async e=>this.getPlatform()==="mobile"&&window.NativeBridge?.location?window.NativeBridge.location.getCurrentPosition(e):new Promise((s,i)=>{navigator.geolocation.getCurrentPosition(r=>{s({latitude:r.coords.latitude,longitude:r.coords.longitude,altitude:r.coords.altitude,accuracy:r.coords.accuracy,timestamp:r.timestamp})},i,{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}};var Q=class{constructor(e){this.http=e}async addDocument(e,t){return this.http.post(`/v1/public/knowledge-bases/${e}/documents`,t)}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 i=new URLSearchParams({query:t});return s&&i.append("top_k",String(s)),this.http.get(`/v1/public/knowledge-bases/${e}/search?${i.toString()}`)}};var x=class{constructor(e){this.http=e}async chat(e){return this.http.post("/v1/public/ai/chat",e)}async chatStream(e,t){let s=await this.http.fetchRaw("/v1/public/ai/chat/stream",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!s.ok){let o=await s.json().catch(()=>({error:"Stream request failed"}));t.onError?.(o.error||"Stream request failed");return}let i=s.body?.getReader();if(!i){t.onError?.("ReadableStream not supported");return}let r=new TextDecoder,n="";for(;;){let{done:o,value:l}=await i.read();if(o)break;n+=r.decode(l,{stream:!0});let a=n.split(`
|
|
3
|
-
`);n=a.pop()||"";for(let d of a){if(!d.startsWith("data: "))continue;let u=d.slice(6).trim();if(u==="[DONE]"){t.onDone?.();return}try{let p=JSON.parse(u);if(p.type==="sources"&&p.sources){t.onSources?.(p.sources);continue}if(p.type==="tool_start"||p.type==="tool_end"){t.onToolEvent?.({type:p.type,name:p.name,toolCallId:p.toolCallId,arguments:p.arguments,result:p.result,success:p.success,durationMs:p.durationMs});continue}if(p.type==="heartbeat"||p.type==="searching")continue;if(p.content&&t.onToken?.(p.content),p.done){t.onDone?.();return}}catch{}}}}};var $=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),i=new Headers(t.headers??{});if(!i.has("X-Public-Key")){let r=this.http.getPublicKey();if(!r)throw new Error("EndpointAPI.call: publicKey not configured. Pass `publicKey` to ConnectBase constructor.");i.set("X-Public-Key",r)}return fetch(s,{method:t.method??"GET",headers:i,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 i=this.http.getBaseUrl().replace(/\/+$/,""),r=`${i}/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(r,{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:a}=await l.json();if(!a)throw new Error("EndpointAPI.connectWebSocket: server returned empty ticket");let d=new URL(`${i}/v1/proxy/${encodeURIComponent(e)}${s}`);if(d.protocol=d.protocol==="https:"?"wss:":"ws:",t.query)for(let[p,g]of Object.entries(t.query))d.searchParams.set(p,g);d.searchParams.set("ticket",a);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,i={}){let r=i.intervalMs??1500,n=i.timeoutMs??5*6e4,o=i.parse??"json",l=Date.now();for(;;){if(i.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 a;try{a=await this.call(e,{...t,signal:i.signal})}catch(p){if(p.name==="AbortError")throw p;await ne(r,i.signal);continue}if(a.status>=500){await Ie(a),await ne(r,i.signal);continue}if(a.status>=400){let p=await a.text().catch(()=>"");throw new Error(`EndpointAPI.pollUntil: ${a.status} ${a.statusText} (label=${e}, path=${t.path})${p?` \u2014 ${p.slice(0,200)}`:""}`)}let d;o==="text"?d=await a.text():o==="none"?d=void 0:d=await a.json().catch(()=>{});let u=await s(d,a);if(u!==void 0)return u;await ne(r,i.signal)}}};async function ne(c,e){if(e?.aborted)throw new DOMException("aborted","AbortError");return new Promise((t,s)=>{let i=setTimeout(()=>{e?.removeEventListener("abort",r),t()},c),r=()=>{clearTimeout(i),s(new DOMException("aborted","AbortError"))};e?.addEventListener("abort",r,{once:!0})})}async function Ie(c){try{await c.text()}catch{}}var X=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 i=s.toString(),r=await this.http.get(`/v1/public/queues/${e}/messages${i?`?${i}`:""}`);return f(r,{messages:{type:"array"}},"queue.consume"),r}async ack(e,t,s){let i={message_ids:t,ack_token:s};return this.http.post(`/v1/public/queues/${e}/messages/ack`,i)}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 Ce=1800*1e3,oe="__cb_session",ae="__cb_visitor_uid",Y="__cb_last_activity";function A(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,c=>{let e=Math.random()*16|0;return(c==="x"?e:e&3|8).toString(16)})}var q=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(Y,String(this._lastActivity))}catch{}}reset(){this._sessionId=null,this._isNewSession=!1;try{typeof sessionStorage<"u"&&(sessionStorage.removeItem(oe),sessionStorage.removeItem(Y))}catch{}}regenerateVisitorUid(){let e=A();this._visitorUid=e;try{typeof localStorage<"u"&&localStorage.setItem(ae,e)}catch{}return this.reset(),e}ensureSession(){let e=Date.now();if(!this._sessionId)try{if(typeof sessionStorage<"u"){this._sessionId=sessionStorage.getItem(oe);let t=sessionStorage.getItem(Y);this._lastActivity=t?parseInt(t,10):0}}catch{}if(!this._sessionId||this._lastActivity>0&&e-this._lastActivity>Ce){this._sessionId=A(),this._isNewSession=!0,this._lastActivity=e;try{typeof sessionStorage<"u"&&(sessionStorage.setItem(oe,this._sessionId),sessionStorage.setItem(Y,String(e)))}catch{}}}loadOrCreateVisitorUid(){try{if(typeof localStorage<"u"){let e=localStorage.getItem(ae);if(e)return e;let t=A();return localStorage.setItem(ae,t),t}}catch{}return A()}};function be(c){if(!c)return"";let e=new URLSearchParams;c.start_date!==void 0&&e.set("start_date",String(c.start_date)),c.end_date!==void 0&&e.set("end_date",String(c.end_date)),c.limit!==void 0&&e.set("limit",String(c.limit));let t=e.toString();return t?`?${t}`:""}function Ee(){if(typeof window>"u")return{};try{let c=new URLSearchParams(window.location.search),e={};for(let t of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let s=c.get(t);s&&(e[t]=s)}return e}catch{return{}}}var Z=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 q}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=Ee(),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(i=>{i&&(this.log("link-member response",i),!t&&i.success===!1&&i.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(i=>{this.log("link-member silent fail",i)})}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 i=null,r=0;this.heatmapScrollHandler=()=>{let n=Math.round((window.scrollY+window.innerHeight)/document.documentElement.scrollHeight*100);r=Math.max(r,n),i&&clearTimeout(i),i=setTimeout(()=>{this.recordHeatmapEvent("scroll",50,r*(window.innerHeight/100),r)},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"),i=be(t);return this.http.get(`/v1/storages/web/${s}/popular-pages${i}`)}async getNavigationFlow(e,t){let s=this.requireServerSideStorageId(e,"getNavigationFlow"),i=be(t);return this.http.get(`/v1/storages/web/${s}/navigation/flow${i}`)}async getVisitors(e,t){let s=this.requireServerSideStorageId(e,"getVisitors"),i=new URLSearchParams;t?.limit!==void 0&&i.set("limit",String(t.limit)),t?.offset!==void 0&&i.set("offset",String(t.offset)),t?.sort_by&&i.set("sort_by",t.sort_by);let r=i.toString()?`?${i.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitors${r}`)}async getVisitorGroups(e,t){let s=this.requireServerSideStorageId(e,"getVisitorGroups"),i=new URLSearchParams;t?.limit!==void 0&&i.set("limit",String(t.limit)),t?.offset!==void 0&&i.set("offset",String(t.offset)),t?.sort_by&&i.set("sort_by",t.sort_by);let r=i.toString()?`?${i.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitor-groups${r}`)}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:A(),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",i=`${this.http.getBaseUrl()}${t}/storages/web/${this.storageWebId}/visitors/batch`,r=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:r,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(i,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`,i=JSON.stringify({visitor_uid:this.session.visitorUid,session_id:this.session.sessionId});try{navigator.sendBeacon(s,new Blob([i],{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,i){if(!this.canTrack()||!this.storageWebId)return;let r=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:i,session_id:this.session.sessionId}),this.heatmapQueue.length>=50){let n=this.heatmapQueue.splice(0);this.http.post(`${r}/storages/web/${this.storageWebId}/heatmap/batch`,{visitor_uid:this.session.visitorUid,events:n}).catch(()=>{})}}log(...e){this.config.debug&&console.log("[Analytics]",...e)}};var xe="$auth.member_id";var ce=class{constructor(e,t,s,i){this.type="webtransport";this.transport=null;this.writer=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=i}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:i,done:r}=await t.read();if(r)break;let n=new Uint8Array(s.length+i.length);for(n.set(s),n.set(i,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 Error("Not connected");let s=typeof e=="string"?new TextEncoder().encode(e):e;if(t){if(this.writer){let i=new Uint8Array(4);new DataView(i.buffer).setUint32(0,s.length,!0);let r=new Uint8Array(4+s.length);r.set(i),r.set(s,4),this.writer.write(r)}}else{let i=this.config.maxDatagramSize||1200;s.length<=i?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}},ee=class{constructor(e,t,s,i){this.type="websocket";this.ws=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=i}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 i=()=>{e()},r=()=>{this.onClose()},n=l=>{let a=new Error("WebSocket error");this.onError(a),t(a)},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",i,{once:!0}),this.ws.addEventListener("close",r),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 i=this.config.appId||"";return`${t}/v1/game/${i}/ws?${s.toString()}`}disconnect(){this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null)}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function le(){return typeof WebTransport<"u"}var te=class{constructor(e){this.transport=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this._connectionStatus="disconnected";this._lastError=null;this._latency=0;this._transportType="websocket";this.decoder=new TextDecoder;this.pendingHandlers=new Map;this.messageId=0;this.config={gameServerUrl:this.getDefaultGameServerUrl(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,connectionTimeout:1e4,transport:"auto",useUnreliableDatagrams:!0,...e}}getDefaultGameServerUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"}get transportType(){return this._transportType}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}get connectionState(){return{status:this._connectionStatus,transport:this._transportType==="auto"?null:this._transportType,roomId:this._roomId,latency:this._latency,reconnectAttempt:this.reconnectAttempts,lastError:this._lastError||void 0}}get latency(){return this._latency}on(e,t){return this.handlers[e]=t,this}async connect(e){if(this.transport?.isConnected())return;this._connectionStatus=this.reconnectAttempts>0?"reconnecting":"connecting";let t=this.config.transport||"auto",s=(t==="webtransport"||t==="auto")&&le(),i=o=>{this.handleMessage(this.decoder.decode(o))},r=()=>{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?.({code:"CONNECTION_ERROR",message:o.message})};if(s)try{this.transport=new ce(this.config,i,r,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new ee(this.config,i,r,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new ee(this.config,i,r,n),await this.transport.connect(),this._transportType="websocket";this._isConnected=!0,this._connectionStatus="connected",this._lastError=null,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),e&&await this.joinRoom(e)}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.transport&&(this.transport.disconnect(),this.transport=null),this._isConnected=!1,this._connectionStatus="disconnected",this._roomId=null,this._state=null}async createRoom(e={}){return new Promise((t,s)=>{let i=r=>{if(r.type==="room_created"){let n=r.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else r.type==="error"&&s(new Error(r.data.message))};this.sendWithHandler("create_room",e,i)})}async joinRoom(e,t){return new Promise((s,i)=>{let r=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&i(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},r)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>{i.type==="room_left"?(this._roomId=null,this._state=null,e()):i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new Error("Not in a room");let s=JSON.stringify({type:"action",data:{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++}}),i=t||this._transportType!=="webtransport";this.transport?.send(s,i)}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>{if(i.type==="state"){let r=i.data;this._state=r,e(r)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("get_state",{},s)})}async ping(){return new Promise((e,t)=>{let s=Date.now(),i=r=>{if(r.type==="pong"){let n=r.data,o=Date.now()-n.clientTimestamp;this._latency=o,this.handlers.onPong?.(n),e(o)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("ping",{timestamp:s},i)})}send(e,t){if(!this.transport?.isConnected())throw new Error("Not connected");let s=JSON.stringify({type:e,data:t});this.transport.send(s,!0)}sendWithHandler(e,t,s){let i=`msg_${this.messageId++}`;this.pendingHandlers.set(i,s),setTimeout(()=>{this.pendingHandlers.delete(i)},1e4),this.send(e,{...t,_msg_id:i})}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?.({code:t.code||"UNKNOWN",message:t.message||"Unknown error"});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(i=>({path:i.path,operation:i.operation,value:i.value,oldValue:i.old_value})),tick:t.tick};if(this._state){for(let i of s.changes)this.applyChange(i);this._state.version=s.toVersion}if(this.handlers.onAction){for(let i of s.changes)if(i.path.startsWith("actions.")&&i.operation==="set"&&i.value){let r=i.value;this.handlers.onAction({type:r.type||"",clientId:r.client_id||"",data:r.data,timestamp:r.timestamp||0})}}this.handlers.onDelta?.(s)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let r=0;r<t.length-1;r++){let n=t[r];n in s||(s[n]={}),s=s[n]}let i=t[t.length-1];e.operation==="delete"?delete s[i]:s[i]=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 $e="https://api.connectbase.world",Me="https://socket.connectbase.world",Ae="https://webrtc.connectbase.world",qe="https://video.connectbase.world",Ue="https://game.connectbase.world",se=class{constructor(e={}){let t={baseUrl:e.baseUrl||$e,publicKey:e.publicKey,secretKey:e.secretKey,persistence:e.persistence,requestTimeoutMs:e.requestTimeoutMs,onError:e.onError,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError,onTokenExpired:e.onTokenExpired};this.http=new D(t),this.auth=new L(this.http),this.database=new H(this.http),this.storage=new O(this.http),this.publicKey=new B(this.http),this.functions=new G(this.http),this.realtime=new F(this.http,e.socketUrl||Me),this.webrtc=new N(this.http,e.webrtcUrl||Ae,e.appId),this.errorTracker=new W(this.http,e.errorTracker),this.oauth=new K(this.http),this.payment=new V(this.http),this.subscription=new j(this.http),this.push=new z(this.http),this.video=new J(this.http,e.videoUrl||qe),this.game=new I(this.http,e.gameUrl||Ue,e.appId),this.ads=new C(this.http),this.native=new E,this.knowledge=new Q(this.http),this.ai=new x(this.http),this.queue=new X(this.http),this.analytics=new Z(this.http),this.endpoint=new $(this.http),this.auth._attachAnalytics(this.analytics)}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},De=se;return _e(Le);})();
|
|
3
|
+
`);n=a.pop()||"";for(let d of a){if(!d.startsWith("data: "))continue;let u=d.slice(6).trim();if(u==="[DONE]"){t.onDone?.();return}try{let p=JSON.parse(u);if(p.type==="sources"&&p.sources){t.onSources?.(p.sources);continue}if(p.type==="tool_start"||p.type==="tool_end"){t.onToolEvent?.({type:p.type,name:p.name,toolCallId:p.toolCallId,arguments:p.arguments,result:p.result,success:p.success,durationMs:p.durationMs});continue}if(p.type==="heartbeat"||p.type==="searching")continue;if(p.content&&t.onToken?.(p.content),p.done){t.onDone?.();return}}catch{}}}}};var $=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),i=new Headers(t.headers??{});if(!i.has("X-Public-Key")){let r=this.http.getPublicKey();if(!r)throw new Error("EndpointAPI.call: publicKey not configured. Pass `publicKey` to ConnectBase constructor.");i.set("X-Public-Key",r)}return fetch(s,{method:t.method??"GET",headers:i,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 i=this.http.getBaseUrl().replace(/\/+$/,""),r=`${i}/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(r,{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:a}=await l.json();if(!a)throw new Error("EndpointAPI.connectWebSocket: server returned empty ticket");let d=new URL(`${i}/v1/proxy/${encodeURIComponent(e)}${s}`);if(d.protocol=d.protocol==="https:"?"wss:":"ws:",t.query)for(let[p,g]of Object.entries(t.query))d.searchParams.set(p,g);d.searchParams.set("ticket",a);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,i={}){let r=i.intervalMs??1500,n=i.timeoutMs??5*6e4,o=i.parse??"json",l=Date.now();for(;;){if(i.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 a;try{a=await this.call(e,{...t,signal:i.signal})}catch(p){if(p.name==="AbortError")throw p;await ne(r,i.signal);continue}if(a.status>=500){await Ie(a),await ne(r,i.signal);continue}if(a.status>=400){let p=await a.text().catch(()=>"");throw new Error(`EndpointAPI.pollUntil: ${a.status} ${a.statusText} (label=${e}, path=${t.path})${p?` \u2014 ${p.slice(0,200)}`:""}`)}let d;o==="text"?d=await a.text():o==="none"?d=void 0:d=await a.json().catch(()=>{});let u=await s(d,a);if(u!==void 0)return u;await ne(r,i.signal)}}};async function ne(c,e){if(e?.aborted)throw new DOMException("aborted","AbortError");return new Promise((t,s)=>{let i=setTimeout(()=>{e?.removeEventListener("abort",r),t()},c),r=()=>{clearTimeout(i),s(new DOMException("aborted","AbortError"))};e?.addEventListener("abort",r,{once:!0})})}async function Ie(c){try{await c.text()}catch{}}var X=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 i=s.toString(),r=await this.http.get(`/v1/public/queues/${e}/messages${i?`?${i}`:""}`);return f(r,{messages:{type:"array"}},"queue.consume"),r}async ack(e,t,s){let i={message_ids:t,ack_token:s};return this.http.post(`/v1/public/queues/${e}/messages/ack`,i)}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 Ce=1800*1e3,oe="__cb_session",ae="__cb_visitor_uid",Y="__cb_last_activity";function A(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,c=>{let e=Math.random()*16|0;return(c==="x"?e:e&3|8).toString(16)})}var q=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(Y,String(this._lastActivity))}catch{}}reset(){this._sessionId=null,this._isNewSession=!1;try{typeof sessionStorage<"u"&&(sessionStorage.removeItem(oe),sessionStorage.removeItem(Y))}catch{}}regenerateVisitorUid(){let e=A();this._visitorUid=e;try{typeof localStorage<"u"&&localStorage.setItem(ae,e)}catch{}return this.reset(),e}ensureSession(){let e=Date.now();if(!this._sessionId)try{if(typeof sessionStorage<"u"){this._sessionId=sessionStorage.getItem(oe);let t=sessionStorage.getItem(Y);this._lastActivity=t?parseInt(t,10):0}}catch{}if(!this._sessionId||this._lastActivity>0&&e-this._lastActivity>Ce){this._sessionId=A(),this._isNewSession=!0,this._lastActivity=e;try{typeof sessionStorage<"u"&&(sessionStorage.setItem(oe,this._sessionId),sessionStorage.setItem(Y,String(e)))}catch{}}}loadOrCreateVisitorUid(){try{if(typeof localStorage<"u"){let e=localStorage.getItem(ae);if(e)return e;let t=A();return localStorage.setItem(ae,t),t}}catch{}return A()}};function be(c){if(!c)return"";let e=new URLSearchParams;c.start_date!==void 0&&e.set("start_date",String(c.start_date)),c.end_date!==void 0&&e.set("end_date",String(c.end_date)),c.limit!==void 0&&e.set("limit",String(c.limit));let t=e.toString();return t?`?${t}`:""}function Ee(){if(typeof window>"u")return{};try{let c=new URLSearchParams(window.location.search),e={};for(let t of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let s=c.get(t);s&&(e[t]=s)}return e}catch{return{}}}var Z=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 q}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=Ee(),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(i=>{i&&(this.log("link-member response",i),!t&&i.success===!1&&i.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(i=>{this.log("link-member silent fail",i)})}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 i=null,r=0;this.heatmapScrollHandler=()=>{let n=Math.round((window.scrollY+window.innerHeight)/document.documentElement.scrollHeight*100);r=Math.max(r,n),i&&clearTimeout(i),i=setTimeout(()=>{this.recordHeatmapEvent("scroll",50,r*(window.innerHeight/100),r)},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"),i=be(t);return this.http.get(`/v1/storages/web/${s}/popular-pages${i}`)}async getNavigationFlow(e,t){let s=this.requireServerSideStorageId(e,"getNavigationFlow"),i=be(t);return this.http.get(`/v1/storages/web/${s}/navigation/flow${i}`)}async getVisitors(e,t){let s=this.requireServerSideStorageId(e,"getVisitors"),i=new URLSearchParams;t?.limit!==void 0&&i.set("limit",String(t.limit)),t?.offset!==void 0&&i.set("offset",String(t.offset)),t?.sort_by&&i.set("sort_by",t.sort_by);let r=i.toString()?`?${i.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitors${r}`)}async getVisitorGroups(e,t){let s=this.requireServerSideStorageId(e,"getVisitorGroups"),i=new URLSearchParams;t?.limit!==void 0&&i.set("limit",String(t.limit)),t?.offset!==void 0&&i.set("offset",String(t.offset)),t?.sort_by&&i.set("sort_by",t.sort_by);let r=i.toString()?`?${i.toString()}`:"";return this.http.get(`/v1/storages/web/${s}/visitor-groups${r}`)}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:A(),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",i=`${this.http.getBaseUrl()}${t}/storages/web/${this.storageWebId}/visitors/batch`,r=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:r,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(i,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`,i=JSON.stringify({visitor_uid:this.session.visitorUid,session_id:this.session.sessionId});try{navigator.sendBeacon(s,new Blob([i],{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,i){if(!this.canTrack()||!this.storageWebId)return;let r=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:i,session_id:this.session.sessionId}),this.heatmapQueue.length>=50){let n=this.heatmapQueue.splice(0);this.http.post(`${r}/storages/web/${this.storageWebId}/heatmap/batch`,{visitor_uid:this.session.visitorUid,events:n}).catch(()=>{})}}log(...e){this.config.debug&&console.log("[Analytics]",...e)}};var xe="$auth.member_id";var ce=class{constructor(e,t,s,i){this.type="webtransport";this.transport=null;this.writer=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=i}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:i,done:r}=await t.read();if(r)break;let n=new Uint8Array(s.length+i.length);for(n.set(s),n.set(i,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 Error("Not connected");let s=typeof e=="string"?new TextEncoder().encode(e):e;if(t){if(this.writer){let i=new Uint8Array(4);new DataView(i.buffer).setUint32(0,s.length,!0);let r=new Uint8Array(4+s.length);r.set(i),r.set(s,4),this.writer.write(r)}}else{let i=this.config.maxDatagramSize||1200;s.length<=i?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}},ee=class{constructor(e,t,s,i){this.type="websocket";this.ws=null;this.config=e,this.onMessage=t,this.onClose=s,this.onError=i}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 i=()=>{e()},r=()=>{this.onClose()},n=l=>{let a=new Error("WebSocket error");this.onError(a),t(a)},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",i,{once:!0}),this.ws.addEventListener("close",r),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 i=this.config.appId||"";return`${t}/v1/game/${i}/ws?${s.toString()}`}disconnect(){this.ws&&(this.ws.close(1e3,"Client disconnected"),this.ws=null)}send(e,t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("Not connected");typeof e=="string"?this.ws.send(e):this.ws.send(e)}isConnected(){return this.ws!==null&&this.ws.readyState===WebSocket.OPEN}};function le(){return typeof WebTransport<"u"}var te=class{constructor(e){this.transport=null;this.handlers={};this.reconnectAttempts=0;this.reconnectTimer=null;this.pingInterval=null;this.actionSequence=0;this._roomId=null;this._state=null;this._isConnected=!1;this._connectionStatus="disconnected";this._lastError=null;this._latency=0;this._transportType="websocket";this.decoder=new TextDecoder;this.pendingHandlers=new Map;this.messageId=0;this.config={gameServerUrl:this.getDefaultGameServerUrl(),autoReconnect:!0,maxReconnectAttempts:5,reconnectInterval:1e3,connectionTimeout:1e4,transport:"auto",useUnreliableDatagrams:!0,...e}}getDefaultGameServerUrl(){if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e==="127.0.0.1")return"ws://localhost:8087"}return"wss://game.connectbase.world"}get transportType(){return this._transportType}get roomId(){return this._roomId}get state(){return this._state}get isConnected(){return this._isConnected}get connectionState(){return{status:this._connectionStatus,transport:this._transportType==="auto"?null:this._transportType,roomId:this._roomId,latency:this._latency,reconnectAttempt:this.reconnectAttempts,lastError:this._lastError||void 0}}get latency(){return this._latency}on(e,t){return this.handlers[e]=t,this}async connect(e){if(this.transport?.isConnected())return;this._connectionStatus=this.reconnectAttempts>0?"reconnecting":"connecting";let t=this.config.transport||"auto",s=(t==="webtransport"||t==="auto")&&le(),i=o=>{this.handleMessage(this.decoder.decode(o))},r=()=>{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?.({code:"CONNECTION_ERROR",message:o.message})};if(s)try{this.transport=new ce(this.config,i,r,n),await this.transport.connect(),this._transportType="webtransport"}catch{console.log("WebTransport failed, falling back to WebSocket"),this.transport=new ee(this.config,i,r,n),await this.transport.connect(),this._transportType="websocket"}else this.transport=new ee(this.config,i,r,n),await this.transport.connect(),this._transportType="websocket";this._isConnected=!0,this._connectionStatus="connected",this._lastError=null,this.reconnectAttempts=0,this.startPingInterval(),this.handlers.onConnect?.(),e&&await this.joinRoom(e)}disconnect(){this.stopPingInterval(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.transport&&(this.transport.disconnect(),this.transport=null),this._isConnected=!1,this._connectionStatus="disconnected",this._roomId=null,this._state=null}async createRoom(e={}){return new Promise((t,s)=>{let i=r=>{if(r.type==="room_created"){let n=r.data;this._roomId=n.room_id,this._state=n.initial_state,t(n.initial_state)}else r.type==="error"&&s(new Error(r.data.message))};this.sendWithHandler("create_room",e,i)})}async joinRoom(e,t){return new Promise((s,i)=>{let r=n=>{if(n.type==="room_joined"){let o=n.data;this._roomId=o.room_id,this._state=o.initial_state,s(o.initial_state)}else n.type==="error"&&i(new Error(n.data.message))};this.sendWithHandler("join_room",{room_id:e,metadata:t},r)})}async leaveRoom(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>{i.type==="room_left"?(this._roomId=null,this._state=null,e()):i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("leave_room",{},s)})}sendAction(e,t=!1){if(!this._roomId)throw new Error("Not in a room");let s=JSON.stringify({type:"action",data:{type:e.type,data:e.data,client_timestamp:e.clientTimestamp??Date.now(),sequence:this.actionSequence++}}),i=t||this._transportType!=="webtransport";this.transport?.send(s,i)}sendChat(e){if(!this._roomId)throw new Error("Not in a room");this.send("chat",{message:e})}async requestState(){return new Promise((e,t)=>{if(!this._roomId){t(new Error("Not in a room"));return}let s=i=>{if(i.type==="state"){let r=i.data;this._state=r,e(r)}else i.type==="error"&&t(new Error(i.data.message))};this.sendWithHandler("get_state",{},s)})}async ping(){return new Promise((e,t)=>{let s=Date.now(),i=r=>{if(r.type==="pong"){let n=r.data,o=Date.now()-n.clientTimestamp;this._latency=o,this.handlers.onPong?.(n),e(o)}else r.type==="error"&&t(new Error(r.data.message))};this.sendWithHandler("ping",{timestamp:s},i)})}send(e,t){if(!this.transport?.isConnected())throw new Error("Not connected");let s=JSON.stringify({type:e,data:t});this.transport.send(s,!0)}sendWithHandler(e,t,s){let i=`msg_${this.messageId++}`;this.pendingHandlers.set(i,s),setTimeout(()=>{this.pendingHandlers.delete(i)},1e4),this.send(e,{...t,_msg_id:i})}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?.({code:t.code||"UNKNOWN",message:t.message||"Unknown error"});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(i=>({path:i.path,operation:i.operation,value:i.value,oldValue:i.old_value})),tick:t.tick};if(this._state){for(let i of s.changes)this.applyChange(i);this._state.version=s.toVersion}if(this.handlers.onAction){for(let i of s.changes)if(i.path.startsWith("actions.")&&i.operation==="set"&&i.value){let r=i.value;this.handlers.onAction({type:r.type||"",clientId:r.client_id||"",data:r.data,timestamp:r.timestamp||0})}}this.handlers.onDelta?.(s)}applyChange(e){if(!this._state)return;let t=e.path.split("."),s=this._state.state;for(let r=0;r<t.length-1;r++){let n=t[r];n in s||(s[n]={}),s=s[n]}let i=t[t.length-1];e.operation==="delete"?delete s[i]:s[i]=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 $e="https://api.connectbase.world",Me="https://socket.connectbase.world",Ae="https://webrtc.connectbase.world",qe="https://video.connectbase.world",Ue="https://game.connectbase.world",se=class{constructor(e={}){let t={baseUrl:e.baseUrl||$e,publicKey:e.publicKey,secretKey:e.secretKey,persistence:e.persistence,autoRestoreSession:e.autoRestoreSession,requestTimeoutMs:e.requestTimeoutMs,onError:e.onError,onTokenRefresh:e.onTokenRefresh,onAuthError:e.onAuthError,onTokenExpired:e.onTokenExpired};this.http=new D(t),this.auth=new H(this.http),this.database=new L(this.http),this.storage=new O(this.http),this.publicKey=new B(this.http),this.functions=new G(this.http),this.realtime=new F(this.http,e.socketUrl||Me),this.webrtc=new N(this.http,e.webrtcUrl||Ae,e.appId),this.errorTracker=new W(this.http,e.errorTracker),this.oauth=new K(this.http),this.payment=new V(this.http),this.subscription=new j(this.http),this.push=new z(this.http),this.video=new J(this.http,e.videoUrl||qe),this.game=new I(this.http,e.gameUrl||Ue,e.appId),this.ads=new C(this.http),this.native=new E,this.knowledge=new Q(this.http),this.ai=new x(this.http),this.queue=new X(this.http),this.analytics=new Z(this.http),this.endpoint=new $(this.http),this.auth._attachAnalytics(this.analytics),(e.autoRestoreSession??!0)&&typeof window<"u"&&this.http.tryRestoreSessionFromCookie()}async restoreSession(){return this.http.tryRestoreSessionFromCookie()}setTokens(e,t){this.http.setTokens(e,t)}clearTokens(){this.http.clearTokens()}updateConfig(e){this.http.updateConfig(e)}},De=se;return _e(He);})();
|
|
4
4
|
var ConnectBase = ConnectBaseModule.default || ConnectBaseModule.ConnectBase;
|
package/dist/index.d.mts
CHANGED
|
@@ -35,15 +35,23 @@ interface HttpClientConfig {
|
|
|
35
35
|
refreshToken?: string;
|
|
36
36
|
/**
|
|
37
37
|
* 토큰 저장 방식. **기본값은 'none' (메모리 저장)**.
|
|
38
|
-
* XSS 취약점이 하나라도 있을 경우
|
|
39
|
-
* 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
38
|
+
* XSS 취약점이 하나라도 있을 경우 영구 저장은 즉시 전 세션 탈취로 이어지므로,
|
|
39
|
+
* 영구 저장 옵션은 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
40
40
|
*
|
|
41
|
-
* - 'none' (권장·기본값):
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* - 'none' (권장·기본값): access token 만 메모리 저장. refresh token 은 서버가 발급한
|
|
42
|
+
* HttpOnly cookie 로만 보관되어 JS 가 접근할 수 없다 (XSS 시 탈취 불가). 새로고침 후에는
|
|
43
|
+
* `autoRestoreSession`(기본 true) 으로 cookie 만으로 자동 복구된다.
|
|
44
|
+
* - 'sessionStorage': 탭 종료 시 삭제. JS 접근 가능 → XSS 로 탭 세션 탈취 가능
|
|
44
45
|
* - 'localStorage': 브라우저 종료 후에도 유지. JS 접근 가능 → XSS 로 영구 탈취 가능
|
|
45
46
|
*/
|
|
46
47
|
persistence?: TokenPersistence;
|
|
48
|
+
/**
|
|
49
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 로부터 access token 을 자동 복구할지 여부.
|
|
50
|
+
* 기본값은 브라우저 환경에서 true. 비-브라우저(Node.js, RN) 에서는 무시된다.
|
|
51
|
+
*
|
|
52
|
+
* cookie 가 없는 경우(미로그인) 조용히 실패하며 콘솔 에러를 발생시키지 않는다.
|
|
53
|
+
*/
|
|
54
|
+
autoRestoreSession?: boolean;
|
|
47
55
|
/**
|
|
48
56
|
* 요청별 기본 타임아웃(ms). 개별 호출의 `timeout` 이 우선.
|
|
49
57
|
* 기본값 30000ms. 0 또는 음수 지정 시 타임아웃 비활성화.
|
|
@@ -121,6 +129,17 @@ declare class HttpClient {
|
|
|
121
129
|
*/
|
|
122
130
|
getBaseUrl(): string;
|
|
123
131
|
private refreshAccessToken;
|
|
132
|
+
/**
|
|
133
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
134
|
+
*
|
|
135
|
+
* 동작:
|
|
136
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
137
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
138
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
139
|
+
*
|
|
140
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
141
|
+
*/
|
|
142
|
+
tryRestoreSessionFromCookie(): Promise<boolean>;
|
|
124
143
|
private emitError;
|
|
125
144
|
private isTokenExpired;
|
|
126
145
|
private prepareHeaders;
|
|
@@ -7420,12 +7439,23 @@ interface ConnectBaseConfig {
|
|
|
7420
7439
|
onTokenExpired?: () => void;
|
|
7421
7440
|
/**
|
|
7422
7441
|
* 토큰 저장 방식.
|
|
7423
|
-
* - 'none' (기본·권장):
|
|
7424
|
-
*
|
|
7425
|
-
*
|
|
7442
|
+
* - 'none' (기본·권장): access token 만 메모리. refresh token 은 서버 HttpOnly cookie 로
|
|
7443
|
+
* 보관되어 JS 가 접근할 수 없음 (XSS 시 탈취 불가). 새로고침 후에는 `autoRestoreSession`
|
|
7444
|
+
* (기본 true) 으로 cookie 만으로 자동 복구.
|
|
7445
|
+
* - 'sessionStorage': 탭 종료 시 삭제 (XSS 시 탭 세션 탈취 가능 — 콘솔 경고 출력)
|
|
7446
|
+
* - 'localStorage': 브라우저 종료 후에도 유지 (XSS 시 영구 탈취 가능 — 콘솔 경고 출력)
|
|
7426
7447
|
* @default 'none'
|
|
7427
7448
|
*/
|
|
7428
7449
|
persistence?: TokenPersistence;
|
|
7450
|
+
/**
|
|
7451
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 자동 복구할지 여부.
|
|
7452
|
+
* 브라우저 환경에서 기본 true. cookie 가 없거나 만료되었으면 조용히 실패.
|
|
7453
|
+
*
|
|
7454
|
+
* cookie 가 없는 미로그인 상태에서 콘솔에 401 노이즈가 찍히지 않도록 silent 처리한다.
|
|
7455
|
+
*
|
|
7456
|
+
* @default true (브라우저), false (Node.js / RN)
|
|
7457
|
+
*/
|
|
7458
|
+
autoRestoreSession?: boolean;
|
|
7429
7459
|
/**
|
|
7430
7460
|
* 에러 트래커 설정
|
|
7431
7461
|
*/
|
|
@@ -7578,6 +7608,15 @@ declare class ConnectBase {
|
|
|
7578
7608
|
*/
|
|
7579
7609
|
readonly endpoint: EndpointAPI;
|
|
7580
7610
|
constructor(config?: ConnectBaseConfig);
|
|
7611
|
+
/**
|
|
7612
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
7613
|
+
*
|
|
7614
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
7615
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
7616
|
+
*
|
|
7617
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
7618
|
+
*/
|
|
7619
|
+
restoreSession(): Promise<boolean>;
|
|
7581
7620
|
/**
|
|
7582
7621
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
7583
7622
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -35,15 +35,23 @@ interface HttpClientConfig {
|
|
|
35
35
|
refreshToken?: string;
|
|
36
36
|
/**
|
|
37
37
|
* 토큰 저장 방식. **기본값은 'none' (메모리 저장)**.
|
|
38
|
-
* XSS 취약점이 하나라도 있을 경우
|
|
39
|
-
* 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
38
|
+
* XSS 취약점이 하나라도 있을 경우 영구 저장은 즉시 전 세션 탈취로 이어지므로,
|
|
39
|
+
* 영구 저장 옵션은 위험을 이해하고 명시적으로 선택한 경우에만 사용한다.
|
|
40
40
|
*
|
|
41
|
-
* - 'none' (권장·기본값):
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* - 'none' (권장·기본값): access token 만 메모리 저장. refresh token 은 서버가 발급한
|
|
42
|
+
* HttpOnly cookie 로만 보관되어 JS 가 접근할 수 없다 (XSS 시 탈취 불가). 새로고침 후에는
|
|
43
|
+
* `autoRestoreSession`(기본 true) 으로 cookie 만으로 자동 복구된다.
|
|
44
|
+
* - 'sessionStorage': 탭 종료 시 삭제. JS 접근 가능 → XSS 로 탭 세션 탈취 가능
|
|
44
45
|
* - 'localStorage': 브라우저 종료 후에도 유지. JS 접근 가능 → XSS 로 영구 탈취 가능
|
|
45
46
|
*/
|
|
46
47
|
persistence?: TokenPersistence;
|
|
48
|
+
/**
|
|
49
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 로부터 access token 을 자동 복구할지 여부.
|
|
50
|
+
* 기본값은 브라우저 환경에서 true. 비-브라우저(Node.js, RN) 에서는 무시된다.
|
|
51
|
+
*
|
|
52
|
+
* cookie 가 없는 경우(미로그인) 조용히 실패하며 콘솔 에러를 발생시키지 않는다.
|
|
53
|
+
*/
|
|
54
|
+
autoRestoreSession?: boolean;
|
|
47
55
|
/**
|
|
48
56
|
* 요청별 기본 타임아웃(ms). 개별 호출의 `timeout` 이 우선.
|
|
49
57
|
* 기본값 30000ms. 0 또는 음수 지정 시 타임아웃 비활성화.
|
|
@@ -121,6 +129,17 @@ declare class HttpClient {
|
|
|
121
129
|
*/
|
|
122
130
|
getBaseUrl(): string;
|
|
123
131
|
private refreshAccessToken;
|
|
132
|
+
/**
|
|
133
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
134
|
+
*
|
|
135
|
+
* 동작:
|
|
136
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
137
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
138
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
139
|
+
*
|
|
140
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
141
|
+
*/
|
|
142
|
+
tryRestoreSessionFromCookie(): Promise<boolean>;
|
|
124
143
|
private emitError;
|
|
125
144
|
private isTokenExpired;
|
|
126
145
|
private prepareHeaders;
|
|
@@ -7420,12 +7439,23 @@ interface ConnectBaseConfig {
|
|
|
7420
7439
|
onTokenExpired?: () => void;
|
|
7421
7440
|
/**
|
|
7422
7441
|
* 토큰 저장 방식.
|
|
7423
|
-
* - 'none' (기본·권장):
|
|
7424
|
-
*
|
|
7425
|
-
*
|
|
7442
|
+
* - 'none' (기본·권장): access token 만 메모리. refresh token 은 서버 HttpOnly cookie 로
|
|
7443
|
+
* 보관되어 JS 가 접근할 수 없음 (XSS 시 탈취 불가). 새로고침 후에는 `autoRestoreSession`
|
|
7444
|
+
* (기본 true) 으로 cookie 만으로 자동 복구.
|
|
7445
|
+
* - 'sessionStorage': 탭 종료 시 삭제 (XSS 시 탭 세션 탈취 가능 — 콘솔 경고 출력)
|
|
7446
|
+
* - 'localStorage': 브라우저 종료 후에도 유지 (XSS 시 영구 탈취 가능 — 콘솔 경고 출력)
|
|
7426
7447
|
* @default 'none'
|
|
7427
7448
|
*/
|
|
7428
7449
|
persistence?: TokenPersistence;
|
|
7450
|
+
/**
|
|
7451
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 자동 복구할지 여부.
|
|
7452
|
+
* 브라우저 환경에서 기본 true. cookie 가 없거나 만료되었으면 조용히 실패.
|
|
7453
|
+
*
|
|
7454
|
+
* cookie 가 없는 미로그인 상태에서 콘솔에 401 노이즈가 찍히지 않도록 silent 처리한다.
|
|
7455
|
+
*
|
|
7456
|
+
* @default true (브라우저), false (Node.js / RN)
|
|
7457
|
+
*/
|
|
7458
|
+
autoRestoreSession?: boolean;
|
|
7429
7459
|
/**
|
|
7430
7460
|
* 에러 트래커 설정
|
|
7431
7461
|
*/
|
|
@@ -7578,6 +7608,15 @@ declare class ConnectBase {
|
|
|
7578
7608
|
*/
|
|
7579
7609
|
readonly endpoint: EndpointAPI;
|
|
7580
7610
|
constructor(config?: ConnectBaseConfig);
|
|
7611
|
+
/**
|
|
7612
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
7613
|
+
*
|
|
7614
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
7615
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
7616
|
+
*
|
|
7617
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
7618
|
+
*/
|
|
7619
|
+
restoreSession(): Promise<boolean>;
|
|
7581
7620
|
/**
|
|
7582
7621
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
7583
7622
|
*/
|
package/dist/index.js
CHANGED
|
@@ -109,11 +109,11 @@ var HttpClient = class {
|
|
|
109
109
|
if (typeof window === "undefined") return;
|
|
110
110
|
if (this.config.persistence === "localStorage") {
|
|
111
111
|
console.warn(
|
|
112
|
-
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC
|
|
112
|
+
`[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.`
|
|
113
113
|
);
|
|
114
114
|
} else if (this.config.persistence === "sessionStorage") {
|
|
115
115
|
console.warn(
|
|
116
|
-
|
|
116
|
+
`[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.`
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -249,7 +249,7 @@ var HttpClient = class {
|
|
|
249
249
|
throw error;
|
|
250
250
|
}
|
|
251
251
|
this.isRefreshing = true;
|
|
252
|
-
if (!this.config.refreshToken) {
|
|
252
|
+
if (!this.config.refreshToken && typeof window === "undefined") {
|
|
253
253
|
this.isRefreshing = false;
|
|
254
254
|
this.config.onTokenExpired?.();
|
|
255
255
|
const error = new AuthError("Refresh token is missing. Please login again.");
|
|
@@ -262,22 +262,35 @@ var HttpClient = class {
|
|
|
262
262
|
timeout: this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
263
263
|
});
|
|
264
264
|
try {
|
|
265
|
+
const headers = {
|
|
266
|
+
"Content-Type": "application/json"
|
|
267
|
+
};
|
|
268
|
+
if (this.config.refreshToken) {
|
|
269
|
+
headers.Authorization = `Bearer ${this.config.refreshToken}`;
|
|
270
|
+
}
|
|
265
271
|
const response = await fetch(`${this.config.baseUrl}/v1/auth/re-issue`, {
|
|
266
272
|
method: "POST",
|
|
267
|
-
headers
|
|
268
|
-
|
|
269
|
-
"Authorization": `Bearer ${this.config.refreshToken}`
|
|
270
|
-
},
|
|
273
|
+
headers,
|
|
274
|
+
credentials: "include",
|
|
271
275
|
signal
|
|
272
276
|
});
|
|
273
277
|
if (!response.ok) {
|
|
274
278
|
throw new Error("Token refresh failed");
|
|
275
279
|
}
|
|
276
280
|
const data = await response.json();
|
|
277
|
-
|
|
281
|
+
if (!data || typeof data.access_token !== "string") {
|
|
282
|
+
throw new Error("Token refresh response missing access_token");
|
|
283
|
+
}
|
|
284
|
+
const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
|
|
285
|
+
if (nextRefreshToken) {
|
|
286
|
+
this.setTokens(data.access_token, nextRefreshToken);
|
|
287
|
+
} else {
|
|
288
|
+
this.config.accessToken = data.access_token;
|
|
289
|
+
this.persistTokens();
|
|
290
|
+
}
|
|
278
291
|
this.config.onTokenRefresh?.({
|
|
279
292
|
accessToken: data.access_token,
|
|
280
|
-
refreshToken:
|
|
293
|
+
refreshToken: nextRefreshToken
|
|
281
294
|
});
|
|
282
295
|
this.refreshFailureCount = 0;
|
|
283
296
|
this.refreshLockedUntil = 0;
|
|
@@ -303,6 +316,28 @@ var HttpClient = class {
|
|
|
303
316
|
})();
|
|
304
317
|
return this.refreshPromise;
|
|
305
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
321
|
+
*
|
|
322
|
+
* 동작:
|
|
323
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
324
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
325
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
326
|
+
*
|
|
327
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
328
|
+
*/
|
|
329
|
+
async tryRestoreSessionFromCookie() {
|
|
330
|
+
if (typeof window === "undefined") return false;
|
|
331
|
+
if (this.config.accessToken && !this.isTokenExpired(this.config.accessToken)) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const newAccessToken = await this.refreshAccessToken();
|
|
336
|
+
return !!newAccessToken;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
306
341
|
emitError(error) {
|
|
307
342
|
try {
|
|
308
343
|
this.config.onError?.(error);
|
|
@@ -402,6 +437,7 @@ var HttpClient = class {
|
|
|
402
437
|
try {
|
|
403
438
|
const response = await fetch(`${this.config.baseUrl}${url}`, {
|
|
404
439
|
...init,
|
|
440
|
+
credentials: "include",
|
|
405
441
|
signal
|
|
406
442
|
});
|
|
407
443
|
return await this.handleResponse(response);
|
|
@@ -470,6 +506,7 @@ var HttpClient = class {
|
|
|
470
506
|
}
|
|
471
507
|
return fetch(`${this.config.baseUrl}${url}`, {
|
|
472
508
|
...init,
|
|
509
|
+
credentials: "include",
|
|
473
510
|
headers: mergedHeaders
|
|
474
511
|
});
|
|
475
512
|
}
|
|
@@ -9600,6 +9637,7 @@ var ConnectBase = class {
|
|
|
9600
9637
|
publicKey: config.publicKey,
|
|
9601
9638
|
secretKey: config.secretKey,
|
|
9602
9639
|
persistence: config.persistence,
|
|
9640
|
+
autoRestoreSession: config.autoRestoreSession,
|
|
9603
9641
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
9604
9642
|
onError: config.onError,
|
|
9605
9643
|
onTokenRefresh: config.onTokenRefresh,
|
|
@@ -9629,6 +9667,21 @@ var ConnectBase = class {
|
|
|
9629
9667
|
this.analytics = new AnalyticsAPI(this.http);
|
|
9630
9668
|
this.endpoint = new EndpointAPI(this.http);
|
|
9631
9669
|
this.auth._attachAnalytics(this.analytics);
|
|
9670
|
+
const shouldAutoRestore = config.autoRestoreSession ?? true;
|
|
9671
|
+
if (shouldAutoRestore && typeof window !== "undefined") {
|
|
9672
|
+
void this.http.tryRestoreSessionFromCookie();
|
|
9673
|
+
}
|
|
9674
|
+
}
|
|
9675
|
+
/**
|
|
9676
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
9677
|
+
*
|
|
9678
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
9679
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
9680
|
+
*
|
|
9681
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
9682
|
+
*/
|
|
9683
|
+
async restoreSession() {
|
|
9684
|
+
return this.http.tryRestoreSessionFromCookie();
|
|
9632
9685
|
}
|
|
9633
9686
|
/**
|
|
9634
9687
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
package/dist/index.mjs
CHANGED
|
@@ -68,11 +68,11 @@ var HttpClient = class {
|
|
|
68
68
|
if (typeof window === "undefined") return;
|
|
69
69
|
if (this.config.persistence === "localStorage") {
|
|
70
70
|
console.warn(
|
|
71
|
-
`[connect-base-client] persistence="localStorage" \uB294 XSS \uC2DC
|
|
71
|
+
`[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.`
|
|
72
72
|
);
|
|
73
73
|
} else if (this.config.persistence === "sessionStorage") {
|
|
74
74
|
console.warn(
|
|
75
|
-
|
|
75
|
+
`[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.`
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -208,7 +208,7 @@ var HttpClient = class {
|
|
|
208
208
|
throw error;
|
|
209
209
|
}
|
|
210
210
|
this.isRefreshing = true;
|
|
211
|
-
if (!this.config.refreshToken) {
|
|
211
|
+
if (!this.config.refreshToken && typeof window === "undefined") {
|
|
212
212
|
this.isRefreshing = false;
|
|
213
213
|
this.config.onTokenExpired?.();
|
|
214
214
|
const error = new AuthError("Refresh token is missing. Please login again.");
|
|
@@ -221,22 +221,35 @@ var HttpClient = class {
|
|
|
221
221
|
timeout: this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
222
222
|
});
|
|
223
223
|
try {
|
|
224
|
+
const headers = {
|
|
225
|
+
"Content-Type": "application/json"
|
|
226
|
+
};
|
|
227
|
+
if (this.config.refreshToken) {
|
|
228
|
+
headers.Authorization = `Bearer ${this.config.refreshToken}`;
|
|
229
|
+
}
|
|
224
230
|
const response = await fetch(`${this.config.baseUrl}/v1/auth/re-issue`, {
|
|
225
231
|
method: "POST",
|
|
226
|
-
headers
|
|
227
|
-
|
|
228
|
-
"Authorization": `Bearer ${this.config.refreshToken}`
|
|
229
|
-
},
|
|
232
|
+
headers,
|
|
233
|
+
credentials: "include",
|
|
230
234
|
signal
|
|
231
235
|
});
|
|
232
236
|
if (!response.ok) {
|
|
233
237
|
throw new Error("Token refresh failed");
|
|
234
238
|
}
|
|
235
239
|
const data = await response.json();
|
|
236
|
-
|
|
240
|
+
if (!data || typeof data.access_token !== "string") {
|
|
241
|
+
throw new Error("Token refresh response missing access_token");
|
|
242
|
+
}
|
|
243
|
+
const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
|
|
244
|
+
if (nextRefreshToken) {
|
|
245
|
+
this.setTokens(data.access_token, nextRefreshToken);
|
|
246
|
+
} else {
|
|
247
|
+
this.config.accessToken = data.access_token;
|
|
248
|
+
this.persistTokens();
|
|
249
|
+
}
|
|
237
250
|
this.config.onTokenRefresh?.({
|
|
238
251
|
accessToken: data.access_token,
|
|
239
|
-
refreshToken:
|
|
252
|
+
refreshToken: nextRefreshToken
|
|
240
253
|
});
|
|
241
254
|
this.refreshFailureCount = 0;
|
|
242
255
|
this.refreshLockedUntil = 0;
|
|
@@ -262,6 +275,28 @@ var HttpClient = class {
|
|
|
262
275
|
})();
|
|
263
276
|
return this.refreshPromise;
|
|
264
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* 새로고침/탭 재개 시 HttpOnly cookie 만으로 access token 을 복구한다.
|
|
280
|
+
*
|
|
281
|
+
* 동작:
|
|
282
|
+
* - 메모리에 access token 이 이미 있으면 그대로 반환 (true).
|
|
283
|
+
* - 없으면 `/v1/auth/re-issue` 를 cookie 만으로 호출. cookie 가 있으면 access token 회복.
|
|
284
|
+
* - cookie 가 없거나(미로그인) 만료된 경우 조용히 false 반환 (콘솔 에러 없음).
|
|
285
|
+
*
|
|
286
|
+
* 비-브라우저 환경에서는 cookie 흐름이 없으므로 즉시 false 반환.
|
|
287
|
+
*/
|
|
288
|
+
async tryRestoreSessionFromCookie() {
|
|
289
|
+
if (typeof window === "undefined") return false;
|
|
290
|
+
if (this.config.accessToken && !this.isTokenExpired(this.config.accessToken)) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const newAccessToken = await this.refreshAccessToken();
|
|
295
|
+
return !!newAccessToken;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
265
300
|
emitError(error) {
|
|
266
301
|
try {
|
|
267
302
|
this.config.onError?.(error);
|
|
@@ -361,6 +396,7 @@ var HttpClient = class {
|
|
|
361
396
|
try {
|
|
362
397
|
const response = await fetch(`${this.config.baseUrl}${url}`, {
|
|
363
398
|
...init,
|
|
399
|
+
credentials: "include",
|
|
364
400
|
signal
|
|
365
401
|
});
|
|
366
402
|
return await this.handleResponse(response);
|
|
@@ -429,6 +465,7 @@ var HttpClient = class {
|
|
|
429
465
|
}
|
|
430
466
|
return fetch(`${this.config.baseUrl}${url}`, {
|
|
431
467
|
...init,
|
|
468
|
+
credentials: "include",
|
|
432
469
|
headers: mergedHeaders
|
|
433
470
|
});
|
|
434
471
|
}
|
|
@@ -9559,6 +9596,7 @@ var ConnectBase = class {
|
|
|
9559
9596
|
publicKey: config.publicKey,
|
|
9560
9597
|
secretKey: config.secretKey,
|
|
9561
9598
|
persistence: config.persistence,
|
|
9599
|
+
autoRestoreSession: config.autoRestoreSession,
|
|
9562
9600
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
9563
9601
|
onError: config.onError,
|
|
9564
9602
|
onTokenRefresh: config.onTokenRefresh,
|
|
@@ -9588,6 +9626,21 @@ var ConnectBase = class {
|
|
|
9588
9626
|
this.analytics = new AnalyticsAPI(this.http);
|
|
9589
9627
|
this.endpoint = new EndpointAPI(this.http);
|
|
9590
9628
|
this.auth._attachAnalytics(this.analytics);
|
|
9629
|
+
const shouldAutoRestore = config.autoRestoreSession ?? true;
|
|
9630
|
+
if (shouldAutoRestore && typeof window !== "undefined") {
|
|
9631
|
+
void this.http.tryRestoreSessionFromCookie();
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
/**
|
|
9635
|
+
* 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
|
|
9636
|
+
*
|
|
9637
|
+
* 일반적으로는 ConnectBase 생성 시 `autoRestoreSession: true` (기본값) 으로 자동 호출되지만,
|
|
9638
|
+
* 호출 결과를 await 해서 로그인 상태에 따라 다른 UI 를 그리고 싶다면 명시적으로 호출한다.
|
|
9639
|
+
*
|
|
9640
|
+
* @returns access token 복원 성공 시 true, 미로그인/cookie 만료 시 false
|
|
9641
|
+
*/
|
|
9642
|
+
async restoreSession() {
|
|
9643
|
+
return this.http.tryRestoreSessionFromCookie();
|
|
9591
9644
|
}
|
|
9592
9645
|
/**
|
|
9593
9646
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|