@authsignal/browser 0.3.4 → 0.3.6

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/index.js CHANGED
@@ -90,1661 +90,1664 @@ var AuthsignalWindowMessage;
90
90
  AuthsignalWindowMessage["AUTHSIGNAL_CLOSE_POPUP"] = "AUTHSIGNAL_CLOSE_POPUP";
91
91
  })(AuthsignalWindowMessage || (AuthsignalWindowMessage = {}));
92
92
 
93
- var DEFAULT_WIDTH$1 = 400;
94
- var DEFAULT_HEIGHT = 500;
95
- var WindowHandler = /** @class */ (function () {
96
- function WindowHandler() {
97
- this.windowRef = null;
98
- }
99
- WindowHandler.prototype.show = function (_a) {
100
- var url = _a.url, _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH$1 : _b, _c = _a.height, height = _c === void 0 ? DEFAULT_HEIGHT : _c;
101
- var windowRef = openWindow({ url: url, width: width, height: height, win: window });
102
- if (!windowRef) {
103
- throw new Error("Window is not initialized");
104
- }
105
- this.windowRef = windowRef;
106
- return windowRef;
107
- };
108
- WindowHandler.prototype.close = function () {
109
- if (!this.windowRef) {
110
- throw new Error("Window is not initialized");
111
- }
112
- this.windowRef.close();
113
- };
114
- return WindowHandler;
115
- }());
116
- function openWindow(_a) {
117
- var url = _a.url, width = _a.width, height = _a.height, win = _a.win;
118
- if (!win.top) {
119
- return null;
120
- }
121
- var y = win.top.outerHeight / 2 + win.top.screenY - height / 2;
122
- var x = win.top.outerWidth / 2 + win.top.screenX - width / 2;
123
- 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));
93
+ /******************************************************************************
94
+ Copyright (c) Microsoft Corporation.
95
+
96
+ Permission to use, copy, modify, and/or distribute this software for any
97
+ purpose with or without fee is hereby granted.
98
+
99
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
100
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
101
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
102
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
103
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
104
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
105
+ PERFORMANCE OF THIS SOFTWARE.
106
+ ***************************************************************************** */
107
+
108
+ function __rest(s, e) {
109
+ var t = {};
110
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
111
+ t[p] = s[p];
112
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
113
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
114
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
115
+ t[p[i]] = s[p[i]];
116
+ }
117
+ return t;
118
+ }
119
+
120
+ function __awaiter(thisArg, _arguments, P, generator) {
121
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
122
+ return new (P || (P = Promise))(function (resolve, reject) {
123
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
124
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
125
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
126
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
127
+ });
128
+ }
129
+
130
+ function __generator(thisArg, body) {
131
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
132
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
133
+ function verb(n) { return function (v) { return step([n, v]); }; }
134
+ function step(op) {
135
+ if (f) throw new TypeError("Generator is already executing.");
136
+ while (_) try {
137
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
138
+ if (y = 0, t) op = [op[0] & 2, t.value];
139
+ switch (op[0]) {
140
+ case 0: case 1: t = op; break;
141
+ case 4: _.label++; return { value: op[1], done: false };
142
+ case 5: _.label++; y = op[1]; op = [0]; continue;
143
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
144
+ default:
145
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
146
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
147
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
148
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
149
+ if (t[2]) _.ops.pop();
150
+ _.trys.pop(); continue;
151
+ }
152
+ op = body.call(thisArg, _);
153
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
154
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
155
+ }
124
156
  }
125
157
 
126
- var focusableSelectors = [
127
- 'a[href]:not([tabindex^="-"])',
128
- 'area[href]:not([tabindex^="-"])',
129
- 'input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])',
130
- 'input[type="radio"]:not([disabled]):not([tabindex^="-"])',
131
- 'select:not([disabled]):not([tabindex^="-"])',
132
- 'textarea:not([disabled]):not([tabindex^="-"])',
133
- 'button:not([disabled]):not([tabindex^="-"])',
134
- 'iframe:not([tabindex^="-"])',
135
- 'audio[controls]:not([tabindex^="-"])',
136
- 'video[controls]:not([tabindex^="-"])',
137
- '[contenteditable]:not([tabindex^="-"])',
138
- '[tabindex]:not([tabindex^="-"])',
139
- ];
140
-
141
- var TAB_KEY = 'Tab';
142
- var ESCAPE_KEY = 'Escape';
143
-
144
- /**
145
- * Define the constructor to instantiate a dialog
146
- *
147
- * @constructor
148
- * @param {Element} element
149
- */
150
- function A11yDialog(element) {
151
- // Prebind the functions that will be bound in addEventListener and
152
- // removeEventListener to avoid losing references
153
- this._show = this.show.bind(this);
154
- this._hide = this.hide.bind(this);
155
- this._maintainFocus = this._maintainFocus.bind(this);
156
- this._bindKeypress = this._bindKeypress.bind(this);
157
-
158
- this.$el = element;
159
- this.shown = false;
160
- this._id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
161
- this._previouslyFocused = null;
162
- this._listeners = {};
163
-
164
- // Initialise everything needed for the dialog to work properly
165
- this.create();
158
+ /* [@simplewebauthn/browser@8.2.1] */
159
+ function utf8StringToBuffer(value) {
160
+ return new TextEncoder().encode(value);
166
161
  }
167
162
 
168
- /**
169
- * Set up everything necessary for the dialog to be functioning
170
- *
171
- * @param {(NodeList | Element | string)} targets
172
- * @return {this}
173
- */
174
- A11yDialog.prototype.create = function () {
175
- this.$el.setAttribute('aria-hidden', true);
176
- this.$el.setAttribute('aria-modal', true);
177
- this.$el.setAttribute('tabindex', -1);
178
-
179
- if (!this.$el.hasAttribute('role')) {
180
- this.$el.setAttribute('role', 'dialog');
181
- }
182
-
183
- // Keep a collection of dialog openers, each of which will be bound a click
184
- // event listener to open the dialog
185
- this._openers = $$('[data-a11y-dialog-show="' + this._id + '"]');
186
- this._openers.forEach(
187
- function (opener) {
188
- opener.addEventListener('click', this._show);
189
- }.bind(this)
190
- );
191
-
192
- // Keep a collection of dialog closers, each of which will be bound a click
193
- // event listener to close the dialog
194
- const $el = this.$el;
195
-
196
- this._closers = $$('[data-a11y-dialog-hide]', this.$el)
197
- // This filter is necessary in case there are nested dialogs, so that
198
- // only closers from the current dialog are retrieved and effective
199
- .filter(function (closer) {
200
- // Testing for `[aria-modal="true"]` is not enough since this attribute
201
- // and the collect of closers is done at instantation time, when nested
202
- // dialogs might not have yet been instantiated. Note that if the dialogs
203
- // are manually instantiated, this could still fail because none of these
204
- // selectors would match; this would cause closers to close all parent
205
- // dialogs instead of just the current one
206
- return closer.closest('[aria-modal="true"], [data-a11y-dialog]') === $el
207
- })
208
- .concat($$('[data-a11y-dialog-hide="' + this._id + '"]'));
209
-
210
- this._closers.forEach(
211
- function (closer) {
212
- closer.addEventListener('click', this._hide);
213
- }.bind(this)
214
- );
163
+ function bufferToBase64URLString(buffer) {
164
+ const bytes = new Uint8Array(buffer);
165
+ let str = '';
166
+ for (const charCode of bytes) {
167
+ str += String.fromCharCode(charCode);
168
+ }
169
+ const base64String = btoa(str);
170
+ return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
171
+ }
215
172
 
216
- // Execute all callbacks registered for the `create` event
217
- this._fire('create');
173
+ function base64URLStringToBuffer(base64URLString) {
174
+ const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
175
+ const padLength = (4 - (base64.length % 4)) % 4;
176
+ const padded = base64.padEnd(base64.length + padLength, '=');
177
+ const binary = atob(padded);
178
+ const buffer = new ArrayBuffer(binary.length);
179
+ const bytes = new Uint8Array(buffer);
180
+ for (let i = 0; i < binary.length; i++) {
181
+ bytes[i] = binary.charCodeAt(i);
182
+ }
183
+ return buffer;
184
+ }
218
185
 
219
- return this
220
- };
186
+ function browserSupportsWebAuthn() {
187
+ return (window?.PublicKeyCredential !== undefined &&
188
+ typeof window.PublicKeyCredential === 'function');
189
+ }
221
190
 
222
- /**
223
- * Show the dialog element, disable all the targets (siblings), trap the
224
- * current focus within it, listen for some specific key presses and fire all
225
- * registered callbacks for `show` event
226
- *
227
- * @param {CustomEvent} event
228
- * @return {this}
229
- */
230
- A11yDialog.prototype.show = function (event) {
231
- // If the dialog is already open, abort
232
- if (this.shown) {
233
- return this
234
- }
191
+ function toPublicKeyCredentialDescriptor(descriptor) {
192
+ const { id } = descriptor;
193
+ return {
194
+ ...descriptor,
195
+ id: base64URLStringToBuffer(id),
196
+ transports: descriptor.transports,
197
+ };
198
+ }
235
199
 
236
- // Keep a reference to the currently focused element to be able to restore
237
- // it later
238
- this._previouslyFocused = document.activeElement;
239
- this.$el.removeAttribute('aria-hidden');
240
- this.shown = true;
200
+ function isValidDomain(hostname) {
201
+ return (hostname === 'localhost' ||
202
+ /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname));
203
+ }
241
204
 
242
- // Set the focus to the dialog element
243
- moveFocusToDialog(this.$el);
205
+ class WebAuthnError extends Error {
206
+ constructor({ message, code, cause, name, }) {
207
+ super(message, { cause });
208
+ this.name = name ?? cause.name;
209
+ this.code = code;
210
+ }
211
+ }
244
212
 
245
- // Bind a focus event listener to the body element to make sure the focus
246
- // stays trapped inside the dialog while open, and start listening for some
247
- // specific key presses (TAB and ESC)
248
- document.body.addEventListener('focus', this._maintainFocus, true);
249
- document.addEventListener('keydown', this._bindKeypress);
250
-
251
- // Execute all callbacks registered for the `show` event
252
- this._fire('show', event);
253
-
254
- return this
255
- };
256
-
257
- /**
258
- * Hide the dialog element, enable all the targets (siblings), restore the
259
- * focus to the previously active element, stop listening for some specific
260
- * key presses and fire all registered callbacks for `hide` event
261
- *
262
- * @param {CustomEvent} event
263
- * @return {this}
264
- */
265
- A11yDialog.prototype.hide = function (event) {
266
- // If the dialog is already closed, abort
267
- if (!this.shown) {
268
- return this
269
- }
270
-
271
- this.shown = false;
272
- this.$el.setAttribute('aria-hidden', 'true');
213
+ function identifyRegistrationError({ error, options, }) {
214
+ const { publicKey } = options;
215
+ if (!publicKey) {
216
+ throw Error('options was missing required publicKey property');
217
+ }
218
+ if (error.name === 'AbortError') {
219
+ if (options.signal instanceof AbortSignal) {
220
+ return new WebAuthnError({
221
+ message: 'Registration ceremony was sent an abort signal',
222
+ code: 'ERROR_CEREMONY_ABORTED',
223
+ cause: error,
224
+ });
225
+ }
226
+ }
227
+ else if (error.name === 'ConstraintError') {
228
+ if (publicKey.authenticatorSelection?.requireResidentKey === true) {
229
+ return new WebAuthnError({
230
+ message: 'Discoverable credentials were required but no available authenticator supported it',
231
+ code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',
232
+ cause: error,
233
+ });
234
+ }
235
+ else if (publicKey.authenticatorSelection?.userVerification === 'required') {
236
+ return new WebAuthnError({
237
+ message: 'User verification was required but no available authenticator supported it',
238
+ code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',
239
+ cause: error,
240
+ });
241
+ }
242
+ }
243
+ else if (error.name === 'InvalidStateError') {
244
+ return new WebAuthnError({
245
+ message: 'The authenticator was previously registered',
246
+ code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
247
+ cause: error,
248
+ });
249
+ }
250
+ else if (error.name === 'NotAllowedError') {
251
+ return new WebAuthnError({
252
+ message: error.message,
253
+ code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
254
+ cause: error,
255
+ });
256
+ }
257
+ else if (error.name === 'NotSupportedError') {
258
+ const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === 'public-key');
259
+ if (validPubKeyCredParams.length === 0) {
260
+ return new WebAuthnError({
261
+ message: 'No entry in pubKeyCredParams was of type "public-key"',
262
+ code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',
263
+ cause: error,
264
+ });
265
+ }
266
+ return new WebAuthnError({
267
+ message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',
268
+ code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',
269
+ cause: error,
270
+ });
271
+ }
272
+ else if (error.name === 'SecurityError') {
273
+ const effectiveDomain = window.location.hostname;
274
+ if (!isValidDomain(effectiveDomain)) {
275
+ return new WebAuthnError({
276
+ message: `${window.location.hostname} is an invalid domain`,
277
+ code: 'ERROR_INVALID_DOMAIN',
278
+ cause: error,
279
+ });
280
+ }
281
+ else if (publicKey.rp.id !== effectiveDomain) {
282
+ return new WebAuthnError({
283
+ message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
284
+ code: 'ERROR_INVALID_RP_ID',
285
+ cause: error,
286
+ });
287
+ }
288
+ }
289
+ else if (error.name === 'TypeError') {
290
+ if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
291
+ return new WebAuthnError({
292
+ message: 'User ID was not between 1 and 64 characters',
293
+ code: 'ERROR_INVALID_USER_ID_LENGTH',
294
+ cause: error,
295
+ });
296
+ }
297
+ }
298
+ else if (error.name === 'UnknownError') {
299
+ return new WebAuthnError({
300
+ message: 'The authenticator was unable to process the specified options, or could not create a new credential',
301
+ code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
302
+ cause: error,
303
+ });
304
+ }
305
+ return error;
306
+ }
273
307
 
274
- // If there was a focused element before the dialog was opened (and it has a
275
- // `focus` method), restore the focus back to it
276
- // See: https://github.com/KittyGiraudel/a11y-dialog/issues/108
277
- if (this._previouslyFocused && this._previouslyFocused.focus) {
278
- this._previouslyFocused.focus();
279
- }
308
+ class WebAuthnAbortService {
309
+ createNewAbortSignal() {
310
+ if (this.controller) {
311
+ const abortError = new Error('Cancelling existing WebAuthn API call for new one');
312
+ abortError.name = 'AbortError';
313
+ this.controller.abort(abortError);
314
+ }
315
+ const newController = new AbortController();
316
+ this.controller = newController;
317
+ return newController.signal;
318
+ }
319
+ }
320
+ const webauthnAbortService = new WebAuthnAbortService();
280
321
 
281
- // Remove the focus event listener to the body element and stop listening
282
- // for specific key presses
283
- document.body.removeEventListener('focus', this._maintainFocus, true);
284
- document.removeEventListener('keydown', this._bindKeypress);
322
+ const attachments = ['cross-platform', 'platform'];
323
+ function toAuthenticatorAttachment(attachment) {
324
+ if (!attachment) {
325
+ return;
326
+ }
327
+ if (attachments.indexOf(attachment) < 0) {
328
+ return;
329
+ }
330
+ return attachment;
331
+ }
285
332
 
