@lee-zg/melange 1.2.2 → 1.2.5

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 (38) hide show
  1. package/README.md +257 -256
  2. package/dist/{chunk-YZVCK6VZ.cjs → chunk-AAWWAPSG.cjs} +1425 -2
  3. package/dist/{chunk-3RM45M64.js → chunk-R3BPDZ3R.js} +1388 -3
  4. package/dist/container-B8n-ok8M.d.cts +201 -0
  5. package/dist/container-n8Q2Xa_y.d.ts +201 -0
  6. package/dist/core/index.d.cts +3 -200
  7. package/dist/core/index.d.ts +3 -200
  8. package/dist/generator-CEj5qxFh.d.cts +1752 -0
  9. package/dist/generator-D7ofMxfc.d.ts +1752 -0
  10. package/dist/index.cjs +138 -34
  11. package/dist/index.d.cts +4 -3
  12. package/dist/index.d.ts +4 -3
  13. package/dist/index.js +2 -2
  14. package/dist/plugins/index.cjs +162 -9
  15. package/dist/plugins/index.d.cts +108 -755
  16. package/dist/plugins/index.d.ts +108 -755
  17. package/dist/plugins/index.js +2 -1
  18. package/package.json +143 -135
  19. package/dist/chunk-3RM45M64.js.map +0 -1
  20. package/dist/chunk-7QVYU63E.js.map +0 -1
  21. package/dist/chunk-BEY4UAYF.cjs.map +0 -1
  22. package/dist/chunk-GXFWPL5M.js.map +0 -1
  23. package/dist/chunk-GZBY4BUP.js.map +0 -1
  24. package/dist/chunk-ILNGTDQ4.js.map +0 -1
  25. package/dist/chunk-JLBTZPBY.cjs.map +0 -1
  26. package/dist/chunk-PK6SKIKE.cjs.map +0 -1
  27. package/dist/chunk-UYJUSNDI.cjs.map +0 -1
  28. package/dist/chunk-YZVCK6VZ.cjs.map +0 -1
  29. package/dist/core/index.cjs.map +0 -1
  30. package/dist/core/index.js.map +0 -1
  31. package/dist/fp/index.cjs.map +0 -1
  32. package/dist/fp/index.js.map +0 -1
  33. package/dist/index.cjs.map +0 -1
  34. package/dist/index.js.map +0 -1
  35. package/dist/plugins/index.cjs.map +0 -1
  36. package/dist/plugins/index.js.map +0 -1
  37. package/dist/utils/index.cjs.map +0 -1
  38. package/dist/utils/index.js.map +0 -1
@@ -1,8 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ var chunkBEY4UAYF_cjs = require('./chunk-BEY4UAYF.cjs');
3
4
  var chunkPK6SKIKE_cjs = require('./chunk-PK6SKIKE.cjs');
4
5
 
5
6
  // src/plugins/speech/synthesis.ts
