@iam-protocol/pulse-sdk 0.3.4 → 0.3.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.
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +48 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/pulse.ts +25 -12
package/dist/index.d.mts
CHANGED
|
@@ -54,6 +54,10 @@ interface CaptureOptions {
|
|
|
54
54
|
maxDurationMs?: number;
|
|
55
55
|
/** Called with RMS audio level (0-1) on each buffer during audio capture (~4x per second). */
|
|
56
56
|
onAudioLevel?: (rms: number) => void;
|
|
57
|
+
/** Pre-acquired MediaStream. If provided, captureAudio skips getUserMedia. */
|
|
58
|
+
stream?: MediaStream;
|
|
59
|
+
/** If true, captureMotion skips requestMotionPermission (already acquired). */
|
|
60
|
+
permissionGranted?: boolean;
|
|
57
61
|
}
|
|
58
62
|
/** Stage of a capture session */
|
|
59
63
|
type CaptureStage = "audio" | "motion" | "touch";
|
|
@@ -127,6 +131,7 @@ declare class PulseSession {
|
|
|
127
131
|
startMotion(): Promise<void>;
|
|
128
132
|
stopMotion(): Promise<MotionSample[]>;
|
|
129
133
|
skipMotion(): void;
|
|
134
|
+
isMotionCapturing(): boolean;
|
|
130
135
|
startTouch(): Promise<void>;
|
|
131
136
|
stopTouch(): Promise<TouchSample[]>;
|
|
132
137
|
skipTouch(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,10 @@ interface CaptureOptions {
|
|
|
54
54
|
maxDurationMs?: number;
|
|
55
55
|
/** Called with RMS audio level (0-1) on each buffer during audio capture (~4x per second). */
|
|
56
56
|
onAudioLevel?: (rms: number) => void;
|
|
57
|
+
/** Pre-acquired MediaStream. If provided, captureAudio skips getUserMedia. */
|
|
58
|
+
stream?: MediaStream;
|
|
59
|
+
/** If true, captureMotion skips requestMotionPermission (already acquired). */
|
|
60
|
+
permissionGranted?: boolean;
|
|
57
61
|
}
|
|
58
62
|
/** Stage of a capture session */
|
|
59
63
|
type CaptureStage = "audio" | "motion" | "touch";
|
|
@@ -127,6 +131,7 @@ declare class PulseSession {
|
|
|
127
131
|
startMotion(): Promise<void>;
|
|
128
132
|
stopMotion(): Promise<MotionSample[]>;
|
|
129
133
|
skipMotion(): void;
|
|
134
|
+
isMotionCapturing(): boolean;
|
|
130
135
|
startTouch(): Promise<void>;
|
|
131
136
|
stopTouch(): Promise<TouchSample[]>;
|
|
132
137
|
skipTouch(): void;
|
package/dist/index.js
CHANGED
|
@@ -109,9 +109,10 @@ async function captureAudio(options = {}) {
|
|
|
109
109
|
signal,
|
|
110
110
|
minDurationMs = MIN_CAPTURE_MS,
|
|
111
111
|
maxDurationMs = MAX_CAPTURE_MS,
|
|
112
|
-
onAudioLevel
|
|
112
|
+
onAudioLevel,
|
|
113
|
+
stream: preAcquiredStream
|
|
113
114
|
} = options;
|
|
114
|
-
const stream = await navigator.mediaDevices.getUserMedia({
|
|
115
|
+
const stream = preAcquiredStream ?? await navigator.mediaDevices.getUserMedia({
|
|
115
116
|
audio: {
|
|
116
117
|
sampleRate: TARGET_SAMPLE_RATE,
|
|
117
118
|
channelCount: 1,
|
|
@@ -121,6 +122,7 @@ async function captureAudio(options = {}) {
|
|
|
121
122
|
}
|
|
122
123
|
});
|
|
123
124
|
const ctx = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE });
|
|
125
|
+
await ctx.resume();
|
|
124
126
|
const capturedSampleRate = ctx.sampleRate;
|
|
125
127
|
const source = ctx.createMediaStreamSource(stream);
|
|
126
128
|
const chunks = [];
|
|
@@ -197,7 +199,7 @@ async function captureMotion(options = {}) {
|
|
|
197
199
|
minDurationMs = MIN_CAPTURE_MS,
|
|
198
200
|
maxDurationMs = MAX_CAPTURE_MS
|
|
199
201
|
} = options;
|
|
200
|
-
const hasPermission = await requestMotionPermission();
|
|
202
|
+
const hasPermission = options.permissionGranted ?? await requestMotionPermission();
|
|
201
203
|
if (!hasPermission) return [];
|
|
202
204
|
const samples = [];
|
|
203
205
|
const startTime = performance.now();
|
|
@@ -1469,6 +1471,15 @@ async function processSensorData(sensorData, config, wallet, connection) {
|
|
|
1469
1471
|
error: "No voice data detected. Please speak the phrase clearly during capture."
|
|
1470
1472
|
};
|
|
1471
1473
|
}
|
|
1474
|
+
const hasPreviousData = loadVerificationData() !== null;
|
|
1475
|
+
if (hasPreviousData && !hasMotion && !hasTouch) {
|
|
1476
|
+
return {
|
|
1477
|
+
success: false,
|
|
1478
|
+
commitment: new Uint8Array(32),
|
|
1479
|
+
isFirstVerification: false,
|
|
1480
|
+
error: "Insufficient sensor data for re-verification. Please trace the curve and allow motion access."
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1472
1483
|
const features = await extractFeatures(sensorData);
|
|
1473
1484
|
const nonZero = features.filter((v) => v !== 0).length;
|
|
1474
1485
|
console.log(
|
|
@@ -1578,12 +1589,25 @@ var PulseSession = class {
|
|
|
1578
1589
|
async startAudio(onAudioLevel) {
|
|
1579
1590
|
if (this.audioStageState !== "idle")
|
|
1580
1591
|
throw new Error("Audio capture already started");
|
|
1592
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1593
|
+
audio: {
|
|
1594
|
+
sampleRate: 16e3,
|
|
1595
|
+
channelCount: 1,
|
|
1596
|
+
echoCancellation: false,
|
|
1597
|
+
noiseSuppression: false,
|
|
1598
|
+
autoGainControl: false
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1581
1601
|
this.audioStageState = "capturing";
|
|
1582
1602
|
this.audioController = new AbortController();
|
|
1583
1603
|
this.audioPromise = captureAudio({
|
|
1584
1604
|
signal: this.audioController.signal,
|
|
1585
|
-
onAudioLevel
|
|
1586
|
-
|
|
1605
|
+
onAudioLevel,
|
|
1606
|
+
stream
|
|
1607
|
+
}).catch(() => {
|
|
1608
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
1609
|
+
return null;
|
|
1610
|
+
});
|
|
1587
1611
|
}
|
|
1588
1612
|
async stopAudio() {
|
|
1589
1613
|
if (this.audioStageState !== "capturing")
|
|
@@ -1599,10 +1623,16 @@ var PulseSession = class {
|
|
|
1599
1623
|
async startMotion() {
|
|
1600
1624
|
if (this.motionStageState !== "idle")
|
|
1601
1625
|
throw new Error("Motion capture already started");
|
|
1626
|
+
const hasPermission = await requestMotionPermission();
|
|
1627
|
+
if (!hasPermission) {
|
|
1628
|
+
this.motionStageState = "skipped";
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1602
1631
|
this.motionStageState = "capturing";
|
|
1603
1632
|
this.motionController = new AbortController();
|
|
1604
1633
|
this.motionPromise = captureMotion({
|
|
1605
|
-
signal: this.motionController.signal
|
|
1634
|
+
signal: this.motionController.signal,
|
|
1635
|
+
permissionGranted: true
|
|
1606
1636
|
}).catch(() => []);
|
|
1607
1637
|
}
|
|
1608
1638
|
async stopMotion() {
|
|
@@ -1618,6 +1648,9 @@ var PulseSession = class {
|
|
|
1618
1648
|
throw new Error("Motion capture already started");
|
|
1619
1649
|
this.motionStageState = "skipped";
|
|
1620
1650
|
}
|
|
1651
|
+
isMotionCapturing() {
|
|
1652
|
+
return this.motionStageState === "capturing";
|
|
1653
|
+
}
|
|
1621
1654
|
// --- Touch ---
|
|
1622
1655
|
async startTouch() {
|
|
1623
1656
|
if (this.touchStageState !== "idle")
|
|
@@ -1690,22 +1723,23 @@ var PulseSDK = class {
|
|
|
1690
1723
|
const session = this.createSession(touchElement);
|
|
1691
1724
|
const stopPromises = [];
|
|
1692
1725
|
try {
|
|
1693
|
-
await session.
|
|
1726
|
+
await session.startMotion();
|
|
1727
|
+
} catch {
|
|
1728
|
+
}
|
|
1729
|
+
if (session.isMotionCapturing()) {
|
|
1694
1730
|
stopPromises.push(
|
|
1695
|
-
new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.
|
|
1731
|
+
new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.stopMotion()).then(() => {
|
|
1696
1732
|
})
|
|
1697
1733
|
);
|
|
1698
|
-
} catch (err) {
|
|
1699
|
-
throw new Error(`Audio capture failed: ${err?.message ?? "microphone unavailable"}`);
|
|
1700
1734
|
}
|
|
1701
1735
|
try {
|
|
1702
|
-
await session.
|
|
1736
|
+
await session.startAudio();
|
|
1703
1737
|
stopPromises.push(
|
|
1704
|
-
new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.
|
|
1738
|
+
new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.stopAudio()).then(() => {
|
|
1705
1739
|
})
|
|
1706
1740
|
);
|
|
1707
|
-
} catch {
|
|
1708
|
-
|
|
1741
|
+
} catch (err) {
|
|
1742
|
+
throw new Error(`Audio capture failed: ${err?.message ?? "microphone unavailable"}`);
|
|
1709
1743
|
}
|
|
1710
1744
|
if (touchElement) {
|
|
1711
1745
|
try {
|