mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-02 10:45:57 +00:00
157 lines
3.7 KiB
Go
157 lines
3.7 KiB
Go
package ngrok
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"time"
|
|
|
|
"golang.ngrok.com/ngrok"
|
|
"golang.ngrok.com/ngrok/config"
|
|
)
|
|
|
|
// NewService creates a new ngrok service instance
|
|
func NewService() *Service {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
return &Service{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
info: TunnelInfo{
|
|
Status: StatusDisconnected,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Start initiates a new ngrok tunnel
|
|
func (s *Service) Start(authToken string, localPort int) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.info.Status == StatusConnected || s.info.Status == StatusConnecting {
|
|
return ErrAlreadyRunning
|
|
}
|
|
|
|
s.info.Status = StatusConnecting
|
|
s.info.Error = ""
|
|
s.info.LocalURL = fmt.Sprintf("http://127.0.0.1:%d", localPort)
|
|
|
|
// Start tunnel in a goroutine
|
|
go func() {
|
|
if err := s.startTunnel(authToken, localPort); err != nil {
|
|
s.mu.Lock()
|
|
s.info.Status = StatusError
|
|
s.info.Error = err.Error()
|
|
s.mu.Unlock()
|
|
log.Printf("[ERROR] Ngrok tunnel failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// startTunnel creates and maintains the ngrok tunnel
|
|
func (s *Service) startTunnel(authToken string, localPort int) error {
|
|
// Create local URL for forwarding
|
|
localURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localPort))
|
|
if err != nil {
|
|
return fmt.Errorf("invalid local port: %w", err)
|
|
}
|
|
|
|
// Create forwarder that automatically handles the tunnel and forwarding
|
|
forwarder, err := ngrok.ListenAndForward(s.ctx, localURL, config.HTTPEndpoint(), ngrok.WithAuthtoken(authToken))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create ngrok tunnel: %w", err)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.forwarder = forwarder
|
|
s.info.URL = forwarder.URL()
|
|
s.info.Status = StatusConnected
|
|
s.info.ConnectedAt = time.Now()
|
|
s.mu.Unlock()
|
|
|
|
log.Printf("[INFO] Ngrok tunnel established: %s -> http://127.0.0.1:%d", forwarder.URL(), localPort)
|
|
|
|
// Wait for the forwarder to close
|
|
return forwarder.Wait()
|
|
}
|
|
|
|
// Stop terminates the ngrok tunnel
|
|
func (s *Service) Stop() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.info.Status == StatusDisconnected {
|
|
return ErrNotConnected
|
|
}
|
|
|
|
// Cancel context to stop all operations
|
|
s.cancel()
|
|
|
|
// Close forwarder if it exists
|
|
if s.forwarder != nil {
|
|
if err := s.forwarder.Close(); err != nil {
|
|
log.Printf("[WARNING] Error closing ngrok forwarder: %v", err)
|
|
}
|
|
s.forwarder = nil
|
|
}
|
|
|
|
// Reset status
|
|
s.info.Status = StatusDisconnected
|
|
s.info.URL = ""
|
|
s.info.Error = ""
|
|
s.info.ConnectedAt = time.Time{}
|
|
|
|
// Create new context for potential restart
|
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
|
|
|
log.Printf("[INFO] Ngrok tunnel stopped")
|
|
return nil
|
|
}
|
|
|
|
// GetStatus returns the current tunnel status
|
|
func (s *Service) GetStatus() StatusResponse {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return StatusResponse{
|
|
TunnelInfo: s.info,
|
|
IsRunning: s.info.Status == StatusConnected || s.info.Status == StatusConnecting,
|
|
}
|
|
}
|
|
|
|
// IsRunning returns true if the tunnel is active
|
|
func (s *Service) IsRunning() bool {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.info.Status == StatusConnected || s.info.Status == StatusConnecting
|
|
}
|
|
|
|
// GetURL returns the public tunnel URL
|
|
func (s *Service) GetURL() string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.info.URL
|
|
}
|
|
|
|
// SetConfig updates the ngrok configuration
|
|
func (s *Service) SetConfig(config Config) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.config = config
|
|
}
|
|
|
|
// GetConfig returns the current configuration
|
|
func (s *Service) GetConfig() Config {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.config
|
|
}
|
|
|
|
// Cleanup performs cleanup when the service is being destroyed
|
|
func (s *Service) Cleanup() {
|
|
if err := s.Stop(); err != nil && err != ErrNotConnected {
|
|
log.Printf("[WARNING] Error during ngrok cleanup: %v", err)
|
|
}
|
|
}
|