func New(ctx context.Context, server io.Server, formatter Formatter, cfg Config) *echo.Echo {
	ctx = log.NewContextWithField(ctx, "namespace", "gatewayserver/io/ws")
	webServer := echo.New()
	webServer.Logger = web.NewNoopLogger()
	webServer.HTTPErrorHandler = errorHandler
	webServer.Use(
		middleware.ID(""),
		echomiddleware.BodyLimit("16M"),
		middleware.Log(log.FromContext(ctx)),
		ratelimit.EchoMiddleware(server.RateLimiter(), "gs:accept:ws"),
		middleware.Recover(),
	)
	s := &srv{
		ctx:       ctx,
		server:    server,
		upgrader:  &websocket.Upgrader{},
		webServer: webServer,
		formatter: formatter,
		cfg:       cfg,
	}
	eps := s.formatter.Endpoints()
	webServer.GET(eps.ConnectionInfo, s.handleConnectionInfo)
	webServer.GET(eps.Traffic, s.handleTraffic)
	go func() {
		<-ctx.Done()
		webServer.Close()
	}()
	return webServer
}
New creates a new WebSocket frontend.
package ws
import (
	"context"
	"encoding/hex"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"
	"github.com/gorilla/websocket"
	echo "github.com/labstack/echo/v4"
	echomiddleware "github.com/labstack/echo/v4/middleware"
	"go.thethings.network/lorawan-stack/v3/pkg/auth/rights"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/frequencyplans"
	"go.thethings.network/lorawan-stack/v3/pkg/gatewayserver/io"
	"go.thethings.network/lorawan-stack/v3/pkg/log"
	"go.thethings.network/lorawan-stack/v3/pkg/ratelimit"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
	"go.thethings.network/lorawan-stack/v3/pkg/types"
	"go.thethings.network/lorawan-stack/v3/pkg/unique"
	"go.thethings.network/lorawan-stack/v3/pkg/web"
	"go.thethings.network/lorawan-stack/v3/pkg/web/middleware"
	"google.golang.org/grpc/metadata"
)
var (
	errGatewayID      = errors.DefineInvalidArgument("invalid_gateway_id", "invalid gateway ID `{id}`")
	errNoAuthProvided = errors.DefineUnauthenticated("no_auth_provided", "no auth provided `{uid}`")
)
type srv struct {
	ctx                  context.Context
	server               io.Server
	webServer            *echo.Echo
	upgrader             *websocket.Upgrader
	useTrafficTLSAddress bool
	wsPingInterval       time.Duration
	cfg                  Config
	formatter            Formatter
}
func (s *srv) Protocol() string            { return "ws" }
func (s *srv) SupportsDownlinkClaim() bool { return false }