ssml-builder-js 2.13.0 → 2.15.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/README.md +12 -2
- package/dist/{chunk-LCTE26LS.mjs → chunk-BDY2Q2JL.mjs} +2 -2
- package/dist/{chunk-25LOR4AJ.mjs → chunk-FXUM45ZY.mjs} +402 -169
- package/dist/chunk-FXUM45ZY.mjs.map +1 -0
- package/dist/{chunk-CZ2F3TET.mjs → chunk-WXFLUCLR.mjs} +227 -9
- package/dist/chunk-WXFLUCLR.mjs.map +1 -0
- package/dist/core.d.mts +65 -17
- package/dist/core.d.ts +65 -17
- package/dist/core.js +401 -166
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +5 -1
- package/dist/elements.js +101 -8
- package/dist/elements.js.map +1 -1
- package/dist/elements.mjs +2 -2
- package/dist/{index.d-Xbr6ZWnK.d.mts → index.d-8BvkB9gz.d.mts} +40 -2
- package/dist/{index.d-Xbr6ZWnK.d.ts → index.d-8BvkB9gz.d.ts} +40 -2
- package/dist/index.d.mts +170 -18
- package/dist/index.d.ts +170 -18
- package/dist/index.js +1512 -489
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +612 -47
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +15 -2
- package/dist/react.d.ts +15 -2
- package/dist/react.js +204 -23
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +105 -17
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-25LOR4AJ.mjs.map +0 -1
- package/dist/chunk-CZ2F3TET.mjs.map +0 -1
- /package/dist/{chunk-LCTE26LS.mjs.map → chunk-BDY2Q2JL.mjs.map} +0 -0
package/dist/core.mjs
CHANGED
|
@@ -2,11 +2,13 @@ import {
|
|
|
2
2
|
areAzureLanguagesEquivalent,
|
|
3
3
|
buildPartialSsml,
|
|
4
4
|
buildSsml,
|
|
5
|
+
createAzureUrlValidatorRunner,
|
|
5
6
|
extractSsmlText,
|
|
6
7
|
extractSsmlTranslatableText,
|
|
7
8
|
fromPlainTextToSsml,
|
|
8
9
|
getAzureVoiceCatalogMetadata,
|
|
9
10
|
getBuiltInVoiceCatalogMetadata,
|
|
11
|
+
getSsmlSourceMap,
|
|
10
12
|
isValidAzureAudioDuration,
|
|
11
13
|
mapSsmlTextNodes,
|
|
12
14
|
normalizeAzureLanguage,
|
|
@@ -15,17 +17,19 @@ import {
|
|
|
15
17
|
validateAzureSsml,
|
|
16
18
|
validateSsml,
|
|
17
19
|
validateSsmlStructureIntegrity
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FXUM45ZY.mjs";
|
|
19
21
|
import "./chunk-6S5ODO6A.mjs";
|
|
20
22
|
export {
|
|
21
23
|
areAzureLanguagesEquivalent,
|
|
22
24
|
buildPartialSsml,
|
|
23
25
|
buildSsml,
|
|
26
|
+
createAzureUrlValidatorRunner,
|
|
24
27
|
extractSsmlText,
|
|
25
28
|
extractSsmlTranslatableText,
|
|
26
29
|
fromPlainTextToSsml,
|
|
27
30
|
getAzureVoiceCatalogMetadata,
|
|
28
31
|
getBuiltInVoiceCatalogMetadata,
|
|
32
|
+
getSsmlSourceMap,
|
|
29
33
|
isValidAzureAudioDuration,
|
|
30
34
|
mapSsmlTextNodes,
|
|
31
35
|
normalizeAzureLanguage,
|
package/dist/elements.js
CHANGED
|
@@ -1017,6 +1017,86 @@ var AZURE_VOICE_DEFINITIONS = [
|
|
|
1017
1017
|
},
|
|
1018
1018
|
{ name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
|
|
1019
1019
|
];
|
|
1020
|
+
function createAzureUrlValidatorRunner(validator, options = {}) {
|
|
1021
|
+
if (typeof validator !== "function") throw new TypeError("A URL validator function is required.");
|
|
1022
|
+
const concurrency = options.concurrency === void 0 ? Infinity : Number.isFinite(options.concurrency) ? Math.max(1, Math.floor(options.concurrency)) : Infinity;
|
|
1023
|
+
const cache = options.cache ?? /* @__PURE__ */ new Map();
|
|
1024
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
1025
|
+
const waiters = [];
|
|
1026
|
+
let active = 0;
|
|
1027
|
+
const configuredSignal = options.signal ?? new AbortController().signal;
|
|
1028
|
+
const acquire = async (signal) => {
|
|
1029
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1030
|
+
if (active < concurrency) {
|
|
1031
|
+
active += 1;
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
await new Promise((resolve, reject) => {
|
|
1035
|
+
let waiter;
|
|
1036
|
+
const abortHandler = () => {
|
|
1037
|
+
const index = waiters.indexOf(waiter);
|
|
1038
|
+
if (index >= 0) waiters.splice(index, 1);
|
|
1039
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1040
|
+
reject(new Error("URL validation was aborted."));
|
|
1041
|
+
};
|
|
1042
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1043
|
+
waiter = () => {
|
|
1044
|
+
signal.removeEventListener("abort", abortHandler);
|
|
1045
|
+
resolve();
|
|
1046
|
+
};
|
|
1047
|
+
waiters.push(waiter);
|
|
1048
|
+
});
|
|
1049
|
+
active += 1;
|
|
1050
|
+
};
|
|
1051
|
+
const release = () => {
|
|
1052
|
+
active -= 1;
|
|
1053
|
+
waiters.shift()?.();
|
|
1054
|
+
};
|
|
1055
|
+
const check = async (url, context, signal = configuredSignal) => {
|
|
1056
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1057
|
+
const key = `${context.tag}:${context.attribute}:${url}`;
|
|
1058
|
+
const cached = cache.get(key);
|
|
1059
|
+
if (cached !== void 0) return cached;
|
|
1060
|
+
const existing = inFlight.get(key);
|
|
1061
|
+
if (existing) return existing;
|
|
1062
|
+
const promise = (async () => {
|
|
1063
|
+
await acquire(signal);
|
|
1064
|
+
try {
|
|
1065
|
+
if (signal.aborted) throw new Error("URL validation was aborted.");
|
|
1066
|
+
const validation = Promise.resolve(validator(url, context, signal));
|
|
1067
|
+
let timer;
|
|
1068
|
+
let abortHandler;
|
|
1069
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
1070
|
+
abortHandler = () => reject(new Error("URL validation was aborted."));
|
|
1071
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1072
|
+
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
1073
|
+
timer = setTimeout(
|
|
1074
|
+
() => reject(new Error(`URL validation timed out after ${options.timeoutMs} ms.`)),
|
|
1075
|
+
options.timeoutMs
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
try {
|
|
1080
|
+
const result = await (timer || abortHandler ? Promise.race([validation, cancellation]) : validation);
|
|
1081
|
+
cache.set(key, result);
|
|
1082
|
+
return result;
|
|
1083
|
+
} finally {
|
|
1084
|
+
if (timer) clearTimeout(timer);
|
|
1085
|
+
if (abortHandler) signal.removeEventListener("abort", abortHandler);
|
|
1086
|
+
}
|
|
1087
|
+
} finally {
|
|
1088
|
+
release();
|
|
1089
|
+
}
|
|
1090
|
+
})();
|
|
1091
|
+
inFlight.set(key, promise);
|
|
1092
|
+
try {
|
|
1093
|
+
return await promise;
|
|
1094
|
+
} finally {
|
|
1095
|
+
inFlight.delete(key);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
return (url, context, signal) => check(url, context, signal ?? configuredSignal);
|
|
1099
|
+
}
|
|
1020
1100
|
var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
|
|
1021
1101
|
var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
|
|
1022
1102
|
"characters",
|
|
@@ -1301,23 +1381,23 @@ function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, defin
|
|
|
1301
1381
|
);
|
|
1302
1382
|
}
|
|
1303
1383
|
}
|
|
1304
|
-
function validateAudioSource(token, source, diagnostics, options,
|
|
1384
|
+
function validateAudioSource(token, source, diagnostics, options, elementName3) {
|
|
1305
1385
|
const src = attr(token, "src");
|
|
1306
1386
|
if (!src) {
|
|
1307
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
1387
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3}> requires a "src" attribute.`);
|
|
1308
1388
|
return;
|
|
1309
1389
|
}
|
|
1310
1390
|
let parsed;
|
|
1311
1391
|
try {
|
|
1312
1392
|
parsed = new URL(src);
|
|
1313
1393
|
} catch {
|
|
1314
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
1394
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must be an absolute HTTP(S) URL.`);
|
|
1315
1395
|
return;
|
|
1316
1396
|
}
|
|
1317
1397
|
if (parsed.username || parsed.password)
|
|
1318
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
1398
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must not contain URL credentials.`);
|
|
1319
1399
|
if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
|
|
1320
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
1400
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> must use HTTPS.`);
|
|
1321
1401
|
const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
|
|
1322
1402
|
try {
|
|
1323
1403
|
const configured = new URL(allowedOrigin);
|
|
@@ -1329,13 +1409,13 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
|
|
|
1329
1409
|
}
|
|
1330
1410
|
}) ?? false;
|
|
1331
1411
|
if (options.allowedAudioOrigins && !isAllowedOrigin)
|
|
1332
|
-
addDiagnostic(diagnostics, source, token.start, `<${
|
|
1412
|
+
addDiagnostic(diagnostics, source, token.start, `<${elementName3} src> origin "${parsed.origin}" is not allowed.`);
|
|
1333
1413
|
else if (!isAllowedOrigin && !options.allowExternalAudio)
|
|
1334
1414
|
addDiagnostic(
|
|
1335
1415
|
diagnostics,
|
|
1336
1416
|
source,
|
|
1337
1417
|
token.start,
|
|
1338
|
-
`<${
|
|
1418
|
+
`<${elementName3} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
|
|
1339
1419
|
);
|
|
1340
1420
|
}
|
|
1341
1421
|
function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
|
|
@@ -1653,6 +1733,15 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1653
1733
|
const diagnostics = validateAzureSsmlStatic(ssml, options);
|
|
1654
1734
|
const validator = options.urlValidator ?? options.customUrlValidator;
|
|
1655
1735
|
if (!validator || typeof ssml !== "string") return diagnostics;
|
|
1736
|
+
const runnerOptions = options.urlValidation ?? {};
|
|
1737
|
+
const boundedValidator = createAzureUrlValidatorRunner(validator, {
|
|
1738
|
+
...runnerOptions,
|
|
1739
|
+
...options.urlValidatorConcurrency !== void 0 ? { concurrency: options.urlValidatorConcurrency } : {},
|
|
1740
|
+
...options.urlValidatorTimeoutMs !== void 0 ? { timeoutMs: options.urlValidatorTimeoutMs } : {},
|
|
1741
|
+
...options.urlValidatorSignal ? { signal: options.urlValidatorSignal } : {},
|
|
1742
|
+
...options.urlValidatorCache ? { cache: options.urlValidatorCache } : {}
|
|
1743
|
+
});
|
|
1744
|
+
const validationSignal = options.urlValidatorSignal ?? options.urlValidation?.signal ?? new AbortController().signal;
|
|
1656
1745
|
let tokens;
|
|
1657
1746
|
try {
|
|
1658
1747
|
tokens = tokenizeElements(ssml);
|
|
@@ -1662,7 +1751,11 @@ function validateAzureSsml(ssml, options = {}) {
|
|
|
1662
1751
|
const checks = tokens.flatMap(
|
|
1663
1752
|
(token) => urlAttributes(token).map(async ({ attribute, value }) => {
|
|
1664
1753
|
try {
|
|
1665
|
-
const result = await
|
|
1754
|
+
const result = await boundedValidator(
|
|
1755
|
+
value,
|
|
1756
|
+
{ tag: token.name, attribute, ...options.sourceNodePath ? { sourceNodePath: options.sourceNodePath } : {} },
|
|
1757
|
+
validationSignal
|
|
1758
|
+
);
|
|
1666
1759
|
const valid = typeof result === "boolean" ? result : result.valid;
|
|
1667
1760
|
if (!valid) {
|
|
1668
1761
|
const reason = typeof result === "boolean" ? void 0 : result.reason;
|