286
- // Execute all callbacks registered for the `hide` event
287
- this._fire('hide', event);
333
+ async function startRegistration(creationOptionsJSON) {
334
+ if (!browserSupportsWebAuthn()) {
335
+ throw new Error('WebAuthn is not supported in this browser');
336
+ }
337
+ const publicKey = {
338
+ ...creationOptionsJSON,
339
+ challenge: base64URLStringToBuffer(creationOptionsJSON.challenge),
340
+ user: {
341
+ ...creationOptionsJSON.user,
342
+ id: utf8StringToBuffer(creationOptionsJSON.user.id),
343
+ },
344
+ excludeCredentials: creationOptionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor),
345
+ };
346
+ const options = { publicKey };
347
+ options.signal = webauthnAbortService.createNewAbortSignal();
348
+ let credential;
349
+ try {
350
+ credential = (await navigator.credentials.create(options));
351
+ }
352
+ catch (err) {
353
+ throw identifyRegistrationError({ error: err, options });
354
+ }
355
+ if (!credential) {
356
+ throw new Error('Registration was not completed');
357
+ }
358
+ const { id, rawId, response, type } = credential;
359
+ let transports = undefined;
360
+ if (typeof response.getTransports === 'function') {
361
+ transports = response.getTransports();
362
+ }
363
+ let responsePublicKeyAlgorithm = undefined;
364
+ if (typeof response.getPublicKeyAlgorithm === 'function') {
365
+ try {
366
+ responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
367
+ }
368
+ catch (error) {
369
+ warnOnBrokenImplementation('getPublicKeyAlgorithm()', error);
370
+ }
371
+ }
372
+ let responsePublicKey = undefined;
373
+ if (typeof response.getPublicKey === 'function') {
374
+ try {
375
+ const _publicKey = response.getPublicKey();
376
+ if (_publicKey !== null) {
377
+ responsePublicKey = bufferToBase64URLString(_publicKey);
378
+ }
379
+ }
380
+ catch (error) {
381
+ warnOnBrokenImplementation('getPublicKey()', error);
382
+ }
383
+ }
384
+ let responseAuthenticatorData;
385
+ if (typeof response.getAuthenticatorData === 'function') {
386
+ try {
387
+ responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
388
+ }
389
+ catch (error) {
390
+ warnOnBrokenImplementation('getAuthenticatorData()', error);
391
+ }
392
+ }
393
+ return {
394
+ id,
395
+ rawId: bufferToBase64URLString(rawId),
396
+ response: {
397
+ attestationObject: bufferToBase64URLString(response.attestationObject),
398
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
399
+ transports,
400
+ publicKeyAlgorithm: responsePublicKeyAlgorithm,
401
+ publicKey: responsePublicKey,
402
+ authenticatorData: responseAuthenticatorData,
403
+ },
404
+ type,
405
+ clientExtensionResults: credential.getClientExtensionResults(),
406
+ authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
407
+ };
408
+ }
409
+ function warnOnBrokenImplementation(methodName, cause) {
410
+ console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.\n`, cause);
411
+ }
288
412
 
289
- return this
290
- };
413
+ function bufferToUTF8String(value) {
414
+ return new TextDecoder('utf-8').decode(value);
415
+ }
291
416
 
292
- /**
293
- * Destroy the current instance (after making sure the dialog has been hidden)
294
- * and remove all associated listeners from dialog openers and closers
295
- *
296
- * @return {this}
297
- */
298
- A11yDialog.prototype.destroy = function () {
299
- // Hide the dialog to avoid destroying an open instance
300
- this.hide();
417
+ function browserSupportsWebAuthnAutofill() {
418
+ const globalPublicKeyCredential = window
419
+ .PublicKeyCredential;
420
+ if (globalPublicKeyCredential.isConditionalMediationAvailable === undefined) {
421
+ return new Promise((resolve) => resolve(false));
422
+ }
423
+ return globalPublicKeyCredential.isConditionalMediationAvailable();
424
+ }
301
425
 
302
- // Remove the click event listener from all dialog openers
303
- this._openers.forEach(
304
- function (opener) {
305
- opener.removeEventListener('click', this._show);
306
- }.bind(this)
307
- );
308
-
309
- // Remove the click event listener from all dialog closers
310
- this._closers.forEach(
311
- function (closer) {
312
- closer.removeEventListener('click', this._hide);
313
- }.bind(this)
314
- );
315
-
316
- // Execute all callbacks registered for the `destroy` event
317
- this._fire('destroy');
426
+ function identifyAuthenticationError({ error, options, }) {
427
+ const { publicKey } = options;
428
+ if (!publicKey) {
429
+ throw Error('options was missing required publicKey property');
430
+ }
431
+ if (error.name === 'AbortError') {
432
+ if (options.signal instanceof AbortSignal) {
433
+ return new WebAuthnError({
434
+ message: 'Authentication ceremony was sent an abort signal',
435
+ code: 'ERROR_CEREMONY_ABORTED',
436
+ cause: error,
437
+ });
438
+ }
439
+ }
440
+ else if (error.name === 'NotAllowedError') {
441
+ return new WebAuthnError({
442
+ message: error.message,
443
+ code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
444
+ cause: error,
445
+ });
446
+ }
447
+ else if (error.name === 'SecurityError') {
448
+ const effectiveDomain = window.location.hostname;
449
+ if (!isValidDomain(effectiveDomain)) {
450
+ return new WebAuthnError({
451
+ message: `${window.location.hostname} is an invalid domain`,
452
+ code: 'ERROR_INVALID_DOMAIN',
453
+ cause: error,
454
+ });
455
+ }
456
+ else if (publicKey.rpId !== effectiveDomain) {
457
+ return new WebAuthnError({
458
+ message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
459
+ code: 'ERROR_INVALID_RP_ID',
460
+ cause: error,
461
+ });
462
+ }
463
+ }
464
+ else if (error.name === 'UnknownError') {
465
+ return new WebAuthnError({
466
+ message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature',
467
+ code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
468
+ cause: error,
469
+ });
470
+ }
471
+ return error;
472
+ }
318
473
 
319
- // Keep an object of listener types mapped to callback functions
320
- this._listeners = {};
474
+ async function startAuthentication(requestOptionsJSON, useBrowserAutofill = false) {
475
+ if (!browserSupportsWebAuthn()) {
476
+ throw new Error('WebAuthn is not supported in this browser');
477
+ }
478
+ let allowCredentials;
479
+ if (requestOptionsJSON.allowCredentials?.length !== 0) {
480
+ allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
481
+ }
482
+ const publicKey = {
483
+ ...requestOptionsJSON,
484
+ challenge: base64URLStringToBuffer(requestOptionsJSON.challenge),
485
+ allowCredentials,
486
+ };
487
+ const options = {};
488
+ if (useBrowserAutofill) {
489
+ if (!(await browserSupportsWebAuthnAutofill())) {
490
+ throw Error('Browser does not support WebAuthn autofill');
491
+ }
492
+ const eligibleInputs = document.querySelectorAll('input[autocomplete*=\'webauthn\']');
493
+ if (eligibleInputs.length < 1) {
494
+ throw Error('No <input> with `"webauthn"` in its `autocomplete` attribute was detected');
495
+ }
496
+ options.mediation = 'conditional';
497
+ publicKey.allowCredentials = [];
498
+ }
499
+ options.publicKey = publicKey;
500
+ options.signal = webauthnAbortService.createNewAbortSignal();
501
+ let credential;
502
+ try {
503
+ credential = (await navigator.credentials.get(options));
504
+ }
505
+ catch (err) {
506
+ throw identifyAuthenticationError({ error: err, options });
507
+ }
508
+ if (!credential) {
509
+ throw new Error('Authentication was not completed');
510
+ }
511
+ const { id, rawId, response, type } = credential;
512
+ let userHandle = undefined;
513
+ if (response.userHandle) {
514
+ userHandle = bufferToUTF8String(response.userHandle);
515
+ }
516
+ return {
517
+ id,
518
+ rawId: bufferToBase64URLString(rawId),
519
+ response: {
520
+ authenticatorData: bufferToBase64URLString(response.authenticatorData),
521
+ clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
522
+ signature: bufferToBase64URLString(response.signature),
523
+ userHandle,
524
+ },
525
+ type,
526
+ clientExtensionResults: credential.getClientExtensionResults(),
527
+ authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
528
+ };
529
+ }
321
530
 
322
- return this
323
- };
531
+ // eslint-lint-disable-next-line @typescript-eslint/naming-convention
532
+ class HTTPError extends Error {
533
+ constructor(response, request, options) {
534
+ const code = (response.status || response.status === 0) ? response.status : '';
535
+ const title = response.statusText || '';
536
+ const status = `${code} ${title}`.trim();
537
+ const reason = status ? `status code ${status}` : 'an unknown error';
538
+ super(`Request failed with ${reason}`);
539
+ Object.defineProperty(this, "response", {
540
+ enumerable: true,
541
+ configurable: true,
542
+ writable: true,
543
+ value: void 0
544
+ });
545
+ Object.defineProperty(this, "request", {
546
+ enumerable: true,
547
+ configurable: true,
548
+ writable: true,
549
+ value: void 0
550
+ });
551
+ Object.defineProperty(this, "options", {
552
+ enumerable: true,
553
+ configurable: true,
554
+ writable: true,
555
+ value: void 0
556
+ });
557
+ this.name = 'HTTPError';
558
+ this.response = response;
559
+ this.request = request;
560
+ this.options = options;
561
+ }
562
+ }
324
563
 
325
- /**
326
- * Register a new callback for the given event type
327
- *
328
- * @param {string} type
329
- * @param {Function} handler
330
- */
331
- A11yDialog.prototype.on = function (type, handler) {
332
- if (typeof this._listeners[type] === 'undefined') {
333
- this._listeners[type] = [];
334
- }
564
+ class TimeoutError extends Error {
565
+ constructor(request) {
566
+ super('Request timed out');
567
+ Object.defineProperty(this, "request", {
568
+ enumerable: true,
569
+ configurable: true,
570
+ writable: true,
571
+ value: void 0
572
+ });
573
+ this.name = 'TimeoutError';
574
+ this.request = request;
575
+ }
576
+ }
335
577
 
336
- this._listeners[type].push(handler);
578
+ // eslint-disable-next-line @typescript-eslint/ban-types
579
+ const isObject = (value) => value !== null && typeof value === 'object';
337
580
 
338
- return this
581
+ const validateAndMerge = (...sources) => {
582
+ for (const source of sources) {
583
+ if ((!isObject(source) || Array.isArray(source)) && typeof source !== 'undefined') {
584
+ throw new TypeError('The `options` argument must be an object');
585
+ }
586
+ }
587
+ return deepMerge({}, ...sources);
339
588
  };
340
-
341
- /**
342
- * Unregister an existing callback for the given event type
343
- *
344
- * @param {string} type
345
- * @param {Function} handler
346
- */
347
- A11yDialog.prototype.off = function (type, handler) {
348
- var index = (this._listeners[type] || []).indexOf(handler);
349
-
350
- if (index > -1) {
351
- this._listeners[type].splice(index, 1);
352
- }
353
-
354
- return this
589
+ const mergeHeaders = (source1 = {}, source2 = {}) => {
590
+ const result = new globalThis.Headers(source1);
591
+ const isHeadersInstance = source2 instanceof globalThis.Headers;
592
+ const source = new globalThis.Headers(source2);
593
+ for (const [key, value] of source.entries()) {
594
+ if ((isHeadersInstance && value === 'undefined') || value === undefined) {
595
+ result.delete(key);
596
+ }
597
+ else {
598
+ result.set(key, value);
599
+ }
600
+ }
601
+ return result;
602
+ };
603
+ // TODO: Make this strongly-typed (no `any`).
604
+ const deepMerge = (...sources) => {
605
+ let returnValue = {};
606
+ let headers = {};
607
+ for (const source of sources) {
608
+ if (Array.isArray(source)) {
609
+ if (!Array.isArray(returnValue)) {
610
+ returnValue = [];
611
+ }
612
+ returnValue = [...returnValue, ...source];
613
+ }
614
+ else if (isObject(source)) {
615
+ for (let [key, value] of Object.entries(source)) {
616
+ if (isObject(value) && key in returnValue) {
617
+ value = deepMerge(returnValue[key], value);
618
+ }
619
+ returnValue = { ...returnValue, [key]: value };
620
+ }
621
+ if (isObject(source.headers)) {
622
+ headers = mergeHeaders(headers, source.headers);
623
+ returnValue.headers = headers;
624
+ }
625
+ }
626
+ }
627
+ return returnValue;
355
628
  };
356
629
 
357
- /**
358
- * Iterate over all registered handlers for given type and call them all with
359
- * the dialog element as first argument, event as second argument (if any). Also
360
- * dispatch a custom event on the DOM element itself to make it possible to
361
- * react to the lifecycle of auto-instantiated dialogs.
362
- *
363
- * @access private
364
- * @param {string} type
365
- * @param {CustomEvent} event
366
- */
367
- A11yDialog.prototype._fire = function (type, event) {
368
- var listeners = this._listeners[type] || [];
369
- var domEvent = new CustomEvent(type, { detail: event });
370
-
371
- this.$el.dispatchEvent(domEvent);
372
-
373
- listeners.forEach(
374
- function (listener) {
375
- listener(this.$el, event);
376
- }.bind(this)
377
- );
630
+ const supportsRequestStreams = (() => {
631
+ let duplexAccessed = false;
632
+ let hasContentType = false;
633
+ const supportsReadableStream = typeof globalThis.ReadableStream === 'function';
634
+ const supportsRequest = typeof globalThis.Request === 'function';
635
+ if (supportsReadableStream && supportsRequest) {
636
+ hasContentType = new globalThis.Request('https://a.com', {
637
+ body: new globalThis.ReadableStream(),
638
+ method: 'POST',
639
+ // @ts-expect-error - Types are outdated.
640
+ get duplex() {
641
+ duplexAccessed = true;
642
+ return 'half';
643
+ },
644
+ }).headers.has('Content-Type');
645
+ }
646
+ return duplexAccessed && !hasContentType;
647
+ })();
648
+ const supportsAbortController = typeof globalThis.AbortController === 'function';
649
+ const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
650
+ const supportsFormData = typeof globalThis.FormData === 'function';
651
+ const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'];
652
+ const responseTypes = {
653
+ json: 'application/json',
654
+ text: 'text/*',
655
+ formData: 'multipart/form-data',
656
+ arrayBuffer: '*/*',
657
+ blob: '*/*',
378
658
  };
659
+ // The maximum value of a 32bit int (see issue #117)
660
+ const maxSafeTimeout = 2147483647;
661
+ const stop = Symbol('stop');
379
662
 
380
- /**
381
- * Private event handler used when listening to some specific key presses
382
- * (namely ESCAPE and TAB)
383
- *
384
- * @access private
385
- * @param {Event} event
386
- */
387
- A11yDialog.prototype._bindKeypress = function (event) {
388
- // This is an escape hatch in case there are nested dialogs, so the keypresses
389
- // are only reacted to for the most recent one
390
- const focused = document.activeElement;
391
- if (focused && focused.closest('[aria-modal="true"]') !== this.$el) return
392
-
393
- // If the dialog is shown and the ESCAPE key is being pressed, prevent any
394
- // further effects from the ESCAPE key and hide the dialog, unless its role
395
- // is 'alertdialog', which should be modal
396
- if (
397
- this.shown &&
398
- event.key === ESCAPE_KEY &&
399
- this.$el.getAttribute('role') !== 'alertdialog'
400
- ) {
401
- event.preventDefault();
402
- this.hide(event);
403
- }
404
-
405
- // If the dialog is shown and the TAB key is being pressed, make sure the
406
- // focus stays trapped within the dialog element
407
- if (this.shown && event.key === TAB_KEY) {
408
- trapTabKey(this.$el, event);
409
- }
663
+ const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
664
+ const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace'];
665
+ const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
666
+ const retryAfterStatusCodes = [413, 429, 503];
667
+ const defaultRetryOptions = {
668
+ limit: 2,
669
+ methods: retryMethods,
670
+ statusCodes: retryStatusCodes,
671
+ afterStatusCodes: retryAfterStatusCodes,
672
+ maxRetryAfter: Number.POSITIVE_INFINITY,
673
+ backoffLimit: Number.POSITIVE_INFINITY,
410
674
  };
411
-
412
- /**
413
- * Private event handler used when making sure the focus stays within the
414
- * currently open dialog
415
- *
416
- * @access private
417
- * @param {Event} event
418
- */
419
- A11yDialog.prototype._maintainFocus = function (event) {
420
- // If the dialog is shown and the focus is not within a dialog element (either
421
- // this one or another one in case of nested dialogs) or within an element
422
- // with the `data-a11y-dialog-focus-trap-ignore` attribute, move it back to
423
- // its first focusable child.
424
- // See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
425
- if (
426
- this.shown &&
427
- !event.target.closest('[aria-modal="true"]') &&
428
- !event.target.closest('[data-a11y-dialog-ignore-focus-trap]')
429
- ) {
430
- moveFocusToDialog(this.$el);
431
- }
675
+ const normalizeRetryOptions = (retry = {}) => {
676
+ if (typeof retry === 'number') {
677
+ return {
678
+ ...defaultRetryOptions,
679
+ limit: retry,
680
+ };
681
+ }
682
+ if (retry.methods && !Array.isArray(retry.methods)) {
683
+ throw new Error('retry.methods must be an array');
684
+ }
685
+ if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
686
+ throw new Error('retry.statusCodes must be an array');
687
+ }
688
+ return {
689
+ ...defaultRetryOptions,
690
+ ...retry,
691
+ afterStatusCodes: retryAfterStatusCodes,
692
+ };
432
693
  };
433
694
 
434
- /**
435
- * Convert a NodeList into an array
436
- *
437
- * @param {NodeList} collection
438
- * @return {Array<Element>}
439
- */
440
- function toArray(collection) {
441
- return Array.prototype.slice.call(collection)
442
- }
443
-
444
- /**
445
- * Query the DOM for nodes matching the given selector, scoped to context (or
446
- * the whole document)
447
- *
448
- * @param {String} selector
449
- * @param {Element} [context = document]
450
- * @return {Array<Element>}
451
- */
452
- function $$(selector, context) {
453
- return toArray((context || document).querySelectorAll(selector))
454
- }
455
-
456
- /**
457
- * Set the focus to the first element with `autofocus` with the element or the
458
- * element itself
459
- *
460
- * @param {Element} node
461
- */
462
- function moveFocusToDialog(node) {
463
- var focused = node.querySelector('[autofocus]') || node;
464
-
465
- focused.focus();
466
- }
467
-
468
- /**
469
- * Get the focusable children of the given element
470
- *
471
- * @param {Element} node
472
- * @return {Array<Element>}
473
- */
474
- function getFocusableChildren(node) {
475
- return $$(focusableSelectors.join(','), node).filter(function (child) {
476
- return !!(
477
- child.offsetWidth ||
478
- child.offsetHeight ||
479
- child.getClientRects().length
480
- )
481
- })
695
+ // `Promise.race()` workaround (#91)
696
+ async function timeout(request, abortController, options) {
697
+ return new Promise((resolve, reject) => {
698
+ const timeoutId = setTimeout(() => {
699
+ if (abortController) {
700
+ abortController.abort();
701
+ }
702
+ reject(new TimeoutError(request));
703
+ }, options.timeout);
704
+ void options
705
+ .fetch(request)
706
+ .then(resolve)
707
+ .catch(reject)
708
+ .then(() => {
709
+ clearTimeout(timeoutId);
710
+ });
711
+ });
482
712
  }
483
713
 
484
- /**
485
- * Trap the focus inside the given element
486
- *
487
- * @param {Element} node
488
- * @param {Event} event
489
- */
490
- function trapTabKey(node, event) {
491
- var focusableChildren = getFocusableChildren(node);
492
- var focusedItemIndex = focusableChildren.indexOf(document.activeElement);
493
-
494
- // If the SHIFT key is being pressed while tabbing (moving backwards) and
495
- // the currently focused item is the first one, move the focus to the last
496
- // focusable item from the dialog element
497
- if (event.shiftKey && focusedItemIndex === 0) {
498
- focusableChildren[focusableChildren.length - 1].focus();
499
- event.preventDefault();
500
- // If the SHIFT key is not being pressed (moving forwards) and the currently
501
- // focused item is the last one, move the focus to the first focusable item
502
- // from the dialog element
503
- } else if (
504
- !event.shiftKey &&
505
- focusedItemIndex === focusableChildren.length - 1
506
- ) {
507
- focusableChildren[0].focus();
508
- event.preventDefault();
509
- }
714
+ // DOMException is supported on most modern browsers and Node.js 18+.
715
+ // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException#browser_compatibility
716
+ const isDomExceptionSupported = Boolean(globalThis.DOMException);
717
+ // TODO: When targeting Node.js 18, use `signal.throwIfAborted()` (https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted)
718
+ function composeAbortError(signal) {
719
+ /*
720
+ NOTE: Use DomException with AbortError name as specified in MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)
721
+ > When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.
722
+ */
723
+ if (isDomExceptionSupported) {
724
+ return new DOMException(signal?.reason ?? 'The operation was aborted.', 'AbortError');
725
+ }
726
+ // DOMException not supported. Fall back to use of error and override name.
727
+ const error = new Error(signal?.reason ?? 'The operation was aborted.');
728
+ error.name = 'AbortError';
729
+ return error;
510
730
  }
511
731
 
512
- function instantiateDialogs() {
513
- $$('[data-a11y-dialog]').forEach(function (node) {
514
- new A11yDialog(node);
515
- });
732
+ // https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111
733
+ async function delay(ms, { signal }) {
734
+ return new Promise((resolve, reject) => {
735
+ if (signal) {
736
+ if (signal.aborted) {
737
+ reject(composeAbortError(signal));
738
+ return;
739
+ }
740
+ signal.addEventListener('abort', handleAbort, { once: true });
741
+ }
742
+ function handleAbort() {
743
+ reject(composeAbortError(signal));
744
+ clearTimeout(timeoutId);
745
+ }
746
+ const timeoutId = setTimeout(() => {
747
+ signal?.removeEventListener('abort', handleAbort);
748
+ resolve();
749
+ }, ms);
750
+ });
516
751
  }
517
752
 
518
- if (typeof document !== 'undefined') {
519
- if (document.readyState === 'loading') {
520
- document.addEventListener('DOMContentLoaded', instantiateDialogs);
521
- } else {
522
- if (window.requestAnimationFrame) {
523
- window.requestAnimationFrame(instantiateDialogs);
524
- } else {
525
- window.setTimeout(instantiateDialogs, 16);
526
- }
527
- }
528
- }
529
-
530
- var CONTAINER_ID = "__authsignal-popup-container";
531
- var CONTENT_ID = "__authsignal-popup-content";
532
- var OVERLAY_ID = "__authsignal-popup-overlay";
533
- var STYLE_ID = "__authsignal-popup-style";
534
- var IFRAME_ID = "__authsignal-popup-iframe";
535
- var DEFAULT_WIDTH = "385px";
536
- var INITIAL_HEIGHT = "384px";
537
- var PopupHandler = /** @class */ (function () {
538
- function PopupHandler(_a) {
539
- var width = _a.width;
540
- this.popup = null;
541
- if (document.querySelector("#".concat(CONTAINER_ID))) {
542
- throw new Error("Multiple instances of Authsignal popup is not supported.");
753
+ class Ky {
754
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
755
+ static create(input, options) {
756
+ const ky = new Ky(input, options);
757
+ const fn = async () => {
758
+ if (ky._options.timeout > maxSafeTimeout) {
759
+ throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
760
+ }
761
+ // Delay the fetch so that body method shortcuts can set the Accept header
762
+ await Promise.resolve();
763
+ let response = await ky._fetch();
764
+ for (const hook of ky._options.hooks.afterResponse) {
765
+ // eslint-disable-next-line no-await-in-loop
766
+ const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone()));
767
+ if (modifiedResponse instanceof globalThis.Response) {
768
+ response = modifiedResponse;
769
+ }
770
+ }
771
+ ky._decorateResponse(response);
772
+ if (!response.ok && ky._options.throwHttpErrors) {
773
+ let error = new HTTPError(response, ky.request, ky._options);
774
+ for (const hook of ky._options.hooks.beforeError) {
775
+ // eslint-disable-next-line no-await-in-loop
776
+ error = await hook(error);
777
+ }
778
+ throw error;
779
+ }
780
+ // If `onDownloadProgress` is passed, it uses the stream API internally
781
+ /* istanbul ignore next */
782
+ if (ky._options.onDownloadProgress) {
783
+ if (typeof ky._options.onDownloadProgress !== 'function') {
784
+ throw new TypeError('The `onDownloadProgress` option must be a function');
785
+ }
786
+ if (!supportsResponseStreams) {
787
+ throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
788
+ }
789
+ return ky._stream(response.clone(), ky._options.onDownloadProgress);
790
+ }
791
+ return response;
792
+ };
793
+ const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
794
+ const result = (isRetriableMethod ? ky._retry(fn) : fn());
795
+ for (const [type, mimeType] of Object.entries(responseTypes)) {
796
+ result[type] = async () => {
797
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
798
+ ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
799
+ const awaitedResult = await result;
800
+ const response = awaitedResult.clone();
801
+ if (type === 'json') {
802
+ if (response.status === 204) {
803
+ return '';
804
+ }
805
+ const arrayBuffer = await response.clone().arrayBuffer();
806
+ const responseSize = arrayBuffer.byteLength;
807
+ if (responseSize === 0) {
808
+ return '';
809
+ }
810
+ if (options.parseJson) {
811
+ return options.parseJson(await response.text());
812
+ }
813
+ }
814
+ return response[type]();
815
+ };
543
816
  }
544
- this.create({ width: width });
817
+ return result;
545
818
  }
546
- PopupHandler.prototype.create = function (_a) {
547
- var _this = this;
548
- var _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH : _b;
549
- var isWidthValidCSSValue = CSS.supports("width", width);
550
- var popupWidth = width;
551
- if (!isWidthValidCSSValue) {
552
- console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead.");
553
- popupWidth = DEFAULT_WIDTH;
554
- }
555
- // Create dialog container
556
- var container = document.createElement("div");
557
- container.setAttribute("id", CONTAINER_ID);
558
- container.setAttribute("aria-hidden", "true");
559
- // Create dialog overlay
560
- var overlay = document.createElement("div");
561
- overlay.setAttribute("id", OVERLAY_ID);
562
- overlay.setAttribute("data-a11y-dialog-hide", "true");
563
- // Create dialog content
564
- var content = document.createElement("div");
565
- content.setAttribute("id", CONTENT_ID);
566
- document.body.appendChild(container);
567
- // Create CSS for dialog
568
- var style = document.createElement("style");
569
- style.setAttribute("id", STYLE_ID);
570
- style.textContent = "\n #".concat(CONTAINER_ID, ",\n #").concat(OVERLAY_ID, " {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n\n #").concat(CONTAINER_ID, " {\n z-index: 2147483647;\n display: flex;\n }\n\n #").concat(CONTAINER_ID, "[aria-hidden='true'] {\n display: none;\n }\n\n #").concat(OVERLAY_ID, " {\n background-color: rgba(0, 0, 0, 0.18);\n }\n\n #").concat(CONTENT_ID, " {\n margin: auto;\n z-index: 2147483647;\n position: relative;\n background-color: transparent;\n border-radius: 8px;\n width: ").concat(popupWidth, ";\n }\n\n #").concat(CONTENT_ID, " iframe {\n width: 1px;\n min-width: 100%;\n border-radius: inherit;\n max-height: 95vh;\n height: ").concat(INITIAL_HEIGHT, ";\n }\n ");
571
- // Attach the created elements
572
- document.head.insertAdjacentElement("beforeend", style);
573
- container.appendChild(overlay);
574
- container.appendChild(content);
575
- this.popup = new A11yDialog(container);
576
- // Make sure to remove any trace of the dialog on hide
577
- this.popup.on("hide", function () {
578
- _this.destroy();
819
+ // eslint-disable-next-line complexity
820
+ constructor(input, options = {}) {
821
+ Object.defineProperty(this, "request", {
822
+ enumerable: true,
823
+ configurable: true,
824
+ writable: true,
825
+ value: void 0
579
826
  });
580
- };
581
- PopupHandler.prototype.destroy = function () {
582
- var dialogEl = document.querySelector("#".concat(CONTAINER_ID));
583
- var styleEl = document.querySelector("#".concat(STYLE_ID));
584
- if (dialogEl && styleEl) {
585
- document.body.removeChild(dialogEl);
586
- document.head.removeChild(styleEl);
827
+ Object.defineProperty(this, "abortController", {
828
+ enumerable: true,
829
+ configurable: true,
830
+ writable: true,
831
+ value: void 0
832
+ });
833
+ Object.defineProperty(this, "_retryCount", {
834
+ enumerable: true,
835
+ configurable: true,
836
+ writable: true,
837
+ value: 0
838
+ });
839
+ Object.defineProperty(this, "_input", {
840
+ enumerable: true,
841
+ configurable: true,
842
+ writable: true,
843
+ value: void 0
844
+ });
845
+ Object.defineProperty(this, "_options", {
846
+ enumerable: true,
847
+ configurable: true,
848
+ writable: true,
849
+ value: void 0
850
+ });
851
+ this._input = input;
852
+ this._options = {
853
+ // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
854
+ credentials: this._input.credentials || 'same-origin',
855
+ ...options,
856
+ headers: mergeHeaders(this._input.headers, options.headers),
857
+ hooks: deepMerge({
858
+ beforeRequest: [],
859
+ beforeRetry: [],
860
+ beforeError: [],
861
+ afterResponse: [],
862
+ }, options.hooks),
863
+ method: normalizeRequestMethod(options.method ?? this._input.method),
864
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
865
+ prefixUrl: String(options.prefixUrl || ''),
866
+ retry: normalizeRetryOptions(options.retry),
867
+ throwHttpErrors: options.throwHttpErrors !== false,
868
+ timeout: typeof options.timeout === 'undefined' ? 10000 : options.timeout,
869
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
870
+ };
871
+ if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
872
+ throw new TypeError('`input` must be a string, URL, or Request');
587
873
  }
588
- window.removeEventListener("message", resizeIframe);
589
- };
590
- PopupHandler.prototype.show = function (_a) {
591
- var _b;
592
- var url = _a.url;
593
- if (!this.popup) {
594
- throw new Error("Popup is not initialized");
874
+ if (this._options.prefixUrl && typeof this._input === 'string') {
875
+ if (this._input.startsWith('/')) {
876
+ throw new Error('`input` must not begin with a slash when using `prefixUrl`');
877
+ }
878
+ if (!this._options.prefixUrl.endsWith('/')) {
879
+ this._options.prefixUrl += '/';
880
+ }
881
+ this._input = this._options.prefixUrl + this._input;
595
882
  }
596
- var iframe = document.createElement("iframe");
597
- iframe.setAttribute("id", IFRAME_ID);
598
- iframe.setAttribute("name", "authsignal");
599
- iframe.setAttribute("title", "Authsignal multi-factor authentication");
600
- iframe.setAttribute("src", url);
601
- iframe.setAttribute("frameborder", "0");
602
- iframe.setAttribute("allow", "publickey-credentials-get *; publickey-credentials-create *; clipboard-write");
603
- var dialogContent = document.querySelector("#".concat(CONTENT_ID));
604
- if (dialogContent) {
605
- dialogContent.appendChild(iframe);
883
+ if (supportsAbortController) {
884
+ this.abortController = new globalThis.AbortController();
885
+ if (this._options.signal) {
886
+ const originalSignal = this._options.signal;
887
+ this._options.signal.addEventListener('abort', () => {
888
+ this.abortController.abort(originalSignal.reason);
889
+ });
890
+ }
891
+ this._options.signal = this.abortController.signal;
606
892
  }
607
- window.addEventListener("message", resizeIframe);
608
- (_b = this.popup) === null || _b === void 0 ? void 0 : _b.show();
609
- };
610
- PopupHandler.prototype.close = function () {
611
- if (!this.popup) {
612
- throw new Error("Popup is not initialized");
893
+ if (supportsRequestStreams) {
894
+ // @ts-expect-error - Types are outdated.
895
+ this._options.duplex = 'half';
613
896
  }
614
- this.popup.hide();
615
- };
616
- PopupHandler.prototype.on = function (event, handler) {
617
- if (!this.popup) {
618
- throw new Error("Popup is not initialized");
897
+ this.request = new globalThis.Request(this._input, this._options);
898
+ if (this._options.searchParams) {
899
+ // eslint-disable-next-line unicorn/prevent-abbreviations
900
+ const textSearchParams = typeof this._options.searchParams === 'string'
901
+ ? this._options.searchParams.replace(/^\?/, '')
902
+ : new URLSearchParams(this._options.searchParams).toString();
903
+ // eslint-disable-next-line unicorn/prevent-abbreviations
904
+ const searchParams = '?' + textSearchParams;
905
+ const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
906
+ // To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
907
+ if (((supportsFormData && this._options.body instanceof globalThis.FormData)
908
+ || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
909
+ this.request.headers.delete('content-type');
910
+ }
911
+ // The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws.
912
+ this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
913
+ }
914
+ if (this._options.json !== undefined) {
915
+ this._options.body = JSON.stringify(this._options.json);
916
+ this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json');
917
+ this.request = new globalThis.Request(this.request, { body: this._options.body });
619
918
  }
620
- this.popup.on(event, handler);
621
- };
622
- return PopupHandler;
623
- }());
624
- function resizeIframe(event) {
625
- var iframeEl = document.querySelector("#".concat(IFRAME_ID));
626
- if (iframeEl && event.data.height) {
627
- iframeEl.style.height = event.data.height + "px";
628
919
  }
629
- }
630
-
631
- /******************************************************************************
632
- Copyright (c) Microsoft Corporation.
633
-
634
- Permission to use, copy, modify, and/or distribute this software for any
635
- purpose with or without fee is hereby granted.
636
-
637
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
638
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
639
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
640
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
641
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
642
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
643
- PERFORMANCE OF THIS SOFTWARE.
644
- ***************************************************************************** */
645
-
646
- function __rest(s, e) {
647
- var t = {};
648
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
649
- t[p] = s[p];
650
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
651
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
652
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
653
- t[p[i]] = s[p[i]];
654
- }
655
- return t;
656
- }
657
-
658
- function __awaiter(thisArg, _arguments, P, generator) {
659
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
660
- return new (P || (P = Promise))(function (resolve, reject) {
661
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
662
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
663
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
664
- step((generator = generator.apply(thisArg, _arguments || [])).next());
665
- });
666
- }
667
-
668
- function __generator(thisArg, body) {
669
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
670
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
671
- function verb(n) { return function (v) { return step([n, v]); }; }
672
- function step(op) {
673
- if (f) throw new TypeError("Generator is already executing.");
674
- while (_) try {
675
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
676
- if (y = 0, t) op = [op[0] & 2, t.value];
677
- switch (op[0]) {
678
- case 0: case 1: t = op; break;
679
- case 4: _.label++; return { value: op[1], done: false };
680
- case 5: _.label++; y = op[1]; op = [0]; continue;
681
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
682
- default:
683
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
684
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
685
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
686
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
687
- if (t[2]) _.ops.pop();
688
- _.trys.pop(); continue;
689
- }
690
- op = body.call(thisArg, _);
691
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
692
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
693
- }
694
- }
695
-
696
- /* [@simplewebauthn/browser@8.2.1] */
697
- function utf8StringToBuffer(value) {
698
- return new TextEncoder().encode(value);
699
- }
700
-
701
- function bufferToBase64URLString(buffer) {
702
- const bytes = new Uint8Array(buffer);
703
- let str = '';
704
- for (const charCode of bytes) {
705
- str += String.fromCharCode(charCode);
706
- }
707
- const base64String = btoa(str);
708
- return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
709
- }
710
-
711
- function base64URLStringToBuffer(base64URLString) {
712
- const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
713
- const padLength = (4 - (base64.length % 4)) % 4;
714
- const padded = base64.padEnd(base64.length + padLength, '=');
715
- const binary = atob(padded);
716
- const buffer = new ArrayBuffer(binary.length);
717
- const bytes = new Uint8Array(buffer);
718
- for (let i = 0; i < binary.length; i++) {
719
- bytes[i] = binary.charCodeAt(i);
720
- }
721
- return buffer;
722
- }
723
-
724
- function browserSupportsWebAuthn() {
725
- return (window?.PublicKeyCredential !== undefined &&
726
- typeof window.PublicKeyCredential === 'function');
727
- }
728
-
729
- function toPublicKeyCredentialDescriptor(descriptor) {
730
- const { id } = descriptor;
731
- return {
732
- ...descriptor,
733
- id: base64URLStringToBuffer(id),
734
- transports: descriptor.transports,
735
- };
736
- }
737
-
738
- function isValidDomain(hostname) {
739
- return (hostname === 'localhost' ||
740
- /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(hostname));
741
- }
742
-
743
- class WebAuthnError extends Error {
744
- constructor({ message, code, cause, name, }) {
745
- super(message, { cause });
746
- this.name = name ?? cause.name;
747
- this.code = code;
748
- }
749
- }
750
-
751
- function identifyRegistrationError({ error, options, }) {
752
- const { publicKey } = options;
753
- if (!publicKey) {
754
- throw Error('options was missing required publicKey property');
755
- }
756
- if (error.name === 'AbortError') {
757
- if (options.signal instanceof AbortSignal) {
758
- return new WebAuthnError({
759
- message: 'Registration ceremony was sent an abort signal',
760
- code: 'ERROR_CEREMONY_ABORTED',
761
- cause: error,
762
- });
920
+ _calculateRetryDelay(error) {
921
+ this._retryCount++;
922
+ if (this._retryCount < this._options.retry.limit && !(error instanceof TimeoutError)) {
923
+ if (error instanceof HTTPError) {
924
+ if (!this._options.retry.statusCodes.includes(error.response.status)) {
925
+ return 0;
926
+ }
927
+ const retryAfter = error.response.headers.get('Retry-After');
928
+ if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
929
+ let after = Number(retryAfter);
930
+ if (Number.isNaN(after)) {
931
+ after = Date.parse(retryAfter) - Date.now();
932
+ }
933
+ else {
934
+ after *= 1000;
935
+ }
936
+ if (typeof this._options.retry.maxRetryAfter !== 'undefined' && after > this._options.retry.maxRetryAfter) {
937
+ return 0;
938
+ }
939
+ return after;
940
+ }
941
+ if (error.response.status === 413) {
942
+ return 0;
943
+ }
944
+ }
945
+ const BACKOFF_FACTOR = 0.3;
946
+ return Math.min(this._options.retry.backoffLimit, BACKOFF_FACTOR * (2 ** (this._retryCount - 1)) * 1000);
763
947
  }
948
+ return 0;
764
949
  }
765
- else if (error.name === 'ConstraintError') {
766
- if (publicKey.authenticatorSelection?.requireResidentKey === true) {
767
- return new WebAuthnError({
768
- message: 'Discoverable credentials were required but no available authenticator supported it',
769
- code: 'ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT',
770
- cause: error,
771
- });
772
- }
773
- else if (publicKey.authenticatorSelection?.userVerification === 'required') {
774
- return new WebAuthnError({
775
- message: 'User verification was required but no available authenticator supported it',
776
- code: 'ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT',
777
- cause: error,
778
- });
950
+ _decorateResponse(response) {
951
+ if (this._options.parseJson) {
952
+ response.json = async () => this._options.parseJson(await response.text());
779
953
  }
954
+ return response;
780
955
  }
781
- else if (error.name === 'InvalidStateError') {
782
- return new WebAuthnError({
783
- message: 'The authenticator was previously registered',
784
- code: 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED',
785
- cause: error,
786
- });
787
- }
788
- else if (error.name === 'NotAllowedError') {
789
- return new WebAuthnError({
790
- message: error.message,
791
- code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
792
- cause: error,
793
- });
794
- }
795
- else if (error.name === 'NotSupportedError') {
796
- const validPubKeyCredParams = publicKey.pubKeyCredParams.filter((param) => param.type === 'public-key');
797
- if (validPubKeyCredParams.length === 0) {
798
- return new WebAuthnError({
799
- message: 'No entry in pubKeyCredParams was of type "public-key"',
800
- code: 'ERROR_MALFORMED_PUBKEYCREDPARAMS',
801
- cause: error,
802
- });
956
+ async _retry(fn) {
957
+ try {
958
+ return await fn();
959
+ // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch
960
+ }
961
+ catch (error) {
962
+ const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
963
+ if (ms !== 0 && this._retryCount > 0) {
964
+ await delay(ms, { signal: this._options.signal });
965
+ for (const hook of this._options.hooks.beforeRetry) {
966
+ // eslint-disable-next-line no-await-in-loop
967
+ const hookResult = await hook({
968
+ request: this.request,
969
+ options: this._options,
970
+ error: error,
971
+ retryCount: this._retryCount,
972
+ });
973
+ // If `stop` is returned from the hook, the retry process is stopped
974
+ if (hookResult === stop) {
975
+ return;
976
+ }
977
+ }
978
+ return this._retry(fn);
979
+ }
980
+ throw error;
803
981
  }
804
- return new WebAuthnError({
805
- message: 'No available authenticator supported any of the specified pubKeyCredParams algorithms',
806
- code: 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG',
807
- cause: error,
808
- });
809
982
  }
810
- else if (error.name === 'SecurityError') {
811
- const effectiveDomain = window.location.hostname;
812
- if (!isValidDomain(effectiveDomain)) {
813
- return new WebAuthnError({
814
- message: `${window.location.hostname} is an invalid domain`,
815
- code: 'ERROR_INVALID_DOMAIN',
816
- cause: error,
817
- });
983
+ async _fetch() {
984
+ for (const hook of this._options.hooks.beforeRequest) {
985
+ // eslint-disable-next-line no-await-in-loop
986
+ const result = await hook(this.request, this._options);
987
+ if (result instanceof Request) {
988
+ this.request = result;
989
+ break;
990
+ }
991
+ if (result instanceof Response) {
992
+ return result;
993
+ }
818
994
  }
819
- else if (publicKey.rp.id !== effectiveDomain) {
820
- return new WebAuthnError({
821
- message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
822
- code: 'ERROR_INVALID_RP_ID',
823
- cause: error,
824
- });
995
+ if (this._options.timeout === false) {
996
+ return this._options.fetch(this.request.clone());
825
997
  }
998
+ return timeout(this.request.clone(), this.abortController, this._options);
826
999
  }
827
- else if (error.name === 'TypeError') {
828
- if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) {
829
- return new WebAuthnError({
830
- message: 'User ID was not between 1 and 64 characters',
831
- code: 'ERROR_INVALID_USER_ID_LENGTH',
832
- cause: error,
1000
+ /* istanbul ignore next */
1001
+ _stream(response, onDownloadProgress) {
1002
+ const totalBytes = Number(response.headers.get('content-length')) || 0;
1003
+ let transferredBytes = 0;
1004
+ if (response.status === 204) {
1005
+ if (onDownloadProgress) {
1006
+ onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
1007
+ }
1008
+ return new globalThis.Response(null, {
1009
+ status: response.status,
1010
+ statusText: response.statusText,
1011
+ headers: response.headers,
833
1012
  });
834
1013
  }
835
- }
836
- else if (error.name === 'UnknownError') {
837
- return new WebAuthnError({
838
- message: 'The authenticator was unable to process the specified options, or could not create a new credential',
839
- code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
840
- cause: error,
1014
+ return new globalThis.Response(new globalThis.ReadableStream({
1015
+ async start(controller) {
1016
+ const reader = response.body.getReader();
1017
+ if (onDownloadProgress) {
1018
+ onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
1019
+ }
1020
+ async function read() {
1021
+ const { done, value } = await reader.read();
1022
+ if (done) {
1023
+ controller.close();
1024
+ return;
1025
+ }
1026
+ if (onDownloadProgress) {
1027
+ transferredBytes += value.byteLength;
1028
+ const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
1029
+ onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
1030
+ }
1031
+ controller.enqueue(value);
1032
+ await read();
1033
+ }
1034
+ await read();
1035
+ },
1036
+ }), {
1037
+ status: response.status,
1038
+ statusText: response.statusText,
1039
+ headers: response.headers,
841
1040
  });
842
1041
  }
843
- return error;
844
- }
845
-
846
- class WebAuthnAbortService {
847
- createNewAbortSignal() {
848
- if (this.controller) {
849
- const abortError = new Error('Cancelling existing WebAuthn API call for new one');
850
- abortError.name = 'AbortError';
851
- this.controller.abort(abortError);
852
- }
853
- const newController = new AbortController();
854
- this.controller = newController;
855
- return newController.signal;
856
- }
857
- }
858
- const webauthnAbortService = new WebAuthnAbortService();
859
-
860
- const attachments = ['cross-platform', 'platform'];
861
- function toAuthenticatorAttachment(attachment) {
862
- if (!attachment) {
863
- return;
864
- }
865
- if (attachments.indexOf(attachment) < 0) {
866
- return;
867
- }
868
- return attachment;
869
- }
870
-
871
- async function startRegistration(creationOptionsJSON) {
872
- if (!browserSupportsWebAuthn()) {
873
- throw new Error('WebAuthn is not supported in this browser');
874
- }
875
- const publicKey = {
876
- ...creationOptionsJSON,
877
- challenge: base64URLStringToBuffer(creationOptionsJSON.challenge),
878
- user: {
879
- ...creationOptionsJSON.user,
880
- id: utf8StringToBuffer(creationOptionsJSON.user.id),
881
- },
882
- excludeCredentials: creationOptionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor),
883
- };
884
- const options = { publicKey };
885
- options.signal = webauthnAbortService.createNewAbortSignal();
886
- let credential;
887
- try {
888
- credential = (await navigator.credentials.create(options));
889
- }
890
- catch (err) {
891
- throw identifyRegistrationError({ error: err, options });
892
- }
893
- if (!credential) {
894
- throw new Error('Registration was not completed');
895
- }
896
- const { id, rawId, response, type } = credential;
897
- let transports = undefined;
898
- if (typeof response.getTransports === 'function') {
899
- transports = response.getTransports();
900
- }
901
- let responsePublicKeyAlgorithm = undefined;
902
- if (typeof response.getPublicKeyAlgorithm === 'function') {
903
- try {
904
- responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
905
- }
906
- catch (error) {
907
- warnOnBrokenImplementation('getPublicKeyAlgorithm()', error);
908
- }
909
- }
910
- let responsePublicKey = undefined;
911
- if (typeof response.getPublicKey === 'function') {
912
- try {
913
- const _publicKey = response.getPublicKey();
914
- if (_publicKey !== null) {
915
- responsePublicKey = bufferToBase64URLString(_publicKey);
916
- }
917
- }
918
- catch (error) {
919
- warnOnBrokenImplementation('getPublicKey()', error);
920
- }
921
- }
922
- let responseAuthenticatorData;
923
- if (typeof response.getAuthenticatorData === 'function') {
924
- try {
925
- responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
926
- }
927
- catch (error) {
928
- warnOnBrokenImplementation('getAuthenticatorData()', error);
929
- }
930
- }
931
- return {
932
- id,
933
- rawId: bufferToBase64URLString(rawId),
934
- response: {
935
- attestationObject: bufferToBase64URLString(response.attestationObject),
936
- clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
937
- transports,
938
- publicKeyAlgorithm: responsePublicKeyAlgorithm,
939
- publicKey: responsePublicKey,
940
- authenticatorData: responseAuthenticatorData,
941
- },
942
- type,
943
- clientExtensionResults: credential.getClientExtensionResults(),
944
- authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
945
- };
946
- }
947
- function warnOnBrokenImplementation(methodName, cause) {
948
- console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.\n`, cause);
949
- }
950
-
951
- function bufferToUTF8String(value) {
952
- return new TextDecoder('utf-8').decode(value);
953
1042
  }
