func (c *Client) CreateMailbox(ctx context.Context) (string, error) {
	nameplateResp, err := c.allocateNameplate(ctx)
	if err != nil {
		c.closeWithError(err)
		return "", err
	}
	claimed, err := c.claimNameplate(ctx, nameplateResp.Nameplate)
	if err != nil {
		c.closeWithError(err)
		return "", err
	}
	c.nameplate = nameplateResp.Nameplate
	err = c.openMailbox(ctx, claimed.Mailbox)
	if err != nil {
		c.closeWithError(err)
		return "", err
	}
	return nameplateResp.Nameplate, nil
}
CreateMailbox allocates a nameplate, claims it, and then opens
the associated mailbox. It returns the nameplate id string.
package rendezvous
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"sync"
	"sync/atomic"
	"github.com/psanford/wormhole-william/internal/crypto"
	"github.com/psanford/wormhole-william/rendezvous/internal/msgs"
	"github.com/psanford/wormhole-william/version"
	"nhooyr.io/websocket"
	"nhooyr.io/websocket/wsjson"
)
func NewClient(url, sideID, appID string, opts ...ClientOption) *Client {
	c := &Client{
		url:         url,
		sideID:      sideID,
		appID:       appID,
		pendingMsgs: make([]pendingMsg, 0, 2),
		mailboxMsgs:           make([]MailboxEvent, 0),
		pendingMailboxWaiters: make(map[uint32]chan int),
		pendingMsgWaiters: make(map[uint32]chan uint32),
	}
	for _, opt := range opts {
		opt.setValue(c)
	}
	return c
}
type pendingMsg struct {
	id      uint32
	msgType string
	raw     []byte
}
type Client struct {
	url       string
	appID     string
	sideID    string
	mailboxID string
	nameplate string
	agentString  string
	agentVersion string
	wsClient *websocket.Conn
	mailboxMsgs           []MailboxEvent
	pendingMailboxWaiters map[uint32]chan int
	pendingMsgIDCntr     uint32
	pendingMsgWaiterCntr uint32
	sendCmdMu sync.Mutex
	pendingMsgMu      sync.Mutex
	pendingMsgs       []pendingMsg
	pendingMsgWaiters map[uint32]chan uint32
	clientState clientState
	err         error
}
type MailboxEvent struct {
	Error error
	Side  string
	Phase string
	Body  string
}
type clientState int32
const (
	statePending clientState = iota
	stateOpen
	stateError
	stateClosed
)
func (c clientState) String() string {
	switch c {
	case statePending:
		return "Pending"
	case stateOpen:
		return "Open"
	case stateError:
		return "Error"
	case stateClosed:
		return "Closed"
	default:
		return fmt.Sprintf("Unknown client state: %d", c)
	}
}
func (c *Client) closeWithError(err error) {
	atomic.StoreInt32((*int32)(&c.clientState), int32(stateError))
	c.err = err
}
type ConnectInfo struct {
	MOTD              string
	CurrentCLIVersion string
}
func (c *Client) Connect(ctx context.Context) (*ConnectInfo, error) {
	swapped := atomic.CompareAndSwapInt32((*int32)(&c.clientState), int32(statePending), int32(stateOpen))
	if !swapped {
		return nil, fmt.Errorf("current client state %s != pending, cannot connect", c.clientState)
	}
	var err error
	c.wsClient, _, err = websocket.Dial(ctx, c.url, nil)
	if err != nil {
		wrappedErr := fmt.Errorf("dial %s: %s", c.url, err)
		c.closeWithError(wrappedErr)
		return nil, wrappedErr
	}
	go c.readMessages(ctx)
	var welcome msgs.Welcome
	err = c.readMsg(ctx, &welcome)
	if err != nil {
		c.closeWithError(err)
		return nil, err
	}
	if welcome.Welcome.Error != "" {
		err := fmt.Errorf("server error: %s", err)
		c.closeWithError(err)
		return nil, err
	}
	if err := c.bind(ctx, c.sideID, c.appID); err != nil {
		c.closeWithError(err)
		return nil, err
	}
	info := ConnectInfo{
		MOTD:              welcome.Welcome.MOTD,
		CurrentCLIVersion: welcome.Welcome.CurrentCLIVersion,
	}
	return &info, nil
}
func (c *Client) searchPendingMsgs(ctx context.Context, msgType string) *pendingMsg {
	c.pendingMsgMu.Lock()
	defer c.pendingMsgMu.Unlock()
	for i, pending := range c.pendingMsgs {
		if pending.msgType == msgType {
			copyMsg := pending
			orig := c.pendingMsgs
			c.pendingMsgs = c.pendingMsgs[:i]
			c.pendingMsgs = append(c.pendingMsgs, orig[i+1:]...)
			return &copyMsg
		}
	}
	return nil
}
func (c *Client) registerWaiter() (uint32, <-chan uint32) {
	nextID := atomic.AddUint32(&c.pendingMsgWaiterCntr, 1)
	ch := make(chan uint32, 1)
	c.pendingMsgMu.Lock()
	defer c.pendingMsgMu.Unlock()
	c.pendingMsgWaiters[nextID] = ch
	if len(c.pendingMsgs) > 0 {
		ch <- c.pendingMsgs[len(c.pendingMsgs)-1].id
	}
	return nextID, ch
}
func (c *Client) deregisterWaiter(id uint32) {
	c.pendingMsgMu.Lock()
	defer c.pendingMsgMu.Unlock()
	delete(c.pendingMsgWaiters, id)
}
func (c *Client) readMsg(ctx context.Context, m interface{}) error {
	expectMsgType := msgType(m)
	waiterID, ch := c.registerWaiter()
	defer c.deregisterWaiter(waiterID)
	for {
		<-ch
		msg := c.searchPendingMsgs(ctx, expectMsgType)
		if msg != nil {
			err := json.Unmarshal(msg.raw, m)
			if err != nil {
				wrappedErr := fmt.Errorf("JSON unmarshal: %s", err)
				return wrappedErr
			}
			return nil
		}
	}
}
func msgType(msg interface{}) string {
	ptr := reflect.TypeOf(msg)
	if ptr.Kind() != reflect.Ptr {
		panic("msg must be a pointer")
	}
	structType := ptr.Elem()
	for i := 0; i < structType.NumField(); i++ {
		field := structType.Field(i)
		if field.Tag.Get("json") == "type" {
			return field.Tag.Get("rendezvous_value")
		}
	}
	return ""
}