Add a button to sign in with a passkey

This commit is contained in:
Stanko K.R.
2026-03-26 15:59:09 +01:00
parent 09d5b8c92f
commit 9614c1ecfb
2 changed files with 37 additions and 14 deletions
+25 -4
View File
@@ -53,6 +53,7 @@ class PasskeyButton extends HTMLElement {
// Arrow function to preserve `this` binding for addEventListener/removeEventListener.
#perform = async () => {
await this.abortConditionalMediation?.()
this.button.disabled = true
this.#hideErrors()
this.button.dispatchEvent(new CustomEvent("passkey:start", { bubbles: true }))
@@ -105,6 +106,9 @@ class PasskeyRegistrationButton extends PasskeyButton {
}
class PasskeySignInButton extends PasskeyButton {
#conditionalMediationController = null
#conditionalMediationPromise = null
get purpose() { return "authentication" }
connectedCallback() {
@@ -116,32 +120,49 @@ class PasskeySignInButton extends PasskeyButton {
return this.getAttribute("mediation")
}
async perform(options, { mediation } = {}) {
return await authenticate(options, { mediation })
async perform(options, { signal, mediation } = {}) {
return await authenticate(options, { signal, mediation })
}
fillForm(passkey) {
fillSignInForm(this.form, passkey)
}
async abortConditionalMediation() {
if (this.#conditionalMediationController) {
this.#conditionalMediationController.abort()
await this.#conditionalMediationPromise
}
}
async #attemptConditionalMediation() {
if (await this.#conditionalMediationAvailable()) {
const options = this.options
this.form.dispatchEvent(new CustomEvent("passkey:start", { bubbles: true }))
this.#conditionalMediationController = new AbortController()
this.#conditionalMediationPromise = this.#runConditionalMediation(options)
}
}
async #runConditionalMediation(options) {
try {
await refreshChallenge(options, this.challengeUrl, this.purpose)
const passkey = await this.perform(options, { mediation: this.mediation })
const passkey = await this.perform(options, { signal: this.#conditionalMediationController.signal, mediation: this.mediation })
this.form.dispatchEvent(new CustomEvent("passkey:success", { bubbles: true }))
this.fillForm(passkey)
this.form.submit()
} catch (error) {
if (error.name === "AbortError") return
console.error("Passkey conditional mediation failed", error)
const type = errorType(error)
this.button.dispatchEvent(new CustomEvent("passkey:error", { bubbles: true, detail: { error, type } }))
}
} finally {
this.#conditionalMediationController = null
this.#conditionalMediationPromise = null
}
}
+3 -1
View File
@@ -22,7 +22,9 @@
</button>
<% end %>
<%= passkey_sign_in_button "Sign in with a passkey", session_passkey_path, options: @authentication_options, mediation: "conditional", class: "btn btn--link center txt-medium", hidden: true %>
<p>
<%= passkey_sign_in_button "Sign in with a passkey", session_passkey_path, options: @authentication_options, mediation: "conditional", class: "btn btn--plain txt-link center txt-small" %>
</p>
</div>