@authsignal/browser 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/passkey-api-client.d.ts +3 -2
- package/dist/api/types.d.ts +6 -1
- package/dist/handlers/popup-handler.d.ts +5 -5
- package/dist/index.js +479 -398
- package/dist/index.min.js +1 -1
- package/dist/passkey.d.ts +6 -1
- package/dist/types.d.ts +4 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddAuthenticatorRequest, AddAuthenticatorResponse, AuthenticationOptsRequest, AuthenticationOptsResponse, RegistrationOptsRequest, RegistrationOptsResponse, VerifyRequest, VerifyResponse } from "./types";
|
|
1
|
+
import { AddAuthenticatorRequest, AddAuthenticatorResponse, AuthenticationOptsRequest, AuthenticationOptsResponse, PasskeyAuthenticatorResponse, RegistrationOptsRequest, RegistrationOptsResponse, VerifyRequest, VerifyResponse } from "./types";
|
|
2
2
|
declare type PasskeyApiClientOptions = {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
tenantId: string;
|
|
@@ -7,10 +7,11 @@ export declare class PasskeyApiClient {
|
|
|
7
7
|
tenantId: string;
|
|
8
8
|
baseUrl: string;
|
|
9
9
|
constructor({ baseUrl, tenantId }: PasskeyApiClientOptions);
|
|
10
|
-
registrationOptions({ token, userName }: RegistrationOptsRequest): Promise<RegistrationOptsResponse>;
|
|
10
|
+
registrationOptions({ token, userName, authenticatorAttachment, }: RegistrationOptsRequest): Promise<RegistrationOptsResponse>;
|
|
11
11
|
authenticationOptions({ token }: AuthenticationOptsRequest): Promise<AuthenticationOptsResponse>;
|
|
12
12
|
addAuthenticator({ token, ...rest }: AddAuthenticatorRequest): Promise<AddAuthenticatorResponse>;
|
|
13
13
|
verify({ token, ...rest }: VerifyRequest): Promise<VerifyResponse>;
|
|
14
|
+
getPasskeyAuthenticator(credentialId: string): Promise<PasskeyAuthenticatorResponse>;
|
|
14
15
|
private buildHeaders;
|
|
15
16
|
}
|
|
16
17
|
export {};
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, RegistrationResponseJSON } from "@simplewebauthn/types";
|
|
1
|
+
import { AuthenticationResponseJSON, AuthenticatorAttachment, PublicKeyCredentialCreationOptionsJSON, RegistrationResponseJSON } from "@simplewebauthn/types";
|
|
2
2
|
export declare type RegistrationOptsRequest = {
|
|
3
3
|
userName?: string;
|
|
4
4
|
token: string;
|
|
5
|
+
authenticatorAttachment?: AuthenticatorAttachment | null;
|
|
5
6
|
};
|
|
6
7
|
export declare type RegistrationOptsResponse = {
|
|
7
8
|
challengeId: string;
|
|
@@ -33,3 +34,7 @@ export declare type VerifyResponse = {
|
|
|
33
34
|
isVerified: boolean;
|
|
34
35
|
accessToken?: string;
|
|
35
36
|
};
|
|
37
|
+
export declare type PasskeyAuthenticatorResponse = {
|
|
38
|
+
credentialId: string;
|
|
39
|
+
verifiedAt: string;
|
|
40
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import { A11yDialogEvent } from "a11y-dialog";
|
|
1
2
|
declare type PopupShowInput = {
|
|
2
3
|
url: string;
|
|
3
4
|
};
|
|
4
|
-
declare type EventType = "show" | "hide" | "destroy" | "create";
|
|
5
|
-
declare type EventHandler = (node: Element, event?: Event) => void;
|
|
6
5
|
declare type PopupHandlerOptions = {
|
|
7
6
|
width?: string;
|
|
7
|
+
isClosable?: boolean;
|
|
8
8
|
};
|
|
9
9
|
export declare class PopupHandler {
|
|
10
10
|
private popup;
|
|
11
|
-
constructor({ width }: PopupHandlerOptions);
|
|
12
|
-
create({ width }: PopupHandlerOptions): void;
|
|
11
|
+
constructor({ width, isClosable }: PopupHandlerOptions);
|
|
12
|
+
create({ width, isClosable }: PopupHandlerOptions): void;
|
|
13
13
|
destroy(): void;
|
|
14
14
|
show({ url }: PopupShowInput): void;
|
|
15
15
|
close(): void;
|
|
16
|
-
on(event:
|
|
16
|
+
on(event: A11yDialogEvent, listener: EventListener): void;
|
|
17
17
|
}
|
|
18
18
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -543,18 +543,21 @@ var PasskeyApiClient = /** @class */ (function () {
|
|
|
543
543
|
this.baseUrl = baseUrl;
|
|
544
544
|
}
|
|
545
545
|
PasskeyApiClient.prototype.registrationOptions = function (_a) {
|
|
546
|
-
var token = _a.token, userName = _a.userName;
|
|
546
|
+
var token = _a.token, userName = _a.userName, authenticatorAttachment = _a.authenticatorAttachment;
|
|
547
547
|
return __awaiter(this, void 0, void 0, function () {
|
|
548
|
-
var
|
|
548
|
+
var body, response;
|
|
549
549
|
return __generator(this, function (_b) {
|
|
550
550
|
switch (_b.label) {
|
|
551
551
|
case 0:
|
|
552
|
-
|
|
552
|
+
body = Boolean(authenticatorAttachment)
|
|
553
|
+
? { username: userName, authenticatorAttachment: authenticatorAttachment }
|
|
554
|
+
: { username: userName };
|
|
555
|
+
response = fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey/registration-options"), {
|
|
553
556
|
method: "POST",
|
|
554
557
|
headers: this.buildHeaders(token),
|
|
555
|
-
body: JSON.stringify(
|
|
558
|
+
body: JSON.stringify(body)
|
|
556
559
|
});
|
|
557
|
-
return [4 /*yield*/,
|
|
560
|
+
return [4 /*yield*/, response];
|
|
558
561
|
case 1: return [2 /*return*/, (_b.sent()).json()];
|
|
559
562
|
}
|
|
560
563
|
});
|
|
@@ -563,16 +566,16 @@ var PasskeyApiClient = /** @class */ (function () {
|
|
|
563
566
|
PasskeyApiClient.prototype.authenticationOptions = function (_a) {
|
|
564
567
|
var token = _a.token;
|
|
565
568
|
return __awaiter(this, void 0, void 0, function () {
|
|
566
|
-
var
|
|
569
|
+
var response;
|
|
567
570
|
return __generator(this, function (_b) {
|
|
568
571
|
switch (_b.label) {
|
|
569
572
|
case 0:
|
|
570
|
-
|
|
573
|
+
response = fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey/authentication-options"), {
|
|
571
574
|
method: "POST",
|
|
572
575
|
headers: this.buildHeaders(token),
|
|
573
576
|
body: JSON.stringify({})
|
|
574
577
|
});
|
|
575
|
-
return [4 /*yield*/,
|
|
578
|
+
return [4 /*yield*/, response];
|
|
576
579
|
case 1: return [2 /*return*/, (_b.sent()).json()];
|
|
577
580
|
}
|
|
578
581
|
});
|
|
@@ -581,16 +584,16 @@ var PasskeyApiClient = /** @class */ (function () {
|
|
|
581
584
|
PasskeyApiClient.prototype.addAuthenticator = function (_a) {
|
|
582
585
|
var token = _a.token, rest = __rest(_a, ["token"]);
|
|
583
586
|
return __awaiter(this, void 0, void 0, function () {
|
|
584
|
-
var
|
|
587
|
+
var response;
|
|
585
588
|
return __generator(this, function (_b) {
|
|
586
589
|
switch (_b.label) {
|
|
587
590
|
case 0:
|
|
588
|
-
|
|
591
|
+
response = fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey"), {
|
|
589
592
|
method: "POST",
|
|
590
593
|
headers: this.buildHeaders(token),
|
|
591
594
|
body: JSON.stringify(rest)
|
|
592
595
|
});
|
|
593
|
-
return [4 /*yield*/,
|
|
596
|
+
return [4 /*yield*/, response];
|
|
594
597
|
case 1: return [2 /*return*/, (_b.sent()).json()];
|
|
595
598
|
}
|
|
596
599
|
});
|
|
@@ -599,21 +602,40 @@ var PasskeyApiClient = /** @class */ (function () {
|
|
|
599
602
|
PasskeyApiClient.prototype.verify = function (_a) {
|
|
600
603
|
var token = _a.token, rest = __rest(_a, ["token"]);
|
|
601
604
|
return __awaiter(this, void 0, void 0, function () {
|
|
602
|
-
var
|
|
605
|
+
var response;
|
|
603
606
|
return __generator(this, function (_b) {
|
|
604
607
|
switch (_b.label) {
|
|
605
608
|
case 0:
|
|
606
|
-
|
|
609
|
+
response = fetch("".concat(this.baseUrl, "/client/verify/passkey"), {
|
|
607
610
|
method: "POST",
|
|
608
611
|
headers: this.buildHeaders(token),
|
|
609
612
|
body: JSON.stringify(rest)
|
|
610
613
|
});
|
|
611
|
-
return [4 /*yield*/,
|
|
614
|
+
return [4 /*yield*/, response];
|
|
612
615
|
case 1: return [2 /*return*/, (_b.sent()).json()];
|
|
613
616
|
}
|
|
614
617
|
});
|
|
615
618
|
});
|
|
616
619
|
};
|
|
620
|
+
PasskeyApiClient.prototype.getPasskeyAuthenticator = function (credentialId) {
|
|
621
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
622
|
+
var response;
|
|
623
|
+
return __generator(this, function (_a) {
|
|
624
|
+
switch (_a.label) {
|
|
625
|
+
case 0: return [4 /*yield*/, fetch("".concat(this.baseUrl, "/client/user-authenticators/passkey?credentialId=").concat(credentialId), {
|
|
626
|
+
method: "GET",
|
|
627
|
+
headers: this.buildHeaders()
|
|
628
|
+
})];
|
|
629
|
+
case 1:
|
|
630
|
+
response = _a.sent();
|
|
631
|
+
if (!response.ok) {
|
|
632
|
+
throw new Error(response.statusText);
|
|
633
|
+
}
|
|
634
|
+
return [2 /*return*/, response.json()];
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
};
|
|
617
639
|
PasskeyApiClient.prototype.buildHeaders = function (token) {
|
|
618
640
|
var authorizationHeader = token ? "Bearer ".concat(token) : "Basic ".concat(window.btoa(encodeURIComponent(this.tenantId)));
|
|
619
641
|
return {
|
|
@@ -627,27 +649,31 @@ var PasskeyApiClient = /** @class */ (function () {
|
|
|
627
649
|
var Passkey = /** @class */ (function () {
|
|
628
650
|
function Passkey(_a) {
|
|
629
651
|
var baseUrl = _a.baseUrl, tenantId = _a.tenantId;
|
|
652
|
+
this.passkeyLocalStorageKey = "as_passkey_credential_id";
|
|
630
653
|
this.api = new PasskeyApiClient({ baseUrl: baseUrl, tenantId: tenantId });
|
|
631
654
|
}
|
|
632
655
|
Passkey.prototype.signUp = function (_a) {
|
|
633
|
-
var userName = _a.userName, token = _a.token;
|
|
656
|
+
var userName = _a.userName, token = _a.token, _b = _a.authenticatorAttachment, authenticatorAttachment = _b === void 0 ? "platform" : _b;
|
|
634
657
|
return __awaiter(this, void 0, void 0, function () {
|
|
635
658
|
var optionsResponse, registrationResponse, addAuthenticatorResponse;
|
|
636
|
-
return __generator(this, function (
|
|
637
|
-
switch (
|
|
638
|
-
case 0: return [4 /*yield*/, this.api.registrationOptions({ userName: userName, token: token })];
|
|
659
|
+
return __generator(this, function (_c) {
|
|
660
|
+
switch (_c.label) {
|
|
661
|
+
case 0: return [4 /*yield*/, this.api.registrationOptions({ userName: userName, token: token, authenticatorAttachment: authenticatorAttachment })];
|
|
639
662
|
case 1:
|
|
640
|
-
optionsResponse =
|
|
663
|
+
optionsResponse = _c.sent();
|
|
641
664
|
return [4 /*yield*/, startRegistration(optionsResponse.options)];
|
|
642
665
|
case 2:
|
|
643
|
-
registrationResponse =
|
|
666
|
+
registrationResponse = _c.sent();
|
|
644
667
|
return [4 /*yield*/, this.api.addAuthenticator({
|
|
645
668
|
challengeId: optionsResponse.challengeId,
|
|
646
669
|
registrationCredential: registrationResponse,
|
|
647
670
|
token: token
|
|
648
671
|
})];
|
|
649
672
|
case 3:
|
|
650
|
-
addAuthenticatorResponse =
|
|
673
|
+
addAuthenticatorResponse = _c.sent();
|
|
674
|
+
if (addAuthenticatorResponse === null || addAuthenticatorResponse === void 0 ? void 0 : addAuthenticatorResponse.isVerified) {
|
|
675
|
+
this.storeCredentialAgainstDevice(registrationResponse);
|
|
676
|
+
}
|
|
651
677
|
return [2 /*return*/, addAuthenticatorResponse === null || addAuthenticatorResponse === void 0 ? void 0 : addAuthenticatorResponse.accessToken];
|
|
652
678
|
}
|
|
653
679
|
});
|
|
@@ -675,11 +701,46 @@ var Passkey = /** @class */ (function () {
|
|
|
675
701
|
})];
|
|
676
702
|
case 3:
|
|
677
703
|
verifyResponse = _a.sent();
|
|
704
|
+
if (verifyResponse === null || verifyResponse === void 0 ? void 0 : verifyResponse.isVerified) {
|
|
705
|
+
this.storeCredentialAgainstDevice(authenticationResponse);
|
|
706
|
+
}
|
|
678
707
|
return [2 /*return*/, verifyResponse === null || verifyResponse === void 0 ? void 0 : verifyResponse.accessToken];
|
|
679
708
|
}
|
|
680
709
|
});
|
|
681
710
|
});
|
|
682
711
|
};
|
|
712
|
+
Passkey.prototype.isAvailableOnDevice = function () {
|
|
713
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
714
|
+
var credentialId;
|
|
715
|
+
return __generator(this, function (_b) {
|
|
716
|
+
switch (_b.label) {
|
|
717
|
+
case 0:
|
|
718
|
+
credentialId = localStorage.getItem(this.passkeyLocalStorageKey);
|
|
719
|
+
if (!credentialId) {
|
|
720
|
+
return [2 /*return*/, false];
|
|
721
|
+
}
|
|
722
|
+
_b.label = 1;
|
|
723
|
+
case 1:
|
|
724
|
+
_b.trys.push([1, 3, , 4]);
|
|
725
|
+
return [4 /*yield*/, this.api.getPasskeyAuthenticator(credentialId)];
|
|
726
|
+
case 2:
|
|
727
|
+
_b.sent();
|
|
728
|
+
return [2 /*return*/, true];
|
|
729
|
+
case 3:
|
|
730
|
+
_b.sent();
|
|
731
|
+
return [2 /*return*/, false];
|
|
732
|
+
case 4: return [2 /*return*/];
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
};
|
|
737
|
+
Passkey.prototype.storeCredentialAgainstDevice = function (_a) {
|
|
738
|
+
var id = _a.id, authenticatorAttachment = _a.authenticatorAttachment;
|
|
739
|
+
if (authenticatorAttachment === "cross-platform") {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
localStorage.setItem(this.passkeyLocalStorageKey, id);
|
|
743
|
+
};
|
|
683
744
|
return Passkey;
|
|
684
745
|
}());
|
|
685
746
|
|
|
@@ -716,408 +777,423 @@ function openWindow(_a) {
|
|
|
716
777
|
return window.open(url, "", "toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=".concat(width, ", height=").concat(height, ", top=").concat(y, ", left=").concat(x));
|
|
717
778
|
}
|
|
718
779
|
|
|
780
|
+
const not = {
|
|
781
|
+
inert: ':not([inert]):not([inert] *)',
|
|
782
|
+
negTabIndex: ':not([tabindex^="-"])',
|
|
783
|
+
disabled: ':not(:disabled)',
|
|
784
|
+
};
|
|
785
|
+
|
|
719
786
|
var focusableSelectors = [
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
787
|
+
`a[href]${not.inert}${not.negTabIndex}`,
|
|
788
|
+
`area[href]${not.inert}${not.negTabIndex}`,
|
|
789
|
+
`input:not([type="hidden"]):not([type="radio"])${not.inert}${not.negTabIndex}${not.disabled}`,
|
|
790
|
+
`input[type="radio"]${not.inert}${not.negTabIndex}${not.disabled}`,
|
|
791
|
+
`select${not.inert}${not.negTabIndex}${not.disabled}`,
|
|
792
|
+
`textarea${not.inert}${not.negTabIndex}${not.disabled}`,
|
|
793
|
+
`button${not.inert}${not.negTabIndex}${not.disabled}`,
|
|
794
|
+
`details${not.inert} > summary:first-of-type${not.negTabIndex}`,
|
|
795
|
+
// Discard until Firefox supports `:has()`
|
|
796
|
+
// See: https://github.com/KittyGiraudel/focusable-selectors/issues/12
|
|
797
|
+
// `details:not(:has(> summary))${not.inert}${not.negTabIndex}`,
|
|
798
|
+
`iframe${not.inert}${not.negTabIndex}`,
|
|
799
|
+
`audio[controls]${not.inert}${not.negTabIndex}`,
|
|
800
|
+
`video[controls]${not.inert}${not.negTabIndex}`,
|
|
801
|
+
`[contenteditable]${not.inert}${not.negTabIndex}`,
|
|
802
|
+
`[tabindex]${not.inert}${not.negTabIndex}`,
|
|
732
803
|
];
|
|
733
804
|
|
|
734
|
-
var TAB_KEY = 'Tab';
|
|
735
|
-
var ESCAPE_KEY = 'Escape';
|
|
736
|
-
|
|
737
805
|
/**
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
* @constructor
|
|
741
|
-
* @param {Element} element
|
|
806
|
+
* Set the focus to the first element with `autofocus` with the element or the
|
|
807
|
+
* element itself.
|
|
742
808
|
*/
|
|
743
|
-
function
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
this._show = this.show.bind(this);
|
|
747
|
-
this._hide = this.hide.bind(this);
|
|
748
|
-
this._maintainFocus = this._maintainFocus.bind(this);
|
|
749
|
-
this._bindKeypress = this._bindKeypress.bind(this);
|
|
750
|
-
|
|
751
|
-
this.$el = element;
|
|
752
|
-
this.shown = false;
|
|
753
|
-
this._id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
|
|
754
|
-
this._previouslyFocused = null;
|
|
755
|
-
this._listeners = {};
|
|
756
|
-
|
|
757
|
-
// Initialise everything needed for the dialog to work properly
|
|
758
|
-
this.create();
|
|
809
|
+
function moveFocusToDialog(el) {
|
|
810
|
+
const focused = (el.querySelector('[autofocus]') || el);
|
|
811
|
+
focused.focus();
|
|
759
812
|
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Set up everything necessary for the dialog to be functioning
|
|
763
|
-
*
|
|
764
|
-
* @param {(NodeList | Element | string)} targets
|
|
765
|
-
* @return {this}
|
|
766
|
-
*/
|
|
767
|
-
A11yDialog.prototype.create = function () {
|
|
768
|
-
this.$el.setAttribute('aria-hidden', true);
|
|
769
|
-
this.$el.setAttribute('aria-modal', true);
|
|
770
|
-
this.$el.setAttribute('tabindex', -1);
|
|
771
|
-
|
|
772
|
-
if (!this.$el.hasAttribute('role')) {
|
|
773
|
-
this.$el.setAttribute('role', 'dialog');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Keep a collection of dialog openers, each of which will be bound a click
|
|
777
|
-
// event listener to open the dialog
|
|
778
|
-
this._openers = $$('[data-a11y-dialog-show="' + this._id + '"]');
|
|
779
|
-
this._openers.forEach(
|
|
780
|
-
function (opener) {
|
|
781
|
-
opener.addEventListener('click', this._show);
|
|
782
|
-
}.bind(this)
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
// Keep a collection of dialog closers, each of which will be bound a click
|
|
786
|
-
// event listener to close the dialog
|
|
787
|
-
const $el = this.$el;
|
|
788
|
-
|
|
789
|
-
this._closers = $$('[data-a11y-dialog-hide]', this.$el)
|
|
790
|
-
// This filter is necessary in case there are nested dialogs, so that
|
|
791
|
-
// only closers from the current dialog are retrieved and effective
|
|
792
|
-
.filter(function (closer) {
|
|
793
|
-
// Testing for `[aria-modal="true"]` is not enough since this attribute
|
|
794
|
-
// and the collect of closers is done at instantation time, when nested
|
|
795
|
-
// dialogs might not have yet been instantiated. Note that if the dialogs
|
|
796
|
-
// are manually instantiated, this could still fail because none of these
|
|
797
|
-
// selectors would match; this would cause closers to close all parent
|
|
798
|
-
// dialogs instead of just the current one
|
|
799
|
-
return closer.closest('[aria-modal="true"], [data-a11y-dialog]') === $el
|
|
800
|
-
})
|
|
801
|
-
.concat($$('[data-a11y-dialog-hide="' + this._id + '"]'));
|
|
802
|
-
|
|
803
|
-
this._closers.forEach(
|
|
804
|
-
function (closer) {
|
|
805
|
-
closer.addEventListener('click', this._hide);
|
|
806
|
-
}.bind(this)
|
|
807
|
-
);
|
|
808
|
-
|
|
809
|
-
// Execute all callbacks registered for the `create` event
|
|
810
|
-
this._fire('create');
|
|
811
|
-
|
|
812
|
-
return this
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Show the dialog element, disable all the targets (siblings), trap the
|
|
817
|
-
* current focus within it, listen for some specific key presses and fire all
|
|
818
|
-
* registered callbacks for `show` event
|
|
819
|
-
*
|
|
820
|
-
* @param {CustomEvent} event
|
|
821
|
-
* @return {this}
|
|
822
|
-
*/
|
|
823
|
-
A11yDialog.prototype.show = function (event) {
|
|
824
|
-
// If the dialog is already open, abort
|
|
825
|
-
if (this.shown) {
|
|
826
|
-
return this
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Keep a reference to the currently focused element to be able to restore
|
|
830
|
-
// it later
|
|
831
|
-
this._previouslyFocused = document.activeElement;
|
|
832
|
-
this.$el.removeAttribute('aria-hidden');
|
|
833
|
-
this.shown = true;
|
|
834
|
-
|
|
835
|
-
// Set the focus to the dialog element
|
|
836
|
-
moveFocusToDialog(this.$el);
|
|
837
|
-
|
|
838
|
-
// Bind a focus event listener to the body element to make sure the focus
|
|
839
|
-
// stays trapped inside the dialog while open, and start listening for some
|
|
840
|
-
// specific key presses (TAB and ESC)
|
|
841
|
-
document.body.addEventListener('focus', this._maintainFocus, true);
|
|
842
|
-
document.addEventListener('keydown', this._bindKeypress);
|
|
843
|
-
|
|
844
|
-
// Execute all callbacks registered for the `show` event
|
|
845
|
-
this._fire('show', event);
|
|
846
|
-
|
|
847
|
-
return this
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Hide the dialog element, enable all the targets (siblings), restore the
|
|
852
|
-
* focus to the previously active element, stop listening for some specific
|
|
853
|
-
* key presses and fire all registered callbacks for `hide` event
|
|
854
|
-
*
|
|
855
|
-
* @param {CustomEvent} event
|
|
856
|
-
* @return {this}
|
|
857
|
-
*/
|
|
858
|
-
A11yDialog.prototype.hide = function (event) {
|
|
859
|
-
// If the dialog is already closed, abort
|
|
860
|
-
if (!this.shown) {
|
|
861
|
-
return this
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
this.shown = false;
|
|
865
|
-
this.$el.setAttribute('aria-hidden', 'true');
|
|
866
|
-
|
|
867
|
-
// If there was a focused element before the dialog was opened (and it has a
|
|
868
|
-
// `focus` method), restore the focus back to it
|
|
869
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/issues/108
|
|
870
|
-
if (this._previouslyFocused && this._previouslyFocused.focus) {
|
|
871
|
-
this._previouslyFocused.focus();
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// Remove the focus event listener to the body element and stop listening
|
|
875
|
-
// for specific key presses
|
|
876
|
-
document.body.removeEventListener('focus', this._maintainFocus, true);
|
|
877
|
-
document.removeEventListener('keydown', this._bindKeypress);
|
|
878
|
-
|
|
879
|
-
// Execute all callbacks registered for the `hide` event
|
|
880
|
-
this._fire('hide', event);
|
|
881
|
-
|
|
882
|
-
return this
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* Destroy the current instance (after making sure the dialog has been hidden)
|
|
887
|
-
* and remove all associated listeners from dialog openers and closers
|
|
888
|
-
*
|
|
889
|
-
* @return {this}
|
|
890
|
-
*/
|
|
891
|
-
A11yDialog.prototype.destroy = function () {
|
|
892
|
-
// Hide the dialog to avoid destroying an open instance
|
|
893
|
-
this.hide();
|
|
894
|
-
|
|
895
|
-
// Remove the click event listener from all dialog openers
|
|
896
|
-
this._openers.forEach(
|
|
897
|
-
function (opener) {
|
|
898
|
-
opener.removeEventListener('click', this._show);
|
|
899
|
-
}.bind(this)
|
|
900
|
-
);
|
|
901
|
-
|
|
902
|
-
// Remove the click event listener from all dialog closers
|
|
903
|
-
this._closers.forEach(
|
|
904
|
-
function (closer) {
|
|
905
|
-
closer.removeEventListener('click', this._hide);
|
|
906
|
-
}.bind(this)
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
// Execute all callbacks registered for the `destroy` event
|
|
910
|
-
this._fire('destroy');
|
|
911
|
-
|
|
912
|
-
// Keep an object of listener types mapped to callback functions
|
|
913
|
-
this._listeners = {};
|
|
914
|
-
|
|
915
|
-
return this
|
|
916
|
-
};
|
|
917
|
-
|
|
918
813
|
/**
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
* @param {string} type
|
|
922
|
-
* @param {Function} handler
|
|
814
|
+
* Get the first and last focusable elements in a given tree.
|
|
923
815
|
*/
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/**
|
|
935
|
-
* Unregister an existing callback for the given event type
|
|
936
|
-
*
|
|
937
|
-
* @param {string} type
|
|
938
|
-
* @param {Function} handler
|
|
939
|
-
*/
|
|
940
|
-
A11yDialog.prototype.off = function (type, handler) {
|
|
941
|
-
var index = (this._listeners[type] || []).indexOf(handler);
|
|
942
|
-
|
|
943
|
-
if (index > -1) {
|
|
944
|
-
this._listeners[type].splice(index, 1);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return this
|
|
948
|
-
};
|
|
949
|
-
|
|
816
|
+
function getFocusableEdges(el) {
|
|
817
|
+
// Check for a focusable element within the subtree of `el`.
|
|
818
|
+
const first = findFocusableElement(el, true);
|
|
819
|
+
// Only if we find the first element do we need to look for the last one. If
|
|
820
|
+
// there’s no last element, we set `last` as a reference to `first` so that
|
|
821
|
+
// the returned array is always of length 2.
|
|
822
|
+
const last = first ? findFocusableElement(el, false) || first : null;
|
|
823
|
+
return [first, last];
|
|
824
|
+
}
|
|
950
825
|
/**
|
|
951
|
-
*
|
|
952
|
-
* the
|
|
953
|
-
* dispatch a custom event on the DOM element itself to make it possible to
|
|
954
|
-
* react to the lifecycle of auto-instantiated dialogs.
|
|
955
|
-
*
|
|
956
|
-
* @access private
|
|
957
|
-
* @param {string} type
|
|
958
|
-
* @param {CustomEvent} event
|
|
826
|
+
* Find the first focusable element inside the given node if `forward` is truthy
|
|
827
|
+
* or the last focusable element otherwise.
|
|
959
828
|
*/
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
829
|
+
function findFocusableElement(node, forward) {
|
|
830
|
+
// If we’re walking forward, check if this node is focusable, and return it
|
|
831
|
+
// immediately if it is.
|
|
832
|
+
if (forward && isFocusable(node))
|
|
833
|
+
return node;
|
|
834
|
+
// We should only search the subtree of this node if it can have focusable
|
|
835
|
+
// children.
|
|
836
|
+
if (canHaveFocusableChildren(node)) {
|
|
837
|
+
// Start walking the DOM tree, looking for focusable elements.
|
|
838
|
+
// Case 1: If this node has a shadow root, search it recursively.
|
|
839
|
+
if (node.shadowRoot) {
|
|
840
|
+
// Descend into this subtree.
|
|
841
|
+
let next = getNextChildEl(node.shadowRoot, forward);
|
|
842
|
+
// Traverse siblings, searching the subtree of each one
|
|
843
|
+
// for focusable elements.
|
|
844
|
+
while (next) {
|
|
845
|
+
const focusableEl = findFocusableElement(next, forward);
|
|
846
|
+
if (focusableEl)
|
|
847
|
+
return focusableEl;
|
|
848
|
+
next = getNextSiblingEl(next, forward);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// Case 2: If this node is a slot for a Custom Element, search its assigned
|
|
852
|
+
// nodes recursively.
|
|
853
|
+
else if (node.localName === 'slot') {
|
|
854
|
+
const assignedElements = node.assignedElements({
|
|
855
|
+
flatten: true,
|
|
856
|
+
});
|
|
857
|
+
if (!forward)
|
|
858
|
+
assignedElements.reverse();
|
|
859
|
+
for (const assignedElement of assignedElements) {
|
|
860
|
+
const focusableEl = findFocusableElement(assignedElement, forward);
|
|
861
|
+
if (focusableEl)
|
|
862
|
+
return focusableEl;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// Case 3: this is a regular Light DOM node. Search its subtree.
|
|
866
|
+
else {
|
|
867
|
+
// Descend into this subtree.
|
|
868
|
+
let next = getNextChildEl(node, forward);
|
|
869
|
+
// Traverse siblings, searching the subtree of each one
|
|
870
|
+
// for focusable elements.
|
|
871
|
+
while (next) {
|
|
872
|
+
const focusableEl = findFocusableElement(next, forward);
|
|
873
|
+
if (focusableEl)
|
|
874
|
+
return focusableEl;
|
|
875
|
+
next = getNextSiblingEl(next, forward);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// If we’re walking backward, we want to check the node’s entire subtree
|
|
880
|
+
// before checking the node itself. If this node is focusable, return it.
|
|
881
|
+
if (!forward && isFocusable(node))
|
|
882
|
+
return node;
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
function getNextChildEl(node, forward) {
|
|
886
|
+
return forward ? node.firstElementChild : node.lastElementChild;
|
|
887
|
+
}
|
|
888
|
+
function getNextSiblingEl(el, forward) {
|
|
889
|
+
return forward ? el.nextElementSibling : el.previousElementSibling;
|
|
890
|
+
}
|
|
973
891
|
/**
|
|
974
|
-
*
|
|
975
|
-
* (namely ESCAPE and TAB)
|
|
976
|
-
*
|
|
977
|
-
* @access private
|
|
978
|
-
* @param {Event} event
|
|
892
|
+
* Determine if an element is hidden from the user.
|
|
979
893
|
*/
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
this.shown &&
|
|
991
|
-
event.key === ESCAPE_KEY &&
|
|
992
|
-
this.$el.getAttribute('role') !== 'alertdialog'
|
|
993
|
-
) {
|
|
994
|
-
event.preventDefault();
|
|
995
|
-
this.hide(event);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// If the dialog is shown and the TAB key is being pressed, make sure the
|
|
999
|
-
// focus stays trapped within the dialog element
|
|
1000
|
-
if (this.shown && event.key === TAB_KEY) {
|
|
1001
|
-
trapTabKey(this.$el, event);
|
|
1002
|
-
}
|
|
894
|
+
const isHidden = (el) => {
|
|
895
|
+
// Browsers hide all non-<summary> descendants of closed <details> elements
|
|
896
|
+
// from user interaction, but those non-<summary> elements may still match our
|
|
897
|
+
// focusable-selectors and may still have dimensions, so we need a special
|
|
898
|
+
// case to ignore them.
|
|
899
|
+
if (el.matches('details:not([open]) *') &&
|
|
900
|
+
!el.matches('details>summary:first-of-type'))
|
|
901
|
+
return true;
|
|
902
|
+
// If this element has no painted dimensions, it's hidden.
|
|
903
|
+
return !(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
|
|
1003
904
|
};
|
|
1004
|
-
|
|
1005
905
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
* currently open dialog
|
|
1008
|
-
*
|
|
1009
|
-
* @access private
|
|
1010
|
-
* @param {Event} event
|
|
906
|
+
* Determine if an element is focusable and has user-visible painted dimensions.
|
|
1011
907
|
*/
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
908
|
+
const isFocusable = (el) => {
|
|
909
|
+
// A shadow host that delegates focus will never directly receive focus,
|
|
910
|
+
// even with `tabindex=0`. Consider our <fancy-button> custom element, which
|
|
911
|
+
// delegates focus to its shadow button:
|
|
912
|
+
//
|
|
913
|
+
// <fancy-button tabindex="0">
|
|
914
|
+
// #shadow-root
|
|
915
|
+
// <button><slot></slot></button>
|
|
916
|
+
// </fancy-button>
|
|
917
|
+
//
|
|
918
|
+
// The browser acts as as if there is only one focusable element – the shadow
|
|
919
|
+
// button. Our library should behave the same way.
|
|
920
|
+
if (el.shadowRoot?.delegatesFocus)
|
|
921
|
+
return false;
|
|
922
|
+
return el.matches(focusableSelectors.join(',')) && !isHidden(el);
|
|
1025
923
|
};
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Convert a NodeList into an array
|
|
1029
|
-
*
|
|
1030
|
-
* @param {NodeList} collection
|
|
1031
|
-
* @return {Array<Element>}
|
|
1032
|
-
*/
|
|
1033
|
-
function toArray(collection) {
|
|
1034
|
-
return Array.prototype.slice.call(collection)
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
924
|
/**
|
|
1038
|
-
*
|
|
1039
|
-
* the
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
925
|
+
* Determine if an element can have focusable children. Useful for bailing out
|
|
926
|
+
* early when walking the DOM tree.
|
|
927
|
+
* @example
|
|
928
|
+
* This div is inert, so none of its children can be focused, even though they
|
|
929
|
+
* meet our criteria for what is focusable. Once we check the div, we can skip
|
|
930
|
+
* the rest of the subtree.
|
|
931
|
+
* ```html
|
|
932
|
+
* <div inert>
|
|
933
|
+
* <button>Button</button>
|
|
934
|
+
* <a href="#">Link</a>
|
|
935
|
+
* </div>
|
|
936
|
+
* ```
|
|
1044
937
|
*/
|
|
1045
|
-
function
|
|
1046
|
-
|
|
938
|
+
function canHaveFocusableChildren(el) {
|
|
939
|
+
// The browser will never send focus into a Shadow DOM if the host element
|
|
940
|
+
// has a negative tabindex. This applies to both slotted Light DOM Shadow DOM
|
|
941
|
+
// children
|
|
942
|
+
if (el.shadowRoot && el.getAttribute('tabindex') === '-1')
|
|
943
|
+
return false;
|
|
944
|
+
// Elemments matching this selector are either hidden entirely from the user,
|
|
945
|
+
// or are visible but unavailable for interaction. Their descentants can never
|
|
946
|
+
// receive focus.
|
|
947
|
+
return !el.matches(':disabled,[hidden],[inert]');
|
|
1047
948
|
}
|
|
1048
|
-
|
|
1049
949
|
/**
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
* @param {Element} node
|
|
950
|
+
* Get the active element, accounting for Shadow DOM subtrees.
|
|
951
|
+
* @author Cory LaViska
|
|
952
|
+
* @see: https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
|
|
1054
953
|
*/
|
|
1055
|
-
function
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
*/
|
|
1067
|
-
function getFocusableChildren(node) {
|
|
1068
|
-
return $$(focusableSelectors.join(','), node).filter(function (child) {
|
|
1069
|
-
return !!(
|
|
1070
|
-
child.offsetWidth ||
|
|
1071
|
-
child.offsetHeight ||
|
|
1072
|
-
child.getClientRects().length
|
|
1073
|
-
)
|
|
1074
|
-
})
|
|
954
|
+
function getActiveElement(root = document) {
|
|
955
|
+
const activeEl = root.activeElement;
|
|
956
|
+
if (!activeEl)
|
|
957
|
+
return null;
|
|
958
|
+
// If there’s a shadow root, recursively find the active element within it.
|
|
959
|
+
// If the recursive call returns null, return the active element
|
|
960
|
+
// of the top-level Document.
|
|
961
|
+
if (activeEl.shadowRoot)
|
|
962
|
+
return getActiveElement(activeEl.shadowRoot) || document.activeElement;
|
|
963
|
+
// If not, we can just return the active element
|
|
964
|
+
return activeEl;
|
|
1075
965
|
}
|
|
1076
|
-
|
|
1077
966
|
/**
|
|
1078
967
|
* Trap the focus inside the given element
|
|
1079
|
-
*
|
|
1080
|
-
* @param {Element} node
|
|
1081
|
-
* @param {Event} event
|
|
1082
968
|
*/
|
|
1083
|
-
function trapTabKey(
|
|
1084
|
-
|
|
1085
|
-
|
|
969
|
+
function trapTabKey(el, event) {
|
|
970
|
+
const [firstFocusableChild, lastFocusableChild] = getFocusableEdges(el);
|
|
971
|
+
// If there are no focusable children in the dialog, prevent the user from
|
|
972
|
+
// tabbing out of it
|
|
973
|
+
if (!firstFocusableChild)
|
|
974
|
+
return event.preventDefault();
|
|
975
|
+
const activeElement = getActiveElement();
|
|
976
|
+
// If the SHIFT key is pressed while tabbing (moving backwards) and the
|
|
977
|
+
// currently focused item is the first one, move the focus to the last
|
|
978
|
+
// focusable item from the dialog element
|
|
979
|
+
if (event.shiftKey && activeElement === firstFocusableChild) {
|
|
980
|
+
// @ts-ignore: we know that `lastFocusableChild` is not null here
|
|
981
|
+
lastFocusableChild.focus();
|
|
982
|
+
event.preventDefault();
|
|
983
|
+
}
|
|
984
|
+
// If the SHIFT key is not pressed (moving forwards) and the currently focused
|
|
985
|
+
// item is the last one, move the focus to the first focusable item from the
|
|
986
|
+
// dialog element
|
|
987
|
+
else if (!event.shiftKey && activeElement === lastFocusableChild) {
|
|
988
|
+
firstFocusableChild.focus();
|
|
989
|
+
event.preventDefault();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
1086
992
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
993
|
+
class A11yDialog {
|
|
994
|
+
$el;
|
|
995
|
+
id;
|
|
996
|
+
previouslyFocused;
|
|
997
|
+
shown;
|
|
998
|
+
constructor(element) {
|
|
999
|
+
this.$el = element;
|
|
1000
|
+
this.id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
|
|
1001
|
+
this.previouslyFocused = null;
|
|
1002
|
+
this.shown = false;
|
|
1003
|
+
this.maintainFocus = this.maintainFocus.bind(this);
|
|
1004
|
+
this.bindKeypress = this.bindKeypress.bind(this);
|
|
1005
|
+
this.handleTriggerClicks = this.handleTriggerClicks.bind(this);
|
|
1006
|
+
this.show = this.show.bind(this);
|
|
1007
|
+
this.hide = this.hide.bind(this);
|
|
1008
|
+
this.$el.setAttribute('aria-hidden', 'true');
|
|
1009
|
+
this.$el.setAttribute('aria-modal', 'true');
|
|
1010
|
+
this.$el.setAttribute('tabindex', '-1');
|
|
1011
|
+
if (!this.$el.hasAttribute('role')) {
|
|
1012
|
+
this.$el.setAttribute('role', 'dialog');
|
|
1013
|
+
}
|
|
1014
|
+
document.addEventListener('click', this.handleTriggerClicks, true);
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Destroy the current instance (after making sure the dialog has been hidden)
|
|
1018
|
+
* and remove all associated listeners from dialog openers and closers
|
|
1019
|
+
*/
|
|
1020
|
+
destroy() {
|
|
1021
|
+
// Hide the dialog to avoid destroying an open instance
|
|
1022
|
+
this.hide();
|
|
1023
|
+
// Remove the click event delegates for our openers and closers
|
|
1024
|
+
document.removeEventListener('click', this.handleTriggerClicks, true);
|
|
1025
|
+
// Clone and replace the dialog element to prevent memory leaks caused by
|
|
1026
|
+
// event listeners that the author might not have cleaned up.
|
|
1027
|
+
this.$el.replaceWith(this.$el.cloneNode(true));
|
|
1028
|
+
// Dispatch a `destroy` event
|
|
1029
|
+
this.fire('destroy');
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Show the dialog element, trap the current focus within it, listen for some
|
|
1034
|
+
* specific key presses and fire all registered callbacks for `show` event
|
|
1035
|
+
*/
|
|
1036
|
+
show(event) {
|
|
1037
|
+
// If the dialog is already open, abort
|
|
1038
|
+
if (this.shown)
|
|
1039
|
+
return this;
|
|
1040
|
+
// Keep a reference to the currently focused element to be able to restore
|
|
1041
|
+
// it later
|
|
1042
|
+
this.shown = true;
|
|
1043
|
+
this.$el.removeAttribute('aria-hidden');
|
|
1044
|
+
this.previouslyFocused = getActiveElement();
|
|
1045
|
+
// Due to a long lasting bug in Safari, clicking an interactive element
|
|
1046
|
+
// (like a <button>) does *not* move the focus to that element, which means
|
|
1047
|
+
// `document.activeElement` is whatever element is currently focused (like
|
|
1048
|
+
// an <input>), or the <body> element otherwise. We can work around that
|
|
1049
|
+
// problem by checking whether the focused element is the <body>, and if it,
|
|
1050
|
+
// store the click event target.
|
|
1051
|
+
// See: https://bugs.webkit.org/show_bug.cgi?id=22261
|
|
1052
|
+
if (this.previouslyFocused?.tagName === 'BODY' && event?.target) {
|
|
1053
|
+
this.previouslyFocused = event.target;
|
|
1054
|
+
}
|
|
1055
|
+
// Set the focus to the dialog element
|
|
1056
|
+
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/583
|
|
1057
|
+
if (event?.type === 'focus') {
|
|
1058
|
+
this.maintainFocus(event);
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
moveFocusToDialog(this.$el);
|
|
1062
|
+
}
|
|
1063
|
+
// Bind a focus event listener to the body element to make sure the focus
|
|
1064
|
+
// stays trapped inside the dialog while open, and start listening for some
|
|
1065
|
+
// specific key presses (TAB and ESC)
|
|
1066
|
+
document.body.addEventListener('focus', this.maintainFocus, true);
|
|
1067
|
+
this.$el.addEventListener('keydown', this.bindKeypress, true);
|
|
1068
|
+
// Dispatch a `show` event
|
|
1069
|
+
this.fire('show', event);
|
|
1070
|
+
return this;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Hide the dialog element, restore the focus to the previously active
|
|
1074
|
+
* element, stop listening for some specific key presses and fire all
|
|
1075
|
+
* registered callbacks for `hide` event
|
|
1076
|
+
*/
|
|
1077
|
+
hide(event) {
|
|
1078
|
+
// If the dialog is already closed, abort
|
|
1079
|
+
if (!this.shown)
|
|
1080
|
+
return this;
|
|
1081
|
+
this.shown = false;
|
|
1082
|
+
this.$el.setAttribute('aria-hidden', 'true');
|
|
1083
|
+
this.previouslyFocused?.focus?.();
|
|
1084
|
+
// Remove the focus event listener to the body element and stop listening
|
|
1085
|
+
// for specific key presses
|
|
1086
|
+
document.body.removeEventListener('focus', this.maintainFocus, true);
|
|
1087
|
+
this.$el.removeEventListener('keydown', this.bindKeypress, true);
|
|
1088
|
+
// Dispatch a `hide` event
|
|
1089
|
+
this.fire('hide', event);
|
|
1090
|
+
return this;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Register a new callback for the given event type
|
|
1094
|
+
*/
|
|
1095
|
+
on(type, handler, options) {
|
|
1096
|
+
this.$el.addEventListener(type, handler, options);
|
|
1097
|
+
return this;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Unregister an existing callback for the given event type
|
|
1101
|
+
*/
|
|
1102
|
+
off(type, handler, options) {
|
|
1103
|
+
this.$el.removeEventListener(type, handler, options);
|
|
1104
|
+
return this;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Dispatch a custom event from the DOM element associated with this dialog.
|
|
1108
|
+
* This allows authors to listen for and respond to the events in their own
|
|
1109
|
+
* code
|
|
1110
|
+
*/
|
|
1111
|
+
fire(type, event) {
|
|
1112
|
+
this.$el.dispatchEvent(new CustomEvent(type, {
|
|
1113
|
+
detail: event,
|
|
1114
|
+
cancelable: true,
|
|
1115
|
+
}));
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Add a delegated event listener for when elememts that open or close the
|
|
1119
|
+
* dialog are clicked, and call `show` or `hide`, respectively
|
|
1120
|
+
*/
|
|
1121
|
+
handleTriggerClicks(event) {
|
|
1122
|
+
const target = event.target;
|
|
1123
|
+
// We use `.closest(..)` and not `.matches(..)` here so that clicking
|
|
1124
|
+
// an element nested within a dialog opener does cause the dialog to open
|
|
1125
|
+
if (target.closest(`[data-a11y-dialog-show="${this.id}"]`)) {
|
|
1126
|
+
this.show(event);
|
|
1127
|
+
}
|
|
1128
|
+
if (target.closest(`[data-a11y-dialog-hide="${this.id}"]`) ||
|
|
1129
|
+
(target.closest('[data-a11y-dialog-hide]') &&
|
|
1130
|
+
target.closest('[aria-modal="true"]') === this.$el)) {
|
|
1131
|
+
this.hide(event);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Private event handler used when listening to some specific key presses
|
|
1136
|
+
* (namely ESC and TAB)
|
|
1137
|
+
*/
|
|
1138
|
+
bindKeypress(event) {
|
|
1139
|
+
// This is an escape hatch in case there are nested open dialogs, so that
|
|
1140
|
+
// only the top most dialog gets interacted with
|
|
1141
|
+
if (document.activeElement?.closest('[aria-modal="true"]') !== this.$el) {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
let hasOpenPopover = false;
|
|
1145
|
+
try {
|
|
1146
|
+
hasOpenPopover = !!this.$el.querySelector('[popover]:not([popover="manual"]):popover-open');
|
|
1147
|
+
}
|
|
1148
|
+
catch {
|
|
1149
|
+
// Run that DOM query in a try/catch because not all browsers support the
|
|
1150
|
+
// `:popover-open` selector, which would cause the whole expression to
|
|
1151
|
+
// fail
|
|
1152
|
+
// See: https://caniuse.com/mdn-css_selectors_popover-open
|
|
1153
|
+
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/578#discussion_r1343215149
|
|
1154
|
+
}
|
|
1155
|
+
// If the dialog is shown and the ESC key is pressed, prevent any further
|
|
1156
|
+
// effects from the ESC key and hide the dialog, unless:
|
|
1157
|
+
// - its role is `alertdialog`, which means it should be modal
|
|
1158
|
+
// - or it contains an open popover, in which case ESC should close it
|
|
1159
|
+
if (event.key === 'Escape' &&
|
|
1160
|
+
this.$el.getAttribute('role') !== 'alertdialog' &&
|
|
1161
|
+
!hasOpenPopover) {
|
|
1162
|
+
event.preventDefault();
|
|
1163
|
+
this.hide(event);
|
|
1164
|
+
}
|
|
1165
|
+
// If the dialog is shown and the TAB key is pressed, make sure the focus
|
|
1166
|
+
// stays trapped within the dialog element
|
|
1167
|
+
if (event.key === 'Tab') {
|
|
1168
|
+
trapTabKey(this.$el, event);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* If the dialog is shown and the focus is not within a dialog element (either
|
|
1173
|
+
* this one or another one in case of nested dialogs) or attribute, move it
|
|
1174
|
+
* back to the dialog container
|
|
1175
|
+
* See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
|
|
1176
|
+
*/
|
|
1177
|
+
maintainFocus(event) {
|
|
1178
|
+
const target = event.target;
|
|
1179
|
+
if (!target.closest('[aria-modal="true"], [data-a11y-dialog-ignore-focus-trap]')) {
|
|
1180
|
+
moveFocusToDialog(this.$el);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1103
1183
|
}
|
|
1104
1184
|
|
|
1105
1185
|
function instantiateDialogs() {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1186
|
+
for (const el of document.querySelectorAll('[data-a11y-dialog]')) {
|
|
1187
|
+
new A11yDialog(el);
|
|
1188
|
+
}
|
|
1109
1189
|
}
|
|
1110
|
-
|
|
1111
1190
|
if (typeof document !== 'undefined') {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
} else {
|
|
1118
|
-
window.setTimeout(instantiateDialogs, 16);
|
|
1191
|
+
if (document.readyState === 'loading') {
|
|
1192
|
+
document.addEventListener('DOMContentLoaded', instantiateDialogs);
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
instantiateDialogs();
|
|
1119
1196
|
}
|
|
1120
|
-
}
|
|
1121
1197
|
}
|
|
1122
1198
|
|
|
1123
1199
|
var CONTAINER_ID = "__authsignal-popup-container";
|
|
@@ -1129,16 +1205,16 @@ var DEFAULT_WIDTH = "385px";
|
|
|
1129
1205
|
var INITIAL_HEIGHT = "384px";
|
|
1130
1206
|
var PopupHandler = /** @class */ (function () {
|
|
1131
1207
|
function PopupHandler(_a) {
|
|
1132
|
-
var width = _a.width;
|
|
1208
|
+
var width = _a.width, isClosable = _a.isClosable;
|
|
1133
1209
|
this.popup = null;
|
|
1134
1210
|
if (document.querySelector("#".concat(CONTAINER_ID))) {
|
|
1135
1211
|
throw new Error("Multiple instances of Authsignal popup is not supported.");
|
|
1136
1212
|
}
|
|
1137
|
-
this.create({ width: width });
|
|
1213
|
+
this.create({ width: width, isClosable: isClosable });
|
|
1138
1214
|
}
|
|
1139
1215
|
PopupHandler.prototype.create = function (_a) {
|
|
1140
1216
|
var _this = this;
|
|
1141
|
-
var _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH : _b;
|
|
1217
|
+
var _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH : _b, _c = _a.isClosable, isClosable = _c === void 0 ? true : _c;
|
|
1142
1218
|
var isWidthValidCSSValue = CSS.supports("width", width);
|
|
1143
1219
|
var popupWidth = width;
|
|
1144
1220
|
if (!isWidthValidCSSValue) {
|
|
@@ -1149,10 +1225,15 @@ var PopupHandler = /** @class */ (function () {
|
|
|
1149
1225
|
var container = document.createElement("div");
|
|
1150
1226
|
container.setAttribute("id", CONTAINER_ID);
|
|
1151
1227
|
container.setAttribute("aria-hidden", "true");
|
|
1228
|
+
if (!isClosable) {
|
|
1229
|
+
container.setAttribute("role", "alertdialog");
|
|
1230
|
+
}
|
|
1152
1231
|
// Create dialog overlay
|
|
1153
1232
|
var overlay = document.createElement("div");
|
|
1154
1233
|
overlay.setAttribute("id", OVERLAY_ID);
|
|
1155
|
-
|
|
1234
|
+
if (isClosable) {
|
|
1235
|
+
overlay.setAttribute("data-a11y-dialog-hide", "true");
|
|
1236
|
+
}
|
|
1156
1237
|
// Create dialog content
|
|
1157
1238
|
var content = document.createElement("div");
|
|
1158
1239
|
content.setAttribute("id", CONTENT_ID);
|
|
@@ -1206,11 +1287,11 @@ var PopupHandler = /** @class */ (function () {
|
|
|
1206
1287
|
}
|
|
1207
1288
|
this.popup.hide();
|
|
1208
1289
|
};
|
|
1209
|
-
PopupHandler.prototype.on = function (event,
|
|
1290
|
+
PopupHandler.prototype.on = function (event, listener) {
|
|
1210
1291
|
if (!this.popup) {
|
|
1211
1292
|
throw new Error("Popup is not initialized");
|
|
1212
1293
|
}
|
|
1213
|
-
this.popup.on(event,
|
|
1294
|
+
this.popup.on(event, listener);
|
|
1214
1295
|
};
|
|
1215
1296
|
return PopupHandler;
|
|
1216
1297
|
}());
|
|
@@ -1305,7 +1386,7 @@ var Authsignal = /** @class */ (function () {
|
|
|
1305
1386
|
Authsignal.prototype.launchWithPopup = function (url, options) {
|
|
1306
1387
|
var _this = this;
|
|
1307
1388
|
var popupOptions = options.popupOptions;
|
|
1308
|
-
var popupHandler = new PopupHandler({ width: popupOptions === null || popupOptions === void 0 ? void 0 : popupOptions.width });
|
|
1389
|
+
var popupHandler = new PopupHandler({ width: popupOptions === null || popupOptions === void 0 ? void 0 : popupOptions.width, isClosable: popupOptions === null || popupOptions === void 0 ? void 0 : popupOptions.isClosable });
|
|
1309
1390
|
var popupUrl = "".concat(url, "&mode=popup");
|
|
1310
1391
|
popupHandler.show({ url: popupUrl });
|
|
1311
1392
|
return new Promise(function (resolve) {
|
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var authsignal=function(t){"use strict";let e;const n=new Uint8Array(16);function o(){if(!e&&(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!e))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(n)}const i=[];for(let t=0;t<256;++t)i.push((t+256).toString(16).slice(1));var r={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function a(t,e,n){if(r.randomUUID&&!e&&!t)return r.randomUUID();const a=(t=t||{}).random||(t.rng||o)();if(a[6]=15&a[6]|64,a[8]=63&a[8]|128,e){n=n||0;for(let t=0;t<16;++t)e[n+t]=a[t];return e}return function(t,e=0){return(i[t[e+0]]+i[t[e+1]]+i[t[e+2]]+i[t[e+3]]+"-"+i[t[e+4]]+i[t[e+5]]+"-"+i[t[e+6]]+i[t[e+7]]+"-"+i[t[e+8]]+i[t[e+9]]+"-"+i[t[e+10]]+i[t[e+11]]+i[t[e+12]]+i[t[e+13]]+i[t[e+14]]+i[t[e+15]]).toLowerCase()}(a)}var s=function(t){var e=t.name,n=t.value,o=t.expire,i=t.domain,r=t.secure,a=o===1/0?" expires=Fri, 31 Dec 9999 23:59:59 GMT":"; max-age="+o;document.cookie=encodeURIComponent(e)+"="+n+"; path=/;"+a+(i?"; domain="+i:"")+(r?"; secure":"")};function c(t,e){var n={};for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&e.indexOf(o)<0&&(n[o]=t[o]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(o=Object.getOwnPropertySymbols(t);i<o.length;i++)e.indexOf(o[i])<0&&Object.prototype.propertyIsEnumerable.call(t,o[i])&&(n[o[i]]=t[o[i]])}return n}function u(t,e,n,o){return new(n||(n=Promise))((function(i,r){function a(t){try{c(o.next(t))}catch(t){r(t)}}function s(t){try{c(o.throw(t))}catch(t){r(t)}}function c(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(a,s)}c((o=o.apply(t,e||[])).next())}))}function d(t,e){var n,o,i,r,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return r={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(r[Symbol.iterator]=function(){return this}),r;function s(r){return function(s){return function(r){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,o&&(i=2&r[0]?o.return:r[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,r[1])).done)return i;switch(o=0,i&&(r=[2&r[0],i.value]),r[0]){case 0:case 1:i=r;break;case 4:return a.label++,{value:r[1],done:!1};case 5:a.label++,o=r[1],r=[0];continue;case 7:r=a.ops.pop(),a.trys.pop();continue;default:if(!(i=a.trys,(i=i.length>0&&i[i.length-1])||6!==r[0]&&2!==r[0])){a=0;continue}if(3===r[0]&&(!i||r[1]>i[0]&&r[1]<i[3])){a.label=r[1];break}if(6===r[0]&&a.label<i[1]){a.label=i[1],i=r;break}if(i&&a.label<i[2]){a.label=i[2],a.ops.push(r);break}i[2]&&a.ops.pop(),a.trys.pop();continue}r=e.call(t,a)}catch(t){r=[6,t],o=0}finally{n=i=0}if(5&r[0])throw r[1];return{value:r[0]?r[1]:void 0,done:!0}}([r,s])}}}function l(t){const e=new Uint8Array(t);let n="";for(const t of e)n+=String.fromCharCode(t);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function h(t){const e=t.replace(/-/g,"+").replace(/_/g,"/"),n=(4-e.length%4)%4,o=e.padEnd(e.length+n,"="),i=atob(o),r=new ArrayBuffer(i.length),a=new Uint8Array(r);for(let t=0;t<i.length;t++)a[t]=i.charCodeAt(t);return r}function p(){return void 0!==window?.PublicKeyCredential&&"function"==typeof window.PublicKeyCredential}function f(t){const{id:e}=t;return{...t,id:h(e),transports:t.transports}}function w(t){return"localhost"===t||/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(t)}t.AuthsignalWindowMessage=void 0,(t.AuthsignalWindowMessage||(t.AuthsignalWindowMessage={})).AUTHSIGNAL_CLOSE_POPUP="AUTHSIGNAL_CLOSE_POPUP";class m extends Error{constructor({message:t,code:e,cause:n,name:o}){super(t,{cause:n}),this.name=o??n.name,this.code=e}}const y=new class{createNewAbortSignal(){if(this.controller){const t=new Error("Cancelling existing WebAuthn API call for new one");t.name="AbortError",this.controller.abort(t)}const t=new AbortController;return this.controller=t,t.signal}cancelCeremony(){if(this.controller){const t=new Error("Manually cancelling existing WebAuthn API call");t.name="AbortError",this.controller.abort(t),this.controller=void 0}}},b=["cross-platform","platform"];function g(t){if(t&&!(b.indexOf(t)<0))return t}async function v(t){if(!p())throw new Error("WebAuthn is not supported in this browser");var e;const n={publicKey:{...t,challenge:h(t.challenge),user:{...t.user,id:(e=t.user.id,(new TextEncoder).encode(e))},excludeCredentials:t.excludeCredentials?.map(f)}};let o;n.signal=y.createNewAbortSignal();try{o=await navigator.credentials.create(n)}catch(t){throw function({error:t,options:e}){const{publicKey:n}=e;if(!n)throw Error("options was missing required publicKey property");if("AbortError"===t.name){if(e.signal instanceof AbortSignal)return new m({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else if("ConstraintError"===t.name){if(!0===n.authenticatorSelection?.requireResidentKey)return new m({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:t});if("required"===n.authenticatorSelection?.userVerification)return new m({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:t})}else{if("InvalidStateError"===t.name)return new m({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:t});if("NotAllowedError"===t.name)return new m({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if("NotSupportedError"===t.name)return 0===n.pubKeyCredParams.filter((t=>"public-key"===t.type)).length?new m({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:t}):new m({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:t});if("SecurityError"===t.name){const e=window.location.hostname;if(!w(e))return new m({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t});if(n.rp.id!==e)return new m({message:`The RP ID "${n.rp.id}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else if("TypeError"===t.name){if(n.user.id.byteLength<1||n.user.id.byteLength>64)return new m({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:t})}else if("UnknownError"===t.name)return new m({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}({error:t,options:n})}if(!o)throw new Error("Registration was not completed");const{id:i,rawId:r,response:a,type:s}=o;let c,u,d,b;if("function"==typeof a.getTransports&&(c=a.getTransports()),"function"==typeof a.getPublicKeyAlgorithm)try{u=a.getPublicKeyAlgorithm()}catch(t){_("getPublicKeyAlgorithm()",t)}if("function"==typeof a.getPublicKey)try{const t=a.getPublicKey();null!==t&&(d=l(t))}catch(t){_("getPublicKey()",t)}if("function"==typeof a.getAuthenticatorData)try{b=l(a.getAuthenticatorData())}catch(t){_("getAuthenticatorData()",t)}return{id:i,rawId:l(r),response:{attestationObject:l(a.attestationObject),clientDataJSON:l(a.clientDataJSON),transports:c,publicKeyAlgorithm:u,publicKey:d,authenticatorData:b},type:s,clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:g(o.authenticatorAttachment)}}function _(t,e){console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${t}. You should report this error to them.\n`,e)}async function E(t,e=!1){if(!p())throw new Error("WebAuthn is not supported in this browser");let n;0!==t.allowCredentials?.length&&(n=t.allowCredentials?.map(f));const o={...t,challenge:h(t.challenge),allowCredentials:n},i={};if(e){if(!await function(){const t=window.PublicKeyCredential;return void 0===t.isConditionalMediationAvailable?new Promise((t=>t(!1))):t.isConditionalMediationAvailable()}())throw Error("Browser does not support WebAuthn autofill");if(document.querySelectorAll("input[autocomplete$='webauthn']").length<1)throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');i.mediation="conditional",o.allowCredentials=[]}let r;i.publicKey=o,i.signal=y.createNewAbortSignal();try{r=await navigator.credentials.get(i)}catch(t){throw function({error:t,options:e}){const{publicKey:n}=e;if(!n)throw Error("options was missing required publicKey property");if("AbortError"===t.name){if(e.signal instanceof AbortSignal)return new m({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else{if("NotAllowedError"===t.name)return new m({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if("SecurityError"===t.name){const e=window.location.hostname;if(!w(e))return new m({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t});if(n.rpId!==e)return new m({message:`The RP ID "${n.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else if("UnknownError"===t.name)return new m({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}({error:t,options:i})}if(!r)throw new Error("Authentication was not completed");const{id:a,rawId:s,response:c,type:u}=r;let d;var b;return c.userHandle&&(b=c.userHandle,d=new TextDecoder("utf-8").decode(b)),{id:a,rawId:l(s),response:{authenticatorData:l(c.authenticatorData),clientDataJSON:l(c.clientDataJSON),signature:l(c.signature),userHandle:d},type:u,clientExtensionResults:r.getClientExtensionResults(),authenticatorAttachment:g(r.authenticatorAttachment)}}var A=function(){function t(t){var e=t.baseUrl,n=t.tenantId;this.tenantId=n,this.baseUrl=e}return t.prototype.registrationOptions=function(t){var e=t.token,n=t.userName;return u(this,void 0,void 0,(function(){return d(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey/registration-options"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify({username:n})})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.authenticationOptions=function(t){var e=t.token;return u(this,void 0,void 0,(function(){return d(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey/authentication-options"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify({})})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.addAuthenticator=function(t){var e=t.token,n=c(t,["token"]);return u(this,void 0,void 0,(function(){return d(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify(n)})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.verify=function(t){var e=t.token,n=c(t,["token"]);return u(this,void 0,void 0,(function(){return d(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/verify/passkey"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify(n)})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.buildHeaders=function(t){return{"Content-Type":"application/json",Authorization:t?"Bearer ".concat(t):"Basic ".concat(window.btoa(encodeURIComponent(this.tenantId)))}},t}(),R=function(){function t(t){var e=t.baseUrl,n=t.tenantId;this.api=new A({baseUrl:e,tenantId:n})}return t.prototype.signUp=function(t){var e=t.userName,n=t.token;return u(this,void 0,void 0,(function(){var t,o,i;return d(this,(function(r){switch(r.label){case 0:return[4,this.api.registrationOptions({userName:e,token:n})];case 1:return[4,v((t=r.sent()).options)];case 2:return o=r.sent(),[4,this.api.addAuthenticator({challengeId:t.challengeId,registrationCredential:o,token:n})];case 3:return[2,null==(i=r.sent())?void 0:i.accessToken]}}))}))},t.prototype.signIn=function(t){return u(this,void 0,void 0,(function(){var e,n,o;return d(this,(function(i){switch(i.label){case 0:if((null==t?void 0:t.token)&&t.autofill)throw new Error("Autofill is not supported when providing a token");return[4,this.api.authenticationOptions({token:null==t?void 0:t.token})];case 1:return[4,E((e=i.sent()).options,null==t?void 0:t.autofill)];case 2:return n=i.sent(),[4,this.api.verify({challengeId:e.challengeId,authenticationCredential:n,token:null==t?void 0:t.token})];case 3:return[2,null==(o=i.sent())?void 0:o.accessToken]}}))}))},t}(),O=function(){function t(){this.windowRef=null}return t.prototype.show=function(t){var e=t.url,n=t.width,o=void 0===n?400:n,i=t.height,r=function(t){var e=t.url,n=t.width,o=t.height,i=t.win;if(!i.top)return null;var r=i.top.outerHeight/2+i.top.screenY-o/2,a=i.top.outerWidth/2+i.top.screenX-n/2;return window.open(e,"","toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=".concat(n,", height=").concat(o,", top=").concat(r,", left=").concat(a))}({url:e,width:o,height:void 0===i?500:i,win:window});if(!r)throw new Error("Window is not initialized");return this.windowRef=r,r},t.prototype.close=function(){if(!this.windowRef)throw new Error("Window is not initialized");this.windowRef.close()},t}();var I=['a[href]:not([tabindex^="-"])','area[href]:not([tabindex^="-"])','input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])','input[type="radio"]:not([disabled]):not([tabindex^="-"])','select:not([disabled]):not([tabindex^="-"])','textarea:not([disabled]):not([tabindex^="-"])','button:not([disabled]):not([tabindex^="-"])','iframe:not([tabindex^="-"])','audio[controls]:not([tabindex^="-"])','video[controls]:not([tabindex^="-"])','[contenteditable]:not([tabindex^="-"])','[tabindex]:not([tabindex^="-"])'];function S(t){this._show=this.show.bind(this),this._hide=this.hide.bind(this),this._maintainFocus=this._maintainFocus.bind(this),this._bindKeypress=this._bindKeypress.bind(this),this.$el=t,this.shown=!1,this._id=this.$el.getAttribute("data-a11y-dialog")||this.$el.id,this._previouslyFocused=null,this._listeners={},this.create()}function C(t,e){return n=(e||document).querySelectorAll(t),Array.prototype.slice.call(n);var n}function P(t){(t.querySelector("[autofocus]")||t).focus()}function k(){C("[data-a11y-dialog]").forEach((function(t){new S(t)}))}S.prototype.create=function(){this.$el.setAttribute("aria-hidden",!0),this.$el.setAttribute("aria-modal",!0),this.$el.setAttribute("tabindex",-1),this.$el.hasAttribute("role")||this.$el.setAttribute("role","dialog"),this._openers=C('[data-a11y-dialog-show="'+this._id+'"]'),this._openers.forEach(function(t){t.addEventListener("click",this._show)}.bind(this));const t=this.$el;return this._closers=C("[data-a11y-dialog-hide]",this.$el).filter((function(e){return e.closest('[aria-modal="true"], [data-a11y-dialog]')===t})).concat(C('[data-a11y-dialog-hide="'+this._id+'"]')),this._closers.forEach(function(t){t.addEventListener("click",this._hide)}.bind(this)),this._fire("create"),this},S.prototype.show=function(t){return this.shown||(this._previouslyFocused=document.activeElement,this.$el.removeAttribute("aria-hidden"),this.shown=!0,P(this.$el),document.body.addEventListener("focus",this._maintainFocus,!0),document.addEventListener("keydown",this._bindKeypress),this._fire("show",t)),this},S.prototype.hide=function(t){return this.shown?(this.shown=!1,this.$el.setAttribute("aria-hidden","true"),this._previouslyFocused&&this._previouslyFocused.focus&&this._previouslyFocused.focus(),document.body.removeEventListener("focus",this._maintainFocus,!0),document.removeEventListener("keydown",this._bindKeypress),this._fire("hide",t),this):this},S.prototype.destroy=function(){return this.hide(),this._openers.forEach(function(t){t.removeEventListener("click",this._show)}.bind(this)),this._closers.forEach(function(t){t.removeEventListener("click",this._hide)}.bind(this)),this._fire("destroy"),this._listeners={},this},S.prototype.on=function(t,e){return void 0===this._listeners[t]&&(this._listeners[t]=[]),this._listeners[t].push(e),this},S.prototype.off=function(t,e){var n=(this._listeners[t]||[]).indexOf(e);return n>-1&&this._listeners[t].splice(n,1),this},S.prototype._fire=function(t,e){var n=this._listeners[t]||[],o=new CustomEvent(t,{detail:e});this.$el.dispatchEvent(o),n.forEach(function(t){t(this.$el,e)}.bind(this))},S.prototype._bindKeypress=function(t){const e=document.activeElement;e&&e.closest('[aria-modal="true"]')!==this.$el||(this.shown&&"Escape"===t.key&&"alertdialog"!==this.$el.getAttribute("role")&&(t.preventDefault(),this.hide(t)),this.shown&&"Tab"===t.key&&function(t,e){var n=function(t){return C(I.join(","),t).filter((function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}))}(t),o=n.indexOf(document.activeElement);e.shiftKey&&0===o?(n[n.length-1].focus(),e.preventDefault()):e.shiftKey||o!==n.length-1||(n[0].focus(),e.preventDefault())}(this.$el,t))},S.prototype._maintainFocus=function(t){!this.shown||t.target.closest('[aria-modal="true"]')||t.target.closest("[data-a11y-dialog-ignore-focus-trap]")||P(this.$el)},"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",k):window.requestAnimationFrame?window.requestAnimationFrame(k):window.setTimeout(k,16));var x="__authsignal-popup-container",U="__authsignal-popup-content",T="__authsignal-popup-overlay",N="__authsignal-popup-style",D="__authsignal-popup-iframe",L="385px",K=function(){function t(t){var e=t.width;if(this.popup=null,document.querySelector("#".concat(x)))throw new Error("Multiple instances of Authsignal popup is not supported.");this.create({width:e})}return t.prototype.create=function(t){var e=this,n=t.width,o=void 0===n?L:n,i=o;CSS.supports("width",o)||(console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead."),i=L);var r=document.createElement("div");r.setAttribute("id",x),r.setAttribute("aria-hidden","true");var a=document.createElement("div");a.setAttribute("id",T),a.setAttribute("data-a11y-dialog-hide","true");var s=document.createElement("div");s.setAttribute("id",U),document.body.appendChild(r);var c=document.createElement("style");c.setAttribute("id",N),c.textContent="\n #".concat(x,",\n #").concat(T," {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n\n #").concat(x," {\n z-index: 2147483647;\n display: flex;\n }\n\n #").concat(x,"[aria-hidden='true'] {\n display: none;\n }\n\n #").concat(T," {\n background-color: rgba(0, 0, 0, 0.18);\n }\n\n #").concat(U," {\n margin: auto;\n z-index: 2147483647;\n position: relative;\n background-color: transparent;\n border-radius: 8px;\n width: ").concat(i,";\n }\n\n #").concat(U," iframe {\n width: 1px;\n min-width: 100%;\n border-radius: inherit;\n max-height: 95vh;\n height: ").concat("384px",";\n }\n "),document.head.insertAdjacentElement("beforeend",c),r.appendChild(a),r.appendChild(s),this.popup=new S(r),this.popup.on("hide",(function(){e.destroy()}))},t.prototype.destroy=function(){var t=document.querySelector("#".concat(x)),e=document.querySelector("#".concat(N));t&&e&&(document.body.removeChild(t),document.head.removeChild(e)),window.removeEventListener("message",$)},t.prototype.show=function(t){var e,n=t.url;if(!this.popup)throw new Error("Popup is not initialized");var o=document.createElement("iframe");o.setAttribute("id",D),o.setAttribute("name","authsignal"),o.setAttribute("title","Authsignal multi-factor authentication"),o.setAttribute("src",n),o.setAttribute("frameborder","0"),o.setAttribute("allow","publickey-credentials-get *; publickey-credentials-create *; clipboard-write");var i=document.querySelector("#".concat(U));i&&i.appendChild(o),window.addEventListener("message",$),null===(e=this.popup)||void 0===e||e.show()},t.prototype.close=function(){if(!this.popup)throw new Error("Popup is not initialized");this.popup.hide()},t.prototype.on=function(t,e){if(!this.popup)throw new Error("Popup is not initialized");this.popup.on(t,e)},t}();function $(t){var e=document.querySelector("#".concat(D));e&&t.data.height&&(e.style.height=t.data.height+"px")}var H="4a08uqve",W=function(){function e(t){var e=t.cookieDomain,n=t.cookieName,o=void 0===n?"__as_aid":n,i=t.baseUrl,r=void 0===i?"https://api.authsignal.com/v1":i,c=t.tenantId;if(this.anonymousId="",this.profilingId="",this.cookieDomain="",this.anonymousIdCookieName="",this._token=void 0,this.cookieDomain=e||document.location.hostname.replace("www.",""),this.anonymousIdCookieName=o,!c)throw new Error("tenantId is required");this.passkey=new R({tenantId:c,baseUrl:r});var u,d=(u=this.anonymousIdCookieName)&&decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*"+encodeURIComponent(u).replace(/[\-\.\+\*]/g,"\\$&")+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1"))||null;d?this.anonymousId=d:(this.anonymousId=a(),s({name:this.anonymousIdCookieName,value:this.anonymousId,expire:1/0,domain:this.cookieDomain,secure:"http:"!==document.location.protocol}))}return e.prototype.launch=function(t,e){switch(null==e?void 0:e.mode){case"window":return this.launchWithWindow(t,e);case"popup":return this.launchWithPopup(t,e);default:this.launchWithRedirect(t)}},e.prototype.initAdvancedProfiling=function(t){var e=a();this.profilingId=e,s({name:"__as_pid",value:e,expire:1/0,domain:this.cookieDomain,secure:"http:"!==document.location.protocol});var n=t?"".concat(t,"/fp/tags.js?org_id=").concat(H,"&session_id=").concat(e):"https://h.online-metrix.net/fp/tags.js?org_id=".concat(H,"&session_id=").concat(e),o=document.createElement("script");o.src=n,o.async=!1,o.id="as_adv_profile",document.head.appendChild(o);var i=document.createElement("noscript");i.setAttribute("id","as_adv_profile_pixel"),i.setAttribute("aria-hidden","true");var r=document.createElement("iframe"),c=t?"".concat(t,"/fp/tags?org_id=").concat(H,"&session_id=").concat(e):"https://h.online-metrix.net/fp/tags?org_id=".concat(H,"&session_id=").concat(e);r.setAttribute("id","as_adv_profile_pixel"),r.setAttribute("src",c),r.setAttribute("style","width: 100px; height: 100px; border: 0; position: absolute; top: -5000px;"),i&&(i.appendChild(r),document.body.prepend(i))},e.prototype.launchWithRedirect=function(t){window.location.href=t},e.prototype.launchWithPopup=function(e,n){var o=this,i=n.popupOptions,r=new K({width:null==i?void 0:i.width}),a="".concat(e,"&mode=popup");return r.show({url:a}),new Promise((function(e){r.on("hide",(function(){e({token:o._token})})),window.addEventListener("message",(function(e){var n=null;try{n=JSON.parse(e.data)}catch(t){}(null==n?void 0:n.event)===t.AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP&&(o._token=n.token,r.close())}),!1)}))},e.prototype.launchWithWindow=function(e,n){var o=this,i=n.windowOptions,r=new O,a="".concat(e,"&mode=popup");return r.show({url:a,width:null==i?void 0:i.width,height:null==i?void 0:i.height}),new Promise((function(e){window.addEventListener("message",(function(n){var i=null;try{i=JSON.parse(n.data)}catch(t){}(null==i?void 0:i.event)===t.AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP&&(o._token=i.token,r.close(),e({token:o._token}))}),!1)}))},e}();return t.Authsignal=W,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
1
|
+
var authsignal=function(t){"use strict";let e;const n=new Uint8Array(16);function o(){if(!e&&(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!e))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(n)}const i=[];for(let t=0;t<256;++t)i.push((t+256).toString(16).slice(1));var r={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function a(t,e,n){if(r.randomUUID&&!e&&!t)return r.randomUUID();const a=(t=t||{}).random||(t.rng||o)();if(a[6]=15&a[6]|64,a[8]=63&a[8]|128,e){n=n||0;for(let t=0;t<16;++t)e[n+t]=a[t];return e}return function(t,e=0){return(i[t[e+0]]+i[t[e+1]]+i[t[e+2]]+i[t[e+3]]+"-"+i[t[e+4]]+i[t[e+5]]+"-"+i[t[e+6]]+i[t[e+7]]+"-"+i[t[e+8]]+i[t[e+9]]+"-"+i[t[e+10]]+i[t[e+11]]+i[t[e+12]]+i[t[e+13]]+i[t[e+14]]+i[t[e+15]]).toLowerCase()}(a)}var s=function(t){var e=t.name,n=t.value,o=t.expire,i=t.domain,r=t.secure,a=o===1/0?" expires=Fri, 31 Dec 9999 23:59:59 GMT":"; max-age="+o;document.cookie=encodeURIComponent(e)+"="+n+"; path=/;"+a+(i?"; domain="+i:"")+(r?"; secure":"")};function c(t,e){var n={};for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&e.indexOf(o)<0&&(n[o]=t[o]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(o=Object.getOwnPropertySymbols(t);i<o.length;i++)e.indexOf(o[i])<0&&Object.prototype.propertyIsEnumerable.call(t,o[i])&&(n[o[i]]=t[o[i]])}return n}function u(t,e,n,o){return new(n||(n=Promise))((function(i,r){function a(t){try{c(o.next(t))}catch(t){r(t)}}function s(t){try{c(o.throw(t))}catch(t){r(t)}}function c(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(a,s)}c((o=o.apply(t,e||[])).next())}))}function l(t,e){var n,o,i,r,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return r={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(r[Symbol.iterator]=function(){return this}),r;function s(r){return function(s){return function(r){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,o&&(i=2&r[0]?o.return:r[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,r[1])).done)return i;switch(o=0,i&&(r=[2&r[0],i.value]),r[0]){case 0:case 1:i=r;break;case 4:return a.label++,{value:r[1],done:!1};case 5:a.label++,o=r[1],r=[0];continue;case 7:r=a.ops.pop(),a.trys.pop();continue;default:if(!(i=a.trys,(i=i.length>0&&i[i.length-1])||6!==r[0]&&2!==r[0])){a=0;continue}if(3===r[0]&&(!i||r[1]>i[0]&&r[1]<i[3])){a.label=r[1];break}if(6===r[0]&&a.label<i[1]){a.label=i[1],i=r;break}if(i&&a.label<i[2]){a.label=i[2],a.ops.push(r);break}i[2]&&a.ops.pop(),a.trys.pop();continue}r=e.call(t,a)}catch(t){r=[6,t],o=0}finally{n=i=0}if(5&r[0])throw r[1];return{value:r[0]?r[1]:void 0,done:!0}}([r,s])}}}function d(t){const e=new Uint8Array(t);let n="";for(const t of e)n+=String.fromCharCode(t);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function h(t){const e=t.replace(/-/g,"+").replace(/_/g,"/"),n=(4-e.length%4)%4,o=e.padEnd(e.length+n,"="),i=atob(o),r=new ArrayBuffer(i.length),a=new Uint8Array(r);for(let t=0;t<i.length;t++)a[t]=i.charCodeAt(t);return r}function p(){return void 0!==window?.PublicKeyCredential&&"function"==typeof window.PublicKeyCredential}function f(t){const{id:e}=t;return{...t,id:h(e),transports:t.transports}}function m(t){return"localhost"===t||/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(t)}t.AuthsignalWindowMessage=void 0,(t.AuthsignalWindowMessage||(t.AuthsignalWindowMessage={})).AUTHSIGNAL_CLOSE_POPUP="AUTHSIGNAL_CLOSE_POPUP";class w extends Error{constructor({message:t,code:e,cause:n,name:o}){super(t,{cause:n}),this.name=o??n.name,this.code=e}}const y=new class{createNewAbortSignal(){if(this.controller){const t=new Error("Cancelling existing WebAuthn API call for new one");t.name="AbortError",this.controller.abort(t)}const t=new AbortController;return this.controller=t,t.signal}cancelCeremony(){if(this.controller){const t=new Error("Manually cancelling existing WebAuthn API call");t.name="AbortError",this.controller.abort(t),this.controller=void 0}}},g=["cross-platform","platform"];function b(t){if(t&&!(g.indexOf(t)<0))return t}async function v(t){if(!p())throw new Error("WebAuthn is not supported in this browser");var e;const n={publicKey:{...t,challenge:h(t.challenge),user:{...t.user,id:(e=t.user.id,(new TextEncoder).encode(e))},excludeCredentials:t.excludeCredentials?.map(f)}};let o;n.signal=y.createNewAbortSignal();try{o=await navigator.credentials.create(n)}catch(t){throw function({error:t,options:e}){const{publicKey:n}=e;if(!n)throw Error("options was missing required publicKey property");if("AbortError"===t.name){if(e.signal instanceof AbortSignal)return new w({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else if("ConstraintError"===t.name){if(!0===n.authenticatorSelection?.requireResidentKey)return new w({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:t});if("required"===n.authenticatorSelection?.userVerification)return new w({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:t})}else{if("InvalidStateError"===t.name)return new w({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:t});if("NotAllowedError"===t.name)return new w({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if("NotSupportedError"===t.name)return 0===n.pubKeyCredParams.filter((t=>"public-key"===t.type)).length?new w({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:t}):new w({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:t});if("SecurityError"===t.name){const e=window.location.hostname;if(!m(e))return new w({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t});if(n.rp.id!==e)return new w({message:`The RP ID "${n.rp.id}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else if("TypeError"===t.name){if(n.user.id.byteLength<1||n.user.id.byteLength>64)return new w({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:t})}else if("UnknownError"===t.name)return new w({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}({error:t,options:n})}if(!o)throw new Error("Registration was not completed");const{id:i,rawId:r,response:a,type:s}=o;let c,u,l,g;if("function"==typeof a.getTransports&&(c=a.getTransports()),"function"==typeof a.getPublicKeyAlgorithm)try{u=a.getPublicKeyAlgorithm()}catch(t){E("getPublicKeyAlgorithm()",t)}if("function"==typeof a.getPublicKey)try{const t=a.getPublicKey();null!==t&&(l=d(t))}catch(t){E("getPublicKey()",t)}if("function"==typeof a.getAuthenticatorData)try{g=d(a.getAuthenticatorData())}catch(t){E("getAuthenticatorData()",t)}return{id:i,rawId:d(r),response:{attestationObject:d(a.attestationObject),clientDataJSON:d(a.clientDataJSON),transports:c,publicKeyAlgorithm:u,publicKey:l,authenticatorData:g},type:s,clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:b(o.authenticatorAttachment)}}function E(t,e){console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${t}. You should report this error to them.\n`,e)}async function A(t,e=!1){if(!p())throw new Error("WebAuthn is not supported in this browser");let n;0!==t.allowCredentials?.length&&(n=t.allowCredentials?.map(f));const o={...t,challenge:h(t.challenge),allowCredentials:n},i={};if(e){if(!await function(){const t=window.PublicKeyCredential;return void 0===t.isConditionalMediationAvailable?new Promise((t=>t(!1))):t.isConditionalMediationAvailable()}())throw Error("Browser does not support WebAuthn autofill");if(document.querySelectorAll("input[autocomplete$='webauthn']").length<1)throw Error('No <input> with "webauthn" as the only or last value in its `autocomplete` attribute was detected');i.mediation="conditional",o.allowCredentials=[]}let r;i.publicKey=o,i.signal=y.createNewAbortSignal();try{r=await navigator.credentials.get(i)}catch(t){throw function({error:t,options:e}){const{publicKey:n}=e;if(!n)throw Error("options was missing required publicKey property");if("AbortError"===t.name){if(e.signal instanceof AbortSignal)return new w({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else{if("NotAllowedError"===t.name)return new w({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if("SecurityError"===t.name){const e=window.location.hostname;if(!m(e))return new w({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t});if(n.rpId!==e)return new w({message:`The RP ID "${n.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else if("UnknownError"===t.name)return new w({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}({error:t,options:i})}if(!r)throw new Error("Authentication was not completed");const{id:a,rawId:s,response:c,type:u}=r;let l;var g;return c.userHandle&&(g=c.userHandle,l=new TextDecoder("utf-8").decode(g)),{id:a,rawId:d(s),response:{authenticatorData:d(c.authenticatorData),clientDataJSON:d(c.clientDataJSON),signature:d(c.signature),userHandle:l},type:u,clientExtensionResults:r.getClientExtensionResults(),authenticatorAttachment:b(r.authenticatorAttachment)}}var R=function(){function t(t){var e=t.baseUrl,n=t.tenantId;this.tenantId=n,this.baseUrl=e}return t.prototype.registrationOptions=function(t){var e=t.token,n=t.userName,o=t.authenticatorAttachment;return u(this,void 0,void 0,(function(){var t;return l(this,(function(i){switch(i.label){case 0:return t=Boolean(o)?{username:n,authenticatorAttachment:o}:{username:n},[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey/registration-options"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify(t)})];case 1:return[2,i.sent().json()]}}))}))},t.prototype.authenticationOptions=function(t){var e=t.token;return u(this,void 0,void 0,(function(){return l(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey/authentication-options"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify({})})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.addAuthenticator=function(t){var e=t.token,n=c(t,["token"]);return u(this,void 0,void 0,(function(){return l(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify(n)})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.verify=function(t){var e=t.token,n=c(t,["token"]);return u(this,void 0,void 0,(function(){return l(this,(function(t){switch(t.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/verify/passkey"),{method:"POST",headers:this.buildHeaders(e),body:JSON.stringify(n)})];case 1:return[2,t.sent().json()]}}))}))},t.prototype.getPasskeyAuthenticator=function(t){return u(this,void 0,void 0,(function(){var e;return l(this,(function(n){switch(n.label){case 0:return[4,fetch("".concat(this.baseUrl,"/client/user-authenticators/passkey?credentialId=").concat(t),{method:"GET",headers:this.buildHeaders()})];case 1:if(!(e=n.sent()).ok)throw new Error(e.statusText);return[2,e.json()]}}))}))},t.prototype.buildHeaders=function(t){return{"Content-Type":"application/json",Authorization:t?"Bearer ".concat(t):"Basic ".concat(window.btoa(encodeURIComponent(this.tenantId)))}},t}(),_=function(){function t(t){var e=t.baseUrl,n=t.tenantId;this.passkeyLocalStorageKey="as_passkey_credential_id",this.api=new R({baseUrl:e,tenantId:n})}return t.prototype.signUp=function(t){var e=t.userName,n=t.token,o=t.authenticatorAttachment,i=void 0===o?"platform":o;return u(this,void 0,void 0,(function(){var t,o,r;return l(this,(function(a){switch(a.label){case 0:return[4,this.api.registrationOptions({userName:e,token:n,authenticatorAttachment:i})];case 1:return[4,v((t=a.sent()).options)];case 2:return o=a.sent(),[4,this.api.addAuthenticator({challengeId:t.challengeId,registrationCredential:o,token:n})];case 3:return(null==(r=a.sent())?void 0:r.isVerified)&&this.storeCredentialAgainstDevice(o),[2,null==r?void 0:r.accessToken]}}))}))},t.prototype.signIn=function(t){return u(this,void 0,void 0,(function(){var e,n,o;return l(this,(function(i){switch(i.label){case 0:if((null==t?void 0:t.token)&&t.autofill)throw new Error("Autofill is not supported when providing a token");return[4,this.api.authenticationOptions({token:null==t?void 0:t.token})];case 1:return[4,A((e=i.sent()).options,null==t?void 0:t.autofill)];case 2:return n=i.sent(),[4,this.api.verify({challengeId:e.challengeId,authenticationCredential:n,token:null==t?void 0:t.token})];case 3:return(null==(o=i.sent())?void 0:o.isVerified)&&this.storeCredentialAgainstDevice(n),[2,null==o?void 0:o.accessToken]}}))}))},t.prototype.isAvailableOnDevice=function(){return u(this,void 0,void 0,(function(){var t;return l(this,(function(e){switch(e.label){case 0:if(!(t=localStorage.getItem(this.passkeyLocalStorageKey)))return[2,!1];e.label=1;case 1:return e.trys.push([1,3,,4]),[4,this.api.getPasskeyAuthenticator(t)];case 2:return e.sent(),[2,!0];case 3:return e.sent(),[2,!1];case 4:return[2]}}))}))},t.prototype.storeCredentialAgainstDevice=function(t){var e=t.id;"cross-platform"!==t.authenticatorAttachment&&localStorage.setItem(this.passkeyLocalStorageKey,e)},t}(),O=function(){function t(){this.windowRef=null}return t.prototype.show=function(t){var e=t.url,n=t.width,o=void 0===n?400:n,i=t.height,r=function(t){var e=t.url,n=t.width,o=t.height,i=t.win;if(!i.top)return null;var r=i.top.outerHeight/2+i.top.screenY-o/2,a=i.top.outerWidth/2+i.top.screenX-n/2;return window.open(e,"","toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=".concat(n,", height=").concat(o,", top=").concat(r,", left=").concat(a))}({url:e,width:o,height:void 0===i?500:i,win:window});if(!r)throw new Error("Window is not initialized");return this.windowRef=r,r},t.prototype.close=function(){if(!this.windowRef)throw new Error("Window is not initialized");this.windowRef.close()},t}();const I=":not([inert]):not([inert] *)",C=':not([tabindex^="-"])',S=":not(:disabled)";var k=[`a[href]${I}${C}`,`area[href]${I}${C}`,`input:not([type="hidden"]):not([type="radio"])${I}${C}${S}`,`input[type="radio"]${I}${C}${S}`,`select${I}${C}${S}`,`textarea${I}${C}${S}`,`button${I}${C}${S}`,`details${I} > summary:first-of-type${C}`,`iframe${I}${C}`,`audio[controls]${I}${C}`,`video[controls]${I}${C}`,`[contenteditable]${I}${C}`,`[tabindex]${I}${C}`];function $(t){(t.querySelector("[autofocus]")||t).focus()}function P(t,e){if(e&&N(t))return t;if(!((n=t).shadowRoot&&"-1"===n.getAttribute("tabindex")||n.matches(":disabled,[hidden],[inert]")))if(t.shadowRoot){let n=T(t.shadowRoot,e);for(;n;){const t=P(n,e);if(t)return t;n=U(n,e)}}else if("slot"===t.localName){const n=t.assignedElements({flatten:!0});e||n.reverse();for(const t of n){const n=P(t,e);if(n)return n}}else{let n=T(t,e);for(;n;){const t=P(n,e);if(t)return t;n=U(n,e)}}var n;return!e&&N(t)?t:null}function T(t,e){return e?t.firstElementChild:t.lastElementChild}function U(t,e){return e?t.nextElementSibling:t.previousElementSibling}const N=t=>!t.shadowRoot?.delegatesFocus&&(t.matches(k.join(","))&&!(t=>!(!t.matches("details:not([open]) *")||t.matches("details>summary:first-of-type"))||!(t.offsetWidth||t.offsetHeight||t.getClientRects().length))(t));function D(t=document){const e=t.activeElement;return e?e.shadowRoot?D(e.shadowRoot)||document.activeElement:e:null}function x(t,e){const[n,o]=function(t){const e=P(t,!0);return[e,e?P(t,!1)||e:null]}(t);if(!n)return e.preventDefault();const i=D();e.shiftKey&&i===n?(o.focus(),e.preventDefault()):e.shiftKey||i!==o||(n.focus(),e.preventDefault())}class L{$el;id;previouslyFocused;shown;constructor(t){this.$el=t,this.id=this.$el.getAttribute("data-a11y-dialog")||this.$el.id,this.previouslyFocused=null,this.shown=!1,this.maintainFocus=this.maintainFocus.bind(this),this.bindKeypress=this.bindKeypress.bind(this),this.handleTriggerClicks=this.handleTriggerClicks.bind(this),this.show=this.show.bind(this),this.hide=this.hide.bind(this),this.$el.setAttribute("aria-hidden","true"),this.$el.setAttribute("aria-modal","true"),this.$el.setAttribute("tabindex","-1"),this.$el.hasAttribute("role")||this.$el.setAttribute("role","dialog"),document.addEventListener("click",this.handleTriggerClicks,!0)}destroy(){return this.hide(),document.removeEventListener("click",this.handleTriggerClicks,!0),this.$el.replaceWith(this.$el.cloneNode(!0)),this.fire("destroy"),this}show(t){return this.shown||(this.shown=!0,this.$el.removeAttribute("aria-hidden"),this.previouslyFocused=D(),"BODY"===this.previouslyFocused?.tagName&&t?.target&&(this.previouslyFocused=t.target),"focus"===t?.type?this.maintainFocus(t):$(this.$el),document.body.addEventListener("focus",this.maintainFocus,!0),this.$el.addEventListener("keydown",this.bindKeypress,!0),this.fire("show",t)),this}hide(t){return this.shown?(this.shown=!1,this.$el.setAttribute("aria-hidden","true"),this.previouslyFocused?.focus?.(),document.body.removeEventListener("focus",this.maintainFocus,!0),this.$el.removeEventListener("keydown",this.bindKeypress,!0),this.fire("hide",t),this):this}on(t,e,n){return this.$el.addEventListener(t,e,n),this}off(t,e,n){return this.$el.removeEventListener(t,e,n),this}fire(t,e){this.$el.dispatchEvent(new CustomEvent(t,{detail:e,cancelable:!0}))}handleTriggerClicks(t){const e=t.target;e.closest(`[data-a11y-dialog-show="${this.id}"]`)&&this.show(t),(e.closest(`[data-a11y-dialog-hide="${this.id}"]`)||e.closest("[data-a11y-dialog-hide]")&&e.closest('[aria-modal="true"]')===this.$el)&&this.hide(t)}bindKeypress(t){if(document.activeElement?.closest('[aria-modal="true"]')!==this.$el)return;let e=!1;try{e=!!this.$el.querySelector('[popover]:not([popover="manual"]):popover-open')}catch{}"Escape"!==t.key||"alertdialog"===this.$el.getAttribute("role")||e||(t.preventDefault(),this.hide(t)),"Tab"===t.key&&x(this.$el,t)}maintainFocus(t){t.target.closest('[aria-modal="true"], [data-a11y-dialog-ignore-focus-trap]')||$(this.$el)}}function K(){for(const t of document.querySelectorAll("[data-a11y-dialog]"))new L(t)}"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",K):K());var H="__authsignal-popup-container",W="__authsignal-popup-content",M="__authsignal-popup-overlay",j="__authsignal-popup-style",q="__authsignal-popup-iframe",F="385px",G=function(){function t(t){var e=t.width,n=t.isClosable;if(this.popup=null,document.querySelector("#".concat(H)))throw new Error("Multiple instances of Authsignal popup is not supported.");this.create({width:e,isClosable:n})}return t.prototype.create=function(t){var e=this,n=t.width,o=void 0===n?F:n,i=t.isClosable,r=void 0===i||i,a=o;CSS.supports("width",o)||(console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead."),a=F);var s=document.createElement("div");s.setAttribute("id",H),s.setAttribute("aria-hidden","true"),r||s.setAttribute("role","alertdialog");var c=document.createElement("div");c.setAttribute("id",M),r&&c.setAttribute("data-a11y-dialog-hide","true");var u=document.createElement("div");u.setAttribute("id",W),document.body.appendChild(s);var l=document.createElement("style");l.setAttribute("id",j),l.textContent="\n #".concat(H,",\n #").concat(M," {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n\n #").concat(H," {\n z-index: 2147483647;\n display: flex;\n }\n\n #").concat(H,"[aria-hidden='true'] {\n display: none;\n }\n\n #").concat(M," {\n background-color: rgba(0, 0, 0, 0.18);\n }\n\n #").concat(W," {\n margin: auto;\n z-index: 2147483647;\n position: relative;\n background-color: transparent;\n border-radius: 8px;\n width: ").concat(a,";\n }\n\n #").concat(W," iframe {\n width: 1px;\n min-width: 100%;\n border-radius: inherit;\n max-height: 95vh;\n height: ").concat("384px",";\n }\n "),document.head.insertAdjacentElement("beforeend",l),s.appendChild(c),s.appendChild(u),this.popup=new L(s),this.popup.on("hide",(function(){e.destroy()}))},t.prototype.destroy=function(){var t=document.querySelector("#".concat(H)),e=document.querySelector("#".concat(j));t&&e&&(document.body.removeChild(t),document.head.removeChild(e)),window.removeEventListener("message",V)},t.prototype.show=function(t){var e,n=t.url;if(!this.popup)throw new Error("Popup is not initialized");var o=document.createElement("iframe");o.setAttribute("id",q),o.setAttribute("name","authsignal"),o.setAttribute("title","Authsignal multi-factor authentication"),o.setAttribute("src",n),o.setAttribute("frameborder","0"),o.setAttribute("allow","publickey-credentials-get *; publickey-credentials-create *; clipboard-write");var i=document.querySelector("#".concat(W));i&&i.appendChild(o),window.addEventListener("message",V),null===(e=this.popup)||void 0===e||e.show()},t.prototype.close=function(){if(!this.popup)throw new Error("Popup is not initialized");this.popup.hide()},t.prototype.on=function(t,e){if(!this.popup)throw new Error("Popup is not initialized");this.popup.on(t,e)},t}();function V(t){var e=document.querySelector("#".concat(q));e&&t.data.height&&(e.style.height=t.data.height+"px")}var z="4a08uqve",B=function(){function e(t){var e=t.cookieDomain,n=t.cookieName,o=void 0===n?"__as_aid":n,i=t.baseUrl,r=void 0===i?"https://api.authsignal.com/v1":i,c=t.tenantId;if(this.anonymousId="",this.profilingId="",this.cookieDomain="",this.anonymousIdCookieName="",this._token=void 0,this.cookieDomain=e||document.location.hostname.replace("www.",""),this.anonymousIdCookieName=o,!c)throw new Error("tenantId is required");this.passkey=new _({tenantId:c,baseUrl:r});var u,l=(u=this.anonymousIdCookieName)&&decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*"+encodeURIComponent(u).replace(/[\-\.\+\*]/g,"\\$&")+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1"))||null;l?this.anonymousId=l:(this.anonymousId=a(),s({name:this.anonymousIdCookieName,value:this.anonymousId,expire:1/0,domain:this.cookieDomain,secure:"http:"!==document.location.protocol}))}return e.prototype.launch=function(t,e){switch(null==e?void 0:e.mode){case"window":return this.launchWithWindow(t,e);case"popup":return this.launchWithPopup(t,e);default:this.launchWithRedirect(t)}},e.prototype.initAdvancedProfiling=function(t){var e=a();this.profilingId=e,s({name:"__as_pid",value:e,expire:1/0,domain:this.cookieDomain,secure:"http:"!==document.location.protocol});var n=t?"".concat(t,"/fp/tags.js?org_id=").concat(z,"&session_id=").concat(e):"https://h.online-metrix.net/fp/tags.js?org_id=".concat(z,"&session_id=").concat(e),o=document.createElement("script");o.src=n,o.async=!1,o.id="as_adv_profile",document.head.appendChild(o);var i=document.createElement("noscript");i.setAttribute("id","as_adv_profile_pixel"),i.setAttribute("aria-hidden","true");var r=document.createElement("iframe"),c=t?"".concat(t,"/fp/tags?org_id=").concat(z,"&session_id=").concat(e):"https://h.online-metrix.net/fp/tags?org_id=".concat(z,"&session_id=").concat(e);r.setAttribute("id","as_adv_profile_pixel"),r.setAttribute("src",c),r.setAttribute("style","width: 100px; height: 100px; border: 0; position: absolute; top: -5000px;"),i&&(i.appendChild(r),document.body.prepend(i))},e.prototype.launchWithRedirect=function(t){window.location.href=t},e.prototype.launchWithPopup=function(e,n){var o=this,i=n.popupOptions,r=new G({width:null==i?void 0:i.width,isClosable:null==i?void 0:i.isClosable}),a="".concat(e,"&mode=popup");return r.show({url:a}),new Promise((function(e){r.on("hide",(function(){e({token:o._token})})),window.addEventListener("message",(function(e){var n=null;try{n=JSON.parse(e.data)}catch(t){}(null==n?void 0:n.event)===t.AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP&&(o._token=n.token,r.close())}),!1)}))},e.prototype.launchWithWindow=function(e,n){var o=this,i=n.windowOptions,r=new O,a="".concat(e,"&mode=popup");return r.show({url:a,width:null==i?void 0:i.width,height:null==i?void 0:i.height}),new Promise((function(e){window.addEventListener("message",(function(n){var i=null;try{i=JSON.parse(n.data)}catch(t){}(null==i?void 0:i.event)===t.AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP&&(o._token=i.token,r.close(),e({token:o._token}))}),!1)}))},e}();return t.Authsignal=B,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
package/dist/passkey.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PasskeyApiClient } from "./api";
|
|
2
|
+
import { AuthenticatorAttachment } from "@simplewebauthn/types";
|
|
2
3
|
declare type PasskeyOptions = {
|
|
3
4
|
baseUrl: string;
|
|
4
5
|
tenantId: string;
|
|
@@ -6,11 +7,13 @@ declare type PasskeyOptions = {
|
|
|
6
7
|
declare type SignUpParams = {
|
|
7
8
|
userName?: string;
|
|
8
9
|
token: string;
|
|
10
|
+
authenticatorAttachment?: AuthenticatorAttachment | null;
|
|
9
11
|
};
|
|
10
12
|
export declare class Passkey {
|
|
11
13
|
api: PasskeyApiClient;
|
|
14
|
+
private passkeyLocalStorageKey;
|
|
12
15
|
constructor({ baseUrl, tenantId }: PasskeyOptions);
|
|
13
|
-
signUp({ userName, token }: SignUpParams): Promise<string | undefined>;
|
|
16
|
+
signUp({ userName, token, authenticatorAttachment }: SignUpParams): Promise<string | undefined>;
|
|
14
17
|
signIn(): Promise<string | undefined>;
|
|
15
18
|
signIn(params?: {
|
|
16
19
|
token: string;
|
|
@@ -18,5 +21,7 @@ export declare class Passkey {
|
|
|
18
21
|
signIn(params?: {
|
|
19
22
|
autofill: boolean;
|
|
20
23
|
}): Promise<string | undefined>;
|
|
24
|
+
isAvailableOnDevice(): Promise<boolean>;
|
|
25
|
+
private storeCredentialAgainstDevice;
|
|
21
26
|
}
|
|
22
27
|
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export declare type PopupLaunchOptions = BaseLaunchOptions & {
|
|
|
19
19
|
* @deprecated The popup will automatically resize to fit the content.
|
|
20
20
|
*/
|
|
21
21
|
height?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the popup is closable with the escape key and by clicking the backdrop.
|
|
24
|
+
*/
|
|
25
|
+
isClosable?: boolean;
|
|
22
26
|
};
|
|
23
27
|
};
|
|
24
28
|
export declare type WindowLaunchOptions = BaseLaunchOptions & {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authsignal/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@fingerprintjs/fingerprintjs": "^3.3.6",
|
|
31
31
|
"@simplewebauthn/browser": "^9.0.1",
|
|
32
32
|
"@simplewebauthn/types": "^9.0.1",
|
|
33
|
-
"a11y-dialog": "
|
|
33
|
+
"a11y-dialog": "8.0.4",
|
|
34
34
|
"uuid": "^9.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|