bright-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.
@@ -128,12 +128,21 @@ var src_default = BrightAuth;
128
128
  function registerSignInButton() {
129
129
  if (customElements.get("bright-signin")) return;
130
130
  customElements.define("bright-signin", class BrightSignIn extends HTMLElement {
131
- connectedCallback() {
131
+ async connectedCallback() {
132
132
  const clientId = this.getAttribute("client-id") || "";
133
133
  const clientSecret = this.getAttribute("client-secret") || "";
134
134
  const redirectUri = this.getAttribute("redirect-uri") || "";
135
135
  const scopes = (this.getAttribute("scopes") || "health").split(",").map((s) => s.trim());
136
136
  const auth = new BrightAuth({ clientId, clientSecret, redirectUri });
137
+ try {
138
+ const token = await auth.handleCallback();
139
+ if (token) {
140
+ const user = await auth.getUser().catch(() => null);
141
+ this.dispatchEvent(new CustomEvent("bright-auth", { detail: { token, user }, bubbles: true }));
142
+ }
143
+ } catch (e) {
144
+ this.dispatchEvent(new CustomEvent("bright-auth-error", { detail: { error: e.message }, bubbles: true }));
145
+ }
137
146
  const shadow = this.attachShadow({ mode: "closed" });
138
147
  const style = document.createElement("style");
139
148
  style.textContent = `
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.js"],
4
- "sourcesContent": ["const DEFAULT_API_BASE = \"https://api.thebrightapp.xyz/v1\"\nconst DEFAULT_AUTH_BASE = \"https://developers.thebrightapp.xyz\"\nconst TOKEN_KEY = \"bright_access_token\"\n\nexport class BrightAuth {\n /**\n * @param {{ clientId: string, clientSecret: string, redirectUri: string, apiBase?: string, authBase?: string }} config\n */\n constructor({ clientId, clientSecret, redirectUri, apiBase, authBase } = {}) {\n if (!clientId) throw new Error(\"BrightAuth: clientId is required\")\n if (!clientSecret) throw new Error(\"BrightAuth: clientSecret is required\")\n if (!redirectUri) throw new Error(\"BrightAuth: redirectUri is required\")\n\n this._clientId = clientId\n this._clientSecret = clientSecret\n this._redirectUri = redirectUri\n this._apiBase = (apiBase || DEFAULT_API_BASE).replace(/\\/$/, \"\")\n this._authBase = (authBase || DEFAULT_AUTH_BASE).replace(/\\/$/, \"\")\n }\n\n /** Redirect the user to the Bright consent page. */\n signIn(scopes = [\"health\"]) {\n const url = new URL(`${this._authBase}/oauth/authorize`)\n url.searchParams.set(\"client_id\", this._clientId)\n url.searchParams.set(\"redirect_uri\", this._redirectUri)\n scopes.forEach(s => url.searchParams.append(\"scope\", s))\n window.location.href = url.toString()\n }\n\n /**\n * Call on every page load. If the URL contains ?code=, exchanges it for an\n * access token, stores it, and cleans the URL.\n * @returns {Promise<string|null>} The access token, or null if no code in URL.\n */\n async handleCallback() {\n const params = new URLSearchParams(window.location.search)\n const code = params.get(\"code\")\n const error = params.get(\"error\")\n\n if (error) throw new Error(`Bright OAuth error: ${error}`)\n if (!code) return null\n\n window.history.replaceState({}, \"\", window.location.pathname)\n\n const res = await fetch(`${this._apiBase}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: this._clientId,\n client_secret: this._clientSecret,\n code,\n }),\n })\n const data = await res.json()\n if (!res.ok) throw new Error(data.detail || \"Token exchange failed\")\n\n localStorage.setItem(TOKEN_KEY, data.access_token)\n return data.access_token\n }\n\n /** Returns the stored access token, or null if not signed in. */\n getToken() {\n return localStorage.getItem(TOKEN_KEY)\n }\n\n /** Remove the stored token (signs the user out locally). */\n signOut() {\n localStorage.removeItem(TOKEN_KEY)\n }\n\n /** Returns true if an access token is stored. */\n isSignedIn() {\n return !!this.getToken()\n }\n\n /**\n * Fetch the signed-in user's identity from Bright.\n * @returns {Promise<{ uid: string, email?: string, name?: string, photo_url?: string }>}\n */\n async getUser() {\n return this._call(\"/oauth/me\")\n }\n\n /**\n * Make an authenticated request to any Bright API endpoint.\n * @param {string} path e.g. \"/sleep_track/home\"\n * @param {object} [init] fetch options (method, body, etc.)\n * @param {object} [query] query params object\n */\n async fetch(path, init = {}, query = {}) {\n const token = this.getToken()\n if (!token) throw new Error(\"BrightAuth: not signed in\")\n\n const url = new URL(`${this._apiBase}${path}`)\n Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v))\n\n const res = await globalThis.fetch(url.toString(), {\n ...init,\n headers: {\n ...(init.headers || {}),\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n })\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.detail || `Bright API error ${res.status}`)\n }\n return res.json()\n }\n\n /** @private */\n async _call(path, query = {}) {\n return this.fetch(path, {}, query)\n }\n}\n\nexport default BrightAuth\n\n/**\n * Registers the <bright-signin> custom element.\n * Styling is locked inside Shadow DOM \u2014 cannot be overridden by the host page.\n *\n * Usage:\n * <bright-signin client-id=\"proj_xxx\" client-secret=\"sk_xxx\" redirect-uri=\"https://yourapp.com\"></bright-signin>\n *\n * Or via JS:\n * registerSignInButton()\n * // then use <bright-signin> in your HTML\n */\nexport function registerSignInButton() {\n if (customElements.get(\"bright-signin\")) return\n\n customElements.define(\"bright-signin\", class BrightSignIn extends HTMLElement {\n connectedCallback() {\n const clientId = this.getAttribute(\"client-id\") || \"\"\n const clientSecret = this.getAttribute(\"client-secret\") || \"\"\n const redirectUri = this.getAttribute(\"redirect-uri\") || \"\"\n const scopes = (this.getAttribute(\"scopes\") || \"health\").split(\",\").map(s => s.trim())\n\n const auth = new BrightAuth({ clientId, clientSecret, redirectUri })\n\n const shadow = this.attachShadow({ mode: \"closed\" })\n\n const style = document.createElement(\"style\")\n style.textContent = `\n button {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: #10b981;\n color: #fff;\n border: none;\n border-radius: 10px;\n padding: 12px 20px;\n font-size: 14px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n transition: opacity 0.15s;\n white-space: nowrap;\n }\n button:hover { opacity: 0.85; }\n button:active { opacity: 0.7; transform: scale(0.98); }\n svg { flex-shrink: 0; }\n `\n\n const btn = document.createElement(\"button\")\n btn.innerHTML = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4 3.6-7 8-7s8 3 8 7\"/>\n </svg>\n Sign in with Bright\n `\n btn.addEventListener(\"click\", () => auth.signIn(scopes))\n\n shadow.appendChild(style)\n shadow.appendChild(btn)\n }\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["const DEFAULT_API_BASE = \"https://api.thebrightapp.xyz/v1\"\nconst DEFAULT_AUTH_BASE = \"https://developers.thebrightapp.xyz\"\nconst TOKEN_KEY = \"bright_access_token\"\n\nexport class BrightAuth {\n /**\n * @param {{ clientId: string, clientSecret: string, redirectUri: string, apiBase?: string, authBase?: string }} config\n */\n constructor({ clientId, clientSecret, redirectUri, apiBase, authBase } = {}) {\n if (!clientId) throw new Error(\"BrightAuth: clientId is required\")\n if (!clientSecret) throw new Error(\"BrightAuth: clientSecret is required\")\n if (!redirectUri) throw new Error(\"BrightAuth: redirectUri is required\")\n\n this._clientId = clientId\n this._clientSecret = clientSecret\n this._redirectUri = redirectUri\n this._apiBase = (apiBase || DEFAULT_API_BASE).replace(/\\/$/, \"\")\n this._authBase = (authBase || DEFAULT_AUTH_BASE).replace(/\\/$/, \"\")\n }\n\n /** Redirect the user to the Bright consent page. */\n signIn(scopes = [\"health\"]) {\n const url = new URL(`${this._authBase}/oauth/authorize`)\n url.searchParams.set(\"client_id\", this._clientId)\n url.searchParams.set(\"redirect_uri\", this._redirectUri)\n scopes.forEach(s => url.searchParams.append(\"scope\", s))\n window.location.href = url.toString()\n }\n\n /**\n * Call on every page load. If the URL contains ?code=, exchanges it for an\n * access token, stores it, and cleans the URL.\n * @returns {Promise<string|null>} The access token, or null if no code in URL.\n */\n async handleCallback() {\n const params = new URLSearchParams(window.location.search)\n const code = params.get(\"code\")\n const error = params.get(\"error\")\n\n if (error) throw new Error(`Bright OAuth error: ${error}`)\n if (!code) return null\n\n window.history.replaceState({}, \"\", window.location.pathname)\n\n const res = await fetch(`${this._apiBase}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: this._clientId,\n client_secret: this._clientSecret,\n code,\n }),\n })\n const data = await res.json()\n if (!res.ok) throw new Error(data.detail || \"Token exchange failed\")\n\n localStorage.setItem(TOKEN_KEY, data.access_token)\n return data.access_token\n }\n\n /** Returns the stored access token, or null if not signed in. */\n getToken() {\n return localStorage.getItem(TOKEN_KEY)\n }\n\n /** Remove the stored token (signs the user out locally). */\n signOut() {\n localStorage.removeItem(TOKEN_KEY)\n }\n\n /** Returns true if an access token is stored. */\n isSignedIn() {\n return !!this.getToken()\n }\n\n /**\n * Fetch the signed-in user's identity from Bright.\n * @returns {Promise<{ uid: string, email?: string, name?: string, photo_url?: string }>}\n */\n async getUser() {\n return this._call(\"/oauth/me\")\n }\n\n /**\n * Make an authenticated request to any Bright API endpoint.\n * @param {string} path e.g. \"/sleep_track/home\"\n * @param {object} [init] fetch options (method, body, etc.)\n * @param {object} [query] query params object\n */\n async fetch(path, init = {}, query = {}) {\n const token = this.getToken()\n if (!token) throw new Error(\"BrightAuth: not signed in\")\n\n const url = new URL(`${this._apiBase}${path}`)\n Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v))\n\n const res = await globalThis.fetch(url.toString(), {\n ...init,\n headers: {\n ...(init.headers || {}),\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n })\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.detail || `Bright API error ${res.status}`)\n }\n return res.json()\n }\n\n /** @private */\n async _call(path, query = {}) {\n return this.fetch(path, {}, query)\n }\n}\n\nexport default BrightAuth\n\n/**\n * Registers the <bright-signin> custom element.\n * Styling is locked inside Shadow DOM \u2014 cannot be overridden by the host page.\n *\n * Usage:\n * <bright-signin client-id=\"proj_xxx\" client-secret=\"sk_xxx\" redirect-uri=\"https://yourapp.com\"></bright-signin>\n *\n * Or via JS:\n * registerSignInButton()\n * // then use <bright-signin> in your HTML\n */\nexport function registerSignInButton() {\n if (customElements.get(\"bright-signin\")) return\n\n customElements.define(\"bright-signin\", class BrightSignIn extends HTMLElement {\n async connectedCallback() {\n const clientId = this.getAttribute(\"client-id\") || \"\"\n const clientSecret = this.getAttribute(\"client-secret\") || \"\"\n const redirectUri = this.getAttribute(\"redirect-uri\") || \"\"\n const scopes = (this.getAttribute(\"scopes\") || \"health\").split(\",\").map(s => s.trim())\n\n const auth = new BrightAuth({ clientId, clientSecret, redirectUri })\n\n // Automatically handle the OAuth callback if ?code= is in the URL\n try {\n const token = await auth.handleCallback()\n if (token) {\n const user = await auth.getUser().catch(() => null)\n this.dispatchEvent(new CustomEvent(\"bright-auth\", { detail: { token, user }, bubbles: true }))\n }\n } catch (e) {\n this.dispatchEvent(new CustomEvent(\"bright-auth-error\", { detail: { error: e.message }, bubbles: true }))\n }\n\n const shadow = this.attachShadow({ mode: \"closed\" })\n\n const style = document.createElement(\"style\")\n style.textContent = `\n button {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: #10b981;\n color: #fff;\n border: none;\n border-radius: 10px;\n padding: 12px 20px;\n font-size: 14px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n transition: opacity 0.15s;\n white-space: nowrap;\n }\n button:hover { opacity: 0.85; }\n button:active { opacity: 0.7; transform: scale(0.98); }\n svg { flex-shrink: 0; }\n `\n\n const btn = document.createElement(\"button\")\n btn.innerHTML = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4 3.6-7 8-7s8 3 8 7\"/>\n </svg>\n Sign in with Bright\n `\n btn.addEventListener(\"click\", () => auth.signIn(scopes))\n\n shadow.appendChild(style)\n shadow.appendChild(btn)\n }\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,12 +103,21 @@ var src_default = BrightAuth;
103
103
  function registerSignInButton() {
104
104
  if (customElements.get("bright-signin")) return;
105
105
  customElements.define("bright-signin", class BrightSignIn extends HTMLElement {
106
- connectedCallback() {
106
+ async connectedCallback() {
107
107
  const clientId = this.getAttribute("client-id") || "";
108
108
  const clientSecret = this.getAttribute("client-secret") || "";
109
109
  const redirectUri = this.getAttribute("redirect-uri") || "";
110
110
  const scopes = (this.getAttribute("scopes") || "health").split(",").map((s) => s.trim());
111
111
  const auth = new BrightAuth({ clientId, clientSecret, redirectUri });
112
+ try {
113
+ const token = await auth.handleCallback();
114
+ if (token) {
115
+ const user = await auth.getUser().catch(() => null);
116
+ this.dispatchEvent(new CustomEvent("bright-auth", { detail: { token, user }, bubbles: true }));
117
+ }
118
+ } catch (e) {
119
+ this.dispatchEvent(new CustomEvent("bright-auth-error", { detail: { error: e.message }, bubbles: true }));
120
+ }
112
121
  const shadow = this.attachShadow({ mode: "closed" });
113
122
  const style = document.createElement("style");
114
123
  style.textContent = `
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.js"],
4
- "sourcesContent": ["const DEFAULT_API_BASE = \"https://api.thebrightapp.xyz/v1\"\nconst DEFAULT_AUTH_BASE = \"https://developers.thebrightapp.xyz\"\nconst TOKEN_KEY = \"bright_access_token\"\n\nexport class BrightAuth {\n /**\n * @param {{ clientId: string, clientSecret: string, redirectUri: string, apiBase?: string, authBase?: string }} config\n */\n constructor({ clientId, clientSecret, redirectUri, apiBase, authBase } = {}) {\n if (!clientId) throw new Error(\"BrightAuth: clientId is required\")\n if (!clientSecret) throw new Error(\"BrightAuth: clientSecret is required\")\n if (!redirectUri) throw new Error(\"BrightAuth: redirectUri is required\")\n\n this._clientId = clientId\n this._clientSecret = clientSecret\n this._redirectUri = redirectUri\n this._apiBase = (apiBase || DEFAULT_API_BASE).replace(/\\/$/, \"\")\n this._authBase = (authBase || DEFAULT_AUTH_BASE).replace(/\\/$/, \"\")\n }\n\n /** Redirect the user to the Bright consent page. */\n signIn(scopes = [\"health\"]) {\n const url = new URL(`${this._authBase}/oauth/authorize`)\n url.searchParams.set(\"client_id\", this._clientId)\n url.searchParams.set(\"redirect_uri\", this._redirectUri)\n scopes.forEach(s => url.searchParams.append(\"scope\", s))\n window.location.href = url.toString()\n }\n\n /**\n * Call on every page load. If the URL contains ?code=, exchanges it for an\n * access token, stores it, and cleans the URL.\n * @returns {Promise<string|null>} The access token, or null if no code in URL.\n */\n async handleCallback() {\n const params = new URLSearchParams(window.location.search)\n const code = params.get(\"code\")\n const error = params.get(\"error\")\n\n if (error) throw new Error(`Bright OAuth error: ${error}`)\n if (!code) return null\n\n window.history.replaceState({}, \"\", window.location.pathname)\n\n const res = await fetch(`${this._apiBase}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: this._clientId,\n client_secret: this._clientSecret,\n code,\n }),\n })\n const data = await res.json()\n if (!res.ok) throw new Error(data.detail || \"Token exchange failed\")\n\n localStorage.setItem(TOKEN_KEY, data.access_token)\n return data.access_token\n }\n\n /** Returns the stored access token, or null if not signed in. */\n getToken() {\n return localStorage.getItem(TOKEN_KEY)\n }\n\n /** Remove the stored token (signs the user out locally). */\n signOut() {\n localStorage.removeItem(TOKEN_KEY)\n }\n\n /** Returns true if an access token is stored. */\n isSignedIn() {\n return !!this.getToken()\n }\n\n /**\n * Fetch the signed-in user's identity from Bright.\n * @returns {Promise<{ uid: string, email?: string, name?: string, photo_url?: string }>}\n */\n async getUser() {\n return this._call(\"/oauth/me\")\n }\n\n /**\n * Make an authenticated request to any Bright API endpoint.\n * @param {string} path e.g. \"/sleep_track/home\"\n * @param {object} [init] fetch options (method, body, etc.)\n * @param {object} [query] query params object\n */\n async fetch(path, init = {}, query = {}) {\n const token = this.getToken()\n if (!token) throw new Error(\"BrightAuth: not signed in\")\n\n const url = new URL(`${this._apiBase}${path}`)\n Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v))\n\n const res = await globalThis.fetch(url.toString(), {\n ...init,\n headers: {\n ...(init.headers || {}),\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n })\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.detail || `Bright API error ${res.status}`)\n }\n return res.json()\n }\n\n /** @private */\n async _call(path, query = {}) {\n return this.fetch(path, {}, query)\n }\n}\n\nexport default BrightAuth\n\n/**\n * Registers the <bright-signin> custom element.\n * Styling is locked inside Shadow DOM \u2014 cannot be overridden by the host page.\n *\n * Usage:\n * <bright-signin client-id=\"proj_xxx\" client-secret=\"sk_xxx\" redirect-uri=\"https://yourapp.com\"></bright-signin>\n *\n * Or via JS:\n * registerSignInButton()\n * // then use <bright-signin> in your HTML\n */\nexport function registerSignInButton() {\n if (customElements.get(\"bright-signin\")) return\n\n customElements.define(\"bright-signin\", class BrightSignIn extends HTMLElement {\n connectedCallback() {\n const clientId = this.getAttribute(\"client-id\") || \"\"\n const clientSecret = this.getAttribute(\"client-secret\") || \"\"\n const redirectUri = this.getAttribute(\"redirect-uri\") || \"\"\n const scopes = (this.getAttribute(\"scopes\") || \"health\").split(\",\").map(s => s.trim())\n\n const auth = new BrightAuth({ clientId, clientSecret, redirectUri })\n\n const shadow = this.attachShadow({ mode: \"closed\" })\n\n const style = document.createElement(\"style\")\n style.textContent = `\n button {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: #10b981;\n color: #fff;\n border: none;\n border-radius: 10px;\n padding: 12px 20px;\n font-size: 14px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n transition: opacity 0.15s;\n white-space: nowrap;\n }\n button:hover { opacity: 0.85; }\n button:active { opacity: 0.7; transform: scale(0.98); }\n svg { flex-shrink: 0; }\n `\n\n const btn = document.createElement(\"button\")\n btn.innerHTML = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4 3.6-7 8-7s8 3 8 7\"/>\n </svg>\n Sign in with Bright\n `\n btn.addEventListener(\"click\", () => auth.signIn(scopes))\n\n shadow.appendChild(style)\n shadow.appendChild(btn)\n }\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["const DEFAULT_API_BASE = \"https://api.thebrightapp.xyz/v1\"\nconst DEFAULT_AUTH_BASE = \"https://developers.thebrightapp.xyz\"\nconst TOKEN_KEY = \"bright_access_token\"\n\nexport class BrightAuth {\n /**\n * @param {{ clientId: string, clientSecret: string, redirectUri: string, apiBase?: string, authBase?: string }} config\n */\n constructor({ clientId, clientSecret, redirectUri, apiBase, authBase } = {}) {\n if (!clientId) throw new Error(\"BrightAuth: clientId is required\")\n if (!clientSecret) throw new Error(\"BrightAuth: clientSecret is required\")\n if (!redirectUri) throw new Error(\"BrightAuth: redirectUri is required\")\n\n this._clientId = clientId\n this._clientSecret = clientSecret\n this._redirectUri = redirectUri\n this._apiBase = (apiBase || DEFAULT_API_BASE).replace(/\\/$/, \"\")\n this._authBase = (authBase || DEFAULT_AUTH_BASE).replace(/\\/$/, \"\")\n }\n\n /** Redirect the user to the Bright consent page. */\n signIn(scopes = [\"health\"]) {\n const url = new URL(`${this._authBase}/oauth/authorize`)\n url.searchParams.set(\"client_id\", this._clientId)\n url.searchParams.set(\"redirect_uri\", this._redirectUri)\n scopes.forEach(s => url.searchParams.append(\"scope\", s))\n window.location.href = url.toString()\n }\n\n /**\n * Call on every page load. If the URL contains ?code=, exchanges it for an\n * access token, stores it, and cleans the URL.\n * @returns {Promise<string|null>} The access token, or null if no code in URL.\n */\n async handleCallback() {\n const params = new URLSearchParams(window.location.search)\n const code = params.get(\"code\")\n const error = params.get(\"error\")\n\n if (error) throw new Error(`Bright OAuth error: ${error}`)\n if (!code) return null\n\n window.history.replaceState({}, \"\", window.location.pathname)\n\n const res = await fetch(`${this._apiBase}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n client_id: this._clientId,\n client_secret: this._clientSecret,\n code,\n }),\n })\n const data = await res.json()\n if (!res.ok) throw new Error(data.detail || \"Token exchange failed\")\n\n localStorage.setItem(TOKEN_KEY, data.access_token)\n return data.access_token\n }\n\n /** Returns the stored access token, or null if not signed in. */\n getToken() {\n return localStorage.getItem(TOKEN_KEY)\n }\n\n /** Remove the stored token (signs the user out locally). */\n signOut() {\n localStorage.removeItem(TOKEN_KEY)\n }\n\n /** Returns true if an access token is stored. */\n isSignedIn() {\n return !!this.getToken()\n }\n\n /**\n * Fetch the signed-in user's identity from Bright.\n * @returns {Promise<{ uid: string, email?: string, name?: string, photo_url?: string }>}\n */\n async getUser() {\n return this._call(\"/oauth/me\")\n }\n\n /**\n * Make an authenticated request to any Bright API endpoint.\n * @param {string} path e.g. \"/sleep_track/home\"\n * @param {object} [init] fetch options (method, body, etc.)\n * @param {object} [query] query params object\n */\n async fetch(path, init = {}, query = {}) {\n const token = this.getToken()\n if (!token) throw new Error(\"BrightAuth: not signed in\")\n\n const url = new URL(`${this._apiBase}${path}`)\n Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v))\n\n const res = await globalThis.fetch(url.toString(), {\n ...init,\n headers: {\n ...(init.headers || {}),\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n })\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}))\n throw new Error(err.detail || `Bright API error ${res.status}`)\n }\n return res.json()\n }\n\n /** @private */\n async _call(path, query = {}) {\n return this.fetch(path, {}, query)\n }\n}\n\nexport default BrightAuth\n\n/**\n * Registers the <bright-signin> custom element.\n * Styling is locked inside Shadow DOM \u2014 cannot be overridden by the host page.\n *\n * Usage:\n * <bright-signin client-id=\"proj_xxx\" client-secret=\"sk_xxx\" redirect-uri=\"https://yourapp.com\"></bright-signin>\n *\n * Or via JS:\n * registerSignInButton()\n * // then use <bright-signin> in your HTML\n */\nexport function registerSignInButton() {\n if (customElements.get(\"bright-signin\")) return\n\n customElements.define(\"bright-signin\", class BrightSignIn extends HTMLElement {\n async connectedCallback() {\n const clientId = this.getAttribute(\"client-id\") || \"\"\n const clientSecret = this.getAttribute(\"client-secret\") || \"\"\n const redirectUri = this.getAttribute(\"redirect-uri\") || \"\"\n const scopes = (this.getAttribute(\"scopes\") || \"health\").split(\",\").map(s => s.trim())\n\n const auth = new BrightAuth({ clientId, clientSecret, redirectUri })\n\n // Automatically handle the OAuth callback if ?code= is in the URL\n try {\n const token = await auth.handleCallback()\n if (token) {\n const user = await auth.getUser().catch(() => null)\n this.dispatchEvent(new CustomEvent(\"bright-auth\", { detail: { token, user }, bubbles: true }))\n }\n } catch (e) {\n this.dispatchEvent(new CustomEvent(\"bright-auth-error\", { detail: { error: e.message }, bubbles: true }))\n }\n\n const shadow = this.attachShadow({ mode: \"closed\" })\n\n const style = document.createElement(\"style\")\n style.textContent = `\n button {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: #10b981;\n color: #fff;\n border: none;\n border-radius: 10px;\n padding: 12px 20px;\n font-size: 14px;\n font-weight: 600;\n font-family: system-ui, -apple-system, sans-serif;\n cursor: pointer;\n transition: opacity 0.15s;\n white-space: nowrap;\n }\n button:hover { opacity: 0.85; }\n button:active { opacity: 0.7; transform: scale(0.98); }\n svg { flex-shrink: 0; }\n `\n\n const btn = document.createElement(\"button\")\n btn.innerHTML = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 20c0-4 3.6-7 8-7s8 3 8 7\"/>\n </svg>\n Sign in with Bright\n `\n btn.addEventListener(\"click\", () => auth.signIn(scopes))\n\n shadow.appendChild(style)\n shadow.appendChild(btn)\n }\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bright-auth",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sign in with Bright — OAuth SDK for web",
5
5
  "main": "dist/bright-auth.cjs.js",
6
6
  "module": "dist/bright-auth.esm.js",