@xinosolutions/auth-sdk 0.1.0 → 0.3.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.cts CHANGED
@@ -1,3 +1,50 @@
1
+ /** XS Auth browser session account (multi-account SSO on the Auth-Server domain). */
2
+ interface XSBrowserAccount {
3
+ sessionId: string;
4
+ userId: string;
5
+ email: string;
6
+ name: string;
7
+ isPrimary: boolean;
8
+ }
9
+ interface OpenSessionBridgeOptions {
10
+ clientId: string;
11
+ parentOrigin?: string;
12
+ timeoutMs?: number;
13
+ }
14
+ interface SessionBridge {
15
+ listAccounts(): Promise<XSBrowserAccount[]>;
16
+ revokeAccount(sessionId: string): Promise<{
17
+ accounts: XSBrowserAccount[];
18
+ remainingCount: number;
19
+ }>;
20
+ revokeAll(): Promise<void>;
21
+ setPrimary(sessionId: string): Promise<XSBrowserAccount[]>;
22
+ close(): void;
23
+ }
24
+ declare class SessionBridgeError extends Error {
25
+ readonly name = "SessionBridgeError";
26
+ readonly errorCode: string;
27
+ constructor(errorCode: string, message: string);
28
+ }
29
+ /** True when the user must pick another primary account before signing out. */
30
+ declare function isPrimaryAccountError(error: unknown): boolean;
31
+
32
+ interface SignOutWithXSOptions {
33
+ clientId: string;
34
+ parentOrigin?: string;
35
+ /** XS Auth user id (`sub`) for the account active in the host app. */
36
+ activeUserId?: string;
37
+ timeoutMs?: number;
38
+ /** Override Auth-Server base URL (e.g. same-origin `/xs-auth` proxy in local dev). */
39
+ authBaseUrl?: string;
40
+ }
41
+ type SignOutAction = 'revoked_one' | 'revoked_all' | 'cancelled';
42
+ interface SignOutWithXSResult {
43
+ action: SignOutAction;
44
+ revokedCurrent: boolean;
45
+ remainingCount: number;
46
+ }
47
+
1
48
  /** Popup was blocked by the browser or environment. */