7
+ var SynthesisStatus = /* @__PURE__ */ ((SynthesisStatus2) => {
8
+ SynthesisStatus2["IDLE"] = "IDLE";
9
+ SynthesisStatus2["LOADING"] = "LOADING";
10
+ SynthesisStatus2["SPEAKING"] = "SPEAKING";
11
+ SynthesisStatus2["PAUSED"] = "PAUSED";
12
+ return SynthesisStatus2;
13
+ })(SynthesisStatus || {});
6
14
  var SynthesisAudioUtils = {
7
15
  /**
8
16
  * 创建 AudioContext
@@ -53,6 +61,380 @@ var SynthesisAudioUtils = {
53
61
  return Math.ceil(byteLength * 8 / bitRates[format] * 1e3);
54
62
  }
55
63
  };
64
+ var GenericSynthesisAdapter = class {
65
+ constructor(baseUrl) {
66
+ this.baseUrl = baseUrl;
67
+ }
68
+ static {
69
+ chunkPK6SKIKE_cjs.__name(this, "GenericSynthesisAdapter");
70
+ }
71
+ name = "Generic/BFF";
72
+ async synthesize(text, config) {
73
+ const response = await fetch(`${this.baseUrl}/synthesize`, {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({
77
+ text,
78
+ lang: config?.lang ?? "zh-CN",
79
+ voice: typeof config?.voice === "string" ? config.voice : config?.voice?.id,
80
+ rate: config?.rate ?? 1,
81
+ pitch: config?.pitch ?? 1,
82
+ volume: config?.volume ?? 1,
83
+ format: config?.audioFormat ?? "mp3"
84
+ })
85
+ });
86
+ if (!response.ok) {
87
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
88
+ }
89
+ const audioData = await response.arrayBuffer();
90
+ return {
91
+ audioData,
92
+ format: config?.audioFormat ?? "mp3",
93
+ duration: SynthesisAudioUtils.estimateDuration(
94
+ audioData.byteLength,
95
+ config?.audioFormat ?? "mp3"
96
+ )
97
+ };
98
+ }
99
+ async getVoices() {
100
+ try {
101
+ const response = await fetch(`${this.baseUrl}/voices`);
102
+ if (!response.ok) return [];
103
+ const data = await response.json();
104
+ return data.voices ?? [];
105
+ } catch {
106
+ return [];
107
+ }
108
+ }
109
+ };
110
+ var AzureSynthesisAdapter = class {
111
+ constructor(subscriptionKey, region, defaultVoice = "zh-CN-XiaoxiaoNeural") {
112
+ this.subscriptionKey = subscriptionKey;
113
+ this.region = region;
114
+ this.defaultVoice = defaultVoice;
115
+ }
116
+ static {
117
+ chunkPK6SKIKE_cjs.__name(this, "AzureSynthesisAdapter");
118
+ }
119
+ name = "Azure";
120
+ async synthesize(text, config) {
121
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
122
+ const rate = config?.rate ?? 1;
123
+ const pitch = config?.pitch ?? 1;
124
+ const ssml = `
125
+ <speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="${config?.lang ?? "zh-CN"}">
126
+ <voice name="${voice}">
127
+ <prosody rate="${rate}" pitch="${(pitch - 1) * 50}%">
128
+ ${this.escapeXml(text)}
129
+ </prosody>
130
+ </voice>
131
+ </speak>`;
132
+ const response = await fetch(
133
+ `https://${this.region}.tts.speech.microsoft.com/cognitiveservices/v1`,
134
+ {
135
+ method: "POST",
136
+ headers: {
137
+ "Ocp-Apim-Subscription-Key": this.subscriptionKey,
138
+ "Content-Type": "application/ssml+xml",
139
+ "X-Microsoft-OutputFormat": "audio-16khz-128kbitrate-mono-mp3"
140
+ },
141
+ body: ssml
142
+ }
143
+ );
144
+ if (!response.ok) {
145
+ throw new Error(`Azure TTS Error: ${response.status}`);
146
+ }
147
+ const audioData = await response.arrayBuffer();
148
+ return { audioData, format: "mp3" };
149
+ }
150
+ async getVoices() {
151
+ const response = await fetch(
152
+ `https://${this.region}.tts.speech.microsoft.com/cognitiveservices/voices/list`,
153
+ {
154
+ headers: { "Ocp-Apim-Subscription-Key": this.subscriptionKey }
155
+ }
156
+ );
157
+ if (!response.ok) return [];
158
+ const voices = await response.json();
159
+ return voices.map((v) => ({
160
+ id: v.ShortName,
161
+ name: v.DisplayName,
162
+ lang: v.Locale,
163
+ gender: v.Gender.toLowerCase(),
164
+ provider: "Azure"
165
+ }));
166
+ }
167
+ escapeXml(text) {
168
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
169
+ }
170
+ };
171
+ var GoogleSynthesisAdapter = class {
172
+ constructor(apiKey, defaultVoice = "zh-CN-Wavenet-A") {
173
+ this.apiKey = apiKey;
174
+ this.defaultVoice = defaultVoice;
175
+ }
176
+ static {
177
+ chunkPK6SKIKE_cjs.__name(this, "GoogleSynthesisAdapter");
178
+ }
179
+ name = "Google";
180
+ async synthesize(text, config) {
181
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
182
+ const lang = config?.lang ?? "zh-CN";
183
+ const payload = {
184
+ input: config?.enableSSML ? { ssml: text } : { text },
185
+ voice: {
186
+ languageCode: lang,
187
+ name: voice
188
+ },
189
+ audioConfig: {
190
+ audioEncoding: "MP3",
191
+ speakingRate: config?.rate ?? 1,
192
+ pitch: config?.pitch ?? 0,
193
+ volumeGainDb: ((config?.volume ?? 1) - 1) * 10
194
+ }
195
+ };
196
+ const response = await fetch(
197
+ `https://texttospeech.googleapis.com/v1/text:synthesize?key=${this.apiKey}`,
198
+ {
199
+ method: "POST",
200
+ headers: { "Content-Type": "application/json" },
201
+ body: JSON.stringify(payload)
202
+ }
203
+ );
204
+ if (!response.ok) {
205
+ const error = await response.json();
206
+ throw new Error(error.error?.message ?? "Google TTS Error");
207
+ }
208
+ const data = await response.json();
209
+ const audioData = SynthesisAudioUtils.base64ToArrayBuffer(data.audioContent);
210
+ return { audioData, format: "mp3" };
211
+ }
212
+ async getVoices() {
213
+ const response = await fetch(
214
+ `https://texttospeech.googleapis.com/v1/voices?key=${this.apiKey}`
215
+ );
216
+ if (!response.ok) return [];
217
+ const data = await response.json();
218
+ return data.voices.map((v) => ({
219
+ id: v.name,
220
+ name: v.name,
221
+ lang: v.languageCodes[0] ?? "en-US",
222
+ gender: v.ssmlGender.toLowerCase(),
223
+ provider: "Google"
224
+ }));
225
+ }
226
+ };
227
+ var AWSSynthesisAdapter = class {
228
+ constructor(accessKeyId, secretAccessKey, region, defaultVoice = "Zhiyu") {
229
+ this.accessKeyId = accessKeyId;
230
+ this.secretAccessKey = secretAccessKey;
231
+ this.region = region;
232
+ this.defaultVoice = defaultVoice;
233
+ }
234
+ static {
235
+ chunkPK6SKIKE_cjs.__name(this, "AWSSynthesisAdapter");
236
+ }
237
+ name = "AWS";
238
+ async synthesize(text, config) {
239
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
240
+ const response = await fetch("/api/aws-polly/synthesize", {
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json" },
243
+ body: JSON.stringify({
244
+ text,
245
+ voiceId: voice,
246
+ languageCode: config?.lang ?? "cmn-CN",
247
+ outputFormat: "mp3",
248
+ accessKeyId: this.accessKeyId,
249
+ secretAccessKey: this.secretAccessKey,
250
+ region: this.region
251
+ })
252
+ });
253
+ if (!response.ok) {
254
+ throw new Error(`AWS Polly Error: ${response.status}`);
255
+ }
256
+ const audioData = await response.arrayBuffer();
257
+ return { audioData, format: "mp3" };
258
+ }
259
+ getVoices() {
260
+ return Promise.resolve([
261
+ { id: "Zhiyu", name: "\u667A\u745C", lang: "cmn-CN", gender: "female", provider: "AWS" }
262
+ ]);
263
+ }
264
+ };
265
+ var XunfeiSynthesisAdapter = class {
266
+ // Note: apiKey and apiSecret are reserved for future direct API authentication
267
+ // Currently using BFF proxy mode which handles auth server-side
268
+ constructor(appId, _apiKey, _apiSecret, defaultVoice = "xiaoyan") {
269
+ this.appId = appId;
270
+ this.defaultVoice = defaultVoice;
271
+ }
272
+ static {
273
+ chunkPK6SKIKE_cjs.__name(this, "XunfeiSynthesisAdapter");
274
+ }
275
+ name = "Xunfei";
276
+ async synthesize(text, config) {
277
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
278
+ const response = await fetch("/api/xunfei-tts/synthesize", {
279
+ method: "POST",
280
+ headers: { "Content-Type": "application/json" },
281
+ body: JSON.stringify({
282
+ text,
283
+ appId: this.appId,
284
+ vcn: voice,
285
+ speed: Math.round((config?.rate ?? 1) * 50),
286
+ pitch: Math.round((config?.pitch ?? 1) * 50),
287
+ volume: Math.round((config?.volume ?? 1) * 100),
288
+ aue: "lame"
289
+ // mp3
290
+ })
291
+ });
292
+ if (!response.ok) {
293
+ throw new Error(`\u8BAF\u98DE TTS Error: ${response.status}`);
294
+ }
295
+ const audioData = await response.arrayBuffer();
296
+ return { audioData, format: "mp3" };
297
+ }
298
+ getVoices() {
299
+ return Promise.resolve([
300
+ { id: "xiaoyan", name: "\u5C0F\u71D5", lang: "zh-CN", gender: "female", provider: "Xunfei" },
301
+ { id: "aisjiuxu", name: "\u8BB8\u4E45", lang: "zh-CN", gender: "male", provider: "Xunfei" },
302
+ { id: "aisxping", name: "\u5C0F\u840D", lang: "zh-CN", gender: "female", provider: "Xunfei" },
303
+ { id: "aisjinger", name: "\u5C0F\u5A67", lang: "zh-CN", gender: "female", provider: "Xunfei" }
304
+ ]);
305
+ }
306
+ };
307
+ var TencentSynthesisAdapter = class {
308
+ constructor(secretId, secretKey, defaultVoice = "101001") {
309
+ this.secretId = secretId;
310
+ this.secretKey = secretKey;
311
+ this.defaultVoice = defaultVoice;
312
+ }
313
+ static {
314
+ chunkPK6SKIKE_cjs.__name(this, "TencentSynthesisAdapter");
315
+ }
316
+ name = "Tencent";
317
+ async synthesize(text, config) {
318
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
319
+ const response = await fetch("/api/tencent-tts/synthesize", {
320
+ method: "POST",
321
+ headers: { "Content-Type": "application/json" },
322
+ body: JSON.stringify({
323
+ text,
324
+ secretId: this.secretId,
325
+ secretKey: this.secretKey,
326
+ voiceType: Number(voice),
327
+ speed: config?.rate ?? 1,
328
+ volume: config?.volume ?? 0,
329
+ codec: "mp3"
330
+ })
331
+ });
332
+ if (!response.ok) {
333
+ throw new Error(`\u817E\u8BAF\u4E91 TTS Error: ${response.status}`);
334
+ }
335
+ const audioData = await response.arrayBuffer();
336
+ return { audioData, format: "mp3" };
337
+ }
338
+ getVoices() {
339
+ return Promise.resolve([
340
+ { id: "101001", name: "\u667A\u745C", lang: "zh-CN", gender: "female", provider: "Tencent" },
341
+ { id: "101002", name: "\u667A\u8046", lang: "zh-CN", gender: "male", provider: "Tencent" },
342
+ { id: "101003", name: "\u667A\u7F8E", lang: "zh-CN", gender: "female", provider: "Tencent" }
343
+ ]);
344
+ }
345
+ };
346
+ var BaiduSynthesisAdapter = class {
347
+ constructor(accessToken, defaultVoice = "0") {
348
+ this.accessToken = accessToken;
349
+ this.defaultVoice = defaultVoice;
350
+ }
351
+ static {
352
+ chunkPK6SKIKE_cjs.__name(this, "BaiduSynthesisAdapter");
353
+ }
354
+ name = "Baidu";
355
+ async synthesize(text, config) {
356
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
357
+ const params = new URLSearchParams({
358
+ tex: encodeURIComponent(text),
359
+ tok: this.accessToken,
360
+ cuid: `sdk-user-${Date.now()}`,
361
+ ctp: "1",
362
+ lan: config?.lang === "en-US" ? "en" : "zh",
363
+ spd: String(Math.round((config?.rate ?? 1) * 5)),
364
+ pit: String(Math.round((config?.pitch ?? 1) * 5)),
365
+ vol: String(Math.round((config?.volume ?? 1) * 15)),
366
+ per: voice,
367
+ aue: "3"
368
+ // mp3
369
+ });
370
+ const response = await fetch("/api/baidu-tts/synthesize", {
371
+ method: "POST",
372
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
373
+ body: params.toString()
374
+ });
375
+ const contentType = response.headers.get("Content-Type") ?? "";
376
+ if (contentType.includes("audio")) {
377
+ const audioData = await response.arrayBuffer();
378
+ return { audioData, format: "mp3" };
379
+ }
380
+ const error = await response.json();
381
+ throw new Error(error.err_msg ?? "\u767E\u5EA6 TTS Error");
382
+ }
383
+ getVoices() {
384
+ return Promise.resolve([
385
+ { id: "0", name: "\u5EA6\u5C0F\u7F8E", lang: "zh-CN", gender: "female", provider: "Baidu" },
386
+ { id: "1", name: "\u5EA6\u5C0F\u5B87", lang: "zh-CN", gender: "male", provider: "Baidu" },
387
+ { id: "3", name: "\u5EA6\u900D\u9065", lang: "zh-CN", gender: "male", provider: "Baidu" },
388
+ { id: "4", name: "\u5EA6\u4E2B\u4E2B", lang: "zh-CN", gender: "female", provider: "Baidu" }
389
+ ]);
390
+ }
391
+ };
392
+ var AlibabaSynthesisAdapter = class {
393
+ // Note: accessKeySecret is reserved for future direct API authentication
394
+ // Currently using BFF proxy mode which handles auth server-side
395
+ constructor(accessKeyId, _accessKeySecret, appKey, defaultVoice = "xiaoyun") {
396
+ this.accessKeyId = accessKeyId;
397
+ this.appKey = appKey;
398
+ this.defaultVoice = defaultVoice;
399
+ }
400
+ static {
401
+ chunkPK6SKIKE_cjs.__name(this, "AlibabaSynthesisAdapter");
402
+ }
403
+ name = "Alibaba";
404
+ async synthesize(text, config) {
405
+ const voice = typeof config?.voice === "string" ? config.voice : config?.voice?.id ?? this.defaultVoice;
406
+ const response = await fetch("/api/alibaba-tts/synthesize", {
407
+ method: "POST",
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ "X-NLS-Token": this.accessKeyId
411
+ },
412
+ body: JSON.stringify({
413
+ appkey: this.appKey,
414
+ text,
415
+ format: "mp3",
416
+ voice,
417
+ sample_rate: 16e3,
418
+ speech_rate: Math.round((config?.rate ?? 1 - 1) * 500),
419
+ pitch_rate: Math.round((config?.pitch ?? 1 - 1) * 500),
420
+ volume: Math.round((config?.volume ?? 1) * 100)
421
+ })
422
+ });
423
+ if (!response.ok) {
424
+ throw new Error(`\u963F\u91CC\u4E91 TTS Error: ${response.status}`);
425
+ }
426
+ const audioData = await response.arrayBuffer();
427
+ return { audioData, format: "mp3" };
428
+ }
429
+ getVoices() {
430
+ return Promise.resolve([
431
+ { id: "xiaoyun", name: "\u5C0F\u4E91", lang: "zh-CN", gender: "female", provider: "Alibaba" },
432
+ { id: "xiaogang", name: "\u5C0F\u521A", lang: "zh-CN", gender: "male", provider: "Alibaba" },
433
+ { id: "ruoxi", name: "\u82E5\u516E", lang: "zh-CN", gender: "female", provider: "Alibaba" },
434
+ { id: "siqi", name: "\u601D\u742A", lang: "zh-CN", gender: "female", provider: "Alibaba" }
435
+ ]);
436
+ }
437
+ };
56
438
  var BaseSynthesisStrategy = class {
57
439
  constructor(config) {
58
440
  this.config = config;
@@ -631,8 +1013,28 @@ async function speak(text, config) {
631
1013
  }
632
1014
  }
633
1015
  chunkPK6SKIKE_cjs.__name(speak, "speak");
1016
+ async function speakWithCloud(text, adapter, config) {
1017
+ const synthesizer = await createSpeechSynthesizer({
1018
+ ...config,
1019
+ mode: "cloud",
1020
+ cloudAdapter: adapter
1021
+ });
1022
+ try {
1023
+ await synthesizer.speak(text, config);
1024
+ } finally {
1025
+ synthesizer.dispose();
1026
+ }
1027
+ }
1028
+ chunkPK6SKIKE_cjs.__name(speakWithCloud, "speakWithCloud");
634
1029
 
635
1030
  // src/plugins/speech/recognition.ts
1031
+ var RecognitionStatus = /* @__PURE__ */ ((RecognitionStatus2) => {
1032
+ RecognitionStatus2["IDLE"] = "IDLE";
1033
+ RecognitionStatus2["CONNECTING"] = "CONNECTING";
1034
+ RecognitionStatus2["RECORDING"] = "RECORDING";
1035
+ RecognitionStatus2["PROCESSING"] = "PROCESSING";
1036
+ return RecognitionStatus2;
1037
+ })(RecognitionStatus || {});
636
1038
  var AudioUtils = {
637
1039
  /**
638
1040
  * 重采样音频数据
@@ -828,6 +1230,455 @@ class SpeechProcessor extends AudioWorkletProcessor {
828
1230
  }
829
1231
  registerProcessor('speech-processor', SpeechProcessor);
830
1232
  `;
1233
+ var GenericAdapter = class {
1234
+ constructor(baseUrl) {
1235
+ this.baseUrl = baseUrl;
1236
+ }
1237
+ static {
1238
+ chunkPK6SKIKE_cjs.__name(this, "GenericAdapter");
1239
+ }
1240
+ name = "Generic/BFF";
1241
+ getConnectUrl() {
1242
+ return this.baseUrl.replace(/^http/, "ws");
1243
+ }
1244
+ async recognizeShortAudio(audioData) {
1245
+ const formData = new FormData();
1246
+ formData.append("file", new Blob([audioData], { type: "audio/wav" }));
1247
+ const res = await fetch(`${this.baseUrl}/recognize`, {
1248
+ method: "POST",
1249
+ body: formData
1250
+ });
1251
+ if (!res.ok) {
1252
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
1253
+ }
1254
+ const json = await res.json();
1255
+ const result = this.parseResult(json);
1256
+ if (!result) throw new Error("\u89E3\u6790\u7ED3\u679C\u5931\u8D25");
1257
+ return result;
1258
+ }
1259
+ parseResult(data) {
1260
+ return {
1261
+ transcript: data["text"] || data["transcript"] || "",
1262
+ isFinal: true,
1263
+ confidence: data["score"] || data["confidence"] || 0.9,
1264
+ original: data
1265
+ };
1266
+ }
1267
+ };
1268
+ var XunfeiAdapter = class {
1269
+ constructor(appId, apiKey, apiSecret) {
1270
+ this.appId = appId;
1271
+ this.apiKey = apiKey;
1272
+ this.apiSecret = apiSecret;
1273
+ }
1274
+ static {
1275
+ chunkPK6SKIKE_cjs.__name(this, "XunfeiAdapter");
1276
+ }
1277
+ name = "Xunfei";
1278
+ /** API Key - 生产环境应在后端使用 */
1279
+ apiKey;
1280
+ /** API Secret - 生产环境应在后端使用 */
1281
+ apiSecret;
1282
+ getConnectUrl() {
1283
+ const host = "iat-api.xfyun.cn";
1284
+ const path = "/v2/iat";
1285
+ return `wss://${host}${path}?authorization=...&date=...&host=${host}`;
1286
+ }
1287
+ getHandshakeParams() {
1288
+ return {
1289
+ common: { app_id: this.appId },
1290
+ business: {
1291
+ language: "zh_cn",
1292
+ domain: "iat",
1293
+ accent: "mandarin",
1294
+ vad_eos: 3e3
1295
+ },
1296
+ data: {
1297
+ status: 0,
1298
+ format: "audio/L16;rate=16000",
1299
+ encoding: "raw"
1300
+ }
1301
+ };
1302
+ }
1303
+ async recognizeShortAudio(audioData) {
1304
+ const blob = new Blob([audioData], { type: "audio/wav" });
1305
+ const formData = new FormData();
1306
+ formData.append("audio", blob);
1307
+ formData.append("engine_type", "sms16k");
1308
+ const response = await fetch("https://api.xfyun.cn/v1/service/v1/iat", {
1309
+ method: "POST",
1310
+ headers: {
1311
+ "X-Appid": this.appId
1312
+ // 生产环境需添加: 'X-CurTime', 'X-Param', 'X-CheckSum'
1313
+ },
1314
+ body: formData
1315
+ });
1316
+ const data = await response.json();
1317
+ const result = this.parseResult(data);
1318
+ if (!result) throw new Error("\u8BAF\u98DE\u8BC6\u522B\u5931\u8D25");
1319
+ return result;
1320
+ }
1321
+ parseResult(data) {
1322
+ if (data["code"] !== void 0 && data["code"] !== 0) {
1323
+ return null;
1324
+ }
1325
+ let text = "";
1326
+ const wsData = data["data"];
1327
+ if (wsData?.["result"]) {
1328
+ const result = wsData["result"];
1329
+ text = result.ws?.map((w) => w.cw[0]?.w ?? "").join("") ?? "";
1330
+ }
1331
+ if (data["desc"] === "success" && typeof data["data"] === "string") {
1332
+ text = data["data"];
1333
+ }
1334
+ if (!text) return null;
1335
+ return {
1336
+ transcript: text,
1337
+ isFinal: wsData?.["status"] === 2 || !!data["desc"],
1338
+ confidence: 0.9,
1339
+ original: data
1340
+ };
1341
+ }
1342
+ };
1343
+ var TencentAdapter = class {
1344
+ static {
1345
+ chunkPK6SKIKE_cjs.__name(this, "TencentAdapter");
1346
+ }
1347
+ name = "Tencent";
1348
+ /** Secret ID - 生产环境应在后端使用 */
1349
+ secretId;
1350
+ /** Secret Key - 生产环境应在后端使用 */
1351
+ secretKey;
1352
+ constructor(secretId, secretKey) {
1353
+ this.secretId = secretId;
1354
+ this.secretKey = secretKey;
1355
+ }
1356
+ async recognizeShortAudio(audioData) {
1357
+ const base64Audio = AudioUtils.arrayBufferToBase64(audioData);
1358
+ const payload = {
1359
+ ProjectId: 0,
1360
+ SubServiceType: 2,
1361
+ EngSerViceType: "16k_zh",
1362
+ SourceType: 1,
1363
+ VoiceFormat: "wav",
1364
+ UsrAudioKey: `session-${Date.now()}`,
1365
+ Data: base64Audio,
1366
+ DataLen: audioData.byteLength
1367
+ };
1368
+ const res = await fetch("https://asr.tencentcloudapi.com", {
1369
+ method: "POST",
1370
+ headers: {
1371
+ "Content-Type": "application/json",
1372
+ "X-TC-Action": "SentenceRecognition"
1373
+ // 生产环境需添加: 'Authorization', 'X-TC-Timestamp' 等
1374
+ },
1375
+ body: JSON.stringify(payload)
1376
+ });
1377
+ const json = await res.json();
1378
+ const result = this.parseResult(json);
1379
+ if (!result) throw new Error("\u817E\u8BAF\u4E91\u8BC6\u522B\u5931\u8D25");
1380
+ return result;
1381
+ }
1382
+ parseResult(data) {
1383
+ const resp = data["Response"];
1384
+ if (resp?.["Error"]) {
1385
+ const error = resp["Error"];
1386
+ throw new Error(error.Message ?? "\u817E\u8BAF\u4E91 API \u9519\u8BEF");
1387
+ }
1388
+ if (resp?.["Result"]) {
1389
+ return {
1390
+ transcript: resp["Result"],
1391
+ isFinal: true,
1392
+ confidence: 0.9,
1393
+ original: data
1394
+ };
1395
+ }
1396
+ return null;
1397
+ }
1398
+ };
1399
+ var BaiduAdapter = class {
1400
+ constructor(accessToken, appId, appKey, devPid = 1537) {
1401
+ this.accessToken = accessToken;
1402
+ this.appId = appId;
1403
+ this.appKey = appKey;
1404
+ this.devPid = devPid;
1405
+ }
1406
+ static {
1407
+ chunkPK6SKIKE_cjs.__name(this, "BaiduAdapter");
1408
+ }
1409
+ name = "Baidu";
1410
+ getConnectUrl() {
1411
+ const sn = Math.random().toString(36).substring(2) + Date.now();
1412
+ return `wss://vop.baidu.com/realtime_asr?sn=${sn}`;
1413
+ }
1414
+ getHandshakeParams() {
1415
+ if (!this.appId || !this.appKey) {
1416
+ console.warn("[BaiduAdapter] WebSocket \u6A21\u5F0F\u9700\u8981 appId \u548C appKey");
1417
+ }
1418
+ return {
1419
+ type: "START",
1420
+ data: {
1421
+ appid: Number(this.appId),
1422
+ appkey: this.appKey,
1423
+ dev_pid: this.devPid,
1424
+ cuid: `sdk-user-${Date.now()}`,
1425
+ format: "pcm",
1426
+ sample: 16e3
1427
+ }
1428
+ };
1429
+ }
1430
+ async recognizeShortAudio(audioData) {
1431
+ const base64Audio = AudioUtils.arrayBufferToBase64(audioData);
1432
+ const payload = {
1433
+ format: "wav",
1434
+ rate: 16e3,
1435
+ channel: 1,
1436
+ cuid: `sdk-user-${Date.now()}`,
1437
+ token: this.accessToken,
1438
+ dev_pid: this.devPid,
1439
+ speech: base64Audio,
1440
+ len: audioData.byteLength
1441
+ };
1442
+ const response = await fetch("/api/baidu-speech/pro_api", {
1443
+ method: "POST",
1444
+ headers: { "Content-Type": "application/json" },
1445
+ body: JSON.stringify(payload)
1446
+ });
1447
+ const data = await response.json();
1448
+ const result = this.parseResult(data);
1449
+ if (!result) throw new Error("\u767E\u5EA6\u8BC6\u522B\u5931\u8D25");
1450
+ return result;
1451
+ }
1452
+ parseResult(data) {
1453
+ if (data["err_no"] !== void 0) {
1454
+ if (data["err_no"] !== 0) {
1455
+ throw new Error(`Baidu API Error [${String(data["err_no"])}]: ${String(data["err_msg"])}`);
1456
+ }
1457
+ const result = data["result"];
1458
+ if (result && result.length > 0) {
1459
+ return {
1460
+ transcript: result[0] ?? "",
1461
+ isFinal: true,
1462
+ confidence: 0.9,
1463
+ original: data
1464
+ };
1465
+ }
1466
+ }
1467
+ if (data["type"]) {
1468
+ if (data["type"] === "HEARTBEAT") return null;
1469
+ if (data["type"] === "ERROR") {
1470
+ throw new Error(`Baidu WS Error: ${String(data["err_msg"])}`);
1471
+ }
1472
+ if (data["type"] === "MID_TEXT" || data["type"] === "FIN_TEXT") {
1473
+ return {
1474
+ transcript: data["result"],
1475
+ isFinal: data["type"] === "FIN_TEXT",
1476
+ confidence: 0.9,
1477
+ original: data
1478
+ };
1479
+ }
1480
+ }
1481
+ return null;
1482
+ }
1483
+ };
1484
+ var AlibabaAdapter = class {
1485
+ constructor(accessKeyId, accessKeySecret, appKey) {
1486
+ this.accessKeyId = accessKeyId;
1487
+ this.appKey = appKey;
1488
+ this.accessKeySecret = accessKeySecret;
1489
+ }
1490
+ static {
1491
+ chunkPK6SKIKE_cjs.__name(this, "AlibabaAdapter");
1492
+ }
1493
+ name = "Alibaba";
1494
+ /** Access Key Secret - 生产环境应在后端使用 */
1495
+ accessKeySecret;
1496
+ getConnectUrl() {
1497
+ return `wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1`;
1498
+ }
1499
+ getHandshakeParams() {
1500
+ return {
1501
+ header: {
1502
+ message_id: this.generateUUID(),
1503
+ task_id: this.generateUUID(),
1504
+ namespace: "SpeechRecognizer",
1505
+ name: "StartRecognition",
1506
+ appkey: this.appKey
1507
+ },
1508
+ payload: {
1509
+ format: "pcm",
1510
+ sample_rate: 16e3,
1511
+ enable_intermediate_result: true,
1512
+ enable_punctuation_prediction: true,
1513
+ enable_inverse_text_normalization: true
1514
+ }
1515
+ };
1516
+ }
1517
+ async recognizeShortAudio(audioData) {
1518
+ const base64Audio = AudioUtils.arrayBufferToBase64(audioData);
1519
+ const response = await fetch("/api/alibaba-speech/recognize", {
1520
+ method: "POST",
1521
+ headers: {
1522
+ "Content-Type": "application/json",
1523
+ "X-NLS-Token": this.accessKeyId
1524
+ },
1525
+ body: JSON.stringify({
1526
+ appkey: this.appKey,
1527
+ format: "wav",
1528
+ sample_rate: 16e3,
1529
+ audio: base64Audio
1530
+ })
1531
+ });
1532
+ const data = await response.json();
1533
+ const result = this.parseResult(data);
1534
+ if (!result) throw new Error("\u963F\u91CC\u4E91\u8BC6\u522B\u5931\u8D25");
1535
+ return result;
1536
+ }
1537
+ parseResult(data) {
1538
+ const header = data["header"];
1539
+ if (header) {
1540
+ const status = header["status"];
1541
+ if (status !== 2e7) {
1542
+ throw new Error(`Alibaba Error [${status}]: ${String(header["status_text"])}`);
1543
+ }
1544
+ const payload = data["payload"];
1545
+ if (payload?.["result"]) {
1546
+ return {
1547
+ transcript: payload["result"],
1548
+ isFinal: header["name"] === "RecognitionCompleted",
1549
+ confidence: 0.9,
1550
+ original: data
1551
+ };
1552
+ }
1553
+ }
1554
+ if (data["result"]) {
1555
+ return {
1556
+ transcript: data["result"],
1557
+ isFinal: true,
1558
+ confidence: data["confidence"] || 0.9,
1559
+ original: data
1560
+ };
1561
+ }
1562
+ return null;
1563
+ }
1564
+ generateUUID() {
1565
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1566
+ const r = Math.random() * 16 | 0;
1567
+ const v = c === "x" ? r : r & 3 | 8;
1568
+ return v.toString(16);
1569
+ });
1570
+ }
1571
+ };
1572
+ var GoogleAdapter = class {
1573
+ constructor(apiKey, languageCode = "zh-CN") {
1574
+ this.apiKey = apiKey;
1575
+ this.languageCode = languageCode;
1576
+ }
1577
+ static {
1578
+ chunkPK6SKIKE_cjs.__name(this, "GoogleAdapter");
1579
+ }
1580
+ name = "Google";
1581
+ async recognizeShortAudio(audioData) {
1582
+ const base64Audio = AudioUtils.arrayBufferToBase64(audioData);
1583
+ const payload = {
1584
+ config: {
1585
+ encoding: "LINEAR16",
1586
+ sampleRateHertz: 16e3,
1587
+ languageCode: this.languageCode,
1588
+ enableAutomaticPunctuation: true
1589
+ },
1590
+ audio: {
1591
+ content: base64Audio
1592
+ }
1593
+ };
1594
+ const response = await fetch(
1595
+ `https://speech.googleapis.com/v1/speech:recognize?key=${this.apiKey}`,
1596
+ {
1597
+ method: "POST",
1598
+ headers: { "Content-Type": "application/json" },
1599
+ body: JSON.stringify(payload)
1600
+ }
1601
+ );
1602
+ const data = await response.json();
1603
+ const result = this.parseResult(data);
1604
+ if (!result) throw new Error("Google \u8BC6\u522B\u5931\u8D25");
1605
+ return result;
1606
+ }
1607
+ parseResult(data) {
1608
+ if (data["error"]) {
1609
+ const error = data["error"];
1610
+ throw new Error(error.message ?? "Google API Error");
1611
+ }
1612
+ const results = data["results"];
1613
+ if (results && results.length > 0) {
1614
+ const alternatives = results[0]?.alternatives;
1615
+ if (alternatives && alternatives.length > 0) {
1616
+ return {
1617
+ transcript: alternatives[0]?.transcript ?? "",
1618
+ isFinal: true,
1619
+ confidence: alternatives[0]?.confidence ?? 0.9,
1620
+ original: data
1621
+ };
1622
+ }
1623
+ }
1624
+ return null;
1625
+ }
1626
+ };
1627
+ var AzureAdapter = class {
1628
+ constructor(subscriptionKey, region, language = "zh-CN") {
1629
+ this.subscriptionKey = subscriptionKey;
1630
+ this.region = region;
1631
+ this.language = language;
1632
+ }
1633
+ static {
1634
+ chunkPK6SKIKE_cjs.__name(this, "AzureAdapter");
1635
+ }
1636
+ name = "Azure";
1637
+ getConnectUrl() {
1638
+ return `wss://${this.region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${this.language}`;
1639
+ }
1640
+ getHandshakeParams() {
1641
+ return {
1642
+ "Ocp-Apim-Subscription-Key": this.subscriptionKey
1643
+ };
1644
+ }
1645
+ async recognizeShortAudio(audioData) {
1646
+ const response = await fetch(
1647
+ `https://${this.region}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${this.language}`,
1648
+ {
1649
+ method: "POST",
1650
+ headers: {
1651
+ "Ocp-Apim-Subscription-Key": this.subscriptionKey,
1652
+ "Content-Type": "audio/wav; codecs=audio/pcm; samplerate=16000"
1653
+ },
1654
+ body: audioData
1655
+ }
1656
+ );
1657
+ const data = await response.json();
1658
+ const result = this.parseResult(data);
1659
+ if (!result) throw new Error("Azure \u8BC6\u522B\u5931\u8D25");
1660
+ return result;
1661
+ }
1662
+ parseResult(data) {
1663
+ if (data["RecognitionStatus"] === "Success") {
1664
+ return {
1665
+ transcript: data["DisplayText"] || data["Text"] || "",
1666
+ isFinal: true,
1667
+ confidence: data["Confidence"] || 0.9,
1668
+ original: data
1669
+ };
1670
+ }
1671
+ if (data["Text"]) {
1672
+ return {
1673
+ transcript: data["Text"],
1674
+ isFinal: data["RecognitionStatus"] === "Success",
1675
+ confidence: 0.9,
1676
+ original: data
1677
+ };
1678
+ }
1679
+ return null;
1680
+ }
1681
+ };
831
1682
  var BaseRecognitionStrategy = class {
832
1683
  constructor(config) {
833
1684
  this.config = config;
@@ -1633,14 +2484,586 @@ async function listen(config) {
1633
2484
  });
1634
2485
  }
