connectbase-client 5.12.1 → 5.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +25 -33
- package/dist/index.d.ts +25 -33
- package/dist/index.js +71 -10
- package/dist/index.mjs +71 -10
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -276,6 +276,19 @@ declare class HttpClient {
|
|
|
276
276
|
* 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
|
|
277
277
|
*/
|
|
278
278
|
private directDisabled;
|
|
279
|
+
/**
|
|
280
|
+
* 서버가 `X-CB-Realtime-Direct-Base` 로 알려준 socket-server 저지연 원본(`wss://...`).
|
|
281
|
+
* HTTP direct(`directBaseUrl`)와 별도 값·별도 킬 스위치 — core-server 응답에서만 학습되고
|
|
282
|
+
* (WS 업그레이드 응답 헤더는 브라우저가 못 읽는다), `RealtimeAPI.doConnect()` 가 매 연결
|
|
283
|
+
* 시도마다 lazy 하게 조회한다. platform-issue 019fe448.
|
|
284
|
+
*/
|
|
285
|
+
private directRealtimeBaseUrl;
|
|
286
|
+
/**
|
|
287
|
+
* realtime WS 연결이 direct 원본에서 네트워크 레벨로 실패하면 세운다 — `directDisabled`
|
|
288
|
+
* 와 같은 철학("한 번 실패하면 이 세션에서는 포기하고 원래 호스트로")이지만, 실패 감지가
|
|
289
|
+
* WS 상태 머신(RealtimeAPI) 쪽에서 일어나므로 별도 플래그로 둔다.
|
|
290
|
+
*/
|
|
291
|
+
private directRealtimeDisabled;
|
|
279
292
|
/**
|
|
280
293
|
* 클라이언트 생성 시 명시된 요청 타임아웃(ms). 미지정이면 `undefined`.
|
|
281
294
|
*
|
|
@@ -377,6 +390,18 @@ declare class HttpClient {
|
|
|
377
390
|
* Base URL 반환
|
|
378
391
|
*/
|
|
379
392
|
getBaseUrl(): string;
|
|
393
|
+
/**
|
|
394
|
+
* socket-server 의 direct(저지연) WS 원본. 아직 학습 전이거나(REST 요청을 한 번도 안
|
|
395
|
+
* 보냈거나 서버가 광고를 안 함) 네트워크 레벨로 실패해 이 세션에서 포기했으면 `null` —
|
|
396
|
+
* 호출자(`RealtimeAPI`)는 이 경우 원래 소켓 URL로 폴백해야 한다.
|
|
397
|
+
*/
|
|
398
|
+
getDirectRealtimeBaseUrl(): string | null;
|
|
399
|
+
/**
|
|
400
|
+
* direct WS 원본이 네트워크 레벨로 실패했을 때 `RealtimeAPI` 가 호출한다. 이후 연결
|
|
401
|
+
* 시도는 이 세션 동안 전부 원래 소켓 URL로 간다 — direct 는 최적화일 뿐 필수가 아니므로
|
|
402
|
+
* 재시도를 반복하지 않는다(`directDisabled`/HTTP direct 와 같은 철학).
|
|
403
|
+
*/
|
|
404
|
+
disableDirectRealtime(): void;
|
|
380
405
|
/**
|
|
381
406
|
* 이 클라이언트에 설정된 앱 ID 반환 (미설정 시 undefined).
|
|
382
407
|
*
|
|
@@ -7154,39 +7179,6 @@ interface ReadReceiptInfo {
|
|
|
7154
7179
|
/** 읽음 확인 핸들러 */
|
|
7155
7180
|
type ReadReceiptHandler = (receipt: ReadReceiptInfo) => void;
|
|
7156
7181
|
|
|
7157
|
-
/**
|
|
7158
|
-
* Realtime API
|
|
7159
|
-
* WebSocket 기반 실시간 통신 API
|
|
7160
|
-
*
|
|
7161
|
-
* @example
|
|
7162
|
-
* ```typescript
|
|
7163
|
-
* // 연결
|
|
7164
|
-
* await client.realtime.connect()
|
|
7165
|
-
*
|
|
7166
|
-
* // 카테고리 구독
|
|
7167
|
-
* const chat = await client.realtime.subscribe('chat-room-1')
|
|
7168
|
-
*
|
|
7169
|
-
* // 메시지 수신
|
|
7170
|
-
* chat.onMessage((msg) => {
|
|
7171
|
-
* console.log('Received:', msg.data)
|
|
7172
|
-
* })
|
|
7173
|
-
*
|
|
7174
|
-
* // 메시지 전송
|
|
7175
|
-
* await chat.send({ text: 'Hello!' })
|
|
7176
|
-
*
|
|
7177
|
-
* // 히스토리 조회 (persist=true인 카테고리만)
|
|
7178
|
-
* if (chat.info.persist) {
|
|
7179
|
-
* const history = await chat.getHistory(50)
|
|
7180
|
-
* console.log('History:', history.messages)
|
|
7181
|
-
* }
|
|
7182
|
-
*
|
|
7183
|
-
* // 구독 해제
|
|
7184
|
-
* await chat.unsubscribe()
|
|
7185
|
-
*
|
|
7186
|
-
* // 연결 해제
|
|
7187
|
-
* client.realtime.disconnect()
|
|
7188
|
-
* ```
|
|
7189
|
-
*/
|
|
7190
7182
|
declare class RealtimeAPI {
|
|
7191
7183
|
private http;
|
|
7192
7184
|
private socketUrl;
|
package/dist/index.d.ts
CHANGED
|
@@ -276,6 +276,19 @@ declare class HttpClient {
|
|
|
276
276
|
* 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
|
|
277
277
|
*/
|
|
278
278
|
private directDisabled;
|
|
279
|
+
/**
|
|
280
|
+
* 서버가 `X-CB-Realtime-Direct-Base` 로 알려준 socket-server 저지연 원본(`wss://...`).
|
|
281
|
+
* HTTP direct(`directBaseUrl`)와 별도 값·별도 킬 스위치 — core-server 응답에서만 학습되고
|
|
282
|
+
* (WS 업그레이드 응답 헤더는 브라우저가 못 읽는다), `RealtimeAPI.doConnect()` 가 매 연결
|
|
283
|
+
* 시도마다 lazy 하게 조회한다. platform-issue 019fe448.
|
|
284
|
+
*/
|
|
285
|
+
private directRealtimeBaseUrl;
|
|
286
|
+
/**
|
|
287
|
+
* realtime WS 연결이 direct 원본에서 네트워크 레벨로 실패하면 세운다 — `directDisabled`
|
|
288
|
+
* 와 같은 철학("한 번 실패하면 이 세션에서는 포기하고 원래 호스트로")이지만, 실패 감지가
|
|
289
|
+
* WS 상태 머신(RealtimeAPI) 쪽에서 일어나므로 별도 플래그로 둔다.
|
|
290
|
+
*/
|
|
291
|
+
private directRealtimeDisabled;
|
|
279
292
|
/**
|
|
280
293
|
* 클라이언트 생성 시 명시된 요청 타임아웃(ms). 미지정이면 `undefined`.
|
|
281
294
|
*
|
|
@@ -377,6 +390,18 @@ declare class HttpClient {
|
|
|
377
390
|
* Base URL 반환
|
|
378
391
|
*/
|
|
379
392
|
getBaseUrl(): string;
|
|
393
|
+
/**
|
|
394
|
+
* socket-server 의 direct(저지연) WS 원본. 아직 학습 전이거나(REST 요청을 한 번도 안
|
|
395
|
+
* 보냈거나 서버가 광고를 안 함) 네트워크 레벨로 실패해 이 세션에서 포기했으면 `null` —
|
|
396
|
+
* 호출자(`RealtimeAPI`)는 이 경우 원래 소켓 URL로 폴백해야 한다.
|
|
397
|
+
*/
|
|
398
|
+
getDirectRealtimeBaseUrl(): string | null;
|
|
399
|
+
/**
|
|
400
|
+
* direct WS 원본이 네트워크 레벨로 실패했을 때 `RealtimeAPI` 가 호출한다. 이후 연결
|
|
401
|
+
* 시도는 이 세션 동안 전부 원래 소켓 URL로 간다 — direct 는 최적화일 뿐 필수가 아니므로
|
|
402
|
+
* 재시도를 반복하지 않는다(`directDisabled`/HTTP direct 와 같은 철학).
|
|
403
|
+
*/
|
|
404
|
+
disableDirectRealtime(): void;
|
|
380
405
|
/**
|
|
381
406
|
* 이 클라이언트에 설정된 앱 ID 반환 (미설정 시 undefined).
|
|
382
407
|
*
|
|
@@ -7154,39 +7179,6 @@ interface ReadReceiptInfo {
|
|
|
7154
7179
|
/** 읽음 확인 핸들러 */
|
|
7155
7180
|
type ReadReceiptHandler = (receipt: ReadReceiptInfo) => void;
|
|
7156
7181
|
|
|
7157
|
-
/**
|
|
7158
|
-
* Realtime API
|
|
7159
|
-
* WebSocket 기반 실시간 통신 API
|
|
7160
|
-
*
|
|
7161
|
-
* @example
|
|
7162
|
-
* ```typescript
|
|
7163
|
-
* // 연결
|
|
7164
|
-
* await client.realtime.connect()
|
|
7165
|
-
*
|
|
7166
|
-
* // 카테고리 구독
|
|
7167
|
-
* const chat = await client.realtime.subscribe('chat-room-1')
|
|
7168
|
-
*
|
|
7169
|
-
* // 메시지 수신
|
|
7170
|
-
* chat.onMessage((msg) => {
|
|
7171
|
-
* console.log('Received:', msg.data)
|
|
7172
|
-
* })
|
|
7173
|
-
*
|
|
7174
|
-
* // 메시지 전송
|
|
7175
|
-
* await chat.send({ text: 'Hello!' })
|
|
7176
|
-
*
|
|
7177
|
-
* // 히스토리 조회 (persist=true인 카테고리만)
|
|
7178
|
-
* if (chat.info.persist) {
|
|
7179
|
-
* const history = await chat.getHistory(50)
|
|
7180
|
-
* console.log('History:', history.messages)
|
|
7181
|
-
* }
|
|
7182
|
-
*
|
|
7183
|
-
* // 구독 해제
|
|
7184
|
-
* await chat.unsubscribe()
|
|
7185
|
-
*
|
|
7186
|
-
* // 연결 해제
|
|
7187
|
-
* client.realtime.disconnect()
|
|
7188
|
-
* ```
|
|
7189
|
-
*/
|
|
7190
7182
|
declare class RealtimeAPI {
|
|
7191
7183
|
private http;
|
|
7192
7184
|
private socketUrl;
|
package/dist/index.js
CHANGED
|
@@ -7209,6 +7209,7 @@ var QueueAPI = class {
|
|
|
7209
7209
|
};
|
|
7210
7210
|
|
|
7211
7211
|
// src/api/realtime.ts
|
|
7212
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7212
7213
|
var RealtimeAPI = class {
|
|
7213
7214
|
constructor(http, socketUrl) {
|
|
7214
7215
|
this.ws = null;
|
|
@@ -7286,6 +7287,11 @@ var RealtimeAPI = class {
|
|
|
7286
7287
|
if (options.userId) {
|
|
7287
7288
|
this.userId = options.userId;
|
|
7288
7289
|
}
|
|
7290
|
+
if (this.userId && !this.options.accessToken && UUID_RE.test(this.userId)) {
|
|
7291
|
+
throw new Error(
|
|
7292
|
+
`Invalid userId: "${this.userId}" looks like a UUID, which is reserved for authenticated members. Pass that member's accessToken (JWT) instead of userId+publicKey, or use a non-UUID identifier for anonymous/guest connections.`
|
|
7293
|
+
);
|
|
7294
|
+
}
|
|
7289
7295
|
this.connectPromise = this.doConnect();
|
|
7290
7296
|
try {
|
|
7291
7297
|
await this.connectPromise;
|
|
@@ -8135,7 +8141,9 @@ var RealtimeAPI = class {
|
|
|
8135
8141
|
this.state = "connecting";
|
|
8136
8142
|
this.notifyStateChange();
|
|
8137
8143
|
this.log("Connecting...");
|
|
8138
|
-
const
|
|
8144
|
+
const directWsBase = this.http.getDirectRealtimeBaseUrl();
|
|
8145
|
+
const usingDirect = !!directWsBase;
|
|
8146
|
+
const wsUrl = (directWsBase ?? this.socketUrl).replace(/^http/, "ws");
|
|
8139
8147
|
let url;
|
|
8140
8148
|
if (this.options.accessToken) {
|
|
8141
8149
|
url = `${wsUrl}/v1/realtime/auth?access_token=${encodeURIComponent(this.options.accessToken)}&client_id=${this.clientId}`;
|
|
@@ -8165,6 +8173,7 @@ var RealtimeAPI = class {
|
|
|
8165
8173
|
this.ws.close();
|
|
8166
8174
|
this.ws = null;
|
|
8167
8175
|
}
|
|
8176
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8168
8177
|
this.state = "disconnected";
|
|
8169
8178
|
this.notifyStateChange();
|
|
8170
8179
|
reject(
|
|
@@ -8203,6 +8212,7 @@ var RealtimeAPI = class {
|
|
|
8203
8212
|
if (!settled && this.state === "connecting") {
|
|
8204
8213
|
settled = true;
|
|
8205
8214
|
clearTimeout(timeoutId);
|
|
8215
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8206
8216
|
reject(
|
|
8207
8217
|
new Error(
|
|
8208
8218
|
`Connection closed: ${event.reason || "unknown reason"}`
|
|
@@ -8220,12 +8230,14 @@ var RealtimeAPI = class {
|
|
|
8220
8230
|
if (!settled && this.state === "connecting") {
|
|
8221
8231
|
settled = true;
|
|
8222
8232
|
clearTimeout(timeoutId);
|
|
8233
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8223
8234
|
reject(new Error("Failed to connect"));
|
|
8224
8235
|
}
|
|
8225
8236
|
};
|
|
8226
8237
|
} catch (e) {
|
|
8227
8238
|
settled = true;
|
|
8228
8239
|
clearTimeout(timeoutId);
|
|
8240
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8229
8241
|
reject(e);
|
|
8230
8242
|
}
|
|
8231
8243
|
});
|
|
@@ -11544,6 +11556,19 @@ var HttpClient = class {
|
|
|
11544
11556
|
* 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
|
|
11545
11557
|
*/
|
|
11546
11558
|
this.directDisabled = false;
|
|
11559
|
+
/**
|
|
11560
|
+
* 서버가 `X-CB-Realtime-Direct-Base` 로 알려준 socket-server 저지연 원본(`wss://...`).
|
|
11561
|
+
* HTTP direct(`directBaseUrl`)와 별도 값·별도 킬 스위치 — core-server 응답에서만 학습되고
|
|
11562
|
+
* (WS 업그레이드 응답 헤더는 브라우저가 못 읽는다), `RealtimeAPI.doConnect()` 가 매 연결
|
|
11563
|
+
* 시도마다 lazy 하게 조회한다. platform-issue 019fe448.
|
|
11564
|
+
*/
|
|
11565
|
+
this.directRealtimeBaseUrl = null;
|
|
11566
|
+
/**
|
|
11567
|
+
* realtime WS 연결이 direct 원본에서 네트워크 레벨로 실패하면 세운다 — `directDisabled`
|
|
11568
|
+
* 와 같은 철학("한 번 실패하면 이 세션에서는 포기하고 원래 호스트로")이지만, 실패 감지가
|
|
11569
|
+
* WS 상태 머신(RealtimeAPI) 쪽에서 일어나므로 별도 플래그로 둔다.
|
|
11570
|
+
*/
|
|
11571
|
+
this.directRealtimeDisabled = false;
|
|
11547
11572
|
this.config = { ...config };
|
|
11548
11573
|
this.storageKey = this.buildStorageKey();
|
|
11549
11574
|
this.warnIfUnsafePersistence();
|
|
@@ -11789,6 +11814,22 @@ var HttpClient = class {
|
|
|
11789
11814
|
getBaseUrl() {
|
|
11790
11815
|
return this.config.baseUrl;
|
|
11791
11816
|
}
|
|
11817
|
+
/**
|
|
11818
|
+
* socket-server 의 direct(저지연) WS 원본. 아직 학습 전이거나(REST 요청을 한 번도 안
|
|
11819
|
+
* 보냈거나 서버가 광고를 안 함) 네트워크 레벨로 실패해 이 세션에서 포기했으면 `null` —
|
|
11820
|
+
* 호출자(`RealtimeAPI`)는 이 경우 원래 소켓 URL로 폴백해야 한다.
|
|
11821
|
+
*/
|
|
11822
|
+
getDirectRealtimeBaseUrl() {
|
|
11823
|
+
return this.directRealtimeDisabled ? null : this.directRealtimeBaseUrl;
|
|
11824
|
+
}
|
|
11825
|
+
/**
|
|
11826
|
+
* direct WS 원본이 네트워크 레벨로 실패했을 때 `RealtimeAPI` 가 호출한다. 이후 연결
|
|
11827
|
+
* 시도는 이 세션 동안 전부 원래 소켓 URL로 간다 — direct 는 최적화일 뿐 필수가 아니므로
|
|
11828
|
+
* 재시도를 반복하지 않는다(`directDisabled`/HTTP direct 와 같은 철학).
|
|
11829
|
+
*/
|
|
11830
|
+
disableDirectRealtime() {
|
|
11831
|
+
this.directRealtimeDisabled = true;
|
|
11832
|
+
}
|
|
11792
11833
|
/**
|
|
11793
11834
|
* 이 클라이언트에 설정된 앱 ID 반환 (미설정 시 undefined).
|
|
11794
11835
|
*
|
|
@@ -12172,17 +12213,37 @@ var HttpClient = class {
|
|
|
12172
12213
|
* 일은 없어야 한다.
|
|
12173
12214
|
*/
|
|
12174
12215
|
learnDirectOrigin(response) {
|
|
12175
|
-
if (
|
|
12176
|
-
const
|
|
12177
|
-
|
|
12216
|
+
if (!response.headers) return;
|
|
12217
|
+
const tail = (h) => h.split(".").slice(-2).join(".");
|
|
12218
|
+
let base;
|
|
12178
12219
|
try {
|
|
12179
|
-
|
|
12180
|
-
const base = new URL(this.config.baseUrl);
|
|
12181
|
-
if (next.protocol !== "https:") return;
|
|
12182
|
-
const tail = (h) => h.split(".").slice(-2).join(".");
|
|
12183
|
-
if (tail(next.hostname) !== tail(base.hostname)) return;
|
|
12184
|
-
this.directBaseUrl = next.origin;
|
|
12220
|
+
base = new URL(this.config.baseUrl);
|
|
12185
12221
|
} catch {
|
|
12222
|
+
return;
|
|
12223
|
+
}
|
|
12224
|
+
if (!this.directDisabled && !this.directBaseUrl) {
|
|
12225
|
+
const advertised = response.headers.get("X-CB-Direct-Base");
|
|
12226
|
+
if (advertised) {
|
|
12227
|
+
try {
|
|
12228
|
+
const next = new URL(advertised);
|
|
12229
|
+
if (next.protocol === "https:" && tail(next.hostname) === tail(base.hostname)) {
|
|
12230
|
+
this.directBaseUrl = next.origin;
|
|
12231
|
+
}
|
|
12232
|
+
} catch {
|
|
12233
|
+
}
|
|
12234
|
+
}
|
|
12235
|
+
}
|
|
12236
|
+
if (!this.directRealtimeDisabled && !this.directRealtimeBaseUrl) {
|
|
12237
|
+
const advertisedRealtime = response.headers.get("X-CB-Realtime-Direct-Base");
|
|
12238
|
+
if (advertisedRealtime) {
|
|
12239
|
+
try {
|
|
12240
|
+
const next = new URL(advertisedRealtime);
|
|
12241
|
+
if (next.protocol === "wss:" && tail(next.hostname) === tail(base.hostname)) {
|
|
12242
|
+
this.directRealtimeBaseUrl = next.origin;
|
|
12243
|
+
}
|
|
12244
|
+
} catch {
|
|
12245
|
+
}
|
|
12246
|
+
}
|
|
12186
12247
|
}
|
|
12187
12248
|
}
|
|
12188
12249
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -7160,6 +7160,7 @@ var QueueAPI = class {
|
|
|
7160
7160
|
};
|
|
7161
7161
|
|
|
7162
7162
|
// src/api/realtime.ts
|
|
7163
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7163
7164
|
var RealtimeAPI = class {
|
|
7164
7165
|
constructor(http, socketUrl) {
|
|
7165
7166
|
this.ws = null;
|
|
@@ -7237,6 +7238,11 @@ var RealtimeAPI = class {
|
|
|
7237
7238
|
if (options.userId) {
|
|
7238
7239
|
this.userId = options.userId;
|
|
7239
7240
|
}
|
|
7241
|
+
if (this.userId && !this.options.accessToken && UUID_RE.test(this.userId)) {
|
|
7242
|
+
throw new Error(
|
|
7243
|
+
`Invalid userId: "${this.userId}" looks like a UUID, which is reserved for authenticated members. Pass that member's accessToken (JWT) instead of userId+publicKey, or use a non-UUID identifier for anonymous/guest connections.`
|
|
7244
|
+
);
|
|
7245
|
+
}
|
|
7240
7246
|
this.connectPromise = this.doConnect();
|
|
7241
7247
|
try {
|
|
7242
7248
|
await this.connectPromise;
|
|
@@ -8086,7 +8092,9 @@ var RealtimeAPI = class {
|
|
|
8086
8092
|
this.state = "connecting";
|
|
8087
8093
|
this.notifyStateChange();
|
|
8088
8094
|
this.log("Connecting...");
|
|
8089
|
-
const
|
|
8095
|
+
const directWsBase = this.http.getDirectRealtimeBaseUrl();
|
|
8096
|
+
const usingDirect = !!directWsBase;
|
|
8097
|
+
const wsUrl = (directWsBase ?? this.socketUrl).replace(/^http/, "ws");
|
|
8090
8098
|
let url;
|
|
8091
8099
|
if (this.options.accessToken) {
|
|
8092
8100
|
url = `${wsUrl}/v1/realtime/auth?access_token=${encodeURIComponent(this.options.accessToken)}&client_id=${this.clientId}`;
|
|
@@ -8116,6 +8124,7 @@ var RealtimeAPI = class {
|
|
|
8116
8124
|
this.ws.close();
|
|
8117
8125
|
this.ws = null;
|
|
8118
8126
|
}
|
|
8127
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8119
8128
|
this.state = "disconnected";
|
|
8120
8129
|
this.notifyStateChange();
|
|
8121
8130
|
reject(
|
|
@@ -8154,6 +8163,7 @@ var RealtimeAPI = class {
|
|
|
8154
8163
|
if (!settled && this.state === "connecting") {
|
|
8155
8164
|
settled = true;
|
|
8156
8165
|
clearTimeout(timeoutId);
|
|
8166
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8157
8167
|
reject(
|
|
8158
8168
|
new Error(
|
|
8159
8169
|
`Connection closed: ${event.reason || "unknown reason"}`
|
|
@@ -8171,12 +8181,14 @@ var RealtimeAPI = class {
|
|
|
8171
8181
|
if (!settled && this.state === "connecting") {
|
|
8172
8182
|
settled = true;
|
|
8173
8183
|
clearTimeout(timeoutId);
|
|
8184
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8174
8185
|
reject(new Error("Failed to connect"));
|
|
8175
8186
|
}
|
|
8176
8187
|
};
|
|
8177
8188
|
} catch (e) {
|
|
8178
8189
|
settled = true;
|
|
8179
8190
|
clearTimeout(timeoutId);
|
|
8191
|
+
if (usingDirect) this.http.disableDirectRealtime();
|
|
8180
8192
|
reject(e);
|
|
8181
8193
|
}
|
|
8182
8194
|
});
|
|
@@ -11495,6 +11507,19 @@ var HttpClient = class {
|
|
|
11495
11507
|
* 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
|
|
11496
11508
|
*/
|
|
11497
11509
|
this.directDisabled = false;
|
|
11510
|
+
/**
|
|
11511
|
+
* 서버가 `X-CB-Realtime-Direct-Base` 로 알려준 socket-server 저지연 원본(`wss://...`).
|
|
11512
|
+
* HTTP direct(`directBaseUrl`)와 별도 값·별도 킬 스위치 — core-server 응답에서만 학습되고
|
|
11513
|
+
* (WS 업그레이드 응답 헤더는 브라우저가 못 읽는다), `RealtimeAPI.doConnect()` 가 매 연결
|
|
11514
|
+
* 시도마다 lazy 하게 조회한다. platform-issue 019fe448.
|
|
11515
|
+
*/
|
|
11516
|
+
this.directRealtimeBaseUrl = null;
|
|
11517
|
+
/**
|
|
11518
|
+
* realtime WS 연결이 direct 원본에서 네트워크 레벨로 실패하면 세운다 — `directDisabled`
|
|
11519
|
+
* 와 같은 철학("한 번 실패하면 이 세션에서는 포기하고 원래 호스트로")이지만, 실패 감지가
|
|
11520
|
+
* WS 상태 머신(RealtimeAPI) 쪽에서 일어나므로 별도 플래그로 둔다.
|
|
11521
|
+
*/
|
|
11522
|
+
this.directRealtimeDisabled = false;
|
|
11498
11523
|
this.config = { ...config };
|
|
11499
11524
|
this.storageKey = this.buildStorageKey();
|
|
11500
11525
|
this.warnIfUnsafePersistence();
|
|
@@ -11740,6 +11765,22 @@ var HttpClient = class {
|
|
|
11740
11765
|
getBaseUrl() {
|
|
11741
11766
|
return this.config.baseUrl;
|
|
11742
11767
|
}
|
|
11768
|
+
/**
|
|
11769
|
+
* socket-server 의 direct(저지연) WS 원본. 아직 학습 전이거나(REST 요청을 한 번도 안
|
|
11770
|
+
* 보냈거나 서버가 광고를 안 함) 네트워크 레벨로 실패해 이 세션에서 포기했으면 `null` —
|
|
11771
|
+
* 호출자(`RealtimeAPI`)는 이 경우 원래 소켓 URL로 폴백해야 한다.
|
|
11772
|
+
*/
|
|
11773
|
+
getDirectRealtimeBaseUrl() {
|
|
11774
|
+
return this.directRealtimeDisabled ? null : this.directRealtimeBaseUrl;
|
|
11775
|
+
}
|
|
11776
|
+
/**
|
|
11777
|
+
* direct WS 원본이 네트워크 레벨로 실패했을 때 `RealtimeAPI` 가 호출한다. 이후 연결
|
|
11778
|
+
* 시도는 이 세션 동안 전부 원래 소켓 URL로 간다 — direct 는 최적화일 뿐 필수가 아니므로
|
|
11779
|
+
* 재시도를 반복하지 않는다(`directDisabled`/HTTP direct 와 같은 철학).
|
|
11780
|
+
*/
|
|
11781
|
+
disableDirectRealtime() {
|
|
11782
|
+
this.directRealtimeDisabled = true;
|
|
11783
|
+
}
|
|
11743
11784
|
/**
|
|
11744
11785
|
* 이 클라이언트에 설정된 앱 ID 반환 (미설정 시 undefined).
|
|
11745
11786
|
*
|
|
@@ -12123,17 +12164,37 @@ var HttpClient = class {
|
|
|
12123
12164
|
* 일은 없어야 한다.
|
|
12124
12165
|
*/
|
|
12125
12166
|
learnDirectOrigin(response) {
|
|
12126
|
-
if (
|
|
12127
|
-
const
|
|
12128
|
-
|
|
12167
|
+
if (!response.headers) return;
|
|
12168
|
+
const tail = (h) => h.split(".").slice(-2).join(".");
|
|
12169
|
+
let base;
|
|
12129
12170
|
try {
|
|
12130
|
-
|
|
12131
|
-
const base = new URL(this.config.baseUrl);
|
|
12132
|
-
if (next.protocol !== "https:") return;
|
|
12133
|
-
const tail = (h) => h.split(".").slice(-2).join(".");
|
|
12134
|
-
if (tail(next.hostname) !== tail(base.hostname)) return;
|
|
12135
|
-
this.directBaseUrl = next.origin;
|
|
12171
|
+
base = new URL(this.config.baseUrl);
|
|
12136
12172
|
} catch {
|
|
12173
|
+
return;
|
|
12174
|
+
}
|
|
12175
|
+
if (!this.directDisabled && !this.directBaseUrl) {
|
|
12176
|
+
const advertised = response.headers.get("X-CB-Direct-Base");
|
|
12177
|
+
if (advertised) {
|
|
12178
|
+
try {
|
|
12179
|
+
const next = new URL(advertised);
|
|
12180
|
+
if (next.protocol === "https:" && tail(next.hostname) === tail(base.hostname)) {
|
|
12181
|
+
this.directBaseUrl = next.origin;
|
|
12182
|
+
}
|
|
12183
|
+
} catch {
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
}
|
|
12187
|
+
if (!this.directRealtimeDisabled && !this.directRealtimeBaseUrl) {
|
|
12188
|
+
const advertisedRealtime = response.headers.get("X-CB-Realtime-Direct-Base");
|
|
12189
|
+
if (advertisedRealtime) {
|
|
12190
|
+
try {
|
|
12191
|
+
const next = new URL(advertisedRealtime);
|
|
12192
|
+
if (next.protocol === "wss:" && tail(next.hostname) === tail(base.hostname)) {
|
|
12193
|
+
this.directRealtimeBaseUrl = next.origin;
|
|
12194
|
+
}
|
|
12195
|
+
} catch {
|
|
12196
|
+
}
|
|
12197
|
+
}
|
|
12137
12198
|
}
|
|
12138
12199
|
}
|
|
12139
12200
|
/**
|