@authhero/widget 0.28.2 → 0.29.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/hydrate/index.mjs CHANGED
@@ -6821,7 +6821,18 @@ class AuthheroWidget {
6821
6821
  e.preventDefault();
6822
6822
  if (!this._screen || this.loading)
6823
6823
  return;
6824
- const submitData = overrideData || this.formData;
6824
+ let submitData = { ...this.formData, ...(overrideData || {}) };
6825
+ // Merge hidden input values from DOM (may have been set programmatically
6826
+ // by inline scripts, e.g. passkey management buttons)
6827
+ const form = this.el.shadowRoot?.querySelector("form");
6828
+ if (form) {
6829
+ const hiddenInputs = form.querySelectorAll('input[type="hidden"]');
6830
+ hiddenInputs.forEach((input) => {
6831
+ if (input.name && input.value) {
6832
+ submitData[input.name] = input.value;
6833
+ }
6834
+ });
6835
+ }
6825
6836
  // Always emit the submit event
6826
6837
  this.formSubmit.emit({
6827
6838
  screen: this._screen,
@@ -6897,6 +6908,10 @@ class AuthheroWidget {
6897
6908
  this.state = result.state;
6898
6909
  this.persistState();
6899
6910
  }
6911
+ // Perform WebAuthn ceremony if present (structured data, not script)
6912
+ if (result.ceremony) {
6913
+ this.performWebAuthnCeremony(result.ceremony);
6914
+ }
6900
6915
  // Focus first input on new screen
6901
6916
  this.focusFirstInput();
6902
6917
  }
@@ -6930,6 +6945,192 @@ class AuthheroWidget {
6930
6945
  this.loading = false;
6931
6946
  }
6932
6947
  };
6948
+ /**
6949
+ * Override form.submit() so that scripts (e.g. WebAuthn ceremony) that call
6950
+ * form.submit() go through the widget's JSON fetch pipeline instead of a
6951
+ * native form-urlencoded POST.
6952
+ */
6953
+ overrideFormSubmit() {
6954
+ const shadowRoot = this.el.shadowRoot;
6955
+ if (!shadowRoot)
6956
+ return;
6957
+ const form = shadowRoot.querySelector("form");
6958
+ if (!form)
6959
+ return;
6960
+ form.submit = () => {
6961
+ const formData = new FormData(form);
6962
+ const data = {};
6963
+ formData.forEach((value, key) => {
6964
+ if (typeof value === "string") {
6965
+ data[key] = value;
6966
+ }
6967
+ });
6968
+ const syntheticEvent = { preventDefault: () => { } };
6969
+ this.handleSubmit(syntheticEvent, data);
6970
+ };
6971
+ }
6972
+ /**
6973
+ * Validate and execute a structured WebAuthn ceremony returned by the server.
6974
+ * Instead of injecting arbitrary script content, this parses the ceremony JSON,
6975
+ * validates the expected fields, and calls the WebAuthn API natively.
6976
+ */
6977
+ performWebAuthnCeremony(ceremony) {
6978
+ if (!this.isValidWebAuthnCeremony(ceremony)) {
6979
+ console.error("Invalid WebAuthn ceremony payload", ceremony);
6980
+ return;
6981
+ }
6982
+ requestAnimationFrame(() => {
6983
+ this.overrideFormSubmit();
6984
+ this.executeWebAuthnRegistration(ceremony);
6985
+ });
6986
+ }
6987
+ /**
6988
+ * Schema validation for WebAuthn ceremony payloads.
6989
+ * Checks required fields and types before invoking browser APIs.
6990
+ */
6991
+ isValidWebAuthnCeremony(data) {
6992
+ if (typeof data !== "object" || data === null)
6993
+ return false;
6994
+ const obj = data;
6995
+ if (obj.type !== "webauthn-registration")
6996
+ return false;
6997
+ if (typeof obj.successAction !== "string")
6998
+ return false;
6999
+ const opts = obj.options;
7000
+ if (typeof opts !== "object" || opts === null)
7001
+ return false;
7002
+ const o = opts;
7003
+ if (typeof o.challenge !== "string")
7004
+ return false;
7005
+ const rp = o.rp;
7006
+ if (typeof rp !== "object" || rp === null)
7007
+ return false;
7008
+ if (typeof rp.id !== "string" ||
7009
+ typeof rp.name !== "string")
7010
+ return false;
7011
+ const user = o.user;
7012
+ if (typeof user !== "object" || user === null)
7013
+ return false;
7014
+ const u = user;
7015
+ if (typeof u.id !== "string" ||
7016
+ typeof u.name !== "string" ||
7017
+ typeof u.displayName !== "string")
7018
+ return false;
7019
+ if (!Array.isArray(o.pubKeyCredParams))
7020
+ return false;
7021
+ return true;
7022
+ }
7023
+ /**
7024
+ * Perform the WebAuthn navigator.credentials.create() ceremony and submit
7025
+ * the credential result via the form.
7026
+ */
7027
+ async executeWebAuthnRegistration(ceremony) {
7028
+ const opts = ceremony.options;
7029
+ const b64uToBuf = (s) => {
7030
+ s = s.replace(/-/g, "+").replace(/_/g, "/");
7031
+ while (s.length % 4)
7032
+ s += "=";
7033
+ const b = atob(s);
7034
+ const a = new Uint8Array(b.length);
7035
+ for (let i = 0; i < b.length; i++)
7036
+ a[i] = b.charCodeAt(i);
7037
+ return a.buffer;
7038
+ };
7039
+ const bufToB64u = (b) => {
7040
+ const a = new Uint8Array(b);
7041
+ let s = "";
7042
+ for (let i = 0; i < a.length; i++)
7043
+ s += String.fromCharCode(a[i]);
7044
+ return btoa(s)
7045
+ .replace(/\+/g, "-")
7046
+ .replace(/\//g, "_")
7047
+ .replace(/=+$/, "");
7048
+ };
7049
+ const findForm = () => {
7050
+ const shadowRoot = this.el?.shadowRoot;
7051
+ if (shadowRoot) {
7052
+ const f = shadowRoot.querySelector("form");
7053
+ if (f)
7054
+ return f;
7055
+ }
7056
+ return document.querySelector("form");
7057
+ };
7058
+ try {
7059
+ const publicKey = {
7060
+ challenge: b64uToBuf(opts.challenge),
7061
+ rp: { id: opts.rp.id, name: opts.rp.name },
7062
+ user: {
7063
+ id: b64uToBuf(opts.user.id),
7064
+ name: opts.user.name,
7065
+ displayName: opts.user.displayName,
7066
+ },
7067
+ pubKeyCredParams: opts.pubKeyCredParams.map((p) => ({
7068
+ alg: p.alg,
7069
+ type: p.type,
7070
+ })),
7071
+ timeout: opts.timeout,
7072
+ attestation: (opts.attestation || "none"),
7073
+ authenticatorSelection: opts.authenticatorSelection
7074
+ ? {
7075
+ residentKey: (opts.authenticatorSelection.residentKey ||
7076
+ "preferred"),
7077
+ userVerification: (opts.authenticatorSelection
7078
+ .userVerification ||
7079
+ "preferred"),
7080
+ }
7081
+ : undefined,
7082
+ };
7083
+ if (opts.excludeCredentials?.length) {
7084
+ publicKey.excludeCredentials = opts.excludeCredentials.map((c) => ({
7085
+ id: b64uToBuf(c.id),
7086
+ type: c.type,
7087
+ transports: (c.transports || []),
7088
+ }));
7089
+ }
7090
+ const cred = (await navigator.credentials.create({
7091
+ publicKey,
7092
+ }));
7093
+ const response = cred.response;
7094
+ const resp = {
7095
+ id: cred.id,
7096
+ rawId: bufToB64u(cred.rawId),
7097
+ type: cred.type,
7098
+ response: {
7099
+ attestationObject: bufToB64u(response.attestationObject),
7100
+ clientDataJSON: bufToB64u(response.clientDataJSON),
7101
+ },
7102
+ clientExtensionResults: cred.getClientExtensionResults(),
7103
+ authenticatorAttachment: cred.authenticatorAttachment || undefined,
7104
+ };
7105
+ if (typeof response.getTransports === "function") {
7106
+ resp.response.transports =
7107
+ response.getTransports();
7108
+ }
7109
+ const form = findForm();
7110
+ if (form) {
7111
+ const cf = form.querySelector('[name="credential-field"]') ||
7112
+ form.querySelector("#credential-field");
7113
+ const af = form.querySelector('[name="action-field"]') ||
7114
+ form.querySelector("#action-field");
7115
+ if (cf)
7116
+ cf.value = JSON.stringify(resp);
7117
+ if (af)
7118
+ af.value = ceremony.successAction;
7119
+ form.submit();
7120
+ }
7121
+ }
7122
+ catch (e) {
7123
+ console.error("WebAuthn registration error:", e);
7124
+ const form = findForm();
7125
+ if (form) {
7126
+ const af = form.querySelector('[name="action-field"]') ||
7127
+ form.querySelector("#action-field");
7128
+ if (af)
7129
+ af.value = "error";
7130
+ form.submit();
7131
+ }
7132
+ }
7133
+ }
6933
7134
  handleButtonClick = (detail) => {
6934
7135
  // If this is a submit button click, trigger form submission
6935
7136
  if (detail.type === "submit") {
@@ -7132,9 +7333,11 @@ class AuthheroWidget {
7132
7333
  // Use the local screen variable for all rendering
7133
7334
  const screenErrors = screen.messages?.filter((m) => m.type === "error") || [];
7134
7335
  const screenSuccesses = screen.messages?.filter((m) => m.type === "success") || [];
7135
- const components = [...(screen.components ?? [])]
7336
+ const allComponents = [...(screen.components ?? [])];
7337
+ const components = allComponents
7136
7338
  .filter((c) => c.visible !== false)
7137
7339
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
7340
+ const hiddenComponents = allComponents.filter((c) => c.visible === false);
7138
7341
  // Separate social, divider, and field components for layout ordering
7139
7342
  const socialComponents = components.filter((c) => this.isSocialComponent(c));
7140
7343
  const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && !this.isDividerComponent(c));
@@ -7169,7 +7372,7 @@ class AuthheroWidget {
7169
7372
  };
7170
7373
  // Get logo URL from theme.widget (takes precedence) or branding
7171
7374
  const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
7172
- return (hAsync("div", { class: "widget-container", part: "container", "data-authstack-container": true }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, part: "form" }, hAsync("div", { class: "form-content" }, socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading, exportparts: getExportParts(component) }))))), socialComponents.length > 0 &&
7375
+ return (hAsync("div", { class: "widget-container", part: "container", "data-authstack-container": true }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, part: "form" }, hiddenComponents.map((c) => (hAsync("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), hAsync("div", { class: "form-content" }, socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading, exportparts: getExportParts(component) }))))), socialComponents.length > 0 &&
7173
7376
  fieldComponents.length > 0 &&
7174
7377
  hasDivider && (hAsync("div", { class: "divider", part: "divider" }, hAsync("span", { class: "divider-text" }, dividerText))), hAsync("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading })))))), screen.links && screen.links.length > 0 && (hAsync("div", { class: "links", part: "links" }, screen.links.map((link) => (hAsync("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (hAsync("span", null, link.text, " ", hAsync("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
7175
7378
  id: link.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authhero/widget",
3
- "version": "0.28.2",
3
+ "version": "0.29.1",
4
4
  "description": "Server-Driven UI widget for AuthHero authentication flows",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "country-list": "^2.4.1",
41
- "@authhero/adapter-interfaces": "0.152.0"
41
+ "@authhero/adapter-interfaces": "0.153.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@hono/node-server": "^1.14.1",
@@ -1 +0,0 @@
1
- import{r as t,c as e,g as i,h as s}from"./p-BFP_5sHV.js";function o(t){const e=t.match(/^#([0-9a-f]{3})$/i)||t.match(/^#([0-9a-f]{6})$/i);if(!e)return null;let i=e[1];3===i.length&&(i=i[0]+i[0]+i[1]+i[1]+i[2]+i[2]);const s=parseInt(i,16);return[s>>16&255,s>>8&255,255&s]}function n(t){const e=o(t);if(!e)return NaN;const[i,s,n]=e.map((t=>{const e=t/255;return e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)}));return.2126*i+.7152*s+.0722*n}function r(t,e){const i=n(t),s=n(e);return isNaN(i)||isNaN(s)?NaN:(Math.max(i,s)+.05)/(Math.min(i,s)+.05)}function a(t,e="light"){const i=r(t,"#000000"),s=r(t,"#ffffff");return"light"===e?i>1.35*s?"#000000":"#ffffff":1.35*i>s?"#000000":"#ffffff"}function c(t,e){const i=o(t);if(!i)return t;const[s,n,r]=i,a=t=>Math.max(0,Math.round(t*(1-e))).toString(16).padStart(2,"0");return`#${a(s)}${a(n)}${a(r)}`}function h(t,e){const i=o(t);if(!i)return t;const[s,n,r]=i,a=t=>Math.min(255,Math.round(t+(255-t)*e)).toString(16).padStart(2,"0");return`#${a(s)}${a(n)}${a(r)}`}function l(t,e,i=4.5){if(r(t,e)>=i)return t;const s=n(e)>.5;let o=t;for(let n=1;n<=10;n++)if(o=s?c(t,.1*n):h(t,.1*n),r(o,e)>=i)return o;return s?"#000000":"#ffffff"}function d(t,e){if(void 0!==e)return`${e}px`;switch(t){case"pill":return"9999px";case"rounded":return"8px";case"sharp":return"0";default:return}}function f(t){if(!t)return{};const e={};if(t.colors?.primary&&(e["--ah-color-primary"]=t.colors.primary,e["--ah-color-primary-hover"]=t.colors.primary),t.colors?.page_background){const i=t.colors.page_background;"solid"===i.type&&i.start?e["--ah-page-bg"]=i.start:"gradient"===i.type&&i.start&&i.end&&(e["--ah-page-bg"]=`linear-gradient(${i.angle_deg??180}deg, ${i.start}, ${i.end})`)}return t.logo_url&&(e["--ah-logo-url"]=`url(${t.logo_url})`),t.font?.url&&(e["--ah-font-url"]=t.font.url),e}function p(t){if(!t)return{};const e={};if(t.borders){const i=t.borders;void 0!==i.widget_corner_radius&&(e["--ah-widget-radius"]=`${i.widget_corner_radius}px`),void 0!==i.widget_border_weight&&(e["--ah-widget-border-width"]=`${i.widget_border_weight}px`),!1===i.show_widget_shadow&&(e["--ah-widget-shadow"]="none");const s=d(i.buttons_style,i.button_border_radius);s&&(e["--ah-btn-radius"]=s),void 0!==i.button_border_weight&&(e["--ah-btn-border-width"]=`${i.button_border_weight}px`);const o=d(i.inputs_style,i.input_border_radius);o&&(e["--ah-input-radius"]=o),void 0!==i.input_border_weight&&(e["--ah-input-border-width"]=`${i.input_border_weight}px`)}if(t.colors){const i=t.colors;if(i.primary_button)if(e["--ah-color-primary"]=i.primary_button,e["--ah-color-primary-hover"]=i.primary_button,i.primary_button_label&&r(i.primary_button_label,i.primary_button)>=4.5)e["--ah-color-text-on-primary"]=i.primary_button_label;else{e["--ah-color-text-on-primary"]=a(i.primary_button,"light");const t=a(i.primary_button,"dark");t!==e["--ah-color-text-on-primary"]&&(e["--ah-color-text-on-primary-dark"]=t)}else i.primary_button_label&&(e["--ah-color-text-on-primary"]=i.primary_button_label);i.secondary_button_border&&(e["--ah-btn-secondary-border"]=i.secondary_button_border),i.secondary_button_label&&(e["--ah-btn-secondary-text"]=i.secondary_button_label),i.body_text&&(e["--ah-color-text"]=i.body_text),i.header&&(e["--ah-color-text-header"]=i.header),i.input_labels_placeholders&&(e["--ah-color-text-label"]=i.input_labels_placeholders,e["--ah-color-text-muted"]=i.input_labels_placeholders),i.input_filled_text&&(e["--ah-color-input-text"]=i.input_filled_text),i.widget_background&&(e["--ah-color-bg"]=i.widget_background),i.input_background&&(e["--ah-color-input-bg"]=i.input_background),i.widget_border&&(e["--ah-widget-border-color"]=i.widget_border),i.input_border&&(e["--ah-color-border"]=i.input_border),i.links_focused_components&&(e["--ah-color-link"]=l(i.links_focused_components,i.widget_background||"#ffffff")),i.base_focus_color&&(e["--ah-color-focus-ring"]=i.base_focus_color),i.base_hover_color&&(e["--ah-color-primary-hover"]=i.base_hover_color),i.error&&(e["--ah-color-error"]=i.error),i.success&&(e["--ah-color-success"]=i.success),i.icons&&(e["--ah-color-icon"]=i.icons);const s=i.widget_background||"#ffffff",o=i.input_background||s,n=e["--ah-color-border"]||i.input_border||"#c9cace",c=r(n,s),h=r(n,o);Math.min(c,h)<3&&(e["--ah-color-border"]=l(n,c<h?s:o,3))}if(t.fonts){const i=t.fonts,s=i.reference_text_size||16,o=t=>t>=50?Math.round(t/100*s):t;i.font_url&&(e["--ah-font-url"]=i.font_url),i.reference_text_size&&(e["--ah-font-size-base"]=`${i.reference_text_size}px`),i.title?.size&&(e["--ah-font-size-title"]=`${o(i.title.size)}px`),i.subtitle?.size&&(e["--ah-font-size-subtitle"]=`${o(i.subtitle.size)}px`),i.body_text?.size&&(e["--ah-font-size-body"]=`${o(i.body_text.size)}px`),i.input_labels?.size&&(e["--ah-font-size-label"]=`${o(i.input_labels.size)}px`),i.buttons_text?.size&&(e["--ah-font-size-btn"]=`${o(i.buttons_text.size)}px`),i.links?.size&&(e["--ah-font-size-link"]=`${o(i.links.size)}px`),"underlined"===i.links_style&&(e["--ah-link-decoration"]="underline"),void 0!==i.title?.bold&&(e["--ah-font-weight-title"]=i.title.bold?"700":"400"),void 0!==i.subtitle?.bold&&(e["--ah-font-weight-subtitle"]=i.subtitle.bold?"700":"400"),void 0!==i.body_text?.bold&&(e["--ah-font-weight-body"]=i.body_text.bold?"700":"400"),void 0!==i.input_labels?.bold&&(e["--ah-font-weight-label"]=i.input_labels.bold?"700":"400"),void 0!==i.buttons_text?.bold&&(e["--ah-font-weight-btn"]=i.buttons_text.bold?"600":"400"),void 0!==i.links?.bold&&(e["--ah-font-weight-link"]=i.links.bold?"700":"400")}if(t.widget){const i=t.widget;if(i.header_text_alignment&&(e["--ah-title-align"]=i.header_text_alignment),i.logo_height&&(e["--ah-logo-height"]=`${i.logo_height}px`),i.logo_position){const t={center:"center",left:"flex-start",right:"flex-end"};"none"===i.logo_position?e["--ah-logo-display"]="none":e["--ah-logo-align"]=t[i.logo_position]??"center"}i.social_buttons_layout&&("top"===i.social_buttons_layout?(e["--ah-social-order"]="0",e["--ah-divider-order"]="1",e["--ah-fields-order"]="2"):(e["--ah-social-order"]="2",e["--ah-divider-order"]="1",e["--ah-fields-order"]="0"))}if(t.page_background){const i=t.page_background;i.background_color&&(e["--ah-page-bg"]=i.background_color),i.background_image_url&&(e["--ah-page-bg-image"]=`url(${i.background_image_url})`)}return e}const u={br:[],em:[],i:[],strong:[],b:[],u:[],span:["class"],a:["href","class"]};function g(t){if(!t)return"";if(!t.includes("<"))return t;let e=t;e=e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");for(const[t,i]of Object.entries(u)){if("br"===t){e=e.replace(/&lt;br\s*\/?&gt;/gi,"<br>");continue}const s=new RegExp(`&lt;${t}((?:\\s+[a-z-]+(?:=&quot;[^&]*&quot;|=&#39;[^&]*&#39;)?)*)\\s*&gt;`,"gi");e=e.replace(s,((e,s)=>{const o=[];if(s){const t=s.replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">"),e=/([a-z-]+)=["']([^"']*)["']/gi;let n;for(;null!==(n=e.exec(t));){const[,t,e]=n;t&&i.includes(t.toLowerCase())&&("href"===t.toLowerCase()?m(e||"")&&o.push(`${t}="${x(e||"")}"`):o.push(`${t}="${x(e||"")}"`))}}"a"===t&&(o.push('target="_blank"'),o.push('rel="noopener noreferrer"'));const n=o.length?" "+o.join(" "):"";return`<${t}${n}>`}));const o=new RegExp(`&lt;/${t}&gt;`,"gi");e=e.replace(o,`</${t}>`)}return e}function m(t){if(!t)return!1;if(t.startsWith("/")||t.startsWith("#")||t.startsWith("?"))return!0;try{const e=new URL(t,"https://example.com");return"http:"===e.protocol||"https:"===e.protocol}catch{return!1}}function x(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}const w=class{constructor(i){t(this,i),this.formSubmit=e(this,"formSubmit"),this.buttonClick=e(this,"buttonClick"),this.linkClick=e(this,"linkClick"),this.navigate=e(this,"navigate"),this.flowComplete=e(this,"flowComplete"),this.flowError=e(this,"flowError"),this.screenChange=e(this,"screenChange")}get el(){return i(this)}screen;apiUrl;baseUrl;state;screenId;watchScreenId(){this.updateDataScreenAttribute()}authParams;statePersistence="memory";storageKey="authhero_widget";branding;theme;loading=!1;autoSubmit=!1;autoNavigate;_screen;_authParams;_branding;_theme;formData={};formSubmit;buttonClick;linkClick;navigate;flowComplete;flowError;screenChange;watchScreen(t){if("string"==typeof t)try{this._screen=JSON.parse(t)}catch{console.error("Failed to parse screen JSON")}else this._screen=t;this._screen&&(this.formData={},this.initFormDataFromDefaults(this._screen),this.screenChange.emit(this._screen),this.updateDataScreenAttribute())}initFormDataFromDefaults(t){const e={};for(const i of t.components||[])if("config"in i&&i.config&&"default_value"in i.config&&i.config.default_value){const t=i.config.default_value;"string"==typeof t&&""!==t&&(e[i.id]=t)}Object.keys(e).length>0&&(this.formData={...e,...this.formData})}updateDataScreenAttribute(){const t=this._screen?.name||this.screenId;t?this.el.setAttribute("data-screen",t):this.el.removeAttribute("data-screen");const e=this.el.closest("[data-authhero-widget-container]");e&&(t?e.setAttribute("data-screen",t):e.removeAttribute("data-screen"))}watchBranding(t){if("string"==typeof t)try{this._branding=JSON.parse(t)}catch{console.error("Failed to parse branding JSON")}else this._branding=t;this.applyThemeStyles()}watchTheme(t){if("string"==typeof t)try{this._theme=JSON.parse(t)}catch{console.error("Failed to parse theme JSON")}else this._theme=t;this.applyThemeStyles()}watchAuthParams(t){if("string"==typeof t)try{this._authParams=JSON.parse(t)}catch{console.error("Failed to parse authParams JSON")}else this._authParams=t}applyThemeStyles(){const t=(e=this._theme,{...f(this._branding),...p(e)});var e;!function(t,e){Object.entries(e).forEach((([e,i])=>{t.style.setProperty(e,i)}))}(this.el,t)}focusFirstInput(){requestAnimationFrame((()=>{const t=this.el.shadowRoot;if(!t)return;const e=t.querySelectorAll("authhero-node");for(const t of Array.from(e)){const e=t.shadowRoot;if(e){const t=e.querySelector('input:not([type="hidden"]):not([type="checkbox"]):not([disabled]), textarea:not([disabled])');if(t)return void t.focus()}}}))}get shouldAutoNavigate(){return this.autoNavigate??this.autoSubmit}buildUrl(t){return this.baseUrl?new URL(t,this.baseUrl).toString():t}loadPersistedState(){if("url"===this.statePersistence){const t=new URL(window.location.href).searchParams.get("state");t&&!this.state&&(this.state=t)}else if("session"===this.statePersistence)try{const t=sessionStorage.getItem(`${this.storageKey}_state`);t&&!this.state&&(this.state=t);const e=sessionStorage.getItem(`${this.storageKey}_screenId`);e&&!this.screenId&&(this.screenId=e)}catch{}}persistState(){if("url"===this.statePersistence){const t=new URL(window.location.href);this.state&&t.searchParams.set("state",this.state),this.screenId&&t.searchParams.set("screen",this.screenId),window.history.replaceState({},"",t.toString())}else if("session"===this.statePersistence)try{this.state&&sessionStorage.setItem(`${this.storageKey}_state`,this.state),this.screenId&&sessionStorage.setItem(`${this.storageKey}_screenId`,this.screenId)}catch{}}handlePopState=t=>{if(!this.apiUrl)return;t.state?.state&&(this.state=t.state.state);const e=t.state?.screen??this.extractScreenIdFromHref(location.href);e&&this.fetchScreen(e)};connectedCallback(){window.addEventListener("popstate",this.handlePopState)}disconnectedCallback(){window.removeEventListener("popstate",this.handlePopState)}async componentWillLoad(){if(!this._screen){const t=this.screen||this.el?.getAttribute("screen");t&&this.watchScreen(t)}this._branding||this.watchBranding(this.branding),this._theme||this.watchTheme(this.theme),this._authParams||this.watchAuthParams(this.authParams),this.loadPersistedState(),this.apiUrl&&!this._screen&&await this.fetchScreen(this.screenId)}async fetchScreen(t,e){if(!this.apiUrl)return!1;const i=t||this.screenId;let s=this.apiUrl;i&&s.includes("{screenId}")&&(s=s.replace("{screenId}",encodeURIComponent(i)));const o=new URL(s,this.baseUrl||window.location.origin);this.state&&o.searchParams.set("state",this.state),e&&o.searchParams.set("nodeId",e),this.loading=!0;try{const t=await fetch(this.buildUrl(o.pathname+o.search),{credentials:"include",headers:{Accept:"application/json"}});if(t.ok){const e=await t.json();if(e.screen?(this._screen=e.screen,e.branding&&(this._branding=e.branding,this.applyThemeStyles()),e.state&&(this.state=e.state),e.screenId&&(this.screenId=e.screenId)):this._screen=e,this._screen)return i&&i!==this.screenId&&(this.screenId=i),this.initFormDataFromDefaults(this._screen),this.screenChange.emit(this._screen),this.updateDataScreenAttribute(),this.persistState(),this.focusFirstInput(),!0}else{const e=await t.json().catch((()=>({message:"Failed to load screen"})));this.flowError.emit({message:e.message||"Failed to load screen"})}}catch(t){console.error("Failed to fetch screen:",t),this.flowError.emit({message:t instanceof Error?t.message:"Failed to fetch screen"})}finally{this.loading=!1}return!1}handleInputChange=(t,e)=>{this.formData={...this.formData,[t]:e}};handleSubmit=async(t,e)=>{if(t.preventDefault(),!this._screen||this.loading)return;const i=e||this.formData;if(this.formSubmit.emit({screen:this._screen,data:i}),this.autoSubmit)if(this._screen.method&&"GET"!==this._screen.method.toUpperCase()){this.loading=!0;try{const t=await fetch(this.buildUrl(this._screen.action),{method:this._screen.method,credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({data:i})}),e=t.headers.get("content-type");if(e?.includes("application/json")){const e=await t.json();e.redirect?(this.flowComplete.emit({redirectUrl:e.redirect}),this.navigate.emit({url:e.redirect}),this.shouldAutoNavigate&&(window.location.href=e.redirect)):!t.ok&&e.screen?(this._screen=e.screen,this.initFormDataFromDefaults(e.screen),this.screenChange.emit(e.screen),this.updateDataScreenAttribute(),this.focusFirstInput()):e.screen?(this._screen=e.screen,this.formData={},this.initFormDataFromDefaults(e.screen),this.screenChange.emit(e.screen),this.updateDataScreenAttribute(),e.screenId&&(this.screenId=e.screenId),this.persistState(),e.navigateUrl&&this.shouldAutoNavigate&&window.history.pushState({screen:e.screenId,state:this.state},"",e.navigateUrl),e.branding&&(this._branding=e.branding,this.applyThemeStyles()),e.state&&(this.state=e.state,this.persistState()),this.focusFirstInput()):e.complete?this.flowComplete.emit({}):!t.ok&&e.error&&(this._screen&&(this._screen={...this._screen,messages:[...this._screen.messages||[],{text:e.error,type:"error"}]}),this.flowError.emit({message:e.error}))}}catch(t){console.error("Form submission failed:",t),this.flowError.emit({message:t instanceof Error?t.message:"Form submission failed"})}finally{this.loading=!1}}else window.location.href=this.buildUrl(this._screen.action)};handleButtonClick=t=>{if("submit"!==t.type)if(this.buttonClick.emit(t),"SOCIAL"===t.type&&t.value&&this.shouldAutoNavigate){const e=this.getProviderHref(t.value);if(e){const t=this.extractScreenIdFromHref(e);return void(t&&this.apiUrl?this.navigateToScreen(t,e):window.location.href=e)}this.handleSocialLogin(t.value)}else"RESEND_BUTTON"===t.type&&this.shouldAutoNavigate&&this.handleResend();else{if((!this._screen?.method||"GET"===this._screen.method.toUpperCase())&&this._screen?.action)return void(window.location.href=this.buildUrl(this._screen.action));const e={...this.formData,[t.id]:"true"};this.formData=e,this.handleSubmit({preventDefault:()=>{}},e)}};handleSocialLogin(t){const e=this._authParams||{},i={connection:t};this.state?i.state=this.state:e.state&&(i.state=e.state),e.client_id&&(i.client_id=e.client_id),e.redirect_uri&&(i.redirect_uri=e.redirect_uri),e.scope&&(i.scope=e.scope),e.audience&&(i.audience=e.audience),e.nonce&&(i.nonce=e.nonce),e.response_type&&(i.response_type=e.response_type);const s=this.buildUrl("/authorize?"+new URLSearchParams(i).toString());this.navigate.emit({url:s}),window.location.href=s}async handleResend(){if(this._screen?.action)try{const t=this._screen.action+(this._screen.action.includes("?")?"&":"?")+"action=resend";await fetch(this.buildUrl(t),{method:"POST",credentials:"include"})}catch(t){console.error("Resend failed:",t)}}extractScreenIdFromHref(t){try{const e=new URL(t,window.location.origin).pathname,i=e.match(/\/u2\/login\/([^/]+)$/);if(i)return i[1];const s=e.match(/\/u2\/([^/]+)$/);return s&&"login"!==s[1]&&"screen"!==s[1]?s[1]:null}catch{return null}}handleLinkClick=(t,e)=>{if(this.linkClick.emit({id:e.id,href:e.href,text:e.text}),!this.shouldAutoNavigate)return void t.preventDefault();const i=this.extractScreenIdFromHref(e.href);return i&&this.apiUrl?(t.preventDefault(),void this.navigateToScreen(i,e.href)):void 0};async navigateToScreen(t,e){await this.fetchScreen(t)?window.history.pushState({screen:t,state:this.state},"",e):window.location.href=e}getProviderHref(t){if(!this._screen)return null;for(const e of this._screen.components){const i=e;if("SOCIAL"===i.type&&i.config?.provider_details){const e=i.config.provider_details.find((e=>e.name===t));if(e?.href)return e.href}}return null}isSocialComponent(t){return"SOCIAL"===t.type}isDividerComponent(t){return"DIVIDER"===t.type}render(){const t=this._screen;if(this.loading&&!t)return s("div",{class:"widget-container"},s("div",{class:"loading-spinner"}));if(!t)return s("div",{class:"widget-container"},s("div",{class:"error-message"},"No screen configuration provided"));const e=t.messages?.filter((t=>"error"===t.type))||[],i=t.messages?.filter((t=>"success"===t.type))||[],o=[...t.components??[]].filter((t=>!1!==t.visible)).sort(((t,e)=>(t.order??0)-(e.order??0))),n=o.filter((t=>this.isSocialComponent(t))),r=o.filter((t=>!this.isSocialComponent(t)&&!this.isDividerComponent(t))),a=o.find((t=>this.isDividerComponent(t))),c=!!a,h=a?.config?.text||"Or",l=t=>{const e=t.config;return["social-buttons","button","button-secondary","button-social","button-social-content","button-social-text","button-social-subtitle","social-icon",...(e?.providers??[]).flatMap((t=>{const e=t.replace(/[^a-zA-Z0-9-]/g,"-");return[`button-social-${e}`,`button-social-content-${e}`,`button-social-text-${e}`,`button-social-subtitle-${e}`,`social-icon-${e}`]}))].join(", ")},d=this._theme?.widget?.logo_url||this._branding?.logo_url;return s("div",{class:"widget-container",part:"container","data-authstack-container":!0},s("header",{class:"widget-header",part:"header"},d&&s("div",{class:"logo-wrapper",part:"logo-wrapper"},s("img",{class:"logo",part:"logo",src:d,alt:"Logo"})),t.title&&s("h1",{class:"title",part:"title",innerHTML:g(t.title)}),t.description&&s("p",{class:"description",part:"description",innerHTML:g(t.description)})),s("div",{class:"widget-body",part:"body"},e.map((t=>s("div",{class:"message message-error",part:"message message-error",key:t.id??t.text},t.text))),i.map((t=>s("div",{class:"message message-success",part:"message message-success",key:t.id??t.text},t.text))),s("form",{onSubmit:this.handleSubmit,part:"form"},s("div",{class:"form-content"},n.length>0&&s("div",{class:"social-section",part:"social-section"},n.map((t=>s("authhero-node",{key:t.id,component:t,value:this.formData[t.id],onFieldChange:t=>this.handleInputChange(t.detail.id,t.detail.value),onButtonClick:t=>this.handleButtonClick(t.detail),disabled:this.loading,exportparts:l(t)})))),n.length>0&&r.length>0&&c&&s("div",{class:"divider",part:"divider"},s("span",{class:"divider-text"},h)),s("div",{class:"fields-section",part:"fields-section"},r.map((t=>s("authhero-node",{key:t.id,component:t,value:this.formData[t.id],onFieldChange:t=>this.handleInputChange(t.detail.id,t.detail.value),onButtonClick:t=>this.handleButtonClick(t.detail),disabled:this.loading})))))),t.links&&t.links.length>0&&s("div",{class:"links",part:"links"},t.links.map((t=>s("span",{class:"link-wrapper",part:"link-wrapper",key:t.id??t.href},t.linkText?s("span",null,t.text," ",s("a",{href:t.href,class:"link",part:"link",onClick:e=>this.handleLinkClick(e,{id:t.id,href:t.href,text:t.linkText||t.text})},t.linkText)):s("a",{href:t.href,class:"link",part:"link",onClick:e=>this.handleLinkClick(e,{id:t.id,href:t.href,text:t.text})},t.text))))),t.footer&&s("div",{class:"widget-footer",part:"footer",innerHTML:g(t.footer)})))}static get watchers(){return{screenId:[{watchScreenId:0}],screen:[{watchScreen:0}],branding:[{watchBranding:0}],theme:[{watchTheme:0}],authParams:[{watchAuthParams:0}]}}};w.style=":host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 16px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{width:var(--ah-widget-max-width, 400px);margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 24px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-subtitle, 16px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}";export{w as authhero_widget}