/* New - Creates a binder targeting an existing document determined via an ID. Must provide a store.Type to acquire the document and apply future updates to. */ func New( id string, block store.Type, config Config, errorChan chan<- Error, log log.Modular, stats metrics.Aggregator, ) (Type, error) { binder := impl{ id: id, config: config, otBuffer: text.NewOTBuffer(config.OTBufferConfig), block: block, log: log.NewModule(":binder"), stats: stats, clients: make([]*binderClient, 0), subscribeChan: make(chan subscribeRequest), transformChan: make(chan transformSubmission), messageChan: make(chan messageSubmission), usersRequestChan: make(chan usersRequest), exitChan: make(chan *binderClient), kickChan: make(chan kickRequest), errorChan: errorChan, closedChan: make(chan struct{}), } binder.log.Debugln("Bound to document, attempting flush") if _, err := binder.flush(); err != nil { stats.Incr("binder.new.error", 1) return nil, err } go binder.loop() stats.Incr("binder.new.success", 1) return &binder, nil }
/* WebsocketHandler - Returns a websocket handler that routes new websockets to a curator. Use this with an HTTP server with the "golang.org/x/net/websocket" package. */ func WebsocketHandler( finder curator.Type, timeout time.Duration, logger log.Modular, stats metrics.Aggregator, ) func(ws *websocket.Conn) { return func(ws *websocket.Conn) { var err error var session binder.Portal defer func() { if err != nil { websocket.JSON.Send(ws, leapHTTPServerMessage{ Type: "error", Error: fmt.Sprintf("socket initialization failed: %v", err), }) } if err = ws.Close(); err != nil { logger.Errorf("Failed to close socket: %v\n", err) } stats.Decr("http.open_websockets", 1) }() stats.Incr("http.websocket.opened", 1) stats.Incr("http.open_websockets", 1) for session == nil && err == nil { var clientMsg leapHTTPClientMessage websocket.JSON.Receive(ws, &clientMsg) switch clientMsg.Command { case "create": if clientMsg.Document == nil { err = ErrInvalidDocument } else { session, err = finder.CreateDocument( clientMsg.UserID, clientMsg.Token, *clientMsg.Document, timeout) } case "read": if len(clientMsg.DocID) <= 0 { err = ErrInvalidDocument } else { session, err = finder.ReadDocument( clientMsg.UserID, clientMsg.Token, clientMsg.DocID, timeout) } case "edit": if len(clientMsg.DocID) <= 0 { err = ErrInvalidDocument } else { session, err = finder.EditDocument( clientMsg.UserID, clientMsg.Token, clientMsg.DocID, timeout) } case "ping": // Ignore and continue waiting for init message. default: err = fmt.Errorf( "first command must be init or ping, client sent: %v", clientMsg.Command, ) } } if session != nil && err == nil { version := session.BaseVersion() websocket.JSON.Send(ws, leapHTTPServerMessage{ Type: "document", Document: session.Document(), Version: &version, }) session.ReleaseDocument() // Begin serving websocket IO. serveWebsocketIO(ws, session, timeout, logger, stats) } } }