arblant
firefox-vpn-client / core / internal / desktop / host.go
host.go85 lines · 2.5 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
package desktop

import (
	"context"
	"encoding/json"
	"time"

	"github.com/armanlife/firefox-vpn-client/core/internal/service"
)

// Code shared by the desktop processes that host the service in-process:
// the tray icon (-tray), the desktop app's core process (-app), and the
// terminal status screen when the proxy runs in its window.

// statusPollTimeout bounds one status request.
const statusPollTimeout = 5 * time.Second

// pollStatus reads the service status through the local API.
func pollStatus(client *service.Client) (service.TrayStatus, error) {
	ctx, cancel := context.WithTimeout(context.Background(), statusPollTimeout)
	defer cancel()
	return client.Status(ctx)
}

// reachable reports whether a copy of the program answers the local API.
func reachable(client *service.Client, timeout time.Duration) bool {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	_, err := client.Status(ctx)
	return err == nil
}

// newHostedDaemon creates the service to run in this process. exit ends
// the process (POST /api/app/exit, which also makes /api/status report
// can_exit), and the dashboard's "Open folder" opens the file manager here.
func newHostedDaemon(paths service.DataPaths, verbose bool, exit func()) (*service.Daemon, error) {
	d, err := service.NewDaemon(paths, verbose)
	if err != nil {
		return nil, err
	}
	d.ExitApp = exit
	d.OpenFolder = openBrowser
	d.SystemProxySupported = systemProxySupported()
	return d, nil
}

// daemonTrayStatus reads the status of the daemon in this process as
// GET /api/status gives it, without the local API (which needs the
// dashboard).
func daemonTrayStatus(d *service.Daemon) (service.TrayStatus, error) {
	view, err := d.StatusView()
	if err != nil {
		return service.TrayStatus{}, err
	}
	data, err := json.Marshal(view)
	if err != nil {
		return service.TrayStatus{}, err
	}
	var st service.TrayStatus
	err = json.Unmarshal(data, &st)
	return st, err
}

// follow applies the system proxy mode from the status that poll reads
// every interval until ctx ends; a poll that fails counts as a stopped
// service, so this program's setting is removed. The app mode and the
// status screen's own proxy use it; the tray applies the mode from its own
// polling instead.
func (s *systemProxySync) follow(ctx context.Context, poll func() (service.TrayStatus, error), interval time.Duration) {
	if !systemProxySupported() {
		return
	}
	for {
		if st, err := poll(); err != nil {
			s.update(nil)
		} else {
			s.update(&st)
		}
		select {
		case <-ctx.Done():
			return
		case <-time.After(interval):
		}
	}
}