@socialproof/mysocial-auth 0.2.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @socialproof/mysocial-auth
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fixed
8
+
9
+ ## 0.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Fixed auth route
14
+
3
15
  ## 0.2.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -68,7 +68,7 @@ await auth.signOut();
68
68
 
69
69
  ### auth.signIn(options?)
70
70
 
71
- - `provider?`: 'google' | 'apple' | 'facebook' | 'twitch'
71
+ - `provider?`: 'google' | 'apple' | 'facebook' | 'twitch' | 'none' (default 'none' = home screen)
72
72
  - `mode?`: 'popup' | 'redirect' (default: 'popup')
73
73
  - Returns: `Promise<Session>` (popup) or never (redirect; page navigates)
74
74
 
@@ -86,8 +86,8 @@ await auth.signOut();
86
86
 
87
87
  ### auth.handleRedirectCallback(url?)
88
88
 
89
- - For redirect mode. Parses URL for code/state/nonce, exchanges for tokens. Omit `url` to use
90
- `window.location.href`.
89
+ - For redirect mode. Parses URL for code/state/nonce, builds session from payload (no exchange).
90
+ Omit `url` to use `window.location.href`.
91
91
 
92
92
  ### auth.onAuthStateChange(callback)
93
93
 
@@ -95,7 +95,10 @@ await auth.signOut();
95
95
 
96
96
  ## Hosted UI Contract (auth.mysocial.network)
97
97
 
98
- The SDK passes `return_origin` in the login URL params. This must match the dApp's origin
98
+ The SDK passes `return_origin`, `code_challenge_method: S256`, and `provider` in the login URL
99
+ params. `provider` is never empty: `'google'`, `'apple'`, `'facebook'`, `'twitch'`, or `'none'`
100
+ (home screen). The auth frontend generates PKCE server-side; the package does not send
101
+ code_challenge. `return_origin` must match the dApp's origin
99
102
  (`window.location.origin`) so the auth server can post the auth result to the correct target.
100
103
  The auth server uses `return_origin` as the `postMessage` targetOrigin when sending the result.
101
104
 
@@ -105,7 +108,7 @@ The popup page must call:
105
108
  window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT "*"
106
109
  ```
107
110
 
108
- **Success payload:**
111
+ **Success payload (MYSOCIAL_AUTH_RESULT is the final result; no exchange needed):**
109
112
 
110
113
  ```json
111
114
  {
@@ -114,10 +117,17 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
114
117
  "state": "...",
115
118
  "nonce": "...",
116
119
  "clientId": "...",
117
- "requestId": "..."
120
+ "requestId": "...",
121
+ "salt": "...",
122
+ "user": {},
123
+ "access_token": "...",
124
+ "refresh_token": "...",
125
+ "expires_at": 0
118
126
  }
119
127
  ```
120
128
 
129
+ `code` is the token (id_token or access_token). Optional: `salt`, `user`, `access_token`, `refresh_token`, `expires_at`.
130
+
121
131
  **Error payload:**
122
132
 
123
133
  ```json
@@ -125,7 +135,6 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
125
135
  "type": "MYSOCIAL_AUTH_ERROR",
126
136
  "error": "error_code_or_message",
127
137
  "state": "...",
128
- "nonce": "...",
129
138
  "clientId": "...",
130
139
  "requestId": "..."
131
140
  }
@@ -134,13 +143,13 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
134
143
  `return_origin` must never be trusted from the query param. The backend must validate it against the
135
144
  allowlist for `client_id`.
136
145
 
146
+ **Important:** The auth frontend and salt service handle the OAuth exchange internally. The package
147
+ does not call `/auth/exchange`. `MYSOCIAL_AUTH_RESULT` is the final result: `code` is the token.
148
+
137
149
  ## Backend Contract
138
150
 
139
151
  - `POST ${apiBaseUrl}/auth/request` (optional): `{ client_id, redirect_uri, return_origin }` →
140
152
  `{ request_id }`
141
- - `POST ${apiBaseUrl}/auth/exchange`:
142
- `{ code, code_verifier?, redirect_uri, state?, nonce?, request_id? }` →
143
- `{ access_token, refresh_token?, expires_in, user }`
144
153
  - `POST ${apiBaseUrl}/auth/refresh`: `{ refresh_token }` →
145
154
  `{ access_token, refresh_token?, expires_in, user }`
146
155
  - `POST ${apiBaseUrl}/auth/logout`
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;UAsBiB,YAAA;EAChB,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,OAAA;EACzC,OAAA,IAAW,OAAA;EACX,UAAA,IAAc,OAAA,CAAQ,OAAA;EACtB,OAAA,IAAW,OAAA,CAAQ,OAAA;EACnB,sBAAA,CAAuB,GAAA,YAAe,OAAA,CAAQ,OAAA;EAC9C,iBAAA,CAAkB,QAAA,EAAU,uBAAA;AAAA"}
1
+ {"version":3,"file":"auth.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;UAiBiB,YAAA;EAChB,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,OAAA;EACzC,OAAA,IAAW,OAAA;EACX,UAAA,IAAc,OAAA,CAAQ,OAAA;EACtB,OAAA,IAAW,OAAA,CAAQ,OAAA;EACnB,sBAAA,CAAuB,GAAA,YAAe,OAAA,CAAQ,OAAA;EAC9C,iBAAA,CAAkB,QAAA,EAAU,uBAAA;AAAA"}
package/dist/auth.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { REDIRECT_STATE_PREFIX, SESSION_KEY, createStorage, redirectStorage } from "./storage.mjs";
2
- import { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState } from "./pkce.mjs";
3
- import { exchangeCode, fetchRequestId, logout, refreshTokens } from "./exchange.mjs";
2
+ import { generateNonce, generateState } from "./pkce.mjs";
3
+ import { fetchRequestId, logout, refreshTokens } from "./exchange.mjs";
4
4
  import { openAuthPopup } from "./popup.mjs";
5
5
  import { mitt } from "@socialproof/utils";
6
6
 
@@ -67,8 +67,6 @@ function createAuth(config) {
67
67
  }
68
68
  const state = generateState();
69
69
  const nonce = generateNonce();
70
- const codeVerifier = await generateCodeVerifier();
71
- const codeChallenge = await generateCodeChallenge(codeVerifier);
72
70
  const returnOrigin = getReturnOrigin(config.redirectUri);
73
71
  let requestId;
