connectbase-client 3.30.0 → 3.32.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
@@ -3847,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3847
3847
  */
3848
3848
  declare class OAuthAPI {
3849
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3850
3856
  constructor(http: HttpClient);
3851
3857
  /**
3852
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3971,6 +3977,28 @@ declare class OAuthAPI {
3971
3977
  state?: string;
3972
3978
  error?: string;
3973
3979
  } | null>;
3980
+ /**
3981
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
3982
+ *
3983
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
3984
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
3985
+ * 적재해 세션을 확립한다.
3986
+ *
3987
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
3988
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
3989
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
3990
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
3991
+ *
3992
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
3993
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
3994
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
3995
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
3996
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
3997
+ *
3998
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
3999
+ */
4000
+ consumeRedirectCallbackOnBoot(): Promise<boolean>;
4001
+ private doConsumeRedirectOnBoot;
3974
4002
  /**
3975
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3976
4004
  *
@@ -7515,6 +7543,21 @@ interface AIChatRequest {
7515
7543
  topK?: number;
7516
7544
  agentic?: boolean;
7517
7545
  toolGroupId?: string;
7546
+ /**
7547
+ * 0 보다 크면, 서버측 도구 그룹 실행(`toolGroupId` 사용 시)에서 각 도구 결과를 모델에
7548
+ * **재투입하기 전** N 자(rune 기준)로 잘라낸다(말미에 잘림 표시). SSE `onToolEvent` 의
7549
+ * `tool_end.result` 로 표시되는 값은 원문을 유지한다.
7550
+ *
7551
+ * 거대한 도구 결과(예: 복잡한 페이지의 `browser_snapshot`)가 누적 컨텍스트를 부풀려
7552
+ * 매 턴 prefill 을 무겁게 만들 때, 표시는 원문으로 두고 모델 재투입분만 압축해
7553
+ * 지연을 줄이는 용도. 미지정/0 이면 자르지 않는다.
7554
+ */
7555
+ toolResultMaxChars?: number;
7556
+ /**
7557
+ * 요청에 포함한 MCP 도구(`tools`)를 호출할 외부 MCP 서버의 Public Key (cb_pk_* 형식).
7558
+ * MCP 도구를 직접 넘기지 않으면 불필요하다.
7559
+ */
7560
+ mcpPublicKey?: string;
7518
7561
  }
7519
7562
  /**
7520
7563
  * `chatStream` 콜백.
@@ -8812,9 +8855,18 @@ declare class ConnectBase {
8812
8855
  *
8813
8856
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8814
8857
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8815
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8858
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8816
8859
  */
8817
8860
  private isOAuthCallbackUrl;
8861
+ /**
8862
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8863
+ *
8864
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8865
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8866
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8867
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8868
+ */
8869
+ private isOAuthRedirectCallbackUrl;
8818
8870
  /**
8819
8871
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8820
8872
  *
package/dist/index.d.ts CHANGED
@@ -3847,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3847
3847
  */
