@authhero/widget 0.29.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authhero-widget/authhero-widget.esm.js +1 -1
- package/dist/authhero-widget/index.esm.js +1 -1
- package/dist/authhero-widget/p-53f4e14a.entry.js +1 -0
- package/dist/authhero-widget/p-e91b632f.entry.js +1 -0
- package/dist/cjs/authhero-node.cjs.entry.js +39 -38
- package/dist/cjs/authhero-widget.cjs.entry.js +237 -21
- package/dist/cjs/index.cjs.js +1 -1
- package/dist/collection/components/authhero-node/authhero-node.js +39 -38
- package/dist/collection/components/authhero-widget/authhero-widget.js +237 -21
- package/dist/components/authhero-node.js +1 -1
- package/dist/components/authhero-widget.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/p-DqexL9yF.js +1 -0
- package/dist/esm/authhero-node.entry.js +39 -38
- package/dist/esm/authhero-widget.entry.js +237 -21
- package/dist/esm/index.js +1 -1
- package/dist/types/components/authhero-node/authhero-node.d.ts +8 -0
- package/dist/types/components/authhero-widget/authhero-widget.d.ts +16 -0
- package/hydrate/index.js +276 -59
- package/hydrate/index.mjs +276 -59
- package/package.json +2 -2
- package/dist/authhero-widget/p-5428e2e1.entry.js +0 -1
- package/dist/authhero-widget/p-8514f73f.entry.js +0 -1
- package/dist/components/p-CncHQfQk.js +0 -1
package/hydrate/index.mjs
CHANGED
|
@@ -5341,6 +5341,38 @@ class AuthheroNode {
|
|
|
5341
5341
|
}
|
|
5342
5342
|
return null;
|
|
5343
5343
|
}
|
|
5344
|
+
/**
|
|
5345
|
+
* Shared phone-input cleaning logic. Detects dial codes, strips non-phone
|
|
5346
|
+
* characters, updates the input value, and emits the full number.
|
|
5347
|
+
* @param allowPlus When true, uses a two-pass clean that first keeps '+'
|
|
5348
|
+
* then strips it (for combined tel+email fields). When false, strips '+'
|
|
5349
|
+
* in a single pass (phone-only fields where the picker provides the prefix).
|
|
5350
|
+
*/
|
|
5351
|
+
processPhoneInput(target, value, allowPlus) {
|
|
5352
|
+
const dialLocal = this.detectDialCodeFromInput(value);
|
|
5353
|
+
if (dialLocal !== null) {
|
|
5354
|
+
const cleanedLocal = allowPlus
|
|
5355
|
+
? dialLocal.replace(/[^+\d\s\-()]/g, "").replace(/\+/g, "")
|
|
5356
|
+
: dialLocal.replace(/[^\d\s\-()]/g, "");
|
|
5357
|
+
target.value = cleanedLocal;
|
|
5358
|
+
this.localPhoneNumber = cleanedLocal;
|
|
5359
|
+
const fullNumber = `${this.selectedCountry.dialCode}${cleanedLocal}`;
|
|
5360
|
+
this.fieldChange.emit({ id: this.component.id, value: fullNumber });
|
|
5361
|
+
}
|
|
5362
|
+
else {
|
|
5363
|
+
const cleaned = allowPlus
|
|
5364
|
+
? value.replace(/[^+\d\s\-()]/g, "").replace(/\+/g, "")
|
|
5365
|
+
: value.replace(/[^\d\s\-()]/g, "");
|
|
5366
|
+
if (cleaned !== value) {
|
|
5367
|
+
target.value = cleaned;
|
|
5368
|
+
}
|
|
5369
|
+
this.localPhoneNumber = cleaned;
|
|
5370
|
+
const fullNumber = cleaned
|
|
5371
|
+
? `${this.selectedCountry.dialCode}${cleaned}`
|
|
5372
|
+
: "";
|
|
5373
|
+
this.fieldChange.emit({ id: this.component.id, value: fullNumber });
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5344
5376
|
handlePhoneInput = (e) => {
|
|
5345
5377
|
const target = e.target;
|
|
5346
5378
|
const value = target.value;
|
|
@@ -5353,31 +5385,7 @@ class AuthheroNode {
|
|
|
5353
5385
|
const looksLikePhone = value.length > 0 && /^[+\d]/.test(value) && !value.includes("@");
|
|
5354
5386
|
this.telEmailMode = !looksLikePhone;
|
|
5355
5387
|
if (!this.telEmailMode) {
|
|
5356
|
-
|
|
5357
|
-
// so that typing +46 or 0046 can match a country
|
|
5358
|
-
const dialLocal = this.detectDialCodeFromInput(value);
|
|
5359
|
-
if (dialLocal !== null) {
|
|
5360
|
-
// Dial code matched — strip it from the input and show only local part
|
|
5361
|
-
const cleanedLocal = dialLocal
|
|
5362
|
-
.replace(/[^+\d\s\-()]/g, "")
|
|
5363
|
-
.replace(/\+/g, "");
|
|
5364
|
-
target.value = cleanedLocal;
|
|
5365
|
-
this.localPhoneNumber = cleanedLocal;
|
|
5366
|
-
const fullNumber = `${this.selectedCountry.dialCode}${cleanedLocal}`;
|
|
5367
|
-
this.fieldChange.emit({ id: this.component.id, value: fullNumber });
|
|
5368
|
-
}
|
|
5369
|
-
else {
|
|
5370
|
-
// No dial code — strip non-phone chars and '+' (picker provides the prefix)
|
|
5371
|
-
const cleaned = value.replace(/[^+\d\s\-()]/g, "").replace(/\+/g, "");
|
|
5372
|
-
if (cleaned !== value) {
|
|
5373
|
-
target.value = cleaned;
|
|
5374
|
-
}
|
|
5375
|
-
this.localPhoneNumber = cleaned;
|
|
5376
|
-
const fullNumber = cleaned
|
|
5377
|
-
? `${this.selectedCountry.dialCode}${cleaned}`
|
|
5378
|
-
: "";
|
|
5379
|
-
this.fieldChange.emit({ id: this.component.id, value: fullNumber });
|
|
5380
|
-
}
|
|
5388
|
+
this.processPhoneInput(target, value, true);
|
|
5381
5389
|
}
|
|
5382
5390
|
else {
|
|
5383
5391
|
// Email or text — emit as-is
|
|
@@ -5386,16 +5394,8 @@ class AuthheroNode {
|
|
|
5386
5394
|
}
|
|
5387
5395
|
return;
|
|
5388
5396
|
}
|
|
5389
|
-
// Standard phone-only mode
|
|
5390
|
-
|
|
5391
|
-
if (cleaned !== value) {
|
|
5392
|
-
target.value = cleaned;
|
|
5393
|
-
}
|
|
5394
|
-
this.localPhoneNumber = cleaned;
|
|
5395
|
-
const fullNumber = cleaned
|
|
5396
|
-
? `${this.selectedCountry.dialCode}${cleaned}`
|
|
5397
|
-
: "";
|
|
5398
|
-
this.fieldChange.emit({ id: this.component.id, value: fullNumber });
|
|
5397
|
+
// Standard phone-only mode
|
|
5398
|
+
this.processPhoneInput(target, value, false);
|
|
5399
5399
|
};
|
|
5400
5400
|
handleInput = (e) => {
|
|
5401
5401
|
const target = e.target;
|
|
@@ -5555,20 +5555,21 @@ class AuthheroNode {
|
|
|
5555
5555
|
renderTextField(component) {
|
|
5556
5556
|
const inputId = `input-${component.id}`;
|
|
5557
5557
|
const errors = this.getErrors();
|
|
5558
|
-
const { multiline, max_length } = component.config ?? {};
|
|
5558
|
+
const { multiline, max_length, autocomplete } = component.config ?? {};
|
|
5559
5559
|
const effectiveValue = this.getEffectiveValue();
|
|
5560
5560
|
const hasValue = !!(effectiveValue && effectiveValue.length > 0);
|
|
5561
5561
|
if (multiline) {
|
|
5562
5562
|
return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("textarea", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input textarea", name: component.id, placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput }, effectiveValue ?? ""), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
|
|
5563
5563
|
}
|
|
5564
|
-
return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
|
|
5564
|
+
return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, autoComplete: autocomplete, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
|
|
5565
5565
|
}
|
|
5566
5566
|
renderEmailField(component) {
|
|
5567
5567
|
const inputId = `input-${component.id}`;
|
|
5568
5568
|
const errors = this.getErrors();
|
|
5569
5569
|
const effectiveValue = this.getEffectiveValue();
|
|
5570
5570
|
const hasValue = !!(effectiveValue && effectiveValue.length > 0);
|
|
5571
|
-
|
|
5571
|
+
const { autocomplete } = (component.config ?? {});
|
|
5572
|
+
return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "email", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: autocomplete || "email", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
|
|
5572
5573
|
}
|
|
5573
5574
|
renderPasswordField(component) {
|
|
5574
5575
|
const inputId = `input-${component.id}`;
|
|
@@ -6433,6 +6434,11 @@ class AuthheroWidget {
|
|
|
6433
6434
|
* Form data collected from inputs.
|
|
6434
6435
|
*/
|
|
6435
6436
|
formData = {};
|
|
6437
|
+
/**
|
|
6438
|
+
* AbortController for an in-flight conditional mediation request.
|
|
6439
|
+
* Aborted on screen change or component disconnect.
|
|
6440
|
+
*/
|
|
6441
|
+
conditionalMediationAbort;
|
|
6436
6442
|
/**
|
|
6437
6443
|
* Emitted when the form is submitted.
|
|
6438
6444
|
* The consuming application should handle the submission unless autoSubmit is true.
|
|
@@ -6467,6 +6473,9 @@ class AuthheroWidget {
|
|
|
6467
6473
|
*/
|
|
6468
6474
|
screenChange;
|
|
6469
6475
|
watchScreen(newValue) {
|
|
6476
|
+
// Abort any in-flight conditional mediation when screen changes
|
|
6477
|
+
this.conditionalMediationAbort?.abort();
|
|
6478
|
+
this.conditionalMediationAbort = undefined;
|
|
6470
6479
|
if (typeof newValue === "string") {
|
|
6471
6480
|
try {
|
|
6472
6481
|
this._screen = JSON.parse(newValue);
|
|
@@ -6700,6 +6709,8 @@ class AuthheroWidget {
|
|
|
6700
6709
|
}
|
|
6701
6710
|
disconnectedCallback() {
|
|
6702
6711
|
window.removeEventListener("popstate", this.handlePopState);
|
|
6712
|
+
this.conditionalMediationAbort?.abort();
|
|
6713
|
+
this.conditionalMediationAbort = undefined;
|
|
6703
6714
|
}
|
|
6704
6715
|
async componentWillLoad() {
|
|
6705
6716
|
// Parse initial props - this prevents unnecessary state changes during hydration that cause flashes
|
|
@@ -6788,6 +6799,10 @@ class AuthheroWidget {
|
|
|
6788
6799
|
this.updateDataScreenAttribute();
|
|
6789
6800
|
this.persistState();
|
|
6790
6801
|
this.focusFirstInput();
|
|
6802
|
+
// Start WebAuthn ceremony if returned with the screen (e.g. conditional mediation)
|
|
6803
|
+
if (data.ceremony) {
|
|
6804
|
+
this.performWebAuthnCeremony(data.ceremony);
|
|
6805
|
+
}
|
|
6791
6806
|
return true;
|
|
6792
6807
|
}
|
|
6793
6808
|
}
|
|
@@ -6979,9 +6994,19 @@ class AuthheroWidget {
|
|
|
6979
6994
|
console.error("Invalid WebAuthn ceremony payload", ceremony);
|
|
6980
6995
|
return;
|
|
6981
6996
|
}
|
|
6997
|
+
if (ceremony.type === "webauthn-authentication-conditional") {
|
|
6998
|
+
// Conditional mediation runs in the background, no requestAnimationFrame needed
|
|
6999
|
+
this.executeWebAuthnConditionalMediation(ceremony);
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
6982
7002
|
requestAnimationFrame(() => {
|
|
6983
7003
|
this.overrideFormSubmit();
|
|
6984
|
-
|
|
7004
|
+
if (ceremony.type === "webauthn-authentication") {
|
|
7005
|
+
this.executeWebAuthnAuthentication(ceremony);
|
|
7006
|
+
}
|
|
7007
|
+
else {
|
|
7008
|
+
this.executeWebAuthnRegistration(ceremony);
|
|
7009
|
+
}
|
|
6985
7010
|
});
|
|
6986
7011
|
}
|
|
6987
7012
|
/**
|
|
@@ -6992,8 +7017,6 @@ class AuthheroWidget {
|
|
|
6992
7017
|
if (typeof data !== "object" || data === null)
|
|
6993
7018
|
return false;
|
|
6994
7019
|
const obj = data;
|
|
6995
|
-
if (obj.type !== "webauthn-registration")
|
|
6996
|
-
return false;
|
|
6997
7020
|
if (typeof obj.successAction !== "string")
|
|
6998
7021
|
return false;
|
|
6999
7022
|
const opts = obj.options;
|
|
@@ -7002,23 +7025,30 @@ class AuthheroWidget {
|
|
|
7002
7025
|
const o = opts;
|
|
7003
7026
|
if (typeof o.challenge !== "string")
|
|
7004
7027
|
return false;
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
typeof rp.
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
typeof u.
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7028
|
+
if (obj.type === "webauthn-registration") {
|
|
7029
|
+
const rp = o.rp;
|
|
7030
|
+
if (typeof rp !== "object" || rp === null)
|
|
7031
|
+
return false;
|
|
7032
|
+
if (typeof rp.id !== "string" ||
|
|
7033
|
+
typeof rp.name !== "string")
|
|
7034
|
+
return false;
|
|
7035
|
+
const user = o.user;
|
|
7036
|
+
if (typeof user !== "object" || user === null)
|
|
7037
|
+
return false;
|
|
7038
|
+
const u = user;
|
|
7039
|
+
if (typeof u.id !== "string" ||
|
|
7040
|
+
typeof u.name !== "string" ||
|
|
7041
|
+
typeof u.displayName !== "string")
|
|
7042
|
+
return false;
|
|
7043
|
+
if (!Array.isArray(o.pubKeyCredParams))
|
|
7044
|
+
return false;
|
|
7045
|
+
return true;
|
|
7046
|
+
}
|
|
7047
|
+
if (obj.type === "webauthn-authentication" ||
|
|
7048
|
+
obj.type === "webauthn-authentication-conditional") {
|
|
7049
|
+
return true;
|
|
7050
|
+
}
|
|
7051
|
+
return false;
|
|
7022
7052
|
}
|
|
7023
7053
|
/**
|
|
7024
7054
|
* Perform the WebAuthn navigator.credentials.create() ceremony and submit
|
|
@@ -7131,6 +7161,193 @@ class AuthheroWidget {
|
|
|
7131
7161
|
}
|
|
7132
7162
|
}
|
|
7133
7163
|
}
|
|
7164
|
+
/**
|
|
7165
|
+
* Perform the WebAuthn navigator.credentials.get() ceremony (explicit modal)
|
|
7166
|
+
* and submit the credential result via the form.
|
|
7167
|
+
*/
|
|
7168
|
+
async executeWebAuthnAuthentication(ceremony) {
|
|
7169
|
+
const opts = ceremony.options;
|
|
7170
|
+
const b64uToBuf = (s) => {
|
|
7171
|
+
s = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
7172
|
+
while (s.length % 4)
|
|
7173
|
+
s += "=";
|
|
7174
|
+
const b = atob(s);
|
|
7175
|
+
const a = new Uint8Array(b.length);
|
|
7176
|
+
for (let i = 0; i < b.length; i++)
|
|
7177
|
+
a[i] = b.charCodeAt(i);
|
|
7178
|
+
return a.buffer;
|
|
7179
|
+
};
|
|
7180
|
+
const bufToB64u = (b) => {
|
|
7181
|
+
const a = new Uint8Array(b);
|
|
7182
|
+
let s = "";
|
|
7183
|
+
for (let i = 0; i < a.length; i++)
|
|
7184
|
+
s += String.fromCharCode(a[i]);
|
|
7185
|
+
return btoa(s)
|
|
7186
|
+
.replace(/\+/g, "-")
|
|
7187
|
+
.replace(/\//g, "_")
|
|
7188
|
+
.replace(/=+$/, "");
|
|
7189
|
+
};
|
|
7190
|
+
const findForm = () => {
|
|
7191
|
+
const shadowRoot = this.el?.shadowRoot;
|
|
7192
|
+
if (shadowRoot) {
|
|
7193
|
+
const f = shadowRoot.querySelector("form");
|
|
7194
|
+
if (f)
|
|
7195
|
+
return f;
|
|
7196
|
+
}
|
|
7197
|
+
return document.querySelector("form");
|
|
7198
|
+
};
|
|
7199
|
+
try {
|
|
7200
|
+
const publicKey = {
|
|
7201
|
+
challenge: b64uToBuf(opts.challenge),
|
|
7202
|
+
rpId: opts.rpId,
|
|
7203
|
+
timeout: opts.timeout,
|
|
7204
|
+
userVerification: opts.userVerification || "preferred",
|
|
7205
|
+
};
|
|
7206
|
+
if (opts.allowCredentials?.length) {
|
|
7207
|
+
publicKey.allowCredentials = opts.allowCredentials.map((c) => ({
|
|
7208
|
+
id: b64uToBuf(c.id),
|
|
7209
|
+
type: c.type,
|
|
7210
|
+
transports: (c.transports || []),
|
|
7211
|
+
}));
|
|
7212
|
+
}
|
|
7213
|
+
const cred = (await navigator.credentials.get({
|
|
7214
|
+
publicKey,
|
|
7215
|
+
}));
|
|
7216
|
+
const response = cred.response;
|
|
7217
|
+
const resp = {
|
|
7218
|
+
id: cred.id,
|
|
7219
|
+
rawId: bufToB64u(cred.rawId),
|
|
7220
|
+
type: cred.type,
|
|
7221
|
+
response: {
|
|
7222
|
+
authenticatorData: bufToB64u(response.authenticatorData),
|
|
7223
|
+
clientDataJSON: bufToB64u(response.clientDataJSON),
|
|
7224
|
+
signature: bufToB64u(response.signature),
|
|
7225
|
+
},
|
|
7226
|
+
clientExtensionResults: cred.getClientExtensionResults(),
|
|
7227
|
+
authenticatorAttachment: cred.authenticatorAttachment || undefined,
|
|
7228
|
+
};
|
|
7229
|
+
if (response.userHandle) {
|
|
7230
|
+
resp.response.userHandle = bufToB64u(response.userHandle);
|
|
7231
|
+
}
|
|
7232
|
+
const form = findForm();
|
|
7233
|
+
if (form) {
|
|
7234
|
+
const cf = form.querySelector('[name="credential-field"]') ||
|
|
7235
|
+
form.querySelector("#credential-field");
|
|
7236
|
+
const af = form.querySelector('[name="action-field"]') ||
|
|
7237
|
+
form.querySelector("#action-field");
|
|
7238
|
+
if (cf)
|
|
7239
|
+
cf.value = JSON.stringify(resp);
|
|
7240
|
+
if (af)
|
|
7241
|
+
af.value = ceremony.successAction;
|
|
7242
|
+
form.submit();
|
|
7243
|
+
}
|
|
7244
|
+
}
|
|
7245
|
+
catch (e) {
|
|
7246
|
+
console.error("WebAuthn authentication error:", e);
|
|
7247
|
+
const form = findForm();
|
|
7248
|
+
if (form) {
|
|
7249
|
+
const af = form.querySelector('[name="action-field"]') ||
|
|
7250
|
+
form.querySelector("#action-field");
|
|
7251
|
+
if (af)
|
|
7252
|
+
af.value = "error";
|
|
7253
|
+
form.submit();
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7258
|
+
* Execute WebAuthn conditional mediation (autofill-assisted passkeys).
|
|
7259
|
+
* Runs in the background — the browser shows passkey suggestions in the
|
|
7260
|
+
* username field's autofill dropdown. Silently ignored if unsupported.
|
|
7261
|
+
*/
|
|
7262
|
+
async executeWebAuthnConditionalMediation(ceremony) {
|
|
7263
|
+
// Feature detection
|
|
7264
|
+
if (!window.PublicKeyCredential ||
|
|
7265
|
+
!PublicKeyCredential.isConditionalMediationAvailable) {
|
|
7266
|
+
return;
|
|
7267
|
+
}
|
|
7268
|
+
const available = await PublicKeyCredential.isConditionalMediationAvailable();
|
|
7269
|
+
if (!available)
|
|
7270
|
+
return;
|
|
7271
|
+
// Abort any previous conditional mediation request
|
|
7272
|
+
this.conditionalMediationAbort?.abort();
|
|
7273
|
+
const abortController = new AbortController();
|
|
7274
|
+
this.conditionalMediationAbort = abortController;
|
|
7275
|
+
const opts = ceremony.options;
|
|
7276
|
+
const b64uToBuf = (s) => {
|
|
7277
|
+
s = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
7278
|
+
while (s.length % 4)
|
|
7279
|
+
s += "=";
|
|
7280
|
+
const b = atob(s);
|
|
7281
|
+
const a = new Uint8Array(b.length);
|
|
7282
|
+
for (let i = 0; i < b.length; i++)
|
|
7283
|
+
a[i] = b.charCodeAt(i);
|
|
7284
|
+
return a.buffer;
|
|
7285
|
+
};
|
|
7286
|
+
const bufToB64u = (b) => {
|
|
7287
|
+
const a = new Uint8Array(b);
|
|
7288
|
+
let s = "";
|
|
7289
|
+
for (let i = 0; i < a.length; i++)
|
|
7290
|
+
s += String.fromCharCode(a[i]);
|
|
7291
|
+
return btoa(s)
|
|
7292
|
+
.replace(/\+/g, "-")
|
|
7293
|
+
.replace(/\//g, "_")
|
|
7294
|
+
.replace(/=+$/, "");
|
|
7295
|
+
};
|
|
7296
|
+
try {
|
|
7297
|
+
const cred = (await navigator.credentials.get({
|
|
7298
|
+
mediation: "conditional",
|
|
7299
|
+
signal: abortController.signal,
|
|
7300
|
+
publicKey: {
|
|
7301
|
+
challenge: b64uToBuf(opts.challenge),
|
|
7302
|
+
rpId: opts.rpId,
|
|
7303
|
+
timeout: opts.timeout,
|
|
7304
|
+
userVerification: opts.userVerification ||
|
|
7305
|
+
"preferred",
|
|
7306
|
+
},
|
|
7307
|
+
}));
|
|
7308
|
+
const response = cred.response;
|
|
7309
|
+
const resp = {
|
|
7310
|
+
id: cred.id,
|
|
7311
|
+
rawId: bufToB64u(cred.rawId),
|
|
7312
|
+
type: cred.type,
|
|
7313
|
+
response: {
|
|
7314
|
+
authenticatorData: bufToB64u(response.authenticatorData),
|
|
7315
|
+
clientDataJSON: bufToB64u(response.clientDataJSON),
|
|
7316
|
+
signature: bufToB64u(response.signature),
|
|
7317
|
+
},
|
|
7318
|
+
clientExtensionResults: cred.getClientExtensionResults(),
|
|
7319
|
+
authenticatorAttachment: cred.authenticatorAttachment || undefined,
|
|
7320
|
+
};
|
|
7321
|
+
if (response.userHandle) {
|
|
7322
|
+
resp.response.userHandle = bufToB64u(response.userHandle);
|
|
7323
|
+
}
|
|
7324
|
+
// Submit via the widget's form handling
|
|
7325
|
+
this.formData["credential-field"] = JSON.stringify(resp);
|
|
7326
|
+
this.formData["action-field"] = ceremony.successAction;
|
|
7327
|
+
// Ensure form submit override is set up, then submit
|
|
7328
|
+
this.overrideFormSubmit();
|
|
7329
|
+
const shadowRoot = this.el?.shadowRoot;
|
|
7330
|
+
const form = shadowRoot?.querySelector("form");
|
|
7331
|
+
if (form) {
|
|
7332
|
+
// Set the hidden input values directly
|
|
7333
|
+
const cf = form.querySelector('[name="credential-field"]') ||
|
|
7334
|
+
form.querySelector("#credential-field");
|
|
7335
|
+
const af = form.querySelector('[name="action-field"]') ||
|
|
7336
|
+
form.querySelector("#action-field");
|
|
7337
|
+
if (cf)
|
|
7338
|
+
cf.value = JSON.stringify(resp);
|
|
7339
|
+
if (af)
|
|
7340
|
+
af.value = ceremony.successAction;
|
|
7341
|
+
form.submit();
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
catch (e) {
|
|
7345
|
+
// Silently ignore AbortError and NotAllowedError
|
|
7346
|
+
if (e?.name === "AbortError" || e?.name === "NotAllowedError")
|
|
7347
|
+
return;
|
|
7348
|
+
console.error("Conditional mediation error:", e);
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7134
7351
|
handleButtonClick = (detail) => {
|
|
7135
7352
|
// If this is a submit button click, trigger form submission
|
|
7136
7353
|
if (detail.type === "submit") {
|
|
@@ -7372,7 +7589,7 @@ class AuthheroWidget {
|
|
|
7372
7589
|
};
|
|
7373
7590
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
7374
7591
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
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 &&
|
|
7592
|
+
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, action: screen.action, method: screen.method || "POST", 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 &&
|
|
7376
7593
|
fieldComponents.length > 0 &&
|
|
7377
7594
|
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, {
|
|
7378
7595
|
id: link.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authhero/widget",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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.
|
|
41
|
+
"@authhero/adapter-interfaces": "0.155.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 n(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 r(t){const e=n(t);if(!e)return NaN;const[i,s,r]=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*r}function o(t,e){const i=r(t),s=r(e);return isNaN(i)||isNaN(s)?NaN:(Math.max(i,s)+.05)/(Math.min(i,s)+.05)}function a(t,e="light"){const i=o(t,"#000000"),s=o(t,"#ffffff");return"light"===e?i>1.35*s?"#000000":"#ffffff":1.35*i>s?"#000000":"#ffffff"}function c(t,e){const i=n(t);if(!i)return t;const[s,r,o]=i,a=t=>Math.max(0,Math.round(t*(1-e))).toString(16).padStart(2,"0");return`#${a(s)}${a(r)}${a(o)}`}function h(t,e){const i=n(t);if(!i)return t;const[s,r,o]=i,a=t=>Math.min(255,Math.round(t+(255-t)*e)).toString(16).padStart(2,"0");return`#${a(s)}${a(r)}${a(o)}`}function l(t,e,i=4.5){if(o(t,e)>=i)return t;const s=r(e)>.5;let n=t;for(let r=1;r<=10;r++)if(n=s?c(t,.1*r):h(t,.1*r),o(n,e)>=i)return n;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 u(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 n=d(i.inputs_style,i.input_border_radius);n&&(e["--ah-input-radius"]=n),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&&o(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",n=i.input_background||s,r=e["--ah-color-border"]||i.input_border||"#c9cace",c=o(r,s),h=o(r,n);Math.min(c,h)<3&&(e["--ah-color-border"]=l(r,c<h?s:n,3))}if(t.fonts){const i=t.fonts,s=i.reference_text_size||16,n=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"]=`${n(i.title.size)}px`),i.subtitle?.size&&(e["--ah-font-size-subtitle"]=`${n(i.subtitle.size)}px`),i.body_text?.size&&(e["--ah-font-size-body"]=`${n(i.body_text.size)}px`),i.input_labels?.size&&(e["--ah-font-size-label"]=`${n(i.input_labels.size)}px`),i.buttons_text?.size&&(e["--ah-font-size-btn"]=`${n(i.buttons_text.size)}px`),i.links?.size&&(e["--ah-font-size-link"]=`${n(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 p={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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");for(const[t,i]of Object.entries(p)){if("br"===t){e=e.replace(/<br\s*\/?>/gi,"<br>");continue}const s=new RegExp(`<${t}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`,"gi");e=e.replace(s,((e,s)=>{const n=[];if(s){const t=s.replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),e=/([a-z-]+)=["']([^"']*)["']/gi;let r;for(;null!==(r=e.exec(t));){const[,t,e]=r;t&&i.includes(t.toLowerCase())&&("href"===t.toLowerCase()?m(e||"")&&n.push(`${t}="${x(e||"")}"`):n.push(`${t}="${x(e||"")}"`))}}"a"===t&&(n.push('target="_blank"'),n.push('rel="noopener noreferrer"'));const r=n.length?" "+n.join(" "):"";return`<${t}${r}>`}));const n=new RegExp(`</${t}>`,"gi");e=e.replace(n,`</${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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}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),...u(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 n=new URL(s,this.baseUrl||window.location.origin);this.state&&n.searchParams.set("state",this.state),e&&n.searchParams.set("nodeId",e),this.loading=!0;try{const t=await fetch(this.buildUrl(n.pathname+n.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;let i={...this.formData,...e||{}};const s=this.el.shadowRoot?.querySelector("form");if(s&&s.querySelectorAll('input[type="hidden"]').forEach((t=>{t.name&&t.value&&(i[t.name]=t.value)})),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()),e.ceremony&&this.performWebAuthnCeremony(e.ceremony),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)};overrideFormSubmit(){const t=this.el.shadowRoot;if(!t)return;const e=t.querySelector("form");e&&(e.submit=()=>{const t=new FormData(e),i={};t.forEach(((t,e)=>{"string"==typeof t&&(i[e]=t)})),this.handleSubmit({preventDefault:()=>{}},i)})}performWebAuthnCeremony(t){this.isValidWebAuthnCeremony(t)?requestAnimationFrame((()=>{this.overrideFormSubmit(),this.executeWebAuthnRegistration(t)})):console.error("Invalid WebAuthn ceremony payload",t)}isValidWebAuthnCeremony(t){if("object"!=typeof t||null===t)return!1;if("webauthn-registration"!==t.type)return!1;if("string"!=typeof t.successAction)return!1;const e=t.options;if("object"!=typeof e||null===e)return!1;if("string"!=typeof e.challenge)return!1;const i=e.rp;if("object"!=typeof i||null===i)return!1;if("string"!=typeof i.id||"string"!=typeof i.name)return!1;const s=e.user;return"object"==typeof s&&null!==s&&("string"==typeof s.id&&"string"==typeof s.name&&"string"==typeof s.displayName&&!!Array.isArray(e.pubKeyCredParams))}async executeWebAuthnRegistration(t){const e=t.options,i=t=>{for(t=t.replace(/-/g,"+").replace(/_/g,"/");t.length%4;)t+="=";const e=atob(t),i=new Uint8Array(e.length);for(let t=0;t<e.length;t++)i[t]=e.charCodeAt(t);return i.buffer},s=t=>{const e=new Uint8Array(t);let i="";for(let t=0;t<e.length;t++)i+=String.fromCharCode(e[t]);return btoa(i).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")},n=()=>{const t=this.el?.shadowRoot;if(t){const e=t.querySelector("form");if(e)return e}return document.querySelector("form")};try{const r={challenge:i(e.challenge),rp:{id:e.rp.id,name:e.rp.name},user:{id:i(e.user.id),name:e.user.name,displayName:e.user.displayName},pubKeyCredParams:e.pubKeyCredParams.map((t=>({alg:t.alg,type:t.type}))),timeout:e.timeout,attestation:e.attestation||"none",authenticatorSelection:e.authenticatorSelection?{residentKey:e.authenticatorSelection.residentKey||"preferred",userVerification:e.authenticatorSelection.userVerification||"preferred"}:void 0};e.excludeCredentials?.length&&(r.excludeCredentials=e.excludeCredentials.map((t=>({id:i(t.id),type:t.type,transports:t.transports||[]}))));const o=await navigator.credentials.create({publicKey:r}),a=o.response,c={id:o.id,rawId:s(o.rawId),type:o.type,response:{attestationObject:s(a.attestationObject),clientDataJSON:s(a.clientDataJSON)},clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:o.authenticatorAttachment||void 0};"function"==typeof a.getTransports&&(c.response.transports=a.getTransports());const h=n();if(h){const e=h.querySelector('[name="credential-field"]')||h.querySelector("#credential-field"),i=h.querySelector('[name="action-field"]')||h.querySelector("#action-field");e&&(e.value=JSON.stringify(c)),i&&(i.value=t.successAction),h.submit()}}catch(t){console.error("WebAuthn registration error:",t);const e=n();if(e){const t=e.querySelector('[name="action-field"]')||e.querySelector("#action-field");t&&(t.value="error"),e.submit()}}}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))||[],n=[...t.components??[]],r=n.filter((t=>!1!==t.visible)).sort(((t,e)=>(t.order??0)-(e.order??0))),o=n.filter((t=>!1===t.visible)),a=r.filter((t=>this.isSocialComponent(t))),c=r.filter((t=>!this.isSocialComponent(t)&&!this.isDividerComponent(t))),h=r.find((t=>this.isDividerComponent(t))),l=!!h,d=h?.config?.text||"Or",f=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(", ")},u=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"},u&&s("div",{class:"logo-wrapper",part:"logo-wrapper"},s("img",{class:"logo",part:"logo",src:u,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"},o.map((t=>s("input",{type:"hidden",name:t.id,id:t.id,key:t.id,value:this.formData[t.id]||""}))),s("div",{class:"form-content"},a.length>0&&s("div",{class:"social-section",part:"social-section"},a.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:f(t)})))),a.length>0&&c.length>0&&l&&s("div",{class:"divider",part:"divider"},s("span",{class:"divider-text"},d)),s("div",{class:"fields-section",part:"fields-section"},c.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}
|