@flun/html-template 4.0.10

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.
Files changed (59) hide show
  1. package/.env +9 -0
  2. package/LICENSE +15 -0
  3. package/build.js +3 -0
  4. package/compile.js +349 -0
  5. package/copy-files.js +200 -0
  6. package/customize/account.js +726 -0
  7. package/customize/data.json +484 -0
  8. package/customize/functions.js +48 -0
  9. package/customize/hotReloadInjector.js +25 -0
  10. package/customize/routes.js +141 -0
  11. package/customize/users.json +44 -0
  12. package/customize/variables.js +70 -0
  13. package/dev-server.js +344 -0
  14. package/dev.js +4 -0
  15. package/f-CHANGELOG.md +4 -0
  16. package/f-README.md +485 -0
  17. package/index.d.ts +133 -0
  18. package/index.js +4 -0
  19. package/package.json +77 -0
  20. package/restoreDefaults.js +8 -0
  21. package/services/templateService.js +962 -0
  22. package/static/about.css +118 -0
  23. package/static/auth.js +27 -0
  24. package/static/constants.css +138 -0
  25. package/static/img/dark.png +0 -0
  26. package/static/img/favicon.ico +0 -0
  27. package/static/img/light.png +0 -0
  28. package/static/img/top.png +0 -0
  29. package/static/index.css +86 -0
  30. package/static/mouseOrTouch.js +156 -0
  31. package/static/public.css +288 -0
  32. package/static/script.css +318 -0
  33. package/static/script.js +392 -0
  34. package/static/styling.css +874 -0
  35. package/static/styling.js +933 -0
  36. package/static/themeImg.css +10 -0
  37. package/static/themeImg.js +19 -0
  38. package/static/themeModule.js +222 -0
  39. package/static/topImg.css +19 -0
  40. package/static/topImg.js +21 -0
  41. package/static/utils/browser13.js +270 -0
  42. package/static/utils/closebrackets.js +166 -0
  43. package/static/utils/css-lint.js +308 -0
  44. package/static/utils/custom-css-hint.js +876 -0
  45. package/static/utils/foldgutter.js +141 -0
  46. package/static/utils/match-highlighter.js +70 -0
  47. package/templates/about.html +236 -0
  48. package/templates/account/2fa.html +184 -0
  49. package/templates/account/forgot-password.html +226 -0
  50. package/templates/account/login.html +230 -0
  51. package/templates/account/profile.html +977 -0
  52. package/templates/account/register.html +224 -0
  53. package/templates/account/reset-password.html +205 -0
  54. package/templates/account/verify-email.html +163 -0
  55. package/templates/base.html +71 -0
  56. package/templates/footer-content.html +5 -0
  57. package/templates/index.html +140 -0
  58. package/templates/script.html +209 -0
  59. package/templates/test-include.html +11 -0
