@buildrbets/react 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -67,12 +67,29 @@ function clearStoredOAuthParams() {
67
67
  var import_jsx_runtime = require("react/jsx-runtime");
68
68
  var DEFAULT_BASE_URL = "https://buildr.bet";
69
69
  var DEFAULT_SCOPES = "openid email profile";
70
+ var defaultStyle = {
71
+ backgroundColor: "#5C3FBD",
72
+ color: "#FFFFFF",
73
+ fontSize: "0.875rem",
74
+ fontWeight: 500,
75
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
76
+ height: "2.5rem",
77
+ padding: "0 1rem",
78
+ borderRadius: "0.75rem",
79
+ border: "none",
80
+ cursor: "pointer",
81
+ display: "inline-flex",
82
+ alignItems: "center",
83
+ justifyContent: "center",
84
+ transition: "background-color 0.15s ease"
85
+ };
70
86
  function LoginWithBuildr({
71
87
  clientId,
72
88
  redirectUri,
73
89
  onError,
74
90
  baseUrl = DEFAULT_BASE_URL,
75
91
  children,
92
+ style,
76
93
  ...buttonProps
77
94
  }) {
78
95
  const handleClick = (0, import_react.useCallback)(async () => {
@@ -95,7 +112,24 @@ function LoginWithBuildr({
95
112
  onError?.(err instanceof Error ? err : new Error(String(err)));
96
113
  }
97
114
  }, [clientId, redirectUri, baseUrl, onError]);
98
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: handleClick, type: "button", ...buttonProps, children: children ?? "Login with Buildr" });
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
+ "button",
117
+ {
118
+ onClick: handleClick,
119
+ type: "button",
120
+ style: { ...defaultStyle, ...style },
121
+ onMouseEnter: (e) => {
122
+ e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#4A2A9B";
123
+ buttonProps.onMouseEnter?.(e);
124
+ },
125
+ onMouseLeave: (e) => {
126
+ e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#5C3FBD";
127
+ buttonProps.onMouseLeave?.(e);
128
+ },
129
+ ...buttonProps,
130
+ children: children ?? "Login with Buildr"
131
+ }
132
+ );
99
133
  }
100
134
 
101
135
  // src/BuildrCallback.tsx
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["export { LoginWithBuildr } from \"./LoginWithBuildr\";\nexport { BuildrCallback } from \"./BuildrCallback\";\nexport type {\n BuildrUser,\n LoginWithBuildrProps,\n BuildrCallbackProps,\n} from \"./types\";\n","import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button onClick={handleClick} type=\"button\" {...buttonProps}>\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["export { LoginWithBuildr } from \"./LoginWithBuildr\";\nexport { BuildrCallback } from \"./BuildrCallback\";\nexport type {\n BuildrUser,\n LoginWithBuildrProps,\n BuildrCallbackProps,\n} from \"./types\";\n","import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nconst defaultStyle: React.CSSProperties = {\n backgroundColor: \"#5C3FBD\",\n color: \"#FFFFFF\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n fontFamily:\n \"Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n height: \"2.5rem\",\n padding: \"0 1rem\",\n borderRadius: \"0.75rem\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n transition: \"background-color 0.15s ease\",\n};\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n style,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button\n onClick={handleClick}\n type=\"button\"\n style={{ ...defaultStyle, ...style }}\n onMouseEnter={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#4A2A9B\");\n buttonProps.onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#5C3FBD\");\n buttonProps.onMouseLeave?.(e);\n }}\n {...buttonProps}\n >\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","DEFAULT_BASE_URL"]}
package/dist/index.d.cts CHANGED
@@ -34,7 +34,7 @@ interface BuildrCallbackProps {
34
34
  children?: React.ReactNode;
35
35
  }
36
36
 
37
- declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
37
+ declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, style, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
38
38
 
39
39
  declare function BuildrCallback({ clientId, redirectUri, onSuccess, onError, baseUrl, children, }: BuildrCallbackProps): react_jsx_runtime.JSX.Element;
40
40
 
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ interface BuildrCallbackProps {
34
34
  children?: React.ReactNode;
35
35
  }
36
36
 
37
- declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
37
+ declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, style, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
38
38
 
39
39
  declare function BuildrCallback({ clientId, redirectUri, onSuccess, onError, baseUrl, children, }: BuildrCallbackProps): react_jsx_runtime.JSX.Element;
40
40
 
package/dist/index.js CHANGED
@@ -40,12 +40,29 @@ function clearStoredOAuthParams() {
40
40
  import { jsx } from "react/jsx-runtime";
41
41
  var DEFAULT_BASE_URL = "https://buildr.bet";
42
42
  var DEFAULT_SCOPES = "openid email profile";
43
+ var defaultStyle = {
44
+ backgroundColor: "#5C3FBD",
45
+ color: "#FFFFFF",
46
+ fontSize: "0.875rem",
47
+ fontWeight: 500,
48
+ fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
49
+ height: "2.5rem",
50
+ padding: "0 1rem",
51
+ borderRadius: "0.75rem",
52
+ border: "none",
53
+ cursor: "pointer",
54
+ display: "inline-flex",
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ transition: "background-color 0.15s ease"
58
+ };
43
59
  function LoginWithBuildr({
44
60
  clientId,
45
61
  redirectUri,
46
62
  onError,
47
63
  baseUrl = DEFAULT_BASE_URL,
48
64
  children,
65
+ style,
49
66
  ...buttonProps
50
67
  }) {
51
68
  const handleClick = useCallback(async () => {
@@ -68,7 +85,24 @@ function LoginWithBuildr({
68
85
  onError?.(err instanceof Error ? err : new Error(String(err)));
69
86
  }
70
87
  }, [clientId, redirectUri, baseUrl, onError]);
71
- return /* @__PURE__ */ jsx("button", { onClick: handleClick, type: "button", ...buttonProps, children: children ?? "Login with Buildr" });
88
+ return /* @__PURE__ */ jsx(
89
+ "button",
90
+ {
91
+ onClick: handleClick,
92
+ type: "button",
93
+ style: { ...defaultStyle, ...style },
94
+ onMouseEnter: (e) => {
95
+ e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#4A2A9B";
96
+ buttonProps.onMouseEnter?.(e);
97
+ },
98
+ onMouseLeave: (e) => {
99
+ e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#5C3FBD";
100
+ buttonProps.onMouseLeave?.(e);
101
+ },
102
+ ...buttonProps,
103
+ children: children ?? "Login with Buildr"
104
+ }
105
+ );
72
106
  }
73
107
 
74
108
  // src/BuildrCallback.tsx
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button onClick={handleClick} type=\"button\" {...buttonProps}>\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["jsx","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nconst defaultStyle: React.CSSProperties = {\n backgroundColor: \"#5C3FBD\",\n color: \"#FFFFFF\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n fontFamily:\n \"Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n height: \"2.5rem\",\n padding: \"0 1rem\",\n borderRadius: \"0.75rem\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n transition: \"background-color 0.15s ease\",\n};\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n style,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button\n onClick={handleClick}\n type=\"button\"\n style={{ ...defaultStyle, ...style }}\n onMouseEnter={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#4A2A9B\");\n buttonProps.onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#5C3FBD\");\n buttonProps.onMouseLeave?.(e);\n }}\n {...buttonProps}\n >\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["jsx","DEFAULT_BASE_URL"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildrbets/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Login with Buildr — React SDK for OAuth 2.0 + PKCE",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",