954
1043
 
955
- function browserSupportsWebAuthnAutofill() {
956
- const globalPublicKeyCredential = window
957
- .PublicKeyCredential;
958
- if (globalPublicKeyCredential.isConditionalMediationAvailable === undefined) {
959
- return new Promise((resolve) => resolve(false));
1044
+ /*! MIT License © Sindre Sorhus */
1045
+ const createInstance = (defaults) => {
1046
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
1047
+ const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
1048
+ for (const method of requestMethods) {
1049
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
1050
+ ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
960
1051
  }
961
- return globalPublicKeyCredential.isConditionalMediationAvailable();
962
- }
1052
+ ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
1053
+ ky.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
1054
+ ky.stop = stop;
1055
+ return ky;
1056
+ };
1057
+ const ky = createInstance();
1058
+ var ky$1 = ky;
963
1059
 
964
- function identifyAuthenticationError({ error, options, }) {
965
- const { publicKey } = options;
966
- if (!publicKey) {
967
- throw Error('options was missing required publicKey property');
968
- }
969
- if (error.name === 'AbortError') {
970
- if (options.signal instanceof AbortSignal) {
971
- return new WebAuthnError({
972
- message: 'Authentication ceremony was sent an abort signal',
973
- code: 'ERROR_CEREMONY_ABORTED',
974
- cause: error,
975
- });
976
- }
977
- }
978
- else if (error.name === 'NotAllowedError') {
979
- return new WebAuthnError({
980
- message: error.message,
981
- code: 'ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY',
982
- cause: error,
1060
+ var PasskeyApiClient = /** @class */ (function () {
1061
+ function PasskeyApiClient(_a) {
1062
+ var baseUrl = _a.baseUrl, tenantId = _a.tenantId;
1063
+ this.tenantId = tenantId;
1064
+ this.api = ky$1.create({
1065
+ prefixUrl: baseUrl
983
1066
  });
984
1067
  }
985
- else if (error.name === 'SecurityError') {
986
- const effectiveDomain = window.location.hostname;
987
- if (!isValidDomain(effectiveDomain)) {
988
- return new WebAuthnError({
989
- message: `${window.location.hostname} is an invalid domain`,
990
- code: 'ERROR_INVALID_DOMAIN',
991
- cause: error,
992
- });
993
- }
994
- else if (publicKey.rpId !== effectiveDomain) {
995
- return new WebAuthnError({
996
- message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
997
- code: 'ERROR_INVALID_RP_ID',
998
- cause: error,
1068
+ PasskeyApiClient.prototype.registrationOptions = function (_a) {
1069
+ var token = _a.token, userName = _a.userName;
1070
+ return __awaiter(this, void 0, void 0, function () {
1071
+ var response;
1072
+ return __generator(this, function (_b) {
1073
+ switch (_b.label) {
1074
+ case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey/registration-options", {
1075
+ json: { username: userName },
1076
+ headers: this.buildHeaders(token)
1077
+ })];
1078
+ case 1:
1079
+ response = _b.sent();
1080
+ return [2 /*return*/, response.json()];
1081
+ }
999
1082
  });
1000
- }
1001
- }
1002
- else if (error.name === 'UnknownError') {
1003
- return new WebAuthnError({
1004
- message: 'The authenticator was unable to process the specified options, or could not create a new assertion signature',
1005
- code: 'ERROR_AUTHENTICATOR_GENERAL_ERROR',
1006
- cause: error,
1007
1083
  });
1008
- }
1009
- return error;
1010
- }
1011
-
1012
- async function startAuthentication(requestOptionsJSON, useBrowserAutofill = false) {
1013
- if (!browserSupportsWebAuthn()) {
1014
- throw new Error('WebAuthn is not supported in this browser');
1015
- }
1016
- let allowCredentials;
1017
- if (requestOptionsJSON.allowCredentials?.length !== 0) {
1018
- allowCredentials = requestOptionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
1019
- }
1020
- const publicKey = {
1021
- ...requestOptionsJSON,
1022
- challenge: base64URLStringToBuffer(requestOptionsJSON.challenge),
1023
- allowCredentials,
1024
1084
  };
1025
- const options = {};
1026
- if (useBrowserAutofill) {
1027
- if (!(await browserSupportsWebAuthnAutofill())) {
1028
- throw Error('Browser does not support WebAuthn autofill');
1029
- }
1030
- const eligibleInputs = document.querySelectorAll('input[autocomplete*=\'webauthn\']');
1031
- if (eligibleInputs.length < 1) {
1032
- throw Error('No <input> with `"webauthn"` in its `autocomplete` attribute was detected');
1033
- }
1034
- options.mediation = 'conditional';
1035
- publicKey.allowCredentials = [];
1036
- }
1037
- options.publicKey = publicKey;
1038
- options.signal = webauthnAbortService.createNewAbortSignal();
1039
- let credential;
1040
- try {
1041
- credential = (await navigator.credentials.get(options));
1042
- }
1043
- catch (err) {
1044
- throw identifyAuthenticationError({ error: err, options });
1045
- }
1046
- if (!credential) {
1047
- throw new Error('Authentication was not completed');
1048
- }
1049
- const { id, rawId, response, type } = credential;
1050
- let userHandle = undefined;
1051
- if (response.userHandle) {
1052
- userHandle = bufferToUTF8String(response.userHandle);
1053
- }
1054
- return {
1055
- id,
1056
- rawId: bufferToBase64URLString(rawId),
1057
- response: {
1058
- authenticatorData: bufferToBase64URLString(response.authenticatorData),
1059
- clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
1060
- signature: bufferToBase64URLString(response.signature),
1061
- userHandle,
1062
- },
1063
- type,
1064
- clientExtensionResults: credential.getClientExtensionResults(),
1065
- authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
1066
- };
1067
- }
1068
-
1069
- // eslint-lint-disable-next-line @typescript-eslint/naming-convention
1070
- class HTTPError extends Error {
1071
- constructor(response, request, options) {
1072
- const code = (response.status || response.status === 0) ? response.status : '';
1073
- const title = response.statusText || '';
1074
- const status = `${code} ${title}`.trim();
1075
- const reason = status ? `status code ${status}` : 'an unknown error';
1076
- super(`Request failed with ${reason}`);
1077
- Object.defineProperty(this, "response", {
1078
- enumerable: true,
1079
- configurable: true,
1080
- writable: true,
1081
- value: void 0
1085
+ PasskeyApiClient.prototype.authenticationOptions = function (_a) {
1086
+ var token = _a.token;
1087
+ return __awaiter(this, void 0, void 0, function () {
1088
+ var response;
1089
+ return __generator(this, function (_b) {
1090
+ switch (_b.label) {
1091
+ case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey/authentication-options", {
1092
+ json: {},
1093
+ headers: this.buildHeaders(token)
1094
+ })];
1095
+ case 1:
1096
+ response = _b.sent();
1097
+ return [2 /*return*/, response.json()];
1098
+ }
1099
+ });
1082
1100
  });
1083
- Object.defineProperty(this, "request", {
1084
- enumerable: true,
1085
- configurable: true,
1086
- writable: true,
1087
- value: void 0
1101
+ };
1102
+ PasskeyApiClient.prototype.addAuthenticator = function (_a) {
1103
+ var token = _a.token, rest = __rest(_a, ["token"]);
1104
+ return __awaiter(this, void 0, void 0, function () {
1105
+ var response;
1106
+ return __generator(this, function (_b) {
1107
+ switch (_b.label) {
1108
+ case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey", {
1109
+ json: rest,
1110
+ headers: this.buildHeaders(token)
1111
+ })];
1112
+ case 1:
1113
+ response = _b.sent();
1114
+ return [2 /*return*/, response.json()];
1115
+ }
1116
+ });
1088
1117
  });
1089
- Object.defineProperty(this, "options", {
1090
- enumerable: true,
1091
- configurable: true,
1092
- writable: true,
1093
- value: void 0
1118
+ };
1119
+ PasskeyApiClient.prototype.verify = function (_a) {
1120
+ var token = _a.token, rest = __rest(_a, ["token"]);
1121
+ return __awaiter(this, void 0, void 0, function () {
1122
+ var response;
1123
+ return __generator(this, function (_b) {
1124
+ switch (_b.label) {
1125
+ case 0: return [4 /*yield*/, this.api.post("client/verify/passkey", {
1126
+ json: rest,
1127
+ headers: this.buildHeaders(token)
1128
+ })];
1129
+ case 1:
1130
+ response = _b.sent();
1131
+ return [2 /*return*/, response.json()];
1132
+ }
1133
+ });
1094
1134
  });
1095
- this.name = 'HTTPError';
1096
- this.response = response;
1097
- this.request = request;
1098
- this.options = options;
1099
- }
1100
- }
1135
+ };
1136
+ PasskeyApiClient.prototype.buildHeaders = function (token) {
1137
+ var authorizationHeader = token ? "Bearer ".concat(token) : "Basic ".concat(window.btoa(encodeURIComponent(this.tenantId)));
1138
+ return {
1139
+ Authorization: authorizationHeader
1140
+ };
1141
+ };
1142
+ return PasskeyApiClient;
1143
+ }());
1101
1144
 
1102
- class TimeoutError extends Error {
1103
- constructor(request) {
1104
- super('Request timed out');
1105
- Object.defineProperty(this, "request", {
1106
- enumerable: true,
1107
- configurable: true,
1108
- writable: true,
1109
- value: void 0
1110
- });
1111
- this.name = 'TimeoutError';
1112
- this.request = request;
1145
+ var Passkey = /** @class */ (function () {
1146
+ function Passkey(_a) {
1147
+ var baseUrl = _a.baseUrl, tenantId = _a.tenantId;
1148
+ this.api = new PasskeyApiClient({ baseUrl: baseUrl, tenantId: tenantId });
1113
1149
  }
1114
- }
1115
-
1116
- // eslint-disable-next-line @typescript-eslint/ban-types
1117
- const isObject = (value) => value !== null && typeof value === 'object';
1150
+ Passkey.prototype.signUp = function (_a) {
1151
+ var userName = _a.userName, token = _a.token;
1152
+ return __awaiter(this, void 0, void 0, function () {
1153
+ var optionsResponse, registrationResponse, addAuthenticatorResponse;
1154
+ return __generator(this, function (_b) {
1155
+ switch (_b.label) {
1156
+ case 0: return [4 /*yield*/, this.api.registrationOptions({ userName: userName, token: token })];
1157
+ case 1:
1158
+ optionsResponse = _b.sent();
1159
+ return [4 /*yield*/, startRegistration(optionsResponse.options)];
1160
+ case 2:
1161
+ registrationResponse = _b.sent();
1162
+ return [4 /*yield*/, this.api.addAuthenticator({
1163
+ challengeId: optionsResponse.challengeId,
1164
+ registrationCredential: registrationResponse,
1165
+ token: token
1166
+ })];
1167
+ case 3:
1168
+ addAuthenticatorResponse = _b.sent();
1169
+ return [2 /*return*/, addAuthenticatorResponse === null || addAuthenticatorResponse === void 0 ? void 0 : addAuthenticatorResponse.accessToken];
1170
+ }
1171
+ });
1172
+ });
1173
+ };
1174
+ Passkey.prototype.signIn = function (params) {
1175
+ return __awaiter(this, void 0, void 0, function () {
1176
+ var optionsResponse, authenticationResponse, verifyResponse;
1177
+ return __generator(this, function (_a) {
1178
+ switch (_a.label) {
1179
+ case 0:
1180
+ if ((params === null || params === void 0 ? void 0 : params.token) && params.autofill) {
1181
+ throw new Error("Autofill is not supported when providing a token");
1182
+ }
1183
+ return [4 /*yield*/, this.api.authenticationOptions({ token: params === null || params === void 0 ? void 0 : params.token })];
1184
+ case 1:
1185
+ optionsResponse = _a.sent();
1186
+ return [4 /*yield*/, startAuthentication(optionsResponse.options, params === null || params === void 0 ? void 0 : params.autofill)];
1187
+ case 2:
1188
+ authenticationResponse = _a.sent();
1189
+ return [4 /*yield*/, this.api.verify({
1190
+ challengeId: optionsResponse.challengeId,
1191
+ authenticationCredential: authenticationResponse,
1192
+ token: params === null || params === void 0 ? void 0 : params.token
1193
+ })];
1194
+ case 3:
1195
+ verifyResponse = _a.sent();
1196
+ return [2 /*return*/, verifyResponse === null || verifyResponse === void 0 ? void 0 : verifyResponse.accessToken];
1197
+ }
1198
+ });
1199
+ });
1200
+ };
1201
+ return Passkey;
1202
+ }());
1118
1203
 
1119
- const validateAndMerge = (...sources) => {
1120
- for (const source of sources) {
1121
- if ((!isObject(source) || Array.isArray(source)) && typeof source !== 'undefined') {
1122
- throw new TypeError('The `options` argument must be an object');
1123
- }
1204
+ var DEFAULT_WIDTH$1 = 400;
1205
+ var DEFAULT_HEIGHT = 500;
1206
+ var WindowHandler = /** @class */ (function () {
1207
+ function WindowHandler() {
1208
+ this.windowRef = null;
1124
1209
  }
1125
- return deepMerge({}, ...sources);
1126
- };
1127
- const mergeHeaders = (source1 = {}, source2 = {}) => {
1128
- const result = new globalThis.Headers(source1);
1129
- const isHeadersInstance = source2 instanceof globalThis.Headers;
1130
- const source = new globalThis.Headers(source2);
1131
- for (const [key, value] of source.entries()) {
1132
- if ((isHeadersInstance && value === 'undefined') || value === undefined) {
1133
- result.delete(key);
1210
+ WindowHandler.prototype.show = function (_a) {
1211
+ var url = _a.url, _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH$1 : _b, _c = _a.height, height = _c === void 0 ? DEFAULT_HEIGHT : _c;
1212
+ var windowRef = openWindow({ url: url, width: width, height: height, win: window });
1213
+ if (!windowRef) {
1214
+ throw new Error("Window is not initialized");
1134
1215
  }
1135
- else {
1136
- result.set(key, value);
1216
+ this.windowRef = windowRef;
1217
+ return windowRef;
1218
+ };
1219
+ WindowHandler.prototype.close = function () {
1220
+ if (!this.windowRef) {
1221
+ throw new Error("Window is not initialized");
1137
1222
  }
1223
+ this.windowRef.close();
1224
+ };
1225
+ return WindowHandler;
1226
+ }());
1227
+ function openWindow(_a) {
1228
+ var url = _a.url, width = _a.width, height = _a.height, win = _a.win;
1229
+ if (!win.top) {
1230
+ return null;
1138
1231
  }
1139
- return result;
1140
- };
1141
- // TODO: Make this strongly-typed (no `any`).
1142
- const deepMerge = (...sources) => {
1143
- let returnValue = {};
1144
- let headers = {};
1145
- for (const source of sources) {
1146
- if (Array.isArray(source)) {
1147
- if (!Array.isArray(returnValue)) {
1148
- returnValue = [];
1149
- }
1150
- returnValue = [...returnValue, ...source];
1151
- }
1152
- else if (isObject(source)) {
1153
- for (let [key, value] of Object.entries(source)) {
1154
- if (isObject(value) && key in returnValue) {
1155
- value = deepMerge(returnValue[key], value);
1156
- }
1157
- returnValue = { ...returnValue, [key]: value };
1158
- }
1159
- if (isObject(source.headers)) {
1160
- headers = mergeHeaders(headers, source.headers);
1161
- returnValue.headers = headers;
1162
- }
1163
- }
1164
- }
1165
- return returnValue;
1166
- };
1232
+ var y = win.top.outerHeight / 2 + win.top.screenY - height / 2;
1233
+ var x = win.top.outerWidth / 2 + win.top.screenX - width / 2;
1234
+ 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));
1235
+ }
1167
1236
 
1168
- const supportsRequestStreams = (() => {
1169
- let duplexAccessed = false;
1170
- let hasContentType = false;
1171
- const supportsReadableStream = typeof globalThis.ReadableStream === 'function';
1172
- const supportsRequest = typeof globalThis.Request === 'function';
1173
- if (supportsReadableStream && supportsRequest) {
1174
- hasContentType = new globalThis.Request('https://a.com', {
1175
- body: new globalThis.ReadableStream(),
1176
- method: 'POST',
1177
- // @ts-expect-error - Types are outdated.
1178
- get duplex() {
1179
- duplexAccessed = true;
1180
- return 'half';
1181
- },
1182
- }).headers.has('Content-Type');
1183
- }
1184
- return duplexAccessed && !hasContentType;
1185
- })();
1186
- const supportsAbortController = typeof globalThis.AbortController === 'function';
1187
- const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
1188
- const supportsFormData = typeof globalThis.FormData === 'function';
1189
- const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'];
1190
- const responseTypes = {
1191
- json: 'application/json',
1192
- text: 'text/*',
1193
- formData: 'multipart/form-data',
1194
- arrayBuffer: '*/*',
1195
- blob: '*/*',
1196
- };
1197
- // The maximum value of a 32bit int (see issue #117)
1198
- const maxSafeTimeout = 2147483647;
1199
- const stop = Symbol('stop');
1237
+ var focusableSelectors = [
1238
+ 'a[href]:not([tabindex^="-"])',
1239
+ 'area[href]:not([tabindex^="-"])',
1240
+ 'input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])',
1241
+ 'input[type="radio"]:not([disabled]):not([tabindex^="-"])',
1242
+ 'select:not([disabled]):not([tabindex^="-"])',
1243
+ 'textarea:not([disabled]):not([tabindex^="-"])',
1244
+ 'button:not([disabled]):not([tabindex^="-"])',
1245
+ 'iframe:not([tabindex^="-"])',
1246
+ 'audio[controls]:not([tabindex^="-"])',
1247
+ 'video[controls]:not([tabindex^="-"])',
1248
+ '[contenteditable]:not([tabindex^="-"])',
1249
+ '[tabindex]:not([tabindex^="-"])',
1250
+ ];
1200
1251
 
1201
- const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
1202
- const retryMethods = ['get', 'put', 'head', 'delete', 'options', 'trace'];
1203
- const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
1204
- const retryAfterStatusCodes = [413, 429, 503];
1205
- const defaultRetryOptions = {
1206
- limit: 2,
1207
- methods: retryMethods,
1208
- statusCodes: retryStatusCodes,
1209
- afterStatusCodes: retryAfterStatusCodes,
1210
- maxRetryAfter: Number.POSITIVE_INFINITY,
1211
- backoffLimit: Number.POSITIVE_INFINITY,
1212
- };
1213
- const normalizeRetryOptions = (retry = {}) => {
1214
- if (typeof retry === 'number') {
1215
- return {
1216
- ...defaultRetryOptions,
1217
- limit: retry,
1218
- };
1219
- }
1220
- if (retry.methods && !Array.isArray(retry.methods)) {
1221
- throw new Error('retry.methods must be an array');
1222
- }
1223
- if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
1224
- throw new Error('retry.statusCodes must be an array');
1225
- }
1226
- return {
1227
- ...defaultRetryOptions,
1228
- ...retry,
1229
- afterStatusCodes: retryAfterStatusCodes,
1230
- };
1231
- };
1252
+ var TAB_KEY = 'Tab';
1253
+ var ESCAPE_KEY = 'Escape';
1232
1254
 
1233
- // `Promise.race()` workaround (#91)
1234
- async function timeout(request, abortController, options) {
1235
- return new Promise((resolve, reject) => {
1236
- const timeoutId = setTimeout(() => {
1237
- if (abortController) {
1238
- abortController.abort();
1239
- }
1240
- reject(new TimeoutError(request));
1241
- }, options.timeout);
1242
- void options
1243
- .fetch(request)
1244
- .then(resolve)
1245
- .catch(reject)
1246
- .then(() => {
1247
- clearTimeout(timeoutId);
1248
- });
1249
- });
1250
- }
1255
+ /**
1256
+ * Define the constructor to instantiate a dialog
1257
+ *
1258
+ * @constructor
1259
+ * @param {Element} element
1260
+ */
1261
+ function A11yDialog(element) {
1262
+ // Prebind the functions that will be bound in addEventListener and
1263
+ // removeEventListener to avoid losing references
1264
+ this._show = this.show.bind(this);
1265
+ this._hide = this.hide.bind(this);
1266
+ this._maintainFocus = this._maintainFocus.bind(this);
1267
+ this._bindKeypress = this._bindKeypress.bind(this);
1251
1268
 
1252
- // DOMException is supported on most modern browsers and Node.js 18+.
1253
- // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException#browser_compatibility
1254
- const isDomExceptionSupported = Boolean(globalThis.DOMException);
1255
- // TODO: When targeting Node.js 18, use `signal.throwIfAborted()` (https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted)
1256
- function composeAbortError(signal) {
1257
- /*
1258
- NOTE: Use DomException with AbortError name as specified in MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)
1259
- > When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.
1260
- */
1261
- if (isDomExceptionSupported) {
1262
- return new DOMException(signal?.reason ?? 'The operation was aborted.', 'AbortError');
1263
- }
1264
- // DOMException not supported. Fall back to use of error and override name.
1265
- const error = new Error(signal?.reason ?? 'The operation was aborted.');
1266
- error.name = 'AbortError';
1267
- return error;
1268
- }
1269
+ this.$el = element;
1270
+ this.shown = false;
1271
+ this._id = this.$el.getAttribute('data-a11y-dialog') || this.$el.id;
1272
+ this._previouslyFocused = null;
1273
+ this._listeners = {};
1269
1274
 
1270
- // https://github.com/sindresorhus/delay/tree/ab98ae8dfcb38e1593286c94d934e70d14a4e111
1271
- async function delay(ms, { signal }) {
1272
- return new Promise((resolve, reject) => {
1273
- if (signal) {
1274
- if (signal.aborted) {
1275
- reject(composeAbortError(signal));
1276
- return;
1277
- }
1278
- signal.addEventListener('abort', handleAbort, { once: true });
1279
- }
1280
- function handleAbort() {
1281
- reject(composeAbortError(signal));
1282
- clearTimeout(timeoutId);
1283
- }
1284
- const timeoutId = setTimeout(() => {
1285
- signal?.removeEventListener('abort', handleAbort);
1286
- resolve();
1287
- }, ms);
1288
- });
1275
+ // Initialise everything needed for the dialog to work properly
1276
+ this.create();
1289
1277
  }
1290
1278
 
1291
- class Ky {
1292
- // eslint-disable-next-line @typescript-eslint/promise-function-async
1293
- static create(input, options) {
1294
- const ky = new Ky(input, options);
1295
- const fn = async () => {
1296
- if (ky._options.timeout > maxSafeTimeout) {
1297
- throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
1298
- }
1299
- // Delay the fetch so that body method shortcuts can set the Accept header
1300
- await Promise.resolve();
1301
- let response = await ky._fetch();
1302
- for (const hook of ky._options.hooks.afterResponse) {
1303
- // eslint-disable-next-line no-await-in-loop
1304
- const modifiedResponse = await hook(ky.request, ky._options, ky._decorateResponse(response.clone()));
1305
- if (modifiedResponse instanceof globalThis.Response) {
1306
- response = modifiedResponse;
1307
- }
1308
- }
1309
- ky._decorateResponse(response);
1310
- if (!response.ok && ky._options.throwHttpErrors) {
1311
- let error = new HTTPError(response, ky.request, ky._options);
1312
- for (const hook of ky._options.hooks.beforeError) {
1313
- // eslint-disable-next-line no-await-in-loop
1314
- error = await hook(error);
1315
- }
1316
- throw error;
1317
- }
1318
- // If `onDownloadProgress` is passed, it uses the stream API internally
1319
- /* istanbul ignore next */
1320
- if (ky._options.onDownloadProgress) {
1321
- if (typeof ky._options.onDownloadProgress !== 'function') {
1322
- throw new TypeError('The `onDownloadProgress` option must be a function');
1323
- }
1324
- if (!supportsResponseStreams) {
1325
- throw new Error('Streams are not supported in your environment. `ReadableStream` is missing.');
1326
- }
1327
- return ky._stream(response.clone(), ky._options.onDownloadProgress);
1328
- }
1329
- return response;
1330
- };
1331
- const isRetriableMethod = ky._options.retry.methods.includes(ky.request.method.toLowerCase());
1332
- const result = (isRetriableMethod ? ky._retry(fn) : fn());
1333
- for (const [type, mimeType] of Object.entries(responseTypes)) {
1334
- result[type] = async () => {
1335
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1336
- ky.request.headers.set('accept', ky.request.headers.get('accept') || mimeType);
1337
- const awaitedResult = await result;
1338
- const response = awaitedResult.clone();
1339
- if (type === 'json') {
1340
- if (response.status === 204) {
1341
- return '';
1342
- }
1343
- const arrayBuffer = await response.clone().arrayBuffer();
1344
- const responseSize = arrayBuffer.byteLength;
1345
- if (responseSize === 0) {
1346
- return '';
1347
- }
1348
- if (options.parseJson) {
1349
- return options.parseJson(await response.text());
1350
- }
1351
- }
1352
- return response[type]();
1353
- };
1354
- }
1355
- return result;
1356
- }
1357
- // eslint-disable-next-line complexity
1358
- constructor(input, options = {}) {
1359
- Object.defineProperty(this, "request", {
1360
- enumerable: true,
1361
- configurable: true,
1362
- writable: true,
1363
- value: void 0
1364
- });
1365
- Object.defineProperty(this, "abortController", {
1366
- enumerable: true,
1367
- configurable: true,
1368
- writable: true,
1369
- value: void 0
1370
- });
1371
- Object.defineProperty(this, "_retryCount", {
1372
- enumerable: true,
1373
- configurable: true,
1374
- writable: true,
1375
- value: 0
1376
- });
1377
- Object.defineProperty(this, "_input", {
1378
- enumerable: true,
1379
- configurable: true,
1380
- writable: true,
1381
- value: void 0
1382
- });
1383
- Object.defineProperty(this, "_options", {
1384
- enumerable: true,
1385
- configurable: true,
1386
- writable: true,
1387
- value: void 0
1388
- });
1389
- this._input = input;
1390
- this._options = {
1391
- // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
1392
- credentials: this._input.credentials || 'same-origin',
1393
- ...options,
1394
- headers: mergeHeaders(this._input.headers, options.headers),
1395
- hooks: deepMerge({
1396
- beforeRequest: [],
1397
- beforeRetry: [],
1398
- beforeError: [],
1399
- afterResponse: [],
1400
- }, options.hooks),
1401
- method: normalizeRequestMethod(options.method ?? this._input.method),
1402
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
1403
- prefixUrl: String(options.prefixUrl || ''),
1404
- retry: normalizeRetryOptions(options.retry),
1405
- throwHttpErrors: options.throwHttpErrors !== false,
1406
- timeout: typeof options.timeout === 'undefined' ? 10000 : options.timeout,
1407
- fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
1408
- };
1409
- if (typeof this._input !== 'string' && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
1410
- throw new TypeError('`input` must be a string, URL, or Request');
1411
- }
1412
- if (this._options.prefixUrl && typeof this._input === 'string') {
1413
- if (this._input.startsWith('/')) {
1414
- throw new Error('`input` must not begin with a slash when using `prefixUrl`');
1415
- }
1416
- if (!this._options.prefixUrl.endsWith('/')) {
1417
- this._options.prefixUrl += '/';
1418
- }
1419
- this._input = this._options.prefixUrl + this._input;
1420
- }
1421
- if (supportsAbortController) {
1422
- this.abortController = new globalThis.AbortController();
1423
- if (this._options.signal) {
1424
- const originalSignal = this._options.signal;
1425
- this._options.signal.addEventListener('abort', () => {
1426
- this.abortController.abort(originalSignal.reason);
1427
- });
1428
- }
1429
- this._options.signal = this.abortController.signal;
1430
- }
1431
- if (supportsRequestStreams) {
1432
- // @ts-expect-error - Types are outdated.
1433
- this._options.duplex = 'half';
1434
- }
1435
- this.request = new globalThis.Request(this._input, this._options);
1436
- if (this._options.searchParams) {
1437
- // eslint-disable-next-line unicorn/prevent-abbreviations
1438
- const textSearchParams = typeof this._options.searchParams === 'string'
1439
- ? this._options.searchParams.replace(/^\?/, '')
1440
- : new URLSearchParams(this._options.searchParams).toString();
1441
- // eslint-disable-next-line unicorn/prevent-abbreviations
1442
- const searchParams = '?' + textSearchParams;
1443
- const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
1444
- // To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
1445
- if (((supportsFormData && this._options.body instanceof globalThis.FormData)
1446
- || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
1447
- this.request.headers.delete('content-type');
1448
- }
1449
- // The spread of `this.request` is required as otherwise it misses the `duplex` option for some reason and throws.
1450
- this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
1451
- }
1452
- if (this._options.json !== undefined) {
1453
- this._options.body = JSON.stringify(this._options.json);
1454
- this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json');
1455
- this.request = new globalThis.Request(this.request, { body: this._options.body });
1456
- }
1457
- }
1458
- _calculateRetryDelay(error) {
1459
- this._retryCount++;
1460
- if (this._retryCount < this._options.retry.limit && !(error instanceof TimeoutError)) {
1461
- if (error instanceof HTTPError) {
1462
- if (!this._options.retry.statusCodes.includes(error.response.status)) {
1463
- return 0;
1464
- }
1465
- const retryAfter = error.response.headers.get('Retry-After');
1466
- if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
1467
- let after = Number(retryAfter);
1468
- if (Number.isNaN(after)) {
1469
- after = Date.parse(retryAfter) - Date.now();
1470
- }
1471
- else {
1472
- after *= 1000;
1473
- }
1474
- if (typeof this._options.retry.maxRetryAfter !== 'undefined' && after > this._options.retry.maxRetryAfter) {
1475
- return 0;
1476
- }
1477
- return after;
1478
- }
1479
- if (error.response.status === 413) {
1480
- return 0;
1481
- }
1482
- }
1483
- const BACKOFF_FACTOR = 0.3;
1484
- return Math.min(this._options.retry.backoffLimit, BACKOFF_FACTOR * (2 ** (this._retryCount - 1)) * 1000);
1485
- }
1486
- return 0;
1487
- }
1488
- _decorateResponse(response) {
1489
- if (this._options.parseJson) {
1490
- response.json = async () => this._options.parseJson(await response.text());
1491
- }
1492
- return response;
1493
- }
1494
- async _retry(fn) {
1495
- try {
1496
- return await fn();
1497
- // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch
1498
- }
1499
- catch (error) {
1500
- const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
1501
- if (ms !== 0 && this._retryCount > 0) {
1502
- await delay(ms, { signal: this._options.signal });
1503
- for (const hook of this._options.hooks.beforeRetry) {
1504
- // eslint-disable-next-line no-await-in-loop
1505
- const hookResult = await hook({
1506
- request: this.request,
1507
- options: this._options,
1508
- error: error,
1509
- retryCount: this._retryCount,
1510
- });
1511
- // If `stop` is returned from the hook, the retry process is stopped
1512
- if (hookResult === stop) {
1513
- return;
1514
- }
1515
- }
1516
- return this._retry(fn);
1517
- }
1518
- throw error;
1519
- }
1520
- }
1521
- async _fetch() {
1522
- for (const hook of this._options.hooks.beforeRequest) {
1523
- // eslint-disable-next-line no-await-in-loop
1524
- const result = await hook(this.request, this._options);
1525
- if (result instanceof Request) {
1526
- this.request = result;
1527
- break;
1528
- }
1529
- if (result instanceof Response) {
1530
- return result;
1531
- }
1532
- }
1533
- if (this._options.timeout === false) {
1534
- return this._options.fetch(this.request.clone());
1535
- }
1536
- return timeout(this.request.clone(), this.abortController, this._options);
1537
- }
1538
- /* istanbul ignore next */
1539
- _stream(response, onDownloadProgress) {
1540
- const totalBytes = Number(response.headers.get('content-length')) || 0;
1541
- let transferredBytes = 0;
1542
- if (response.status === 204) {
1543
- if (onDownloadProgress) {
1544
- onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
1545
- }
1546
- return new globalThis.Response(null, {
1547
- status: response.status,
1548
- statusText: response.statusText,
1549
- headers: response.headers,
1550
- });
1551
- }
1552
- return new globalThis.Response(new globalThis.ReadableStream({
1553
- async start(controller) {
1554
- const reader = response.body.getReader();
1555
- if (onDownloadProgress) {
1556
- onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
1557
- }
1558
- async function read() {
1559
- const { done, value } = await reader.read();
1560
- if (done) {
1561
- controller.close();
1562
- return;
1563
- }
1564
- if (onDownloadProgress) {
1565
- transferredBytes += value.byteLength;
1566
- const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
1567
- onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
1568
- }
1569
- controller.enqueue(value);
1570
- await read();
1571
- }
1572
- await read();
1573
- },
1574
- }), {
1575
- status: response.status,
1576
- statusText: response.statusText,
1577
- headers: response.headers,
1578
- });
1579
- }
1279
+ /**
1280
+ * Set up everything necessary for the dialog to be functioning
1281
+ *
1282
+ * @param {(NodeList | Element | string)} targets
1283
+ * @return {this}
1284
+ */
1285
+ A11yDialog.prototype.create = function () {
1286
+ this.$el.setAttribute('aria-hidden', true);
1287
+ this.$el.setAttribute('aria-modal', true);
1288
+ this.$el.setAttribute('tabindex', -1);
1289
+
1290
+ if (!this.$el.hasAttribute('role')) {
1291
+ this.$el.setAttribute('role', 'dialog');
1292
+ }
1293
+
1294
+ // Keep a collection of dialog openers, each of which will be bound a click
1295
+ // event listener to open the dialog
1296
+ this._openers = $$('[data-a11y-dialog-show="' + this._id + '"]');
1297
+ this._openers.forEach(
1298
+ function (opener) {
1299
+ opener.addEventListener('click', this._show);
1300
+ }.bind(this)
1301
+ );
1302
+
1303
+ // Keep a collection of dialog closers, each of which will be bound a click
1304
+ // event listener to close the dialog
1305
+ const $el = this.$el;
1306
+
1307
+ this._closers = $$('[data-a11y-dialog-hide]', this.$el)
1308
+ // This filter is necessary in case there are nested dialogs, so that
1309
+ // only closers from the current dialog are retrieved and effective
1310
+ .filter(function (closer) {
1311
+ // Testing for `[aria-modal="true"]` is not enough since this attribute
1312
+ // and the collect of closers is done at instantation time, when nested
1313
+ // dialogs might not have yet been instantiated. Note that if the dialogs
1314
+ // are manually instantiated, this could still fail because none of these
1315
+ // selectors would match; this would cause closers to close all parent
1316
+ // dialogs instead of just the current one
1317
+ return closer.closest('[aria-modal="true"], [data-a11y-dialog]') === $el
1318
+ })
1319
+ .concat($$('[data-a11y-dialog-hide="' + this._id + '"]'));
1320
+
1321
+ this._closers.forEach(
1322
+ function (closer) {
1323
+ closer.addEventListener('click', this._hide);
1324
+ }.bind(this)
1325
+ );
1326
+
1327
+ // Execute all callbacks registered for the `create` event
1328
+ this._fire('create');
1329
+
1330
+ return this
1331
+ };
1332
+
1333
+ /**
1334
+ * Show the dialog element, disable all the targets (siblings), trap the
1335
+ * current focus within it, listen for some specific key presses and fire all
1336
+ * registered callbacks for `show` event
1337
+ *
1338
+ * @param {CustomEvent} event
1339
+ * @return {this}
1340
+ */
1341
+ A11yDialog.prototype.show = function (event) {
1342
+ // If the dialog is already open, abort
1343
+ if (this.shown) {
1344
+ return this
1345
+ }
1346
+
1347
+ // Keep a reference to the currently focused element to be able to restore
1348
+ // it later
1349
+ this._previouslyFocused = document.activeElement;
1350
+ this.$el.removeAttribute('aria-hidden');
1351
+ this.shown = true;
1352
+
1353
+ // Set the focus to the dialog element
1354
+ moveFocusToDialog(this.$el);
1355
+
1356
+ // Bind a focus event listener to the body element to make sure the focus
1357
+ // stays trapped inside the dialog while open, and start listening for some
1358
+ // specific key presses (TAB and ESC)
1359
+ document.body.addEventListener('focus', this._maintainFocus, true);
1360
+ document.addEventListener('keydown', this._bindKeypress);
1361
+
1362
+ // Execute all callbacks registered for the `show` event
1363
+ this._fire('show', event);
1364
+
1365
+ return this
1366
+ };
1367
+
1368
+ /**
1369
+ * Hide the dialog element, enable all the targets (siblings), restore the
1370
+ * focus to the previously active element, stop listening for some specific
1371
+ * key presses and fire all registered callbacks for `hide` event
1372
+ *
1373
+ * @param {CustomEvent} event
1374
+ * @return {this}
1375
+ */
1376
+ A11yDialog.prototype.hide = function (event) {
1377
+ // If the dialog is already closed, abort
1378
+ if (!this.shown) {
1379
+ return this
1380
+ }
1381
+
1382
+ this.shown = false;
1383
+ this.$el.setAttribute('aria-hidden', 'true');
1384
+
1385
+ // If there was a focused element before the dialog was opened (and it has a
1386
+ // `focus` method), restore the focus back to it
1387
+ // See: https://github.com/KittyGiraudel/a11y-dialog/issues/108
1388
+ if (this._previouslyFocused && this._previouslyFocused.focus) {
1389
+ this._previouslyFocused.focus();
1390
+ }
1391
+
1392
+ // Remove the focus event listener to the body element and stop listening
1393
+ // for specific key presses
1394
+ document.body.removeEventListener('focus', this._maintainFocus, true);
1395
+ document.removeEventListener('keydown', this._bindKeypress);
1396
+
1397
+ // Execute all callbacks registered for the `hide` event
1398
+ this._fire('hide', event);
1399
+
1400
+ return this
1401
+ };
1402
+
1403
+ /**
1404
+ * Destroy the current instance (after making sure the dialog has been hidden)
1405
+ * and remove all associated listeners from dialog openers and closers
1406
+ *
1407
+ * @return {this}
1408
+ */
1409
+ A11yDialog.prototype.destroy = function () {
1410
+ // Hide the dialog to avoid destroying an open instance
1411
+ this.hide();
1412
+
1413
+ // Remove the click event listener from all dialog openers
1414
+ this._openers.forEach(
1415
+ function (opener) {
1416
+ opener.removeEventListener('click', this._show);
1417
+ }.bind(this)
1418
+ );
1419
+
1420
+ // Remove the click event listener from all dialog closers
1421
+ this._closers.forEach(
1422
+ function (closer) {
1423
+ closer.removeEventListener('click', this._hide);
1424
+ }.bind(this)
1425
+ );
1426
+
1427
+ // Execute all callbacks registered for the `destroy` event
1428
+ this._fire('destroy');
1429
+
1430
+ // Keep an object of listener types mapped to callback functions
1431
+ this._listeners = {};
1432
+
1433
+ return this
1434
+ };
1435
+
1436
+ /**
1437
+ * Register a new callback for the given event type
1438
+ *
1439
+ * @param {string} type
1440
+ * @param {Function} handler
1441
+ */
1442
+ A11yDialog.prototype.on = function (type, handler) {
1443
+ if (typeof this._listeners[type] === 'undefined') {
1444
+ this._listeners[type] = [];
1445
+ }
1446
+
1447
+ this._listeners[type].push(handler);
1448
+
1449
+ return this
1450
+ };
1451
+
1452
+ /**
1453
+ * Unregister an existing callback for the given event type
1454
+ *
1455
+ * @param {string} type
1456
+ * @param {Function} handler
1457
+ */
1458
+ A11yDialog.prototype.off = function (type, handler) {
1459
+ var index = (this._listeners[type] || []).indexOf(handler);
1460
+
1461
+ if (index > -1) {
1462
+ this._listeners[type].splice(index, 1);
1463
+ }
1464
+
1465
+ return this
1466
+ };
1467
+
1468
+ /**
1469
+ * Iterate over all registered handlers for given type and call them all with
1470
+ * the dialog element as first argument, event as second argument (if any). Also
1471
+ * dispatch a custom event on the DOM element itself to make it possible to
1472
+ * react to the lifecycle of auto-instantiated dialogs.
1473
+ *
1474
+ * @access private
1475
+ * @param {string} type
1476
+ * @param {CustomEvent} event
1477
+ */
1478
+ A11yDialog.prototype._fire = function (type, event) {
1479
+ var listeners = this._listeners[type] || [];
1480
+ var domEvent = new CustomEvent(type, { detail: event });
1481
+
1482
+ this.$el.dispatchEvent(domEvent);
1483
+
1484
+ listeners.forEach(
1485
+ function (listener) {
1486
+ listener(this.$el, event);
1487
+ }.bind(this)
1488
+ );
1489
+ };
1490
+
1491
+ /**
1492
+ * Private event handler used when listening to some specific key presses
1493
+ * (namely ESCAPE and TAB)
1494
+ *
1495
+ * @access private
1496
+ * @param {Event} event
1497
+ */
1498
+ A11yDialog.prototype._bindKeypress = function (event) {
1499
+ // This is an escape hatch in case there are nested dialogs, so the keypresses
1500
+ // are only reacted to for the most recent one
1501
+ const focused = document.activeElement;
1502
+ if (focused && focused.closest('[aria-modal="true"]') !== this.$el) return
1503
+
1504
+ // If the dialog is shown and the ESCAPE key is being pressed, prevent any
1505
+ // further effects from the ESCAPE key and hide the dialog, unless its role
1506
+ // is 'alertdialog', which should be modal
1507
+ if (
1508
+ this.shown &&
1509
+ event.key === ESCAPE_KEY &&
1510
+ this.$el.getAttribute('role') !== 'alertdialog'
1511
+ ) {
1512
+ event.preventDefault();
1513
+ this.hide(event);
1514
+ }
1515
+
1516
+ // If the dialog is shown and the TAB key is being pressed, make sure the
1517
+ // focus stays trapped within the dialog element
1518
+ if (this.shown && event.key === TAB_KEY) {
1519
+ trapTabKey(this.$el, event);
1520
+ }
1521
+ };
1522
+
1523
+ /**
1524
+ * Private event handler used when making sure the focus stays within the
1525
+ * currently open dialog
1526
+ *
1527
+ * @access private
1528
+ * @param {Event} event
1529
+ */
1530
+ A11yDialog.prototype._maintainFocus = function (event) {
1531
+ // If the dialog is shown and the focus is not within a dialog element (either
1532
+ // this one or another one in case of nested dialogs) or within an element
1533
+ // with the `data-a11y-dialog-focus-trap-ignore` attribute, move it back to
1534
+ // its first focusable child.
1535
+ // See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
1536
+ if (
1537
+ this.shown &&
1538
+ !event.target.closest('[aria-modal="true"]') &&
1539
+ !event.target.closest('[data-a11y-dialog-ignore-focus-trap]')
1540
+ ) {
1541
+ moveFocusToDialog(this.$el);
1542
+ }
1543
+ };
1544
+
1545
+ /**
1546
+ * Convert a NodeList into an array
1547
+ *
1548
+ * @param {NodeList} collection
1549
+ * @return {Array<Element>}
1550
+ */
1551
+ function toArray(collection) {
1552
+ return Array.prototype.slice.call(collection)
1553
+ }
1554
+
1555
+ /**
1556
+ * Query the DOM for nodes matching the given selector, scoped to context (or
1557
+ * the whole document)
1558
+ *
1559
+ * @param {String} selector
1560
+ * @param {Element} [context = document]
1561
+ * @return {Array<Element>}
1562
+ */
1563
+ function $$(selector, context) {
1564
+ return toArray((context || document).querySelectorAll(selector))
1565
+ }
1566
+
1567
+ /**
1568
+ * Set the focus to the first element with `autofocus` with the element or the
1569
+ * element itself
1570
+ *
1571
+ * @param {Element} node
1572
+ */
1573
+ function moveFocusToDialog(node) {
1574
+ var focused = node.querySelector('[autofocus]') || node;
1575
+
1576
+ focused.focus();
1577
+ }
1578
+
1579
+ /**
1580
+ * Get the focusable children of the given element
1581
+ *
1582
+ * @param {Element} node
1583
+ * @return {Array<Element>}
1584
+ */
1585
+ function getFocusableChildren(node) {
1586
+ return $$(focusableSelectors.join(','), node).filter(function (child) {
1587
+ return !!(
1588
+ child.offsetWidth ||
1589
+ child.offsetHeight ||
1590
+ child.getClientRects().length
1591
+ )
1592
+ })
1593
+ }
1594
+
1595
+ /**
1596
+ * Trap the focus inside the given element
1597
+ *
1598
+ * @param {Element} node
1599
+ * @param {Event} event
1600
+ */
1601
+ function trapTabKey(node, event) {
1602
+ var focusableChildren = getFocusableChildren(node);
1603
+ var focusedItemIndex = focusableChildren.indexOf(document.activeElement);
1604
+
1605
+ // If the SHIFT key is being pressed while tabbing (moving backwards) and
1606
+ // the currently focused item is the first one, move the focus to the last
1607
+ // focusable item from the dialog element
1608
+ if (event.shiftKey && focusedItemIndex === 0) {
1609
+ focusableChildren[focusableChildren.length - 1].focus();
1610
+ event.preventDefault();
1611
+ // If the SHIFT key is not being pressed (moving forwards) and the currently
1612
+ // focused item is the last one, move the focus to the first focusable item
1613
+ // from the dialog element
1614
+ } else if (
1615
+ !event.shiftKey &&
1616
+ focusedItemIndex === focusableChildren.length - 1
1617
+ ) {
1618
+ focusableChildren[0].focus();
1619
+ event.preventDefault();
1620
+ }
1621
+ }
1622
+
1623
+ function instantiateDialogs() {
1624
+ $$('[data-a11y-dialog]').forEach(function (node) {
1625
+ new A11yDialog(node);
1626
+ });
1580
1627
  }
1581
1628
 
1582
- /*! MIT License © Sindre Sorhus */
1583
- const createInstance = (defaults) => {
1584
- // eslint-disable-next-line @typescript-eslint/promise-function-async
1585
- const ky = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
1586
- for (const method of requestMethods) {
1587
- // eslint-disable-next-line @typescript-eslint/promise-function-async
1588
- ky[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
1629
+ if (typeof document !== 'undefined') {
1630
+ if (document.readyState === 'loading') {
1631
+ document.addEventListener('DOMContentLoaded', instantiateDialogs);
1632
+ } else {
1633
+ if (window.requestAnimationFrame) {
1634
+ window.requestAnimationFrame(instantiateDialogs);
1635
+ } else {
1636
+ window.setTimeout(instantiateDialogs, 16);
1589
1637
  }
1590
- ky.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
1591
- ky.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
1592
- ky.stop = stop;
1593
- return ky;
1594
- };
1595
- const ky = createInstance();
1596
- var ky$1 = ky;
1638
+ }
1639
+ }
1597
1640
 
1598
- var PasskeyApiClient = /** @class */ (function () {
1599
- function PasskeyApiClient(_a) {
1600
- var baseUrl = _a.baseUrl, tenantId = _a.tenantId;
1601
- this.tenantId = tenantId;
1602
- this.api = ky$1.create({
1603
- prefixUrl: baseUrl
1604
- });
1641
+ var CONTAINER_ID = "__authsignal-popup-container";
1642
+ var CONTENT_ID = "__authsignal-popup-content";
1643
+ var OVERLAY_ID = "__authsignal-popup-overlay";
1644
+ var STYLE_ID = "__authsignal-popup-style";
1645
+ var IFRAME_ID = "__authsignal-popup-iframe";
1646
+ var DEFAULT_WIDTH = "385px";
1647
+ var INITIAL_HEIGHT = "384px";
1648
+ var PopupHandler = /** @class */ (function () {
1649
+ function PopupHandler(_a) {
1650
+ var width = _a.width;
1651
+ this.popup = null;
1652
+ if (document.querySelector("#".concat(CONTAINER_ID))) {
1653
+ throw new Error("Multiple instances of Authsignal popup is not supported.");
1654
+ }
1655
+ this.create({ width: width });
1605
1656
  }
1606
- PasskeyApiClient.prototype.registrationOptions = function (_a) {
1607
- var token = _a.token, userName = _a.userName;
1608
- return __awaiter(this, void 0, void 0, function () {
1609
- var response;
1610
- return __generator(this, function (_b) {
1611
- switch (_b.label) {
1612
- case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey/registration-options", {
1613
- json: { username: userName },
1614
- headers: this.buildHeaders(token)
1615
- })];
1616
- case 1:
1617
- response = _b.sent();
1618
- return [2 /*return*/, response.json()];
1619
- }
1620
- });
1657
+ PopupHandler.prototype.create = function (_a) {
1658
+ var _this = this;
1659
+ var _b = _a.width, width = _b === void 0 ? DEFAULT_WIDTH : _b;
1660
+ var isWidthValidCSSValue = CSS.supports("width", width);
1661
+ var popupWidth = width;
1662
+ if (!isWidthValidCSSValue) {
1663
+ console.warn("Invalid CSS value for `popupOptions.width`. Using default value instead.");
1664
+ popupWidth = DEFAULT_WIDTH;
1665
+ }
1666
+ // Create dialog container
1667
+ var container = document.createElement("div");
1668
+ container.setAttribute("id", CONTAINER_ID);
1669
+ container.setAttribute("aria-hidden", "true");
1670
+ // Create dialog overlay
1671
+ var overlay = document.createElement("div");
1672
+ overlay.setAttribute("id", OVERLAY_ID);
1673
+ overlay.setAttribute("data-a11y-dialog-hide", "true");
1674
+ // Create dialog content
1675
+ var content = document.createElement("div");
1676
+ content.setAttribute("id", CONTENT_ID);
1677
+ document.body.appendChild(container);
1678
+ // Create CSS for dialog
1679
+ var style = document.createElement("style");
1680
+ style.setAttribute("id", STYLE_ID);
1681
+ style.textContent = "\n #".concat(CONTAINER_ID, ",\n #").concat(OVERLAY_ID, " {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n\n #").concat(CONTAINER_ID, " {\n z-index: 2147483647;\n display: flex;\n }\n\n #").concat(CONTAINER_ID, "[aria-hidden='true'] {\n display: none;\n }\n\n #").concat(OVERLAY_ID, " {\n background-color: rgba(0, 0, 0, 0.18);\n }\n\n #").concat(CONTENT_ID, " {\n margin: auto;\n z-index: 2147483647;\n position: relative;\n background-color: transparent;\n border-radius: 8px;\n width: ").concat(popupWidth, ";\n }\n\n #").concat(CONTENT_ID, " iframe {\n width: 1px;\n min-width: 100%;\n border-radius: inherit;\n max-height: 95vh;\n height: ").concat(INITIAL_HEIGHT, ";\n }\n ");
1682
+ // Attach the created elements
1683
+ document.head.insertAdjacentElement("beforeend", style);
1684
+ container.appendChild(overlay);
1685
+ container.appendChild(content);
1686
+ this.popup = new A11yDialog(container);
1687
+ // Make sure to remove any trace of the dialog on hide
1688
+ this.popup.on("hide", function () {
1689
+ _this.destroy();
1621
1690
  });
1622
1691
  };
1623
- PasskeyApiClient.prototype.authenticationOptions = function (_a) {
1624
- var token = _a.token;
1625
- return __awaiter(this, void 0, void 0, function () {
1626
- var response;
1627
- return __generator(this, function (_b) {
1628
- switch (_b.label) {
1629
- case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey/authentication-options", {
1630
- json: {},
1631
- headers: this.buildHeaders(token)
1632
- })];
1633
- case 1:
1634
- response = _b.sent();
1635
- return [2 /*return*/, response.json()];
1636
- }
1637
- });
1638
- });
1692
+ PopupHandler.prototype.destroy = function () {
1693
+ var dialogEl = document.querySelector("#".concat(CONTAINER_ID));
1694
+ var styleEl = document.querySelector("#".concat(STYLE_ID));
1695
+ if (dialogEl && styleEl) {
1696
+ document.body.removeChild(dialogEl);
1697
+ document.head.removeChild(styleEl);
1698
+ }
1699
+ window.removeEventListener("message", resizeIframe);
1639
1700
  };
1640
- PasskeyApiClient.prototype.addAuthenticator = function (_a) {
1641
- var token = _a.token, rest = __rest(_a, ["token"]);
1642
- return __awaiter(this, void 0, void 0, function () {
1643
- var response;
1644
- return __generator(this, function (_b) {
1645
- switch (_b.label) {
1646
- case 0: return [4 /*yield*/, this.api.post("client/user-authenticators/passkey", {
1647
- json: rest,
1648
- headers: this.buildHeaders(token)
1649
- })];
1650
- case 1:
1651
- response = _b.sent();
1652
- return [2 /*return*/, response.json()];
1653
- }
1654
- });
1655
- });
1701
+ PopupHandler.prototype.show = function (_a) {
1702
+ var _b;
1703
+ var url = _a.url;
1704
+ if (!this.popup) {
1705
+ throw new Error("Popup is not initialized");
1706
+ }
1707
+ var iframe = document.createElement("iframe");
1708
+ iframe.setAttribute("id", IFRAME_ID);
1709
+ iframe.setAttribute("name", "authsignal");
1710
+ iframe.setAttribute("title", "Authsignal multi-factor authentication");
1711
+ iframe.setAttribute("src", url);
1712
+ iframe.setAttribute("frameborder", "0");
1713
+ iframe.setAttribute("allow", "publickey-credentials-get *; publickey-credentials-create *; clipboard-write");
1714
+ var dialogContent = document.querySelector("#".concat(CONTENT_ID));
1715
+ if (dialogContent) {
1716
+ dialogContent.appendChild(iframe);
1717
+ }
1718
+ window.addEventListener("message", resizeIframe);
1719
+ (_b = this.popup) === null || _b === void 0 ? void 0 : _b.show();
1656
1720
  };
1657
- PasskeyApiClient.prototype.verify = function (_a) {
1658
- var token = _a.token, rest = __rest(_a, ["token"]);
1659
- return __awaiter(this, void 0, void 0, function () {
1660
- var response;
1661
- return __generator(this, function (_b) {
1662
- switch (_b.label) {
1663
- case 0: return [4 /*yield*/, this.api.post("client/verify/passkey", {
1664
- json: rest,
1665
- headers: this.buildHeaders(token)
1666
- })];
1667
- case 1:
1668
- response = _b.sent();
1669
- return [2 /*return*/, response.json()];
1670
- }
1671
- });
1672
- });
1721
+ PopupHandler.prototype.close = function () {
1722
+ if (!this.popup) {
1723
+ throw new Error("Popup is not initialized");
1724
+ }
1725
+ this.popup.hide();
1673
1726
  };
1674
- PasskeyApiClient.prototype.buildHeaders = function (token) {
1675
- var authorizationHeader = token ? "Bearer ".concat(token) : "Basic ".concat(window.btoa(encodeURIComponent(this.tenantId)));
1676
- return {
1677
- Authorization: authorizationHeader
1678
- };
1727
+ PopupHandler.prototype.on = function (event, handler) {
1728
+ if (!this.popup) {
1729
+ throw new Error("Popup is not initialized");
1730
+ }
1731
+ this.popup.on(event, handler);
1679
1732
  };
1680
- return PasskeyApiClient;
1733
+ return PopupHandler;
1681
1734
  }());
1682
-
1683
- var Passkey = /** @class */ (function () {
1684
- function Passkey(_a) {
1685
- var baseUrl = _a.baseUrl, tenantId = _a.tenantId;
1686
- this.api = new PasskeyApiClient({ baseUrl: baseUrl, tenantId: tenantId });
1735
+ function resizeIframe(event) {
1736
+ var iframeEl = document.querySelector("#".concat(IFRAME_ID));
1737
+ if (iframeEl && event.data.height) {
1738
+ iframeEl.style.height = event.data.height + "px";
1687
1739
  }
1688
- Passkey.prototype.signUp = function (_a) {
1689
- var userName = _a.userName, token = _a.token;
1690
- return __awaiter(this, void 0, void 0, function () {
1691
- var optionsResponse, registrationResponse, addAuthenticatorResponse;
1692
- return __generator(this, function (_b) {
1693
- switch (_b.label) {
1694
- case 0: return [4 /*yield*/, this.api.registrationOptions({ userName: userName, token: token })];
1695
- case 1:
1696
- optionsResponse = _b.sent();
1697
- return [4 /*yield*/, startRegistration(optionsResponse.options)];
1698
- case 2:
1699
- registrationResponse = _b.sent();
1700
- return [4 /*yield*/, this.api.addAuthenticator({
1701
- challengeId: optionsResponse.challengeId,
1702
- registrationCredential: registrationResponse,
1703
- token: token
1704
- })];
1705
- case 3:
1706
- addAuthenticatorResponse = _b.sent();
1707
- return [2 /*return*/, addAuthenticatorResponse === null || addAuthenticatorResponse === void 0 ? void 0 : addAuthenticatorResponse.accessToken];
1708
- }
1709
- });
1710
- });
1711
- };
1712
- Passkey.prototype.signIn = function (params) {
1713
- return __awaiter(this, void 0, void 0, function () {
1714
- var optionsResponse, authenticationResponse, verifyResponse;
1715
- return __generator(this, function (_a) {
1716
- switch (_a.label) {
1717
- case 0:
1718
- if ((params === null || params === void 0 ? void 0 : params.token) && params.autofill) {
1719
- throw new Error("Autofill is not supported when providing a token");
1720
- }
1721
- return [4 /*yield*/, this.api.authenticationOptions({ token: params === null || params === void 0 ? void 0 : params.token })];
1722
- case 1:
1723
- optionsResponse = _a.sent();
1724
- return [4 /*yield*/, startAuthentication(optionsResponse.options, params === null || params === void 0 ? void 0 : params.autofill)];
1725
- case 2:
1726
- authenticationResponse = _a.sent();
1727
- return [4 /*yield*/, this.api.verify({
1728
- challengeId: optionsResponse.challengeId,
1729
- authenticationCredential: authenticationResponse,
1730
- token: params === null || params === void 0 ? void 0 : params.token
1731
- })];
1732
- case 3:
1733
- verifyResponse = _a.sent();
1734
- return [2 /*return*/, verifyResponse === null || verifyResponse === void 0 ? void 0 : verifyResponse.accessToken];
1735
- }
1736
- });
1737
- });
1738
- };
1739
- return Passkey;
1740
- }());
1740
+ }
1741
1741
 
1742
1742
  var DEFAULT_COOKIE_NAME = "__as_aid";
1743
+ var DEFAULT_PROFILING_COOKIE_NAME = "__as_pid";
1743
1744
  var DEFAULT_BASE_URL = "https://api.authsignal.com/v1";
1745
+ var TMX_ORG_ID = "4a08uqve";
1744
1746
  var Authsignal = /** @class */ (function () {
1745
1747
  function Authsignal(_a) {
1746
1748
  var cookieDomain = _a.cookieDomain, _b = _a.cookieName, cookieName = _b === void 0 ? DEFAULT_COOKIE_NAME : _b, _c = _a.baseUrl, baseUrl = _c === void 0 ? DEFAULT_BASE_URL : _c, tenantId = _a.tenantId;
1747
1749
  this.anonymousId = "";
1750
+ this.profilingId = "";
1748
1751
  this.cookieDomain = "";
1749
1752
  this.anonymousIdCookieName = "";
1750
1753
  this._token = undefined;
@@ -1780,6 +1783,40 @@ var Authsignal = /** @class */ (function () {
1780
1783
  this.launchWithRedirect(url);
1781
1784
  }
1782
1785
  };
1786
+ Authsignal.prototype.initAdvancedProfiling = function (baseUrl) {
1787
+ var profilingId = v4();
1788
+ this.profilingId = profilingId;
1789
+ setCookie({
1790
+ name: DEFAULT_PROFILING_COOKIE_NAME,
1791
+ value: profilingId,
1792
+ expire: Infinity,
1793
+ domain: this.cookieDomain,
1794
+ secure: document.location.protocol !== "http:"
1795
+ });
1796
+ var tmxProfilingScruiptUrl = baseUrl
1797
+ ? "".concat(baseUrl, "/fp/tags.js?org_id=").concat(TMX_ORG_ID, "&session_id=").concat(profilingId)
1798
+ : "https://h.online-metrix.net/fp/tags.js?org_id=".concat(TMX_ORG_ID, "&session_id=").concat(profilingId);
1799
+ var script = document.createElement("script");
1800
+ script.src = tmxProfilingScruiptUrl;
1801
+ script.async = false;
1802
+ script.id = "as_adv_profile";
1803
+ document.head.appendChild(script);
1804
+ var pixelContainer = document.createElement("noscript");
1805
+ pixelContainer.setAttribute("id", "as_adv_profile_pixel");
1806
+ pixelContainer.setAttribute("aria-hidden", "true");
1807
+ // Instantiate Pixel
1808
+ var iframe = document.createElement("iframe");
1809
+ var profilingPixelUrl = baseUrl
1810
+ ? "".concat(baseUrl, "/fp/tags?org_id=").concat(TMX_ORG_ID, "&session_id=").concat(profilingId)
1811
+ : "https://h.online-metrix.net/fp/tags?org_id=".concat(TMX_ORG_ID, "&session_id=").concat(profilingId);
1812
+ iframe.setAttribute("id", "as_adv_profile_pixel");
1813
+ iframe.setAttribute("src", profilingPixelUrl);
1814
+ iframe.setAttribute("style", "width: 100px; height: 100px; border: 0; position: absolute; top: -5000px;");
1815
+ if (pixelContainer) {
1816
+ pixelContainer.appendChild(iframe);
1817
+ document.body.prepend(pixelContainer);
1818
+ }
1819
+ };
1783
1820
  Authsignal.prototype.launchWithRedirect = function (url) {
1784
1821
  window.location.href = url;
1785
1822
  };