@socialproof/mysocial-auth 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @socialproof/mysocial-auth
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Fixed auth route
8
+
3
9
  ## 0.2.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -95,7 +95,9 @@ 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` and `code_challenge_method: S256` in the login URL params. The auth
99
+ frontend generates PKCE (code_challenge/code_verifier) server-side for Google/Apple; the package
100
+ does not send them. `return_origin` must match the dApp's origin
99
101
  (`window.location.origin`) so the auth server can post the auth result to the correct target.
100
102
  The auth server uses `return_origin` as the `postMessage` targetOrigin when sending the result.
101
103
 
@@ -125,7 +127,6 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
125
127
  "type": "MYSOCIAL_AUTH_ERROR",
126
128
  "error": "error_code_or_message",
127
129
  "state": "...",
128
- "nonce": "...",
129
130
  "clientId": "...",
130
131
  "requestId": "..."
131
132
  }
@@ -134,12 +135,15 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
134
135
  `return_origin` must never be trusted from the query param. The backend must validate it against the
135
136
  allowlist for `client_id`.
136
137
 
138
+ **Important:** The package calls your app's backend (`apiBaseUrl`), never the auth frontend. The auth
139
+ frontend's `/api/auth/callback` is internal and is not used by this SDK.
140
+
137
141
  ## Backend Contract
138
142
 
139
143
  - `POST ${apiBaseUrl}/auth/request` (optional): `{ client_id, redirect_uri, return_origin }` →
140
144
  `{ request_id }`
141
145
  - `POST ${apiBaseUrl}/auth/exchange`:
142
- `{ code, code_verifier?, redirect_uri, state?, nonce?, request_id? }` →
146
+ `{ code, redirect_uri, state?, nonce?, request_id? }` (your app's backend; code_verifier not sent—auth frontend handles OAuth/salt internally)
143
147
  `{ access_token, refresh_token?, expires_in, user }`
144
148
  - `POST ${apiBaseUrl}/auth/refresh`: `{ refresh_token }` →
145
149
  `{ access_token, refresh_token?, expires_in, user }`
@@ -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,5 +1,5 @@
1
1
  import { REDIRECT_STATE_PREFIX, SESSION_KEY, createStorage, redirectStorage } from "./storage.mjs";
2
- import { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState } from "./pkce.mjs";
2
+ import { generateNonce, generateState } from "./pkce.mjs";
3
3
  import { exchangeCode, fetchRequestId, logout, refreshTokens } from "./exchange.mjs";
4
4
  import { openAuthPopup } from "./popup.mjs";
5
5
  import { mitt } from "@socialproof/utils";
@@ -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}`;
@@ -92,7 +89,6 @@ function createAuth(config) {
92
89
  return_origin: returnOrigin,
93
90
  mode: "redirect",
94
91
  provider: provider ?? "",
95
- code_challenge: codeChallenge,
96
92
  code_challenge_method: "S256"
97
93
  });
98
94
  if (requestId) params.set("request_id", requestId);
@@ -142,7 +138,6 @@ function createAuth(config) {
142
138
  redirectStorage.remove(key);
143
139
  const session = await exchangeCode(config.apiBaseUrl, {
144
140
  code,
145
- code_verifier: redirectState.codeVerifier,
146
141
  redirect_uri: config.redirectUri,
147
142
  state,
148
143
  nonce,
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 { exchangeCode, 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 ?? '',\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\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\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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"}
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,5 +1,5 @@
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
4
  import { exchangeCode, fetchRequestId } from "./exchange.mjs";
5
5
 
@@ -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();
@@ -46,7 +39,6 @@ async function openAuthPopup(options) {
46
39
  return_origin: returnOrigin,
47
40
  mode: "popup",
48
41
  provider: provider ?? "",
49
- code_challenge: codeChallenge,
50
42
  code_challenge_method: "S256"
51
43
  });
52
44
  if (requestId) params.set("request_id", requestId);
@@ -88,7 +80,6 @@ async function openAuthPopup(options) {
88
80
  } catch {}
89
81
  exchangeCode(apiBaseUrl, {
90
82
  code: msg.code,
91
- code_verifier: codeVerifier,
92
83
  redirect_uri: redirectUri,
93
84
  state,
94
85
  nonce,
@@ -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 { 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\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_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\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":";;;;;;;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;AAIR,iBAAa,YAAY;KACxB,MAAM,IAAI;KACV,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socialproof/mysocial-auth",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",