3848
3848
  declare class OAuthAPI {
3849
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3850
3856
  constructor(http: HttpClient);
3851
3857
  /**
3852
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3971,6 +3977,28 @@ declare class OAuthAPI {
3971
3977
  state?: string;
3972
3978
  error?: string;
3973
3979
  } | null>;
3980
+ /**
3981
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
3982
+ *
3983
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
3984
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
3985
+ * 적재해 세션을 확립한다.
3986
+ *
3987
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
3988
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
3989
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
3990
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
3991
+ *
3992
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
3993
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
3994
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
3995
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
3996
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
3997
+ *
3998
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
3999
+ */
4000
+ consumeRedirectCallbackOnBoot(): Promise<boolean>;
4001
+ private doConsumeRedirectOnBoot;
3974
4002
  /**
3975
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3976
4004
  *
@@ -7515,6 +7543,21 @@ interface AIChatRequest {
7515
7543
  topK?: number;
7516
7544
  agentic?: boolean;
7517
7545
  toolGroupId?: string;
7546
+ /**
7547
+ * 0 보다 크면, 서버측 도구 그룹 실행(`toolGroupId` 사용 시)에서 각 도구 결과를 모델에
7548
+ * **재투입하기 전** N 자(rune 기준)로 잘라낸다(말미에 잘림 표시). SSE `onToolEvent` 의
7549
+ * `tool_end.result` 로 표시되는 값은 원문을 유지한다.
7550
+ *
7551
+ * 거대한 도구 결과(예: 복잡한 페이지의 `browser_snapshot`)가 누적 컨텍스트를 부풀려
7552
+ * 매 턴 prefill 을 무겁게 만들 때, 표시는 원문으로 두고 모델 재투입분만 압축해
7553
+ * 지연을 줄이는 용도. 미지정/0 이면 자르지 않는다.
7554
+ */
7555
+ toolResultMaxChars?: number;
7556
+ /**
7557
+ * 요청에 포함한 MCP 도구(`tools`)를 호출할 외부 MCP 서버의 Public Key (cb_pk_* 형식).
7558
+ * MCP 도구를 직접 넘기지 않으면 불필요하다.
7559
+ */
7560
+ mcpPublicKey?: string;
7518
7561
  }
7519
7562
  /**
7520
7563
  * `chatStream` 콜백.
@@ -8812,9 +8855,18 @@ declare class ConnectBase {
8812
8855
  *
8813
8856
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8814
8857
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8815
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8858
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8816
8859
  */
8817
8860
  private isOAuthCallbackUrl;
8861
+ /**
8862
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8863
+ *
8864
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8865
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8866
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8867
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8868
+ */
8869
+ private isOAuthRedirectCallbackUrl;
8818
8870
  /**
8819
8871
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8820
8872
  *
package/dist/index.js CHANGED
@@ -153,6 +153,10 @@ function sanitizePathForBreadcrumb(rawUrl) {
153
153
  }
154
154
 
155
155
  // src/core/http.ts
156
+ function fetchCredentialsForPath(url) {
157
+ const path = url.split("?")[0];
158
+ return path.startsWith("/v1/public/") ? "omit" : "include";
159
+ }
156
160
  var TOKEN_STORAGE_KEY = "cb_auth_tokens";
157
161
  function decodeJwtPayload(token) {
158
162
  try {
@@ -679,7 +683,7 @@ var HttpClient = class {
679
683
  try {
680
684
  const response = await fetch(`${this.config.baseUrl}${url}`, {
681
685
  ...init,
682
- credentials: "include",
686
+ credentials: fetchCredentialsForPath(url),
683
687
  signal
684
688
  });
685
689
  return { response, ok: response.ok, status: response.status };
@@ -748,7 +752,7 @@ var HttpClient = class {
748
752
  }
749
753
  return fetch(`${this.config.baseUrl}${url}`, {
750
754
  ...init,
751
- credentials: "include",
755
+ credentials: fetchCredentialsForPath(url),
752
756
  headers: mergedHeaders
753
757
  });
754
758
  }
@@ -4810,6 +4814,12 @@ function escapeToExternalBrowser(currentUrl) {
4810
4814
  var OAuthAPI = class {
4811
4815
  constructor(http) {
4812
4816
  this.http = http;
4817
+ /**
4818
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4819
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4820
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4821
+ */
4822
+ this.bootConsumePromise = null;
4813
4823
  }
