@authsignal/browser 0.4.0 → 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.
@@ -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: EventType, handler: EventHandler): void;
16
+ on(event: A11yDialogEvent, listener: EventListener): void;
17
17
  }
18
18
  export {};
package/dist/index.js CHANGED
@@ -777,408 +777,423 @@ function openWindow(_a) {
777
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));
778
778
  }
779
779
 
780
+ const not = {
781
+ inert: ':not([inert]):not([inert] *)',
782
+ negTabIndex: ':not([tabindex^="-"])',
783
+ disabled: ':not(:disabled)',
784
+ };
785
+
780
786
  var focusableSelectors = [
781
- 'a[href]:not([tabindex^="-"])',
782
- 'area[href]:not([tabindex^="-"])',
783
- 'input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])',
784
- 'input[type="radio"]:not([disabled]):not([tabindex^="-"])',
785
- 'select:not([disabled]):not([tabindex^="-"])',
786
- 'textarea:not([disabled]):not([tabindex^="-"])',
787
- 'button:not([disabled]):not([tabindex^="-"])',
788
- 'iframe:not([tabindex^="-"])',
789
- 'audio[controls]:not([tabindex^="-"])',
790
- 'video[controls]:not([tabindex^="-"])',
791
- '[contenteditable]:not([tabindex^="-"])',
792
- '[tabindex]:not([tabindex^="-"])',
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}`,
793
803
  ];
794
804
 
795
- var TAB_KEY = 'Tab';
796
- var ESCAPE_KEY = 'Escape';
797
-
798
805
  /**
799
- * Define the constructor to instantiate a dialog
800
- *
801
- * @constructor
802
- * @param {Element} element
806
+ * Set the focus to the first element with `autofocus` with the element or the
807
+ * element itself.
803
808
  */
804
- function A11yDialog(element) {
805
- // Prebind the functions that will be bound in addEventListener and
806
- // removeEventListener to avoid losing references
807
- this._show = this.show.bind(this);
808
- this._hide = this.hide.bind(this);
809
- this._maintainFocus = this._maintainFocus.bind(this);
810
- this._bindKeypress = this._bindKeypress.bind(this);
811
-
812
- this.$el = element;
813
- this.shown = false;
814
- this._id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
815
- this._previouslyFocused = null;
816
- this._listeners = {};
817
-
818
- // Initialise everything needed for the dialog to work properly
819
- this.create();
809
+ function moveFocusToDialog(el) {
810
+ const focused = (el.querySelector('[autofocus]') || el);
811
+ focused.focus();
820
812
  }
821
-
822
- /**
823
- * Set up everything necessary for the dialog to be functioning
824
- *
825
- * @param {(NodeList | Element | string)} targets
826
- * @return {this}
827
- */
828
- A11yDialog.prototype.create = function () {
829
- this.$el.setAttribute('aria-hidden', true);
830
- this.$el.setAttribute('aria-modal', true);
831
- this.$el.setAttribute('tabindex', -1);
832
-
833
- if (!this.$el.hasAttribute('role')) {
834
- this.$el.setAttribute('role', 'dialog');
835
- }
836
-
837
- // Keep a collection of dialog openers, each of which will be bound a click
838
- // event listener to open the dialog
839
- this._openers = $$('[data-a11y-dialog-show="' + this._id + '"]');
840
- this._openers.forEach(
841
- function (opener) {
842
- opener.addEventListener('click', this._show);
843
- }.bind(this)
844
- );
845
-
846
- // Keep a collection of dialog closers, each of which will be bound a click
847
- // event listener to close the dialog
848
- const $el = this.$el;
849
-
850
- this._closers = $$('[data-a11y-dialog-hide]', this.$el)
851
- // This filter is necessary in case there are nested dialogs, so that
852
- // only closers from the current dialog are retrieved and effective
853
- .filter(function (closer) {
854
- // Testing for `[aria-modal="true"]` is not enough since this attribute
855
- // and the collect of closers is done at instantation time, when nested
856
- // dialogs might not have yet been instantiated. Note that if the dialogs
857
- // are manually instantiated, this could still fail because none of these
858
- // selectors would match; this would cause closers to close all parent
859
- // dialogs instead of just the current one
860
- return closer.closest('[aria-modal="true"], [data-a11y-dialog]') === $el
861
- })
862
- .concat($$('[data-a11y-dialog-hide="' + this._id + '"]'));
863
-
864
- this._closers.forEach(
865
- function (closer) {
866
- closer.addEventListener('click', this._hide);
867
- }.bind(this)
868
- );
869
-
870
- // Execute all callbacks registered for the `create` event
871
- this._fire('create');
872
-
873
- return this
874
- };
875
-
876
813
  /**
877
- * Show the dialog element, disable all the targets (siblings), trap the
878
- * current focus within it, listen for some specific key presses and fire all
879
- * registered callbacks for `show` event
880
- *
881
- * @param {CustomEvent} event
882
- * @return {this}
814
+ * Get the first and last focusable elements in a given tree.
883
815
  */
884
- A11yDialog.prototype.show = function (event) {
885
- // If the dialog is already open, abort
886
- if (this.shown) {
887
- return this
888
- }
889
-
890
- // Keep a reference to the currently focused element to be able to restore
891
- // it later
892
- this._previouslyFocused = document.activeElement;
893
- this.$el.removeAttribute('aria-hidden');
894
- this.shown = true;
895
-
896
- // Set the focus to the dialog element
897
- moveFocusToDialog(this.$el);
898
-
899
- // Bind a focus event listener to the body element to make sure the focus
900
- // stays trapped inside the dialog while open, and start listening for some
901
- // specific key presses (TAB and ESC)
902
- document.body.addEventListener('focus', this._maintainFocus, true);
903
- document.addEventListener('keydown', this._bindKeypress);
904
-
905
- // Execute all callbacks registered for the `show` event
906
- this._fire('show', event);
907
-
908
- return this
909
- };
910
-
911
- /**
912
- * Hide the dialog element, enable all the targets (siblings), restore the
913
- * focus to the previously active element, stop listening for some specific
914
- * key presses and fire all registered callbacks for `hide` event
915
- *
916
- * @param {CustomEvent} event
917
- * @return {this}
918
- */
919
- A11yDialog.prototype.hide = function (event) {
920
- // If the dialog is already closed, abort
921
- if (!this.shown) {
922
- return this
923
- }
924
-
925
- this.shown = false;
926
- this.$el.setAttribute('aria-hidden', 'true');
927
-
928
- // If there was a focused element before the dialog was opened (and it has a
929
- // `focus` method), restore the focus back to it
930
- // See: https://github.com/KittyGiraudel/a11y-dialog/issues/108
931
- if (this._previouslyFocused && this._previouslyFocused.focus) {
932
- this._previouslyFocused.focus();
933
- }
934
-
935
- // Remove the focus event listener to the body element and stop listening
936
- // for specific key presses
937
- document.body.removeEventListener('focus', this._maintainFocus, true);
938
- document.removeEventListener('keydown', this._bindKeypress);
939
-
940
- // Execute all callbacks registered for the `hide` event
941
- this._fire('hide', event);
942
-
943
- return this
944
- };
945
-
946
- /**
947
- * Destroy the current instance (after making sure the dialog has been hidden)
948
- * and remove all associated listeners from dialog openers and closers
949
- *
950
- * @return {this}
951
- */
952
- A11yDialog.prototype.destroy = function () {
953
- // Hide the dialog to avoid destroying an open instance
954
- this.hide();
955
-
956
- // Remove the click event listener from all dialog openers
957
- this._openers.forEach(
958
- function (opener) {
959
- opener.removeEventListener('click', this._show);
960
- }.bind(this)
961
- );
962
-
963
- // Remove the click event listener from all dialog closers
964
- this._closers.forEach(
965
- function (closer) {
966
- closer.removeEventListener('click', this._hide);
967
- }.bind(this)
968
- );
969
-
970
- // Execute all callbacks registered for the `destroy` event
971
- this._fire('destroy');
972
-
973
- // Keep an object of listener types mapped to callback functions
974
- this._listeners = {};
975
-
976
- return this
977
- };
978
-
979
- /**
980
- * Register a new callback for the given event type
981
- *
982
- * @param {string} type
983
- * @param {Function} handler
984
- */
985
- A11yDialog.prototype.on = function (type, handler) {
986
- if (typeof this._listeners[type] === 'undefined') {
987
- this._listeners[type] = [];
988
- }
989
-
990
- this._listeners[type].push(handler);
991
-
992
- return this
993
- };
994
-
995
- /**
996
- * Unregister an existing callback for the given event type
997
- *
998
- * @param {string} type
999
- * @param {Function} handler
1000
- */
1001
- A11yDialog.prototype.off = function (type, handler) {
1002
- var index = (this._listeners[type] || []).indexOf(handler);
1003
-
1004
- if (index > -1) {
1005
- this._listeners[type].splice(index, 1);
1006
- }
1007
-
1008
- return this
1009
- };
1010
-
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
+ }
1011
825
  /**
1012
- * Iterate over all registered handlers for given type and call them all with
1013
- * the dialog element as first argument, event as second argument (if any). Also
1014
- * dispatch a custom event on the DOM element itself to make it possible to
1015
- * react to the lifecycle of auto-instantiated dialogs.
1016
- *
1017
- * @access private
1018
- * @param {string} type
1019
- * @param {CustomEvent} event
826
+ * Find the first focusable element inside the given node if `forward` is truthy
827
+ * or the last focusable element otherwise.
1020
828
  */
1021
- A11yDialog.prototype._fire = function (type, event) {
1022
- var listeners = this._listeners[type] || [];
1023
- var domEvent = new CustomEvent(type, { detail: event });
1024
-
1025
- this.$el.dispatchEvent(domEvent);
1026
-
1027
- listeners.forEach(
1028
- function (listener) {
1029
- listener(this.$el, event);
1030
- }.bind(this)
1031
- );
1032
- };
1033
-
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
+ }
1034
891
  /**
1035
- * Private event handler used when listening to some specific key presses
1036
- * (namely ESCAPE and TAB)
1037
- *
1038
- * @access private
1039
- * @param {Event} event
892
+ * Determine if an element is hidden from the user.
1040
893
  */
1041
- A11yDialog.prototype._bindKeypress = function (event) {
1042
- // This is an escape hatch in case there are nested dialogs, so the keypresses
1043
- // are only reacted to for the most recent one
1044
- const focused = document.activeElement;
1045
- if (focused && focused.closest('[aria-modal="true"]') !== this.$el) return
1046
-
1047
- // If the dialog is shown and the ESCAPE key is being pressed, prevent any
1048
- // further effects from the ESCAPE key and hide the dialog, unless its role
1049
- // is 'alertdialog', which should be modal
1050
- if (
1051
- this.shown &&
1052
- event.key === ESCAPE_KEY &&
1053
- this.$el.getAttribute('role') !== 'alertdialog'
1054
- ) {
1055
- event.preventDefault();
1056
- this.hide(event);
1057
- }
1058
-
1059
- // If the dialog is shown and the TAB key is being pressed, make sure the
1060
- // focus stays trapped within the dialog element
1061
- if (this.shown && event.key === TAB_KEY) {
1062
- trapTabKey(this.$el, event);
1063
- }
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);
1064
904
  };
1065
-
1066
905
  /**
1067
- * Private event handler used when making sure the focus stays within the
1068
- * currently open dialog
1069
- *
1070
- * @access private
1071
- * @param {Event} event
906
+ * Determine if an element is focusable and has user-visible painted dimensions.
1072
907
  */
1073
- A11yDialog.prototype._maintainFocus = function (event) {
1074
- // If the dialog is shown and the focus is not within a dialog element (either
1075
- // this one or another one in case of nested dialogs) or within an element
1076
- // with the `data-a11y-dialog-focus-trap-ignore` attribute, move it back to
1077
- // its first focusable child.
1078
- // See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
1079
- if (
1080
- this.shown &&
1081
- !event.target.closest('[aria-modal="true"]') &&
1082
- !event.target.closest('[data-a11y-dialog-ignore-focus-trap]')
1083
- ) {
1084
- moveFocusToDialog(this.$el);
1085
- }
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);
1086
923
  };
1087
-
1088
- /**
1089
- * Convert a NodeList into an array
1090
- *
1091
- * @param {NodeList} collection
1092
- * @return {Array<Element>}
1093
- */
1094
- function toArray(collection) {
1095
- return Array.prototype.slice.call(collection)
1096
- }
1097
-
1098
- /**
1099
- * Query the DOM for nodes matching the given selector, scoped to context (or
1100
- * the whole document)
1101
- *
1102
- * @param {String} selector
1103
- * @param {Element} [context = document]
1104
- * @return {Array<Element>}
1105
- */
1106
- function $$(selector, context) {
1107
- return toArray((context || document).querySelectorAll(selector))
1108
- }
1109
-
1110
924
  /**
1111
- * Set the focus to the first element with `autofocus` with the element or the
1112
- * element itself
1113
- *
1114
- * @param {Element} node
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
+ * ```
1115
937
  */
