@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 +2 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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);
|