Launcher: Add --addon option to CLI

This commit is contained in:
daijro 2024-08-08 04:32:54 -05:00
parent 8e5144abe0
commit 077f6acf47
4 changed files with 217 additions and 37 deletions

137
launcher/addon.go Normal file
View file

@ -0,0 +1,137 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"strconv"
"strings"
)
func getDebugPort(args *[]string) int {
debugPort := parseArgs("-start-debugger-server", "", args, false)
var debugPortInt int
var err error
if debugPort == "" {
// Create new debugger server port
debugPortInt = getOpenPort()
// Add -start-debugger-server {debugPort} to args
*args = append(*args, "-start-debugger-server", strconv.Itoa(debugPortInt))
} else {
debugPortInt, err = strconv.Atoi(debugPort)
if err != nil {
fmt.Printf("Error parsing debug port. Must be an integer: %v\n", err)
os.Exit(1)
}
}
return debugPortInt
}
func confirmPaths(paths []string) {
// Confirm paths are valid
for _, path := range paths {
if _, err := os.Stat(path); err != nil {
fmt.Printf("Error: %s is not a valid addon path.\n", path)
os.Exit(1)
}
}
}
func getOpenPort() int {
// Generate an open port
ln, err := net.Listen("tcp", ":0") // listen on a random port
if err != nil {
return 0
}
defer ln.Close()
addr := ln.Addr().(*net.TCPAddr) // type assert to *net.TCPAddr to get the Port
return addr.Port
}
// Firefox addon loader
// Ported from this Nodejs implementation:
// https://github.com/microsoft/playwright/issues/7297#issuecomment-1211763085
func loadFirefoxAddon(port int, addonPath string) bool {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", "localhost", port))
if err != nil {
return false
}
defer conn.Close()
success := false
send := func(data map[string]string) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "%d:%s", len(jsonData), jsonData)
return err
}
err = send(map[string]string{
"to": "root",
"type": "getRoot",
})
if err != nil {
return false
}
onMessage := func(message map[string]interface{}) bool {
if addonsActor, ok := message["addonsActor"].(string); ok {
err := send(map[string]string{
"to": addonsActor,
"type": "installTemporaryAddon",
"addonPath": addonPath,
})
if err != nil {
return true
}
}
if _, ok := message["addon"]; ok {
success = true
return true
}
if _, ok := message["error"]; ok {
return true
}
return false
}
reader := bufio.NewReader(conn)
for {
lengthStr, err := reader.ReadString(':')
if err != nil {
break
}
length, err := strconv.Atoi(strings.TrimSuffix(lengthStr, ":"))
if err != nil {
break
}
jsonData := make([]byte, length)
_, err = reader.Read(jsonData)
if err != nil {
break
}
var message map[string]interface{}
err = json.Unmarshal(jsonData, &message)
if err != nil {
break
}
if onMessage(message) {
break
}
}
return success
}

View file