1116
- function moveFocusToDialog(node) {
1117
- var focused = node.querySelector('[autofocus]') || node;
1118
-
1119
- focused.focus();
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]');
1120
948
  }
1121
-
1122
949
  /**
1123
- * Get the focusable children of the given element
1124
- *
1125
- * @param {Element} node
1126
- * @return {Array<Element>}
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/
1127
953
  */
1128
- function getFocusableChildren(node) {
1129
- return $$(focusableSelectors.join(','), node).filter(function (child) {
1130
- return !!(
1131
- child.offsetWidth ||
1132
- child.offsetHeight ||
1133
- child.getClientRects().length
1134
- )
1135
- })
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;
1136
965
  }
1137
-
1138
966
  /**
1139
967
  * Trap the focus inside the given element
1140
- *
1141
- * @param {Element} node
1142
- * @param {Event} event
1143
968
  */
1144
- function trapTabKey(node, event) {
1145
- var focusableChildren = getFocusableChildren(node);
1146
- var focusedItemIndex = focusableChildren.indexOf(document.activeElement);
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
+ }
1147
992
 
1148
- // If the SHIFT key is being pressed while tabbing (moving backwards) and
1149
- // the currently focused item is the first one, move the focus to the last
1150
- // focusable item from the dialog element
1151
- if (event.shiftKey && focusedItemIndex === 0) {
1152
- focusableChildren[focusableChildren.length - 1].focus();
1153
- event.preventDefault();
1154
- // If the SHIFT key is not being pressed (moving forwards) and the currently
1155
- // focused item is the last one, move the focus to the first focusable item
1156
- // from the dialog element
1157
- } else if (
1158
- !event.shiftKey &&
1159
- focusedItemIndex === focusableChildren.length - 1
1160
- ) {
1161
- focusableChildren[0].focus();
1162
- event.preventDefault();
1163
- }
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
+ }
1164
1183
  }