2
49
  declare class PopupBlockedError extends Error {
3
50
  readonly name = "PopupBlockedError";
@@ -17,6 +64,15 @@ declare class XSAuthDeniedError extends Error {
17
64
  readonly description: string;
18
65
  constructor(errorCode: string, description: string);
19
66
  }
67
+ /** Session bridge did not respond within timeout. */
68
+ declare class LoginBridgeTimeoutError extends Error {
69
+ readonly name = "LoginBridgeTimeoutError";
70
+ }
71
+
72
+ /** Opens the Auth-Server session bridge popup (multi-account sign-out). */
73
+ declare function openSessionBridge(opts: OpenSessionBridgeOptions): Promise<SessionBridge>;
74
+ /** Google-style sign-out popup on the Auth-Server domain. */
75
+ declare function signOutWithXS(opts: SignOutWithXSOptions): Promise<SignOutWithXSResult>;
20
76
  /** Signed-in user profile from XS Auth. */
21
77
  interface XSAuthUser {
22
78
  email: string;
@@ -34,6 +90,19 @@ interface LoginWithXSOptions {
34
90
  * Request a `nonce` inside `idToken` — your backend should verify it matches when validating the JWT.
35
91
  */
36
92
  useNonce?: boolean;
93
+ /**
94
+ * OAuth-style prompt for multi-account SSO on the Auth-Server domain.
95
+ * - `none`: silent sign-in with the primary browser session (fails with `login_required` if absent)
96
+ * - `select_account`: always show the account chooser when sessions exist
97
+ * - `login`: force email/password (add another account)
98
+ */
99
+ prompt?: 'none' | 'select_account' | 'login';
100
+ /** Prefer a specific XS Auth user id (`sub`) when continuing an existing browser session. */
101
+ accountId?: string;
102
+ /** When true and `prompt` is unset, defaults to `none` instead of account chooser. */
103
+ preferPrimary?: boolean;
104
+ /** Override Auth-Server base URL (e.g. same-origin `/xs-auth` proxy in local dev). */
105
+ authBaseUrl?: string;
37
106
  }
38
107
  interface LoginWithXSResult {
39
108
  user: XSAuthUser;
@@ -47,7 +116,12 @@ declare function createPkcePair(): Promise<{
47
116
  }>;
48
117
  /**
49
118
  * Opens the XS Auth sign-in popup and returns the signed-in user plus a verifiable `idToken`.
119
+ *
120
+ * All Auth-Server HTTP calls happen inside the popup (same origin). The host app only
121
+ * opens the popup and exchanges postMessages with the PKCE verifier — no fetch to Auth-Server.
50
122
  */
51
123
  declare function loginWithXS(opts: LoginWithXSOptions): Promise<LoginWithXSResult>;
124
+ /** True when Auth-Server has no active browser session for silent sign-in. */
125
+ declare function isLoginRequiredError(error: unknown): boolean;
52
126
 
53
- export { LoginTimeoutError, type LoginWithXSOptions, type LoginWithXSResult, PopupBlockedError, PopupClosedError, XSAuthDeniedError, type XSAuthUser, createPkcePair, loginWithXS };
127
+ export { LoginBridgeTimeoutError, LoginTimeoutError, type LoginWithXSOptions, type LoginWithXSResult, type OpenSessionBridgeOptions, PopupBlockedError, PopupClosedError, type SessionBridge, SessionBridgeError, type SignOutAction, type SignOutWithXSOptions, type SignOutWithXSResult, XSAuthDeniedError, type XSAuthUser, type XSBrowserAccount, createPkcePair, isLoginRequiredError, isPrimaryAccountError, loginWithXS, openSessionBridge, signOutWithXS };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,50 @@
1
+ /** XS Auth browser session account (multi-account SSO on the Auth-Server domain). */
2
+ interface XSBrowserAccount {
3
+ sessionId: string;
4
+ userId: string;
5
+ email: string;
6
+ name: string;
7
+ isPrimary: boolean;
8
+ }
9
+ interface OpenSessionBridgeOptions {
10
+ clientId: string;
11
+ parentOrigin?: string;
12
+ timeoutMs?: number;
13
+ }
14
+ interface SessionBridge {
15
+ listAccounts(): Promise<XSBrowserAccount[]>;
16
+ revokeAccount(sessionId: string): Promise<{
17
+ accounts: XSBrowserAccount[];
18
+ remainingCount: number;
19
+ }>;
20
+ revokeAll(): Promise<void>;
21
+ setPrimary(sessionId: string): Promise<XSBrowserAccount[]>;
22
+ close(): void;
23
+ }
24
+ declare class SessionBridgeError extends Error {
25
+ readonly name = "SessionBridgeError";
26
+ readonly errorCode: string;
27
+ constructor(errorCode: string, message: string);
28
+ }
29
+ /** True when the user must pick another primary account before signing out. */
30
+ declare function isPrimaryAccountError(error: unknown): boolean;
31
+
32
+ interface SignOutWithXSOptions {
33
+ clientId: string;
34
+ parentOrigin?: string;
35
+ /** XS Auth user id (`sub`) for the account active in the host app. */
36
+ activeUserId?: string;
37
+ timeoutMs?: number;
38
+ /** Override Auth-Server base URL (e.g. same-origin `/xs-auth` proxy in local dev). */
39
+ authBaseUrl?: string;
40
+ }
41
+ type SignOutAction = 'revoked_one' | 'revoked_all' | 'cancelled';
42
+ interface SignOutWithXSResult {
43
+ action: SignOutAction;
44
+ revokedCurrent: boolean;
45
+ remainingCount: number;
46
+ }
47
+
1
48
  /** Popup was blocked by the browser or environment. */
2
49
  declare class PopupBlockedError extends Error {
3
50
  readonly name = "PopupBlockedError";
@@ -17,6 +64,15 @@ declare class XSAuthDeniedError extends Error {
17
64
  readonly description: string;
18
65
  constructor(errorCode: string, description: string);
19
66
  }
67
+ /** Session bridge did not respond within timeout. */
68
+ declare class LoginBridgeTimeoutError extends Error {
69
+ readonly name = "LoginBridgeTimeoutError";
70
+ }
71
+
72
+ /** Opens the Auth-Server session bridge popup (multi-account sign-out). */
73
+ declare function openSessionBridge(opts: OpenSessionBridgeOptions): Promise<SessionBridge>;
74
+ /** Google-style sign-out popup on the Auth-Server domain. */
75
+ declare function signOutWithXS(opts: SignOutWithXSOptions): Promise<SignOutWithXSResult>;
20
76
  /** Signed-in user profile from XS Auth. */
21
77
  interface XSAuthUser {
22
78
  email: string;
@@ -34,6 +90,19 @@ interface LoginWithXSOptions {
34
90
  * Request a `nonce` inside `idToken` — your backend should verify it matches when validating the JWT.
35
91
  */
36
92
  useNonce?: boolean;
93
+ /**
94
+ * OAuth-style prompt for multi-account SSO on the Auth-Server domain.
95
+ * - `none`: silent sign-in with the primary browser session (fails with `login_required` if absent)
96
+ * - `select_account`: always show the account chooser when sessions exist
97
+ * - `login`: force email/password (add another account)
98
+ */
99
+ prompt?: 'none' | 'select_account' | 'login';
100
+ /** Prefer a specific XS Auth user id (`sub`) when continuing an existing browser session. */
101
+ accountId?: string;
102
+ /** When true and `prompt` is unset, defaults to `none` instead of account chooser. */
103
+ preferPrimary?: boolean;
104
+ /** Override Auth-Server base URL (e.g. same-origin `/xs-auth` proxy in local dev). */
105
+ authBaseUrl?: string;
37
106
  }
38
107
  interface LoginWithXSResult {
39
108
  user: XSAuthUser;
@@ -47,7 +116,12 @@ declare function createPkcePair(): Promise<{
47
116
  }>;
48
117
  /**
49
118
  * Opens the XS Auth sign-in popup and returns the signed-in user plus a verifiable `idToken`.
119
+ *
120
+ * All Auth-Server HTTP calls happen inside the popup (same origin). The host app only
121
+ * opens the popup and exchanges postMessages with the PKCE verifier — no fetch to Auth-Server.
50
122
  */
51
123
  declare function loginWithXS(opts: LoginWithXSOptions): Promise<LoginWithXSResult>;
124
+ /** True when Auth-Server has no active browser session for silent sign-in. */
125
+ declare function isLoginRequiredError(error: unknown): boolean;
52
126
 
53
- export { LoginTimeoutError, type LoginWithXSOptions, type LoginWithXSResult, PopupBlockedError, PopupClosedError, XSAuthDeniedError, type XSAuthUser, createPkcePair, loginWithXS };
127
+ export { LoginBridgeTimeoutError, LoginTimeoutError, type LoginWithXSOptions, type LoginWithXSResult, type OpenSessionBridgeOptions, PopupBlockedError, PopupClosedError, type SessionBridge, SessionBridgeError, type SignOutAction, type SignOutWithXSOptions, type SignOutWithXSResult, XSAuthDeniedError, type XSAuthUser, type XSBrowserAccount, createPkcePair, isLoginRequiredError, isPrimaryAccountError, loginWithXS, openSessionBridge, signOutWithXS };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/index.ts
1
+ // src/errors.ts
2
2
  var PopupBlockedError = class extends Error {
3
3
  name = "PopupBlockedError";
4
4
  };
@@ -18,7 +18,327 @@ var XSAuthDeniedError = class extends Error {
18
18
  this.description = description;
19
19
  }
20
20
  };
21
+ var LoginBridgeTimeoutError = class extends Error {
22
+ name = "LoginBridgeTimeoutError";
23
+ };
24
+
25
+ // src/session-bridge.ts
26
+ var SessionBridgeError = class extends Error {
27
+ name = "SessionBridgeError";
28
+ errorCode;
29
+ constructor(errorCode, message) {
30
+ super(message || errorCode);
31
+ this.errorCode = errorCode;
32
+ }
33
+ };
34
+ var BRIDGE_STATE_KEY = "__xs_bridge_state";
35
+ function parseAccounts(raw) {
36
+ if (!Array.isArray(raw)) return [];
37
+ return raw.map((item) => {
38
+ if (!item || typeof item !== "object") return null;
39
+ const row = item;
40
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof row.session_id === "string" ? row.session_id : "";
41
+ const userId = typeof row.userId === "string" ? row.userId : typeof row.user_id === "string" ? row.user_id : "";
42
+ const email = typeof row.email === "string" ? row.email : "";
43
+ const name = typeof row.name === "string" ? row.name : email;
44
+ if (!sessionId || !email) return null;
45
+ return {
46
+ sessionId,
47
+ userId,
48
+ email,
49
+ name,
50
+ isPrimary: row.isPrimary === true || row.is_primary === true
51
+ };
52
+ }).filter((x) => x !== null);
53
+ }
54
+ function hiddenBridgePopupFeatures() {
55
+ return "width=1,height=1,left=-10000,top=-10000,resizable=no,scrollbars=no";
56
+ }
57
+ function randomBridgeState() {
58
+ const bytes = new Uint8Array(32);
59
+ crypto.getRandomValues(bytes);
60
+ let binary = "";
61
+ for (let i = 0; i < bytes.length; i++) {
62
+ binary += String.fromCharCode(bytes[i]);
63
+ }
64
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
65
+ }
66
+ async function openSessionBridge(opts, authBaseUrl) {
67
+ const parentOrigin = opts.parentOrigin ?? globalThis.location?.origin;
68
+ if (!parentOrigin || typeof window === "undefined") {
69
+ throw new Error(
70
+ "openSessionBridge requires a browser Window and explicit parentOrigin (or window.location)."
71
+ );
72
+ }
73
+ const normalizedBase = new URL(authBaseUrl);
74
+ const authOrigin = normalizedBase.origin;
75
+ const state = randomBridgeState();
76
+ sessionStorage.setItem(BRIDGE_STATE_KEY, state);
77
+ const params = new URLSearchParams({
78
+ client_id: opts.clientId,
79
+ parent_origin: parentOrigin,
80
+ state
81
+ });
82
+ const bridgeUrl = `${normalizedBase.href.replace(/\/?$/u, "")}/session/bridge?${params.toString()}`;
83
+ const popup = window.open(
84
+ bridgeUrl,
85
+ `xs-auth-bridge-${crypto.randomUUID()}`,
86
+ hiddenBridgePopupFeatures()
87
+ );
88
+ if (!popup) throw new PopupBlockedError();
89
+ const timeoutMs = opts.timeoutMs !== void 0 ? opts.timeoutMs : 12e4;
90
+ let cleaned = false;
91
+ let closePoll;
92
+ const teardown = () => {
93
+ if (cleaned) return;
94
+ cleaned = true;
95
+ window.removeEventListener("message", handleMessage);
96
+ if (closePoll !== void 0) clearInterval(closePoll);
97
+ sessionStorage.removeItem(BRIDGE_STATE_KEY);
98
+ try {
99
+ popup.close();
100
+ } catch {
101
+ }
102
+ };
103
+ const pending = /* @__PURE__ */ new Map();
104
+ let bridgeReady = false;
105
+ let readyResolve = null;
106
+ let readyReject = null;
107
+ const readyPromise = new Promise((resolve, reject) => {
108
+ readyResolve = resolve;
109
+ readyReject = reject;
110
+ });
111
+ const handleMessage = (event) => {
112
+ if (event.origin !== authOrigin) return;
113
+ const d = event.data;
114
+ if (d?.source !== "xs-auth") return;
115
+ const st = typeof d.state === "string" ? d.state : "";
116
+ if (!st || st !== sessionStorage.getItem(BRIDGE_STATE_KEY)) return;
117
+ if (d.type === "bridge_ready") {
118
+ bridgeReady = true;
119
+ readyResolve?.();
120
+ return;
121
+ }
122
+ const requestId = typeof d.requestId === "string" ? d.requestId : "";
123
+ if (!requestId) return;
124
+ const entry = pending.get(requestId);
125
+ if (!entry) return;
126
+ pending.delete(requestId);
127
+ if (typeof d.error === "string" && d.error.length > 0) {
128
+ entry.reject(
129
+ new SessionBridgeError(
130
+ String(d.error),
131
+ typeof d.error_description === "string" ? d.error_description : ""
132
+ )
133
+ );
134
+ return;
135
+ }
136
+ if (d.type === "result") {
137
+ entry.resolve(d);
138
+ }
139
+ };
140
+ window.addEventListener("message", handleMessage);
141
+ closePoll = window.setInterval(() => {
142
+ if (!popup.closed) return;
143
+ if (cleaned) return;
144
+ for (const [, entry] of pending) {
145
+ entry.reject(new PopupClosedError());
146
+ }
147
+ pending.clear();
148
+ readyReject?.(new PopupClosedError());
149
+ teardown();
150
+ }, 280);
151
+ const readyTimer = window.setTimeout(() => {
152
+ if (bridgeReady) return;
153
+ readyReject?.(new LoginBridgeTimeoutError());
154
+ teardown();
155
+ }, timeoutMs);
156
+ try {
157
+ await readyPromise;
158
+ } catch (error) {
159
+ clearTimeout(readyTimer);
160
+ throw error;
161
+ }
162
+ clearTimeout(readyTimer);
163
+ const sendRequest = (type, extra = {}) => {
164
+ if (popup.closed) {
165
+ return Promise.reject(new PopupClosedError());
166
+ }
167
+ const requestId = crypto.randomUUID();
168
+ return new Promise((resolve, reject) => {
169
+ const timer = window.setTimeout(() => {
170
+ pending.delete(requestId);
171
+ reject(new LoginBridgeTimeoutError());
172
+ }, timeoutMs);
173
+ pending.set(requestId, {
174
+ resolve: (value) => {
175
+ clearTimeout(timer);
176
+ resolve(value);
177
+ },
178
+ reject: (error) => {
179
+ clearTimeout(timer);
180
+ reject(error);
181
+ }
182
+ });
183
+ try {
184
+ popup.postMessage(
185
+ {
186
+ source: "xs-auth-opener",
187
+ state,
188
+ client_id: opts.clientId,
189
+ requestId,
190
+ type,
191
+ ...extra
192
+ },
193
+ authOrigin
194
+ );
195
+ } catch {
196
+ pending.delete(requestId);
197
+ clearTimeout(timer);
198
+ reject(new SessionBridgeError("post_message_failed", "Could not reach the session bridge."));
199
+ }
200
+ });
201
+ };
202
+ return {
203
+ async listAccounts() {
204
+ const result = await sendRequest("list_accounts");
205
+ return parseAccounts(result.accounts);
206
+ },
207
+ async revokeAccount(sessionId) {
208
+ const result = await sendRequest("revoke", { session_id: sessionId.trim() });
209
+ const accounts = parseAccounts(result.accounts);
210
+ const remainingCount = typeof result.remainingCount === "number" ? result.remainingCount : accounts.length;
211
+ return { accounts, remainingCount };
212
+ },
213
+ async revokeAll() {
214
+ await sendRequest("revoke_all");
215
+ },
216
+ async setPrimary(sessionId) {
217
+ const result = await sendRequest("set_primary", { session_id: sessionId.trim() });
218
+ return parseAccounts(result.accounts);
219
+ },
220
+ close() {
221
+ teardown();
222
+ }
223
+ };
224
+ }
225
+ function isPrimaryAccountError(error) {
226
+ return error instanceof SessionBridgeError && error.errorCode === "primary_account";
227
+ }
228
+
229
+ // src/sign-out.ts
230
+ var SIGN_OUT_STATE_KEY = "__xs_sign_out_state";
231
+ function popupWindowFeatures(width, height) {
232
+ const win = window;
233
+ const outerW = win.outerWidth > 0 ? win.outerWidth : win.innerWidth;
234
+ const outerH = win.outerHeight > 0 ? win.outerHeight : win.innerHeight;
235
+ const screenX = win.screenX ?? win.screenLeft ?? 0;
236
+ const screenY = win.screenY ?? win.screenTop ?? 0;
237
+ const left = Math.round(screenX + Math.max(0, (outerW - width) / 2));
238
+ const top = Math.round(screenY + Math.max(0, (outerH - height) / 2));
239
+ return `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
240
+ }
241
+ function randomState() {
242
+ const bytes = new Uint8Array(32);
243
+ crypto.getRandomValues(bytes);
244
+ let binary = "";
245
+ for (let i = 0; i < bytes.length; i++) {
246
+ binary += String.fromCharCode(bytes[i]);
247
+ }
248
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
249
+ }
250
+ function parseSignOutResult(raw, data) {
251
+ return {
252
+ action: raw,
253
+ revokedCurrent: data.revokedCurrent === true,
254
+ remainingCount: typeof data.remainingCount === "number" && Number.isFinite(data.remainingCount) ? Math.max(0, data.remainingCount) : 0
255
+ };
256
+ }
257
+ async function signOutWithXS(opts, authBaseUrl) {
258
+ const parentOrigin = opts.parentOrigin ?? globalThis.location?.origin;
259
+ if (!parentOrigin || typeof window === "undefined") {
260
+ throw new Error(
261
+ "signOutWithXS requires a browser Window and explicit parentOrigin (or window.location)."
262
+ );
263
+ }
264
+ const normalizedBase = new URL(authBaseUrl);
265
+ const authOrigin = normalizedBase.origin;
266
+ const state = randomState();
267
+ sessionStorage.setItem(SIGN_OUT_STATE_KEY, state);
268
+ const params = new URLSearchParams({
269
+ client_id: opts.clientId,
270
+ parent_origin: parentOrigin,
271
+ state
272
+ });
273
+ if (opts.activeUserId?.trim()) {
274
+ params.set("active_user_id", opts.activeUserId.trim());
275
+ }
276
+ const url = `${normalizedBase.href.replace(/\/?$/u, "")}/session/sign-out?${params.toString()}`;
277
+ const popup = window.open(
278
+ url,
279
+ `xs-auth-signout-${crypto.randomUUID()}`,
280
+ popupWindowFeatures(480, 640)
281
+ );
282
+ if (!popup) throw new PopupBlockedError();
283
+ const timeoutMs = opts.timeoutMs !== void 0 ? opts.timeoutMs : 12e4;
284
+ return await new Promise((resolve, reject) => {
285
+ let cleaned = false;
286
+ const teardown = () => {
287
+ if (cleaned) return;
288
+ cleaned = true;
289
+ window.removeEventListener("message", handleMessage);
290
+ clearInterval(closePoll);
291
+ clearTimeout(timer);
292
+ sessionStorage.removeItem(SIGN_OUT_STATE_KEY);
293
+ try {
294
+ popup.close();
295
+ } catch {
296
+ }
297
+ };
298
+ const fail = (error) => {
299
+ teardown();
300
+ reject(error);
301
+ };
302
+ const timer = window.setTimeout(() => {
303
+ fail(new LoginBridgeTimeoutError());
304
+ }, timeoutMs);
305
+ const handleMessage = (event) => {
306
+ if (event.origin !== authOrigin) return;
307
+ const d = event.data;
308
+ if (d?.source !== "xs-auth") return;
309
+ const st = typeof d.state === "string" ? d.state : "";
310
+ if (!st || st !== sessionStorage.getItem(SIGN_OUT_STATE_KEY)) return;
311
+ if (d.type === "sign_out_cancelled") {
312
+ teardown();
313
+ resolve(parseSignOutResult("cancelled", d));
314
+ return;
315
+ }
316
+ if (d.type === "sign_out_complete") {
317
+ teardown();
318
+ const actionRaw = typeof d.action === "string" ? d.action : "revoked_one";
319
+ const action = actionRaw === "revoked_all" ? "revoked_all" : actionRaw === "cancelled" ? "cancelled" : "revoked_one";
320
+ resolve(parseSignOutResult(action, d));
321
+ }
322
+ };
323
+ window.addEventListener("message", handleMessage);
324
+ const closePoll = window.setInterval(() => {
325
+ if (!popup.closed) return;
326
+ if (cleaned) return;
327
+ if (!sessionStorage.getItem(SIGN_OUT_STATE_KEY)) return;
328
+ fail(new PopupClosedError());
329
+ }, 280);
330
+ });
331
+ }
332
+
333
+ // src/index.ts
21
334
  var XS_AUTH_BASE_URL = "https://auth.xinosolutions.com";
335
+ function openSessionBridge2(opts) {
336
+ return openSessionBridge(opts, XS_AUTH_BASE_URL);
337
+ }
338
+ function signOutWithXS2(opts) {
339
+ const authBase = opts.authBaseUrl?.trim() || XS_AUTH_BASE_URL;
340
+ return signOutWithXS(opts, authBase);
341
+ }
22
342
  function bytesBuffer(len) {
23
343
  const out = new Uint8Array(len);
24
344
  crypto.getRandomValues(out);
@@ -57,8 +377,26 @@ function parseSdkUser(raw) {
57
377
  if (lastName) user.lastName = lastName;
58
378
  return user;
59
379
  }
380
+ function resolvePrompt(opts) {
381
+ if (opts.prompt) return opts.prompt;
382
+ if (opts.preferPrimary) return "none";
383
+ return void 0;
384
+ }
385
+ function authPublicPathFromBase(authBaseUrl) {
386
+ try {
387
+ const pathname = new URL(authBaseUrl).pathname.replace(/\/$/u, "");
388
+ return pathname === "/" ? "" : pathname;
389
+ } catch {
390
+ return "";
391
+ }
392
+ }
393
+ function maybeSetPublicPath(params, authBaseUrl) {
394
+ const publicPath = authPublicPathFromBase(authBaseUrl);
395
+ if (publicPath) params.set("public_path", publicPath);
396
+ }
60
397
  async function loginWithXS(opts) {
61
- const normalizedBase = normalizeBaseUrl(XS_AUTH_BASE_URL);
398
+ const authBase = opts.authBaseUrl?.trim() || XS_AUTH_BASE_URL;
399
+ const normalizedBase = normalizeBaseUrl(authBase);
62
400
  const authOrigin = normalizedBase.origin;
63
401
  const parentOrigin = opts.parentOrigin ?? globalThis.location?.origin;
64
402
  if (!parentOrigin || typeof window === "undefined") {
@@ -81,13 +419,17 @@ async function loginWithXS(opts) {
81
419
  });
82
420
  if (opts.scope?.trim()) params.set("scope", opts.scope.trim());
83
421
  if (nonce) params.set("nonce", nonce);
422
+ const prompt = resolvePrompt(opts);
423
+ if (prompt) params.set("prompt", prompt);
424
+ if (opts.accountId?.trim()) params.set("account_id", opts.accountId.trim());
425
+ maybeSetPublicPath(params, authBase);
84
426
  const authorizeUrl = `${normalizedBase.href.replace(/\/?$/u, "")}/authorize?${params.toString()}`;
85
- const popupWidth = 480;
86
- const popupHeight = 640;
427
+ const popupWidth = prompt === "none" ? 420 : 480;
428
+ const popupHeight = prompt === "none" ? 120 : 640;
87
429
  const popup = window.open(
88
430
  authorizeUrl,
89
431
  `xs-auth-${crypto.randomUUID()}`,
90
- popupWindowFeatures(popupWidth, popupHeight)
432
+ popupWindowFeatures2(popupWidth, popupHeight)
91
433
  );
92
434
  if (!popup) throw new PopupBlockedError();
93
435
  return await new Promise((resolve, reject) => {
@@ -126,6 +468,22 @@ async function loginWithXS(opts) {
126
468
  );
127
469
  return;
128
470
  }
471
+ if (d.type === "need_verifier") {
472
+ try {
473
+ popup.postMessage(
474
+ {
475
+ source: "xs-auth-opener",
476
+ state: st,
477
+ code_verifier: pair.verifier,
478
+ client_id: opts.clientId
479
+ },
480
+ authOrigin
481
+ );
482
+ } catch {
483
+ fail(new XSAuthDeniedError("exchange_failed", "Sign in could not be completed."));
484
+ }
485
+ return;
486
+ }
129
487
  const idToken = typeof d.id_token === "string" ? d.id_token : typeof d.idToken === "string" ? d.idToken : "";
130
488
  const user = parseSdkUser(d.user);
131
489
  if (!idToken || !user) return;
@@ -156,7 +514,7 @@ function randomBase64State() {
156
514
  function normalizeBaseUrl(s) {
157
515
  return new URL(s);
158
516
  }
159
- function popupWindowFeatures(width, height) {
517
+ function popupWindowFeatures2(width, height) {
160
518
  const win = window;
161
519
  const outerW = win.outerWidth > 0 ? win.outerWidth : win.innerWidth;
162
520
  const outerH = win.outerHeight > 0 ? win.outerHeight : win.innerHeight;
@@ -166,12 +524,21 @@ function popupWindowFeatures(width, height) {
166
524
  const top = Math.round(screenY + Math.max(0, (outerH - height) / 2));
167
525
  return `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
168
526
  }
527
+ function isLoginRequiredError(error) {
528
+ return error instanceof XSAuthDeniedError && error.errorCode === "login_required";
529
+ }
169
530
  export {
531
+ LoginBridgeTimeoutError,
170
532
  LoginTimeoutError,
171
533
  PopupBlockedError,
172
534
  PopupClosedError,
535
+ SessionBridgeError,
173
536
  XSAuthDeniedError,
174
537
  createPkcePair,
175
- loginWithXS
538
+ isLoginRequiredError,
539
+ isPrimaryAccountError,
540
+ loginWithXS,
541
+ openSessionBridge2 as openSessionBridge,
542
+ signOutWithXS2 as signOutWithXS
176
543
  };
177
544
  //# sourceMappingURL=index.js.map