2025-11-03 20:15:55 +00:00
|
|
|
// ESM loader for Poindexter WASM
|
|
|
|
|
// Usage:
|
|
|
|
|
// import { init } from '@snider/poindexter-wasm';
|
|
|
|
|
// const px = await init();
|
|
|
|
|
// const tree = await px.newTree(2);
|
|
|
|
|
// await tree.insert({ id: 'a', coords: [0,0], value: 'A' });
|
|
|
|
|
// const res = await tree.nearest([0.1, 0.2]);
|
|
|
|
|
|
|
|
|
|
async function loadScriptOnce(src) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
// If already present, resolve immediately
|
|
|
|
|
if (document.querySelector(`script[src="${src}"]`)) return resolve();
|
|
|
|
|
const s = document.createElement('script');
|
|
|
|
|
s.src = src;
|
|
|
|
|
s.onload = () => resolve();
|
|
|
|
|
s.onerror = (e) => reject(new Error(`Failed to load ${src}`));
|
|
|
|
|
document.head.appendChild(s);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureWasmExec(url) {
|
|
|
|
|
if (typeof window !== 'undefined' && typeof window.Go === 'function') return;
|
|
|
|
|
await loadScriptOnce(url);
|
|
|
|
|
if (typeof window === 'undefined' || typeof window.Go !== 'function') {
|
|
|
|
|
throw new Error('wasm_exec.js did not define window.Go');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function unwrap(result) {
|
|
|
|
|
if (!result || typeof result !== 'object') throw new Error('bad result');
|
|
|
|
|
if (result.ok) return result.data;
|
|
|
|
|
throw new Error(result.error || 'unknown error');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function call(name, ...args) {
|
|
|
|
|
const fn = globalThis[name];
|
|
|
|
|
if (typeof fn !== 'function') throw new Error(`WASM function ${name} not found`);
|
|
|
|
|
return unwrap(fn(...args));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class PxTree {
|
|
|
|
|
constructor(treeId) { this.treeId = treeId; }
|
2025-12-25 12:18:18 +00:00
|
|
|
// Core operations
|
2025-11-03 20:15:55 +00:00
|
|
|
async len() { return call('pxTreeLen', this.treeId); }
|
|
|
|
|
async dim() { return call('pxTreeDim', this.treeId); }
|
|
|
|
|
async insert(point) { return call('pxInsert', this.treeId, point); }
|
|
|
|
|
async deleteByID(id) { return call('pxDeleteByID', this.treeId, id); }
|
|
|
|
|
async nearest(query) { return call('pxNearest', this.treeId, query); }
|
|
|
|
|
async kNearest(query, k) { return call('pxKNearest', this.treeId, query, k); }
|
|
|
|
|
async radius(query, r) { return call('pxRadius', this.treeId, query, r); }
|
|
|
|
|
async exportJSON() { return call('pxExportJSON', this.treeId); }
|
2025-12-25 12:18:18 +00:00
|
|
|
// Analytics operations
|
|
|
|
|
async getAnalytics() { return call('pxGetAnalytics', this.treeId); }
|
|
|
|
|
async getPeerStats() { return call('pxGetPeerStats', this.treeId); }
|
|
|
|
|
async getTopPeers(n) { return call('pxGetTopPeers', this.treeId, n); }
|
|
|
|
|
async getAxisDistributions(axisNames) { return call('pxGetAxisDistributions', this.treeId, axisNames); }
|
|
|
|
|
async resetAnalytics() { return call('pxResetAnalytics', this.treeId); }
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function init(options = {}) {
|
|
|
|
|
const {
|
|
|
|
|
wasmURL = new URL('./dist/poindexter.wasm', import.meta.url).toString(),
|
|
|
|
|
wasmExecURL = new URL('./dist/wasm_exec.js', import.meta.url).toString(),
|
|
|
|
|
instantiateWasm // optional custom instantiator: (source, importObject) => WebAssembly.Instance
|
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
|
|
await ensureWasmExec(wasmExecURL);
|
|
|
|
|
const go = new window.Go();
|
|
|
|
|
|
|
|
|
|
let result;
|
|
|
|
|
if (instantiateWasm) {
|
|
|
|
|
const source = await fetch(wasmURL).then(r => r.arrayBuffer());
|
|
|
|
|
const inst = await instantiateWasm(source, go.importObject);
|
|
|
|
|
result = { instance: inst };
|
|
|
|
|
} else if (WebAssembly.instantiateStreaming) {
|
|
|
|
|
result = await WebAssembly.instantiateStreaming(fetch(wasmURL), go.importObject);
|
|
|
|
|
} else {
|
|
|
|
|
const resp = await fetch(wasmURL);
|
|
|
|
|
const bytes = await resp.arrayBuffer();
|
|
|
|
|
result = await WebAssembly.instantiate(bytes, go.importObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run the Go program (it registers globals like pxNewTree, etc.)
|
2025-11-04 00:38:18 +00:00
|
|
|
// Do not await: the Go WASM main may block (e.g., via select{}), so awaiting never resolves.
|
|
|
|
|
go.run(result.instance);
|
2025-11-03 20:15:55 +00:00
|
|
|
|
|
|
|
|
const api = {
|
2025-12-25 12:18:18 +00:00
|
|
|
// Core functions
|
2025-11-03 20:15:55 +00:00
|
|
|
version: async () => call('pxVersion'),
|
|
|
|
|
hello: async (name) => call('pxHello', name ?? ''),
|
|
|
|
|
newTree: async (dim) => {
|
|
|
|
|
const info = call('pxNewTree', dim);
|
|
|
|
|
return new PxTree(info.treeId);
|
2025-12-25 12:18:18 +00:00
|
|
|
},
|
|
|
|
|
// Statistics utilities
|
|
|
|
|
computeDistributionStats: async (distances) => call('pxComputeDistributionStats', distances),
|
|
|
|
|
// NAT routing / peer quality functions
|
|
|
|
|
computePeerQualityScore: async (metrics, weights) => call('pxComputePeerQualityScore', metrics, weights),
|
|
|
|
|
computeTrustScore: async (metrics) => call('pxComputeTrustScore', metrics),
|
|
|
|
|
getDefaultQualityWeights: async () => call('pxGetDefaultQualityWeights'),
|
|
|
|
|
getDefaultPeerFeatureRanges: async () => call('pxGetDefaultPeerFeatureRanges'),
|
|
|
|
|
normalizePeerFeatures: async (features, ranges) => call('pxNormalizePeerFeatures', features, ranges),
|
feat: Add DNS tools with lookup, RDAP, and external tool links
Add comprehensive DNS tools module for network analysis:
DNS Lookup functionality:
- Support for A, AAAA, MX, TXT, NS, CNAME, SOA, PTR, SRV, CAA records
- DNSLookup() and DNSLookupAll() for single/complete lookups
- Configurable timeouts
- Structured result types for all record types
RDAP (new-style WHOIS) support:
- RDAPLookupDomain() for domain registration data
- RDAPLookupIP() for IP address information
- RDAPLookupASN() for autonomous system info
- Built-in server registry for common TLDs and RIRs
- ParseRDAPResponse() for extracting key domain info
External tool link generators:
- GetExternalToolLinks() - 20+ links for domain analysis
- GetExternalToolLinksIP() - IP-specific analysis tools
- GetExternalToolLinksEmail() - Email/domain verification
Tools include: MXToolbox (DNS, MX, SPF, DMARC, DKIM, blacklist),
DNSChecker, ViewDNS, IntoDNS, DNSViz, SecurityTrails, SSL Labs,
Shodan, Censys, IPInfo, AbuseIPDB, VirusTotal, and more.
WASM bindings expose link generators and RDAP URL builders
for use in TypeScript/browser environments.
2025-12-25 12:26:06 +00:00
|
|
|
weightedPeerFeatures: async (normalized, weights) => call('pxWeightedPeerFeatures', normalized, weights),
|
|
|
|
|
// DNS tools
|
|
|
|
|
getExternalToolLinks: async (domain) => call('pxGetExternalToolLinks', domain),
|
|
|
|
|
getExternalToolLinksIP: async (ip) => call('pxGetExternalToolLinksIP', ip),
|
|
|
|
|
getExternalToolLinksEmail: async (emailOrDomain) => call('pxGetExternalToolLinksEmail', emailOrDomain),
|
|
|
|
|
getRDAPServers: async () => call('pxGetRDAPServers'),
|
|
|
|
|
buildRDAPDomainURL: async (domain) => call('pxBuildRDAPDomainURL', domain),
|
|
|
|
|
buildRDAPIPURL: async (ip) => call('pxBuildRDAPIPURL', ip),
|
|
|
|
|
buildRDAPASNURL: async (asn) => call('pxBuildRDAPASNURL', asn),
|
feat: Add extended DNS record types (ClouDNS compatible)
- Add support for 13 additional record types: ALIAS, RP, SSHFP, TLSA,
DS, DNSKEY, NAPTR, LOC, HINFO, CERT, SMIMEA, WR (Web Redirect), SPF
- Add GetDNSRecordTypeInfo() for metadata with RFC references
- Add GetCommonDNSRecordTypes() for commonly used types
- Add structured types for CAA, SSHFP, TLSA, DS, DNSKEY, NAPTR, RP,
LOC, ALIAS, and WebRedirect records
- Export new functions in WASM bindings
- Update TypeScript definitions and loader.js
- Add comprehensive tests for new record types
2025-12-25 12:38:32 +00:00
|
|
|
getDNSRecordTypes: async () => call('pxGetDNSRecordTypes'),
|
|
|
|
|
getDNSRecordTypeInfo: async () => call('pxGetDNSRecordTypeInfo'),
|
|
|
|
|
getCommonDNSRecordTypes: async () => call('pxGetCommonDNSRecordTypes')
|
2025-11-03 20:15:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return api;
|
|
|
|
|
}
|