@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 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
- }).catch(() => null);
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.startAudio();
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.stopAudio()).then(() => {
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.startMotion();
1736
+ await session.startAudio();
1703
1737
  stopPromises.push(
1704
- new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.stopMotion()).then(() => {
1738
+ new Promise((r) => setTimeout(r, DEFAULT_CAPTURE_MS)).then(() => session.stopAudio()).then(() => {
1705
1739
  })
1706
1740
  );
1707
- } catch {
1708
- session.skipMotion();
1741
+ } catch (err) {
1742
+ throw new Error(`Audio capture failed: ${err?.message ?? "microphone unavailable"}`);
1709
1743
  }
1710
1744
  if (touchElement) {
1711
1745
  try {