@nopwdio/sdk-js
Version:
Nopwd JS SDK
267 lines • 10.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
// Import necessary modules and components from lit and local files
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { core } from "../internal/styles/core.styles.js";
import { component } from "../internal/styles/semantic.styles.js";
import styles from "./np-login.styles.js";
import { arrowRightCircle, arrowRightCircleSolid, busy, checkCircle, envelope, exclamationCircle, } from "../internal/styles/icons.styles.js";
import { handleCallbackCode, hasCallbackCode, request } from "../core/email.js";
import { getChallenge, isWebauthnSupported, signChallenge, verifySignature, } from "../core/webauthn.js";
import { create, get } from "../core/session.js";
import { wait } from "../internal/util/wait.js";
import { NetworkError } from "../core/errors.js";
// Define the different states the component can be in
export var State;
(function (State) {
State["EMAIL_SENDING"] = "email:link:sending";
State["EMAIL_SENT"] = "email:link:sent";
State["EMAIL_VERIFYING"] = "email:link:verifying";
State["AUTHENTICATED"] = "authenticated";
State["PASSKEYS_VERIFYING"] = "passkeys:verifying";
State["ERROR"] = "error";
})(State || (State = {}));
/**
* @summary `np-login` is a custom element for user authentication via email link (magic-link) or Passkeys.
*
* @description
* This component manages the authentication process using WebAuthn passkeys or email link authentication.
* It handles sending email links, processing callback codes, and verifying passkey challenges.
* The component emits events for successful login and errors, allowing other parts of the application to
* respond accordingly. It also provides visual feedback for different states of the authentication process.
*
* @event np:login - Emitted when the session is successfully created.
* @event np:error - Emitted when an error occurs during the authentication process.
*
* @csspart button - The submit button wrapper.
* @csspart input - The email input wrapper.
*/
let NpLogin = class NpLogin extends LitElement {
constructor() {
super(...arguments);
this.placeholder = "Your email";
this.id = "input";
this.value = "";
}
// Lifecycle method called when the element is added to the document
connectedCallback() {
const _super = Object.create(null, {
connectedCallback: { get: () => super.connectedCallback }
});
return __awaiter(this, void 0, void 0, function* () {
_super.connectedCallback.call(this);
this.startConditionalUI();
this.handleCallbackIfNeeded();
});
}
disconnectedCallback() {
const _super = Object.create(null, {
disconnectedCallback: { get: () => super.disconnectedCallback }
});
return __awaiter(this, void 0, void 0, function* () {
var _a;
_super.disconnectedCallback.call(this);
(_a = this.abortConditianal) === null || _a === void 0 ? void 0 : _a.abort();
this.abortConditianal = undefined;
});
}
// Method to handle login with email
loginWithEmail() {
return __awaiter(this, void 0, void 0, function* () {
if ((this.state !== undefined && this.state !== State.EMAIL_SENT) || this.value.length < 3) {
return;
}
try {
this.state = State.EMAIL_SENDING;
const resp = yield request({ email: this.value });
this.state = State.EMAIL_SENT;
return resp;
}
catch (e) {
this.signalError(e);
}
});
}
// Method to handle callback if needed
handleCallbackIfNeeded() {
return __awaiter(this, void 0, void 0, function* () {
if (!hasCallbackCode()) {
return false;
}
try {
this.state = State.EMAIL_VERIFYING;
const token = yield handleCallbackCode();
const session = yield create(token, this.lifetime, this.idletimeout);
yield this.signalSuccess(session);
return true;
}
catch (e) {
yield this.signalError(e);
return false;
}
});
}
// Method to start conditional UI based on WebAuthn support
startConditionalUI() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
this.passkeys = undefined;
this.passkeys = yield isWebauthnSupported();
(_a = this.abortConditianal) === null || _a === void 0 ? void 0 : _a.abort();
this.abortConditianal = new AbortController();
for (let i = 0; i <= 20; i++) {
try {
const { challenge } = yield getChallenge();
this.passkeys = true;
const auth = yield signChallenge(challenge, this.abortConditianal.signal);
this.state = State.PASSKEYS_VERIFYING;
const token = yield verifySignature(auth);
const session = yield create(token, this.lifetime, this.idletimeout);
yield this.signalSuccess(session);
}
catch (e) {
console.log(e);
if (e instanceof NetworkError) {
this.state = undefined;
yield wait((i + 1) * 1000);
}
else {
yield this.signalError(e);
}
}
}
return false;
});
}
// Method to get the current session
getSession() {
return __awaiter(this, void 0, void 0, function* () {
return get();
});
}
// Method to signal successful authentication
signalSuccess(session) {
return __awaiter(this, void 0, void 0, function* () {
this.state = State.AUTHENTICATED;
this.dispatchEvent(new CustomEvent("np:login", {
composed: true,
cancelable: true,
bubbles: true,
detail: session,
}));
yield wait(3000);
this.state = undefined;
});
}
// Method to signal an error
signalError(e) {
return __awaiter(this, void 0, void 0, function* () {
this.state = State.ERROR;
this.dispatchEvent(new CustomEvent("np:error", {
composed: true,
cancelable: true,
bubbles: true,
detail: e,
}));
yield wait(1000);
this.state = undefined;
});
}
// Method to handle input events
onInput() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const input = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector("input");
if (!input) {
return;
}
const oldValue = this.value;
this.value = input.value;
if (this.state === State.EMAIL_SENT) {
this.state = undefined;
}
// capturing copy/past or password manager
if (oldValue.length === 0 && this.value.length >= 3) {
this.loginWithEmail();
}
});
}
// Method to handle key up events
onKeyUp(e) {
if (e.key === "Enter") {
this.loginWithEmail();
}
}
// Render the component's HTML template
render() {
return html `
<input
type="email"
=${this.onInput}
=${this.onKeyUp}
value=${this.value}
placeholder=${this.placeholder}
id="${this.id}"
autocomplete="username webauthn"
part="input"
/>
<button =${this.loginWithEmail} part="button">
${this.state === State.EMAIL_SENDING
? html `${busy}`
: this.state === State.EMAIL_SENT
? html `${envelope}`
: this.state === State.EMAIL_VERIFYING
? html `${busy}`
: this.state === State.PASSKEYS_VERIFYING
? html `${busy}`
: this.state === State.AUTHENTICATED
? html `${checkCircle}`
: this.state === State.ERROR
? html `${exclamationCircle}`
: html `${this.passkeys ? arrowRightCircleSolid : arrowRightCircle}`}
</button>
`;
}
};
// Apply styles to the component
NpLogin.styles = [core, component, styles];
__decorate([
property({ reflect: true })
], NpLogin.prototype, "state", void 0);
__decorate([
property({ reflect: true, type: Boolean })
], NpLogin.prototype, "passkeys", void 0);
__decorate([
property({ type: String })
], NpLogin.prototype, "placeholder", void 0);
__decorate([
property({ type: String })
], NpLogin.prototype, "id", void 0);
__decorate([
property({ type: Number })
], NpLogin.prototype, "lifetime", void 0);
__decorate([
property({ type: Number })
], NpLogin.prototype, "idletimeout", void 0);
__decorate([
property({ type: String })
], NpLogin.prototype, "value", void 0);
NpLogin = __decorate([
customElement("np-login")
], NpLogin);
export { NpLogin };
//# sourceMappingURL=np-login.js.map