1635
2486
  chunkPK6SKIKE_cjs.__name(listen, "listen");
2487
+ async function listenWithTimeout(config, timeout = 1e4) {
2488
+ return Promise.race([
2489
+ listen(config),
2490
+ new Promise((_, reject) => {
2491
+ setTimeout(() => reject(new Error("\u8BC6\u522B\u8D85\u65F6")), timeout);
2492
+ })
2493
+ ]);
2494
+ }
2495
+ chunkPK6SKIKE_cjs.__name(listenWithTimeout, "listenWithTimeout");
2496
+
2497
+ // src/plugins/fingerprint/utils.ts
2498
+ var FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
2499
+ var FNV_PRIME = 0x100000001b3n;
2500
+ var UINT64_MASK = 0xffffffffffffffffn;
2501
+ function stableStringify(value) {
2502
+ if (value === null || typeof value !== "object") {
2503
+ return JSON.stringify(value);
2504
+ }
2505
+ if (Array.isArray(value)) {
2506
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
2507
+ }
2508
+ const record = value;
2509
+ const entries = Object.keys(record).sort().map((key) => [key, record[key] ?? null]);
2510
+ return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`;
2511
+ }
2512
+ chunkPK6SKIKE_cjs.__name(stableStringify, "stableStringify");
2513
+ function serializeComponents(components, salt) {
2514
+ const values = {};
2515
+ for (const key of Object.keys(components).sort()) {
2516
+ const component = components[key];
2517
+ if (component) {
2518
+ values[key] = component.value;
2519
+ }
2520
+ }
2521
+ return stableStringify({ salt, values });
2522
+ }
2523
+ chunkPK6SKIKE_cjs.__name(serializeComponents, "serializeComponents");
2524
+ function fnv1a64(input) {
2525
+ let hash = FNV_OFFSET_BASIS;
2526
+ for (let i = 0; i < input.length; i++) {
2527
+ hash ^= BigInt(input.charCodeAt(i));
2528
+ hash = hash * FNV_PRIME & UINT64_MASK;
2529
+ }
2530
+ return hash.toString(16).padStart(16, "0");
2531
+ }
2532
+ chunkPK6SKIKE_cjs.__name(fnv1a64, "fnv1a64");
2533
+ async function sha256(input) {
2534
+ const subtle = globalThis.crypto?.subtle;
2535
+ if (!subtle) {
2536
+ return null;
2537
+ }
2538
+ const data = new TextEncoder().encode(input);
2539
+ const digest = await subtle.digest("SHA-256", data);
2540
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
2541
+ }
2542
+ chunkPK6SKIKE_cjs.__name(sha256, "sha256");
2543
+ async function hashString(input, algorithm = "fnv1a64") {
2544
+ if (algorithm === "sha256") {
2545
+ return await sha256(input) ?? fnv1a64(input);
2546
+ }
2547
+ return fnv1a64(input);
2548
+ }
2549
+ chunkPK6SKIKE_cjs.__name(hashString, "hashString");
2550
+ function bucketNumber(value, bucketSize) {
2551
+ if (!Number.isFinite(value) || bucketSize <= 0) {
2552
+ return value;
2553
+ }
2554
+ return Math.round(value / bucketSize) * bucketSize;
2555
+ }
2556
+ chunkPK6SKIKE_cjs.__name(bucketNumber, "bucketNumber");
2557
+ function normalizeUserAgent(userAgent) {
2558
+ return userAgent.replace(/\b(\d+)\.(\d+)(?:\.\d+)+\b/g, "$1.$2").replace(/\bBuild\/[^\s;)]+/gi, "Build/*").replace(/\bVersion\/(\d+)\.(\d+)(?:\.\d+)+/gi, "Version/$1.$2").trim();
2559
+ }
2560
+ chunkPK6SKIKE_cjs.__name(normalizeUserAgent, "normalizeUserAgent");
2561
+ function bucketHardwareConcurrency(value) {
2562
+ if (value <= 2) return value;
2563
+ if (value <= 4) return 4;
2564
+ if (value <= 8) return 8;
2565
+ return 16;
2566
+ }
2567
+ chunkPK6SKIKE_cjs.__name(bucketHardwareConcurrency, "bucketHardwareConcurrency");
2568
+ function bucketDeviceMemory(value) {
2569
+ if (value <= 1) return 1;
2570
+ if (value <= 2) return 2;
2571
+ if (value <= 4) return 4;
2572
+ if (value <= 8) return 8;
2573
+ return 16;
2574
+ }
2575
+ chunkPK6SKIKE_cjs.__name(bucketDeviceMemory, "bucketDeviceMemory");
2576
+
2577
+ // src/plugins/fingerprint/collectors.ts
2578
+ function getNavigator() {
2579
+ return typeof globalThis.navigator === "undefined" ? void 0 : globalThis.navigator;
2580
+ }
2581
+ chunkPK6SKIKE_cjs.__name(getNavigator, "getNavigator");
2582
+ function getScreen() {
2583
+ return typeof globalThis.screen === "undefined" ? void 0 : globalThis.screen;
2584
+ }
2585
+ chunkPK6SKIKE_cjs.__name(getScreen, "getScreen");
2586
+ function getWindow() {
2587
+ return typeof window === "undefined" ? void 0 : window;
2588
+ }
2589
+ chunkPK6SKIKE_cjs.__name(getWindow, "getWindow");
2590
+ function getStorageAvailability(type) {
2591
+ const currentWindow = getWindow();
2592
+ if (!currentWindow) {
2593
+ return false;
2594
+ }
2595
+ try {
2596
+ return Boolean(currentWindow[type]);
2597
+ } catch {
2598
+ return false;
2599
+ }
2600
+ }
2601
+ chunkPK6SKIKE_cjs.__name(getStorageAvailability, "getStorageAvailability");
2602
+ function getIndexedDBAvailability() {
2603
+ try {
2604
+ return typeof globalThis.indexedDB !== "undefined";
2605
+ } catch {
2606
+ return false;
2607
+ }
2608
+ }
2609
+ chunkPK6SKIKE_cjs.__name(getIndexedDBAvailability, "getIndexedDBAvailability");
2610
+ function getTimezone(context) {
2611
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2612
+ if (context.options.privacyMode === "strict") {
2613
+ return timezone ? "available" : "unknown";
2614
+ }
2615
+ return timezone || "unknown";
2616
+ }
2617
+ chunkPK6SKIKE_cjs.__name(getTimezone, "getTimezone");
2618
+ function getScreenValue(context) {
2619
+ const screen = getScreen();
2620
+ if (!screen) {
2621
+ return null;
2622
+ }
2623
+ const width = screen.width ?? 0;
2624
+ const height = screen.height ?? 0;
2625
+ if (context.options.privacyMode === "debug") {
2626
+ return {
2627
+ width,
2628
+ height,
2629
+ availWidth: screen.availWidth ?? width,
2630
+ availHeight: screen.availHeight ?? height
2631
+ };
2632
+ }
2633
+ const sorted = [width, height].sort((left, right) => left - right);
2634
+ return {
2635
+ min: bucketNumber(sorted[0] ?? 0, context.options.screenBucketSize),
2636
+ max: bucketNumber(sorted[1] ?? 0, context.options.screenBucketSize)
2637
+ };
2638
+ }
2639
+ chunkPK6SKIKE_cjs.__name(getScreenValue, "getScreenValue");
2640
+ function collectUserAgent(context) {
2641
+ const userAgent = getNavigator()?.userAgent;
2642
+ if (!userAgent) {
2643
+ return null;
2644
+ }
2645
+ if (context.options.privacyMode === "strict") {
2646
+ return "available";
2647
+ }
2648
+ return context.options.normalizeUserAgent ? normalizeUserAgent(userAgent) : userAgent;
2649
+ }
2650
+ chunkPK6SKIKE_cjs.__name(collectUserAgent, "collectUserAgent");
2651
+ function collectLanguages(context) {
2652
+ const navigator2 = getNavigator();
2653
+ const languages = navigator2?.languages?.length ? [...navigator2.languages] : [navigator2?.language ?? "unknown"];
2654
+ if (context.options.privacyMode === "strict") {
2655
+ return languages.slice(0, 1);
2656
+ }
2657
+ return languages;
2658
+ }
2659
+ chunkPK6SKIKE_cjs.__name(collectLanguages, "collectLanguages");
2660
+ var defaultFingerprintCollectors = [
2661
+ {
2662
+ // User-Agent 是 FingerprintJS 类工具常见组件。这里默认归一化以降低波动。
2663
+ key: "userAgent",
2664
+ source: "low-entropy",
2665
+ confidence: 0.72,
2666
+ collect: collectUserAgent
2667
+ },
2668
+ {
2669
+ // 平台值通常较稳定,但粒度较粗,因此给中等权重。
2670
+ key: "platform",
2671
+ source: "low-entropy",
2672
+ confidence: 0.55,
2673
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getNavigator()?.platform ?? null, "collect")
2674
+ },
2675
+ {
2676
+ // 首选语言可用于区分环境区域偏好,但不应单独识别用户。
2677
+ key: "language",
2678
+ source: "low-entropy",
2679
+ confidence: 0.5,
2680
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getNavigator()?.language ?? "unknown", "collect")
2681
+ },
2682
+ {
2683
+ // 语言列表在 strict 模式下会收敛为首选语言。
2684
+ key: "languages",
2685
+ source: "low-entropy",
2686
+ confidence: 0.48,
2687
+ collect: collectLanguages
2688
+ },
2689
+ {
2690
+ // 时区在 strict 模式下只记录可用性,避免过度暴露地区信息。
2691
+ key: "timezone",
2692
+ source: "low-entropy",
2693
+ confidence: 0.64,
2694
+ collect: getTimezone
2695
+ },
2696
+ {
2697
+ // 偏移量比 IANA 时区更粗粒度,作为兼容补充。
2698
+ key: "timezoneOffset",
2699
+ source: "low-entropy",
2700
+ confidence: 0.55,
2701
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => (/* @__PURE__ */ new Date()).getTimezoneOffset(), "collect")
2702
+ },
2703
+ {
2704
+ // 屏幕尺寸默认分桶并忽略方向,减少精确设备特征。
2705
+ key: "screen",
2706
+ source: "low-entropy",
2707
+ confidence: 0.62,
2708
+ collect: getScreenValue
2709
+ },
2710
+ {
2711
+ // 色深通常选择空间很小,权重较低。
2712
+ key: "colorDepth",
2713
+ source: "low-entropy",
2714
+ confidence: 0.38,
2715
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getScreen()?.colorDepth ?? null, "collect")
2716
+ },
2717
+ {
2718
+ // 像素比可能受缩放影响,因此按 0.25 分桶并给较低权重。
2719
+ key: "pixelRatio",
2720
+ source: "low-entropy",
2721
+ confidence: 0.42,
2722
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
2723
+ const currentWindow = getWindow();
2724
+ return currentWindow ? bucketNumber(currentWindow.devicePixelRatio || 1, 0.25) : 1;
2725
+ }, "collect")
2726
+ },
2727
+ {
2728
+ // 线程数默认分桶,debug 模式才保留原始值。
2729
+ key: "hardwareConcurrency",
2730
+ source: "low-entropy",
2731
+ confidence: 0.5,
2732
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((context) => {
2733
+ const value = getNavigator()?.hardwareConcurrency;
2734
+ if (!value) {
2735
+ return null;
2736
+ }
2737
+ return context.options.privacyMode === "debug" ? value : bucketHardwareConcurrency(value);
2738
+ }, "collect")
2739
+ },
2740
+ {
2741
+ // 设备内存不是所有浏览器都支持;默认分桶降低唯一性。
2742
+ key: "deviceMemory",
2743
+ source: "low-entropy",
2744
+ confidence: 0.44,
2745
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((context) => {
2746
+ const value = getNavigator()?.deviceMemory;
2747
+ if (!value) {
2748
+ return null;
2749
+ }
2750
+ return context.options.privacyMode === "debug" ? value : bucketDeviceMemory(value);
2751
+ }, "collect")
2752
+ },
2753
+ {
2754
+ // 触摸点有助于区分桌面/触屏环境,但粒度仍较粗。
2755
+ key: "maxTouchPoints",
2756
+ source: "low-entropy",
2757
+ confidence: 0.45,
2758
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getNavigator()?.maxTouchPoints ?? 0, "collect")
2759
+ },
2760
+ {
2761
+ // 仅检测 Cookie 能力,不读写任何 Cookie。
2762
+ key: "cookiesEnabled",
2763
+ source: "capability",
2764
+ confidence: 0.32,
2765
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getNavigator()?.cookieEnabled ?? false, "collect")
2766
+ },
2767
+ {
2768
+ // 仅检测存储 API 是否可访问,不写入测试数据。
2769
+ key: "storage",
2770
+ source: "capability",
2771
+ confidence: 0.28,
2772
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => ({
2773
+ localStorage: getStorageAvailability("localStorage"),
2774
+ sessionStorage: getStorageAvailability("sessionStorage")
2775
+ }), "collect")
2776
+ },
2777
+ {
2778
+ // IndexedDB 只做存在性检测,不打开数据库。
2779
+ key: "indexedDB",
2780
+ source: "capability",
2781
+ confidence: 0.24,
2782
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getIndexedDBAvailability(), "collect")
2783
+ },
2784
+ {
2785
+ // 记录浏览器隐私偏好,业务侧可用来决定是否进一步降级采集。
2786
+ key: "doNotTrack",
2787
+ source: "capability",
2788
+ confidence: 0.2,
2789
+ collect: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => getNavigator()?.doNotTrack ?? null, "collect")
2790
+ }
2791
+ ];
2792
+
2793
+ // src/plugins/fingerprint/generator.ts
2794
+ var FINGERPRINT_VERSION = "1.0.0";
2795
+ var FINGERPRINT_GENERATOR = /* @__PURE__ */ Symbol("melange:fingerprint-generator");
2796
+ var DEFAULT_OPTIONS = {
2797
+ salt: "melange",
2798
+ cache: true,
2799
+ cacheTtl: 5 * 60 * 1e3,
2800
+ componentTimeout: 80,
2801
+ privacyMode: "balanced",
2802
+ hashAlgorithm: "fnv1a64",
2803
+ screenBucketSize: 100,
2804
+ include: [],
2805
+ exclude: [],
2806
+ collectors: []
2807
+ };
2808
+ function now() {
2809
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
2810
+ }
2811
+ chunkPK6SKIKE_cjs.__name(now, "now");
2812
+ function resolveOptions(baseOptions, overrideOptions = {}) {
2813
+ const privacyMode = overrideOptions.privacyMode ?? baseOptions.privacyMode ?? DEFAULT_OPTIONS.privacyMode;
2814
+ const normalizeUserAgent2 = overrideOptions.normalizeUserAgent ?? baseOptions.normalizeUserAgent ?? privacyMode !== "debug";
2815
+ return {
2816
+ salt: overrideOptions.salt ?? baseOptions.salt ?? DEFAULT_OPTIONS.salt,
2817
+ cache: overrideOptions.cache ?? baseOptions.cache ?? DEFAULT_OPTIONS.cache,
2818
+ cacheTtl: overrideOptions.cacheTtl ?? baseOptions.cacheTtl ?? DEFAULT_OPTIONS.cacheTtl,
2819
+ componentTimeout: overrideOptions.componentTimeout ?? baseOptions.componentTimeout ?? DEFAULT_OPTIONS.componentTimeout,
2820
+ privacyMode,
2821
+ hashAlgorithm: overrideOptions.hashAlgorithm ?? baseOptions.hashAlgorithm ?? DEFAULT_OPTIONS.hashAlgorithm,
2822
+ screenBucketSize: overrideOptions.screenBucketSize ?? baseOptions.screenBucketSize ?? DEFAULT_OPTIONS.screenBucketSize,
2823
+ normalizeUserAgent: normalizeUserAgent2,
2824
+ include: overrideOptions.include ?? baseOptions.include ?? DEFAULT_OPTIONS.include,
2825
+ exclude: overrideOptions.exclude ?? baseOptions.exclude ?? DEFAULT_OPTIONS.exclude,
2826
+ collectors: [
2827
+ ...baseOptions.collectors ?? DEFAULT_OPTIONS.collectors,
2828
+ ...overrideOptions.collectors ?? DEFAULT_OPTIONS.collectors
2829
+ ]
2830
+ };
2831
+ }
2832
+ chunkPK6SKIKE_cjs.__name(resolveOptions, "resolveOptions");
2833
+ function createCacheKey(options) {
2834
+ return JSON.stringify({
2835
+ salt: options.salt,
2836
+ privacyMode: options.privacyMode,
2837
+ hashAlgorithm: options.hashAlgorithm,
2838
+ screenBucketSize: options.screenBucketSize,
2839
+ normalizeUserAgent: options.normalizeUserAgent,
2840
+ include: options.include,
2841
+ exclude: options.exclude,
2842
+ collectors: options.collectors.map((collector) => collector.key)
2843
+ });
2844
+ }
2845
+ chunkPK6SKIKE_cjs.__name(createCacheKey, "createCacheKey");
2846
+ function shouldCollect(collector, options) {
2847
+ if (options.include.length > 0 && !options.include.includes(collector.key)) {
2848
+ return false;
2849
+ }
2850
+ return !options.exclude.includes(collector.key);
2851
+ }
2852
+ chunkPK6SKIKE_cjs.__name(shouldCollect, "shouldCollect");
2853
+ function withTimeout(operation, timeout) {
2854
+ return new Promise((resolve, reject) => {
2855
+ const timeoutId = setTimeout(() => resolve(void 0), timeout);
2856
+ operation.then(
2857
+ (value) => {
2858
+ clearTimeout(timeoutId);
2859
+ resolve(value);
2860
+ },
2861
+ (error) => {
2862
+ clearTimeout(timeoutId);
2863
+ reject(error instanceof Error ? error : new Error(String(error)));
2864
+ }
2865
+ );
2866
+ });
2867
+ }
2868
+ chunkPK6SKIKE_cjs.__name(withTimeout, "withTimeout");
2869
+ async function collectWithTimeout(collector, context) {
2870
+ const startedAt = context.now();
2871
+ const value = await withTimeout(
2872
+ Promise.resolve(collector.collect(context)),
2873
+ context.options.componentTimeout
2874
+ );
2875
+ if (value === void 0) {
2876
+ return { key: collector.key };
2877
+ }
2878
+ return {
2879
+ key: collector.key,
2880
+ component: {
2881
+ value,
2882
+ source: collector.source ?? "custom",
2883
+ duration: Math.max(0, context.now() - startedAt),
2884
+ confidence: Math.max(0, Math.min(1, collector.confidence ?? 0.4))
2885
+ }
2886
+ };
2887
+ }
2888
+ chunkPK6SKIKE_cjs.__name(collectWithTimeout, "collectWithTimeout");
2889
+ function calculateConfidence(components, skippedCount) {
2890
+ const values = Object.values(components);
2891
+ if (values.length === 0) {
2892
+ return {
2893
+ score: 0,
2894
+ componentCount: 0,
2895
+ skippedCount
2896
+ };
2897
+ }
2898
+ const totalWeight = values.reduce((sum, component) => sum + component.confidence, 0);
2899
+ const averageWeight = totalWeight / values.length;
2900
+ const coverage = values.length / (values.length + skippedCount);
2901
+ const score = Math.max(0, Math.min(0.99, averageWeight * 0.7 + coverage * 0.3));
2902
+ return {
2903
+ score: Number(score.toFixed(3)),
2904
+ componentCount: values.length,
2905
+ skippedCount
2906
+ };
2907
+ }
2908
+ chunkPK6SKIKE_cjs.__name(calculateConfidence, "calculateConfidence");
2909
+ var FingerprintGeneratorImpl = class {
2910
+ /**
2911
+ * 创建指纹生成器。
2912
+ *
2913
+ * @param baseOptions - 生成器默认配置;后续 `get(options)` 可覆盖这些配置
2914
+ */
2915
+ constructor(baseOptions = {}) {
2916
+ this.baseOptions = baseOptions;
2917
+ }
2918
+ static {
2919
+ chunkPK6SKIKE_cjs.__name(this, "FingerprintGeneratorImpl");
2920
+ }
2921
+ /**
2922
+ * 当前生成器实例的内存缓存。
2923
+ *
2924
+ * @remarks
2925
+ * 只缓存最后一次匹配配置的结果,不持久化。
2926
+ */
2927
+ cacheEntry;
2928
+ /**
2929
+ * 获取指纹结果。
2930
+ *
2931
+ * @description
2932
+ * 生成流程:
2933
+ * 1. 合并默认配置和本次调用配置;
2934
+ * 2. 命中内存缓存时直接返回;
2935
+ * 3. 并发采集组件并跳过失败组件;
2936
+ * 4. 稳定序列化组件值并计算哈希;
2937
+ * 5. 返回 visitorId、组件详情、置信度和耗时。
2938
+ *
2939
+ * @param options - 本次生成配置,会覆盖构造函数中的同名默认配置
2940
+ * @returns 指纹生成结果
2941
+ */
2942
+ async get(options = {}) {
2943
+ const resolvedOptions = resolveOptions(this.baseOptions, options);
2944
+ const cacheKey = createCacheKey(resolvedOptions);
2945
+ const generatedAt = Date.now();
2946
+ if (resolvedOptions.cache && this.cacheEntry?.key === cacheKey && this.cacheEntry.expiresAt > generatedAt) {
2947
+ return this.cacheEntry.result;
2948
+ }
2949
+ const startedAt = now();
2950
+ const collectors = [...defaultFingerprintCollectors, ...resolvedOptions.collectors].filter(
2951
+ (collector) => shouldCollect(collector, resolvedOptions)
2952
+ );
2953
+ const context = {
2954
+ options: resolvedOptions,
2955
+ now
2956
+ };
2957
+ const collected = await Promise.all(
2958
+ collectors.map(
2959
+ (collector) => collectWithTimeout(collector, context).catch(
2960
+ () => ({ key: collector.key })
2961
+ )
2962
+ )
2963
+ );
2964
+ const components = {};
2965
+ let skippedCount = defaultFingerprintCollectors.length + resolvedOptions.collectors.length;
2966
+ for (const item of collected) {
2967
+ if (item.component) {
2968
+ components[item.key] = item.component;
2969
+ skippedCount--;
2970
+ }
2971
+ }
2972
+ const input = serializeComponents(components, resolvedOptions.salt);
2973
+ const visitorId = await hashString(input, resolvedOptions.hashAlgorithm);
2974
+ const result = {
2975
+ visitorId,
2976
+ components,
2977
+ confidence: calculateConfidence(components, skippedCount),
2978
+ version: FINGERPRINT_VERSION,
2979
+ duration: Math.max(0, now() - startedAt),
2980
+ generatedAt
2981
+ };
2982
+ if (resolvedOptions.cache) {
2983
+ this.cacheEntry = {
2984
+ key: cacheKey,
2985
+ expiresAt: generatedAt + resolvedOptions.cacheTtl,
2986
+ result
2987
+ };
2988
+ }
2989
+ return result;
2990
+ }
2991
+ /**
2992
+ * 清除内存缓存。
2993
+ *
2994
+ * @description
2995
+ * 下次调用 `get()` 时会重新采集组件。该方法不会清理任何浏览器存储,
2996
+ * 因为本插件默认不会写入持久化存储。
2997
+ */
2998
+ clearCache() {
2999
+ this.cacheEntry = void 0;
3000
+ }
3001
+ };
3002
+ function createFingerprintGenerator(options = {}) {
3003
+ return new FingerprintGeneratorImpl(options);
3004
+ }
3005
+ chunkPK6SKIKE_cjs.__name(createFingerprintGenerator, "createFingerprintGenerator");
3006
+ async function getFingerprint(options = {}) {
3007
+ return createFingerprintGenerator(options).get();
3008
+ }
3009
+ chunkPK6SKIKE_cjs.__name(getFingerprint, "getFingerprint");
3010
+ function registerFingerprintPlugin(container = chunkBEY4UAYF_cjs.globalContainer, options = {}) {
3011
+ return container.registerSingleton(
3012
+ FINGERPRINT_GENERATOR,
3013
+ () => new FingerprintGeneratorImpl(options)
3014
+ );
3015
+ }
3016
+ chunkPK6SKIKE_cjs.__name(registerFingerprintPlugin, "registerFingerprintPlugin");
3017
+ function isFingerprintSupported() {
3018
+ return typeof globalThis.navigator !== "undefined" || typeof globalThis.screen !== "undefined";
3019
+ }
3020
+ chunkPK6SKIKE_cjs.__name(isFingerprintSupported, "isFingerprintSupported");
1636
3021
 
3022
+ exports.AWSSynthesisAdapter = AWSSynthesisAdapter;
3023
+ exports.AlibabaAdapter = AlibabaAdapter;
3024
+ exports.AlibabaSynthesisAdapter = AlibabaSynthesisAdapter;
3025
+ exports.AudioUtils = AudioUtils;
3026
+ exports.AzureAdapter = AzureAdapter;
3027
+ exports.AzureSynthesisAdapter = AzureSynthesisAdapter;
3028
+ exports.BaiduAdapter = BaiduAdapter;
3029
+ exports.BaiduSynthesisAdapter = BaiduSynthesisAdapter;
3030
+ exports.FINGERPRINT_GENERATOR = FINGERPRINT_GENERATOR;
3031
+ exports.FINGERPRINT_VERSION = FINGERPRINT_VERSION;
3032
+ exports.FingerprintGeneratorImpl = FingerprintGeneratorImpl;
3033
+ exports.GenericAdapter = GenericAdapter;
3034
+ exports.GenericSynthesisAdapter = GenericSynthesisAdapter;
3035
+ exports.GoogleAdapter = GoogleAdapter;
3036
+ exports.GoogleSynthesisAdapter = GoogleSynthesisAdapter;
3037
+ exports.RecognitionStatus = RecognitionStatus;
1637
3038
  exports.SpeechRecognizerImpl = SpeechRecognizerImpl;
1638
3039
  exports.SpeechSynthesizerImpl = SpeechSynthesizerImpl;
3040
+ exports.SynthesisAudioUtils = SynthesisAudioUtils;
3041
+ exports.SynthesisStatus = SynthesisStatus;
3042
+ exports.TencentAdapter = TencentAdapter;
3043
+ exports.TencentSynthesisAdapter = TencentSynthesisAdapter;
3044
+ exports.XunfeiAdapter = XunfeiAdapter;
3045
+ exports.XunfeiSynthesisAdapter = XunfeiSynthesisAdapter;
3046
+ exports.bucketDeviceMemory = bucketDeviceMemory;
3047
+ exports.bucketHardwareConcurrency = bucketHardwareConcurrency;
3048
+ exports.bucketNumber = bucketNumber;
3049
+ exports.createFingerprintGenerator = createFingerprintGenerator;
1639
3050
  exports.createSpeechRecognizer = createSpeechRecognizer;
1640
3051
  exports.createSpeechSynthesizer = createSpeechSynthesizer;
3052
+ exports.defaultFingerprintCollectors = defaultFingerprintCollectors;
3053
+ exports.fnv1a64 = fnv1a64;
3054
+ exports.getFingerprint = getFingerprint;
3055
+ exports.hashString = hashString;
3056
+ exports.isFingerprintSupported = isFingerprintSupported;
1641
3057
  exports.isSpeechRecognitionSupported = isSpeechRecognitionSupported;
1642
3058
  exports.isSpeechSynthesisSupported = isSpeechSynthesisSupported;
1643
3059
  exports.listen = listen;
3060
+ exports.listenWithTimeout = listenWithTimeout;
3061
+ exports.normalizeUserAgent = normalizeUserAgent;
3062
+ exports.registerFingerprintPlugin = registerFingerprintPlugin;
3063
+ exports.serializeComponents = serializeComponents;
3064
+ exports.sha256 = sha256;
1644
3065
  exports.speak = speak;
1645
- //# sourceMappingURL=chunk-YZVCK6VZ.cjs.map
1646
- //# sourceMappingURL=chunk-YZVCK6VZ.cjs.map
3066
+ exports.speakWithCloud = speakWithCloud;
3067
+ exports.stableStringify = stableStringify;
3068
+ //# sourceMappingURL=chunk-AAWWAPSG.cjs.map
3069
+ //# sourceMappingURL=chunk-AAWWAPSG.cjs.map