Skip to content
samcarter edited this page May 15, 2025 · 2 revisions

ℹ️ Finicky v4 is new, and this page might contain mistakes. If you notice one, please edit the page or let the developer know by opening an issue, creating a discussion or reaching out in other ways.

Table of Contents

General options

export default {
  defaultBrowser: "Google Chrome",
  options: {
    // Check for updates. Default: true
    checkForUpdates: true,
    // Log every request to file. Default: false
    logRequests: false,
  },
  rewrite: [
    {
      match: "example.org/*",
      url: "http://example.com",
    },
  ],
  handlers: [
    {
      match: "apple.com/*",
      browser: "Safari",
    },
  ],
};

Matching urls

URL matching is a core feature of Finicky that allows you to route different URLs to different browsers or rewrite them. Here are the different ways to match URLs:

Matching Methods

  1. String matching with wildcards:
match: "http://example.com/*"; // Matches any path after example.com
match: "*.example.com/*"; // Matches any subdomain of example.com
  1. Regular Expression matching:

More powerful than wildcard string matching, but also more complex to read and write.

match: /^https?:\/\/example\.com/.*$/  // Matches http or https URLs from example.com
match: /\.(dev|test)\.example\.com$/   // Matches specific subdomains
  1. Function-based matching:
match: (url) => url.host === "example.com";
match: (url) => url.pathname.startsWith("/api/");
  1. Combined matching:
match: [
  "https://example.com",
  /^http:\/\/example.(org|com)\/.*/,
  (url) => url.pathname.includes("/hello"),
];

Selecting a browser

Finicky provides multiple ways to specify which browser should handle a URL:

1. Simple Browser Selection

export default {
  defaultBrowser: "Safari",
  handlers: [
    {
      match: (url) => url.host.endsWith("example.org"),
      browser: "Firefox",
    },
  ],
};

2. Browser Selection Methods

A. Using browser names or bundle IDs:

browser: "Google Chrome"; // By app name
browser: "com.google.Chrome"; // By bundle ID
browser: "/Applications/Firefox.app"; // By app path

B. Using browser profiles (Chromium browsers):

browser: "Google Chrome:Personal"     // Profile name after colon
browser: {
  name: "Google Chrome",
  profile: "Personal"
}

C. Advanced browser configuration:

browser: {
  name: "Google Chrome",
  appType: "appName",     // Force name type: "appName", "bundleId" or "appPath"
  openInBackground: true, // Open in background instead of foreground
  profile: "Work",
}

D. Dynamic browser selection:

browser: (url) => {
  if (url.host === "example.com") return "Google Chrome";
  if (url.host === "work.example.com") return "Firefox";
  return "Safari";
};

// Or with dynamic background opening:
browser: (url) => ({
  name: "Google Chrome",
  openInBackground: url.href.includes("facebook"),
});

Rewriting urls

URL rewriting allows you to modify URLs before they are opened. Here are the different ways to rewrite URLs:

export default {
  rewrite: [
    // Simple string replacement
    {
      match: "example.org/*",
      url: "https://gitlab.com",
    },

    // Using URL instance
    {
      match: "example.org/*",
      url: new URL("https://example.com"),
    },

    // Dynamic URL modification
    {
      match: "example.org/*",
      url: (url) => {
        url.pathname = "/hello";
        return url;
      },
    },

    // Complex URL transformation
    {
      match: /^https?:\/\/old\.example\.com\/(.*)/,
      url: (url) => {
        const newUrl = new URL("https://new.example.com");
        newUrl.pathname = url.pathname;
        newUrl.search = url.search;
        return newUrl;
      },
    },
  ],
};

Type checking

If your text editor / IDE supports type checking, you can enable it like this.

JavaScript

// @ts-check

/**
 * @typedef {import('/Applications/Finicky.app/Contents/Resources/finicky.d.ts').FinickyConfig} FinickyConfig
 */

/**
 * @type {FinickyConfig}
 */
export default {
  // ... your configuration here
};

TypeScript

// ~/.finicky.ts
import type { FinickyConfig } from "/Applications/Finicky.app/Contents/Resources/finicky.d.ts";

export default {
  // ... your configuration here
} satisfies FinickyConfig;

Function Parameters

All function callbacks (matcher, browser, and url) receive the same parameters:

type CallbackParams = {
  url: URL; // The URL to be handled
  options: {
    opener: {
      name: string; // Name of the app that opened the URL
      bundleId: string; // Bundle ID of the opener
      path: string; // Path to the opener app
    } | null;
  };
};

Utility Functions

Finicky provides several utility functions to help with configuration:

// Logging
console.log("Log message to console");
console.warn("Log warning message to console");
console.error("Log error message to console");

// URL matching
finicky.matchHostnames(["example.com", "*.example.org"]);

// System information
const modifiers = finicky.getModifierKeys(); // Get current modifier key states
// Returns: {
//   shift: boolean,     // Is shift key pressed?
//   option: boolean,    // Is option/alt key pressed?
//   command: boolean,   // Is command key pressed?
//   control: boolean,   // Is control key pressed?
//   capsLock: boolean,  // Is caps lock on?
//   fn: boolean        // Is fn key pressed?
// }

const systemInfo = finicky.getSystemInfo(); // Get system information
// Returns: {
//   localizedName: string,  // Localized system name
//   name: string           // System name
// }

const powerInfo = finicky.getPowerInfo(); // Get power/battery information
// Returns: {
//   isCharging: boolean,    // Is device charging?
//   isConnected: boolean,   // Is power connected?
//   percentage: number | null  // Battery percentage (null if not available)
// }

// App state
const isRunning = finicky.isAppRunning("Google Chrome"); // Check if an app is running
// Returns: boolean  // true if the app is running, false otherwise

Complete Example Configuration

Here's a comprehensive example showing various Finicky features:

export default {
  defaultBrowser: "Google Chrome",
  options: {
    checkForUpdates: true,
    logRequests: true,
  },

  // URL rewriting rules
  rewrite: [
    {
      match: "old.example.com/*",
      url: "https://new.example.com",
    },
    {
      match: /^https?:\/\/redirect\.example\.com\/(.*)/,
      url: (url) => {
        const newUrl = new URL("https://target.example.com");
        newUrl.pathname = url.pathname;
        return newUrl;
      },
    },
  ],

  // Browser selection rules
  handlers: [
    {
      match: "work.example.com/*",
      browser: {
        name: "Google Chrome",
        profile: "Work",
        openInBackground: true,
      },
    },
    {
      match: "personal.example.com/*",
      browser: "Google Chrome:Personal",
    },
    {
      match: "safari.example.com/*",
      browser: "Safari",
    },
    {
      match: (url) => url.host.endsWith(".dev"),
      browser: "Firefox",
    },
  ],
};