4814
4824
  /**
4815
4825
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5104,10 +5114,58 @@ var OAuthAPI = class {
5104
5114
  window.close();
5105
5115
  return result;
5106
5116
  }
5117
+ if (this.bootConsumePromise) {
5118
+ const consumed = await this.bootConsumePromise;
5119
+ if (consumed) {
5120
+ return result;
5121
+ }
5122
+ }
5107
5123
  this.http.setTokens(accessToken, refreshToken);
5108
5124
  await this.http.bootstrapRefreshCookie();
5109
5125
  return result;
5110
5126
  }
5127
+ /**
5128
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5129
+ *
5130
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5131
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5132
+ * 적재해 세션을 확립한다.
5133
+ *
5134
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5135
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5136
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5137
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5138
+ *
5139
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5140
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5141
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5142
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5143
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5144
+ *
5145
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5146
+ */
5147
+ consumeRedirectCallbackOnBoot() {
5148
+ if (!this.bootConsumePromise) {
5149
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5150
+ }
5151
+ return this.bootConsumePromise;
5152
+ }
5153
+ async doConsumeRedirectOnBoot() {
5154
+ try {
5155
+ if (typeof window === "undefined" || !window.location) return false;
5156
+ const params = new URLSearchParams(window.location.search);
5157
+ if (params.get("error")) return false;
5158
+ const accessToken = params.get("access_token");
5159
+ const refreshToken = params.get("refresh_token");
5160
+ const memberId = params.get("member_id");
5161
+ if (!accessToken || !refreshToken || !memberId) return false;
5162
+ this.http.setTokens(accessToken, refreshToken);
5163
+ await this.http.bootstrapRefreshCookie();
5164
+ return true;
5165
+ } catch {
5166
+ return false;
5167
+ }
5168
+ }
5111
5169
  /**
5112
5170
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5113
5171
  *
@@ -10870,8 +10928,12 @@ var ConnectBase = class {
10870
10928
  this.support = new SupportAPI(this.http);
10871
10929
  this.auth._attachAnalytics(this.analytics);
10872
10930
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10873
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10874
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10931
+ if (shouldAutoRestore && typeof window !== "undefined") {
10932
+ if (this.isOAuthRedirectCallbackUrl()) {
10933
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10934
+ } else if (!this.isOAuthCallbackUrl()) {
10935
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10936
+ }
10875
10937
  }
10876
10938
  }
10877
10939
  /**
@@ -10879,7 +10941,7 @@ var ConnectBase = class {
10879
10941
  *
10880
10942
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10881
10943
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10882
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10944
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10883
10945
  */
10884
10946
  isOAuthCallbackUrl() {
10885
10947
  if (typeof window === "undefined" || !window.location) return false;
@@ -10889,6 +10951,20 @@ var ConnectBase = class {
10889
10951
  if (params.has("error") && params.has("state")) return true;
10890
10952
  return false;
10891
10953
  }
10954
+ /**
10955
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10956
+ *
10957
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10958
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10959
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10960
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10961
+ */
10962
+ isOAuthRedirectCallbackUrl() {
10963
+ if (typeof window === "undefined" || !window.location) return false;
10964
+ if (window.opener) return false;
10965
+ const params = new URLSearchParams(window.location.search);
10966
+ return params.has("access_token") && params.has("refresh_token");
10967
+ }
10892
10968
  /**
10893
10969
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10894
10970
  *
package/dist/index.mjs CHANGED
@@ -107,6 +107,10 @@ function sanitizePathForBreadcrumb(rawUrl) {
107
107
  }
108
108
 
109
109
  // src/core/http.ts
110
+ function fetchCredentialsForPath(url) {
111
+ const path = url.split("?")[0];
112
+ return path.startsWith("/v1/public/") ? "omit" : "include";
113
+ }
110
114
  var TOKEN_STORAGE_KEY = "cb_auth_tokens";
111
115
  function decodeJwtPayload(token) {
112
116
  try {
@@ -633,7 +637,7 @@ var HttpClient = class {
633
637
  try {
634
638
  const response = await fetch(`${this.config.baseUrl}${url}`, {
635
639
  ...init,
636
- credentials: "include",
640
+ credentials: fetchCredentialsForPath(url),
637
641
  signal
638
642
  });
639
643
  return { response, ok: response.ok, status: response.status };
@@ -702,7 +706,7 @@ var HttpClient = class {
702
706
  }
703
707
  return fetch(`${this.config.baseUrl}${url}`, {
704
708
  ...init,
705
- credentials: "include",
709
+ credentials: fetchCredentialsForPath(url),
706
710
  headers: mergedHeaders
707
711
  });
708
712
  }
@@ -4764,6 +4768,12 @@ function escapeToExternalBrowser(currentUrl) {
4764
4768
  var OAuthAPI = class {
4765
4769
  constructor(http) {
4766
4770
  this.http = http;
4771
+ /**
4772
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4773
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4774
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4775
+ */
4776
+ this.bootConsumePromise = null;
4767
4777
  }