@ -13,9 +13,21 @@ import (
)
func main() {
configPath, remainingArgs := parseArgs(os.Args[1:]) // Parse args arguments
args := os.Args[1:]
configMap := readAndParseConfig(configPath) // Read and parse the config file
configPath := parseArgs("--config", "{}", &args, true)
addons := parseArgs("--addons", "[]", &args, true)
// Read and parse the config file
var configMap map[string]interface{}
parseJson(configPath, &configMap)
// If addons are passed, handle them
var addonsList []string
parseJson(addons, &addonsList)
// Confirm addon paths are valid
confirmPaths(addonsList)
userAgentOS := determineUserAgentOS(configMap) // Determine the user agent OS
@ -29,58 +41,47 @@ func main() {
fmt.Printf("Error setting executable permissions: %v\n", err)
os.Exit(1)
}
runCamoufox(execName, remainingArgs)
runCamoufox(execName, args, addonsList)
}
func parseArgs(args []string) (string, []string) {
// Parse the arguments
var configPath string
var remainingArgs []string
for i := 0; i < len(args); i++ {
if args[i] == "--config" {
if i+1 < len(args) {
configPath = args[i+1]
remainingArgs = append(args[:i], args[i+2:]...)
break
} else {
fmt.Println("Error: --config flag requires a value")
os.Exit(1)
}
func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs bool) string {
for i := 0; i < len(*args); i++ {
if (*args)[i] != param {
continue
}
if i+1 < len(*args) {
value := (*args)[i+1]
if removeFromArgs {
*args = append((*args)[:i], (*args)[i+2:]...)
}
return value
}
fmt.Printf("Error: %s flag requires a value\n", param)
os.Exit(1)
}
// If no config data is provided, fallback to an empty object
if configPath == "" {
configPath = "{}"
}
return configPath, remainingArgs
return defaultValue
}
func readAndParseConfig(configInput string) map[string]interface{} {
func parseJson(argv string, target interface{}) {
// Unmarshal the config input into a map
var configData []byte
var data []byte
// Check if the input is a file path or inline JSON
if _, err := os.Stat(configInput); err == nil {
configData, err = os.ReadFile(configInput)
if _, err := os.Stat(argv); err == nil {
data, err = os.ReadFile(argv)
if err != nil {
fmt.Printf("Error reading config file: %v\n", err)
os.Exit(1)
}
} else {
// Assume it's inline JSON
configData = []byte(configInput)
data = []byte(argv)
}
var configMap map[string]interface{}
if err := json.Unmarshal(configData, &configMap); err != nil {
if err := json.Unmarshal(data, target); err != nil {
fmt.Printf("Invalid JSON in config: %v\n", err)
os.Exit(1)
}
return configMap
}
func determineUserAgentOS(configMap map[string]interface{}) string {

View file

@ -4,12 +4,14 @@ import (
"bufio"
"fmt"
"io"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"syscall"
"time"
)
func getExecutableName() string {
@ -66,7 +68,32 @@ func filterOutput(r io.Reader, w io.Writer) {
}
}
func runCamoufox(execName string, args []string) {
func tryLoadAddons(debugPortInt int, addonsList []string) {
// Wait for the server to be open
for {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", debugPortInt))
if err == nil {
conn.Close()
break
}
time.Sleep(10 * time.Millisecond)
}
// Load addons
for _, addon := range addonsList {
loadFirefoxAddon(debugPortInt, addon)
}
}
// Run Camoufox
func runCamoufox(execName string, args []string, addonsList []string) {
// If addons are specified, get the debug port
var debugPortInt int
if len(addonsList) > 0 {
debugPortInt = getDebugPort(&args)
}
// Print args
cmd := exec.Command(execName, args...)
setProcessGroupID(cmd)
@ -91,6 +118,10 @@ func runCamoufox(execName string, args []string) {
os.Exit(1)
}
if len(addonsList) > 0 {
go tryLoadAddons(debugPortInt, addonsList)
}
// Channel to signal when the subprocess has finished
subprocessDone := make(chan struct{})

View file

@ -1,3 +1,16 @@
// Camoufox functionality
pref("gfx.bundled-fonts.activate", 1);
pref("devtools.debugger.remote-enabled", true);
pref("devtools.debugger.prompt-connection", false);
pref("privacy.userContext.enabled", true);
pref("browser.sessionstore.max_resumed_crashes", 0);
pref("browser.sessionstore.restore_on_demand", false);
pref("browser.sessionstore.restore_tabs_lazily", false);
// Debloat and speed up Camoufox.
// Debloat (from Peskyfox)
@ -285,7 +298,6 @@ pref("privacy.partition.network_state", false); // Disable network seperations
pref("accessibility.force_disabled", 1);
pref("browser.sessionstore.max_tabs_undo", 0);
pref("browser.sessionstore.max_windows_undo", 0);
pref("browser.sessionstore.resume_from_crash", false);
pref("browser.sessionstore.resuming_after_os_restart", false);
pref("browser.sessionstore.resume_session_once", false);
pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 0);
@ -406,7 +418,6 @@ pref("userChrome.icon.global_menu", true);
pref("userChrome.icon.global_menubar", true);
pref("userChrome.icon.1-25px_stroke", true);
pref("gfx.bundled-fonts.activate", 1);
// =================================================================
// THESE ARE THE PROPERTIES THAT MUST BE ENABLED FOR JUGGLER TO WORK