@authhero/widget 0.28.2 → 0.29.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.
@@ -1019,7 +1019,18 @@ const AuthheroWidget = class {
1019
1019
  e.preventDefault();
1020
1020
  if (!this._screen || this.loading)
1021
1021
  return;
1022
- const submitData = overrideData || this.formData;
1022
+ let submitData = { ...this.formData, ...(overrideData || {}) };
1023
+ // Merge hidden input values from DOM (may have been set programmatically
1024
+ // by inline scripts, e.g. passkey management buttons)
1025
+ const form = this.el.shadowRoot?.querySelector("form");
1026
+ if (form) {
1027
+ const hiddenInputs = form.querySelectorAll('input[type="hidden"]');
1028
+ hiddenInputs.forEach((input) => {
1029
+ if (input.name && input.value) {
1030
+ submitData[input.name] = input.value;
1031
+ }
1032
+ });
1033
+ }
1023
1034
  // Always emit the submit event
1024
1035
  this.formSubmit.emit({
1025
1036
  screen: this._screen,
@@ -1095,6 +1106,10 @@ const AuthheroWidget = class {
1095
1106
  this.state = result.state;
1096
1107
  this.persistState();
1097
1108
  }
1109
+ // Perform WebAuthn ceremony if present (structured data, not script)
1110
+ if (result.ceremony) {
1111
+ this.performWebAuthnCeremony(result.ceremony);
1112
+ }
1098
1113
  // Focus first input on new screen
1099
1114
  this.focusFirstInput();
1100
1115
  }
@@ -1128,6 +1143,192 @@ const AuthheroWidget = class {
1128
1143
  this.loading = false;
1129
1144
  }
1130
1145
  };
1146
+ /**
1147
+ * Override form.submit() so that scripts (e.g. WebAuthn ceremony) that call
1148
+ * form.submit() go through the widget's JSON fetch pipeline instead of a
1149
+ * native form-urlencoded POST.
1150
+ */
1151
+ overrideFormSubmit() {
1152
+ const shadowRoot = this.el.shadowRoot;
1153
+ if (!shadowRoot)
1154
+ return;
1155
+ const form = shadowRoot.querySelector("form");
1156
+ if (!form)
1157
+ return;
1158
+ form.submit = () => {
1159
+ const formData = new FormData(form);
1160
+ const data = {};
1161
+ formData.forEach((value, key) => {
1162
+ if (typeof value === "string") {
1163
+ data[key] = value;
1164
+ }
1165
+ });
1166
+ const syntheticEvent = { preventDefault: () => { } };
1167
+ this.handleSubmit(syntheticEvent, data);
1168
+ };
1169
+ }
1170
+ /**
1171
+ * Validate and execute a structured WebAuthn ceremony returned by the server.
1172
+ * Instead of injecting arbitrary script content, this parses the ceremony JSON,
1173
+ * validates the expected fields, and calls the WebAuthn API natively.
1174
+ */
1175
+ performWebAuthnCeremony(ceremony) {
1176
+ if (!this.isValidWebAuthnCeremony(ceremony)) {
1177
+ console.error("Invalid WebAuthn ceremony payload", ceremony);
1178
+ return;
1179
+ }
1180
+ requestAnimationFrame(() => {
1181
+ this.overrideFormSubmit();
1182
+ this.executeWebAuthnRegistration(ceremony);
1183
+ });
1184
+ }
1185
+ /**
1186
+ * Schema validation for WebAuthn ceremony payloads.
1187
+ * Checks required fields and types before invoking browser APIs.
1188
+ */
1189
+ isValidWebAuthnCeremony(data) {
1190
+ if (typeof data !== "object" || data === null)
1191
+ return false;
1192
+ const obj = data;
1193
+ if (obj.type !== "webauthn-registration")
1194
+ return false;
1195
+ if (typeof obj.successAction !== "string")
1196
+ return false;
1197
+ const opts = obj.options;
1198
+ if (typeof opts !== "object" || opts === null)
1199
+ return false;
1200
+ const o = opts;
1201
+ if (typeof o.challenge !== "string")
1202
+ return false;
1203
+ const rp = o.rp;
1204
+ if (typeof rp !== "object" || rp === null)
1205
+ return false;
1206
+ if (typeof rp.id !== "string" ||
1207
+ typeof rp.name !== "string")
1208
+ return false;
1209
+ const user = o.user;
1210
+ if (typeof user !== "object" || user === null)
1211
+ return false;
1212
+ const u = user;
1213
+ if (typeof u.id !== "string" ||
1214
+ typeof u.name !== "string" ||
1215
+ typeof u.displayName !== "string")
1216
+ return false;
1217
+ if (!Array.isArray(o.pubKeyCredParams))
1218
+ return false;
1219
+ return true;
1220
+ }
1221
+ /**
1222
+ * Perform the WebAuthn navigator.credentials.create() ceremony and submit
1223
+ * the credential result via the form.
1224
+ */
1225
+ async executeWebAuthnRegistration(ceremony) {
1226
+ const opts = ceremony.options;
1227
+ const b64uToBuf = (s) => {
1228
+ s = s.replace(/-/g, "+").replace(/_/g, "/");
1229
+ while (s.length % 4)
1230
+ s += "=";
1231
+ const b = atob(s);
1232
+ const a = new Uint8Array(b.length);
1233
+ for (let i = 0; i < b.length; i++)
1234
+ a[i] = b.charCodeAt(i);
1235
+ return a.buffer;
1236
+ };
1237
+ const bufToB64u = (b) => {
1238
+ const a = new Uint8Array(b);
1239
+ let s = "";
1240
+ for (let i = 0; i < a.length; i++)
1241
+ s += String.fromCharCode(a[i]);
1242
+ return btoa(s)
1243
+ .replace(/\+/g, "-")
1244
+ .replace(/\//g, "_")
1245
+ .replace(/=+$/, "");
1246
+ };
1247
+ const findForm = () => {
1248
+ const shadowRoot = this.el?.shadowRoot;
1249
+ if (shadowRoot) {
1250
+ const f = shadowRoot.querySelector("form");
1251
+ if (f)
1252
+ return f;
1253
+ }
1254
+ return document.querySelector("form");
1255
+ };
1256
+ try {
1257
+ const publicKey = {
1258
+ challenge: b64uToBuf(opts.challenge),
1259
+ rp: { id: opts.rp.id, name: opts.rp.name },
1260
+ user: {
1261
+ id: b64uToBuf(opts.user.id),
1262
+ name: opts.user.name,
1263
+ displayName: opts.user.displayName,
1264
+ },
1265
+ pubKeyCredParams: opts.pubKeyCredParams.map((p) => ({
1266
+ alg: p.alg,
1267
+ type: p.type,
1268
+ })),
1269
+ timeout: opts.timeout,
1270
+ attestation: (opts.attestation || "none"),
1271
+ authenticatorSelection: opts.authenticatorSelection
1272
+ ? {
1273
+ residentKey: (opts.authenticatorSelection.residentKey ||
1274
+ "preferred"),
1275
+ userVerification: (opts.authenticatorSelection
1276
+ .userVerification ||
1277
+ "preferred"),
1278
+ }
1279
+ : undefined,
1280
+ };
1281
+ if (opts.excludeCredentials?.length) {
1282
+ publicKey.excludeCredentials = opts.excludeCredentials.map((c) => ({
1283
+ id: b64uToBuf(c.id),
1284
+ type: c.type,
1285
+ transports: (c.transports || []),
1286
+ }));
1287
+ }
1288
+ const cred = (await navigator.credentials.create({
1289
+ publicKey,
1290
+ }));
1291
+ const response = cred.response;
1292
+ const resp = {
1293
+ id: cred.id,
1294
+ rawId: bufToB64u(cred.rawId),
1295
+ type: cred.type,
1296
+ response: {
1297
+ attestationObject: bufToB64u(response.attestationObject),
1298
+ clientDataJSON: bufToB64u(response.clientDataJSON),
1299
+ },
1300
+ clientExtensionResults: cred.getClientExtensionResults(),
1301
+ authenticatorAttachment: cred.authenticatorAttachment || undefined,
1302
+ };
1303
+ if (typeof response.getTransports === "function") {
1304
+ resp.response.transports =
1305
+ response.getTransports();
1306
+ }
1307
+ const form = findForm();
1308
+ if (form) {
1309
+ const cf = form.querySelector('[name="credential-field"]') ||
1310
+ form.querySelector("#credential-field");
1311
+ const af = form.querySelector('[name="action-field"]') ||
1312
+ form.querySelector("#action-field");
1313
+ if (cf)
1314
+ cf.value = JSON.stringify(resp);
1315
+ if (af)
1316
+ af.value = ceremony.successAction;
1317
+ form.submit();
1318
+ }
1319
+ }
1320
+ catch (e) {
1321
+ console.error("WebAuthn registration error:", e);
1322
+ const form = findForm();
1323
+ if (form) {
1324
+ const af = form.querySelector('[name="action-field"]') ||
1325
+ form.querySelector("#action-field");
1326
+ if (af)
1327
+ af.value = "error";
1328
+ form.submit();
1329
+ }
1330
+ }
1331
+ }
1131
1332
  handleButtonClick = (detail) => {
1132
1333
  // If this is a submit button click, trigger form submission
1133
1334
  if (detail.type === "submit") {
@@ -1330,9 +1531,11 @@ const AuthheroWidget = class {
1330
1531
  // Use the local screen variable for all rendering
1331
1532
  const screenErrors = screen.messages?.filter((m) => m.type === "error") || [];
1332
1533
  const screenSuccesses = screen.messages?.filter((m) => m.type === "success") || [];
1333
- const components = [...(screen.components ?? [])]
1534
+ const allComponents = [...(screen.components ?? [])];
1535
+ const components = allComponents
1334
1536
  .filter((c) => c.visible !== false)
1335
1537
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
1538
+ const hiddenComponents = allComponents.filter((c) => c.visible === false);
1336
1539
  // Separate social, divider, and field components for layout ordering
1337
1540
  const socialComponents = components.filter((c) => this.isSocialComponent(c));
1338
1541
  const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && !this.isDividerComponent(c));
@@ -1367,7 +1570,7 @@ const AuthheroWidget = class {
1367
1570
  };
1368
1571
  // Get logo URL from theme.widget (takes precedence) or branding
1369
1572
  const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
1370
- return (h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, part: "form" }, h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (h("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 &&
1573
+ return (h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, part: "form" }, hiddenComponents.map((c) => (h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (h("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 &&
1371
1574
  fieldComponents.length > 0 &&
1372
1575
  hasDivider && (h("div", { class: "divider", part: "divider" }, h("span", { class: "divider-text" }, dividerText))), h("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (h("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 && (h("div", { class: "links", part: "links" }, screen.links.map((link) => (h("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (h("span", null, link.text, " ", h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
1373
1576
  id: link.id,
package/dist/esm/index.js CHANGED
@@ -1580,20 +1580,38 @@ var decodeURIComponent_ = decodeURIComponent;
1580
1580
  // src/utils/cookie.ts
1581
1581
  var validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/;
1582
1582
  var validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/;
1583
+ var trimCookieWhitespace = (value) => {
1584
+ let start = 0;
1585
+ let end = value.length;
1586
+ while (start < end) {
1587
+ const charCode = value.charCodeAt(start);
1588
+ if (charCode !== 32 && charCode !== 9) {
1589
+ break;
1590
+ }
1591
+ start++;
1592
+ }
1593
+ while (end > start) {
1594
+ const charCode = value.charCodeAt(end - 1);
1595
+ if (charCode !== 32 && charCode !== 9) {
1596
+ break;
1597
+ }
1598
+ end--;
1599
+ }
1600
+ return start === 0 && end === value.length ? value : value.slice(start, end);
1601
+ };
1583
1602
  var parse = (cookie, name) => {
1584
- const pairs = cookie.trim().split(";");
1603
+ const pairs = cookie.split(";");
1585
1604
  const parsedCookie = {};
1586
- for (let pairStr of pairs) {
1587
- pairStr = pairStr.trim();
1605
+ for (const pairStr of pairs) {
1588
1606
  const valueStartPos = pairStr.indexOf("=");
1589
1607
  if (valueStartPos === -1) {
1590
1608
  continue;
1591
1609
  }
1592
- const cookieName = pairStr.substring(0, valueStartPos).trim();
1610
+ const cookieName = trimCookieWhitespace(pairStr.substring(0, valueStartPos));
1593
1611
  if (!validCookieNameRegEx.test(cookieName)) {
1594
1612
  continue;
1595
1613
  }
1596
- let cookieValue = pairStr.substring(valueStartPos + 1).trim();
1614
+ let cookieValue = trimCookieWhitespace(pairStr.substring(valueStartPos + 1));
1597
1615
  if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
1598
1616
  cookieValue = cookieValue.slice(1, -1);
1599
1617
  }
@@ -260,6 +260,28 @@ export declare class AuthheroWidget {
260
260
  fetchScreen(screenIdOverride?: string, nodeId?: string): Promise<boolean>;
261
261
  private handleInputChange;
262
262
  private handleSubmit;
263
+ /**
264
+ * Override form.submit() so that scripts (e.g. WebAuthn ceremony) that call
265
+ * form.submit() go through the widget's JSON fetch pipeline instead of a
266
+ * native form-urlencoded POST.
267
+ */
268
+ private overrideFormSubmit;
269
+ /**
270
+ * Validate and execute a structured WebAuthn ceremony returned by the server.
271
+ * Instead of injecting arbitrary script content, this parses the ceremony JSON,
272
+ * validates the expected fields, and calls the WebAuthn API natively.
273
+ */
274
+ private performWebAuthnCeremony;
275
+ /**
276
+ * Schema validation for WebAuthn ceremony payloads.
277
+ * Checks required fields and types before invoking browser APIs.
278
+ */
279
+ private isValidWebAuthnCeremony;
280
+ /**
281
+ * Perform the WebAuthn navigator.credentials.create() ceremony and submit
282
+ * the credential result via the form.
283
+ */
284
+ private executeWebAuthnRegistration;
263
285
  private handleButtonClick;
264
286
  /**
265
287
  * Handle social login redirect
package/hydrate/index.js CHANGED
@@ -6823,7 +6823,18 @@ class AuthheroWidget {
6823
6823
  e.preventDefault();
6824
6824
  if (!this._screen || this.loading)
6825
6825
  return;
6826
- const submitData = overrideData || this.formData;
6826
+ let submitData = { ...this.formData, ...(overrideData || {}) };
6827
+ // Merge hidden input values from DOM (may have been set programmatically
6828
+ // by inline scripts, e.g. passkey management buttons)
6829
+ const form = this.el.shadowRoot?.querySelector("form");
6830
+ if (form) {
6831
+ const hiddenInputs = form.querySelectorAll('input[type="hidden"]');
6832
+ hiddenInputs.forEach((input) => {
6833
+ if (input.name && input.value) {
6834
+ submitData[input.name] = input.value;
6835
+ }
6836
+ });
6837
+ }
6827
6838
  // Always emit the submit event
6828
6839
  this.formSubmit.emit({
6829
6840
  screen: this._screen,
@@ -6899,6 +6910,10 @@ class AuthheroWidget {
6899
6910
  this.state = result.state;
6900
6911
  this.persistState();
6901
6912
  }
6913
+ // Perform WebAuthn ceremony if present (structured data, not script)
6914
+ if (result.ceremony) {
6915
+ this.performWebAuthnCeremony(result.ceremony);
6916
+ }
6902
6917
  // Focus first input on new screen
6903
6918
  this.focusFirstInput();
6904
6919
  }
@@ -6932,6 +6947,192 @@ class AuthheroWidget {
6932
6947
  this.loading = false;
6933
6948
  }
6934
6949
  };
6950
+ /**
6951
+ * Override form.submit() so that scripts (e.g. WebAuthn ceremony) that call
6952
+ * form.submit() go through the widget's JSON fetch pipeline instead of a
6953
+ * native form-urlencoded POST.
6954
+ */
6955
+ overrideFormSubmit() {
6956
+ const shadowRoot = this.el.shadowRoot;
6957
+ if (!shadowRoot)
6958
+ return;
6959
+ const form = shadowRoot.querySelector("form");
6960
+ if (!form)
6961
+ return;
6962
+ form.submit = () => {
6963
+ const formData = new FormData(form);
6964
+ const data = {};
6965
+ formData.forEach((value, key) => {
6966
+ if (typeof value === "string") {
6967
+ data[key] = value;
6968
+ }
6969
+ });
6970
+ const syntheticEvent = { preventDefault: () => { } };
6971
+ this.handleSubmit(syntheticEvent, data);
6972
+ };
6973
+ }
6974
+ /**
6975
+ * Validate and execute a structured WebAuthn ceremony returned by the server.
6976
+ * Instead of injecting arbitrary script content, this parses the ceremony JSON,
6977
+ * validates the expected fields, and calls the WebAuthn API natively.
6978
+ */
6979
+ performWebAuthnCeremony(ceremony) {
6980
+ if (!this.isValidWebAuthnCeremony(ceremony)) {
6981
+ console.error("Invalid WebAuthn ceremony payload", ceremony);
6982
+ return;
6983
+ }
6984
+ requestAnimationFrame(() => {
6985
+ this.overrideFormSubmit();
6986
+ this.executeWebAuthnRegistration(ceremony);
6987
+ });
6988
+ }
6989
+ /**
6990
+ * Schema validation for WebAuthn ceremony payloads.
6991
+ * Checks required fields and types before invoking browser APIs.
6992
+ */
6993
+ isValidWebAuthnCeremony(data) {
6994
+ if (typeof data !== "object" || data === null)
6995
+ return false;
6996
+ const obj = data;
6997
+ if (obj.type !== "webauthn-registration")
6998
+ return false;
6999
+ if (typeof obj.successAction !== "string")
7000
+ return false;
7001
+ const opts = obj.options;
7002
+ if (typeof opts !== "object" || opts === null)
7003
+ return false;
7004
+ const o = opts;
7005
+ if (typeof o.challenge !== "string")
7006
+ return false;
7007
+ const rp = o.rp;
7008
+ if (typeof rp !== "object" || rp === null)
7009
+ return false;
7010
+ if (typeof rp.id !== "string" ||
7011
+ typeof rp.name !== "string")
7012
+ return false;
7013
+ const user = o.user;
7014
+ if (typeof user !== "object" || user === null)
7015
+ return false;
7016
+ const u = user;
7017
+ if (typeof u.id !== "string" ||
7018
+ typeof u.name !== "string" ||
7019
+ typeof u.displayName !== "string")
7020
+ return false;
7021
+ if (!Array.isArray(o.pubKeyCredParams))
7022
+ return false;
7023
+ return true;
7024
+ }
7025
+ /**
7026
+ * Perform the WebAuthn navigator.credentials.create() ceremony and submit
7027
+ * the credential result via the form.
7028
+ */
7029
+ async executeWebAuthnRegistration(ceremony) {
7030
+ const opts = ceremony.options;
7031
+ const b64uToBuf = (s) => {
7032
+ s = s.replace(/-/g, "+").replace(/_/g, "/");
7033
+ while (s.length % 4)
7034
+ s += "=";
7035
+ const b = atob(s);
7036
+ const a = new Uint8Array(b.length);
7037
+ for (let i = 0; i < b.length; i++)
7038
+ a[i] = b.charCodeAt(i);
7039
+ return a.buffer;
7040
+ };
7041
+ const bufToB64u = (b) => {
7042
+ const a = new Uint8Array(b);
7043
+ let s = "";
7044
+ for (let i = 0; i < a.length; i++)
7045
+ s += String.fromCharCode(a[i]);
7046
+ return btoa(s)
7047
+ .replace(/\+/g, "-")
7048
+ .replace(/\//g, "_")
7049
+ .replace(/=+$/, "");
7050
+ };
7051
+ const findForm = () => {
7052
+ const shadowRoot = this.el?.shadowRoot;
7053
+ if (shadowRoot) {
7054
+ const f = shadowRoot.querySelector("form");
7055
+ if (f)
7056
+ return f;
7057
+ }
7058
+ return document.querySelector("form");
7059
+ };
7060
+ try {
7061
+ const publicKey = {
7062
+ challenge: b64uToBuf(opts.challenge),
7063
+ rp: { id: opts.rp.id, name: opts.rp.name },
7064
+ user: {
7065
+ id: b64uToBuf(opts.user.id),
7066
+ name: opts.user.name,
7067
+ displayName: opts.user.displayName,
7068
+ },
7069
+ pubKeyCredParams: opts.pubKeyCredParams.map((p) => ({
7070
+ alg: p.alg,
7071
+ type: p.type,
7072
+ })),
7073
+ timeout: opts.timeout,
7074
+ attestation: (opts.attestation || "none"),
7075
+ authenticatorSelection: opts.authenticatorSelection
7076
+ ? {
7077
+ residentKey: (opts.authenticatorSelection.residentKey ||
7078
+ "preferred"),
7079
+ userVerification: (opts.authenticatorSelection
7080
+ .userVerification ||
7081
+ "preferred"),
7082
+ }
7083
+ : undefined,
7084
+ };
7085
+ if (opts.excludeCredentials?.length) {
7086
+ publicKey.excludeCredentials = opts.excludeCredentials.map((c) => ({
7087
+ id: b64uToBuf(c.id),
7088
+ type: c.type,
7089
+ transports: (c.transports || []),
7090
+ }));
7091
+ }
7092
+ const cred = (await navigator.credentials.create({
7093
+ publicKey,
7094
+ }));
7095
+ const response = cred.response;
7096
+ const resp = {
7097
+ id: cred.id,
7098
+ rawId: bufToB64u(cred.rawId),
7099
+ type: cred.type,
7100
+ response: {
7101
+ attestationObject: bufToB64u(response.attestationObject),
7102
+ clientDataJSON: bufToB64u(response.clientDataJSON),
7103
+ },
7104
+ clientExtensionResults: cred.getClientExtensionResults(),
7105
+ authenticatorAttachment: cred.authenticatorAttachment || undefined,
7106
+ };
7107
+ if (typeof response.getTransports === "function") {
7108
+ resp.response.transports =
7109
+ response.getTransports();
7110
+ }
7111
+ const form = findForm();
7112
+ if (form) {
7113
+ const cf = form.querySelector('[name="credential-field"]') ||
7114
+ form.querySelector("#credential-field");
7115
+ const af = form.querySelector('[name="action-field"]') ||
7116
+ form.querySelector("#action-field");
7117
+ if (cf)
7118
+ cf.value = JSON.stringify(resp);
7119
+ if (af)
7120
+ af.value = ceremony.successAction;
7121
+ form.submit();
7122
+ }
7123
+ }
7124
+ catch (e) {
7125
+ console.error("WebAuthn registration error:", e);
7126
+ const form = findForm();
7127
+ if (form) {
7128
+ const af = form.querySelector('[name="action-field"]') ||
7129
+ form.querySelector("#action-field");
7130
+ if (af)
7131
+ af.value = "error";
7132
+ form.submit();
7133
+ }
7134
+ }
7135
+ }
6935
7136
  handleButtonClick = (detail) => {
6936
7137
  // If this is a submit button click, trigger form submission
6937
7138
  if (detail.type === "submit") {
@@ -7134,9 +7335,11 @@ class AuthheroWidget {
7134
7335
  // Use the local screen variable for all rendering
7135
7336
  const screenErrors = screen.messages?.filter((m) => m.type === "error") || [];
7136
7337
  const screenSuccesses = screen.messages?.filter((m) => m.type === "success") || [];
7137
- const components = [...(screen.components ?? [])]
7338
+ const allComponents = [...(screen.components ?? [])];
7339
+ const components = allComponents
7138
7340
  .filter((c) => c.visible !== false)
7139
7341
  .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
7342
+ const hiddenComponents = allComponents.filter((c) => c.visible === false);
7140
7343
  // Separate social, divider, and field components for layout ordering
7141
7344
  const socialComponents = components.filter((c) => this.isSocialComponent(c));
7142
7345
  const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && !this.isDividerComponent(c));
@@ -7171,7 +7374,7 @@ class AuthheroWidget {
7171
7374
  };
7172
7375
  // Get logo URL from theme.widget (takes precedence) or branding
7173
7376
  const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
7174
- 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 &&
7377
+ 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 &&
7175
7378
  fieldComponents.length > 0 &&
7176
7379
  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, {
7177
7380
  id: link.id,