@usero/sdk 1.1.9 → 1.1.11
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/dist/plugins/session-replay.cjs +61 -0
- package/dist/plugins/session-replay.cjs.map +1 -1
- package/dist/plugins/session-replay.d.cts +7 -0
- package/dist/plugins/session-replay.d.ts +7 -0
- package/dist/plugins/session-replay.js +61 -0
- package/dist/plugins/session-replay.js.map +1 -1
- package/dist/plugins/user-test.cjs +133 -4
- package/dist/plugins/user-test.cjs.map +1 -1
- package/dist/plugins/user-test.d.cts +12 -2
- package/dist/plugins/user-test.d.ts +12 -2
- package/dist/plugins/user-test.js +132 -5
- package/dist/plugins/user-test.js.map +1 -1
- package/package.json +1 -1
|
@@ -98,6 +98,10 @@ interface RecorderStore {
|
|
|
98
98
|
muted: boolean;
|
|
99
99
|
mutedSinceMs: number | null;
|
|
100
100
|
mutedSegments: MutedSegment[];
|
|
101
|
+
micSilent: boolean;
|
|
102
|
+
silenceMonitor: {
|
|
103
|
+
stop(): void;
|
|
104
|
+
} | null;
|
|
101
105
|
muteToastShown: boolean;
|
|
102
106
|
muteToastTimers: number[];
|
|
103
107
|
notes: InFlightNote[];
|
|
@@ -110,13 +114,19 @@ interface RecorderStore {
|
|
|
110
114
|
declare function getTestSlug(queryParam: string): string | null;
|
|
111
115
|
declare function isMediaRecorderSupported(): boolean;
|
|
112
116
|
declare function pickMimeType(): string | undefined;
|
|
113
|
-
declare function micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'inactive';
|
|
117
|
+
declare function micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'silent' | 'inactive';
|
|
118
|
+
declare function rmsDbFromSamples(samples: Float32Array | number[]): number;
|
|
119
|
+
declare function isStreamSilent(input: number | Float32Array | number[]): boolean;
|
|
114
120
|
declare function userTest(options?: UserTestOptions): UseroPlugin;
|
|
115
121
|
declare const __test__: {
|
|
116
122
|
getTestSlug: typeof getTestSlug;
|
|
117
123
|
pickMimeType: typeof pickMimeType;
|
|
118
124
|
isMediaRecorderSupported: typeof isMediaRecorderSupported;
|
|
119
125
|
micChipState: typeof micChipState;
|
|
126
|
+
isStreamSilent: typeof isStreamSilent;
|
|
127
|
+
rmsDbFromSamples: typeof rmsDbFromSamples;
|
|
128
|
+
SILENCE_RMS_DB_THRESHOLD: number;
|
|
129
|
+
SILENCE_FLOOR_DB: number;
|
|
120
130
|
};
|
|
121
131
|
|
|
122
|
-
export { type UserTestOptions, __test__, userTest };
|
|
132
|
+
export { type UserTestOptions, __test__, isStreamSilent, rmsDbFromSamples, userTest };
|
|
@@ -98,6 +98,10 @@ interface RecorderStore {
|
|
|
98
98
|
muted: boolean;
|
|
99
99
|
mutedSinceMs: number | null;
|
|
100
100
|
mutedSegments: MutedSegment[];
|
|
101
|
+
micSilent: boolean;
|
|
102
|
+
silenceMonitor: {
|
|
103
|
+
stop(): void;
|
|
104
|
+
} | null;
|
|
101
105
|
muteToastShown: boolean;
|
|
102
106
|
muteToastTimers: number[];
|
|
103
107
|
notes: InFlightNote[];
|
|
@@ -110,13 +114,19 @@ interface RecorderStore {
|
|
|
110
114
|
declare function getTestSlug(queryParam: string): string | null;
|
|
111
115
|
declare function isMediaRecorderSupported(): boolean;
|
|
112
116
|
declare function pickMimeType(): string | undefined;
|
|
113
|
-
declare function micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'inactive';
|
|
117
|
+
declare function micChipState(store: RecorderStore): 'recording' | 'muted' | 'none' | 'connecting' | 'silent' | 'inactive';
|
|
118
|
+
declare function rmsDbFromSamples(samples: Float32Array | number[]): number;
|
|
119
|
+
declare function isStreamSilent(input: number | Float32Array | number[]): boolean;
|
|
114
120
|
declare function userTest(options?: UserTestOptions): UseroPlugin;
|
|
115
121
|
declare const __test__: {
|
|
116
122
|
getTestSlug: typeof getTestSlug;
|
|
117
123
|
pickMimeType: typeof pickMimeType;
|
|
118
124
|
isMediaRecorderSupported: typeof isMediaRecorderSupported;
|
|
119
125
|
micChipState: typeof micChipState;
|
|
126
|
+
isStreamSilent: typeof isStreamSilent;
|
|
127
|
+
rmsDbFromSamples: typeof rmsDbFromSamples;
|
|
128
|
+
SILENCE_RMS_DB_THRESHOLD: number;
|
|
129
|
+
SILENCE_FLOOR_DB: number;
|
|
120
130
|
};
|
|
121
131
|
|
|
122
|
-
export { type UserTestOptions, __test__, userTest };
|
|
132
|
+
export { type UserTestOptions, __test__, isStreamSilent, rmsDbFromSamples, userTest };
|
|
@@ -725,7 +725,9 @@ function micChipState(store) {
|
|
|
725
725
|
if (store.micAcquiring) return "connecting";
|
|
726
726
|
return "none";
|
|
727
727
|
}
|
|
728
|
-
|
|
728
|
+
if (store.muted) return "muted";
|
|
729
|
+
if (store.micSilent) return "silent";
|
|
730
|
+
return "recording";
|
|
729
731
|
}
|
|
730
732
|
function renderIndicatorState(store) {
|
|
731
733
|
const root = store.indicatorRoot;
|
|
@@ -738,7 +740,8 @@ function renderIndicatorState(store) {
|
|
|
738
740
|
if (!(dot instanceof HTMLElement) || !mic || !(micIcon instanceof HTMLElement) || !(micLabel instanceof HTMLElement) || !btn) return;
|
|
739
741
|
dot.setAttribute("data-state", store.indicatorState);
|
|
740
742
|
const chipState = micChipState(store);
|
|
741
|
-
|
|
743
|
+
const micStateAttr = chipState === "inactive" || chipState === "silent" ? "none" : chipState;
|
|
744
|
+
mic.setAttribute("data-mic-state", micStateAttr);
|
|
742
745
|
mic.removeAttribute("data-mic-fail");
|
|
743
746
|
if (chipState === "none") mic.setAttribute("data-mic-fail", store.micFailReason ?? "blocked");
|
|
744
747
|
switch (store.indicatorState) {
|
|
@@ -782,6 +785,13 @@ function renderIndicatorState(store) {
|
|
|
782
785
|
mic.setAttribute("aria-pressed", "false");
|
|
783
786
|
mic.setAttribute("tabindex", "-1");
|
|
784
787
|
break;
|
|
788
|
+
case "silent":
|
|
789
|
+
micIcon.innerHTML = MIC_MUTED_ICON_SVG;
|
|
790
|
+
micLabel.textContent = "We can't hear you, tap to recheck";
|
|
791
|
+
mic.setAttribute("aria-label", "We can't hear your microphone. Check your input device, then tap to recheck. Recording continues.");
|
|
792
|
+
mic.setAttribute("aria-pressed", "false");
|
|
793
|
+
mic.removeAttribute("tabindex");
|
|
794
|
+
break;
|
|
785
795
|
case "none": {
|
|
786
796
|
micIcon.innerHTML = MIC_MUTED_ICON_SVG;
|
|
787
797
|
const failLabel = store.micFailReason === "not-found" ? "No mic found, tap to retry" : "Mic blocked, tap to retry";
|
|
@@ -1369,9 +1379,92 @@ function enqueueChunk(store, ctx, blob) {
|
|
|
1369
1379
|
store.pendingUploads -= 1;
|
|
1370
1380
|
});
|
|
1371
1381
|
}
|
|
1382
|
+
var SILENCE_RMS_DB_THRESHOLD = -60;
|
|
1383
|
+
var SILENCE_FLOOR_DB = -100;
|
|
1384
|
+
var SILENCE_SUSTAINED_MS = 1800;
|
|
1385
|
+
var SILENCE_POLL_MS = 250;
|
|
1386
|
+
function rmsDbFromSamples(samples) {
|
|
1387
|
+
const n = samples.length;
|
|
1388
|
+
if (n === 0) return SILENCE_FLOOR_DB;
|
|
1389
|
+
let sumSquares = 0;
|
|
1390
|
+
for (let i = 0; i < n; i += 1) {
|
|
1391
|
+
const s = samples[i] ?? 0;
|
|
1392
|
+
sumSquares += s * s;
|
|
1393
|
+
}
|
|
1394
|
+
const rms = Math.sqrt(sumSquares / n);
|
|
1395
|
+
if (rms <= 0) return SILENCE_FLOOR_DB;
|
|
1396
|
+
const db = 20 * Math.log10(rms);
|
|
1397
|
+
return db < SILENCE_FLOOR_DB ? SILENCE_FLOOR_DB : db;
|
|
1398
|
+
}
|
|
1399
|
+
function isStreamSilent(input) {
|
|
1400
|
+
const rmsDb = typeof input === "number" ? input : rmsDbFromSamples(input);
|
|
1401
|
+
return rmsDb <= SILENCE_RMS_DB_THRESHOLD;
|
|
1402
|
+
}
|
|
1403
|
+
function startSilenceMonitor(stream, onChange, logger) {
|
|
1404
|
+
const Ctor = typeof window !== "undefined" ? window.AudioContext ?? window.webkitAudioContext : void 0;
|
|
1405
|
+
if (!Ctor) return null;
|
|
1406
|
+
let audioCtx;
|
|
1407
|
+
let source;
|
|
1408
|
+
let analyser;
|
|
1409
|
+
try {
|
|
1410
|
+
audioCtx = new Ctor();
|
|
1411
|
+
source = audioCtx.createMediaStreamSource(stream);
|
|
1412
|
+
analyser = audioCtx.createAnalyser();
|
|
1413
|
+
analyser.fftSize = 2048;
|
|
1414
|
+
source.connect(analyser);
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
logger.warn("silence monitor: failed to attach analyser", err);
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1419
|
+
const buffer = new Float32Array(analyser.fftSize);
|
|
1420
|
+
let reportedSilent = false;
|
|
1421
|
+
let runStartedAt = Date.now();
|
|
1422
|
+
let lastRaw = false;
|
|
1423
|
+
let intervalId = null;
|
|
1424
|
+
const tick = () => {
|
|
1425
|
+
try {
|
|
1426
|
+
analyser.getFloatTimeDomainData(buffer);
|
|
1427
|
+
} catch {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const rawSilent = isStreamSilent(buffer);
|
|
1431
|
+
const now = Date.now();
|
|
1432
|
+
if (rawSilent !== lastRaw) {
|
|
1433
|
+
lastRaw = rawSilent;
|
|
1434
|
+
runStartedAt = now;
|
|
1435
|
+
}
|
|
1436
|
+
if (rawSilent !== reportedSilent && now - runStartedAt >= SILENCE_SUSTAINED_MS) {
|
|
1437
|
+
reportedSilent = rawSilent;
|
|
1438
|
+
onChange(reportedSilent);
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
intervalId = setInterval(tick, SILENCE_POLL_MS);
|
|
1442
|
+
return {
|
|
1443
|
+
stop() {
|
|
1444
|
+
if (intervalId !== null) {
|
|
1445
|
+
clearInterval(intervalId);
|
|
1446
|
+
intervalId = null;
|
|
1447
|
+
}
|
|
1448
|
+
try {
|
|
1449
|
+
source.disconnect();
|
|
1450
|
+
analyser.disconnect();
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
try {
|
|
1454
|
+
void audioCtx.close();
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1372
1460
|
async function startRecording(store, ctx) {
|
|
1373
1461
|
store.micAcquiring = true;
|
|
1374
1462
|
store.micFailReason = null;
|
|
1463
|
+
if (store.silenceMonitor) {
|
|
1464
|
+
store.silenceMonitor.stop();
|
|
1465
|
+
store.silenceMonitor = null;
|
|
1466
|
+
}
|
|
1467
|
+
store.micSilent = false;
|
|
1375
1468
|
renderIndicatorState(store);
|
|
1376
1469
|
if (!isMediaRecorderSupported()) {
|
|
1377
1470
|
ctx.logger.warn("MediaRecorder not supported, continuing without audio");
|
|
@@ -1426,6 +1519,17 @@ async function startRecording(store, ctx) {
|
|
|
1426
1519
|
store.indicatorState = "recording";
|
|
1427
1520
|
}
|
|
1428
1521
|
renderIndicatorState(store);
|
|
1522
|
+
const monitor = startSilenceMonitor(
|
|
1523
|
+
stream,
|
|
1524
|
+
(silent) => {
|
|
1525
|
+
const effectiveSilent = silent && !store.muted;
|
|
1526
|
+
if (store.micSilent === effectiveSilent) return;
|
|
1527
|
+
store.micSilent = effectiveSilent;
|
|
1528
|
+
renderIndicatorState(store);
|
|
1529
|
+
},
|
|
1530
|
+
ctx.logger
|
|
1531
|
+
);
|
|
1532
|
+
store.silenceMonitor = monitor;
|
|
1429
1533
|
}
|
|
1430
1534
|
function toggleMute(store) {
|
|
1431
1535
|
if (!store.stream || !store.hasMicPermission) return false;
|
|
@@ -1456,6 +1560,11 @@ function flushMuteIfActive(store) {
|
|
|
1456
1560
|
store.mutedSinceMs = null;
|
|
1457
1561
|
}
|
|
1458
1562
|
function stopRecording(store) {
|
|
1563
|
+
if (store.silenceMonitor) {
|
|
1564
|
+
store.silenceMonitor.stop();
|
|
1565
|
+
store.silenceMonitor = null;
|
|
1566
|
+
}
|
|
1567
|
+
store.micSilent = false;
|
|
1459
1568
|
const recorder = store.recorder;
|
|
1460
1569
|
if (recorder && recorder.state !== "inactive") {
|
|
1461
1570
|
try {
|
|
@@ -1587,6 +1696,8 @@ function userTest(options = {}) {
|
|
|
1587
1696
|
muted: false,
|
|
1588
1697
|
mutedSinceMs: null,
|
|
1589
1698
|
mutedSegments: [],
|
|
1699
|
+
micSilent: false,
|
|
1700
|
+
silenceMonitor: null,
|
|
1590
1701
|
muteToastShown: false,
|
|
1591
1702
|
muteToastTimers: [],
|
|
1592
1703
|
notes: [],
|
|
@@ -1614,9 +1725,16 @@ function userTest(options = {}) {
|
|
|
1614
1725
|
}
|
|
1615
1726
|
return;
|
|
1616
1727
|
}
|
|
1728
|
+
if (store.micSilent && !store.micAcquiring && store.indicatorState !== "finishing" && store.indicatorState !== "done" && store.indicatorState !== "error") {
|
|
1729
|
+
void startRecording(store, ctx);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1617
1732
|
const ok = toggleMute(store);
|
|
1618
1733
|
if (!ok) return;
|
|
1619
|
-
if (store.muted)
|
|
1734
|
+
if (store.muted) {
|
|
1735
|
+
store.micSilent = false;
|
|
1736
|
+
showMuteToast(store);
|
|
1737
|
+
}
|
|
1620
1738
|
renderIndicatorState(store);
|
|
1621
1739
|
};
|
|
1622
1740
|
const closeNote = () => closeNotePopover(store);
|
|
@@ -1752,8 +1870,17 @@ function userTest(options = {}) {
|
|
|
1752
1870
|
}
|
|
1753
1871
|
};
|
|
1754
1872
|
}
|
|
1755
|
-
var __test__ = {
|
|
1873
|
+
var __test__ = {
|
|
1874
|
+
getTestSlug,
|
|
1875
|
+
pickMimeType,
|
|
1876
|
+
isMediaRecorderSupported,
|
|
1877
|
+
micChipState,
|
|
1878
|
+
isStreamSilent,
|
|
1879
|
+
rmsDbFromSamples,
|
|
1880
|
+
SILENCE_RMS_DB_THRESHOLD,
|
|
1881
|
+
SILENCE_FLOOR_DB
|
|
1882
|
+
};
|
|
1756
1883
|
|
|
1757
|
-
export { __test__, userTest };
|
|
1884
|
+
export { __test__, isStreamSilent, rmsDbFromSamples, userTest };
|
|
1758
1885
|
//# sourceMappingURL=user-test.js.map
|
|
1759
1886
|
//# sourceMappingURL=user-test.js.map
|