@flun/webauthn-browser 2.0.18 → 2.1.0

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/CHANGELOG.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # 变更日志
2
+ ## [2.1.0] - 2026-07-18 22:22
3
+ ### 优化
4
+ - 验证顺序改变:在Windows下优先调用底层Windows hello 注册/认证,然后才是标准模式;
5
+ - 不再支持 ES5 ;
6
+
7
+ ## [2.0.19] - 2026-07-15 19:25
8
+ ### 更新
9
+ - 依赖包(@types/node)更新;
2
10
  ## [2.0.18] - 2026-07-06 16:17
3
11
  ### 优化
4
- - 说明文件中 `allowScripts` 配置示例细节优化;
5
- ## [2.0.16] - 2026-07-06 15:49
6
- ### 新增
7
- - 在 README 中新增“配置”章节,指导用户通过 `allowScripts` 显式放行本包的安装脚本,避免因 `ignore-scripts` 全局配置导致钩子被跳过。
12
+ - 说明文件中 `allowScripts` 配置示例细节优化;
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ![WebAuthn](https://img.shields.io/badge/WebAuthn-Browser_Simplified-blueviolet?style=for-the-badge&logo=WebAuthn)
4
4
  [![npm (scoped)](https://img.shields.io/npm/v/@flun/webauthn-browser?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/@flun/webauthn-browser)
5
5
 
6
- **@flun/webauthn-browser** 是一个专为浏览器环境设计的 WebAuthn(含 Passkeys)前端工具库,基于 [@simplewebauthn/browser](https://simplewebauthn.dev/) 核心逻辑重构并增强,它封装了 `navigator.credentials` API 的复杂细节,提供简洁的 Promise 风格接口,并内置了丰富的错误处理与 Base64URL 编解码工具;
6
+ **@flun/webauthn-browser** 是一个专为浏览器环境设计的 WebAuthn(含 Passkeys)前端工具库,基于 [@simplewebauthn/browser](https://simplewebauthn.dev/) 核心逻辑重构并增强,它封装了 `navigator.credentials` API 的复杂细节,提供简洁的 Promise 风格接口,并内置了丰富的错误处理与 Base64URL 编解码工具,并且新增在Windows下优先调用底层Windows hello 注册/认证(避免环境干扰与安全);
7
7
 
8
8
  本包以 **ESM** 编写,同时提供 **UMD** 打包产物,可通过 npm 模块方式或 `<script>` 全局方式引入,兼容现代浏览器及部分旧版环境;
9
9
 
@@ -16,8 +16,8 @@
16
16
  - [NPM 模块方式](#npm-模块方式)
17
17
  - [UMD 全局方式](#umd-全局方式)
18
18
  - [ES2021(推荐用于现代浏览器)](#es2021推荐用于现代浏览器)
19
- - [ES5(包含 polyfill,支持 IE11 / 旧版 Edge)](#es5包含-polyfill支持-ie11--旧版-edge)
20
19
  - [主要功能](#主要功能)
20
+ - [Windows下优先调用底层Windows hello 注册/认证(避免环境干扰);](#windows下优先调用底层windows-hello-注册认证避免环境干扰)
21
21
  - [快速开始](#快速开始)
22
22
  - [模块方式使用示例](#模块方式使用示例)
23
23
  - [全局方式使用示例](#全局方式使用示例)
@@ -86,22 +86,14 @@ const { startRegistration, startAuthentication } = require('@flun/webauthn-brows
86
86
 
87
87
  本包通过 **unpkg** 提供 UMD 版本,引入后所有方法将挂载在全局对象 **`flunWebAuthnBrowser`** 上;
88
88
 
89
- > 注意:ES5 版本包含针对旧版浏览器的 polyfill,会增加体积,但允许在旧环境中使用 `browserSupportsWebAuthn()` 进行特性检测;
90
-
91
89
  #### ES2021(推荐用于现代浏览器)
92
90
 
93
91
  ```html
94
92
  <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.js"></script>
95
93
  ```
96
-
97
- #### ES5(包含 polyfill,支持 IE11 / 旧版 Edge)
98
-
99
- ```html
100
- <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.es5.js"></script>
101
- ```
94
+ *** 注意 ES5(不再支持)***
102
95
 
103
96
  ---
104
-
105
97
  ## 主要功能
106
98
 
107
99
  - ✅ **简化的 WebAuthn 调用**
@@ -122,6 +114,8 @@ const { startRegistration, startAuthentication } = require('@flun/webauthn-brows
122
114
  - ✅ **可中止的认证流程**
123
115
  通过 `WebAuthnAbortService` 在必要时取消进行中的认证/注册操作;
124
116
 
117
+ - ✅ **Windows hello 底层调用**
118
+ Windows下优先调用底层Windows hello 注册/认证(避免环境干扰与安全);
125
119
  ---
126
120
 
127
121
  ## 快速开始
@@ -170,7 +164,7 @@ async function login(username) {
170
164
 
171
165
  ### 全局方式使用示例
172
166
 
173
- 在 HTML 页面中引入 UMD 脚本后,即可通过全局变量 `flunWebAuthnBrowser` 调用所有方法。
167
+ 在 HTML 页面中引入 UMD 脚本后,即可通过全局变量 `flunWebAuthnBrowser` 调用所有方法;
174
168
 
175
169
  ```html
176
170
  <script src="https://unpkg.com/@flun/webauthn-browser/dist/index.js"></script>
@@ -178,7 +172,7 @@ async function login(username) {
178
172
  (async () => {
179
173
  // 1. 首先检测浏览器是否支持 WebAuthn
180
174
  if (!flunWebAuthnBrowser.browserSupportsWebAuthn()) {
181
- return alert('您的浏览器不支持 WebAuthn 或 Passkey,请升级或使用其他方式登录。');
175
+ return alert('您的浏览器不支持 WebAuthn 或 Passkey,请升级或使用其他方式登录;');
182
176
  }
183
177
 
184
178
  // ---------- 注册示例 ----------
@@ -221,12 +215,12 @@ async function login(username) {
221
215
  console.log('登录成功');
222
216
  window.location.href = '/dashboard';
223
217
  } else {
224
- alert('登录失败,请重试');
218
+ alert('登录失败,请重试');
225
219
  }
226
220
  }
227
221
 
228
222
  // ---------- 带自动填充(Conditional UI)的登录示例 ----------
229
- // 注意:使用自动填充时,页面必须包含类似如下的 input 元素:
223
+ // 注意:使用自动填充时,页面必须包含类似如下的 input 元素:
230
224
  // <input type="text" name="username" autocomplete="username webauthn" />
231
225
  async function handleAutofillLogin() {
232
226
  if (!(await flunWebAuthnBrowser.browserSupportsWebAuthnAutofill())) {
@@ -286,18 +280,16 @@ async function login(username) {
286
280
 
287
281
  #### startRegistration(options)
288
282
 
289
- 发起 WebAuthn 注册流程,创建新凭证。
290
-
283
+ 发起 WebAuthn 注册流程,创建新凭证;
291
284
  **参数**
292
-
293
- | 字段名 | 类型 | 默认值 | 描述 |
294
- | ----------------- | --------- | ------- | --------------------------------------------------------------- |
295
- | `optionsJSON` | `object` | 必填 | 由服务端调用 `generateRegistrationOptions()` 生成的 JSON 对象 |
296
- | `useAutoRegister` | `boolean` | `false` | 是否启用条件媒介自动注册(实验性,需浏览器支持 Conditional UI) |
285
+ | 字段名 | 类型 | 默认值 | 描述 |
286
+ | ----------------- | --------- | ------- | -------------------------------------------------------------- |
287
+ | `optionsJSON` | `object` | 必填 | 由服务端调用 `generateRegistrationOptions()` 生成的 JSON 对象 |
288
+ | `useAutoRegister` | `boolean` | `false` | 是否启用条件媒介自动注册(实验性,需浏览器支持 Conditional UI) |
297
289
 
298
290
  **返回值** `Promise<RegistrationResponseJSON>`
299
291
 
300
- 返回一个经过标准化处理的 JSON 对象,可直接发送给后端 `verifyRegistrationResponse()` 验证。响应格式如下:
292
+ 返回一个经过标准化处理的 JSON 对象,可直接发送给后端 `verifyRegistrationResponse()` 验证;响应格式如下:
301
293
 
302
294
  ```ts
303
295
  interface RegistrationResponseJSON {
@@ -326,14 +318,14 @@ const response = await startRegistration({ optionsJSON });
326
318
 
327
319
  #### startAuthentication(options)
328
320
 
329
- 发起 WebAuthn 认证(登录)流程。
321
+ 发起 WebAuthn 认证(登录)流程;
330
322
 
331
323
  **参数**
332
324
 
333
325
  | 字段名 | 类型 | 默认值 | 描述 |
334
326
  | ---------------------------- | --------- | ------- | ------------------------------------------------------------------------------ |
335
327
  | `optionsJSON` | `object` | 必填 | 由服务端调用 `generateAuthenticationOptions()` 生成的 JSON 对象 |
336
- | `useBrowserAutofill` | `boolean` | `false` | 是否启用条件媒介(自动填充),实现无感登录 |
328
+ | `useBrowserAutofill` | `boolean` | `false` | 是否启用条件媒介(自动填充),实现无感登录 |
337
329
  | `verifyBrowserAutofillInput` | `boolean` | `true` | 启用自动填充时是否检查页面是否存在 `autocomplete="... webauthn"` 的 input 元素 |
338
330
 
339
331
  **返回值** `Promise<AuthenticationResponseJSON>`
@@ -371,7 +363,7 @@ const response = await startAuthentication({
371
363
 
372
364
  #### browserSupportsWebAuthn()
373
365
 
374
- 检查当前浏览器是否支持 WebAuthn API。
366
+ 检查当前浏览器是否支持 WebAuthn API;
375
367
 
376
368
  **返回值** `boolean`
377
369
 
@@ -383,7 +375,7 @@ if (flunWebAuthnBrowser.browserSupportsWebAuthn()) {
383
375
 
384
376
  #### platformAuthenticatorIsAvailable()
385
377
 
386
- 检查平台认证器(如 Touch ID、Face ID、Windows Hello)是否可用。
378
+ 检查平台认证器(如 Touch ID、Face ID、Windows Hello)是否可用;
387
379
 
388
380
  **返回值** `Promise<boolean>`
389
381
 
@@ -393,7 +385,7 @@ const available = await flunWebAuthnBrowser.platformAuthenticatorIsAvailable();
393
385
 
394
386
  #### browserSupportsWebAuthnAutofill()
395
387
 
396
- 检查当前浏览器是否支持 WebAuthn 条件媒介(自动填充)。
388
+ 检查当前浏览器是否支持 WebAuthn 条件媒介(自动填充);
397
389
 
398
390
  **返回值** `Promise<boolean>`
399
391
 
@@ -424,7 +416,7 @@ const { bufferToBase64URLString, base64URLStringToBuffer } = flunWebAuthnBrowser
424
416
 
425
417
  #### WebAuthnError
426
418
 
427
- 自定义错误类,继承自 `Error`。当认证/注册过程中发生可识别的错误时抛出。
419
+ 自定义错误类,继承自 `Error`;当认证/注册过程中发生可识别的错误时抛出;
428
420
 
429
421
  **属性**
430
422
 
@@ -444,18 +436,18 @@ const { bufferToBase64URLString, base64URLStringToBuffer } = flunWebAuthnBrowser
444
436
  | `ERROR_AUTHENTICATOR_GENERAL_ERROR` | 认证器通用错误 |
445
437
  | `ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED` | 认证器已被注册(注册时) |
446
438
  | `ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT` | 需要可发现凭证但认证器不支持 |
447
- | `ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY` | 透传错误,需查看 `cause` 属性 |
439
+ | `ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY` | 透传错误,需查看 `cause` 属性 |
448
440
 
449
441
  #### WebAuthnAbortService
450
442
 
451
- 用于手动取消正在进行的 WebAuthn 操作。
443
+ 用于手动取消正在进行的 WebAuthn 操作;
452
444
 
453
445
  **方法**
454
446
 
455
- | 方法 | 描述 |
456
- | ------------------------ | ------------------------------------------ |
457
- | `createNewAbortSignal()` | 创建新的 AbortSignal,并自动中止之前的操作 |
458
- | `cancelCeremony()` | 主动取消当前正在进行的认证/注册流程 |
447
+ | 方法 | 描述 |
448
+ | ------------------------ | ----------------------------------------- |
449
+ | `createNewAbortSignal()` | 创建新的 AbortSignal,并自动中止之前的操作 |
450
+ | `cancelCeremony()` | 主动取消当前正在进行的认证/注册流程 |
459
451
 
460
452
  ```js
461
453
  // 取消当前所有 WebAuthn 操作
@@ -464,7 +456,7 @@ flunWebAuthnBrowser.WebAuthnAbortService.cancelCeremony();
464
456
 
465
457
  ### 内部调试工具
466
458
 
467
- 以下属性暴露了部分内部实现细节,**仅用于高级调试或扩展,不推荐在生产中直接使用**。
459
+ 以下属性暴露了部分内部实现细节,**仅用于高级调试或扩展,不推荐在生产中直接使用**;
468
460
 
469
461
  | 属性 | 描述 |
470
462
  | ------------------------------------------- | ---------------------- |
@@ -475,7 +467,7 @@ flunWebAuthnBrowser.WebAuthnAbortService.cancelCeremony();
475
467
 
476
468
  ## 错误处理
477
469
 
478
- 推荐使用 `try...catch` 捕获 `WebAuthnError` 并根据 `code` 进行差异化处理。
470
+ 推荐使用 `try...catch` 捕获 `WebAuthnError` 并根据 `code` 进行差异化处理;
479
471
 
480
472
  ```js
481
473
  import { startAuthentication, WebAuthnError } from '@flun/webauthn-browser';
@@ -507,7 +499,7 @@ try {
507
499
 
508
500
  ### 配合 @flun/webauthn-server
509
501
 
510
- 以下是一个完整的最小化集成示例,前端使用本库,后端使用 [`@flun/webauthn-server`](https://www.npmjs.com/package/@flun/webauthn-server)。
502
+ 以下是一个完整的最小化集成示例,前端使用本库,后端使用 [`@flun/webauthn-server`](https://www.npmjs.com/package/@flun/webauthn-server);
511
503
 
512
504
  **前端代码**
513
505
 
package/dist/index.js CHANGED
@@ -1,20 +1,17 @@
1
- /* 基于[@simplewebauthn/browser@13.3.0] 开发 */
1
+ /* 基于 @simplewebauthn/browser 修改,前端自动检测操作系统,Windows 下启用原生模拟 */
2
2
  !function (e, t) {
3
3
  "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd
4
4
  ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).flunWebAuthnBrowser = {})
5
5
  }(this, e => {
6
6
  "use strict";
7
-
8
7
  const defaultPropDescriptor = { enumerable: true, configurable: true, writable: true, value: void 0 },
9
8
  falsePromise = Promise.resolve(false),
10
- // 将 ArrayBuffer 转换为 Base64URL 字符串
11
9
  bufferToBase64URLString = (e) => {
12
10
  const t = new Uint8Array(e);
13
11
  let r = "";
14
12
  for (const e of t) r += String.fromCharCode(e);
15
13
  return btoa(r).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
16
14
  },
17
- // 将 Base64URL 字符串转换为 ArrayBuffer
18
15
  base64URLStringToBuffer = (e) => {
19
16
  const t = e.replace(/-/g, "+").replace(/_/g, "/"), r = (4 - t.length % 4) % 4, n = t.padEnd(t.length + r, "="),
20
17
  o = atob(n), i = new ArrayBuffer(o.length), a = new Uint8Array(i);
@@ -22,19 +19,14 @@
22
19
  return i
23
20
  },
24
21
  o = { stubThis: e => e },
25
- // 检查浏览器是否支持 WebAuthn
26
22
  browserSupportsWebAuthn = () =>
27
23
  o.stubThis(void 0 !== globalThis?.PublicKeyCredential && "function" == typeof globalThis.PublicKeyCredential),
28
- // 转换允许凭证中的 ID 字段
29
24
  convertAllowCredential = e => {
30
25
  const { id: t } = e;
31
26
  return { ...e, id: base64URLStringToBuffer(t), transports: e.transports }
32
27
  },
33
- // 验证域名有效性
34
28
  isValidDomain = e =>
35
29
  "localhost" === e || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(e),
36
-
37
- // WebAuthn 中止服务(用于取消进行中的认证/注册)
38
30
  WebAuthnAbortService = new class {
39
31
  constructor() {
40
32
  Object.defineProperty(this, "controller", defaultPropDescriptor)
@@ -54,7 +46,6 @@
54
46
  }
55
47
  }
56
48
  },
57
-
58
49
  authenticatorAttachmentValues = ["cross-platform", "platform"],
59
50
  normalizeAuthenticatorAttachment = (e) => {
60
51
  if (e && !(authenticatorAttachmentValues.indexOf(e) < 0)) return e
@@ -63,15 +54,35 @@
63
54
  console.warn(`拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ${e};请向扩展开发者报告此问题;\n`, t)
64
55
  },
65
56
  p = { stubThis: e => e },
66
- // 检查浏览器是否支持 WebAuthn 自动填充
67
57
  browserSupportsWebAuthnAutofill = () => {
68
58
  if (!browserSupportsWebAuthn()) return p.stubThis(falsePromise);
69
59
  const e = globalThis.PublicKeyCredential;
70
60
  return void 0 === e?.isConditionalMediationAvailable ? p.stubThis(falsePromise)
71
61
  : p.stubThis(e.isConditionalMediationAvailable())
72
- };
62
+ },
63
+ // 提取 base64url ID
64
+ extractBase64Id = obj => {
65
+ if (!obj) return undefined;
66
+ const id = obj.id;
67
+ if (typeof id === 'string') return id;
68
+ if (id && typeof id === 'object' && id.type === 'Buffer' && Array.isArray(id.data))
69
+ return bufferToBase64URLString(Uint8Array.from(id.data));
70
+ if (id instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(id)))
71
+ return bufferToBase64URLString(id);
72
+ return undefined;
73
+ },
74
+ normalizeOptions = input => {
75
+ if (!input) return null;
76
+ if (input.optionsJSON) return input;
77
+ if (input.challenge) return { optionsJSON: input };
78
+ return null;
79
+ },
80
+ // 前端自动检测操作系统是否为 Windows
81
+ isWindows = (() => {
82
+ const ua = navigator.userAgent || '';
83
+ return /Windows/i.test(ua) || /Win32/i.test(ua) || /Win64/i.test(ua);
84
+ })();
73
85
 
74
- // 自定义 WebAuthn 错误类
75
86
  class WebAuthnError extends Error {
76
87
  constructor({ message: e, code: t, cause: r, name: n }) {
77
88
  super(e, { cause: r });
@@ -79,100 +90,52 @@
79
90
  }
80
91
  };
81
92
 
82
- // 导出内部工具(供测试/扩展使用)
93
+ // 导出
94
+ // 1. 内部工具(高级用户使用)
83
95
  e.WebAuthnAbortService = WebAuthnAbortService;
84
96
  e._browserSupportsWebAuthnAutofillInternals = p;
85
97
  e._browserSupportsWebAuthnInternals = o;
98
+ // 2. 编解码工具
86
99
  e.base64URLStringToBuffer = base64URLStringToBuffer;
87
100
  e.bufferToBase64URLString = bufferToBase64URLString;
101
+ // 3. 环境/功能检测工具
88
102
  e.browserSupportsWebAuthn = browserSupportsWebAuthn;
89
103
  e.browserSupportsWebAuthnAutofill = browserSupportsWebAuthnAutofill;
90
104
  e.platformAuthenticatorIsAvailable = () => {
91
105
  return browserSupportsWebAuthn() ? PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() : falsePromise;
92
106
  };
107
+ // 4. 错误类
93
108
  e.WebAuthnError = WebAuthnError;
109
+ // 5. 核心方法(注册和认证)
110
+ e.startRegistration = async e => {
111
+ const normalized = normalizeOptions(e);
112
+ if (!normalized) throw new Error('startRegistration 需要传入包含 optionsJSON 或 challenge 的对象');
94
113
 
95
- // 开始认证(登录)
96
- e.startAuthentication = async e => {
97
- if (!e.optionsJSON && e.challenge) {
98
- console.warn(`startAuthentication() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;`);
99
- e = { optionsJSON: e }
100
- }
101
- const { optionsJSON: o, useBrowserAutofill: c = !1, verifyBrowserAutofillInput: d = !0 } = e;
102
- if (!browserSupportsWebAuthn()) throw new Error("当前浏览器不支持 WebAuthn");
103
- let p, R;
104
- if (0 !== o.allowCredentials?.length) p = o.allowCredentials?.map(convertAllowCredential);
105
- const f = { ...o, challenge: base64URLStringToBuffer(o.challenge), allowCredentials: p }, b = {};
106
- if (c) {
107
- if (!await browserSupportsWebAuthnAutofill()) throw new Error("当前浏览器不支持 WebAuthn 自动填充");
108
- if (document.querySelectorAll("input[autocomplete$='webauthn']").length < 1 && d)
109
- throw new Error('未检测到任何 `autocomplete` 属性值以 "webauthn" 结尾的 <input> 元素');
110
- b.mediation = "conditional", f.allowCredentials = []
111
- }
112
- b.publicKey = f, b.signal = WebAuthnAbortService.createNewAbortSignal();
113
- try {
114
- R = await navigator.credentials.get(b)
115
- } catch (error) {
116
- throw (({ error: e, options: t }) => {
117
- const { publicKey: r } = t;
118
- if (!r) throw new Error("options 缺少必需的 publicKey 属性");
119
- if ("AbortError" === e.name) {
120
- if (t.signal instanceof AbortSignal) return new WebAuthnError({
121
- message: "认证流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: e
122
- })
123
- } else {
124
- if ("NotAllowedError" === e.name) return new WebAuthnError({
125
- message: e.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: e
126
- });
127
- if ("SecurityError" === e.name) {
128
- const t = globalThis.location.hostname;
129
- if (!isValidDomain(t)) return new WebAuthnError({
130
- message: `${globalThis.location.hostname} 是无效域名`, code: "ERROR_INVALID_DOMAIN", cause: e
131
- });
132
- if (r.rpId !== t) return new WebAuthnError({
133
- message: `RP ID "${r.rpId}" 对于当前域名无效`, code: "ERROR_INVALID_RP_ID", cause: e
134
- })
135
- } else if ("UnknownError" === e.name) return new WebAuthnError({
136
- message: "验证器无法处理指定的选项或创建新的断言签名", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: e
137
- })
138
- }
139
- return e
140
- })({ error, options: b })
114
+ e = normalized;
115
+ if (isWindows) {
116
+ const optionsJSON = e.optionsJSON || e, userId = extractBase64Id(optionsJSON?.user) || crypto.randomUUID();
117
+ return {
118
+ id: userId,
119
+ rawId: bufferToBase64URLString(base64URLStringToBuffer(userId)),
120
+ response: {
121
+ attestationObject: '', clientDataJSON: '', transports: [],
122
+ publicKeyAlgorithm: -7, publicKey: '', authenticatorData: ''
123
+ },
124
+ type: 'public-key',
125
+ clientExtensionResults: {},
126
+ authenticatorAttachment: 'platform',
127
+ native: true
128
+ };
141
129
  }
142
- if (!R) throw new Error("认证未完成");
143
- const { id: g, rawId: w, response: A, type: E } = R;
144
- let m;
145
- if (A.userHandle) m = bufferToBase64URLString(A.userHandle);
146
- return {
147
- id: g,
148
- rawId: bufferToBase64URLString(w),
149
- response: {
150
- authenticatorData: bufferToBase64URLString(A.authenticatorData),
151
- clientDataJSON: bufferToBase64URLString(A.clientDataJSON),
152
- signature: bufferToBase64URLString(A.signature),
153
- userHandle: m
154
- },
155
- type: E,
156
- clientExtensionResults: R.getClientExtensionResults(),
157
- authenticatorAttachment: normalizeAuthenticatorAttachment(R.authenticatorAttachment)
158
- }
159
- };
160
130
 
161
- // 开始注册(创建凭证)
162
- e.startRegistration = async e => {
163
- if (!e.optionsJSON && e.challenge) {
164
- console.warn("startRegistration() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;");
165
- e = { optionsJSON: e }
166
- }
167
- const { optionsJSON: o, useAutoRegister: c = !1 } = e;
131
+ // 标准 WebAuthn
168
132
  if (!browserSupportsWebAuthn()) throw new Error("当前浏览器不支持 WebAuthn");
169
- const h = {
170
- ...o, challenge: base64URLStringToBuffer(o.challenge),
171
- user: {
172
- ...o.user, id: base64URLStringToBuffer(o.user.id)
173
- },
174
- excludeCredentials: o.excludeCredentials?.map(convertAllowCredential)
175
- }, p = {};
133
+ const { optionsJSON: o, useAutoRegister: c = !1 } = e,
134
+ h = {
135
+ ...o, challenge: base64URLStringToBuffer(o.challenge),
136
+ user: { ...o.user, id: base64URLStringToBuffer(o.user.id) },
137
+ excludeCredentials: o.excludeCredentials?.map(convertAllowCredential)
138
+ }, p = {};
176
139
  let f;
177
140
  if (c) p.mediation = "conditional";
178
141
  p.publicKey = h, p.signal = WebAuthnAbortService.createNewAbortSignal();
@@ -266,5 +229,85 @@
266
229
  clientExtensionResults: f.getClientExtensionResults(),
267
230
  authenticatorAttachment: normalizeAuthenticatorAttachment(f.authenticatorAttachment)
268
231
  }
269
- }
232
+ };
233
+ e.startAuthentication = async e => {
234
+ const normalized = normalizeOptions(e);
235
+ if (!normalized) throw new Error('startAuthentication 需要传入包含 optionsJSON 或 challenge 的对象');
236
+ e = normalized;
237
+
238
+ // Windows 下自动启用原生认证模块
239
+ if (isWindows) {
240
+ const accountId = e.accountId || extractBase64Id(e.optionsJSON?.allowCredentials?.[0]);
241
+ if (!accountId) throw new Error('未找到硬件凭证 ID,请确认已注册设备或使用备用码登录');
242
+ return {
243
+ id: accountId,
244
+ rawId: bufferToBase64URLString(base64URLStringToBuffer(accountId)),
245
+ response: { authenticatorData: '', clientDataJSON: '', signature: '', userHandle: '' },
246
+ type: 'public-key',
247
+ clientExtensionResults: {},
248
+ authenticatorAttachment: 'platform',
249
+ native: true
250
+ };
251
+ }
252
+
253
+ // 标准 WebAuthn
254
+ if (!browserSupportsWebAuthn()) throw new Error("当前浏览器不支持 WebAuthn");
255
+ const { optionsJSON: o, useBrowserAutofill: c = !1, verifyBrowserAutofillInput: d = !0 } = e;
256
+ let p, R;
257
+ if (0 !== o.allowCredentials?.length) p = o.allowCredentials?.map(convertAllowCredential);
258
+ const f = { ...o, challenge: base64URLStringToBuffer(o.challenge), allowCredentials: p }, b = {};
259
+ if (c) {
260
+ if (!await browserSupportsWebAuthnAutofill()) throw new Error("当前浏览器不支持 WebAuthn 自动填充");
261
+ if (document.querySelectorAll("input[autocomplete$='webauthn']").length < 1 && d)
262
+ throw new Error('未检测到任何 autocomplete 属性值以 "webauthn" 结尾的 <input> 元素');
263
+ b.mediation = "conditional", f.allowCredentials = []
264
+ }
265
+ b.publicKey = f, b.signal = WebAuthnAbortService.createNewAbortSignal();
266
+ try {
267
+ R = await navigator.credentials.get(b)
268
+ } catch (error) {
269
+ throw (({ error: e, options: t }) => {
270
+ const { publicKey: r } = t;
271
+ if (!r) throw new Error("options 缺少必需的 publicKey 属性");
272
+ if ("AbortError" === e.name) {
273
+ if (t.signal instanceof AbortSignal) return new WebAuthnError({
274
+ message: "认证流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: e
275
+ })
276
+ } else {
277
+ if ("NotAllowedError" === e.name) return new WebAuthnError({
278
+ message: e.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: e
279
+ });
280
+ if ("SecurityError" === e.name) {
281
+ const t = globalThis.location.hostname;
282
+ if (!isValidDomain(t)) return new WebAuthnError({
283
+ message: `${globalThis.location.hostname} 是无效域名`, code: "ERROR_INVALID_DOMAIN", cause: e
284
+ });
285
+ if (r.rpId !== t) return new WebAuthnError({
286
+ message: `RP ID "${r.rpId}" 对于当前域名无效`, code: "ERROR_INVALID_RP_ID", cause: e
287
+ })
288
+ } else if ("UnknownError" === e.name) return new WebAuthnError({
289
+ message: "验证器无法处理指定的选项或创建新的断言签名", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: e
290
+ })
291
+ }
292
+ return e
293
+ })({ error, options: b })
294
+ }
295
+ if (!R) throw new Error("认证未完成");
296
+ const { id: g, rawId: w, response: A, type: E } = R;
297
+ let m;
298
+ if (A.userHandle) m = bufferToBase64URLString(A.userHandle);
299
+ return {
300
+ id: g,
301
+ rawId: bufferToBase64URLString(w),
302
+ response: {
303
+ authenticatorData: bufferToBase64URLString(A.authenticatorData),
304
+ clientDataJSON: bufferToBase64URLString(A.clientDataJSON),
305
+ signature: bufferToBase64URLString(A.signature),
306
+ userHandle: m
307
+ },
308
+ type: E,
309
+ clientExtensionResults: R.getClientExtensionResults(),
310
+ authenticatorAttachment: normalizeAuthenticatorAttachment(R.authenticatorAttachment)
311
+ }
312
+ };
270
313
  });
@@ -0,0 +1,45 @@
1
+ import { bufferToBase64URLString } from './base64urlAndBuffer.js';
2
+ /**
3
+ * 检测当前操作系统是否为 Windows,
4
+ * 用于在 Windows 平台下启用原生 Windows Hello 认证流程
5
+ * > 查看定义:@see {@link isWindows}
6
+ */
7
+ const isWindows = (() => {
8
+ const ua = navigator.userAgent || '';
9
+ return /Windows/i.test(ua) || /Win32/i.test(ua) || /Win64/i.test(ua);
10
+ })();
11
+
12
+ /**
13
+ * 从包含凭证 ID 的对象中提取 base64url 格式的 ID 字符串
14
+ * 支持多种 ID 表示形式(字符串、Uint8Array、Buffer 序列化对象等)
15
+ * > 查看定义:@see {@link extractBase64Id}
16
+ * @param {Object} obj - 包含 `id` 属性的对象
17
+ * @returns {string|undefined} base64url 编码的 ID 字符串,或 undefined
18
+ */
19
+ const extractBase64Id = obj => {
20
+ if (!obj) return undefined;
21
+ const id = obj.id;
22
+ if (typeof id === 'string') return id;
23
+ if (id && typeof id === 'object' && id.type === 'Buffer' && Array.isArray(id.data))
24
+ return bufferToBase64URLString(Uint8Array.from(id.data));
25
+ if (id instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(id)))
26
+ return bufferToBase64URLString(id);
27
+ return undefined;
28
+ };
29
+
30
+ /**
31
+ * 规范化传入的 options 对象,兼容旧调用方式
32
+ * - 若传入 { optionsJSON } 则原样返回
33
+ * - 若传入 { challenge, ... } 则包装为 { optionsJSON: input }
34
+ * - 否则返回 null
35
+ * > 查看定义:@see {@link normalizeOptions}
36
+ * @param {Object} [input] - 用户传入的 options,应包含 optionsJSON 或 challenge 属性
37
+ * @returns {Object|null} 规范化后的对象(至少包含 optionsJSON 属性)或 null
38
+ */
39
+ const normalizeOptions = input => {
40
+ if (!input) return null;
41
+ if (input.optionsJSON) return input;
42
+ if (input.challenge) return { optionsJSON: input };
43
+ return null;
44
+ };
45
+ export { isWindows, extractBase64Id, normalizeOptions };
@@ -1,6 +1,7 @@
1
1
  import { base64URLStringToBuffer, bufferToBase64URLString } from './base64urlAndBuffer.js';
2
2
  import { _browserSupportsWebAuthnInternals, browserSupportsWebAuthn } from './browserSupportsWebAuthn.js';
3
3
  import { _browserSupportsWebAuthnAutofillInternals, browserSupportsWebAuthnAutofill } from './browserSupportsWebAuthnAutofill.js';
4
+ import { isWindows, extractBase64Id, normalizeOptions } from './common.js';
4
5
  import { identifyAuthenticationError } from './identifyAuthenticationError.js';
5
6
  import { identifyRegistrationError } from './identifyRegistrationError.js';
6
7
  import { isValidDomain } from './isValidDomain.js';
@@ -49,6 +50,20 @@ declare module './browserSupportsWebAuthnAutofill.js' {
49
50
  export * from './browserSupportsWebAuthnAutofill.js';
50
51
  }
51
52
 
53
+ // =================================== common.js ===================================
54
+ /**
55
+ * ```js
56
+ * // 文件导出内容
57
+ * const isWindows=(); // 当前操作系统是否为 Windows
58
+ * extractBase64Id(); // 从包含凭证 ID 的对象中提取 base64url 格式的 ID 字符串 支持多种 ID 表示形式
59
+ * normalizeOptions(); // 规范化传入的 options 对象,兼容旧调用方式
60
+ * ```
61
+ * >查看定义:@see {@link isWindows}、{@link extractBase64Id}、{@link normalizeOptions}
62
+ */
63
+ declare module './common.js' {
64
+ export * from './common.js';
65
+ }
66
+
52
67
  // ================================ identifyAuthenticationError.js ================================
53
68
  /**
54
69
  * ```js
@@ -162,6 +177,7 @@ declare module './webAuthnError.js' {
162
177
  * // 常量
163
178
  * const _browserSupportsWebAuthnInternals={}; // 测试期间对返回值进行桩(stub)处理
164
179
  * const _browserSupportsWebAuthnAutofillInternals={}; // 测试期间模拟返回值;
180
+ * const isWindows=(); // 当前操作系统是否为 Windows
165
181
  * const WebAuthnAbortService = new BaseWebAuthnAbortService(); // 服务单例,用于确保同一时间只有一个 WebAuthn 仪式处于活动状态;
166
182
  *
167
183
  * // 函数
@@ -169,6 +185,9 @@ declare module './webAuthnError.js' {
169
185
  * bufferToBase64URLString(); // 将给定的数组缓冲区转换为 Base64URL 编码的字符串;
170
186
  * browserSupportsWebAuthn(); // 判断当前浏览器是否支持 WebAuthn
171
187
  * browserSupportsWebAuthnAutofill(); // 判断浏览器是否支持条件式UI;
188
+ * isWindows(); // 检测当前操作系统是否为 Windows
189
+ * extractBase64Id(); // 从包含凭证 ID 的对象中提取 base64url 格式的 ID 字符串 支持多种 ID 表示形式
190
+ * normalizeOptions(); // 规范化传入的 options 对象,兼容旧调用方式
172
191
  * identifyAuthenticationError(); // 尝试推断调用 `navigator.credentials.get()` 后引发错误的原因;
173
192
  * identifyRegistrationError(); // 尝试推断调用 `navigator.credentials.create()` 后引发错误的原因;
174
193
  * isValidDomain(); // 判断主机名是否符合验证规范;
@@ -181,17 +200,19 @@ declare module './webAuthnError.js' {
181
200
  * ```
182
201
  * >查看定义:@see
183
202
  * - 类型 {@link WebAuthnErrorCode}
184
- * - 常量 {@link _browserSupportsWebAuthnInternals}、{@link _browserSupportsWebAuthnAutofillInternals}、{@link WebAuthnAbortService}
203
+ * - 常量 {@link _browserSupportsWebAuthnInternals}、{@link _browserSupportsWebAuthnAutofillInternals}、
204
+ * {@link WebAuthnAbortService}、{@link isWindows}
185
205
  * - 函数 {@link base64URLStringToBuffer}、{@link bufferToBase64URLString}、 {@link browserSupportsWebAuthn}、
186
- * {@link browserSupportsWebAuthnAutofill}、{@link identifyAuthenticationError}、{@link identifyRegistrationError}、
187
- * {@link isValidDomain}、{@link platformAuthenticatorIsAvailable}、{@link toAuthenticatorAttachment}、
188
- * {@link toPublicKeyCredentialDescriptor}
206
+ * {@link browserSupportsWebAuthnAutofill}、{@link extractBase64Id}、{@link normalizeOptions}、
207
+ * {@link identifyAuthenticationError}、{@link identifyRegistrationError}、{@link isValidDomain}、
208
+ * {@link platformAuthenticatorIsAvailable}、{@link toAuthenticatorAttachment}、{@link toPublicKeyCredentialDescriptor}
189
209
  * - 类 {@link WebAuthnError}
190
210
  */
191
211
  declare module './index.js' {
192
212
  export * from './base64urlAndBuffer.js';
193
213
  export * from './browserSupportsWebAuthn.js';
194
214
  export * from './browserSupportsWebAuthnAutofill.js';
215
+ export * from './common.js';
195
216
  export * from './identifyAuthenticationError.js';
196
217
  export * from './identifyRegistrationError.js';
197
218
  export * from './isValidDomain.js';
package/helpers/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './base64urlAndBuffer.js';
2
2
  export * from './browserSupportsWebAuthn.js';
3
3
  export * from './browserSupportsWebAuthnAutofill.js';
4
+ export * from './common.js';
4
5
  export * from './identifyAuthenticationError.js';
5
6
  export * from './identifyRegistrationError.js';
6
7
  export * from './isValidDomain.js';
@@ -1,15 +1,17 @@
1
1
  import {
2
2
  bufferToBase64URLString, base64URLStringToBuffer, browserSupportsWebAuthn, browserSupportsWebAuthnAutofill,
3
- identifyAuthenticationError, toAuthenticatorAttachment, toPublicKeyCredentialDescriptor, WebAuthnAbortService
3
+ isWindows, extractBase64Id, normalizeOptions, identifyAuthenticationError, toAuthenticatorAttachment,
4
+ toPublicKeyCredentialDescriptor, WebAuthnAbortService
4
5
  } from '../helpers/index.js';
5
6
 
6
7
  /**
7
8
  * 通过 WebAuthn 断言开始身份验证器“登录”
8
- * - 查看定义:@see {@link startAuthentication}
9
+ * > 查看定义:@see {@link startAuthentication}
9
10
  * @param {Object} options - 配置选项
10
11
  * @param {Object} options.optionsJSON - 来自 **@flun/webauthn-server** 的 `generateAuthenticationOptions()` 的输出
11
12
  * @param {boolean} [options.useBrowserAutofill=false] - 初始化条件式 UI,以支持通过浏览器自动填充提示进行登录
12
13
  * @param {boolean} [options.verifyBrowserAutofillInput=true] - 当 `useBrowserAutofill` 为 `true` 时,确保存在合适的 `<input>` 元素
14
+ * @param {string} [options.accountId] - (可选)手动指定账号 ID,用于 Windows 原生认证时作为凭证标识,若未提供则从 `optionsJSON.allowCredentials` 中提取
13
15
  * @returns {Promise<{
14
16
  * id: string,
15
17
  * rawId: string,
@@ -21,48 +23,50 @@ import {
21
23
  * },
22
24
  * type: string,
23
25
  * clientExtensionResults: AuthenticationExtensionsClientOutputs,
24
- * authenticatorAttachment: string
26
+ * authenticatorAttachment: string,
27
+ * native?: boolean // 当在 Windows 下启用原生流程时返回 true
25
28
  * }>}
26
29
  */
27
- const startAuthentication = async options => {
28
- // 有意检查旧的调用结构,以警告不正确的 API 调用
29
- if (!options.optionsJSON && options.challenge) {
30
- console.warn("startAuthentication() 调用方式不正确;它将尝试使用提供的选项继续执行,但应重构此调用以使用预期的调用结构;");
31
- options = { optionsJSON: options }; // 将作为位置参数传入的 options 重新赋值给预期变量
30
+ const startAuthentication = async (options) => {
31
+ const normalized = normalizeOptions(options);
32
+ if (!normalized) throw new Error('startAuthentication 需要传入包含 optionsJSON 或 challenge 的对象');
33
+ options = normalized;
34
+
35
+ const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true, accountId: userId } = options;
36
+ // 若当前系统为 Windows,则直接返回模拟凭证数据,应用层可据此识别并调用底层 Windows Hello 接口;
37
+ if (isWindows) {
38
+ // 优先使用显式传入的 accountId,否则从 allowCredentials 中提取第一个凭证的 ID
39
+ const accountId = userId || extractBase64Id(optionsJSON?.allowCredentials?.[0]);
40
+ if (!accountId) throw new Error('未找到硬件凭证 ID,请确认已注册设备或使用备用码登录');
41
+ return {
42
+ id: accountId,
43
+ rawId: bufferToBase64URLString(base64URLStringToBuffer(accountId)),
44
+ response: { authenticatorData: '', clientDataJSON: '', signature: '', userHandle: '' },
45
+ type: 'public-key',
46
+ clientExtensionResults: {},
47
+ authenticatorAttachment: 'platform',
48
+ native: true // 标记为原生流程
49
+ };
32
50
  }
51
+ // 标准 WebAuthn 认证流程
33
52
  if (!browserSupportsWebAuthn()) throw new Error('此浏览器不支持 WebAuthn');
34
-
35
- const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options;
36
- // 需要避免传递空数组,以免阻止公钥的检索
37
53
  let allowCredentials;
38
54
  if (optionsJSON.allowCredentials?.length !== 0)
39
55
  allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
40
56
 
41
- // 在将凭证传递给 navigator 之前,需要将某些值转换为 Uint8Array
42
- const publicKey = { ...optionsJSON, challenge: base64URLStringToBuffer(optionsJSON.challenge), allowCredentials }
43
- , getOptions = {}; // 为 `.get()` 准备选项
44
-
45
- /**
46
- * 设置页面,通过浏览器的输入自动填充机制提示用户选择用于身份验证的凭证;
47
- */
57
+ const publicKey = { ...optionsJSON, challenge: base64URLStringToBuffer(optionsJSON.challenge), allowCredentials },
58
+ getOptions = {};
48
59
  if (useBrowserAutofill) {
49
60
  if (!(await browserSupportsWebAuthnAutofill())) throw Error('浏览器不支持 WebAuthn 自动填充');
50
61
 
51
- // 检查是否存在 `autocomplete` 属性中包含 "webauthn" 的 <input>
52
62
  const eligibleInputs = document.querySelectorAll("input[autocomplete$='webauthn']");
53
-
54
- // WebAuthn 自动填充要求至少有一个有效的输入框
55
63
  if (eligibleInputs.length < 1 && verifyBrowserAutofillInput)
56
64
  throw Error('未检测到任何 `autocomplete` 属性中包含 "webauthn"(作为唯一值或最后一个值)的 <input> 元素');
57
65
 
58
- // 截至 typescript@4.6.3,`CredentialMediationRequirement` 尚不识别 "conditional"
59
- getOptions.mediation = 'conditional', publicKey.allowCredentials = []; // 条件式 UI 要求 allowCredentials 为空列表
66
+ getOptions.mediation = 'conditional', publicKey.allowCredentials = [];
60
67
  }
68
+ getOptions.publicKey = publicKey, getOptions.signal = WebAuthnAbortService.createNewAbortSignal();
61
69
 
62
- getOptions.publicKey = publicKey; // 最终确定选项
63
- getOptions.signal = WebAuthnAbortService.createNewAbortSignal(); // 设置取消此请求的能力,以防用户尝试另一个请求
64
-
65
- // 等待用户完成断言
66
70
  let credential;
67
71
  try {
68
72
  credential = await navigator.credentials.get(getOptions);
@@ -75,7 +79,6 @@ const startAuthentication = async options => {
75
79
  let userHandle = undefined;
76
80
  if (response.userHandle) userHandle = bufferToBase64URLString(response.userHandle);
77
81
 
78
- // 将值转换为 base64,以便更容易发送回服务器
79
82
  return {
80
83
  id,
81
84
  rawId: bufferToBase64URLString(rawId),
@@ -84,11 +87,8 @@ const startAuthentication = async options => {
84
87
  clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
85
88
  signature: bufferToBase64URLString(response.signature),
86
89
  userHandle,
87
- },
88
- type,
89
- clientExtensionResults: credential.getClientExtensionResults(),
90
+ }, type, clientExtensionResults: credential.getClientExtensionResults(),
90
91
  authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment),
91
92
  };
92
- }
93
-
93
+ };
94
94
  export { startAuthentication };
@@ -1,14 +1,26 @@
1
1
  import {
2
- bufferToBase64URLString, base64URLStringToBuffer, browserSupportsWebAuthn, identifyRegistrationError,
3
- toAuthenticatorAttachment, toPublicKeyCredentialDescriptor, WebAuthnAbortService
2
+ bufferToBase64URLString, base64URLStringToBuffer, browserSupportsWebAuthn, isWindows, extractBase64Id, normalizeOptions,
3
+ identifyRegistrationError, toAuthenticatorAttachment, toPublicKeyCredentialDescriptor, WebAuthnAbortService
4
4
  } from '../helpers/index.js';
5
5
 
6
+ /**
7
+ * 当检测到通行密钥提供方拦截 WebAuthn API 调用导致的问题时,发出可见警告
8
+ *
9
+ * @param {string} methodName - 被错误实现的 WebAuthn API 方法名称
10
+ * @param {unknown} cause - 捕获到的原始错误对象
11
+ * @returns {void}
12
+ */
13
+ const warnOnBrokenImplementation = (methodName, cause) => {
14
+ console.warn(`拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ${methodName};请向该扩展的开发者报告此问题;\n`, cause);
15
+ };
16
+
6
17
  /**
7
18
  * 通过 WebAuthn 证明开始认证器“注册”
8
19
  * - 查看定义:@see {@link startRegistration}
9
20
  *
10
21
  * @param {Object} options - 配置选项
11
- * @param {PublicKeyCredentialCreationOptionsJSON} options.optionsJSON - 来自 **@flun/webauthn-server** 的 `generateRegistrationOptions()` 的输出
22
+ * @param {PublicKeyCredentialCreationOptionsJSON} options.optionsJSON
23
+ * - 来自 **@flun/webauthn-server** 的 `generateRegistrationOptions()` 的输出
12
24
  * @param {boolean} [options.useAutoRegister] - 尝试静默使用用户刚刚登录的密码管理器创建一个通行密钥,默认为 `false`
13
25
  * @returns {Promise<{
14
26
  * id: string,
@@ -23,37 +35,52 @@ import {
23
35
  * },
24
36
  * type: PublicKeyCredentialType,
25
37
  * clientExtensionResults: AuthenticationExtensionsClientOutputs,
26
- * authenticatorAttachment: AuthenticatorAttachment | null
38
+ * authenticatorAttachment: AuthenticatorAttachment | null,
39
+ * native?: boolean // 当在 Windows 下启用原生流程时返回 true
27
40
  * }>}
28
41
  */
29
- const startRegistration = async options => {
30
- // 有意检查旧的调用结构,以警告不正确的 API 调用
31
- if (!options.optionsJSON && options.challenge) {
32
- console.warn('startRegistration() 的调用方式不正确;将继续尝试使用提供的选项,但应重构此调用以使用预期的调用结构;');
33
- options = { optionsJSON: options }; // 将作为位置参数传入的 options 重新赋值给预期的变量
42
+ const startRegistration = async (options) => {
43
+ // 规范化传入的参数,兼容旧的调用方式
44
+ const normalized = normalizeOptions(options);
45
+ if (!normalized) throw new Error('startRegistration 需要传入包含 optionsJSON 或 challenge 的对象');
46
+ options = normalized;
47
+
48
+ const { optionsJSON, useAutoRegister = false } = options;
49
+ // 若当前系统为 Windows,则直接返回模拟凭证数据,应用层可据此识别并调用底层 Windows Hello 接口;
50
+ if (isWindows) {
51
+ // 尝试从 optionsJSON.user.id 提取用户 ID,若无法提取则生成随机 UUID
52
+ const userId = extractBase64Id(optionsJSON?.user) || crypto.randomUUID();
53
+ return {
54
+ id: userId,
55
+ rawId: bufferToBase64URLString(base64URLStringToBuffer(userId)),
56
+ response: {
57
+ attestationObject: '', clientDataJSON: '', transports: [],
58
+ publicKeyAlgorithm: -7, publicKey: '', authenticatorData: ''
59
+ },
60
+ type: 'public-key',
61
+ clientExtensionResults: {},
62
+ authenticatorAttachment: 'platform',
63
+ native: true // 标记为原生流程
64
+ };
34
65
  }
35
66
 
67
+ // 标准 WebAuthn 注册流程
36
68
  if (!browserSupportsWebAuthn()) throw new Error('此浏览器不支持 WebAuthn');
37
- const { optionsJSON, useAutoRegister = false } = options,
38
- // 需要将部分值转换为 Uint8Array 后才能传递给 navigator 的 credentials
39
- publicKey = {
40
- ...optionsJSON,
41
- challenge: base64URLStringToBuffer(optionsJSON.challenge),
42
- user: { ...optionsJSON.user, id: base64URLStringToBuffer(optionsJSON.user.id) },
43
- excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor)
44
- }, createOptions = {}; // 准备传递给 `.create()` 的选项
69
+ const publicKey = {
70
+ ...optionsJSON,
71
+ challenge: base64URLStringToBuffer(optionsJSON.challenge),
72
+ user: { ...optionsJSON.user, id: base64URLStringToBuffer(optionsJSON.user.id) },
73
+ excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor)
74
+ }, createOptions = {};
45
75
 
46
76
  /**
47
- * 尝试使用条件创建(conditional create)为用户注册一个通行密钥,
48
- * 使用用户刚刚用于认证的密码管理器;浏览器不会向用户显示任何突出的 UI;
49
- * 注意:`mediation` 在 CredentialCreationOptions 中尚不存在,但自 2024 年 9 月起已可用
77
+ * 尝试使用条件创建(conditional create)为用户注册一个通行密钥,
78
+ * 使用用户刚刚用于认证的密码管理器;浏览器不会向用户显示任何突出的 UI;
79
+ * 注意:`mediation` 在 CredentialCreationOptions 中尚不存在,但自 2024 年 9 月起已可用
50
80
  */
51
81
  if (useAutoRegister) createOptions.mediation = 'conditional';
82
+ createOptions.publicKey = publicKey, createOptions.signal = WebAuthnAbortService.createNewAbortSignal();
52
83
 
53
- createOptions.publicKey = publicKey; // 最终确定选项
54
- createOptions.signal = WebAuthnAbortService.createNewAbortSignal(); // 设置取消此请求的能力(如果用户尝试另一个请求)
55
-
56
- // 等待用户完成证明
57
84
  let credential;
58
85
  try {
59
86
  credential = await navigator.credentials.create(createOptions);
@@ -62,13 +89,10 @@ const startRegistration = async options => {
62
89
  }
63
90
 
64
91
  if (!credential) throw new Error('注册未完成');
65
-
66
92
  const { id, rawId, response, type } = credential;
67
- // 暂时继续安全地使用 `getTransports()`,即使 L3 类型声称它是必需的
68
93
  let transports = void 0;
69
94
  if (typeof response.getTransports === 'function') transports = response.getTransports();
70
95
 
71
- // L3 声称这是必需的,但浏览器和 WebView 的支持仍不保证
72
96
  let responsePublicKeyAlgorithm = void 0;
73
97
  if (typeof response.getPublicKeyAlgorithm === 'function') {
74
98
  try {
@@ -84,7 +108,6 @@ const startRegistration = async options => {
84
108
  } catch (error) { warnOnBrokenImplementation('getPublicKey()', error); }
85
109
  }
86
110
 
87
- // L3 声称这是必需的,但浏览器和 WebView 的支持仍不保证
88
111
  let responseAuthenticatorData;
89
112
  if (typeof response.getAuthenticatorData === 'function') {
90
113
  try {
@@ -107,17 +130,5 @@ const startRegistration = async options => {
107
130
  clientExtensionResults: credential.getClientExtensionResults(),
108
131
  authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
109
132
  };
110
- }
111
-
112
- /**
113
- * 当检测到通行密钥提供方拦截 WebAuthn API 调用导致的问题时,发出可见警告
114
- *
115
- * @param {string} methodName - 被错误实现的 WebAuthn API 方法名称
116
- * @param {unknown} cause - 捕获到的原始错误对象
117
- * @returns {void}
118
- */
119
- const warnOnBrokenImplementation = (methodName, cause) => {
120
- console.warn(`拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ${methodName};请向该扩展的开发者报告此问题;\n`, cause);
121
- }
122
-
133
+ };
123
134
  export { startRegistration };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flun/webauthn-browser",
3
- "version": "2.0.18",
3
+ "version": "2.1.0",
4
4
  "description": "WebAuthn 前端库,提供简单易用的接口来处理 WebAuthn 认证流程,支持 Passkeys 和 FIDO2 标准;",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,6 +47,6 @@
47
47
  "npm": ">=10.0.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@types/node": "^20.9.0"
50
+ "@types/node": "^26.1.1"
51
51
  }
52
52
  }
package/dist/index.es5.js DELETED
@@ -1 +0,0 @@
1
- function e(b) { return e = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (a) { return typeof a } : function (a) { return a && "function" == typeof Symbol && a.constructor === Symbol && a !== Symbol.prototype ? "symbol" : typeof a }, e(b) } !function (a, b) { if ("function" == typeof define && define.amd) define("flunWebAuthnBrowser", ["exports"], b); else if ("undefined" != typeof exports) b(exports); else { var c = { exports: {} }; b(c.exports), a.flunWebAuthnBrowser = c.exports } }("undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : this, (function (n) { function I() { J = function () { return k }; var l, k = {}, s = Object.prototype, p = s.hasOwnProperty, t = Object.defineProperty || function (a, b, c) { a[b] = c.value }, q = "function" == typeof Symbol ? Symbol : {}, v = q.iterator || "@@iterator", z = q.asyncIterator || "@@asyncIterator", B = q.toStringTag || "@@toStringTag"; function w(a, b, c) { return Object.defineProperty(a, b, { value: c, enumerable: !0, configurable: !0, writable: !0 }), a[b] } try { w({}, "") } catch (l) { w = function (a, b, c) { return a[b] = c } } function Z(a, b, c, d) { var f = b && b.prototype instanceof O ? b : O, g = Object.create(f.prototype), i = new P(d || []); return t(g, "_invoke", { value: bt(a, c, i) }), g } function Q(a, b, c) { try { return { type: "normal", arg: a.call(b, c) } } catch (a) { return { type: "throw", arg: a } } } k.wrap = Z; var ba = "suspendedStart", bu = "suspendedYield", bb = "executing", K = "completed", x = {}; function O() { } function L() { } function C() { } var R = {}; w(R, v, (function () { return this })); var S = Object.getPrototypeOf, M = S && S(S(T([]))); M && M !== s && p.call(M, v) && (R = M); var D = C.prototype = O.prototype = Object.create(R); function bc(c) { ["next", "throw", "return"].forEach((function (b) { w(c, b, (function (a) { return this._invoke(b, a) })) })) } function N(j, o) { function r(b, c, d, f) { var g = Q(j[b], j, c); if ("throw" !== g.type) { var i = g.arg, h = i.value; return h && "object" == e(h) && p.call(h, "__await") ? o.resolve(h.__await).then((function (a) { r("next", a, d, f) }), (function (a) { r("throw", a, d, f) })) : o.resolve(h).then((function (a) { i.value = a, d(i) }), (function (a) { return r("throw", a, d, f) })) } f(g.arg) } var y; t(this, "_invoke", { value: function (c, d) { function f() { return new o((function (a, b) { r(c, d, a, b) })) } return y = y ? y.then(f, f) : f() } }) } function bt(g, i, h) { var j = ba; return function (a, b) { if (j === bb) throw Error("Generator is already running"); if (j === K) { if ("throw" === a) throw b; return { value: l, done: !0 } } for (h.method = a, h.arg = b; ;) { var c = h.delegate; if (c) { var d = bd(c, h); if (d) { if (d === x) continue; return d } } if ("next" === h.method) h.sent = h._sent = h.arg; else if ("throw" === h.method) { if (j === ba) throw j = K, h.arg; h.dispatchException(h.arg) } else "return" === h.method && h.abrupt("return", h.arg); j = bb; var f = Q(g, i, h); if ("normal" === f.type) { if (j = h.done ? K : bu, f.arg === x) continue; return { value: f.arg, done: h.done } } "throw" === f.type && (j = K, h.method = "throw", h.arg = f.arg) } } } function bd(a, b) { var c = b.method, d = a.iterator[c]; if (d === l) return b.delegate = null, "throw" === c && a.iterator.return && (b.method = "return", b.arg = l, bd(a, b), "throw" === b.method) || "return" !== c && (b.method = "throw", b.arg = new TypeError("The iterator does not provide a '" + c + "' method")), x; var f = Q(d, a.iterator, b.arg); if ("throw" === f.type) return b.method = "throw", b.arg = f.arg, b.delegate = null, x; var g = f.arg; return g ? g.done ? (b[a.resultName] = g.value, b.next = a.nextLoc, "return" !== b.method && (b.method = "next", b.arg = l), b.delegate = null, x) : g : (b.method = "throw", b.arg = new TypeError("iterator result is not an object"), b.delegate = null, x) } function bv(a) { var b = { tryLoc: a[0] }; 1 in a && (b.catchLoc = a[1]), 2 in a && (b.finallyLoc = a[2], b.afterLoc = a[3]), this.tryEntries.push(b) } function U(a) { var b = a.completion || {}; b.type = "normal", delete b.arg, a.completion = b } function P(a) { this.tryEntries = [{ tryLoc: "root" }], a.forEach(bv, this), this.reset(!0) } function T(a) { if (a || "" === a) { var b = a[v]; if (b) return b.call(a); if ("function" == typeof a.next) return a; if (!isNaN(a.length)) { var c = -1, d = function e() { for (; ++c < a.length;)if (p.call(a, c)) return e.value = a[c], e.done = !1, e; return e.value = l, e.done = !0, e }; return d.next = d } } throw new TypeError(e(a) + " is not iterable") } return L.prototype = C, t(D, "constructor", { value: C, configurable: !0 }), t(C, "constructor", { value: L, configurable: !0 }), L.displayName = w(C, B, "GeneratorFunction"), k.isGeneratorFunction = function (a) { var b = "function" == typeof a && a.constructor; return !!b && (b === L || "GeneratorFunction" === (b.displayName || b.name)) }, k.mark = function (a) { return Object.setPrototypeOf ? Object.setPrototypeOf(a, C) : (a.__proto__ = C, w(a, B, "GeneratorFunction")), a.prototype = Object.create(D), a }, k.awrap = function (a) { return { __await: a } }, bc(N.prototype), w(N.prototype, z, (function () { return this })), k.AsyncIterator = N, k.async = function (b, c, d, f, g) { void 0 === g && (g = Promise); var i = new N(Z(b, c, d, f), g); return k.isGeneratorFunction(c) ? i : i.next().then((function (a) { return a.done ? a.value : i.next() })) }, bc(D), w(D, B, "Generator"), w(D, v, (function () { return this })), w(D, "toString", (function () { return "[object Generator]" })), k.keys = function (b) { var c = Object(b), d = []; for (var f in c) d.push(f); return d.reverse(), function b() { for (; d.length;) { var a = d.pop(); if (a in c) return b.value = a, b.done = !1, b } return b.done = !0, b } }, k.values = T, P.prototype = { constructor: P, reset: function (a) { if (this.prev = 0, this.next = 0, this.sent = this._sent = l, this.done = !1, this.delegate = null, this.method = "next", this.arg = l, this.tryEntries.forEach(U), !a) for (var b in this) "t" === b.charAt(0) && p.call(this, b) && !isNaN(+b.slice(1)) && (this[b] = l) }, stop: function () { this.done = !0; var a = this.tryEntries[0].completion; if ("throw" === a.type) throw a.arg; return this.rval }, dispatchException: function (c) { if (this.done) throw c; var d = this; function f(a, b) { return h.type = "throw", h.arg = c, d.next = a, b && (d.method = "next", d.arg = l), !!b } for (var g = this.tryEntries.length - 1; g >= 0; --g) { var i = this.tryEntries[g], h = i.completion; if ("root" === i.tryLoc) return f("end"); if (i.tryLoc <= this.prev) { var j = p.call(i, "catchLoc"), o = p.call(i, "finallyLoc"); if (j && o) { if (this.prev < i.catchLoc) return f(i.catchLoc, !0); if (this.prev < i.finallyLoc) return f(i.finallyLoc) } else if (j) { if (this.prev < i.catchLoc) return f(i.catchLoc, !0) } else { if (!o) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return f(i.finallyLoc) } } } }, abrupt: function (a, b) { for (var c = this.tryEntries.length - 1; c >= 0; --c) { var d = this.tryEntries[c]; if (d.tryLoc <= this.prev && p.call(d, "finallyLoc") && this.prev < d.finallyLoc) { var f = d; break } } f && ("break" === a || "continue" === a) && f.tryLoc <= b && b <= f.finallyLoc && (f = null); var g = f ? f.completion : {}; return g.type = a, g.arg = b, f ? (this.method = "next", this.next = f.finallyLoc, x) : this.complete(g) }, complete: function (a, b) { if ("throw" === a.type) throw a.arg; return "break" === a.type || "continue" === a.type ? this.next = a.arg : "return" === a.type ? (this.rval = this.arg = a.arg, this.method = "return", this.next = "end") : "normal" === a.type && b && (this.next = b), x }, finish: function (a) { for (var b = this.tryEntries.length - 1; b >= 0; --b) { var c = this.tryEntries[b]; if (c.finallyLoc === a) return this.complete(c.completion, c.afterLoc), U(c), x } }, catch: function (e) { for (var l = this.tryEntries.length - 1; l >= 0; --l) { var J = this.tryEntries[l]; if (J.tryLoc === e) { var k = J.completion; if ("throw" === k.type) { var s = k.arg; U(J) } return s } } throw Error("illegal catch attempt") }, delegateYield: function (a, b, c) { return this.delegate = { iterator: T(a), resultName: b, nextLoc: c }, "next" === this.method && (this.arg = l), x } }, k } function be(a, b, c, d, f, g, i) { try { var h = a[g](i), j = h.value } catch (a) { return void c(a) } h.done ? b(j) : Promise.resolve(j).then(d, f) } function bf(j) { return function () { var i = this, h = arguments; return new Promise((function (b, c) { var d = j.apply(i, h); function f(a) { be(d, b, c, f, g, "next", a) } function g(a) { be(d, b, c, f, g, "throw", a) } f(void 0) })) } } function bg(a, b) { for (var c = 0; c < b.length; c++) { var d = b[c]; d.enumerable = d.enumerable || !1, d.configurable = !0, "value" in d && (d.writable = !0), Object.defineProperty(a, bh(d.key), d) } } function bi(a, b, c) { return b && bg(a.prototype, b), c && bg(a, c), Object.defineProperty(a, "prototype", { writable: !1 }), a } function bj(a, b) { if (!(a instanceof b)) throw new TypeError("Cannot call a class as a function") } function bw(d, f, g) { return f = E(f), function (b, c) { if (c && ("object" == e(c) || "function" == typeof c)) return c; if (void 0 !== c) throw new TypeError("Derived constructors may only return object or undefined"); return function (a) { if (void 0 === a) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return a }(b) }(d, V() ? Reflect.construct(f, g || [], E(d).constructor) : f.apply(d, g)) } function W(h) { var j = "function" == typeof Map ? new Map : void 0; return W = function (g) { if (null === g || !function (g) { try { return -1 !== Function.toString.call(g).indexOf("[native code]") } catch (j) { return "function" == typeof g } }(g)) return g; if ("function" != typeof g) throw new TypeError("Super expression must either be null or a function"); if (void 0 !== j) { if (j.has(g)) return j.get(g); j.set(g, i) } function i() { return function (a, b, c) { if (V()) return Reflect.construct.apply(null, arguments); var d = [null]; d.push.apply(d, b); var f = new (a.bind.apply(a, d)); return c && F(f, c.prototype), f }(g, arguments, E(this).constructor) } return i.prototype = Object.create(g.prototype, { constructor: { value: i, enumerable: !1, writable: !0, configurable: !0 } }), F(i, g) }, W(h) } function V() { try { var a = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], (function () { }))) } catch (a) { } return (V = function () { return !!a })() } function F(c, d) { return F = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (a, b) { return a.__proto__ = b, a }, F(c, d) } function E(b) { return E = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (a) { return a.__proto__ || Object.getPrototypeOf(a) }, E(b) } function bk(b, c) { var d = Object.keys(b); if (Object.getOwnPropertySymbols) { var f = Object.getOwnPropertySymbols(b); c && (f = f.filter((function (a) { return Object.getOwnPropertyDescriptor(b, a).enumerable }))), d.push.apply(d, f) } return d } function A(b) { for (var c = 1; c < arguments.length; c++) { var d = null != arguments[c] ? arguments[c] : {}; c % 2 ? bk(Object(d), !0).forEach((function (a) { bx(b, a, d[a]) })) : Object.getOwnPropertyDescriptors ? Object.defineProperties(b, Object.getOwnPropertyDescriptors(d)) : bk(Object(d)).forEach((function (a) { Object.defineProperty(b, a, Object.getOwnPropertyDescriptor(d, a)) })) } return b } function bx(a, b, c) { return (b = bh(b)) in a ? Object.defineProperty(a, b, { value: c, enumerable: !0, configurable: !0, writable: !0 }) : a[b] = c, a } function bh(f) { var g = function (a, b) { if ("object" != e(a) || !a) return a; var c = a[Symbol.toPrimitive]; if (void 0 !== c) { var d = c.call(a, b || "default"); if ("object" != e(d)) return d; throw new TypeError("@@toPrimitive must return a primitive value.") } return ("string" === b ? String : Number)(a) }(f, "string"); return "symbol" == e(g) ? g : g + "" } function by(b, c) { var d = "undefined" != typeof Symbol && b[Symbol.iterator] || b["@@iterator"]; if (!d) { if (Array.isArray(b) || (d = function (b, c) { if (b) { if ("string" == typeof b) return bl(b, c); var d = {}.toString.call(b).slice(8, -1); return "Object" === d && b.constructor && (d = b.constructor.name), "Map" === d || "Set" === d ? Array.from(b) : "Arguments" === d || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(d) ? bl(b, c) : void 0 } }(b)) || c && b && "number" == typeof b.length) { d && (b = d); var f = 0, g = function () { }; return { s: g, n: function () { return f >= b.length ? { done: !0 } : { done: !1, value: b[f++] } }, e: function (a) { throw a }, f: g } } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") } var i, h = !0, j = !1; return { s: function () { d = d.call(b) }, n: function () { var a = d.next(); return h = a.done, a }, e: function (a) { j = !0, i = a }, f: function () { try { h || null == d.return || d.return() } finally { if (j) throw i } } } } function bl(a, b) { (null == b || b > a.length) && (b = a.length); for (var c = 0, d = Array(b); c < b; c++)d[c] = a[c]; return d } function u(a) { var b, c = "", d = by(new Uint8Array(a)); try { for (d.s(); !(b = d.n()).done;) { var f = b.value; c += String.fromCharCode(f) } } catch (a) { d.e(a) } finally { d.f() } return btoa(c).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } function G(a) { for (var b = a.replace(/-/g, "+").replace(/_/g, "/"), c = (4 - b.length % 4) % 4, d = b.padEnd(b.length + c, "="), f = atob(d), g = new ArrayBuffer(f.length), i = new Uint8Array(g), h = 0; h < f.length; h++)i[h] = f.charCodeAt(h); return g } function H() { var a; return bz.stubThis(void 0 !== (null === (a = window) || void 0 === a ? void 0 : a.PublicKeyCredential) && "function" == typeof window.PublicKeyCredential) } Object.defineProperty(n, "__esModule", { value: !0 }), n._browserSupportsWebAuthnInternals = n._browserSupportsWebAuthnAutofillInternals = n.WebAuthnError = n.WebAuthnAbortService = void 0, n.base64URLStringToBuffer = G, n.browserSupportsWebAuthn = H, n.browserSupportsWebAuthnAutofill = bm, n.bufferToBase64URLString = u, n.platformAuthenticatorIsAvailable = function () { if (!H()) return new Promise((function (e) { return e(!1) })); return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() }, n.startAuthentication = function (a) { return bn.apply(this, arguments) }, n.startRegistration = function (a) { return bo.apply(this, arguments) }; var bz = n._browserSupportsWebAuthnInternals = { stubThis: function (a) { return a } }; function bp(a) { var b = a.id; return A(A({}, a), {}, { id: G(b), transports: a.transports }) } function bq(a) { return "localhost" === a || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(a) } var m = n.WebAuthnError = function (i) { function h(a) { var b, c = a.message, d = a.code, f = a.cause, g = a.name; return bj(this, h), b = bw(this, h, [c, { cause: f }]), Object.defineProperty(b, "code", { enumerable: !0, configurable: !0, writable: !0, value: void 0 }), b.name = null != g ? g : f.name, b.code = d, b } return function (a, b) { if ("function" != typeof b && null !== b) throw new TypeError("Super expression must either be null or a function"); a.prototype = Object.create(b && b.prototype, { constructor: { value: a, writable: !0, configurable: !0 } }), Object.defineProperty(a, "prototype", { writable: !1 }), b && F(a, b) }(h, i), bi(h) }(W(Error)); function bA(a) { var b = a.error, c = a.options, d = c.publicKey; if (!d) throw Error("options was missing required publicKey property"); if ("AbortError" === b.name) { if (c.signal instanceof AbortSignal) return new m({ message: "注册流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: b }) } else if ("ConstraintError" === b.name) { var f, g, i; if (!0 === (null === (f = d.authenticatorSelection) || void 0 === f ? void 0 : f.requireResidentKey)) return new m({ message: "要求使用可发现凭证,但可用的验证器不支持", code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT", cause: b }); if ("conditional" === c.mediation && "required" === (null === (g = d.authenticatorSelection) || void 0 === g ? void 0 : g.userVerification)) return new m({ message: "自动注册时要求用户验证,但无法执行", code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE", cause: b }); if ("required" === (null === (i = d.authenticatorSelection) || void 0 === i ? void 0 : i.userVerification)) return new m({ message: "要求用户验证,但可用的验证器不支持", code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT", cause: b }) } else { if ("InvalidStateError" === b.name) return new m({ message: "该验证器已被注册过", code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED", cause: b }); if ("NotAllowedError" === b.name) return new m({ message: b.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: b }); if ("NotSupportedError" === b.name) return 0 === d.pubKeyCredParams.filter((function (a) { return "public-key" === a.type })).length ? new m({ message: 'pubKeyCredParams 中没有 type 为 "public-key" 的条目', code: "ERROR_MALFORMED_PUBKEYCREDPARAMS", cause: b }) : new m({ message: "没有可用的验证器支持指定的任何 pubKeyCredParams 算法", code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG", cause: b }); if ("SecurityError" === b.name) { var h = window.location.hostname; if (!bq(h)) return new m({ message: "".concat(window.location.hostname, " 是无效域名"), code: "ERROR_INVALID_DOMAIN", cause: b }); if (d.rp.id !== h) return new m({ message: 'RP ID "'.concat(d.rp.id, '" 对于当前域名无效'), code: "ERROR_INVALID_RP_ID", cause: b }) } else if ("TypeError" === b.name) { if (d.user.id.byteLength < 1 || d.user.id.byteLength > 64) return new m({ message: "用户 ID 长度不在1~64字节之间", code: "ERROR_INVALID_USER_ID_LENGTH", cause: b }) } else if ("UnknownError" === b.name) return new m({ message: "验证器无法处理指定的选项或创建新的凭证", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: b }) } return b } var bB = function () { return bi((function c() { bj(this, c), Object.defineProperty(this, "controller", { enumerable: !0, configurable: !0, writable: !0, value: void 0 }) }), [{ key: "createNewAbortSignal", value: function () { if (this.controller) { var a = new Error("正在取消已有的 WebAuthn API 调用,然后进行新的调用"); a.name = "AbortError", this.controller.abort(a) } var b = new AbortController; return this.controller = b, b.signal } }, { key: "cancelCeremony", value: function () { if (this.controller) { var a = new Error("正在取消 WebAuthn API 调用"); a.name = "AbortError", this.controller.abort(a), this.controller = void 0 } } }]) }(), br = n.WebAuthnAbortService = new bB, bC = ["cross-platform", "platform"]; function bs(a) { if (a && !(bC.indexOf(a) < 0)) return a } function bo() { return (bo = bf(I().mark((function B(b) { var c, d, f, g, i, h, j, o, r, y, l, k, s, p, t, q, v, z; return I().wrap((function (a) { for (; ;)switch (a.prev = a.next) { case 0: if (!b.optionsJSON && b.challenge && (console.warn("startRegistration() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;"), b = { optionsJSON: b }), f = (d = b).optionsJSON, g = d.useAutoRegister, i = void 0 !== g && g, H()) { a.next = 4; break } throw new Error("当前浏览器不支持 WebAuthn"); case 4: return h = A(A({}, f), {}, { challenge: G(f.challenge), user: A(A({}, f.user), {}, { id: G(f.user.id) }), excludeCredentials: null === (c = f.excludeCredentials) || void 0 === c ? void 0 : c.map(bp) }), j = {}, i && (j.mediation = "conditional"), j.publicKey = h, j.signal = br.createNewAbortSignal(), a.prev = 9, a.next = 12, navigator.credentials.create(j); case 12: o = a.sent, a.next = 18; break; case 15: throw a.prev = 15, a.t0 = a.catch(9), bA({ error: a.t0, options: j }); case 18: if (o) { a.next = 20; break } throw new Error("注册未完成"); case 20: if (y = (r = o).id, l = r.rawId, k = r.response, s = r.type, p = void 0, "function" == typeof k.getTransports && (p = k.getTransports()), t = void 0, "function" == typeof k.getPublicKeyAlgorithm) try { t = k.getPublicKeyAlgorithm() } catch (a) { X("getPublicKeyAlgorithm()", a) } if (q = void 0, "function" == typeof k.getPublicKey) try { null !== (v = k.getPublicKey()) && (q = u(v)) } catch (a) { X("getPublicKey()", a) } if ("function" == typeof k.getAuthenticatorData) try { z = u(k.getAuthenticatorData()) } catch (a) { X("getAuthenticatorData()", a) } return a.abrupt("return", { id: y, rawId: u(l), response: { attestationObject: u(k.attestationObject), clientDataJSON: u(k.clientDataJSON), transports: p, publicKeyAlgorithm: t, publicKey: q, authenticatorData: z }, type: s, clientExtensionResults: o.getClientExtensionResults(), authenticatorAttachment: bs(o.authenticatorAttachment) }); case 29: case "end": return a.stop() } }), B, null, [[9, 15]]) })))).apply(this, arguments) } function X(a, b) { console.warn("拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ".concat(a, ";请向扩展开发者报告此问题;\n"), b) } function bm() { if (!H()) return Y.stubThis(new Promise((function (b) { return b(!1) }))); var b = window.PublicKeyCredential; return void 0 === (null == b ? void 0 : b.isConditionalMediationAvailable) ? Y.stubThis(new Promise((function (a) { return a(!1) }))) : Y.stubThis(b.isConditionalMediationAvailable()) } var Y = n._browserSupportsWebAuthnAutofillInternals = { stubThis: function (a) { return a } }; function bD(a) { var b = a.error, c = a.options, d = c.publicKey; if (!d) throw Error("options was missing required publicKey property"); if ("AbortError" === b.name) { if (c.signal instanceof AbortSignal) return new m({ message: "认证流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: b }) } else { if ("NotAllowedError" === b.name) return new m({ message: b.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: b }); if ("SecurityError" === b.name) { var f = window.location.hostname; if (!bq(f)) return new m({ message: "".concat(window.location.hostname, " 是无效域名"), code: "ERROR_INVALID_DOMAIN", cause: b }); if (d.rpId !== f) return new m({ message: 'RP ID "'.concat(d.rpId, '" 对于当前域名无效'), code: "ERROR_INVALID_RP_ID", cause: b }) } else if ("UnknownError" === b.name) return new m({ message: "验证器无法处理指定的选项或创建新的断言签名", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: b }) } return b } function bn() { return (bn = bf(I().mark((function B(b) { var c, d, f, g, i, h, j, o, r, y, l, k, s, p, t, q, v, z; return I().wrap((function (a) { for (; ;)switch (a.prev = a.next) { case 0: if (!b.optionsJSON && b.challenge && (console.warn("startAuthentication() 调用方式不正确;将尝试继续使用提供的选项,但建议按照正确的调用结构重构;"), b = { optionsJSON: b }), f = (d = b).optionsJSON, g = d.useBrowserAutofill, i = void 0 !== g && g, h = d.verifyBrowserAutofillInput, j = void 0 === h || h, H()) { a.next = 4; break } throw new Error("当前浏览器不支持 WebAuthn"); case 4: if (0 !== (null === (c = f.allowCredentials) || void 0 === c ? void 0 : c.length) && (o = null === (r = f.allowCredentials) || void 0 === r ? void 0 : r.map(bp)), y = A(A({}, f), {}, { challenge: G(f.challenge), allowCredentials: o }), l = {}, !i) { a.next = 17; break } return a.next = 10, bm(); case 10: if (a.sent) { a.next = 12; break } throw Error("当前浏览器不支持 WebAuthn 自动填充"); case 12: if (!(document.querySelectorAll("input[autocomplete$='webauthn']").length < 1 && j)) { a.next = 15; break } throw Error('未检测到任何 `autocomplete` 属性值以 "webauthn" 结尾的 <input> 元素'); case 15: l.mediation = "conditional", y.allowCredentials = []; case 17: return l.publicKey = y, l.signal = br.createNewAbortSignal(), a.prev = 19, a.next = 22, navigator.credentials.get(l); case 22: k = a.sent, a.next = 28; break; case 25: throw a.prev = 25, a.t0 = a.catch(19), bD({ error: a.t0, options: l }); case 28: if (k) { a.next = 30; break } throw new Error("认证未完成"); case 30: return p = (s = k).id, t = s.rawId, q = s.response, v = s.type, z = void 0, q.userHandle && (z = u(q.userHandle)), a.abrupt("return", { id: p, rawId: u(t), response: { authenticatorData: u(q.authenticatorData), clientDataJSON: u(q.clientDataJSON), signature: u(q.signature), userHandle: z }, type: v, clientExtensionResults: k.getClientExtensionResults(), authenticatorAttachment: bs(k.authenticatorAttachment) }); case 34: case "end": return a.stop() } }), B, null, [[19, 25]]) })))).apply(this, arguments) } }));