Poindexter/wasm/main.go
Claude 298791ef95
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

746 lines
21 KiB
Go

//go:build js && wasm
package main
import (
"encoding/json"
"errors"
"fmt"
"syscall/js"
pd "github.com/Snider/Poindexter"
)
// Simple registry for KDTree instances created from JS.
// We keep values as string for simplicity across the WASM boundary.
var (
treeRegistry = map[int]*pd.KDTree[string]{}
nextTreeID = 1
)
func export(name string, fn func(this js.Value, args []js.Value) (any, error)) {
js.Global().Set(name, js.FuncOf(func(this js.Value, args []js.Value) any {
res, err := fn(this, args)
if err != nil {
return map[string]any{"ok": false, "error": err.Error()}
}
return map[string]any{"ok": true, "data": res}
}))
}
func getInt(v js.Value, idx int) (int, error) {
if len := v.Length(); len > idx {
return v.Index(idx).Int(), nil
}
return 0, errors.New("missing integer argument")
}
func getFloatSlice(arg js.Value) ([]float64, error) {
if arg.IsUndefined() || arg.IsNull() {
return nil, errors.New("coords/query is undefined or null")
}
ln := arg.Length()
res := make([]float64, ln)
for i := 0; i < ln; i++ {
res[i] = arg.Index(i).Float()
}
return res, nil
}
func version(_ js.Value, _ []js.Value) (any, error) {
return pd.Version(), nil
}
func hello(_ js.Value, args []js.Value) (any, error) {
name := ""
if len(args) > 0 {
name = args[0].String()
}
return pd.Hello(name), nil
}
func newTree(_ js.Value, args []js.Value) (any, error) {
if len(args) < 1 {
return nil, errors.New("newTree(dim) requires dim")
}
dim := args[0].Int()
if dim <= 0 {
return nil, pd.ErrZeroDim
}
t, err := pd.NewKDTreeFromDim[string](dim)
if err != nil {
return nil, err
}
id := nextTreeID
nextTreeID++
treeRegistry[id] = t
return map[string]any{"treeId": id, "dim": dim}, nil
}
func treeLen(_ js.Value, args []js.Value) (any, error) {
if len(args) < 1 {
return nil, errors.New("len(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
return t.Len(), nil
}
func treeDim(_ js.Value, args []js.Value) (any, error) {
if len(args) < 1 {
return nil, errors.New("dim(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
return t.Dim(), nil
}
func insert(_ js.Value, args []js.Value) (any, error) {
// insert(treeId, {id: string, coords: number[], value?: string})
if len(args) < 2 {
return nil, errors.New("insert(treeId, point)")
}
id := args[0].Int()
pt := args[1]
pid := pt.Get("id").String()
coords, err := getFloatSlice(pt.Get("coords"))
if err != nil {
return nil, err
}
val := pt.Get("value").String()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
okIns := t.Insert(pd.KDPoint[string]{ID: pid, Coords: coords, Value: val})
return okIns, nil
}
func deleteByID(_ js.Value, args []js.Value) (any, error) {
// deleteByID(treeId, id)
if len(args) < 2 {
return nil, errors.New("deleteByID(treeId, id)")
}
id := args[0].Int()
pid := args[1].String()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
return t.DeleteByID(pid), nil
}
func nearest(_ js.Value, args []js.Value) (any, error) {
// nearest(treeId, query:number[]) -> {point, dist, found}
if len(args) < 2 {
return nil, errors.New("nearest(treeId, query)")
}
id := args[0].Int()
query, err := getFloatSlice(args[1])
if err != nil {
return nil, err
}
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
p, d, found := t.Nearest(query)
out := map[string]any{
"point": map[string]any{"id": p.ID, "coords": p.Coords, "value": p.Value},
"dist": d,
"found": found,
}
return out, nil
}
func kNearest(_ js.Value, args []js.Value) (any, error) {
// kNearest(treeId, query:number[], k:int) -> {points:[...], dists:[...]}
if len(args) < 3 {
return nil, errors.New("kNearest(treeId, query, k)")
}
id := args[0].Int()
query, err := getFloatSlice(args[1])
if err != nil {
return nil, err
}
k := args[2].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
pts, dists := t.KNearest(query, k)
jsPts := make([]any, len(pts))
for i, p := range pts {
jsPts[i] = map[string]any{"id": p.ID, "coords": p.Coords, "value": p.Value}
}
return map[string]any{"points": jsPts, "dists": dists}, nil
}
func radius(_ js.Value, args []js.Value) (any, error) {
// radius(treeId, query:number[], r:number) -> {points:[...], dists:[...]}
if len(args) < 3 {
return nil, errors.New("radius(treeId, query, r)")
}
id := args[0].Int()
query, err := getFloatSlice(args[1])
if err != nil {
return nil, err
}
r := args[2].Float()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
pts, dists := t.Radius(query, r)
jsPts := make([]any, len(pts))
for i, p := range pts {
jsPts[i] = map[string]any{"id": p.ID, "coords": p.Coords, "value": p.Value}
}
return map[string]any{"points": jsPts, "dists": dists}, nil
}
func exportJSON(_ js.Value, args []js.Value) (any, error) {
// exportJSON(treeId) -> string (all points)
if len(args) < 1 {
return nil, errors.New("exportJSON(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
// Export all points
points := t.Points()
jsPts := make([]any, len(points))
for i, p := range points {
jsPts[i] = map[string]any{"id": p.ID, "coords": p.Coords, "value": p.Value}
}
m := map[string]any{
"dim": t.Dim(),
"len": t.Len(),
"backend": string(t.Backend()),
"points": jsPts,
}
b, _ := json.Marshal(m)
return string(b), nil
}
func getAnalytics(_ js.Value, args []js.Value) (any, error) {
// getAnalytics(treeId) -> analytics snapshot
if len(args) < 1 {
return nil, errors.New("getAnalytics(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
snap := t.GetAnalyticsSnapshot()
return map[string]any{
"queryCount": snap.QueryCount,
"insertCount": snap.InsertCount,
"deleteCount": snap.DeleteCount,
"avgQueryTimeNs": snap.AvgQueryTimeNs,
"minQueryTimeNs": snap.MinQueryTimeNs,
"maxQueryTimeNs": snap.MaxQueryTimeNs,
"lastQueryTimeNs": snap.LastQueryTimeNs,
"lastQueryAt": snap.LastQueryAt.UnixMilli(),
"createdAt": snap.CreatedAt.UnixMilli(),
"backendRebuildCount": snap.BackendRebuildCnt,
"lastRebuiltAt": snap.LastRebuiltAt.UnixMilli(),
}, nil
}
func getPeerStats(_ js.Value, args []js.Value) (any, error) {
// getPeerStats(treeId) -> array of peer stats
if len(args) < 1 {
return nil, errors.New("getPeerStats(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
stats := t.GetPeerStats()
jsStats := make([]any, len(stats))
for i, s := range stats {
jsStats[i] = map[string]any{
"peerId": s.PeerID,
"selectionCount": s.SelectionCount,
"avgDistance": s.AvgDistance,
"lastSelectedAt": s.LastSelectedAt.UnixMilli(),
}
}
return jsStats, nil
}
func getTopPeers(_ js.Value, args []js.Value) (any, error) {
// getTopPeers(treeId, n) -> array of top n peer stats
if len(args) < 2 {
return nil, errors.New("getTopPeers(treeId, n)")
}
id := args[0].Int()
n := args[1].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
stats := t.GetTopPeers(n)
jsStats := make([]any, len(stats))
for i, s := range stats {
jsStats[i] = map[string]any{
"peerId": s.PeerID,
"selectionCount": s.SelectionCount,
"avgDistance": s.AvgDistance,
"lastSelectedAt": s.LastSelectedAt.UnixMilli(),
}
}
return jsStats, nil
}
func getAxisDistributions(_ js.Value, args []js.Value) (any, error) {
// getAxisDistributions(treeId, axisNames?: string[]) -> array of axis distribution stats
if len(args) < 1 {
return nil, errors.New("getAxisDistributions(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
var axisNames []string
if len(args) > 1 && !args[1].IsUndefined() && !args[1].IsNull() {
ln := args[1].Length()
axisNames = make([]string, ln)
for i := 0; i < ln; i++ {
axisNames[i] = args[1].Index(i).String()
}
}
dists := t.ComputeDistanceDistribution(axisNames)
jsDists := make([]any, len(dists))
for i, d := range dists {
jsDists[i] = map[string]any{
"axis": d.Axis,
"name": d.Name,
"stats": map[string]any{
"count": d.Stats.Count,
"min": d.Stats.Min,
"max": d.Stats.Max,
"mean": d.Stats.Mean,
"median": d.Stats.Median,
"stdDev": d.Stats.StdDev,
"p25": d.Stats.P25,
"p75": d.Stats.P75,
"p90": d.Stats.P90,
"p99": d.Stats.P99,
"variance": d.Stats.Variance,
"skewness": d.Stats.Skewness,
},
}
}
return jsDists, nil
}
func resetAnalytics(_ js.Value, args []js.Value) (any, error) {
// resetAnalytics(treeId) -> resets all analytics
if len(args) < 1 {
return nil, errors.New("resetAnalytics(treeId)")
}
id := args[0].Int()
t, ok := treeRegistry[id]
if !ok {
return nil, fmt.Errorf("unknown treeId %d", id)
}
t.ResetAnalytics()
return true, nil
}
func computeDistributionStats(_ js.Value, args []js.Value) (any, error) {
// computeDistributionStats(distances: number[]) -> distribution stats
if len(args) < 1 {
return nil, errors.New("computeDistributionStats(distances)")
}
distances, err := getFloatSlice(args[0])
if err != nil {
return nil, err
}
stats := pd.ComputeDistributionStats(distances)
return map[string]any{
"count": stats.Count,
"min": stats.Min,
"max": stats.Max,
"mean": stats.Mean,
"median": stats.Median,
"stdDev": stats.StdDev,
"p25": stats.P25,
"p75": stats.P75,
"p90": stats.P90,
"p99": stats.P99,
"variance": stats.Variance,
"skewness": stats.Skewness,
"sampleSize": stats.SampleSize,
"computedAt": stats.ComputedAt.UnixMilli(),
}, nil
}
func computePeerQualityScore(_ js.Value, args []js.Value) (any, error) {
// computePeerQualityScore(metrics: NATRoutingMetrics, weights?: QualityWeights) -> score
if len(args) < 1 {
return nil, errors.New("computePeerQualityScore(metrics)")
}
m := args[0]
metrics := pd.NATRoutingMetrics{
ConnectivityScore: m.Get("connectivityScore").Float(),
SymmetryScore: m.Get("symmetryScore").Float(),
RelayProbability: m.Get("relayProbability").Float(),
DirectSuccessRate: m.Get("directSuccessRate").Float(),
AvgRTTMs: m.Get("avgRttMs").Float(),
JitterMs: m.Get("jitterMs").Float(),
PacketLossRate: m.Get("packetLossRate").Float(),
BandwidthMbps: m.Get("bandwidthMbps").Float(),
NATType: m.Get("natType").String(),
}
var weights *pd.QualityWeights
if len(args) > 1 && !args[1].IsUndefined() && !args[1].IsNull() {
w := args[1]
weights = &pd.QualityWeights{
Latency: w.Get("latency").Float(),
Jitter: w.Get("jitter").Float(),
PacketLoss: w.Get("packetLoss").Float(),
Bandwidth: w.Get("bandwidth").Float(),
Connectivity: w.Get("connectivity").Float(),
Symmetry: w.Get("symmetry").Float(),
DirectSuccess: w.Get("directSuccess").Float(),
RelayPenalty: w.Get("relayPenalty").Float(),
NATType: w.Get("natType").Float(),
}
}
score := pd.PeerQualityScore(metrics, weights)
return score, nil
}
func computeTrustScore(_ js.Value, args []js.Value) (any, error) {
// computeTrustScore(metrics: TrustMetrics) -> score
if len(args) < 1 {
return nil, errors.New("computeTrustScore(metrics)")
}
m := args[0]
metrics := pd.TrustMetrics{
ReputationScore: m.Get("reputationScore").Float(),
SuccessfulTransactions: int64(m.Get("successfulTransactions").Int()),
FailedTransactions: int64(m.Get("failedTransactions").Int()),
AgeSeconds: int64(m.Get("ageSeconds").Int()),
VouchCount: m.Get("vouchCount").Int(),
FlagCount: m.Get("flagCount").Int(),
ProofOfWork: m.Get("proofOfWork").Float(),
}
score := pd.ComputeTrustScore(metrics)
return score, nil
}
func getDefaultQualityWeights(_ js.Value, _ []js.Value) (any, error) {
w := pd.DefaultQualityWeights()
return map[string]any{
"latency": w.Latency,
"jitter": w.Jitter,
"packetLoss": w.PacketLoss,
"bandwidth": w.Bandwidth,
"connectivity": w.Connectivity,
"symmetry": w.Symmetry,
"directSuccess": w.DirectSuccess,
"relayPenalty": w.RelayPenalty,
"natType": w.NATType,
}, nil
}
func getDefaultPeerFeatureRanges(_ js.Value, _ []js.Value) (any, error) {
ranges := pd.DefaultPeerFeatureRanges()
jsRanges := make([]any, len(ranges.Ranges))
for i, r := range ranges.Ranges {
jsRanges[i] = map[string]any{
"min": r.Min,
"max": r.Max,
}
}
return map[string]any{
"ranges": jsRanges,
"labels": pd.StandardFeatureLabels(),
}, nil
}
func normalizePeerFeatures(_ js.Value, args []js.Value) (any, error) {
// normalizePeerFeatures(features: number[], ranges?: FeatureRanges) -> number[]
if len(args) < 1 {
return nil, errors.New("normalizePeerFeatures(features)")
}
features, err := getFloatSlice(args[0])
if err != nil {
return nil, err
}
ranges := pd.DefaultPeerFeatureRanges()
if len(args) > 1 && !args[1].IsUndefined() && !args[1].IsNull() {
rangesArg := args[1].Get("ranges")
if !rangesArg.IsUndefined() && !rangesArg.IsNull() {
ln := rangesArg.Length()
ranges.Ranges = make([]pd.AxisStats, ln)
for i := 0; i < ln; i++ {
r := rangesArg.Index(i)
ranges.Ranges[i] = pd.AxisStats{
Min: r.Get("min").Float(),
Max: r.Get("max").Float(),
}
}
}
}
normalized := pd.NormalizePeerFeatures(features, ranges)
return normalized, nil
}
func weightedPeerFeatures(_ js.Value, args []js.Value) (any, error) {
// weightedPeerFeatures(normalized: number[], weights: number[]) -> number[]
if len(args) < 2 {
return nil, errors.New("weightedPeerFeatures(normalized, weights)")
}
normalized, err := getFloatSlice(args[0])
if err != nil {
return nil, err
}
weights, err := getFloatSlice(args[1])
if err != nil {
return nil, err
}
weighted := pd.WeightedPeerFeatures(normalized, weights)
return weighted, nil
}
// ============================================================================
// DNS Tools Functions
// ============================================================================
func getExternalToolLinks(_ js.Value, args []js.Value) (any, error) {
// getExternalToolLinks(domain: string) -> ExternalToolLinks
if len(args) < 1 {
return nil, errors.New("getExternalToolLinks(domain)")
}
domain := args[0].String()
links := pd.GetExternalToolLinks(domain)
return externalToolLinksToJS(links), nil
}
func getExternalToolLinksIP(_ js.Value, args []js.Value) (any, error) {
// getExternalToolLinksIP(ip: string) -> ExternalToolLinks
if len(args) < 1 {
return nil, errors.New("getExternalToolLinksIP(ip)")
}
ip := args[0].String()
links := pd.GetExternalToolLinksIP(ip)
return externalToolLinksToJS(links), nil
}
func getExternalToolLinksEmail(_ js.Value, args []js.Value) (any, error) {
// getExternalToolLinksEmail(emailOrDomain: string) -> ExternalToolLinks
if len(args) < 1 {
return nil, errors.New("getExternalToolLinksEmail(emailOrDomain)")
}
emailOrDomain := args[0].String()
links := pd.GetExternalToolLinksEmail(emailOrDomain)
return externalToolLinksToJS(links), nil
}
func externalToolLinksToJS(links pd.ExternalToolLinks) map[string]any {
return map[string]any{
"target": links.Target,
"type": links.Type,
// MXToolbox
"mxtoolboxDns": links.MXToolboxDNS,
"mxtoolboxMx": links.MXToolboxMX,
"mxtoolboxBlacklist": links.MXToolboxBlacklist,
"mxtoolboxSmtp": links.MXToolboxSMTP,
"mxtoolboxSpf": links.MXToolboxSPF,
"mxtoolboxDmarc": links.MXToolboxDMARC,
"mxtoolboxDkim": links.MXToolboxDKIM,
"mxtoolboxHttp": links.MXToolboxHTTP,
"mxtoolboxHttps": links.MXToolboxHTTPS,
"mxtoolboxPing": links.MXToolboxPing,
"mxtoolboxTrace": links.MXToolboxTrace,
"mxtoolboxWhois": links.MXToolboxWhois,
"mxtoolboxAsn": links.MXToolboxASN,
// DNSChecker
"dnscheckerDns": links.DNSCheckerDNS,
"dnscheckerPropagation": links.DNSCheckerPropagation,
// Other tools
"whois": links.WhoIs,
"viewdns": links.ViewDNS,
"intodns": links.IntoDNS,
"dnsviz": links.DNSViz,
"securitytrails": links.SecurityTrails,
"shodan": links.Shodan,
"censys": links.Censys,
"builtwith": links.BuiltWith,
"ssllabs": links.SSLLabs,
"hstsPreload": links.HSTSPreload,
"hardenize": links.Hardenize,
// IP-specific
"ipinfo": links.IPInfo,
"abuseipdb": links.AbuseIPDB,
"virustotal": links.VirusTotal,
"threatcrowd": links.ThreatCrowd,
// Email-specific
"mailtester": links.MailTester,
"learndmarc": links.LearnDMARC,
}
}
func getRDAPServers(_ js.Value, _ []js.Value) (any, error) {
// Returns a list of known RDAP servers for reference
servers := map[string]any{
"tlds": map[string]string{
"com": "https://rdap.verisign.com/com/v1/",
"net": "https://rdap.verisign.com/net/v1/",
"org": "https://rdap.publicinterestregistry.org/rdap/",
"info": "https://rdap.afilias.net/rdap/info/",
"io": "https://rdap.nic.io/",
"co": "https://rdap.nic.co/",
"dev": "https://rdap.nic.google/",
"app": "https://rdap.nic.google/",
},
"rirs": map[string]string{
"arin": "https://rdap.arin.net/registry/",
"ripe": "https://rdap.db.ripe.net/",
"apnic": "https://rdap.apnic.net/",
"afrinic": "https://rdap.afrinic.net/rdap/",
"lacnic": "https://rdap.lacnic.net/rdap/",
},
"universal": "https://rdap.org/",
}
return servers, nil
}
func buildRDAPDomainURL(_ js.Value, args []js.Value) (any, error) {
// buildRDAPDomainURL(domain: string) -> string
if len(args) < 1 {
return nil, errors.New("buildRDAPDomainURL(domain)")
}
domain := args[0].String()
// Use universal RDAP redirector
return fmt.Sprintf("https://rdap.org/domain/%s", domain), nil
}
func buildRDAPIPURL(_ js.Value, args []js.Value) (any, error) {
// buildRDAPIPURL(ip: string) -> string
if len(args) < 1 {
return nil, errors.New("buildRDAPIPURL(ip)")
}
ip := args[0].String()
return fmt.Sprintf("https://rdap.org/ip/%s", ip), nil
}
func buildRDAPASNURL(_ js.Value, args []js.Value) (any, error) {
// buildRDAPASNURL(asn: string) -> string
if len(args) < 1 {
return nil, errors.New("buildRDAPASNURL(asn)")
}
asn := args[0].String()
// Normalize ASN
asnNum := asn
if len(asn) > 2 && (asn[:2] == "AS" || asn[:2] == "as") {
asnNum = asn[2:]
}
return fmt.Sprintf("https://rdap.org/autnum/%s", asnNum), nil
}
func getDNSRecordTypes(_ js.Value, _ []js.Value) (any, error) {
// Returns all available DNS record types
types := pd.GetAllDNSRecordTypes()
result := make([]string, len(types))
for i, t := range types {
result[i] = string(t)
}
return result, nil
}
func getDNSRecordTypeInfo(_ js.Value, _ []js.Value) (any, error) {
// Returns detailed info about all DNS record types
info := pd.GetDNSRecordTypeInfo()
result := make([]any, len(info))
for i, r := range info {
result[i] = map[string]any{
"type": string(r.Type),
"name": r.Name,
"description": r.Description,
"rfc": r.RFC,
"common": r.Common,
}
}
return result, nil
}
func getCommonDNSRecordTypes(_ js.Value, _ []js.Value) (any, error) {
// Returns only commonly used DNS record types
types := pd.GetCommonDNSRecordTypes()
result := make([]string, len(types))
for i, t := range types {
result[i] = string(t)
}
return result, nil
}
func main() {
// Export core API
export("pxVersion", version)
export("pxHello", hello)
export("pxNewTree", newTree)
export("pxTreeLen", treeLen)
export("pxTreeDim", treeDim)
export("pxInsert", insert)
export("pxDeleteByID", deleteByID)
export("pxNearest", nearest)
export("pxKNearest", kNearest)
export("pxRadius", radius)
export("pxExportJSON", exportJSON)
// Export analytics API
export("pxGetAnalytics", getAnalytics)
export("pxGetPeerStats", getPeerStats)
export("pxGetTopPeers", getTopPeers)
export("pxGetAxisDistributions", getAxisDistributions)
export("pxResetAnalytics", resetAnalytics)
export("pxComputeDistributionStats", computeDistributionStats)
// Export NAT routing / peer quality API
export("pxComputePeerQualityScore", computePeerQualityScore)
export("pxComputeTrustScore", computeTrustScore)
export("pxGetDefaultQualityWeights", getDefaultQualityWeights)
export("pxGetDefaultPeerFeatureRanges", getDefaultPeerFeatureRanges)
export("pxNormalizePeerFeatures", normalizePeerFeatures)
export("pxWeightedPeerFeatures", weightedPeerFeatures)
// Export DNS tools API
export("pxGetExternalToolLinks", getExternalToolLinks)
export("pxGetExternalToolLinksIP", getExternalToolLinksIP)
export("pxGetExternalToolLinksEmail", getExternalToolLinksEmail)
export("pxGetRDAPServers", getRDAPServers)
export("pxBuildRDAPDomainURL", buildRDAPDomainURL)
export("pxBuildRDAPIPURL", buildRDAPIPURL)
export("pxBuildRDAPASNURL", buildRDAPASNURL)
export("pxGetDNSRecordTypes", getDNSRecordTypes)
export("pxGetDNSRecordTypeInfo", getDNSRecordTypeInfo)
export("pxGetCommonDNSRecordTypes", getCommonDNSRecordTypes)
// Keep running
select {}
}