package foundry import ( "database/sql" "errors" "fmt" "io" "log/slog" "net/http" "net/url" "strconv" "strings" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" "github.com/gorilla/websocket" ) type Foundry struct { //TODO: make check of admin's authentication isAuth bool logger *slog.Logger config requests.Config ws *webSocketUtil } func NewFoundry() *Foundry { foundry := &Foundry{config: requests.Config{SessionID: ""}, isAuth: false, ws: NewWebSocketUtil()} // err := foundry.config.GetSessionId() // if err != nil { // return nil, err // } return foundry } func (foundry *Foundry) checkAuthRespAnswer(r io.Reader, respLength int) error { authBody := make([]byte, respLength) _, err := r.Read(authBody) if err != nil && err != io.EOF { return err } if !strings.Contains(string(authBody), "Admin authentication successful") { return ErrorAuthPassWrong } foundry.isAuth = true return nil } func (foundry *Foundry) Authenticate() error { if foundry.config.SessionID == "" { err := foundry.config.GetSessionId() if err != nil { return err } } resp, err := foundry.config.PostAuthenticationData() if err != nil { return err } defer resp.Body.Close() contentLen, err := strconv.Atoi(resp.Header.Get("Content-Length")) if err != nil { return err } return foundry.checkAuthRespAnswer(resp.Body, contentLen) } func (foundry *Foundry) StartListen() error { foundry.ws.channels = *types.InitWsChannels() wsChannels := &foundry.ws.channels defer wsChannels.Close() go foundry.ListenAndServeWS() var err error for { select { // case msg, ok := <-foundry.ws.channels.Msg(): // if !ok { // time.Sleep(5 * time.Microsecond) // continue // } // var foundryState *json_model.FoundryState // foundryState, err = json_model.ParseSetupModel(msg.ToByteSlice()) // if err != nil { // return nil // } // foundry.models.FoundryState.Insert(foundryState) case err = <-wsChannels.Err(): var foundryErr *FoundryError if errors.As(err, &foundryErr) { if foundryErr.IsFatal { foundry.CloseWebSocketConn() return foundryErr } foundry.logger.Warn("Got error when listening or served websocket", "err", err.Error()) } case <-wsChannels.Done(): foundry.CloseWebSocketConn() return ListenIsDone } } } func (foundry *Foundry) ConnectToFoundry() error { var err error if foundry == nil { return ErrorFoundryNotInit } ok, err := (*foundry).config.GetSessionToken() if err != nil { return err } if (*foundry).config.Password != "" && !ok { err := (*foundry).Authenticate() if err != nil { return err } } foundry.logger.Info("Successefully connected to Foundry", "host", foundry.config.Host) return nil } func (foundry *Foundry) ConnectToWebSocket() error { if !foundry.isAuth { return ErrorNotAuth } query := url.Values{} query.Add("session", foundry.config.SessionID) query.Add("EIO", "4") query.Add("transport", "websocket") u := url.URL{Scheme: "ws", Host: foundry.config.Host, Path: fmt.Sprintf("%s/", requests.SocketPath), RawQuery: query.Encode()} wsHeader := http.Header{} wsHeader.Set("Cookie", fmt.Sprintf("session=%s", foundry.config.SessionID)) wsConn, _, err := websocket.DefaultDialer.Dial(u.String(), wsHeader) if err != nil { return err } foundry.ws.wsConn = wsConn foundry.ws.currWsId = 0 foundry.logger.Info("Successefully connected to Foundry Websocket") return nil } func (foundry *Foundry) CloseWebSocketConn() { foundry.ws.wsConn.Close() } func (foundry *Foundry) ListenAndServeWS() { go foundry.ws.ListenWebSocket() foundry.ws.ServeWebSocket() } func (foundry *Foundry) HandleWSRequest(msgType string) ([]byte, error) { msg := foundry.ws.CreateWSMessageByPage(msgType) return foundry.ws.HandleWebsocketRequest(msg) } func (foundry *Foundry) HasSessionId() bool { return foundry.config.SessionID != "" } func (foundry *Foundry) SetUpSessionId() error { return foundry.config.GetSessionId() } func (foundry *Foundry) SetConfig(config *requests.Config) { foundry.config = *config } func (foundry *Foundry) SetLogger(slogger *slog.Logger) { foundry.logger = slogger foundry.ws.logger = slogger } func (foundry *Foundry) CreateNewDataModels(db *sql.DB) { foundry.ws.CreateNewDataModels(db) } // /** // * Poll the server to see if a World has become active, and automatically refresh if so. // * @returns {Promise} // */ // async #pollActive() { // const poll = () => { // const status = foundry.utils.getRoute("/api/status"); // this.#activePollState.last = Date.now(); // this.#activePollState.polling = foundry.utils.fetchJsonWithTimeout(status, {}, { // timeoutMs: 10000 // }).catch(() => {}); // this.#pollActive(); // };