2025-11-03 20:15:55 +00:00
|
|
|
//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
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-02 01:27:55 +00:00
|
|
|
// WasmError provides a structured error for WASM responses.
|
|
|
|
|
type WasmError struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *WasmError) Error() string {
|
|
|
|
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard error codes.
|
|
|
|
|
const (
|
|
|
|
|
ErrCodeBadRequest = "bad_request"
|
|
|
|
|
ErrCodeNotFound = "not_found"
|
|
|
|
|
ErrCodeConflict = "conflict"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func newErr(code, msg string) *WasmError {
|
|
|
|
|
return &WasmError{Code: code, Message: msg}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newErrf(code, format string, args ...any) *WasmError {
|
|
|
|
|
return &WasmError{Code: code, Message: fmt.Sprintf(format, args...)}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 20:15:55 +00:00
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
var wasmErr *WasmError
|
|
|
|
|
if errors.As(err, &wasmErr) {
|
|
|
|
|
return map[string]any{
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": map[string]any{"code": wasmErr.Code, "message": wasmErr.Message},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Fallback for generic errors
|
|
|
|
|
return map[string]any{
|
|
|
|
|
"ok": false,
|
|
|
|
|
"error": map[string]any{"code": ErrCodeBadRequest, "message": err.Error()},
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
return map[string]any{"ok": true, "data": res}
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 01:27:55 +00:00
|
|
|
func getInt(args []js.Value, idx int, name string) (int, error) {
|
|
|
|
|
if len(args) > idx {
|
|
|
|
|
return args[idx].Int(), nil
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
return 0, newErrf(ErrCodeBadRequest, "missing integer argument: %s", name)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 01:27:55 +00:00
|
|
|
func getFloatSlice(arg js.Value, name string) ([]float64, error) {
|
2025-11-03 20:15:55 +00:00
|
|
|
if arg.IsUndefined() || arg.IsNull() {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeBadRequest, "argument is undefined or null: %s", name)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
ln := arg.Length()
|
|
|
|
|
res := make([]float64, ln)
|
|
|
|
|
for i := 0; i < ln; i++ {
|
|
|
|
|
res[i] = arg.Index(i).Float()
|
|
|
|
|
}
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 01:27:55 +00:00
|
|
|
// pointToJS converts a KDPoint to a JS-friendly map, ensuring slices are []any.
|
|
|
|
|
func pointToJS(p pd.KDPoint[string]) map[string]any {
|
|
|
|
|
coords := make([]any, len(p.Coords))
|
|
|
|
|
for i, c := range p.Coords {
|
|
|
|
|
coords[i] = c
|
|
|
|
|
}
|
|
|
|
|
return map[string]any{
|
|
|
|
|
"id": p.ID,
|
|
|
|
|
"coords": coords,
|
|
|
|
|
"value": p.Value,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 20:15:55 +00:00
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "newTree(dim) requires 'dim' argument")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
dim := args[0].Int()
|
|
|
|
|
if dim <= 0 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, pd.ErrZeroDim.Error())
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
t, err := pd.NewKDTreeFromDim[string](dim)
|
|
|
|
|
if err != nil {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, err.Error())
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
id := nextTreeID
|
|
|
|
|
nextTreeID++
|
|
|
|
|
treeRegistry[id] = t
|
|
|
|
|
return map[string]any{"treeId": id, "dim": dim}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func treeLen(_ js.Value, args []js.Value) (any, error) {
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
return t.Len(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func treeDim(_ js.Value, args []js.Value) (any, error) {
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
return t.Dim(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func insert(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// insert(treeId, {id: string, coords: number[], value?: string})
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
if len(args) < 2 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "insert(treeId, point) requires 'point' argument")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
pt := args[1]
|
|
|
|
|
pid := pt.Get("id").String()
|
2026-02-02 01:27:55 +00:00
|
|
|
coords, err := getFloatSlice(pt.Get("coords"), "point.coords")
|
2025-11-03 20:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
val := pt.Get("value").String()
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
if okIns := t.Insert(pd.KDPoint[string]{ID: pid, Coords: coords, Value: val}); !okIns {
|
|
|
|
|
return nil, newErr(ErrCodeConflict, "failed to insert point: dimension mismatch or duplicate ID")
|
|
|
|
|
}
|
|
|
|
|
return true, nil
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func deleteByID(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// deleteByID(treeId, id)
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
if len(args) < 2 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "deleteByID(treeId, id) requires 'id' argument")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
pid := args[1].String()
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
|
|
|
|
}
|
|
|
|
|
if !t.DeleteByID(pid) {
|
|
|
|
|
return nil, newErrf(ErrCodeNotFound, "point with id '%s' not found", pid)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
return true, nil
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func nearest(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// nearest(treeId, query:number[]) -> {point, dist, found}
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
if len(args) < 2 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "nearest(treeId, query) requires 'query' argument")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
query, err := getFloatSlice(args[1], "query")
|
2025-11-03 20:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
p, d, found := t.Nearest(query)
|
|
|
|
|
out := map[string]any{
|
2026-02-02 01:27:55 +00:00
|
|
|
"point": pointToJS(p),
|
2025-11-03 20:15:55 +00:00
|
|
|
"dist": d,
|
|
|
|
|
"found": found,
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func kNearest(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// kNearest(treeId, query:number[], k:int) -> {points:[...], dists:[...]}
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
if len(args) < 3 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "kNearest(treeId, query, k) requires 'query' and 'k' arguments")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
query, err := getFloatSlice(args[1], "query")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
k, err := getInt(args, 2, "k")
|
2025-11-03 20:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
pts, dists := t.KNearest(query, k)
|
|
|
|
|
jsPts := make([]any, len(pts))
|
|
|
|
|
for i, p := range pts {
|
2026-02-02 01:27:55 +00:00
|
|
|
jsPts[i] = pointToJS(p)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
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:[...]}
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
if len(args) < 3 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "radius(treeId, query, r) requires 'query' and 'r' arguments")
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
query, err := getFloatSlice(args[1], "query")
|
2025-11-03 20:15:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
r := args[2].Float()
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
pts, dists := t.Radius(query, r)
|
|
|
|
|
jsPts := make([]any, len(pts))
|
|
|
|
|
for i, p := range pts {
|
2026-02-02 01:27:55 +00:00
|
|
|
jsPts[i] = pointToJS(p)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
return map[string]any{"points": jsPts, "dists": dists}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func exportJSON(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// exportJSON(treeId) -> string (all points)
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-11-03 20:15:55 +00:00
|
|
|
}
|
2025-12-25 12:18:18 +00:00
|
|
|
// Export all points
|
|
|
|
|
points := t.Points()
|
|
|
|
|
jsPts := make([]any, len(points))
|
|
|
|
|
for i, p := range points {
|
2026-02-02 01:27:55 +00:00
|
|
|
jsPts[i] = pointToJS(p)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
m := map[string]any{
|
|
|
|
|
"dim": t.Dim(),
|
|
|
|
|
"len": t.Len(),
|
|
|
|
|
"backend": string(t.Backend()),
|
|
|
|
|
"points": jsPts,
|
|
|
|
|
}
|
2025-11-03 20:15:55 +00:00
|
|
|
b, _ := json.Marshal(m)
|
|
|
|
|
return string(b), nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-25 12:18:18 +00:00
|
|
|
func getAnalytics(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// getAnalytics(treeId) -> analytics snapshot
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
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
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
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
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
n, err := getInt(args, 1, "n")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
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
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2026-02-02 01:27:55 +00:00
|
|
|
id, err := getInt(args, 0, "treeId")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t, ok := treeRegistry[id]
|
|
|
|
|
if !ok {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErrf(ErrCodeNotFound, "unknown treeId %d", id)
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
t.ResetAnalytics()
|
|
|
|
|
return true, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func computeDistributionStats(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// computeDistributionStats(distances: number[]) -> distribution stats
|
|
|
|
|
if len(args) < 1 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "computeDistributionStats(distances) requires 'distances' argument")
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
distances, err := getFloatSlice(args[0], "distances")
|
2025-12-25 12:18:18 +00:00
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "computePeerQualityScore(metrics) requires 'metrics' argument")
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "computeTrustScore(metrics) requires 'metrics' argument")
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "normalizePeerFeatures(features) requires 'features' argument")
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
features, err := getFloatSlice(args[0], "features")
|
2025-12-25 12:18:18 +00:00
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "weightedPeerFeatures(normalized, weights) requires 'normalized' and 'weights' arguments")
|
2025-12-25 12:18:18 +00:00
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
normalized, err := getFloatSlice(args[0], "normalized")
|
2025-12-25 12:18:18 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-02-02 01:27:55 +00:00
|
|
|
weights, err := getFloatSlice(args[1], "weights")
|
2025-12-25 12:18:18 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
weighted := pd.WeightedPeerFeatures(normalized, weights)
|
|
|
|
|
return weighted, nil
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// ============================================================================
|
|
|
|
|
// DNS Tools Functions
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
func getExternalToolLinks(_ js.Value, args []js.Value) (any, error) {
|
|
|
|
|
// getExternalToolLinks(domain: string) -> ExternalToolLinks
|
|
|
|
|
if len(args) < 1 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "getExternalToolLinks(domain) requires 'domain' argument")
|
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
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "getExternalToolLinksIP(ip) requires 'ip' argument")
|
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
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "getExternalToolLinksEmail(emailOrDomain) requires 'emailOrDomain' argument")
|
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
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "buildRDAPDomainURL(domain) requires 'domain' argument")
|
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
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "buildRDAPIPURL(ip) requires 'ip' argument")
|
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
|
|
|
}
|
|
|
|
|
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 {
|
2026-02-02 01:27:55 +00:00
|
|
|
return nil, newErr(ErrCodeBadRequest, "buildRDAPASNURL(asn) requires 'asn' argument")
|
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
|
|
|
}
|
|
|
|
|
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) {
|
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
|
|
|
// 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
|
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
|
|
|
}
|
|
|
|
|
|
2025-11-03 20:15:55 +00:00
|
|
|
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)
|
|
|
|
|
|
2025-12-25 12:18:18 +00:00
|
|
|
// 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)
|
|
|
|
|
|
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
|
|
|
// 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)
|
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
|
|
|
export("pxGetDNSRecordTypeInfo", getDNSRecordTypeInfo)
|
|
|
|
|
export("pxGetCommonDNSRecordTypes", getCommonDNSRecordTypes)
|
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
|
|
|
|
2025-11-03 20:15:55 +00:00
|
|
|
// Keep running
|
|
|
|
|
select {}
|
|
|
|
|
}
|