@burdenoff/website-sdk 2026.828.5 → 2026.828.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1276,11 +1276,42 @@ function getRecognitionCtor() {
1276
1276
  const w = window;
1277
1277
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1278
1278
  }
1279
+ var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1280
+ async function ensureMicrophoneAccess() {
1281
+ const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1282
+ if (!media?.getUserMedia) return { ok: true };
1283
+ try {
1284
+ const status = await navigator.permissions?.query({
1285
+ name: "microphone"
1286
+ });
1287
+ if (status?.state === "granted") return { ok: true };
1288
+ if (status?.state === "denied")
1289
+ return { ok: false, message: MIC_DENIED_MESSAGE };
1290
+ } catch {
1291
+ }
1292
+ try {
1293
+ const stream = await media.getUserMedia({ audio: true });
1294
+ for (const track of stream.getTracks()) track.stop();
1295
+ return { ok: true };
1296
+ } catch (error) {
1297
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1298
+ if (name === "NotAllowedError" || name === "SecurityError") {
1299
+ return { ok: false, message: MIC_DENIED_MESSAGE };
1300
+ }
1301
+ if (name === "NotFoundError" || name === "DevicesNotFoundError") {
1302
+ return { ok: false, message: "No microphone was found." };
1303
+ }
1304
+ return {
1305
+ ok: false,
1306
+ message: "Dictation could not start. You can type instead."
1307
+ };
1308
+ }
1309
+ }
1279
1310
  function describeError(code) {
1280
1311
  switch (code) {
1281
1312
  case "not-allowed":
1282
1313
  case "service-not-allowed":
1283
- return "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1314
+ return MIC_DENIED_MESSAGE;
1284
1315
  case "no-speech":
1285
1316
  return "I didn't catch anything \u2014 try again a little closer to the mic.";
1286
1317
  case "audio-capture":
@@ -1303,11 +1334,22 @@ function useSpeechInput({
1303
1334
  const [interim, setInterim] = useState("");
1304
1335
  const [error, setError] = useState(null);
1305
1336
  const recognitionRef = useRef(null);
1337
+ const startTokenRef = useRef(0);
1338
+ const beginRecognitionRef = useRef(null);
1339
+ const beginRecognition = useCallback(
1340
+ (Ctor, token) => {
1341
+ beginRecognitionRef.current?.(Ctor, token);
1342
+ },
1343
+ []
1344
+ );
1306
1345
  const finalRef = useRef(onFinalTranscript);
1307
1346
  const errorRef = useRef(onError);
1308
1347
  finalRef.current = onFinalTranscript;
1309
1348
  errorRef.current = onError;
1310
1349
  const stop = useCallback(() => {
1350
+ startTokenRef.current += 1;
1351
+ setListening(false);
1352
+ setInterim("");
1311
1353
  const recognition = recognitionRef.current;
1312
1354
  if (!recognition) return;
1313
1355
  try {
@@ -1321,48 +1363,69 @@ function useSpeechInput({
1321
1363
  const Ctor = getRecognitionCtor();
1322
1364
  if (!Ctor) return;
1323
1365
  if (recognitionRef.current) stop();
1324
- const recognition = new Ctor();
1325
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1326
- recognition.continuous = true;
1327
- recognition.interimResults = true;
1328
- recognition.maxAlternatives = 1;
1329
- recognition.onstart = () => {
1330
- setError(null);
1331
- setListening(true);
1332
- };
1333
- recognition.onresult = (event) => {
1334
- let settled = "";
1335
- let pending = "";
1336
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1337
- const result = event.results[i];
1338
- if (!result) continue;
1339
- const text = result[0]?.transcript ?? "";
1340
- if (result.isFinal) settled += text;
1341
- else pending += text;
1366
+ setError(null);
1367
+ setListening(true);
1368
+ startTokenRef.current += 1;
1369
+ const token = startTokenRef.current;
1370
+ void ensureMicrophoneAccess().then((access) => {
1371
+ if (token !== startTokenRef.current) return;
1372
+ if (!access.ok) {
1373
+ setListening(false);
1374
+ setError(access.message);
1375
+ errorRef.current?.(access.message);
1376
+ return;
1342
1377
  }
1343
- setInterim(pending);
1344
- if (settled.trim() !== "") finalRef.current(settled);
1345
- };
1346
- recognition.onerror = (event) => {
1347
- const message = describeError(event.error);
1348
- setListening(false);
1349
- setInterim("");
1350
- if (message !== "") {
1351
- setError(message);
1352
- errorRef.current?.(message);
1378
+ beginRecognition(Ctor, token);
1379
+ });
1380
+ }, [beginRecognition, stop]);
1381
+ const beginRecognitionImpl = useCallback(
1382
+ (Ctor, token) => {
1383
+ const recognition = new Ctor();
1384
+ recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1385
+ recognition.continuous = true;
1386
+ recognition.interimResults = true;
1387
+ recognition.maxAlternatives = 1;
1388
+ recognition.onstart = () => {
1389
+ if (token !== startTokenRef.current) return;
1390
+ setError(null);
1391
+ setListening(true);
1392
+ };
1393
+ recognition.onresult = (event) => {
1394
+ let settled = "";
1395
+ let pending = "";
1396
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1397
+ const result = event.results[i];
1398
+ if (!result) continue;
1399
+ const text = result[0]?.transcript ?? "";
1400
+ if (result.isFinal) settled += text;
1401
+ else pending += text;
1402
+ }
1403
+ setInterim(pending);
1404
+ if (settled.trim() !== "") finalRef.current(settled);
1405
+ };
1406
+ recognition.onerror = (event) => {
1407
+ const message = describeError(event.error);
1408
+ setListening(false);
1409
+ setInterim("");
1410
+ if (message !== "") {
1411
+ setError(message);
1412
+ errorRef.current?.(message);
1413
+ }
1414
+ };
1415
+ recognition.onend = () => {
1416
+ setListening(false);
1417
+ setInterim("");
1418
+ };
1419
+ recognitionRef.current = recognition;
1420
+ try {
1421
+ recognition.start();
1422
+ } catch {
1423
+ setListening(false);
1353
1424
  }
1354
- };
1355
- recognition.onend = () => {
1356
- setListening(false);
1357
- setInterim("");
1358
- };
1359
- recognitionRef.current = recognition;
1360
- try {
1361
- recognition.start();
1362
- } catch {
1363
- setListening(false);
1364
- }
1365
- }, [lang, stop]);
1425
+ },
1426
+ [lang]
1427
+ );
1428
+ beginRecognitionRef.current = beginRecognitionImpl;
1366
1429
  const toggle = useCallback(() => {
1367
1430
  if (listening) stop();
1368
1431
  else start();