connectbase-client 5.10.0 → 5.11.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/dist/index.d.mts CHANGED
@@ -264,6 +264,18 @@ declare class HttpClient {
264
264
  */
265
265
  private bootRestorePromise;
266
266
  private bootRestoreAlwaysAwait;
267
+ /**
268
+ * 서버가 `X-CB-Direct-Base` 로 알려준 저지연 원본. 학습 전에는 null 이라 항상 `baseUrl`
269
+ * 을 쓴다. {@link resolveOrigin} 참고.
270
+ */
271
+ private directBaseUrl;
272
+ /**
273
+ * direct 원본을 이 세션에서 더는 쓰지 않는다는 표시. 한 번이라도 네트워크 레벨에서
274
+ * 실패하면(CSP `connect-src` 차단, 사내 프록시, DNS 미전파, 호스트 회수 등) 즉시 세워
275
+ * 이후 요청은 전부 `baseUrl` 로 간다. direct 는 최적화일 뿐 필수가 아니므로 재시도를
276
+ * 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
277
+ */
278
+ private directDisabled;
267
279
  /**
268
280
  * 클라이언트 생성 시 명시된 요청 타임아웃(ms). 미지정이면 `undefined`.
269
281
  *
@@ -406,6 +418,30 @@ declare class HttpClient {
406
418
  * (platform-issue 019e638d, 2026-05-26). retry 는 1회 한정 — 무한 루프 차단.
407
419
  */
408
420
  private doFetch;
421
+ /**
422
+ * 이 요청을 보낼 원본을 고른다.
423
+ *
424
+ * `credentials:'omit'` 인 요청 — 즉 `X-Public-Key` 로만 인증하고 쿠키를 주고받지 않는
425
+ * `/v1/public/*` 데이터 평면 — 만 direct 원본으로 보낸다. 이 조건이 곧 안전 조건이다:
426
+ * 멤버 refresh 쿠키는 **host-only** 로 발급되므로(백엔드 auth_cookie.SetMemberRefreshCookie)
427
+ * 로그인·가입·로그아웃·OAuth·탈퇴를 다른 호스트로 보내면 세션이 조용히 끊긴다.
428
+ * `fetchCredentialsForPath` 가 이미 그 경계를 계산하고 있어, 나중에 쿠키 엔드포인트가
429
+ * 추가돼도 자동으로 제외된다(서버도 같은 경로를 421 로 되돌려보낸다).
430
+ */
431
+ private resolveOrigin;
432
+ /**
433
+ * 응답에서 저지연 원본 광고를 학습한다.
434
+ *
435
+ * 왜 서버가 정하나: 클라이언트는 자기가 어느 나라에서 접속했는지 신뢰성 있게 모르고,
436
+ * 두 호스트를 재보는 프로브는 요청을 한 번 더 쓴다. Cloudflare 엣지는 `CF-IPCountry` 로
437
+ * 이미 알고 있으므로 서버가 판단해 알려주는 쪽이 정확하고 공짜다. 서버 env 하나로
438
+ * 즉시 회수할 수도 있다.
439
+ *
440
+ * 값은 검증한다 — https 이고 `baseUrl` 과 같은 등록 도메인이어야 한다. 응답은 우리
441
+ * 서버에서 TLS 로 오지만, 설정 실수 하나로 SDK 트래픽 전체가 엉뚱한 호스트로 향하는
442
+ * 일은 없어야 한다.
443
+ */
444
+ private learnDirectOrigin;
409
445
  private tryFetchOnce;
410
446
  get<T>(url: string, config?: RequestConfig): Promise<T>;
411
447
  post<T>(url: string, data?: unknown, config?: RequestConfig): Promise<T>;
package/dist/index.d.ts CHANGED
@@ -264,6 +264,18 @@ declare class HttpClient {
264
264
  */
265
265
  private bootRestorePromise;
266
266
  private bootRestoreAlwaysAwait;
267
+ /**
268
+ * 서버가 `X-CB-Direct-Base` 로 알려준 저지연 원본. 학습 전에는 null 이라 항상 `baseUrl`
269
+ * 을 쓴다. {@link resolveOrigin} 참고.
270
+ */
271
+ private directBaseUrl;
272
+ /**
273
+ * direct 원본을 이 세션에서 더는 쓰지 않는다는 표시. 한 번이라도 네트워크 레벨에서
274
+ * 실패하면(CSP `connect-src` 차단, 사내 프록시, DNS 미전파, 호스트 회수 등) 즉시 세워
275
+ * 이후 요청은 전부 `baseUrl` 로 간다. direct 는 최적화일 뿐 필수가 아니므로 재시도를
276
+ * 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
277
+ */
278
+ private directDisabled;
267
279
  /**
268
280
  * 클라이언트 생성 시 명시된 요청 타임아웃(ms). 미지정이면 `undefined`.
269
281
  *
@@ -406,6 +418,30 @@ declare class HttpClient {
406
418
  * (platform-issue 019e638d, 2026-05-26). retry 는 1회 한정 — 무한 루프 차단.
407
419
  */
408
420
  private doFetch;
421
+ /**
422
+ * 이 요청을 보낼 원본을 고른다.
423
+ *
424
+ * `credentials:'omit'` 인 요청 — 즉 `X-Public-Key` 로만 인증하고 쿠키를 주고받지 않는
425
+ * `/v1/public/*` 데이터 평면 — 만 direct 원본으로 보낸다. 이 조건이 곧 안전 조건이다:
426
+ * 멤버 refresh 쿠키는 **host-only** 로 발급되므로(백엔드 auth_cookie.SetMemberRefreshCookie)
427
+ * 로그인·가입·로그아웃·OAuth·탈퇴를 다른 호스트로 보내면 세션이 조용히 끊긴다.
428
+ * `fetchCredentialsForPath` 가 이미 그 경계를 계산하고 있어, 나중에 쿠키 엔드포인트가
429
+ * 추가돼도 자동으로 제외된다(서버도 같은 경로를 421 로 되돌려보낸다).
430
+ */
431
+ private resolveOrigin;
432
+ /**
433
+ * 응답에서 저지연 원본 광고를 학습한다.
434
+ *
435
+ * 왜 서버가 정하나: 클라이언트는 자기가 어느 나라에서 접속했는지 신뢰성 있게 모르고,
436
+ * 두 호스트를 재보는 프로브는 요청을 한 번 더 쓴다. Cloudflare 엣지는 `CF-IPCountry` 로
437
+ * 이미 알고 있으므로 서버가 판단해 알려주는 쪽이 정확하고 공짜다. 서버 env 하나로
438
+ * 즉시 회수할 수도 있다.
439
+ *
440
+ * 값은 검증한다 — https 이고 `baseUrl` 과 같은 등록 도메인이어야 한다. 응답은 우리
441
+ * 서버에서 TLS 로 오지만, 설정 실수 하나로 SDK 트래픽 전체가 엉뚱한 호스트로 향하는
442
+ * 일은 없어야 한다.
443
+ */
444
+ private learnDirectOrigin;
409
445
  private tryFetchOnce;
410
446
  get<T>(url: string, config?: RequestConfig): Promise<T>;
411
447
  post<T>(url: string, data?: unknown, config?: RequestConfig): Promise<T>;
package/dist/index.js CHANGED
@@ -11441,6 +11441,9 @@ var COOKIE_BEARING_PUBLIC_PATHS = /* @__PURE__ */ new Set([
11441
11441
  "/v1/public/app-members/signin",
11442
11442
  "/v1/public/app-members/signout"
11443
11443
  ]);
11444
+ function isAbortError(err) {
11445
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
11446
+ }
11444
11447
  function fetchCredentialsForPath(url) {
11445
11448
  const path = url.split("?")[0];
11446
11449
  if (COOKIE_BEARING_PUBLIC_PATHS.has(path)) {
@@ -11512,6 +11515,18 @@ var HttpClient = class {
11512
11515
  // OAuth 콜백 부트스트랩처럼 세션 힌트 유무와 무관하게 첫 인증 호출이 반드시 기다려야
11513
11516
  // 하는 boot promise 인지 여부 (setBootRestorePromise 의 alwaysAwait 옵션).
11514
11517
  this.bootRestoreAlwaysAwait = false;
11518
+ /**
11519
+ * 서버가 `X-CB-Direct-Base` 로 알려준 저지연 원본. 학습 전에는 null 이라 항상 `baseUrl`
11520
+ * 을 쓴다. {@link resolveOrigin} 참고.
11521
+ */
11522
+ this.directBaseUrl = null;
11523
+ /**
11524
+ * direct 원본을 이 세션에서 더는 쓰지 않는다는 표시. 한 번이라도 네트워크 레벨에서
11525
+ * 실패하면(CSP `connect-src` 차단, 사내 프록시, DNS 미전파, 호스트 회수 등) 즉시 세워
11526
+ * 이후 요청은 전부 `baseUrl` 로 간다. direct 는 최적화일 뿐 필수가 아니므로 재시도를
11527
+ * 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
11528
+ */
11529
+ this.directDisabled = false;
11515
11530
  this.config = { ...config };
11516
11531
  this.storageKey = this.buildStorageKey();
11517
11532
  this.warnIfUnsafePersistence();
@@ -12102,18 +12117,73 @@ var HttpClient = class {
12102
12117
  });
12103
12118
  }
12104
12119
  }
12120
+ /**
12121
+ * 이 요청을 보낼 원본을 고른다.
12122
+ *
12123
+ * `credentials:'omit'` 인 요청 — 즉 `X-Public-Key` 로만 인증하고 쿠키를 주고받지 않는
12124
+ * `/v1/public/*` 데이터 평면 — 만 direct 원본으로 보낸다. 이 조건이 곧 안전 조건이다:
12125
+ * 멤버 refresh 쿠키는 **host-only** 로 발급되므로(백엔드 auth_cookie.SetMemberRefreshCookie)
12126
+ * 로그인·가입·로그아웃·OAuth·탈퇴를 다른 호스트로 보내면 세션이 조용히 끊긴다.
12127
+ * `fetchCredentialsForPath` 가 이미 그 경계를 계산하고 있어, 나중에 쿠키 엔드포인트가
12128
+ * 추가돼도 자동으로 제외된다(서버도 같은 경로를 421 로 되돌려보낸다).
12129
+ */
12130
+ resolveOrigin(url) {
12131
+ if (this.directBaseUrl && !this.directDisabled && fetchCredentialsForPath(url) === "omit") {
12132
+ return this.directBaseUrl;
12133
+ }
12134
+ return this.config.baseUrl;
12135
+ }
12136
+ /**
12137
+ * 응답에서 저지연 원본 광고를 학습한다.
12138
+ *
12139
+ * 왜 서버가 정하나: 클라이언트는 자기가 어느 나라에서 접속했는지 신뢰성 있게 모르고,
12140
+ * 두 호스트를 재보는 프로브는 요청을 한 번 더 쓴다. Cloudflare 엣지는 `CF-IPCountry` 로
12141
+ * 이미 알고 있으므로 서버가 판단해 알려주는 쪽이 정확하고 공짜다. 서버 env 하나로
12142
+ * 즉시 회수할 수도 있다.
12143
+ *
12144
+ * 값은 검증한다 — https 이고 `baseUrl` 과 같은 등록 도메인이어야 한다. 응답은 우리
12145
+ * 서버에서 TLS 로 오지만, 설정 실수 하나로 SDK 트래픽 전체가 엉뚱한 호스트로 향하는
12146
+ * 일은 없어야 한다.
12147
+ */
12148
+ learnDirectOrigin(response) {
12149
+ if (this.directDisabled || this.directBaseUrl) return;
12150
+ const advertised = response.headers?.get("X-CB-Direct-Base");
12151
+ if (!advertised) return;
12152
+ try {
12153
+ const next = new URL(advertised);
12154
+ const base = new URL(this.config.baseUrl);
12155
+ if (next.protocol !== "https:") return;
12156
+ const tail = (h) => h.split(".").slice(-2).join(".");
12157
+ if (tail(next.hostname) !== tail(base.hostname)) return;
12158
+ this.directBaseUrl = next.origin;
12159
+ } catch {
12160
+ }
12161
+ }
12105
12162
  async tryFetchOnce(url, init, config) {
12106
12163
  const { signal, cleanup } = createTimeoutController({
12107
12164
  timeout: config?.timeout ?? this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
12108
12165
  signal: config?.signal
12109
12166
  });
12167
+ const origin = this.resolveOrigin(url);
12110
12168
  try {
12111
- const response = await fetch(`${this.config.baseUrl}${url}`, {
12169
+ const response = await fetch(`${origin}${url}`, {
12112
12170
  ...init,
12113
12171
  credentials: fetchCredentialsForPath(url),
12114
12172
  signal
12115
12173
  });
12174
+ this.learnDirectOrigin(response);
12116
12175
  return { response, ok: response.ok, status: response.status };
12176
+ } catch (err) {
12177
+ if (origin !== this.config.baseUrl && !isAbortError(err)) {
12178
+ this.directDisabled = true;
12179
+ const response = await fetch(`${this.config.baseUrl}${url}`, {
12180
+ ...init,
12181
+ credentials: fetchCredentialsForPath(url),
12182
+ signal
12183
+ });
12184
+ return { response, ok: response.ok, status: response.status };
12185
+ }
12186
+ throw err;
12117
12187
  } finally {
12118
12188
  cleanup();
12119
12189
  }
@@ -12180,11 +12250,21 @@ var HttpClient = class {
12180
12250
  const extra = new Headers(init.headers);
12181
12251
  extra.forEach((value, key) => mergedHeaders.set(key, value));
12182
12252
  }
12183
- return fetch(`${this.config.baseUrl}${url}`, {
12184
- ...init,
12185
- credentials: fetchCredentialsForPath(url),
12186
- headers: mergedHeaders
12187
- });
12253
+ const origin = this.resolveOrigin(url);
12254
+ try {
12255
+ const response = await fetch(`${origin}${url}`, {
12256
+ ...init,
12257
+ credentials: fetchCredentialsForPath(url),
12258
+ headers: mergedHeaders
12259
+ });
12260
+ this.learnDirectOrigin(response);
12261
+ return response;
12262
+ } catch (err) {
12263
+ if (origin !== this.config.baseUrl && !isAbortError(err)) {
12264
+ this.directDisabled = true;
12265
+ }
12266
+ throw err;
12267
+ }
12188
12268
  }
12189
12269
  };
12190
12270
 
package/dist/index.mjs CHANGED
@@ -11392,6 +11392,9 @@ var COOKIE_BEARING_PUBLIC_PATHS = /* @__PURE__ */ new Set([
11392
11392
  "/v1/public/app-members/signin",
11393
11393
  "/v1/public/app-members/signout"
11394
11394
  ]);
11395
+ function isAbortError(err) {
11396
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
11397
+ }
11395
11398
  function fetchCredentialsForPath(url) {
11396
11399
  const path = url.split("?")[0];
11397
11400
  if (COOKIE_BEARING_PUBLIC_PATHS.has(path)) {
@@ -11463,6 +11466,18 @@ var HttpClient = class {
11463
11466
  // OAuth 콜백 부트스트랩처럼 세션 힌트 유무와 무관하게 첫 인증 호출이 반드시 기다려야
11464
11467
  // 하는 boot promise 인지 여부 (setBootRestorePromise 의 alwaysAwait 옵션).
11465
11468
  this.bootRestoreAlwaysAwait = false;
11469
+ /**
11470
+ * 서버가 `X-CB-Direct-Base` 로 알려준 저지연 원본. 학습 전에는 null 이라 항상 `baseUrl`
11471
+ * 을 쓴다. {@link resolveOrigin} 참고.
11472
+ */
11473
+ this.directBaseUrl = null;
11474
+ /**
11475
+ * direct 원본을 이 세션에서 더는 쓰지 않는다는 표시. 한 번이라도 네트워크 레벨에서
11476
+ * 실패하면(CSP `connect-src` 차단, 사내 프록시, DNS 미전파, 호스트 회수 등) 즉시 세워
11477
+ * 이후 요청은 전부 `baseUrl` 로 간다. direct 는 최적화일 뿐 필수가 아니므로 재시도를
11478
+ * 반복하지 않고 한 번에 포기하는 쪽이 안전하다.
11479
+ */
11480
+ this.directDisabled = false;
11466
11481
  this.config = { ...config };
11467
11482
  this.storageKey = this.buildStorageKey();
11468
11483
  this.warnIfUnsafePersistence();
@@ -12053,18 +12068,73 @@ var HttpClient = class {
12053
12068
  });
12054
12069
  }
12055
12070
  }
12071
+ /**
12072
+ * 이 요청을 보낼 원본을 고른다.
12073
+ *
12074
+ * `credentials:'omit'` 인 요청 — 즉 `X-Public-Key` 로만 인증하고 쿠키를 주고받지 않는
12075
+ * `/v1/public/*` 데이터 평면 — 만 direct 원본으로 보낸다. 이 조건이 곧 안전 조건이다:
12076
+ * 멤버 refresh 쿠키는 **host-only** 로 발급되므로(백엔드 auth_cookie.SetMemberRefreshCookie)
12077
+ * 로그인·가입·로그아웃·OAuth·탈퇴를 다른 호스트로 보내면 세션이 조용히 끊긴다.
12078
+ * `fetchCredentialsForPath` 가 이미 그 경계를 계산하고 있어, 나중에 쿠키 엔드포인트가
12079
+ * 추가돼도 자동으로 제외된다(서버도 같은 경로를 421 로 되돌려보낸다).
12080
+ */
12081
+ resolveOrigin(url) {
12082
+ if (this.directBaseUrl && !this.directDisabled && fetchCredentialsForPath(url) === "omit") {
12083
+ return this.directBaseUrl;
12084
+ }
12085
+ return this.config.baseUrl;
12086
+ }
12087
+ /**
12088
+ * 응답에서 저지연 원본 광고를 학습한다.
12089
+ *
12090
+ * 왜 서버가 정하나: 클라이언트는 자기가 어느 나라에서 접속했는지 신뢰성 있게 모르고,
12091
+ * 두 호스트를 재보는 프로브는 요청을 한 번 더 쓴다. Cloudflare 엣지는 `CF-IPCountry` 로
12092
+ * 이미 알고 있으므로 서버가 판단해 알려주는 쪽이 정확하고 공짜다. 서버 env 하나로
12093
+ * 즉시 회수할 수도 있다.
12094
+ *
12095
+ * 값은 검증한다 — https 이고 `baseUrl` 과 같은 등록 도메인이어야 한다. 응답은 우리
12096
+ * 서버에서 TLS 로 오지만, 설정 실수 하나로 SDK 트래픽 전체가 엉뚱한 호스트로 향하는
12097
+ * 일은 없어야 한다.
12098
+ */
12099
+ learnDirectOrigin(response) {
12100
+ if (this.directDisabled || this.directBaseUrl) return;
12101
+ const advertised = response.headers?.get("X-CB-Direct-Base");
12102
+ if (!advertised) return;
12103
+ try {
12104
+ const next = new URL(advertised);
12105
+ const base = new URL(this.config.baseUrl);
12106
+ if (next.protocol !== "https:") return;
12107
+ const tail = (h) => h.split(".").slice(-2).join(".");
12108
+ if (tail(next.hostname) !== tail(base.hostname)) return;
12109
+ this.directBaseUrl = next.origin;
12110
+ } catch {
12111
+ }
12112
+ }
12056
12113
  async tryFetchOnce(url, init, config) {
12057
12114
  const { signal, cleanup } = createTimeoutController({
12058
12115
  timeout: config?.timeout ?? this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
12059
12116
  signal: config?.signal
12060
12117
  });
12118
+ const origin = this.resolveOrigin(url);
12061
12119
  try {
12062
- const response = await fetch(`${this.config.baseUrl}${url}`, {
12120
+ const response = await fetch(`${origin}${url}`, {
12063
12121
  ...init,
12064
12122
  credentials: fetchCredentialsForPath(url),
12065
12123
  signal
12066
12124
  });
12125
+ this.learnDirectOrigin(response);
12067
12126
  return { response, ok: response.ok, status: response.status };
12127
+ } catch (err) {
12128
+ if (origin !== this.config.baseUrl && !isAbortError(err)) {
12129
+ this.directDisabled = true;
12130
+ const response = await fetch(`${this.config.baseUrl}${url}`, {
12131
+ ...init,
12132
+ credentials: fetchCredentialsForPath(url),
12133
+ signal
12134
+ });
12135
+ return { response, ok: response.ok, status: response.status };
12136
+ }
12137
+ throw err;
12068
12138
  } finally {
12069
12139
  cleanup();
12070
12140
  }
@@ -12131,11 +12201,21 @@ var HttpClient = class {
12131
12201
  const extra = new Headers(init.headers);
12132
12202
  extra.forEach((value, key) => mergedHeaders.set(key, value));
12133
12203
  }
12134
- return fetch(`${this.config.baseUrl}${url}`, {
12135
- ...init,
12136
- credentials: fetchCredentialsForPath(url),
12137
- headers: mergedHeaders
12138
- });
12204
+ const origin = this.resolveOrigin(url);
12205
+ try {
12206
+ const response = await fetch(`${origin}${url}`, {
12207
+ ...init,
12208
+ credentials: fetchCredentialsForPath(url),
12209
+ headers: mergedHeaders
12210
+ });
12211
+ this.learnDirectOrigin(response);
12212
+ return response;
12213
+ } catch (err) {
12214
+ if (origin !== this.config.baseUrl && !isAbortError(err)) {
12215
+ this.directDisabled = true;
12216
+ }
12217
+ throw err;
12218
+ }
12139
12219
  }
12140
12220
  };
12141
12221
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",