@entros/pulse-sdk 1.5.0 → 1.5.1
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 +28 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -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;
|
|
@@ -831,6 +851,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
|
|
|
831
851
|
normalizedSamples = samples;
|
|
832
852
|
}
|
|
833
853
|
const { f0, amplitudes: normalizedAmplitudes, periods } = await detectF0Contour(normalizedSamples, sampleRate);
|
|
854
|
+
await yieldToMainThread();
|
|
834
855
|
const amplitudes = [];
|
|
835
856
|
for (let i = 0; i < numFrames; i++) {
|
|
836
857
|
const start = i * hopSize;
|
|
@@ -855,6 +876,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
|
|
|
855
876
|
const hnrStats = condense(hnrValues);
|
|
856
877
|
const hnrEntropy = entropy(hnrValues);
|
|
857
878
|
const hnrFeatures = [hnrStats.mean, hnrStats.variance, hnrStats.skewness, hnrStats.kurtosis, hnrEntropy];
|
|
879
|
+
await yieldToMainThread();
|
|
858
880
|
const { f1f2, f2f3 } = extractFormantRatios(normalizedSamples, sampleRate, frameSize, hopSize);
|
|
859
881
|
const f1f2Stats = condense(f1f2);
|
|
860
882
|
const f2f3Stats = condense(f2f3);
|
|
@@ -868,6 +890,7 @@ async function extractSpeakerFeaturesDetailed(audio) {
|
|
|
868
890
|
f2f3Stats.skewness,
|
|
869
891
|
f2f3Stats.kurtosis
|
|
870
892
|
];
|
|
893
|
+
await yieldToMainThread();
|
|
871
894
|
const ltasFeatures = await computeLTAS(samples, sampleRate);
|
|
872
895
|
const voicingFeatures = [voicedRatio];
|
|
873
896
|
const ampStats = condense(amplitudes);
|
|
@@ -4302,10 +4325,13 @@ async function extractFeatures(data) {
|
|
|
4302
4325
|
const { features: audioFeatures, f0Contour } = await extractSpeakerFeaturesDetailed(
|
|
4303
4326
|
data.audio
|
|
4304
4327
|
);
|
|
4328
|
+
await yieldToMainThread();
|
|
4305
4329
|
const hasMotion = data.motion.length >= MIN_MOTION_SAMPLES;
|
|
4306
4330
|
const hasTouch = data.touch.length >= MIN_TOUCH_SAMPLES;
|
|
4307
4331
|
const motionFeatures = hasMotion && hasTouch ? extractMouseDynamics(data.touch) : hasMotion ? extractMotionFeatures(data.motion) : extractMouseDynamics(data.touch);
|
|
4332
|
+
await yieldToMainThread();
|
|
4308
4333
|
const touchFeatures = extractTouchFeatures(data.touch);
|
|
4334
|
+
await yieldToMainThread();
|
|
4309
4335
|
const accelMagnitude = hasMotion && f0Contour.length > 0 ? extractAccelerationMagnitude(data.motion, f0Contour.length) : [];
|
|
4310
4336
|
return {
|
|
4311
4337
|
raw: fuseRawFeatures(audioFeatures, motionFeatures, touchFeatures),
|
|
@@ -4319,6 +4345,7 @@ var MIN_MOTION_SAMPLES = 10;
|
|
|
4319
4345
|
var MIN_TOUCH_SAMPLES = 10;
|
|
4320
4346
|
async function extractFingerprintAndValidate(sensorData, config, walletAddress, onProgress) {
|
|
4321
4347
|
onProgress?.("Extracting features...");
|
|
4348
|
+
await yieldToMainThread();
|
|
4322
4349
|
const {
|
|
4323
4350
|
raw: features,
|
|
4324
4351
|
normalized: normalizedFeatures,
|
|
@@ -4333,6 +4360,7 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
|
|
|
4333
4360
|
const tbh = await generateTBH(fingerprint);
|
|
4334
4361
|
let signedReceipt;
|
|
4335
4362
|
onProgress?.("Validating...");
|
|
4363
|
+
await yieldToMainThread();
|
|
4336
4364
|
if (config.relayerUrl && walletAddress) {
|
|
4337
4365
|
try {
|
|
4338
4366
|
const baseUrl = new URL(config.relayerUrl);
|