This commit is contained in:
Hadi
2026-08-27 23:14:56 +02:00
commit e9c2a59ee5
41 changed files with 3680 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
package backend
import (
"bytes"
"context"
"errors"
"fmt"
"os/exec"
"strings"
"time"
)
const DefaultTimeout = 15 * time.Second
var BinaryName = "protonvpn"
var ErrTimeout = errors.New("protonvpn: command timeout")
var ErrNotFound = errors.New("protonvpn: binary not found in PATH")
type Result struct {
Stdout string
Stderr string
}
type CommandError struct {
Result *Result
Err error
}
const unexpectedErrorBanner = "An unexpected error occurred. Please try again."
func (e *CommandError) Error() string {
if strings.HasPrefix(e.Result.Stderr, unexpectedErrorBanner) ||
strings.HasPrefix(e.Result.Stdout, unexpectedErrorBanner) {
return unexpectedErrorBanner
}
if e.Result.Stderr != "" {
return fmt.Sprintf("%v (stderr: %q)", e.Err, e.Result.Stderr)
}
if e.Result.Stdout != "" {
return e.Result.Stdout
}
return e.Err.Error()
}
func (e *CommandError) Unwrap() error {
return e.Err
}
func IsAuthRequired(err error) bool {
var cmdErr *CommandError
if !errors.As(err, &cmdErr) {
return false
}
return strings.Contains(cmdErr.Result.Stderr, "protonvpn signin") ||
strings.Contains(cmdErr.Result.Stdout, "protonvpn signin")
}
func IsFreePlanRestricted(err error) bool {
var cmdErr *CommandError
if !errors.As(err, &cmdErr) {
return false
}
return strings.Contains(cmdErr.Result.Stderr, "not available on the free plan") ||
strings.Contains(cmdErr.Result.Stdout, "not available on the free plan")
}
func Available() error {
if _, err := exec.LookPath(BinaryName); err != nil {
return ErrNotFound
}
return nil
}
func Run(ctx context.Context, args ...string) (*Result, error) {
if ctx == nil {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
}
cmd := exec.CommandContext(ctx, BinaryName, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
runErr := cmd.Run()
res := &Result{
Stdout: strings.TrimSpace(stdout.String()),
Stderr: strings.TrimSpace(stderr.String()),
}
if ctx.Err() == context.DeadlineExceeded {
return res, &CommandError{Result: res, Err: ErrTimeout}
}
if runErr != nil {
return res, &CommandError{Result: res, Err: runErr}
}
if strings.HasPrefix(res.Stdout, "Error:") {
return res, &CommandError{Result: res, Err: errors.New(res.Stdout)}
}
return res, nil
}