@burdenoff/website-sdk 2026.828.5 → 2026.828.7

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.
@@ -1278,11 +1278,40 @@ function getRecognitionCtor() {
1278
1278
  const w = window;
1279
1279
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1280
1280
  }
1281
+ var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1282
+ async function ensureMicrophoneAccess() {
1283
+ const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1284
+ if (!media?.getUserMedia) return { ok: true, confirmed: false };
1285
+ try {
1286
+ const status = await navigator.permissions?.query({
1287
+ name: "microphone"
1288
+ });
1289
+ if (status?.state === "granted") return { ok: true, confirmed: true };
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, confirmed: 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
+ }
1281
1310
  function describeError(code) {
1282
1311
  switch (code) {
1283
1312
  case "not-allowed":
1284
1313
  case "service-not-allowed":
1285
- return "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1314
+ return MIC_DENIED_MESSAGE;
1286
1315
  case "no-speech":
1287
1316
  return "I didn't catch anything \u2014 try again a little closer to the mic.";
1288
1317
  case "audio-capture":
@@ -1305,11 +1334,23 @@ function useSpeechInput({
1305
1334
  const [interim, setInterim] = react.useState("");
1306
1335
  const [error, setError] = react.useState(null);
1307
1336
  const recognitionRef = react.useRef(null);
1337
+ const startTokenRef = react.useRef(0);
1338
+ const micConfirmedRef = react.useRef(false);
1339
+ const beginRecognitionRef = react.useRef(null);
1340
+ const beginRecognition = react.useCallback(
1341
+ (Ctor, token) => {
1342
+ beginRecognitionRef.current?.(Ctor, token);
1343
+ },
1344
+ []
1345
+ );
1308
1346
  const finalRef = react.useRef(onFinalTranscript);
1309
1347
  const errorRef = react.useRef(onError);
1310
1348
  finalRef.current = onFinalTranscript;
1311
1349
  errorRef.current = onError;
1312
1350
  const stop = react.useCallback(() => {
1351
+ startTokenRef.current += 1;
1352
+ setListening(false);
1353
+ setInterim("");
1313
1354
  const recognition = recognitionRef.current;
1314
1355
  if (!recognition) return;
1315
1356
  try {
@@ -1323,48 +1364,70 @@ function useSpeechInput({
1323
1364
  const Ctor = getRecognitionCtor();
1324
1365
  if (!Ctor) return;
1325
1366
  if (recognitionRef.current) stop();
1326
- const recognition = new Ctor();
1327
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1328
- recognition.continuous = true;
1329
- recognition.interimResults = true;
1330
- recognition.maxAlternatives = 1;
1331
- recognition.onstart = () => {
1332
- setError(null);
1333
- setListening(true);
1334
- };
1335
- recognition.onresult = (event) => {
1336
- let settled = "";
1337
- let pending = "";
1338
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1339
- const result = event.results[i];
1340
- if (!result) continue;
1341
- const text = result[0]?.transcript ?? "";
1342
- if (result.isFinal) settled += text;
1343
- else pending += text;
1367
+ setError(null);
1368
+ setListening(true);
1369
+ startTokenRef.current += 1;
1370
+ const token = startTokenRef.current;
1371
+ void ensureMicrophoneAccess().then((access) => {
1372
+ if (token !== startTokenRef.current) return;
1373
+ micConfirmedRef.current = access.ok && access.confirmed;
1374
+ if (!access.ok) {
1375
+ setListening(false);
1376
+ setError(access.message);
1377
+ errorRef.current?.(access.message);
1378
+ return;
1344
1379
  }
1345
- setInterim(pending);
1346
- if (settled.trim() !== "") finalRef.current(settled);
1347
- };
1348
- recognition.onerror = (event) => {
1349
- const message = describeError(event.error);
1350
- setListening(false);
1351
- setInterim("");
1352
- if (message !== "") {
1353
- setError(message);
1354
- errorRef.current?.(message);
1380
+ beginRecognition(Ctor, token);
1381
+ });
1382
+ }, [beginRecognition, stop]);
1383
+ const beginRecognitionImpl = react.useCallback(
1384
+ (Ctor, token) => {
1385
+ const recognition = new Ctor();
1386
+ recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1387
+ recognition.continuous = true;
1388
+ recognition.interimResults = true;
1389
+ recognition.maxAlternatives = 1;
1390
+ recognition.onstart = () => {
1391
+ if (token !== startTokenRef.current) return;
1392
+ setError(null);
1393
+ setListening(true);
1394
+ };
1395
+ recognition.onresult = (event) => {
1396
+ let settled = "";
1397
+ let pending = "";
1398
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1399
+ const result = event.results[i];
1400
+ if (!result) continue;
1401
+ const text = result[0]?.transcript ?? "";
1402
+ if (result.isFinal) settled += text;
1403
+ else pending += text;
1404
+ }
1405
+ setInterim(pending);
1406
+ if (settled.trim() !== "") finalRef.current(settled);
1407
+ };
1408
+ recognition.onerror = (event) => {
1409
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : describeError(event.error);
1410
+ setListening(false);
1411
+ setInterim("");
1412
+ if (message !== "") {
1413
+ setError(message);
1414
+ errorRef.current?.(message);
1415
+ }
1416
+ };
1417
+ recognition.onend = () => {
1418
+ setListening(false);
1419
+ setInterim("");
1420
+ };
1421
+ recognitionRef.current = recognition;
1422
+ try {
1423
+ recognition.start();
1424
+ } catch {
1425
+ setListening(false);
1355
1426
  }
1356
- };
1357
- recognition.onend = () => {
1358
- setListening(false);
1359
- setInterim("");
1360
- };
1361
- recognitionRef.current = recognition;
1362
- try {
1363
- recognition.start();
1364
- } catch {
1365
- setListening(false);
1366
- }
1367
- }, [lang, stop]);
1427
+ },
1428
+ [lang]
1429
+ );
1430
+ beginRecognitionRef.current = beginRecognitionImpl;
1368
1431
  const toggle = react.useCallback(() => {
1369
1432
  if (listening) stop();
1370
1433
  else start();