1165
1184
 
1166
1185
  function instantiateDialogs() {
1167
- $$('[data-a11y-dialog]').forEach(function (node) {
1168
- new A11yDialog(node);
1169
- });
1186
+ for (const el of document.querySelectorAll('[data-a11y-dialog]')) {
1187
+ new A11yDialog(el);
1188
+ }
1170
1189
  }
1171
-
1172
1190
  if (typeof document !== 'undefined') {
1173
- if (document.readyState === 'loading') {
1174
- document.addEventListener('DOMContentLoaded', instantiateDialogs);
1175
- } else {
1176
- if (window.requestAnimationFrame) {
1177
- window.requestAnimationFrame(instantiateDialogs);
1178
- } else {
1179
- window.setTimeout(instantiateDialogs, 16);
1191
+ if (document.readyState === 'loading') {
1192
+ document.addEventListener('DOMContentLoaded', instantiateDialogs);
1193
+ }
1194
+ else {
1195
+ instantiateDialogs();
1180
1196
  }
1181
- }
1182
1197
  }
1183
1198
 
1184
1199
  var CONTAINER_ID = "__authsignal-popup-container";
@@ -1190,16 +1205,16 @@ var DEFAULT_WIDTH = "385px";
1190
1205
  var INITIAL_HEIGHT = "384px";
1191
1206
  var PopupHandler = /** @class */ (function () {
1192
1207
  function PopupHandler(_a) {
1193
- var width = _a.width;
1208
+ var width = _a.width, isClosable = _a.isClosable;
1194
1209
  this.popup = null;
1195
1210
  if (document.querySelector("#".concat(CONTAINER_ID))) {
1196
1211
  throw new Error("Multiple instances of Authsignal popup is not supported.");
1197
1212
  }
1198
- this.create({ width: width });
1213
+ this.create({ width: width, isClosable: isClosable });
1199
1214
  }
1200
1215
  PopupHandler.prototype.create = function (_a) {
1201
1216
  var _this = this;
1202
- 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;
1203
1218
  var isWidthValidCSSValue = CSS.supports("width", width);
1204
1219
  var popupWidth = width;
1205
1220
  if (!isWidthValidCSSValue) {
@@ -1210,10 +1225,15 @@ var PopupHandler = /** @class */ (function () {
1210
1225
  var container = document.createElement("div");
1211
1226
  container.setAttribute("id", CONTAINER_ID);
1212
1227
  container.setAttribute("aria-hidden", "true");
1228
+ if (!isClosable) {
1229
+ container.setAttribute("role", "alertdialog");
1230
+ }
1213
1231
  // Create dialog overlay
1214
1232
  var overlay = document.createElement("div");
1215
1233
  overlay.setAttribute("id", OVERLAY_ID);
1216
- overlay.setAttribute("data-a11y-dialog-hide", "true");
1234
+ if (isClosable) {
1235
+ overlay.setAttribute("data-a11y-dialog-hide", "true");
1236
+ }
1217
1237
  // Create dialog content
1218
1238
  var content = document.createElement("div");
1219
1239
  content.setAttribute("id", CONTENT_ID);
@@ -1267,11 +1287,11 @@ var PopupHandler = /** @class */ (function () {
1267
1287
  }
1268
1288
  this.popup.hide();
1269
1289
  };
1270
- PopupHandler.prototype.on = function (event, handler) {
1290
+ PopupHandler.prototype.on = function (event, listener) {
1271
1291
  if (!this.popup) {
1272
1292
  throw new Error("Popup is not initialized");
1273
1293
  }
1274
- this.popup.on(event, handler);
1294
+ this.popup.on(event, listener);
1275
1295
  };
1276
1296
  return PopupHandler;
1277
1297
  }());
@@ -1366,7 +1386,7 @@ var Authsignal = /** @class */ (function () {
1366
1386
  Authsignal.prototype.launchWithPopup = function (url, options) {
1367
1387
  var _this = this;
1368
1388
  var popupOptions = options.popupOptions;
1369
- 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 });
1370
1390
  var popupUrl = "".concat(url, "&mode=popup");
1371
1391
  popupHandler.show({ url: popupUrl });
1372
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 i(){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 o=[];for(let t=0;t<256;++t)o.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||i)();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(o[t[e+0]]+o[t[e+1]]+o[t[e+2]]+o[t[e+3]]+"-"+o[t[e+4]]+o[t[e+5]]+"-"+o[t[e+6]]+o[t[e+7]]+"-"+o[t[e+8]]+o[t[e+9]]+"-"+o[t[e+10]]+o[t[e+11]]+o[t[e+12]]+o[t[e+13]]+o[t[e+14]]+o[t[e+15]]).toLowerCase()}(a)}var s=function(t){var e=t.name,n=t.value,i=t.expire,o=t.domain,r=t.secure,a=i===1/0?" expires=Fri, 31 Dec 9999 23:59:59 GMT":"; max-age="+i;document.cookie=encodeURIComponent(e)+"="+n+"; path=/;"+a+(o?"; domain="+o:"")+(r?"; secure":"")};function c(t,e){var n={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(i=Object.getOwnPropertySymbols(t);o<i.length;o++)e.indexOf(i[o])<0&&Object.prototype.propertyIsEnumerable.call(t,i[o])&&(n[i[o]]=t[i[o]])}return n}function u(t,e,n,i){return new(n||(n=Promise))((function(o,r){function a(t){try{c(i.next(t))}catch(t){r(t)}}function s(t){try{c(i.throw(t))}catch(t){r(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(a,s)}c((i=i.apply(t,e||[])).next())}))}function l(t,e){var n,i,o,r,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[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,i&&(o=2&r[0]?i.return:r[0]?i.throw||((o=i.return)&&o.call(i),0):i.next)&&!(o=o.call(i,r[1])).done)return o;switch(i=0,o&&(r=[2&r[0],o.value]),r[0]){case 0:case 1:o=r;break;case 4:return a.label++,{value:r[1],done:!1};case 5:a.label++,i=r[1],r=[0];continue;case 7:r=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==r[0]&&2!==r[0])){a=0;continue}if(3===r[0]&&(!o||r[1]>o[0]&&r[1]<o[3])){a.label=r[1];break}if(6===r[0]&&a.label<o[1]){a.label=o[1],o=r;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(r);break}o[2]&&a.ops.pop(),a.trys.pop();continue}r=e.call(t,a)}catch(t){r=[6,t],i=0}finally{n=o=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,i=e.padEnd(e.length+n,"="),o=atob(i),r=new ArrayBuffer(o.length),a=new Uint8Array(r);for(let t=0;t<o.length;t++)a[t]=o.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:i}){super(t,{cause:n}),this.name=i??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 i;n.signal=y.createNewAbortSignal();try{i=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(!i)throw new Error("Registration was not completed");const{id:o,rawId:r,response:a,type:s}=i;let c,u,l,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&&(l=d(t))}catch(t){_("getPublicKey()",t)}if("function"==typeof a.getAuthenticatorData)try{b=d(a.getAuthenticatorData())}catch(t){_("getAuthenticatorData()",t)}return{id:o,rawId:d(r),response:{attestationObject:d(a.attestationObject),clientDataJSON:d(a.clientDataJSON),transports:c,publicKeyAlgorithm:u,publicKey:l,authenticatorData:b},type:s,clientExtensionResults:i.getClientExtensionResults(),authenticatorAttachment:g(i.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 i={...t,challenge:h(t.challenge),allowCredentials:n},o={};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');o.mediation="conditional",i.allowCredentials=[]}let r;o.publicKey=i,o.signal=y.createNewAbortSignal();try{r=await navigator.credentials.get(o)}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:o})}if(!r)throw new Error("Authentication was not completed");const{id:a,rawId:s,response:c,type:u}=r;let l;var b;return c.userHandle&&(b=c.userHandle,l=new TextDecoder("utf-8").decode(b)),{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: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,i=t.authenticatorAttachment;return u(this,void 0,void 0,(function(){var t;return l(this,(function(o){switch(o.label){case 0:return t=Boolean(i)?{username:n,authenticatorAttachment:i}:{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,o.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}(),R=function(){function t(t){var e=t.baseUrl,n=t.tenantId;this.passkeyLocalStorageKey="as_passkey_credential_id",this.api=new A({baseUrl:e,tenantId:n})}return t.prototype.signUp=function(t){var e=t.userName,n=t.token,i=t.authenticatorAttachment,o=void 0===i?"platform":i;return u(this,void 0,void 0,(function(){var t,i,r;return l(this,(function(a){switch(a.label){case 0:return[4,this.api.registrationOptions({userName:e,token:n,authenticatorAttachment:o})];case 1:return[4,v((t=a.sent()).options)];case 2:return i=a.sent(),[4,this.api.addAuthenticator({challengeId:t.challengeId,registrationCredential:i,token:n})];case 3:return(null==(r=a.sent())?void 0:r.isVerified)&&this.storeCredentialAgainstDevice(i),[2,null==r?void 0:r.accessToken]}}))}))},t.prototype.signIn=function(t){return u(this,void 0,void 0,(function(){var e,n,i;return l(this,(function(o){switch(o.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=o.sent()).options,null==t?void 0:t.autofill)];case 2:return n=o.sent(),[4,this.api.verify({challengeId:e.challengeId,authenticationCredential:n,token:null==t?void 0:t.token})];case 3:return(null==(i=o.sent())?void 0:i.isVerified)&&this.storeCredentialAgainstDevice(n),[2,null==i?void 0:i.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,i=void 0===n?400:n,o=t.height,r=function(t){var e=t.url,n=t.width,i=t.height,o=t.win;if(!o.top)return null;var r=o.top.outerHeight/2+o.top.screenY-i/2,a=o.top.outerWidth/2+o.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(i,", top=").concat(r,", left=").concat(a))}({url:e,width:i,height:void 0===o?500:o,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 k(t,e){return n=(e||document).querySelectorAll(t),Array.prototype.slice.call(n);var n}function C(t){(t.querySelector("[autofocus]")||t).focus()}function P(){k("[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=k('[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=k("[data-a11y-dialog-hide]",this.$el).filter((function(e){return e.closest('[aria-modal="true"], [data-a11y-dialog]')===t})).concat(k('[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,C(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]||[],i=new CustomEvent(t,{detail:e});this.$el.dispatchEvent(i),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 k(I.join(","),t).filter((function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}))}(t),i=n.indexOf(document.activeElement);e.shiftKey&&0===i?(n[n.length-1].focus(),e.preventDefault()):e.shiftKey||i!==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]")||C(this.$el)},"undefined"!=typeof document&&("loading"===document.readyState?document.addEventListener("DOMContentLoaded",P):window.requestAnimationFrame?window.requestAnimationFrame(P):window.setTimeout(P,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,i=void 0===n?L:n,o=i;CSS.supports("width",i)||(console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead."),o=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(o,";\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 i=document.createElement("iframe");i.setAttribute("id",D),i.setAttribute("name","authsignal"),i.setAttribute("title","Authsignal multi-factor authentication"),i.setAttribute("src",n),i.setAttribute("frameborder","0"),i.setAttribute("allow","publickey-credentials-get *; publickey-credentials-create *; clipboard-write");var o=document.querySelector("#".concat(U));o&&o.appendChild(i),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,i=void 0===n?"__as_aid":n,o=t.baseUrl,r=void 0===o?"https://api.authsignal.com/v1":o,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=i,!c)throw new Error("tenantId is required");this.passkey=new R({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(H,"&session_id=").concat(e):"https://h.online-metrix.net/fp/tags.js?org_id=".concat(H,"&session_id=").concat(e),i=document.createElement("script");i.src=n,i.async=!1,i.id="as_adv_profile",document.head.appendChild(i);var o=document.createElement("noscript");o.setAttribute("id","as_adv_profile_pixel"),o.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;"),o&&(o.appendChild(r),document.body.prepend(o))},e.prototype.launchWithRedirect=function(t){window.location.href=t},e.prototype.launchWithPopup=function(e,n){var i=this,o=n.popupOptions,r=new K({width:null==o?void 0:o.width}),a="".concat(e,"&mode=popup");return r.show({url:a}),new Promise((function(e){r.on("hide",(function(){e({token:i._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&&(i._token=n.token,r.close())}),!1)}))},e.prototype.launchWithWindow=function(e,n){var i=this,o=n.windowOptions,r=new O,a="".concat(e,"&mode=popup");return r.show({url:a,width:null==o?void 0:o.width,height:null==o?void 0:o.height}),new Promise((function(e){window.addEventListener("message",(function(n){var o=null;try{o=JSON.parse(n.data)}catch(t){}(null==o?void 0:o.event)===t.AuthsignalWindowMessage.AUTHSIGNAL_CLOSE_POPUP&&(i._token=o.token,r.close(),e({token:i._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/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.4.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": "^7.5.2",
33
+ "a11y-dialog": "8.0.4",
34
34
  "uuid": "^9.0.0"
35
35
  },
36
36
  "devDependencies": {