4768
4778
  /**
4769
4779
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5058,10 +5068,58 @@ var OAuthAPI = class {
5058
5068
  window.close();
5059
5069
  return result;
5060
5070
  }
5071
+ if (this.bootConsumePromise) {
5072
+ const consumed = await this.bootConsumePromise;
5073
+ if (consumed) {
5074
+ return result;
5075
+ }
5076
+ }
5061
5077
  this.http.setTokens(accessToken, refreshToken);
5062
5078
  await this.http.bootstrapRefreshCookie();
5063
5079
  return result;
5064
5080
  }
5081
+ /**
5082
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5083
+ *
5084
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5085
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5086
+ * 적재해 세션을 확립한다.
5087
+ *
5088
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5089
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5090
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5091
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5092
+ *
5093
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5094
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5095
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5096
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5097
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5098
+ *
5099
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5100
+ */
5101
+ consumeRedirectCallbackOnBoot() {
5102
+ if (!this.bootConsumePromise) {
5103
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5104
+ }
5105
+ return this.bootConsumePromise;
5106
+ }
5107
+ async doConsumeRedirectOnBoot() {
5108
+ try {
5109
+ if (typeof window === "undefined" || !window.location) return false;
5110
+ const params = new URLSearchParams(window.location.search);
5111
+ if (params.get("error")) return false;
5112
+ const accessToken = params.get("access_token");
5113
+ const refreshToken = params.get("refresh_token");
5114
+ const memberId = params.get("member_id");
5115
+ if (!accessToken || !refreshToken || !memberId) return false;
5116
+ this.http.setTokens(accessToken, refreshToken);
5117
+ await this.http.bootstrapRefreshCookie();
5118
+ return true;
5119
+ } catch {
5120
+ return false;
5121
+ }
5122
+ }
5065
5123
  /**
5066
5124
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5067
5125
  *
@@ -10824,8 +10882,12 @@ var ConnectBase = class {
10824
10882
  this.support = new SupportAPI(this.http);
10825
10883
  this.auth._attachAnalytics(this.analytics);
10826
10884
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10827
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10828
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10885
+ if (shouldAutoRestore && typeof window !== "undefined") {
10886
+ if (this.isOAuthRedirectCallbackUrl()) {
10887
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10888
+ } else if (!this.isOAuthCallbackUrl()) {
10889
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10890
+ }
10829
10891
  }
10830
10892
  }
10831
10893
  /**
@@ -10833,7 +10895,7 @@ var ConnectBase = class {
10833
10895
  *
10834
10896
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10835
10897
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10836
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10898
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10837
10899
  */
10838
10900
  isOAuthCallbackUrl() {
10839
10901
  if (typeof window === "undefined" || !window.location) return false;
@@ -10843,6 +10905,20 @@ var ConnectBase = class {
10843
10905
  if (params.has("error") && params.has("state")) return true;
10844
10906
  return false;
10845
10907
  }
10908
+ /**
10909
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10910
+ *
10911
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10912
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10913
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10914
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10915
+ */
10916
+ isOAuthRedirectCallbackUrl() {
10917
+ if (typeof window === "undefined" || !window.location) return false;
10918
+ if (window.opener) return false;
10919
+ const params = new URLSearchParams(window.location.search);
10920
+ return params.has("access_token") && params.has("refresh_token");
10921
+ }
10846
10922
  /**
10847
10923
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10848
10924
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.30.0",
3
+ "version": "3.32.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",