connectbase-client 3.29.0 → 3.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -122,6 +122,13 @@ interface HttpClientConfig {
122
122
  * 서버 환경에서만 사용 (전체 권한, 절대 노출 금지).
123
123
  */
124
124
  secretKey?: string;
125
+ /**
126
+ * 이 클라이언트가 속한 앱 ID (선택). 설정 시, refresh 응답이 **다른 앱**의 access token 을
127
+ * 돌려주면(예: 같은 브라우저의 다른 앱 세션 쿠키로 복구된 토큰) 해당 토큰을 채택하지 않고
128
+ * 세션 없음으로 처리한다. 백엔드 re-issue 가 app-scoped 가드로 1차 차단하지만, SDK 측에서도
129
+ * 방어적으로 한 번 더 막는다 (platform-issue 019e86d1). appId 미설정 시 이 가드는 비활성.
130
+ */
131
+ appId?: string;
125
132
  accessToken?: string;
126
133
  refreshToken?: string;
127
134
  /**
@@ -3840,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3840
3847
  */
3841
3848
  declare class OAuthAPI {
3842
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3843
3856
  constructor(http: HttpClient);
3844
3857
  /**
3845
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3964,6 +3977,28 @@ declare class OAuthAPI {
3964
3977
  state?: string;
3965
3978
  error?: string;
3966
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;
3967
4002
  /**
3968
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3969
4004
  *
@@ -8805,9 +8840,18 @@ declare class ConnectBase {
8805
8840
  *
8806
8841
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8807
8842
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8808
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8843
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8809
8844
  */
8810
8845
  private isOAuthCallbackUrl;
8846
+ /**
8847
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8848
+ *
8849
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8850
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8851
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8852
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8853
+ */
8854
+ private isOAuthRedirectCallbackUrl;
8811
8855
  /**
8812
8856
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8813
8857
  *
package/dist/index.d.ts CHANGED
@@ -122,6 +122,13 @@ interface HttpClientConfig {
122
122
  * 서버 환경에서만 사용 (전체 권한, 절대 노출 금지).
123
123
  */
124
124
  secretKey?: string;
125
+ /**
126
+ * 이 클라이언트가 속한 앱 ID (선택). 설정 시, refresh 응답이 **다른 앱**의 access token 을
127
+ * 돌려주면(예: 같은 브라우저의 다른 앱 세션 쿠키로 복구된 토큰) 해당 토큰을 채택하지 않고
128
+ * 세션 없음으로 처리한다. 백엔드 re-issue 가 app-scoped 가드로 1차 차단하지만, SDK 측에서도
129
+ * 방어적으로 한 번 더 막는다 (platform-issue 019e86d1). appId 미설정 시 이 가드는 비활성.
130
+ */
131
+ appId?: string;
125
132
  accessToken?: string;
126
133
  refreshToken?: string;
127
134
  /**
@@ -3840,6 +3847,12 @@ declare function escapeToExternalBrowser(currentUrl?: string): boolean;
3840
3847
  */
3841
3848
  declare class OAuthAPI {
3842
3849
  private http;
3850
+ /**
3851
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
3852
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
3853
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
3854
+ */
3855
+ private bootConsumePromise;
3843
3856
  constructor(http: HttpClient);
3844
3857
  /**
3845
3858
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -3964,6 +3977,28 @@ declare class OAuthAPI {
3964
3977
  state?: string;
3965
3978
  error?: string;
3966
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;
3967
4002
  /**
3968
4003
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
3969
4004
  *
@@ -8805,9 +8840,18 @@ declare class ConnectBase {
8805
8840
  *
8806
8841
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
8807
8842
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
8808
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8843
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
8809
8844
  */
8810
8845
  private isOAuthCallbackUrl;
8846
+ /**
8847
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
8848
+ *
8849
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
8850
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
8851
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
8852
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
8853
+ */
8854
+ private isOAuthRedirectCallbackUrl;
8811
8855
  /**
8812
8856
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
8813
8857
  *
package/dist/index.js CHANGED
@@ -451,6 +451,15 @@ var HttpClient = class {
451
451
  this.refreshLockedUntil = 0;
452
452
  return null;
453
453
  }
454
+ if (this.config.appId) {
455
+ const tokenAppId = decodeJwtPayload(data.access_token)?.app_id;
456
+ if (typeof tokenAppId === "string" && tokenAppId !== this.config.appId) {
457
+ this.clearTokens();
458
+ this.refreshFailureCount = 0;
459
+ this.refreshLockedUntil = 0;
460
+ return null;
461
+ }
462
+ }
454
463
  const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
455
464
  if (nextRefreshToken) {
456
465
  this.setTokens(data.access_token, nextRefreshToken);
@@ -4801,6 +4810,12 @@ function escapeToExternalBrowser(currentUrl) {
4801
4810
  var OAuthAPI = class {
4802
4811
  constructor(http) {
4803
4812
  this.http = http;
4813
+ /**
4814
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4815
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4816
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4817
+ */
4818
+ this.bootConsumePromise = null;
4804
4819
  }
4805
4820
  /**
4806
4821
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5095,10 +5110,58 @@ var OAuthAPI = class {
5095
5110
  window.close();
5096
5111
  return result;
5097
5112
  }
5113
+ if (this.bootConsumePromise) {
5114
+ const consumed = await this.bootConsumePromise;
5115
+ if (consumed) {
5116
+ return result;
5117
+ }
5118
+ }
5098
5119
  this.http.setTokens(accessToken, refreshToken);
5099
5120
  await this.http.bootstrapRefreshCookie();
5100
5121
  return result;
5101
5122
  }
5123
+ /**
5124
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5125
+ *
5126
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5127
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5128
+ * 적재해 세션을 확립한다.
5129
+ *
5130
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5131
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5132
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5133
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5134
+ *
5135
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5136
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5137
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5138
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5139
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5140
+ *
5141
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5142
+ */
5143
+ consumeRedirectCallbackOnBoot() {
5144
+ if (!this.bootConsumePromise) {
5145
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5146
+ }
5147
+ return this.bootConsumePromise;
5148
+ }
5149
+ async doConsumeRedirectOnBoot() {
5150
+ try {
5151
+ if (typeof window === "undefined" || !window.location) return false;
5152
+ const params = new URLSearchParams(window.location.search);
5153
+ if (params.get("error")) return false;
5154
+ const accessToken = params.get("access_token");
5155
+ const refreshToken = params.get("refresh_token");
5156
+ const memberId = params.get("member_id");
5157
+ if (!accessToken || !refreshToken || !memberId) return false;
5158
+ this.http.setTokens(accessToken, refreshToken);
5159
+ await this.http.bootstrapRefreshCookie();
5160
+ return true;
5161
+ } catch {
5162
+ return false;
5163
+ }
5164
+ }
5102
5165
  /**
5103
5166
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5104
5167
  *
@@ -10826,6 +10889,7 @@ var ConnectBase = class {
10826
10889
  baseUrl: config.baseUrl || env("CB_BASE_URL") || DEFAULT_BASE_URL,
10827
10890
  publicKey: config.publicKey,
10828
10891
  secretKey: config.secretKey,
10892
+ appId: config.appId,
10829
10893
  persistence: config.persistence,
10830
10894
  autoRestoreSession: config.autoRestoreSession,
10831
10895
  requestTimeoutMs: config.requestTimeoutMs,
@@ -10860,8 +10924,12 @@ var ConnectBase = class {
10860
10924
  this.support = new SupportAPI(this.http);
10861
10925
  this.auth._attachAnalytics(this.analytics);
10862
10926
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10863
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10864
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10927
+ if (shouldAutoRestore && typeof window !== "undefined") {
10928
+ if (this.isOAuthRedirectCallbackUrl()) {
10929
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10930
+ } else if (!this.isOAuthCallbackUrl()) {
10931
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10932
+ }
10865
10933
  }
10866
10934
  }
10867
10935
  /**
@@ -10869,7 +10937,7 @@ var ConnectBase = class {
10869
10937
  *
10870
10938
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10871
10939
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10872
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10940
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10873
10941
  */
10874
10942
  isOAuthCallbackUrl() {
10875
10943
  if (typeof window === "undefined" || !window.location) return false;
@@ -10879,6 +10947,20 @@ var ConnectBase = class {
10879
10947
  if (params.has("error") && params.has("state")) return true;
10880
10948
  return false;
10881
10949
  }
10950
+ /**
10951
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10952
+ *
10953
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10954
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10955
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10956
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10957
+ */
10958
+ isOAuthRedirectCallbackUrl() {
10959
+ if (typeof window === "undefined" || !window.location) return false;
10960
+ if (window.opener) return false;
10961
+ const params = new URLSearchParams(window.location.search);
10962
+ return params.has("access_token") && params.has("refresh_token");
10963
+ }
10882
10964
  /**
10883
10965
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10884
10966
  *
package/dist/index.mjs CHANGED
@@ -405,6 +405,15 @@ var HttpClient = class {
405
405
  this.refreshLockedUntil = 0;
406
406
  return null;
407
407
  }
408
+ if (this.config.appId) {
409
+ const tokenAppId = decodeJwtPayload(data.access_token)?.app_id;
410
+ if (typeof tokenAppId === "string" && tokenAppId !== this.config.appId) {
411
+ this.clearTokens();
412
+ this.refreshFailureCount = 0;
413
+ this.refreshLockedUntil = 0;
414
+ return null;
415
+ }
416
+ }
408
417
  const nextRefreshToken = typeof data.refresh_token === "string" && data.refresh_token.length > 0 ? data.refresh_token : this.config.refreshToken ?? "";
409
418
  if (nextRefreshToken) {
410
419
  this.setTokens(data.access_token, nextRefreshToken);
@@ -4755,6 +4764,12 @@ function escapeToExternalBrowser(currentUrl) {
4755
4764
  var OAuthAPI = class {
4756
4765
  constructor(http) {
4757
4766
  this.http = http;
4767
+ /**
4768
+ * 부팅 안전망(consumeRedirectCallbackOnBoot)이 리다이렉트 콜백을 자동 소비했을 때의 promise.
4769
+ * 앱이 그 후 `getCallbackResult()` 를 호출해도 중복으로 `bootstrapRefreshCookie` 를
4770
+ * 발화하지 않도록 이 promise 를 공유한다 (이중 re-issue/rotation 방지).
4771
+ */
4772
+ this.bootConsumePromise = null;
4758
4773
  }
4759
4774
  /**
4760
4775
  * 활성화된 OAuth 프로바이더 목록 조회
@@ -5049,10 +5064,58 @@ var OAuthAPI = class {
5049
5064
  window.close();
5050
5065
  return result;
5051
5066
  }
5067
+ if (this.bootConsumePromise) {
5068
+ const consumed = await this.bootConsumePromise;
5069
+ if (consumed) {
5070
+ return result;
5071
+ }
5072
+ }
5052
5073
  this.http.setTokens(accessToken, refreshToken);
5053
5074
  await this.http.bootstrapRefreshCookie();
5054
5075
  return result;
5055
5076
  }
5077
+ /**
5078
+ * (SDK 내부용 — `ConnectBase` 생성자가 호출) 부팅 안전망.
5079
+ *
5080
+ * OAuth 리다이렉트 콜백 페이지에서 앱이 `getCallbackResult()` 를 호출하지 않아도,
5081
+ * `new ConnectBase()` 만으로 URL 의 토큰(`?access_token=&refresh_token=&member_id=`)을
5082
+ * 적재해 세션을 확립한다.
5083
+ *
5084
+ * **왜 필요한가:** 정적 호스팅(웹 스토리지)은 SPA fallback 으로 `/auth/callback` 같은 콜백
5085
+ * 경로에 홈 `index.html` 을 서빙한다. 그 페이지가 콜백을 처리하지 않으면 URL 토큰이 영영
5086
+ * 소비되지 않아 "로그인 → 다시 로그인 페이지" 무한 루프가 발생한다 (여러 사용자 동시 보고).
5087
+ * 이 안전망은 콜백 처리 코드를 빠뜨린 앱도 동작하게 한다.
5088
+ *
5089
+ * **token rotation race(2026-05-16) 가 없는 이유:** 사전 `re-issue`(cookie 복구) 없이 URL
5090
+ * 토큰을 *직접* 소비한다 — `getCallbackResult()` 의 리다이렉트 경로와 동일한 동작이다. 팝업
5091
+ * (`window.opener` 존재)은 `ConnectBase` 생성자에서 제외되어 호출되지 않는다 (팝업 콜백은
5092
+ * `getCallbackResult()` 의 postMessage 경로 전담). code-only(`?code=`)·에러(`?error=`)
5093
+ * 콜백도 생성자에서 제외된다 (네트워크 교환/에러 분기는 앱이 명시 호출).
5094
+ *
5095
+ * @returns 토큰 적재 성공 시 true, no-op(토큰 없음 등)이면 false.
5096
+ */
5097
+ consumeRedirectCallbackOnBoot() {
5098
+ if (!this.bootConsumePromise) {
5099
+ this.bootConsumePromise = this.doConsumeRedirectOnBoot();
5100
+ }
5101
+ return this.bootConsumePromise;
5102
+ }
5103
+ async doConsumeRedirectOnBoot() {
5104
+ try {
5105
+ if (typeof window === "undefined" || !window.location) return false;
5106
+ const params = new URLSearchParams(window.location.search);
5107
+ if (params.get("error")) return false;
5108
+ const accessToken = params.get("access_token");
5109
+ const refreshToken = params.get("refresh_token");
5110
+ const memberId = params.get("member_id");
5111
+ if (!accessToken || !refreshToken || !memberId) return false;
5112
+ this.http.setTokens(accessToken, refreshToken);
5113
+ await this.http.bootstrapRefreshCookie();
5114
+ return true;
5115
+ } catch {
5116
+ return false;
5117
+ }
5118
+ }
5056
5119
  /**
5057
5120
  * 콜백 URL 에서 1회용 `code` 를 토큰으로 교환합니다 (`OAUTH_CODE_ONLY` 서버 모드용).
5058
5121
  *
@@ -10780,6 +10843,7 @@ var ConnectBase = class {
10780
10843
  baseUrl: config.baseUrl || env("CB_BASE_URL") || DEFAULT_BASE_URL,
10781
10844
  publicKey: config.publicKey,
10782
10845
  secretKey: config.secretKey,
10846
+ appId: config.appId,
10783
10847
  persistence: config.persistence,
10784
10848
  autoRestoreSession: config.autoRestoreSession,
10785
10849
  requestTimeoutMs: config.requestTimeoutMs,
@@ -10814,8 +10878,12 @@ var ConnectBase = class {
10814
10878
  this.support = new SupportAPI(this.http);
10815
10879
  this.auth._attachAnalytics(this.analytics);
10816
10880
  const shouldAutoRestore = config.autoRestoreSession ?? true;
10817
- if (shouldAutoRestore && typeof window !== "undefined" && !this.isOAuthCallbackUrl()) {
10818
- this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10881
+ if (shouldAutoRestore && typeof window !== "undefined") {
10882
+ if (this.isOAuthRedirectCallbackUrl()) {
10883
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
10884
+ } else if (!this.isOAuthCallbackUrl()) {
10885
+ this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
10886
+ }
10819
10887
  }
10820
10888
  }
10821
10889
  /**
@@ -10823,7 +10891,7 @@ var ConnectBase = class {
10823
10891
  *
10824
10892
  * `?access_token=...&refresh_token=...` (legacy 토큰-in-URL 흐름) 또는
10825
10893
  * `?code=...` (OAUTH_CODE_ONLY 흐름) 또는 OAuth 에러 응답 (`?error=...&state=...`)
10826
- * 패턴이면 true. 콜백 페이지에서 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10894
+ * 패턴이면 true. 콜백 페이지에서 일반 `autoRestoreSession` 을 건너뛰어 rotation race 를 피한다.
10827
10895
  */
10828
10896
  isOAuthCallbackUrl() {
10829
10897
  if (typeof window === "undefined" || !window.location) return false;
@@ -10833,6 +10901,20 @@ var ConnectBase = class {
10833
10901
  if (params.has("error") && params.has("state")) return true;
10834
10902
  return false;
10835
10903
  }
10904
+ /**
10905
+ * 현재 페이지가 토큰-in-URL OAuth 리다이렉트 콜백(팝업 아님)인지 판별.
10906
+ *
10907
+ * `?access_token=&refresh_token=` 이 있고 `window.opener` 가 없으면 true — 부팅 안전망
10908
+ * (`oauth.consumeRedirectCallbackOnBoot`)으로 토큰을 자동 소비할 대상이다. 팝업 콜백
10909
+ * (`window.opener` 존재)은 `getCallbackResult()` 의 postMessage 경로가 전담하므로 제외한다.
10910
+ * code-only(`?code=`)·에러(`?error=`) 콜백도 제외(네트워크 교환/에러 분기는 앱이 명시 호출).
10911
+ */
10912
+ isOAuthRedirectCallbackUrl() {
10913
+ if (typeof window === "undefined" || !window.location) return false;
10914
+ if (window.opener) return false;
10915
+ const params = new URLSearchParams(window.location.search);
10916
+ return params.has("access_token") && params.has("refresh_token");
10917
+ }
10836
10918
  /**
10837
10919
  * 새로고침/탭 재개 후 cookie 만으로 세션을 복원한다 (브라우저 전용).
10838
10920
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.29.0",
3
+ "version": "3.31.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",