{"id":13595371,"url":"https://github.com/amirkarimi/browser-hub","last_synced_at":"2025-12-24T02:24:06.107Z","repository":{"id":234590346,"uuid":"789202327","full_name":"amirkarimi/browser-hub","owner":"amirkarimi","description":"A Browser wrapper that allows you run multiple browser instances hassle-free","archived":false,"fork":false,"pushed_at":"2024-06-14T23:48:58.000Z","size":140,"stargazers_count":36,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-11T15:55:21.865Z","etag":null,"topics":["browser","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amirkarimi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-19T23:10:31.000Z","updated_at":"2024-06-14T23:43:36.000Z","dependencies_parsed_at":"2024-08-01T16:39:41.402Z","dependency_job_id":"afec5582-e847-43ff-9f44-4735042c65e4","html_url":"https://github.com/amirkarimi/browser-hub","commit_stats":null,"previous_names":["amirkarimi/browser-hub"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirkarimi%2Fbrowser-hub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirkarimi%2Fbrowser-hub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirkarimi%2Fbrowser-hub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amirkarimi%2Fbrowser-hub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amirkarimi","download_url":"https://codeload.github.com/amirkarimi/browser-hub/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230066795,"owners_count":18167545,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser","python"],"created_at":"2024-08-01T16:01:48.799Z","updated_at":"2025-12-24T02:24:06.034Z","avatar_url":"https://github.com/amirkarimi.png","language":"Python","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"\u003e [!NOTE]  \n\u003e This program is tested on Ubuntu 22.04 and mainly Chrome but same concept\n\u003e applies to other OSes and browsers.\n\n## Problem\n\nAs consultants, we usually work with multiple clients. Separating the work isn't\ndifficult until you start using the browser. For example, they might have\ndifferent Google/Outlook accounts. Add to that your personal account, and it\nquickly becomes a mess to handle in the same browser. Another thing is the\nbrowser extensions. Imagine you and all your clients are using\n[Toggl](https://toggl.com/) extension to track the time, and you should use a\ndifferent account for each.\n\nYou would be able to use multiple profiles in Chrome and Firefox. But there's\nanother problem. Let's say you click on a link from outside the browser (e.g.,\nVSCode). In which profile should it be opened?\n\n## Solution\n\nThe solution is a program that replaces the default browser. It then decides\nwhich browser instance to open based on the domain or a keyword in the URL.\n\n\u003cimg src=\"./browser-hub-diagram.png\" style=\"width: 5in;\"/\u003e\n\nThis is a good start but not enough. At least in Chrome, the window classes\n(`wm_class` in xorg) of different profile instances are the same. So, even if\nyou open a separate window for each profile, they are all grouped together. It\nbecomes harder and harder to distinguish them when switching windows.\n\nThe final piece is using separate user directory in Chrome (via\n`--user-data-dir`). This way Chrome creates a different window class for each\nprofile. We can enhance it further by creating a dedicated launcher and icon.\nHere is an example `.desktop` file:\n\n```\n[Desktop Entry]\nVersion=1.1\nType=Application\nName=Chrome - Client1\nComment=Browser profile for Client1\nIcon=/home/amir/Documents/Icons/web-browser-yellow.svg\nExec=google-chrome --user-data-dir=/home/amir/.config/google-chrome/Client1 %U\nCategories=Network;\nStartupWMClass=google-chrome (/home/amir/.config/google-chrome/Client1)\n```\n\nSpecifying `StartupWMClass` lets the launcher know which profile is currently\nopen. For example, when I open the default and the client-1's browser, the\nlauncher shows this:\n\n\u003cimg src=\"./browsers.png\" style=\"width: 5in;\"/\u003e\n\n## Install\n\n1. Install Browser Hub: `pipx install browser-hub`\n2. [Configure](#configuration) it\n3. Set Browser Hub as your default browser\n\nTo test it, you can run Browser Hub in your terminal:\n\n```\nbrowser-hub {url}\n```\n\n## Configuration\n\nCreate the config folder:\n\n```\nmkdir -p ~/.config/browser-hub\n```\n\nCreate `config.json` file. Here's an example for Chrome:\n\n```json\n{\n  \"default_browser_open_cmd\": \"google-chrome {url}\",\n  \"profiles\": [\n    {\n      \"name\": \"Client1\",\n      \"url_patterns\": [\"client1-domain1\", \"client1-domain2\"],\n      \"browser\": {\n        \"open_cmd\": \"google-chrome --user-data-dir=/home/amir/.config/google-chrome/Client1 \\\"{url}\\\"\",\n        \"process_names\": [\"chrome\"],\n        \"cmd_includes_regex\": \"--user-data-dir=.+google-chrome/Client1\",\n        \"cmd_excludes_regex\": \"--type=renderer\"\n      },\n      \"url_transformers\": [\n        {\n          \"keywords\": [\"/client1-org1\", \"/client1-org2\", \"/client1-org3\"],\n          \"from_url_regex\": \"http(s)?://(.*\\\\.)?github.com\",\n          \"to_url\": \"https://ghe.client1-on-prem.com\"\n        }\n      ]\n    },\n    {\n      \"name\": \"Client2\",\n      \"url_patterns\": [\"client2-domain\"],\n      \"browser\": {\n        \"open_cmd\": \"google-chrome --user-data-dir=/home/amir/.config/google-chrome/Client2 \\\"{url}\\\"\",\n        \"process_names\": [\"chrome\"],\n        \"cmd_includes_regex\": \"--user-data-dir=.+google-chrome/Client1\",\n        \"cmd_excludes_regex\": \"--type=renderer\"\n      },\n      \"url_transformers\": []\n    }\n  ],\n  \"profile_specific_urls\": [\n    \"amazon.com\",\n    \"github.com\",\n    \".google.com\",\n    \"datadoghq.com\",\n    \"sentry.io\",\n    \"lucid.app\"\n  ]\n}\n```\n\nA few examples based on this config:\n\n| Source URL                            | Target URL                                         | Profile                                     |\n| ------------------------------------- | -------------------------------------------------- | ------------------------------------------- |\n| https://www.client1-domain1.com       | Same                                               | Client 1                                    |\n| https://www.client2-domain.com        | Same                                               | Client 2                                    |\n| https://console.amazon.com            | Same                                               | Active Profile based on the running process |\n| https://github.com/client1-org1/repo1 | https://ghe.client1-on-prem.com/client1-org1/repo1 | Client 1                                    |\n| https://news.ycombinator.com/         | Same                                               | Default Chrome Profile                      |\n\n### Firefox Config Example\n\nYou first need to create firefox profiles (in this example: `client-1`,\n`client-2`), then use the following config example:\n\n```json\n{\n  \"default_browser_open_cmd\": \"firefox {url}\",\n  \"profiles\": [\n    {\n      \"name\": \"Client 1\",\n      \"browser\": {\n        \"open_cmd\": \"firefox -P client-1 --class client-1 \\\"{url}\\\"\",\n        \"process_names\": [\"firefox\"],\n        \"cmd_includes_regex\": \"-P client-1\"\n      },\n      \"url_patterns\": [\"client1-domain1\", \"client1-domain2\"]\n    },\n    {\n      \"name\": \"Client 2\",\n      \"browser\": {\n        \"open_cmd\": \"firefox -P client-2 --class client-2 \\\"{url}\\\"\",\n        \"process_names\": [\"firefox\"],\n        \"cmd_includes_regex\": \"-P client-2\"\n      },\n      \"url_patterns\": [\"client2-domain1\", \"client2-domain2\"]\n    }\n  ],\n  \"profile_specific_urls\": [\n    \"amazon.com\",\n    \"github.com\",\n    \".google.com\",\n    \"datadoghq.com\",\n    \"sentry.io\"\n  ]\n}\n```\n\n### Options\n\n- `default_browser_open_cmd`: The shell command to run the default browser when\n  no profile is matching the opened URL. Normally it should be set to your\n  personal profile.\n- `profiles`: [Array] Profiles of your clients.\n  - `name`: Name of the profile.\n  - `url_patterns`: [Array] This profile will be opened if the URL contains any\n    of these patterns. Usually set to the client-specific domains.\n  - `browser`: Browser information specific to this profile.\n    - `open_cmd`: The shell command to open this client profile browser.\n    - `process_names`: [Array] Name of the browser process used to determine\n      whether a profile is active.\n    - `cmd_includes_regex`: A regular expression to distinguish this profile\n      browser processes.\n    - `cmd_excludes_regex`: The processes with a command line that matches this\n      regular expression will be ignored. Useful to exclude the browser process\n      when running in the background.\n  - `url_transformers`: [Array] Transforming a URL to another one. e.g. Mapping\n    GitHub actions to the enterprise on-prem GitHub instance.\n    - `keywords`: [Array] Keywords that trigger the transformer if found in the\n      URL.\n    - `from_url_regex`: Matching regular expression that specifies what to\n      replace in the URL (regex groups).\n    - `to_url`: The URL will be replaced by this string. Backreferences, such as\n      `\\6`, are replaced with the substring matched by group 6 in the\n      `from_url_regex`.\n- `profile_specific_urls`: [Array] If the URL contains any of the specified\n  items in this field, Browser Hub will check whether any profile-specific\n  browser is running, and open the URL in that browser instance. If more than\n  one profile are running, one of them will be selected randomly (usually the\n  one that was run first). This is useful for cases where we can't recognize the\n  profile from the URL (e.g. Google Docs).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirkarimi%2Fbrowser-hub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famirkarimi%2Fbrowser-hub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famirkarimi%2Fbrowser-hub/lists"}