@entros/pulse-sdk 1.5.0 → 1.5.2

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/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  Client-side SDK for the Entros Protocol. Captures behavioral biometrics (voice, motion, touch), extracts 134 statistical features, generates a Groth16 zero-knowledge proof, and submits for on-chain verification on Solana. Raw biometric data stays on-device — only derived features and the proof are transmitted.
7
7
 
8
+ > **Looking for a drop-in?** Most integrators want [`@entros/verify`](https://github.com/entros-protocol/entros-verify) — a popup-pattern React component that wraps this SDK and ships verification in five lines of JSX. Use this package directly when you need to own the verification UX (custom capture canvas, branded loading states, mobile-native).
9
+
8
10
  ## Install
9
11
 
10
12
  ```bash
package/dist/index.js CHANGED
@@ -599,6 +599,26 @@ function extractFormantRatios(samples, sampleRate, frameSize, hopSize) {
599
599
  return { f1f2, f2f3 };
600
600
  }
601
601
 
602
+ // src/yield.ts
603
+ function yieldToMainThread() {
604
+ return new Promise((resolve) => {
605
+ if (typeof MessageChannel !== "undefined") {
606
+ const channel = new MessageChannel();
607
+ channel.port1.onmessage = () => {
608
+ channel.port1.close();
609
+ resolve();
610
+ };
611
+ channel.port2.postMessage(null);
612
+ return;
613
+ }
614
+ if (typeof setTimeout !== "undefined") {
615
+ setTimeout(resolve, 0);
616
+ return;
617
+ }
618
+ resolve();
619
+ });
620
+ }
621
+
602
622
  // src/extraction/speaker.ts
603
623
  function getFrameSize(sampleRate) {
604
624
  const MIN_F0 = 50;
@@ -632,6 +652,7 @@ async function getMeyda() {
632
652
  }
633
653
  return meydaModule.default ?? meydaModule;
634
654
  }
655
+ var F0_YIELD_EVERY_N_FRAMES = 16;
635
656
  async function detectF0Contour(samples, sampleRate) {
636
657
  const detect = await getPitchDetector(sampleRate);
637
658
  const frameSize = getFrameSize(sampleRate);
@@ -658,6 +679,9 @@ async function detectF0Contour(samples, sampleRate) {
658
679
  sum += (frame[j] ?? 0) * (frame[j] ?? 0);
659
680
  }
660
681
  amplitudes.push(Math.sqrt(sum / frame.length));
682
+ if (i > 0 && i < numFrames - 1 && i % F0_YIELD_EVERY_N_FRAMES === 0) {
683
+ await yieldToMainThread();
684
+ }
661
685
  }
662
686
  return { f0, amplitudes, periods };
663
687
  }
@@ -831,6 +855,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
831
855
  normalizedSamples = samples;
832
856
  }
833
857
  const { f0, amplitudes: normalizedAmplitudes, periods } = await detectF0Contour(normalizedSamples, sampleRate);
858
+ await yieldToMainThread();
834
859
  const amplitudes = [];
835
860
  for (let i = 0; i < numFrames; i++) {
836
861
  const start = i * hopSize;
@@ -855,6 +880,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
855
880
  const hnrStats = condense(hnrValues);
856
881
  const hnrEntropy = entropy(hnrValues);
857
882
  const hnrFeatures = [hnrStats.mean, hnrStats.variance, hnrStats.skewness, hnrStats.kurtosis, hnrEntropy];
883
+ await yieldToMainThread();
858
884
  const { f1f2, f2f3 } = extractFormantRatios(normalizedSamples, sampleRate, frameSize, hopSize);
859
885
  const f1f2Stats = condense(f1f2);
860
886
  const f2f3Stats = condense(f2f3);
@@ -868,6 +894,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
868
894
  f2f3Stats.skewness,
869
895
  f2f3Stats.kurtosis
870
896
  ];
897
+ await yieldToMainThread();
871
898
  const ltasFeatures = await computeLTAS(samples, sampleRate);
872
899
  const voicingFeatures = [voicedRatio];
873
900
  const ampStats = condense(amplitudes);
@@ -4302,10 +4329,13 @@ async function extractFeatures(data) {
4302
4329
  const { features: audioFeatures, f0Contour } = await extractSpeakerFeaturesDetailed(
4303
4330
  data.audio
4304
4331
  );
4332
+ await yieldToMainThread();
4305
4333
  const hasMotion = data.motion.length >= MIN_MOTION_SAMPLES;
4306
4334
  const hasTouch = data.touch.length >= MIN_TOUCH_SAMPLES;
4307
4335
  const motionFeatures = hasMotion && hasTouch ? extractMouseDynamics(data.touch) : hasMotion ? extractMotionFeatures(data.motion) : extractMouseDynamics(data.touch);
4336
+ await yieldToMainThread();
4308
4337
  const touchFeatures = extractTouchFeatures(data.touch);
4338
+ await yieldToMainThread();
4309
4339
  const accelMagnitude = hasMotion && f0Contour.length > 0 ? extractAccelerationMagnitude(data.motion, f0Contour.length) : [];
4310
4340
  return {
4311
4341
  raw: fuseRawFeatures(audioFeatures, motionFeatures, touchFeatures),
@@ -4319,6 +4349,7 @@ var MIN_MOTION_SAMPLES = 10;
4319
4349
  var MIN_TOUCH_SAMPLES = 10;
4320
4350
  async function extractFingerprintAndValidate(sensorData, config, walletAddress, onProgress) {
4321
4351
  onProgress?.("Extracting features...");
4352
+ await yieldToMainThread();
4322
4353
  const {
4323
4354
  raw: features,
4324
4355
  normalized: normalizedFeatures,
@@ -4333,6 +4364,7 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
4333
4364
  const tbh = await generateTBH(fingerprint);
4334
4365
  let signedReceipt;
4335
4366
  onProgress?.("Validating...");
4367
+ await yieldToMainThread();
4336
4368
  if (config.relayerUrl && walletAddress) {
4337
4369
  try {
4338
4370
  const baseUrl = new URL(config.relayerUrl);