74
72
  if (config.useRequestId) requestId = await fetchRequestId(config.apiBaseUrl, {
@@ -79,7 +77,6 @@ function createAuth(config) {
79
77
  const redirectState = {
80
78
  state,
81
79
  nonce,
82
- codeVerifier,
83
80
  requestId
84
81
  };
85
82
  const key = `${REDIRECT_STATE_PREFIX}${state}`;
@@ -91,8 +88,7 @@ function createAuth(config) {
91
88
  nonce,
92
89
  return_origin: returnOrigin,
93
90
  mode: "redirect",
94
- provider: provider ?? "",
95
- code_challenge: codeChallenge,
91
+ provider: provider ?? "none",
96
92
  code_challenge_method: "S256"
97
93
  });
98
94
  if (requestId) params.set("request_id", requestId);
@@ -126,6 +122,11 @@ function createAuth(config) {
126
122
  const state = parsed.searchParams.get("state");
127
123
  const nonce = parsed.searchParams.get("nonce");
128
124
  const requestId = parsed.searchParams.get("request_id") ?? void 0;
125
+ const salt = parsed.searchParams.get("salt") ?? void 0;
126
+ const accessToken = parsed.searchParams.get("access_token") ?? void 0;
127
+ const refreshToken = parsed.searchParams.get("refresh_token") ?? void 0;
128
+ const expiresAtParam = parsed.searchParams.get("expires_at");
129
+ const userParam = parsed.searchParams.get("user");
129
130
  if (!code || !state || !nonce) throw new Error("Missing code, state, or nonce in callback URL");
130
131
  const key = `${REDIRECT_STATE_PREFIX}${state}`;
131
132
  const raw = redirectStorage.get(key);
@@ -140,14 +141,17 @@ function createAuth(config) {
140
141
  throw new Error("Request ID mismatch");
141
142
  }
142
143
  redirectStorage.remove(key);
143
- const session = await exchangeCode(config.apiBaseUrl, {
144
- code,
145
- code_verifier: redirectState.codeVerifier,
146
- redirect_uri: config.redirectUri,
147
- state,
148
- nonce,
149
- request_id: requestId ?? redirectState.requestId
150
- });
144
+ let user = {};
145
+ if (userParam) try {
146
+ user = JSON.parse(decodeURIComponent(userParam));
147
+ } catch {}
148
+ const session = {
149
+ access_token: accessToken ?? code,
150
+ refresh_token: refreshToken,
151
+ user,
152
+ expires_at: expiresAtParam ? Number(expiresAtParam) : Date.now() + 36e5,
153
+ ...salt && { salt }
154
+ };
151
155
  await saveSession(session);
152
156
  emitter.emit("change", session);
153
157
  return session;
package/dist/auth.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { mitt, type Emitter } from '@socialproof/utils';\nimport type {\n\tMySocialAuthConfig,\n\tSignInOptions,\n\tSession,\n\tAuthStateChangeCallback,\n} from './types.js';\nimport { createStorage, redirectStorage, SESSION_KEY, REDIRECT_STATE_PREFIX } from './storage.js';\nimport { openAuthPopup } from './popup.js';\nimport { exchangeCode, refreshTokens, logout, fetchRequestId } from './exchange.js';\nimport {\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tgenerateState,\n\tgenerateNonce,\n} from './pkce.js';\n\ntype AuthEvents = { change: Session | null };\n\nexport interface MySocialAuth {\n\tsignIn(options?: SignInOptions): Promise<Session>;\n\tsignOut(): Promise<void>;\n\tgetSession(): Promise<Session | null>;\n\trefresh(): Promise<Session | null>;\n\thandleRedirectCallback(url?: string): Promise<Session>;\n\tonAuthStateChange(callback: AuthStateChangeCallback): () => void;\n}\n\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport function createAuth(config: MySocialAuthConfig): MySocialAuth {\n\tconst storage = createStorage(config.storage);\n\tconst emitter: Emitter<AuthEvents> = mitt<AuthEvents>();\n\n\tlet cachedSession: Session | null = null;\n\n\tasync function loadSession(): Promise<Session | null> {\n\t\tconst raw = storage.get(SESSION_KEY);\n\t\tif (!raw) return null;\n\t\ttry {\n\t\t\tconst session = JSON.parse(raw) as Session;\n\t\t\tif (session.expires_at && session.expires_at > Date.now()) {\n\t\t\t\treturn session;\n\t\t\t}\n\t\t\tif (session.refresh_token) {\n\t\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\t\tawait saveSession(refreshed);\n\t\t\t\temitter.emit('change', refreshed);\n\t\t\t\treturn refreshed;\n\t\t\t}\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\temitter.emit('change', null);\n\t\t\treturn null;\n\t\t} catch {\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function saveSession(session: Session): Promise<void> {\n\t\tcachedSession = session;\n\t\tstorage.set(SESSION_KEY, JSON.stringify(session));\n\t}\n\n\tasync function clearSession(): Promise<void> {\n\t\tcachedSession = null;\n\t\tstorage.remove(SESSION_KEY);\n\t}\n\n\treturn {\n\t\tasync signIn(options: SignInOptions = {}) {\n\t\t\tconst mode = options.mode ?? 'popup';\n\t\t\tconst provider = options.provider;\n\n\t\t\tif (mode === 'popup') {\n\t\t\t\tconst session = await openAuthPopup({\n\t\t\t\t\tapiBaseUrl: config.apiBaseUrl,\n\t\t\t\t\tauthOrigin: config.authOrigin,\n\t\t\t\t\tclientId: config.clientId,\n\t\t\t\t\tredirectUri: config.redirectUri,\n\t\t\t\t\tprovider,\n\t\t\t\t\ttimeout: config.popupTimeout ?? 120_000,\n\t\t\t\t\tuseRequestId: config.useRequestId ?? false,\n\t\t\t\t});\n\t\t\t\tawait saveSession(session);\n\t\t\t\temitter.emit('change', session);\n\t\t\t\treturn session;\n\t\t\t}\n\n\t\t\t// Redirect mode\n\t\t\tconst state = generateState();\n\t\t\tconst nonce = generateNonce();\n\t\t\tconst codeVerifier = await generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\t\t\tconst returnOrigin = getReturnOrigin(config.redirectUri);\n\n\t\t\tlet requestId: string | undefined;\n\t\t\tif (config.useRequestId) {\n\t\t\t\trequestId = await fetchRequestId(config.apiBaseUrl, {\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst redirectState = {\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\tcodeVerifier,\n\t\t\t\trequestId,\n\t\t\t};\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tredirectStorage.set(key, JSON.stringify(redirectState));\n\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\tmode: 'redirect',\n\t\t\t\tprovider: provider ?? '',\n\t\t\t\tcode_challenge: codeChallenge,\n\t\t\t\tcode_challenge_method: 'S256',\n\t\t\t});\n\t\t\tif (requestId) params.set('request_id', requestId);\n\n\t\t\tconst loginUrl = `${config.authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\t\t\twindow.location.href = loginUrl;\n\n\t\t\treturn new Promise<Session>(() => {}); // Never resolves; page navigates away\n\t\t},\n\n\t\tasync signOut() {\n\t\t\ttry {\n\t\t\t\tawait logout(config.apiBaseUrl);\n\t\t\t} catch {\n\t\t\t\t// Best-effort; clear local state regardless\n\t\t\t}\n\t\t\tawait clearSession();\n\t\t\temitter.emit('change', null);\n\t\t},\n\n\t\tasync getSession() {\n\t\t\tif (cachedSession && cachedSession.expires_at > Date.now()) {\n\t\t\t\treturn cachedSession;\n\t\t\t}\n\t\t\treturn loadSession();\n\t\t},\n\n\t\tasync refresh() {\n\t\t\tconst session = await loadSession();\n\t\t\tif (!session?.refresh_token) return null;\n\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\tawait saveSession(refreshed);\n\t\t\temitter.emit('change', refreshed);\n\t\t\treturn refreshed;\n\t\t},\n\n\t\tasync handleRedirectCallback(url?: string) {\n\t\t\tconst targetUrl = url ?? (typeof window !== 'undefined' ? window.location.href : '');\n\t\t\tconst parsed = new URL(targetUrl);\n\t\t\tconst code = parsed.searchParams.get('code');\n\t\t\tconst state = parsed.searchParams.get('state');\n\t\t\tconst nonce = parsed.searchParams.get('nonce');\n\t\t\tconst requestId = parsed.searchParams.get('request_id') ?? undefined;\n\n\t\t\tif (!code || !state || !nonce) {\n\t\t\t\tthrow new Error('Missing code, state, or nonce in callback URL');\n\t\t\t}\n\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tconst raw = redirectStorage.get(key);\n\t\t\tif (!raw) {\n\t\t\t\tthrow new Error('No matching redirect state found. Session may have expired.');\n\t\t\t}\n\n\t\t\tconst redirectState = JSON.parse(raw) as {\n\t\t\t\tstate: string;\n\t\t\t\tnonce: string;\n\t\t\t\tcodeVerifier: string;\n\t\t\t\trequestId?: string;\n\t\t\t};\n\n\t\t\tif (redirectState.state !== state || redirectState.nonce !== nonce) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('State or nonce mismatch');\n\t\t\t}\n\t\t\tif (requestId && redirectState.requestId !== requestId) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('Request ID mismatch');\n\t\t\t}\n\n\t\t\tredirectStorage.remove(key);\n\n\t\t\tconst session = await exchangeCode(config.apiBaseUrl, {\n\t\t\t\tcode,\n\t\t\t\tcode_verifier: redirectState.codeVerifier,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\trequest_id: requestId ?? redirectState.requestId,\n\t\t\t});\n\n\t\t\tawait saveSession(session);\n\t\t\temitter.emit('change', session);\n\t\t\treturn session;\n\t\t},\n\n\t\tonAuthStateChange(callback: AuthStateChangeCallback) {\n\t\t\temitter.on('change', callback);\n\t\t\treturn () => emitter.off('change', callback);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { mitt, type Emitter } from '@socialproof/utils';\nimport type {\n\tMySocialAuthConfig,\n\tSignInOptions,\n\tSession,\n\tAuthStateChangeCallback,\n} from './types.js';\nimport { createStorage, redirectStorage, SESSION_KEY, REDIRECT_STATE_PREFIX } from './storage.js';\nimport { openAuthPopup } from './popup.js';\nimport { refreshTokens, logout, fetchRequestId } from './exchange.js';\nimport { generateState, generateNonce } from './pkce.js';\n\ntype AuthEvents = { change: Session | null };\n\nexport interface MySocialAuth {\n\tsignIn(options?: SignInOptions): Promise<Session>;\n\tsignOut(): Promise<void>;\n\tgetSession(): Promise<Session | null>;\n\trefresh(): Promise<Session | null>;\n\thandleRedirectCallback(url?: string): Promise<Session>;\n\tonAuthStateChange(callback: AuthStateChangeCallback): () => void;\n}\n\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport function createAuth(config: MySocialAuthConfig): MySocialAuth {\n\tconst storage = createStorage(config.storage);\n\tconst emitter: Emitter<AuthEvents> = mitt<AuthEvents>();\n\n\tlet cachedSession: Session | null = null;\n\n\tasync function loadSession(): Promise<Session | null> {\n\t\tconst raw = storage.get(SESSION_KEY);\n\t\tif (!raw) return null;\n\t\ttry {\n\t\t\tconst session = JSON.parse(raw) as Session;\n\t\t\tif (session.expires_at && session.expires_at > Date.now()) {\n\t\t\t\treturn session;\n\t\t\t}\n\t\t\tif (session.refresh_token) {\n\t\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\t\tawait saveSession(refreshed);\n\t\t\t\temitter.emit('change', refreshed);\n\t\t\t\treturn refreshed;\n\t\t\t}\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\temitter.emit('change', null);\n\t\t\treturn null;\n\t\t} catch {\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function saveSession(session: Session): Promise<void> {\n\t\tcachedSession = session;\n\t\tstorage.set(SESSION_KEY, JSON.stringify(session));\n\t}\n\n\tasync function clearSession(): Promise<void> {\n\t\tcachedSession = null;\n\t\tstorage.remove(SESSION_KEY);\n\t}\n\n\treturn {\n\t\tasync signIn(options: SignInOptions = {}) {\n\t\t\tconst mode = options.mode ?? 'popup';\n\t\t\tconst provider = options.provider;\n\n\t\t\tif (mode === 'popup') {\n\t\t\t\tconst session = await openAuthPopup({\n\t\t\t\t\tapiBaseUrl: config.apiBaseUrl,\n\t\t\t\t\tauthOrigin: config.authOrigin,\n\t\t\t\t\tclientId: config.clientId,\n\t\t\t\t\tredirectUri: config.redirectUri,\n\t\t\t\t\tprovider,\n\t\t\t\t\ttimeout: config.popupTimeout ?? 120_000,\n\t\t\t\t\tuseRequestId: config.useRequestId ?? false,\n\t\t\t\t});\n\t\t\t\tawait saveSession(session);\n\t\t\t\temitter.emit('change', session);\n\t\t\t\treturn session;\n\t\t\t}\n\n\t\t\t// Redirect mode\n\t\t\tconst state = generateState();\n\t\t\tconst nonce = generateNonce();\n\t\t\tconst returnOrigin = getReturnOrigin(config.redirectUri);\n\n\t\t\tlet requestId: string | undefined;\n\t\t\tif (config.useRequestId) {\n\t\t\t\trequestId = await fetchRequestId(config.apiBaseUrl, {\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst redirectState = {\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\trequestId,\n\t\t\t};\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tredirectStorage.set(key, JSON.stringify(redirectState));\n\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\tmode: 'redirect',\n\t\t\t\tprovider: provider ?? 'none',\n\t\t\t\tcode_challenge_method: 'S256',\n\t\t\t});\n\t\t\tif (requestId) params.set('request_id', requestId);\n\n\t\t\tconst loginUrl = `${config.authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\t\t\twindow.location.href = loginUrl;\n\n\t\t\treturn new Promise<Session>(() => {}); // Never resolves; page navigates away\n\t\t},\n\n\t\tasync signOut() {\n\t\t\ttry {\n\t\t\t\tawait logout(config.apiBaseUrl);\n\t\t\t} catch {\n\t\t\t\t// Best-effort; clear local state regardless\n\t\t\t}\n\t\t\tawait clearSession();\n\t\t\temitter.emit('change', null);\n\t\t},\n\n\t\tasync getSession() {\n\t\t\tif (cachedSession && cachedSession.expires_at > Date.now()) {\n\t\t\t\treturn cachedSession;\n\t\t\t}\n\t\t\treturn loadSession();\n\t\t},\n\n\t\tasync refresh() {\n\t\t\tconst session = await loadSession();\n\t\t\tif (!session?.refresh_token) return null;\n\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\tawait saveSession(refreshed);\n\t\t\temitter.emit('change', refreshed);\n\t\t\treturn refreshed;\n\t\t},\n\n\t\tasync handleRedirectCallback(url?: string) {\n\t\t\tconst targetUrl = url ?? (typeof window !== 'undefined' ? window.location.href : '');\n\t\t\tconst parsed = new URL(targetUrl);\n\t\t\tconst code = parsed.searchParams.get('code');\n\t\t\tconst state = parsed.searchParams.get('state');\n\t\t\tconst nonce = parsed.searchParams.get('nonce');\n\t\t\tconst requestId = parsed.searchParams.get('request_id') ?? undefined;\n\t\t\tconst salt = parsed.searchParams.get('salt') ?? undefined;\n\t\t\tconst accessToken = parsed.searchParams.get('access_token') ?? undefined;\n\t\t\tconst refreshToken = parsed.searchParams.get('refresh_token') ?? undefined;\n\t\t\tconst expiresAtParam = parsed.searchParams.get('expires_at');\n\t\t\tconst userParam = parsed.searchParams.get('user');\n\n\t\t\tif (!code || !state || !nonce) {\n\t\t\t\tthrow new Error('Missing code, state, or nonce in callback URL');\n\t\t\t}\n\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tconst raw = redirectStorage.get(key);\n\t\t\tif (!raw) {\n\t\t\t\tthrow new Error('No matching redirect state found. Session may have expired.');\n\t\t\t}\n\n\t\t\tconst redirectState = JSON.parse(raw) as {\n\t\t\t\tstate: string;\n\t\t\t\tnonce: string;\n\t\t\t\trequestId?: string;\n\t\t\t};\n\n\t\t\tif (redirectState.state !== state || redirectState.nonce !== nonce) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('State or nonce mismatch');\n\t\t\t}\n\t\t\tif (requestId && redirectState.requestId !== requestId) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('Request ID mismatch');\n\t\t\t}\n\n\t\t\tredirectStorage.remove(key);\n\n\t\t\tlet user: Session['user'] = {};\n\t\t\tif (userParam) {\n\t\t\t\ttry {\n\t\t\t\t\tuser = JSON.parse(decodeURIComponent(userParam)) as Session['user'];\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid user param\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst session: Session = {\n\t\t\t\taccess_token: accessToken ?? code,\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tuser,\n\t\t\t\texpires_at: expiresAtParam ? Number(expiresAtParam) : Date.now() + 3600_000,\n\t\t\t\t...(salt && { salt }),\n\t\t\t};\n\n\t\t\tawait saveSession(session);\n\t\t\temitter.emit('change', session);\n\t\t\treturn session;\n\t\t},\n\n\t\tonAuthStateChange(callback: AuthStateChangeCallback) {\n\t\t\temitter.on('change', callback);\n\t\t\treturn () => emitter.off('change', callback);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
package/dist/exchange.mjs CHANGED
@@ -13,27 +13,6 @@ async function fetchRequestId(apiBaseUrl, body) {
13
13
  }
14
14
  return (await res.json()).request_id;
15
15
  }
16
- /** Exchange auth code for tokens */
17
- async function exchangeCode(apiBaseUrl, body) {
18
- const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/exchange`;
19
- const res = await fetch(url, {
20
- method: "POST",
21
- headers: { "Content-Type": "application/json" },
22
- body: JSON.stringify(body)
23
- });
24
- if (!res.ok) {
25
- const text = await res.text();
26
- throw new Error(`Token exchange failed: ${res.status} ${text}`);
27
- }
28
- const data = await res.json();
29
- const expires_at = Date.now() + data.expires_in * 1e3;
30
- return {
31
- access_token: data.access_token,
32
- refresh_token: data.refresh_token,
33
- expires_at,
34
- user: data.user
35
- };
36
- }
37
16
  /** Refresh access token using refresh_token */
38
17
  async function refreshTokens(apiBaseUrl, refreshToken) {
39
18
  const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/refresh`;
@@ -69,5 +48,5 @@ async function logout(apiBaseUrl) {
69
48
  }
70
49
 
71
50
  //#endregion
72
- export { exchangeCode, fetchRequestId, logout, refreshTokens };
51
+ export { fetchRequestId, logout, refreshTokens };
73
52
  //# sourceMappingURL=exchange.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exchange.mjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n\tAuthRequestRequest,\n\tAuthRequestResponse,\n\tExchangeRequest,\n\tExchangeResponse,\n\tSession,\n} from './types.js';\n\n/** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */\nexport async function fetchRequestId(\n\tapiBaseUrl: string,\n\tbody: AuthRequestRequest,\n): Promise<string> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/request`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Auth request failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as AuthRequestResponse;\n\treturn data.request_id;\n}\n\n/** Exchange auth code for tokens */\nexport async function exchangeCode(apiBaseUrl: string, body: ExchangeRequest): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/exchange`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token exchange failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Refresh access token using refresh_token */\nexport async function refreshTokens(apiBaseUrl: string, refreshToken: string): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/refresh`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token refresh failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token ?? refreshToken,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Logout / invalidate session on backend */\nexport async function logout(apiBaseUrl: string): Promise<void> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/logout`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Logout failed: ${res.status} ${text}`);\n\t}\n}\n"],"mappings":";;AAYA,eAAsB,eACrB,YACA,MACkB;CAClB,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO;;AAG9D,SADc,MAAM,IAAI,MAAM,EAClB;;;AAIb,eAAsB,aAAa,YAAoB,MAAyC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,0BAA0B,IAAI,OAAO,GAAG,OAAO;;CAEhE,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,cAAc,YAAoB,cAAwC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,EAAE,eAAe,cAAc,CAAC;EACrD,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,yBAAyB,IAAI,OAAO,GAAG,OAAO;;CAE/D,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB;EACrC;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,OAAO,YAAmC;CAC/D,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,kBAAkB,IAAI,OAAO,GAAG,OAAO"}
1
+ {"version":3,"file":"exchange.mjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n\tAuthRequestRequest,\n\tAuthRequestResponse,\n\tExchangeRequest,\n\tExchangeResponse,\n\tSession,\n} from './types.js';\n\n/** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */\nexport async function fetchRequestId(\n\tapiBaseUrl: string,\n\tbody: AuthRequestRequest,\n): Promise<string> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/request`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Auth request failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as AuthRequestResponse;\n\treturn data.request_id;\n}\n\n/** Exchange auth code for tokens */\nexport async function exchangeCode(apiBaseUrl: string, body: ExchangeRequest): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/exchange`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token exchange failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Refresh access token using refresh_token */\nexport async function refreshTokens(apiBaseUrl: string, refreshToken: string): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/refresh`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token refresh failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token ?? refreshToken,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Logout / invalidate session on backend */\nexport async function logout(apiBaseUrl: string): Promise<void> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/logout`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Logout failed: ${res.status} ${text}`);\n\t}\n}\n"],"mappings":";;AAYA,eAAsB,eACrB,YACA,MACkB;CAClB,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO;;AAG9D,SADc,MAAM,IAAI,MAAM,EAClB;;;AA0Bb,eAAsB,cAAc,YAAoB,cAAwC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,EAAE,eAAe,cAAc,CAAC;EACrD,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,yBAAyB,IAAI,OAAO,GAAG,OAAO;;CAE/D,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB;EACrC;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,OAAO,YAAmC;CAC/D,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,kBAAkB,IAAI,OAAO,GAAG,OAAO"}
package/dist/pkce.mjs CHANGED
@@ -1,22 +1,5 @@
1
1
  //#region src/pkce.ts
2
2
  /**
3
- * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).
4
- * Used for PKCE in OAuth flows.
5
- */
6
- async function generateCodeVerifier() {
7
- const array = new Uint8Array(32);
8
- crypto.getRandomValues(array);
9
- return base64UrlEncode(array);
10
- }
11
- /**
12
- * Generate code challenge from verifier using SHA-256 (S256 method).
13
- */
14
- async function generateCodeChallenge(verifier) {
15
- const data = new TextEncoder().encode(verifier);
16
- const hash = await crypto.subtle.digest("SHA-256", data);
17
- return base64UrlEncode(new Uint8Array(hash));
18
- }
19
- /**
20
3
  * Generate a random state (128 bits+ entropy) for CSRF protection.
21
4
  */
22
5
  function generateState() {
@@ -37,5 +20,5 @@ function base64UrlEncode(array) {
37
20
  }
38
21
 
39
22
  //#endregion
40
- export { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState };
23
+ export { generateNonce, generateState };
41
24
  //# sourceMappingURL=pkce.mjs.map
package/dist/pkce.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pkce.mjs","names":[],"sources":["../src/pkce.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).\n * Used for PKCE in OAuth flows.\n */\nexport async function generateCodeVerifier(): Promise<string> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate code challenge from verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(new Uint8Array(hash));\n}\n\n/**\n * Generate a random state (128 bits+ entropy) for CSRF protection.\n */\nexport function generateState(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate a random nonce (128 bits+ entropy) for replay protection.\n */\nexport function generateNonce(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\nfunction base64UrlEncode(array: Uint8Array): string {\n\tconst base64 = btoa(String.fromCharCode(...array));\n\treturn base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n"],"mappings":";;;;;AAOA,eAAsB,uBAAwC;CAC7D,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,eAAsB,sBAAsB,UAAmC;CAE9E,MAAM,OADU,IAAI,aAAa,CACZ,OAAO,SAAS;CACrC,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;AACxD,QAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC;;;;;AAM7C,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;AAG9B,SAAS,gBAAgB,OAA2B;AAEnD,QADe,KAAK,OAAO,aAAa,GAAG,MAAM,CAAC,CACpC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG"}
1
+ {"version":3,"file":"pkce.mjs","names":[],"sources":["../src/pkce.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).\n * Used for PKCE in OAuth flows.\n */\nexport async function generateCodeVerifier(): Promise<string> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate code challenge from verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(new Uint8Array(hash));\n}\n\n/**\n * Generate a random state (128 bits+ entropy) for CSRF protection.\n */\nexport function generateState(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate a random nonce (128 bits+ entropy) for replay protection.\n */\nexport function generateNonce(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\nfunction base64UrlEncode(array: Uint8Array): string {\n\tconst base64 = btoa(String.fromCharCode(...array));\n\treturn base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n"],"mappings":";;;;AA0BA,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;AAG9B,SAAS,gBAAgB,OAA2B;AAEnD,QADe,KAAK,OAAO,aAAa,GAAG,MAAM,CAAC,CACpC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG"}
package/dist/popup.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AuthTimeoutError, InvalidStateError, PopupBlockedError, PopupClosedError } from "./errors.mjs";
2
- import { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState } from "./pkce.mjs";
2
+ import { generateNonce, generateState } from "./pkce.mjs";
3
3
  import { getPopupFeatures } from "./popup-utils.mjs";
4
- import { exchangeCode, fetchRequestId } from "./exchange.mjs";
4
+ import { fetchRequestId } from "./exchange.mjs";
5
5
 
6
6
  //#region src/popup.ts
7
7
  /** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */
@@ -28,13 +28,6 @@ async function openAuthPopup(options) {
28
28
  redirect_uri: redirectUri,
29
29
  return_origin: returnOrigin
30
30
  });
31
- const { codeVerifier, codeChallenge } = await (async () => {
32
- const verifier = await generateCodeVerifier();
33
- return {
34
- codeVerifier: verifier,
35
- codeChallenge: await generateCodeChallenge(verifier)
36
- };
37
- })();
38
31
  const features = getPopupFeatures(420, 720);
39
32
  const popup = window.open("about:blank", "_blank", features);
40
33
  if (!popup || popup.closed) throw new PopupBlockedError();
@@ -45,8 +38,7 @@ async function openAuthPopup(options) {
45
38
  nonce,
46
39
  return_origin: returnOrigin,
47
40
  mode: "popup",
48
- provider: provider ?? "",
49
- code_challenge: codeChallenge,
41
+ provider: provider ?? "none",
50
42
  code_challenge_method: "S256"
51
43
  });
52
44
  if (requestId) params.set("request_id", requestId);
@@ -86,14 +78,13 @@ async function openAuthPopup(options) {
86
78
  try {
87
79
  popup.close();
88
80
  } catch {}
89
- exchangeCode(apiBaseUrl, {
90
- code: msg.code,
91
- code_verifier: codeVerifier,
92
- redirect_uri: redirectUri,
93
- state,
94
- nonce,
95
- request_id: requestId
96
- }).then(resolve).catch(reject);
81
+ resolve({
82
+ access_token: msg.access_token ?? msg.code,
83
+ refresh_token: msg.refresh_token,
84
+ user: msg.user ?? {},
85
+ expires_at: msg.expires_at ?? Date.now() + 36e5,
86
+ ...msg.salt && { salt: msg.salt }
87
+ });
97
88
  } else if (data.type === "MYSOCIAL_AUTH_ERROR") {
98
89
  const msg = data;
99
90
  cleanup();
@@ -1 +1 @@
1
- {"version":3,"file":"popup.mjs","names":[],"sources":["../src/popup.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AuthProvider, AuthResultMessage, AuthErrorMessage } from './types.js';\nimport {\n\tAuthTimeoutError,\n\tInvalidStateError,\n\tPopupBlockedError,\n\tPopupClosedError,\n} from './errors.js';\nimport {\n\tgenerateCodeChallenge,\n\tgenerateCodeVerifier,\n\tgenerateNonce,\n\tgenerateState,\n} from './pkce.js';\nimport { getPopupFeatures } from './popup-utils.js';\nimport { exchangeCode, fetchRequestId } from './exchange.js';\nimport type { Session } from './types.js';\n\n/** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport interface OpenPopupOptions {\n\tapiBaseUrl: string;\n\tauthOrigin: string;\n\tclientId: string;\n\tredirectUri: string;\n\tprovider?: AuthProvider;\n\ttimeout?: number;\n\tuseRequestId?: boolean;\n}\n\n/**\n * Open auth popup and wait for postMessage. Safari: open with about:blank first, then set location.\n * Returns session on success. Rejects on error, timeout, or user close.\n */\nexport async function openAuthPopup(options: OpenPopupOptions): Promise<Session> {\n\tconst {\n\t\tapiBaseUrl,\n\t\tauthOrigin,\n\t\tclientId,\n\t\tredirectUri,\n\t\tprovider,\n\t\ttimeout = 120_000,\n\t\tuseRequestId = false,\n\t} = options;\n\n\tconst state = generateState();\n\tconst nonce = generateNonce();\n\tconst returnOrigin =\n\t\ttypeof window !== 'undefined' ? window.location.origin : getReturnOrigin(redirectUri);\n\n\tlet requestId: string | undefined;\n\tif (useRequestId) {\n\t\trequestId = await fetchRequestId(apiBaseUrl, {\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t\treturn_origin: returnOrigin,\n\t\t});\n\t}\n\n\tconst { codeVerifier, codeChallenge } = await (async () => {\n\t\tconst verifier = await generateCodeVerifier();\n\t\tconst challenge = await generateCodeChallenge(verifier);\n\t\treturn { codeVerifier: verifier, codeChallenge: challenge };\n\t})();\n\n\t// Open popup immediately (Safari requires user gesture). Use about:blank, then set location.\n\tconst features = getPopupFeatures(420, 720);\n\tconst popup = window.open('about:blank', '_blank', features);\n\n\tif (!popup || popup.closed) {\n\t\tthrow new PopupBlockedError();\n\t}\n\n\tconst params = new URLSearchParams({\n\t\tclient_id: clientId,\n\t\tredirect_uri: redirectUri,\n\t\tstate,\n\t\tnonce,\n\t\treturn_origin: returnOrigin,\n\t\tmode: 'popup',\n\t\tprovider: provider ?? '',\n\t\tcode_challenge: codeChallenge,\n\t\tcode_challenge_method: 'S256',\n\t});\n\tif (requestId) params.set('request_id', requestId);\n\n\tconst loginUrl = `${authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\n\t// Set location after popup is open (Safari-safe)\n\tpopup.location.href = loginUrl;\n\n\treturn new Promise<Session>((resolve, reject) => {\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\t\tlet closedCheckId: ReturnType<typeof setInterval> | null = null;\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (closedCheckId) clearInterval(closedCheckId);\n\t\t\twindow.removeEventListener('message', handler);\n\t\t};\n\n\t\tconst handler = (event: MessageEvent) => {\n\t\t\tif (event.origin !== authOrigin) return;\n\t\t\tif (event.source !== popup) return;\n\n\t\t\tconst data = event.data;\n\t\t\tif (!data || typeof data !== 'object' || !data.type) return;\n\n\t\t\tif (data.type === 'MYSOCIAL_AUTH_RESULT') {\n\t\t\t\tconst msg = data as AuthResultMessage;\n\t\t\t\tif (msg.state !== state || msg.nonce !== nonce) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (msg.clientId !== clientId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (requestId && msg.requestId !== requestId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed by auth server\n\t\t\t\t}\n\n\t\t\t\texchangeCode(apiBaseUrl, {\n\t\t\t\t\tcode: msg.code,\n\t\t\t\t\tcode_verifier: codeVerifier,\n\t\t\t\t\tredirect_uri: redirectUri,\n\t\t\t\t\tstate,\n\t\t\t\t\tnonce,\n\t\t\t\t\trequest_id: requestId,\n\t\t\t\t})\n\t\t\t\t\t.then(resolve)\n\t\t\t\t\t.catch(reject);\n\t\t\t} else if (data.type === 'MYSOCIAL_AUTH_ERROR') {\n\t\t\t\tconst msg = data as AuthErrorMessage;\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed\n\t\t\t\t}\n\t\t\t\treject(new Error(msg.error ?? 'Authentication failed'));\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener('message', handler);\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\tpopup.close();\n\t\t\t} catch {\n\t\t\t\t// Popup may already be closed\n\t\t\t}\n\t\t\treject(new AuthTimeoutError());\n\t\t}, timeout);\n\n\t\tclosedCheckId = setInterval(() => {\n\t\t\tif (popup.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(new PopupClosedError());\n\t\t\t}\n\t\t}, 200);\n\t});\n}\n"],"mappings":";;;;;;;AAqBA,SAAS,gBAAgB,aAA6B;AACrD,KAAI;EACH,MAAM,IAAI,IAAI,IAAI,YAAY;AAC9B,SAAO,GAAG,EAAE,SAAS,IAAI,EAAE;SACpB;AACP,SAAO;;;;;;;AAkBT,eAAsB,cAAc,SAA6C;CAChF,MAAM,EACL,YACA,YACA,UACA,aACA,UACA,UAAU,MACV,eAAe,UACZ;CAEJ,MAAM,QAAQ,eAAe;CAC7B,MAAM,QAAQ,eAAe;CAC7B,MAAM,eACL,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,gBAAgB,YAAY;CAEtF,IAAI;AACJ,KAAI,aACH,aAAY,MAAM,eAAe,YAAY;EAC5C,WAAW;EACX,cAAc;EACd,eAAe;EACf,CAAC;CAGH,MAAM,EAAE,cAAc,kBAAkB,OAAO,YAAY;EAC1D,MAAM,WAAW,MAAM,sBAAsB;AAE7C,SAAO;GAAE,cAAc;GAAU,eADf,MAAM,sBAAsB,SAAS;GACI;KACxD;CAGJ,MAAM,WAAW,iBAAiB,KAAK,IAAI;CAC3C,MAAM,QAAQ,OAAO,KAAK,eAAe,UAAU,SAAS;AAE5D,KAAI,CAAC,SAAS,MAAM,OACnB,OAAM,IAAI,mBAAmB;CAG9B,MAAM,SAAS,IAAI,gBAAgB;EAClC,WAAW;EACX,cAAc;EACd;EACA;EACA,eAAe;EACf,MAAM;EACN,UAAU,YAAY;EACtB,gBAAgB;EAChB,uBAAuB;EACvB,CAAC;AACF,KAAI,UAAW,QAAO,IAAI,cAAc,UAAU;CAElD,MAAM,WAAW,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC,SAAS,OAAO,UAAU;AAG5E,OAAM,SAAS,OAAO;AAEtB,QAAO,IAAI,SAAkB,SAAS,WAAW;EAChD,IAAI,YAAkD;EACtD,IAAI,gBAAuD;EAE3D,MAAM,gBAAgB;AACrB,OAAI,UAAW,cAAa,UAAU;AACtC,OAAI,cAAe,eAAc,cAAc;AAC/C,UAAO,oBAAoB,WAAW,QAAQ;;EAG/C,MAAM,WAAW,UAAwB;AACxC,OAAI,MAAM,WAAW,WAAY;AACjC,OAAI,MAAM,WAAW,MAAO;GAE5B,MAAM,OAAO,MAAM;AACnB,OAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,OAAI,KAAK,SAAS,wBAAwB;IACzC,MAAM,MAAM;AACZ,QAAI,IAAI,UAAU,SAAS,IAAI,UAAU,OAAO;AAC/C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,IAAI,aAAa,UAAU;AAC9B,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,aAAa,IAAI,cAAc,WAAW;AAC7C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAGD,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAIR,iBAAa,YAAY;KACxB,MAAM,IAAI;KACV,eAAe;KACf,cAAc;KACd;KACA;KACA,YAAY;KACZ,CAAC,CACA,KAAK,QAAQ,CACb,MAAM,OAAO;cACL,KAAK,SAAS,uBAAuB;IAC/C,MAAM,MAAM;AACZ,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAGR,WAAO,IAAI,MAAM,IAAI,SAAS,wBAAwB,CAAC;;;AAIzD,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,cAAY,iBAAiB;AAC5B,YAAS;AACT,OAAI;AACH,UAAM,OAAO;WACN;AAGR,UAAO,IAAI,kBAAkB,CAAC;KAC5B,QAAQ;AAEX,kBAAgB,kBAAkB;AACjC,OAAI,MAAM,QAAQ;AACjB,aAAS;AACT,WAAO,IAAI,kBAAkB,CAAC;;KAE7B,IAAI;GACN"}
1
+ {"version":3,"file":"popup.mjs","names":[],"sources":["../src/popup.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AuthProvider, AuthResultMessage, AuthErrorMessage } from './types.js';\nimport {\n\tAuthTimeoutError,\n\tInvalidStateError,\n\tPopupBlockedError,\n\tPopupClosedError,\n} from './errors.js';\nimport { generateNonce, generateState } from './pkce.js';\nimport { getPopupFeatures } from './popup-utils.js';\nimport { fetchRequestId } from './exchange.js';\nimport type { Session } from './types.js';\n\n/** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport interface OpenPopupOptions {\n\tapiBaseUrl: string;\n\tauthOrigin: string;\n\tclientId: string;\n\tredirectUri: string;\n\tprovider?: AuthProvider;\n\ttimeout?: number;\n\tuseRequestId?: boolean;\n}\n\n/**\n * Open auth popup and wait for postMessage. Safari: open with about:blank first, then set location.\n * Returns session on success. Rejects on error, timeout, or user close.\n */\nexport async function openAuthPopup(options: OpenPopupOptions): Promise<Session> {\n\tconst {\n\t\tapiBaseUrl,\n\t\tauthOrigin,\n\t\tclientId,\n\t\tredirectUri,\n\t\tprovider,\n\t\ttimeout = 120_000,\n\t\tuseRequestId = false,\n\t} = options;\n\n\tconst state = generateState();\n\tconst nonce = generateNonce();\n\tconst returnOrigin =\n\t\ttypeof window !== 'undefined' ? window.location.origin : getReturnOrigin(redirectUri);\n\n\tlet requestId: string | undefined;\n\tif (useRequestId) {\n\t\trequestId = await fetchRequestId(apiBaseUrl, {\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t\treturn_origin: returnOrigin,\n\t\t});\n\t}\n\n\t// Open popup immediately (Safari requires user gesture). Use about:blank, then set location.\n\tconst features = getPopupFeatures(420, 720);\n\tconst popup = window.open('about:blank', '_blank', features);\n\n\tif (!popup || popup.closed) {\n\t\tthrow new PopupBlockedError();\n\t}\n\n\tconst params = new URLSearchParams({\n\t\tclient_id: clientId,\n\t\tredirect_uri: redirectUri,\n\t\tstate,\n\t\tnonce,\n\t\treturn_origin: returnOrigin,\n\t\tmode: 'popup',\n\t\tprovider: provider ?? 'none',\n\t\tcode_challenge_method: 'S256',\n\t});\n\tif (requestId) params.set('request_id', requestId);\n\n\tconst loginUrl = `${authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\n\t// Set location after popup is open (Safari-safe)\n\tpopup.location.href = loginUrl;\n\n\treturn new Promise<Session>((resolve, reject) => {\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\t\tlet closedCheckId: ReturnType<typeof setInterval> | null = null;\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (closedCheckId) clearInterval(closedCheckId);\n\t\t\twindow.removeEventListener('message', handler);\n\t\t};\n\n\t\tconst handler = (event: MessageEvent) => {\n\t\t\tif (event.origin !== authOrigin) return;\n\t\t\tif (event.source !== popup) return;\n\n\t\t\tconst data = event.data;\n\t\t\tif (!data || typeof data !== 'object' || !data.type) return;\n\n\t\t\tif (data.type === 'MYSOCIAL_AUTH_RESULT') {\n\t\t\t\tconst msg = data as AuthResultMessage;\n\t\t\t\tif (msg.state !== state || msg.nonce !== nonce) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (msg.clientId !== clientId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (requestId && msg.requestId !== requestId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed by auth server\n\t\t\t\t}\n\n\t\t\t\tconst session: Session = {\n\t\t\t\t\taccess_token: msg.access_token ?? msg.code,\n\t\t\t\t\trefresh_token: msg.refresh_token,\n\t\t\t\t\tuser: msg.user ?? {},\n\t\t\t\t\texpires_at: msg.expires_at ?? Date.now() + 3600_000,\n\t\t\t\t\t...(msg.salt && { salt: msg.salt }),\n\t\t\t\t};\n\t\t\t\tresolve(session);\n\t\t\t} else if (data.type === 'MYSOCIAL_AUTH_ERROR') {\n\t\t\t\tconst msg = data as AuthErrorMessage;\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed\n\t\t\t\t}\n\t\t\t\treject(new Error(msg.error ?? 'Authentication failed'));\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener('message', handler);\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\tpopup.close();\n\t\t\t} catch {\n\t\t\t\t// Popup may already be closed\n\t\t\t}\n\t\t\treject(new AuthTimeoutError());\n\t\t}, timeout);\n\n\t\tclosedCheckId = setInterval(() => {\n\t\t\tif (popup.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(new PopupClosedError());\n\t\t\t}\n\t\t}, 200);\n\t});\n}\n"],"mappings":";;;;;;;AAgBA,SAAS,gBAAgB,aAA6B;AACrD,KAAI;EACH,MAAM,IAAI,IAAI,IAAI,YAAY;AAC9B,SAAO,GAAG,EAAE,SAAS,IAAI,EAAE;SACpB;AACP,SAAO;;;;;;;AAkBT,eAAsB,cAAc,SAA6C;CAChF,MAAM,EACL,YACA,YACA,UACA,aACA,UACA,UAAU,MACV,eAAe,UACZ;CAEJ,MAAM,QAAQ,eAAe;CAC7B,MAAM,QAAQ,eAAe;CAC7B,MAAM,eACL,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,gBAAgB,YAAY;CAEtF,IAAI;AACJ,KAAI,aACH,aAAY,MAAM,eAAe,YAAY;EAC5C,WAAW;EACX,cAAc;EACd,eAAe;EACf,CAAC;CAIH,MAAM,WAAW,iBAAiB,KAAK,IAAI;CAC3C,MAAM,QAAQ,OAAO,KAAK,eAAe,UAAU,SAAS;AAE5D,KAAI,CAAC,SAAS,MAAM,OACnB,OAAM,IAAI,mBAAmB;CAG9B,MAAM,SAAS,IAAI,gBAAgB;EAClC,WAAW;EACX,cAAc;EACd;EACA;EACA,eAAe;EACf,MAAM;EACN,UAAU,YAAY;EACtB,uBAAuB;EACvB,CAAC;AACF,KAAI,UAAW,QAAO,IAAI,cAAc,UAAU;CAElD,MAAM,WAAW,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC,SAAS,OAAO,UAAU;AAG5E,OAAM,SAAS,OAAO;AAEtB,QAAO,IAAI,SAAkB,SAAS,WAAW;EAChD,IAAI,YAAkD;EACtD,IAAI,gBAAuD;EAE3D,MAAM,gBAAgB;AACrB,OAAI,UAAW,cAAa,UAAU;AACtC,OAAI,cAAe,eAAc,cAAc;AAC/C,UAAO,oBAAoB,WAAW,QAAQ;;EAG/C,MAAM,WAAW,UAAwB;AACxC,OAAI,MAAM,WAAW,WAAY;AACjC,OAAI,MAAM,WAAW,MAAO;GAE5B,MAAM,OAAO,MAAM;AACnB,OAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,OAAI,KAAK,SAAS,wBAAwB;IACzC,MAAM,MAAM;AACZ,QAAI,IAAI,UAAU,SAAS,IAAI,UAAU,OAAO;AAC/C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,IAAI,aAAa,UAAU;AAC9B,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,aAAa,IAAI,cAAc,WAAW;AAC7C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAGD,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAWR,YAPyB;KACxB,cAAc,IAAI,gBAAgB,IAAI;KACtC,eAAe,IAAI;KACnB,MAAM,IAAI,QAAQ,EAAE;KACpB,YAAY,IAAI,cAAc,KAAK,KAAK,GAAG;KAC3C,GAAI,IAAI,QAAQ,EAAE,MAAM,IAAI,MAAM;KAClC,CACe;cACN,KAAK,SAAS,uBAAuB;IAC/C,MAAM,MAAM;AACZ,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAGR,WAAO,IAAI,MAAM,IAAI,SAAS,wBAAwB,CAAC;;;AAIzD,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,cAAY,iBAAiB;AAC5B,YAAS;AACT,OAAI;AACH,UAAM,OAAO;WACN;AAGR,UAAO,IAAI,kBAAkB,CAAC;KAC5B,QAAQ;AAEX,kBAAgB,kBAAkB;AACjC,OAAI,MAAM,QAAQ;AACjB,aAAS;AACT,WAAO,IAAI,kBAAkB,CAAC;;KAE7B,IAAI;GACN"}
package/dist/types.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/types.d.ts
2
- /** OAuth provider options for Login with MySocial */
3
- type AuthProvider = 'google' | 'apple' | 'facebook' | 'twitch';
2
+ /** OAuth provider options for Login with MySocial. Use 'none' for home screen (user picks provider). */
3
+ type AuthProvider = 'google' | 'apple' | 'facebook' | 'twitch' | 'none';
4
4
  /** Auth flow mode: popup (opens window) or redirect (navigates away) */
5
5
  type AuthMode = 'popup' | 'redirect';
6
6
  /** User object returned from exchange/refresh */
@@ -16,6 +16,7 @@ interface Session {
16
16
  refresh_token?: string;
17
17
  expires_at: number;
18
18
  user: AuthUser;
19
+ salt?: string;
19
20
  }
20
21
  /** Storage adapter for persisting session (memory, sessionStorage, or custom) */
21
22
  interface StorageAdapter {
@@ -39,6 +40,7 @@ interface MySocialAuthConfig {
39
40
  }
40
41
  /** Sign-in options */
41
42
  interface SignInOptions {
43
+ /** Provider to use. Default 'none' shows auth home screen. */
42
44
  provider?: AuthProvider;
43
45
  mode?: AuthMode;
44
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KAIY,YAAA;;KAGA,QAAA;;UAGK,QAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAJF;AAAA,UAQiB,OAAA;EAChB,YAAA;EACA,aAAA;EACA,UAAA;EACA,IAAA,EAAM,QAAA;AAAA;;UAIU,cAAA;EAChB,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;AAAA;;KAII,aAAA,0BAAuC,cAAA;;UAGlC,kBAAA;EAChB,UAAA;EACA,UAAA;EACA,QAAA;EACA,WAAA;EACA,OAAA,GAAU,aAAA;EAfoB;EAiB9B,YAAA;EAjB8B;EAmB9B,YAAA;AAAA;;UAIgB,aAAA;EAChB,QAAA,GAAW,YAAA;EACX,IAAA,GAAO,QAAA;AAAA;;KAII,uBAAA,IAA2B,OAAA,EAAS,OAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KAIY,YAAA;;KAGA,QAAA;;UAGK,QAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAJF;AAAA,UAQiB,OAAA;EAChB,YAAA;EACA,aAAA;EACA,UAAA;EACA,IAAA,EAAM,QAAA;EACN,IAAA;AAAA;;UAIgB,cAAA;EAChB,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;AAAA;;KAII,aAAA,0BAAuC,cAAA;;UAGlC,kBAAA;EAChB,UAAA;EACA,UAAA;EACA,QAAA;EACA,WAAA;EACA,OAAA,GAAU,aAAA;EAfoB;EAiB9B,YAAA;EAjB8B;EAmB9B,YAAA;AAAA;;UAIgB,aAAA;EArBC;EAuBjB,QAAA,GAAW,YAAA;EACX,IAAA,GAAO,QAAA;AAAA;;KAII,uBAAA,IAA2B,OAAA,EAAS,OAAA"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@socialproof/mysocial-auth",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "MySocial Auth SDK for Login with MySocial via popup or redirect",
6
6
  "license": "Apache-2.0",
7
- "author": "Mysten Labs <build@socialprooflabs.com>",
7
+ "author": "The Social Proof Foundation, LLC. <build@socialprooflabs.com>",
8
8
  "type": "module",
9
9
  "main": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.mts",
@@ -22,12 +22,12 @@
22
22
  ],
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/MystenLabs/ts-sdks.git"
25
+ "url": "git+https://github.com/the-social-proof-foundation/myso-ts-sdks.git"
26
26
  },
27
27
  "bugs": {
28
- "url": "https://github.com/mystenlabs/ts-sdks/issues"
28
+ "url": "https://github.com/the-social-proof-foundation/myso-ts-sdks/issues"
29
29
  },
30
- "homepage": "https://github.com/MystenLabs/ts-sdks/tree/main/packages/mysocial-auth#readme",
30
+ "homepage": "https://github.com/the-social-proof-foundation/myso-ts-sdks/tree/main/packages/mysocial-auth#readme",
31
31
  "devDependencies": {
32
32
  "@types/node": "^25.0.8",
33
33
  "happy-dom": "^20.1.0",