@@ -0,0 +1,10 @@
1
+ /* 主题图标样式 */
2
+ #theme {
3
+ /* 绝对定位 */
4
+ position: fixed;
5
+ left: 20px;
6
+ top: 10px;
7
+ height: 40px;
8
+ z-index: 1002;
9
+ cursor: pointer;
10
+ }
@@ -0,0 +1,19 @@
1
+ function initThemeImg() {
2
+ const themeIcon = document.createElement('img'), api = '/api/themeImg';
3
+ themeIcon.id = 'theme', themeIcon.alt = '切换主题';
4
+
5
+ // 设置图标回调(根据主题切换图片路径)
6
+ ThemeModule.setIconUpdateCallback(function (actualTheme) {
7
+ if (themeIcon) themeIcon.src = actualTheme === 'light' ? '/static/img/dark.png' : '/static/img/light.png';
8
+ });
9
+
10
+ // 获取储存样式(位置)并应用
11
+ getStyle(themeIcon, api), document.body.append(themeIcon);
12
+
13
+ // 处理点击和拖动
14
+ mouseOrTouch(themeIcon, () => ThemeModule.toggleTheme(), api);
15
+ }
16
+
17
+ // 页面加载完成后创建主题图标
18
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initThemeImg);
19
+ else initThemeImg();
@@ -0,0 +1,222 @@
1
+ // ===================== 主题模块 (优化版) =====================
2
+ /**
3
+ * 主题管理模块,负责处理主题切换功能
4
+ *
5
+ * 主要优化:
6
+ * 1. 移除了重复的主题计算和设置逻辑
7
+ * 2. 使用永久系统主题监听器替代频繁销毁/重建
8
+ * 3. 简化初始化流程,同时保持功能完整
9
+ *
10
+ * 主要功能:
11
+ * 1. 支持三种主题模式:system(跟随系统)、light(明亮模式)、dark(暗黑模式)
12
+ * 2. 自动保存用户偏好到本地存储
13
+ * 3. 自动监听系统主题变化(当处于系统模式时)
14
+ * 4. 提供图标更新回调机制,只在浅色和深色模式间切换
15
+ *
16
+ * 使用说明:
17
+ * 1. 模块加载后自动初始化
18
+ * 2. 使用 setIconUpdateCallback 设置图标更新回调
19
+ * 3. 使用 toggleTheme 或 setPreference 切换主题
20
+ *
21
+ * @namespace
22
+ */
23
+ const ThemeModule = (function () {
24
+ let currentPreference = 'system', iconUpdateCallback = null; // 私有变量,封装模块内部状态(主题偏好和图标更新回调函数)
25
+ const systemMedia = window.matchMedia('(prefers-color-scheme: dark)'); // 系统主题监听器
26
+
27
+ /**
28
+ * 加载用户保存的主题偏好
29
+ * @private
30
+ */
31
+ function loadPreference() {
32
+ const savedPreference = localStorage.getItem('themePreference'); // 从本地存储获取保存的偏好设置
33
+ // 验证保存的偏好是否有效
34
+ if (savedPreference && ['system', 'light', 'dark'].includes(savedPreference)) currentPreference = savedPreference;
35
+ }
36
+
37
+ /**
38
+ * 计算实际应用的主题
39
+ * @private
40
+ * @returns {'light' | 'dark'} 实际应用的主题
41
+ */
42
+ function getActualTheme() {
43
+ if (currentPreference === 'system') return systemMedia.matches ? 'dark' : 'light'; // 当处于系统模式时,返回当前系统主题
44
+ return currentPreference; // 否则返回用户指定的主题
45
+ }
46
+
47
+ /**
48
+ * 应用主题到文档根元素
49
+ * @private
50
+ */
51
+ function applyTheme() {
52
+ const targetTheme = getActualTheme(); // 获取实际应用的主题
53
+ document.documentElement.setAttribute('data-theme', targetTheme); // 设置文档根元素的data-theme属性
54
+ }
55
+
56
+ /**
57
+ * 安全更新主题切换图标
58
+ * @private
59
+ */
60
+ function safeUpdateIcon() {
61
+ // 检查图标更新回调是否已设置
62
+ if (iconUpdateCallback && typeof iconUpdateCallback === 'function') {
63
+ try {
64
+ const actualTheme = getActualTheme(); // 获取当前实际应用的主题
65
+ iconUpdateCallback(actualTheme); // 调用回调函数并传递当前实际主题
66
+ } catch (e) {
67
+ console.warn('主题图标更新失败', e);
68
+ }
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 处理系统主题变化事件
74
+ * @private
75
+ */
76
+ function handleSystemThemeChange() {
77
+ if (currentPreference === 'system') applyTheme(), safeUpdateIcon(); // 仅当处于系统模式时响应变化(更新主题和图标)
78
+ }
79
+
80
+ systemMedia.addEventListener('change', handleSystemThemeChange); // 初始化系统主题监听器
81
+ // 公共API
82
+ return {
83
+ /**
84
+ * 初始化主题模块
85
+ * @method
86
+ */
87
+ init: function () {
88
+ // 加载保存的偏好并应用主题
89
+ loadPreference(), applyTheme();
90
+ const updateOnReady = () => safeUpdateIcon();
91
+ if (document.readyState === 'complete') updateOnReady(); // DOM加载完成后更新图标
92
+ else document.addEventListener('DOMContentLoaded', updateOnReady); // 否则监听DOMContentLoaded事件
93
+ },
94
+
95
+ /**
96
+ * 设置图标更新回调
97
+ *
98
+ * 使用说明:
99
+ * 此方法用于设置主题切换时的图标更新回调
100
+ * 回调函数将接收当前实际应用的主题值('light' 或 'dark')
101
+ *
102
+ * @param {Function} callback - 图标更新回调函数
103
+ * @method
104
+ */
105
+ setIconUpdateCallback: function (callback) {
106
+ if (typeof callback === 'function') iconUpdateCallback = callback, safeUpdateIcon(); // 设置后立即更新一次图标状态
107
+ },
108
+
109
+ /**
110
+ * 设置主题偏好
111
+ *
112
+ * 使用说明:
113
+ * 此方法直接设置主题偏好,适用于需要精确控制主题的场景
114
+ *
115
+ * 示例:
116
+ * ThemeModule.setPreference('dark'); // 设置为暗黑模式
117
+ * ThemeModule.setPreference('system'); // 设置为跟随系统
118
+ *
119
+ * @param {'system' | 'light' | 'dark'} preference - 主题偏好
120
+ * @method
121
+ */
122
+ setPreference: function (preference) {
123
+ if (['system', 'light', 'dark'].indexOf(preference) === -1) return;// 验证输入有效性
124
+ currentPreference = preference; // 更新当前偏好
125
+ localStorage.setItem('themePreference', preference); // 保存到本地存储
126
+ applyTheme(), safeUpdateIcon(); // 应用新主题并更新图标
127
+ },
128
+
129
+ /**
130
+ * 切换主题(在浅色和深色模式间切换)
131
+ *
132
+ * 使用说明:
133
+ * 此方法用于在浅色和深色模式间切换,忽略系统模式
134
+ * 如果当前是系统模式,会先切换到当前实际主题
135
+ *
136
+ * 示例:
137
+ * document.getElementById('theme-toggle').addEventListener('click', () => {
138
+ * ThemeModule.toggleTheme();
139
+ * });
140
+ *
141
+ * @method
142
+ */
143
+ toggleTheme: function () {
144
+ // 获取当前实际主题,切换到相反的主题(忽略系统模式)
145
+ const currentTheme = this.getActualTheme(), newTheme = currentTheme === 'light' ? 'dark' : 'light';
146
+ this.setPreference(newTheme); // 设置新偏好
147
+ },
148
+
149
+ /**
150
+ * 获取当前偏好设置
151
+ *
152
+ * 使用说明:
153
+ * 此方法用于获取当前的主题偏好设置
154
+ *
155
+ * 示例:
156
+ * const currentPref = ThemeModule.getPreference();
157
+ * console.log('当前主题偏好:', currentPref);
158
+ *
159
+ * @returns {string} 当前主题偏好
160
+ * @method
161
+ */
162
+ getPreference: function () {
163
+ return currentPreference;
164
+ },
165
+
166
+ /**
167
+ * 获取实际应用的主题
168
+ *
169
+ * 使用说明:
170
+ * 此方法用于获取实际应用的主题(考虑系统偏好)
171
+ *
172
+ * 示例:
173
+ * const actualTheme = ThemeModule.getActualTheme();
174
+ * console.log('实际应用的主题:', actualTheme);
175
+ *
176
+ * @returns {string} 实际应用的主题 ('light' 或 'dark')
177
+ * @method
178
+ */
179
+ getActualTheme: function () {
180
+ return getActualTheme();
181
+ }
182
+ };
183
+ })();
184
+
185
+ ThemeModule.init(); // 初始化主题模块
186
+ // ===================== 使用示例 =====================
187
+ /**
188
+ * 基本使用:
189
+ * 1. 在页面加载后自动初始化(直接引入模块即可)
190
+
191
+ // 切换主题:
192
+ document.getElementById('themeToggle').addEventListener('click', () => ThemeModule.toggleTheme());
193
+
194
+ // 直接设置主题('system'、'light' 或 'dark'):
195
+ ThemeModule.setPreference('dark');
196
+
197
+ // 包含图标的使用:
198
+ // 1. 设置图标回调(只需一次)
199
+ ThemeModule.setIconUpdateCallback(function (actualTheme) {
200
+ const iconElement = document.getElementById('theme-icon');
201
+ if (iconElement) {
202
+ const iconMap = {
203
+ light: 'fa-sun', // 明亮模式图标
204
+ dark: 'fa-moon' // 暗黑模式图标
205
+ };
206
+ iconElement.className = `fas ${iconMap[actualTheme]}`;
207
+ }
208
+ });
209
+
210
+ // 2. 按钮点击切换主题和图标
211
+ document.getElementById('themeToggle').addEventListener('click', () => {
212
+ ThemeModule.toggleTheme(); // 同时切换主题和图标
213
+ });
214
+
215
+ // 添加图标动画效果(CSS示例)
216
+ .theme-toggle i {
217
+ transition: transform 0.3s ease;
218
+ }
219
+ .theme-toggle:hover i {
220
+ transform: rotate(20deg);
221
+ }
222
+ */
@@ -0,0 +1,19 @@
1
+ /* ======= 返回顶部样式 ======= */
2
+ :root {
3
+ --safe-area-bottom: env(safe-area-inset-bottom, 0px);
4
+ }
5
+
6
+ .scroll-to-top {
7
+ position: fixed;
8
+ right: 20px;
9
+ bottom: calc(8px + var(--safe-area-bottom));
10
+ height: 50px;
11
+ z-index: 1000;
12
+ opacity: 0;
13
+ transition: opacity 0.3s;
14
+ cursor: pointer;
15
+ }
16
+
17
+ .scroll-to-top.show {
18
+ opacity: 1;
19
+ }
@@ -0,0 +1,21 @@
1
+ function initTopImg() {
2
+ const topIcon = document.createElement('img'), api = '/api/topImg';
3
+ topIcon.className = 'scroll-to-top', topIcon.alt = '返回顶部', topIcon.src = '/static/img/top.png';
4
+ // 统一处理按钮显示/隐藏
5
+ function updateTopImg() {
6
+ topIcon.classList.toggle('show', window.pageYOffset > 300);
7
+ };
8
+
9
+ // 获取储存样式(位置)并应用
10
+ getStyle(topIcon, api), document.body.append(topIcon);
11
+
12
+ // 初始化时立即检查一次,然后添加一个事件监听器以检查更新
13
+ updateTopImg(), window.addEventListener('scroll', updateTopImg);
14
+
15
+ // 处理点击和拖动
16
+ mouseOrTouch(topIcon, () => window.scrollTo({ top: 0, behavior: 'smooth' }), api, true);
17
+ }
18
+
19
+ // 初始化
20
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initTopImg);
21
+ else initTopImg();
@@ -0,0 +1,270 @@
1
+ /* 基于[@simplewebauthn/browser@13.3.0] 开发 */
2
+ !function (e, t) {
3
+ "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd
4
+ ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).flunWebAuthnBrowser = {})
5
+ }(this, e => {
6
+ "use strict";
7
+
8
+ const defaultPropDescriptor = { enumerable: true, configurable: true, writable: true, value: void 0 },
9
+ falsePromise = Promise.resolve(false),
10
+ // 将 ArrayBuffer 转换为 Base64URL 字符串
11
+ bufferToBase64URLString = (e) => {
12
+ const t = new Uint8Array(e);
13
+ let r = "";
14
+ for (const e of t) r += String.fromCharCode(e);
15
+ return btoa(r).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
16
+ },
17
+ // 将 Base64URL 字符串转换为 ArrayBuffer
18
+ base64URLStringToBuffer = (e) => {
19
+ const t = e.replace(/-/g, "+").replace(/_/g, "/"), r = (4 - t.length % 4) % 4, n = t.padEnd(t.length + r, "="),
20
+ o = atob(n), i = new ArrayBuffer(o.length), a = new Uint8Array(i);
21
+ for (let e = 0; e < o.length; e++) a[e] = o.charCodeAt(e);
22
+ return i
23
+ },
24
+ o = { stubThis: e => e },
25
+ // 检查浏览器是否支持 WebAuthn
26
+ browserSupportsWebAuthn = () =>
27
+ o.stubThis(void 0 !== globalThis?.PublicKeyCredential && "function" == typeof globalThis.PublicKeyCredential),
28
+ // 转换允许凭证中的 ID 字段
29
+ convertAllowCredential = e => {
30
+ const { id: t } = e;
31
+ return { ...e, id: base64URLStringToBuffer(t), transports: e.transports }
32
+ },
33
+ // 验证域名有效性
34
+ isValidDomain = e =>
35
+ "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
+ WebAuthnAbortService = new class {
39
+ constructor() {
40
+ Object.defineProperty(this, "controller", defaultPropDescriptor)
41
+ }
42
+ createNewAbortSignal() {
43
+ if (this.controller) {
44
+ const e = new Error("正在取消已有的 WebAuthn API 调用,然后进行新的调用");
45
+ e.name = "AbortError", this.controller.abort(e)
46
+ }
47
+ const e = new AbortController;
48
+ return this.controller = e, e.signal
49
+ }
50
+ cancelCeremony() {
51
+ if (this.controller) {
52
+ const e = new Error("正在取消 WebAuthn API 调用");
53
+ e.name = "AbortError", this.controller.abort(e), this.controller = void 0
54
+ }
55
+ }
56
+ },
57
+
58
+ authenticatorAttachmentValues = ["cross-platform", "platform"],
59
+ normalizeAuthenticatorAttachment = (e) => {
60
+ if (e && !(authenticatorAttachmentValues.indexOf(e) < 0)) return e
61
+ },
62
+ warnExtensionMishandling = (e, t) => {
63
+ console.warn(`拦截此 WebAuthn API 调用的浏览器扩展错误地实现了 ${e};请向扩展开发者报告此问题;\n`, t)
64
+ },
65
+ p = { stubThis: e => e },
66
+ // 检查浏览器是否支持 WebAuthn 自动填充
67
+ browserSupportsWebAuthnAutofill = () => {
68
+ if (!browserSupportsWebAuthn()) return p.stubThis(falsePromise);
69
+ const e = globalThis.PublicKeyCredential;
70
+ return void 0 === e?.isConditionalMediationAvailable ? p.stubThis(falsePromise)
71
+ : p.stubThis(e.isConditionalMediationAvailable())
72
+ };
73
+
74
+ // 自定义 WebAuthn 错误类
75
+ class WebAuthnError extends Error {
76
+ constructor({ message: e, code: t, cause: r, name: n }) {
77
+ super(e, { cause: r });
78
+ Object.defineProperty(this, "code", defaultPropDescriptor), this.name = n ?? r.name, this.code = t
79
+ }
80
+ };
81
+
82
+ // 导出内部工具(供测试/扩展使用)
83
+ e.WebAuthnAbortService = WebAuthnAbortService;
84
+ e.WebAuthnError = WebAuthnError;
85
+ e._browserSupportsWebAuthnAutofillInternals = p;
86
+ e._browserSupportsWebAuthnInternals = o;
87
+ e.base64URLStringToBuffer = base64URLStringToBuffer;
88
+ e.browserSupportsWebAuthn = browserSupportsWebAuthn;
89
+ e.browserSupportsWebAuthnAutofill = browserSupportsWebAuthnAutofill;
90
+ e.bufferToBase64URLString = bufferToBase64URLString;
91
+ e.platformAuthenticatorIsAvailable = () => {
92
+ return browserSupportsWebAuthn() ? PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() : falsePromise;
93
+ };
94
+
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 })
141
+ }
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
+
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;
168
+ 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 = {};
176
+ let f;
177
+ if (c) p.mediation = "conditional";
178
+ p.publicKey = h, p.signal = WebAuthnAbortService.createNewAbortSignal();
179
+ try {
180
+ f = await navigator.credentials.create(p)
181
+ } catch (error) {
182
+ throw (({ error: e, options: t }) => {
183
+ const { publicKey: r } = t;
184
+ if (!r) throw new Error("options 缺少必需的 publicKey 属性");
185
+ if ("AbortError" === e.name) {
186
+ if (t.signal instanceof AbortSignal) return new WebAuthnError({
187
+ message: "注册流程收到了中止信号", code: "ERROR_CEREMONY_ABORTED", cause: e
188
+ })
189
+ } else if ("ConstraintError" === e.name) {
190
+ if (!0 === r.authenticatorSelection?.requireResidentKey) return new WebAuthnError({
191
+ message: "要求使用可发现凭证,但可用的验证器不支持",
192
+ code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT", cause: e
193
+ });
194
+ if ("conditional" === t.mediation && "required" === r.authenticatorSelection?.userVerification)
195
+ return new WebAuthnError({
196
+ message: "自动注册时要求用户验证,但无法执行",
197
+ code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE", cause: e
198
+ });
199
+ if ("required" === r.authenticatorSelection?.userVerification) return new WebAuthnError({
200
+ message: "要求用户验证,但可用的验证器不支持",
201
+ code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT", cause: e
202
+ })
203
+ } else {
204
+ if ("InvalidStateError" === e.name) return new WebAuthnError({
205
+ message: "该验证器已被注册过", code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED", cause: e
206
+ });
207
+ if ("NotAllowedError" === e.name) return new WebAuthnError({
208
+ message: e.message, code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY", cause: e
209
+ });
210
+ if ("NotSupportedError" === e.name) {
211
+ if (0 === r.pubKeyCredParams.filter((e => "public-key" === e.type)).length) return new WebAuthnError({
212
+ message: 'pubKeyCredParams 中没有 type 为 "public-key" 的条目',
213
+ code: "ERROR_MALFORMED_PUBKEYCREDPARAMS", cause: e
214
+ });
215
+ return new WebAuthnError({
216
+ message: "没有可用的验证器支持指定的任何 pubKeyCredParams 算法",
217
+ code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG", cause: e
218
+ })
219
+ }
220
+ if ("SecurityError" === e.name) {
221
+ const t = globalThis.location.hostname;
222
+ if (!isValidDomain(t)) return new WebAuthnError({
223
+ message: `${globalThis.location.hostname} 是无效域名`, code: "ERROR_INVALID_DOMAIN", cause: e
224
+ });
225
+ if (r.rp.id !== t) return new WebAuthnError({
226
+ message: `RP ID "${r.rp.id}" 对于当前域名无效`, code: "ERROR_INVALID_RP_ID", cause: e
227
+ })
228
+ } else if ("TypeError" === e.name) {
229
+ if (r.user.id.byteLength < 1 || r.user.id.byteLength > 64) return new WebAuthnError({
230
+ message: "用户 ID 长度不在1~64字节之间", code: "ERROR_INVALID_USER_ID_LENGTH", cause: e
231
+ })
232
+ } else if ("UnknownError" === e.name) return new WebAuthnError({
233
+ message: "验证器无法处理指定的选项或创建新的凭证", code: "ERROR_AUTHENTICATOR_GENERAL_ERROR", cause: e
234
+ })
235
+ }
236
+ return e
237
+ })({ error, options: p })
238
+ }
239
+ if (!f) throw new Error("注册未完成");
240
+ const { id: b, rawId: R, response: g, type: w } = f;
241
+ let A, E, m, y;
242
+ if ("function" == typeof g.getTransports) A = g.getTransports();
243
+ if ("function" == typeof g.getPublicKeyAlgorithm) {
244
+ try { E = g.getPublicKeyAlgorithm() }
245
+ catch (e) { warnExtensionMishandling("getPublicKeyAlgorithm()", e) }
246
+ }
247
+ if ("function" == typeof g.getPublicKey) {
248
+ try {
249
+ const e = g.getPublicKey();
250
+ null !== e && (m = bufferToBase64URLString(e))
251
+ } catch (e) { warnExtensionMishandling("getPublicKey()", e) }
252
+ }
253
+ if ("function" == typeof g.getAuthenticatorData) {
254
+ try { y = bufferToBase64URLString(g.getAuthenticatorData()) }
255
+ catch (e) { warnExtensionMishandling("getAuthenticatorData()", e) }
256
+ }
257
+ return {
258
+ id: b,
259
+ rawId: bufferToBase64URLString(R),
260
+ response: {
261
+ attestationObject: bufferToBase64URLString(g.attestationObject),
262
+ clientDataJSON: bufferToBase64URLString(g.clientDataJSON),
263
+ transports: A, publicKeyAlgorithm: E, publicKey: m, authenticatorData: y
264
+ },
265
+ type: w,
266
+ clientExtensionResults: f.getClientExtensionResults(),
267
+ authenticatorAttachment: normalizeAuthenticatorAttachment(f.authenticatorAttachment)
268
+ }
269
+ }
270
+ });