{"id":13442311,"url":"https://github.com/binaryage/chromex","last_synced_at":"2025-05-16T13:03:54.715Z","repository":{"id":57713302,"uuid":"46436137","full_name":"binaryage/chromex","owner":"binaryage","description":"Write Chrome Extensions in ClojureScript","archived":false,"fork":false,"pushed_at":"2023-05-01T20:23:08.000Z","size":4952,"stargazers_count":414,"open_issues_count":4,"forks_count":21,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-09T08:05:03.763Z","etag":null,"topics":["chrome-extension","clojurescript"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/binaryage.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-11-18T17:40:37.000Z","updated_at":"2025-01-21T03:03:53.000Z","dependencies_parsed_at":"2024-01-15T04:10:38.139Z","dependency_job_id":null,"html_url":"https://github.com/binaryage/chromex","commit_stats":{"total_commits":1037,"total_committers":7,"mean_commits":"148.14285714285714","dds":0.476374156219865,"last_synced_commit":"27609a7025466d8b9bfbb28baa061ea8f51fb807"},"previous_names":[],"tags_count":46,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryage%2Fchromex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryage%2Fchromex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryage%2Fchromex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryage%2Fchromex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/binaryage","download_url":"https://codeload.github.com/binaryage/chromex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254535826,"owners_count":22087398,"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":["chrome-extension","clojurescript"],"created_at":"2024-07-31T03:01:44.192Z","updated_at":"2025-05-16T13:03:54.696Z","avatar_url":"https://github.com/binaryage.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# chromex [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](license.txt) [![Clojars Project](https://img.shields.io/clojars/v/binaryage/chromex.svg)](https://clojars.org/binaryage/chromex) [![Travis](https://img.shields.io/travis/binaryage/chromex.svg)](https://travis-ci.org/binaryage/chromex) [![Example Projects](https://img.shields.io/badge/project-examples-ff69b4.svg)](https://github.com/binaryage/chromex/tree/master/examples)\n\n## Deprecated\n\nChromex only supports Chrome Extensions v2 which are now end of life.  For basic v3 support, please see [this example](https://github.com/thheller/chrome-ext-v3).\n\nThis library is auto-generated. Current version was **generated on 2020-10-13** from [**Chromium @ 9269f9eb1d98**](https://chromium.googlesource.com/chromium/src.git/+/9269f9eb1d98d29564c2b2ab97f30c6e148c4e11).\n\nLooking for a nightly version? Check out [**nightly branch**](https://github.com/binaryage/chromex/tree/nightly) which gets updated if there are any new API changes.\n\n#### Chromex provides idiomatic ClojureScript interface\n\nFor Chrome Extensions and also for Chrome Apps:\n\n| API family | namespaces | properties | functions | events |\n| --- | --- | --- | --- | --- |\n| [Public Chrome Extension APIs](src/exts) | 81 | 61 | 397 | 180 |\n| [Public Chrome App APIs](src/apps) | 68 | 32 | 450 | 153 |\n| [Private Chrome Extension APIs](src/exts_private) | 40 | 0 | 499 | 84 |\n| [Private Chrome App APIs](src/apps_private) | 37 | 0 | 370 | 79 |\n\nNote: Chromex generator uses the same data source as [developer.chrome.com/extensions/api_index](https://developer.chrome.com/extensions/api_index) and\n[developer.chrome.com/apps/api_index](https://developer.chrome.com/apps/api_index) docs.\n\nFollowing documentation is mostly speaking about Chrome Extension development but the same patterns generally apply to Chrome App development as well.\n\nThis library is data-driven. Given an API namespace, all API methods, properties and events are described in a Clojure map\nalong with their parameters, callbacks, versions and additional metadata ([a simple example - look for `api-table` here](src/exts/chromex/ext/context_menus.clj)).\nChromex then provides a set of macros which consume this table and generate actual ClojureScript code wrapping native APIs.\n\nThese macros can be further parametrized which allows for greater flexibility. Sane defaults\nare provided with following goals:\n\n  * API version checking and deprecation warnings at compile-time\n  * flexible marshalling of Javascript values to ClojureScript and back\n  * callbacks are converted to core.async channels\n  * events are emitted into core.async channels\n\n#### API versions and deprecation warnings\n\nChrome Extension API is evolving. You might want to target multiple Chrome versions with slightly\ndifferent APIs. Good news is that our API data map contains full versioning and deprecation information.\n\nBy default you target the latest APIs. But you can target older API version instead and\nwe will warn you during compilation in case you were accessing any API not yet available in that particular version.\n\nAdditionally we are able to detect calls to deprecated APIs and warn you during compilation.\n\n#### Flexible marshalling\n\nGenerated API data map contains information about all parameters and their types. Chromex provides a pluggable system\nto specify how particular types are marshalled when crossing API boundary.\n\nBy default we marshall only a few types where it makes good sense. We don't want to blindly run all\nparameters through `clj-\u003ejs` and `js-\u003eclj` conversions. That could have unexpected consequences and maybe\nperformance implications. Instead we keep marshalling lean and give you an easy way how to provide your own macro which\ncan optionally generate required marshalling code (during compilation).\n\nThere is also a practical reason. This library is auto-generated and quite large - it would be too laborious to maintain\nhairy marshalling conventions up-to-date with evolving Chrome API index. If you want to provide richer set of\nmarshalling for particular APIs you care about, you [can do that consistently](src/lib/chromex/marshalling.clj).\n\n#### Callbacks as core.async channels\n\nMany Chrome API calls are async in nature and require you to specify a callback (for receiving an answer later).\nYou might want to watch this video explaining [API conventions](https://www.youtube.com/watch?v=bmxr75CV36A) in Chrome.\n\nWe automatically turn all API functions with a callback parameter to a ClojureScript function without that callback parameter\nbut returning a new core.async channel instead (`promise-chan`). The channel eventually receives a vector of parameters passed into the callback.\nWhen an error occurs, the channel closes without receiving any result (you receive `nil`). In that case you can immediately\ncall `chromex.error/get-last-error` to obtain relevant error object (which is what was found in `chrome.runtime.lastError` during the callback).\n\nThis mechanism is pluggable, so you can optionally implement your own mechanism of consuming callback calls.\n\n#### Events are emitted into core.async channels\n\nChrome API namespaces usually provide multiple `event` objects which you can subscribe with `.addListener`.\nYou provide a callback function which will get called with future events as they occur. Later you can call `.removeListener`\nto unsubscribe from the event stream.\n\nWe think consuming events via core.async channels is more natural for ClojureScript developers.\nIn Chromex, you can request Chrome events to be emitted into a core.async channel provided by you.\nAnd then implement a single loop to sequentially process events as they appear on the channel.\n\nAgain this mechanism is pluggable, so you can optionally implement a different mechanism for consuming event streams.\n\n### Usage examples\n\nWe provide an example skeleton Chrome extensions [chromex/examples](https://github.com/binaryage/chromex/tree/master/examples). \nThese projects acts as a code examples but also as a skeleton with project configuration. We recommended to use it as starting \npoint when starting development of your own extension.\n\nPlease refer to [readme in chromex/examples](https://github.com/binaryage/chromex/tree/master/examples) for further \nexplanation and code examples.\n\n### Advanced mode compilation\n\nChromex does not rely on externs file. Instead it is rigorously [using string names](https://github.com/clojure/clojurescript/wiki/Dependencies#using-string-names)\nto access Javascript properties. I would recommend you to do the same in your own extension code. It is not that hard after all. You can use `oget`, `ocall` and `oapply`\nmacros from the [cljs-oops](https://github.com/binaryage/cljs-oops) library, which is designed to work with string names.\n\nNote: There is a [chrome_extensions.js](https://github.com/google/closure-compiler/blob/master/contrib/externs/chrome_extensions.js) externs file available,\nbut that's been updated ad-hoc by the community. It is definitely incomplete and may be incorrect. But of course you are free to include the externs\nfile into your own project and rely on it if it works for your code. It depends on how recent/popular APIs are you going to use.\n\n### Tapping events\n\nLet's say for example you want to subscribe to [tab creation events](https://developer.chrome.com/extensions/tabs#event-onCreated) and\n[web navigation's \"committed\" events](https://developer.chrome.com/extensions/webNavigation#event-onCommitted).\n\n```clojure\n(ns your.project\n  (:require [cljs.core.async :refer [chan close! go-loop]]\n            [chromex.ext.tabs :as tabs]\n            [chromex.ext.web-navigation :as web-navigation]\n            [chromex.chrome-event-channel :refer [make-chrome-event-channel]]))\n\n(let [chrome-event-channel (make-chrome-event-channel (chan))]\n  (tabs/tap-on-created-events chrome-event-channel)\n  (web-navigation/tap-on-committed-events chrome-event-channel (clj-\u003ejs {\"url\" [{\"hostSuffix\" \"google.com\"}]}))\n\n  ; do something with the channel...\n  (go-loop []\n    (when-some [[event-id event-params] (\u003c! chrome-event-channel)]\n      (process-chrome-event event-id event-params)\n      (recur))\n    (println \"leaving main event loop\"))\n\n  ; alternatively\n  (close! chrome-event-channel)) ; this will unregister all chrome event listeners on the channel\n```\n\nAs we wrote in previous sections, by default you consume Chrome events via core.async channels:\n\n 1. first, you have to create/provide a channel of your liking\n 2. then optionally wrap it in `make-chrome-event-channel` call\n 3. then call one or more tap-some-events calls\n 4. then you can process events as they appear on the channel\n\nIf you don't want to use the channel anymore, you should `close!` it.\n\nEvents coming from the channel are pairs `[event-id params]`, where params is a vector of parameters passed into event's\ncallback function. See [chromex/examples](https://github.com/binaryage/chromex/blob/master/examples/lein/src/background/chromex_sample/background/core.cljs)\nfor example usage. Refer to [Chrome's API docs](https://developer.chrome.com/extensions/api_index) for specific event objects.\n\nNote: instead of calling tap-some-events you could call `tap-all-events`. This is a convenience function which will\ntap events on all valid non-deprecated event objects in given namespace. For example `tabs/tap-all-events` will subscribe\nto all existing tabs events in the latest API.\n\n`make-chrome-event-channel` is a convenience wrapper for raw core.async channel. It is aware of event listeners and\nis able to unsubscribe them when channel gets closed. But you are free to remove listeners manually as well,\ntap calls return `ChromeEventSubscription` which gives you an interface to `unsubscribe!` given tap. This way you can\ndynamically add/remove subscriptions on the channel.\n\nTap calls accept not only channel but also more optional arguments. These arguments will be passed into `.addListener` call\nwhen registering Chrome event listener. This is needed for scenarios when event objects accept filters or other additional parameters.\n`web-navigation/tap-on-committed-events` is [an example of such situation](https://developer.chrome.com/extensions/events#filtered).\nEven more complex scenario is registering listeners on some [webRequest API events](https://developer.chrome.com/extensions/webRequest)\n(see 'Registering event listeners' section).\n\n#### Synchronous event listeners\n\nIn some rare cases Chrome event listener has to be synchronous. For example [webRequest's onBeforeRequest event](https://developer.chrome.com/extensions/webRequest#examples)\naccepts \"blocking\" flag which instructs Chrome to wait for listener's answer in a blocking call.\n\nHere is an example how you would do this in chromex:\n\n```clojure\n(ns your.project\n  (:require ...\n            [chromex.config :refer-macros [with-custom-event-listener-factory]]\n            [chromex.chrome-event-channel :refer [make-chrome-event-channel]]\n            [chromex.ext.web-request :as web-request]))\n\n(defn my-event-listener-factory []\n  (fn [\u0026 args]\n    ; do something useful with args...\n    #js [\"return native answer\"])) ; note: this value will be passed back to Chrome as-is, marshalling won't be applied here\n\n...\n(with-custom-event-listener-factory my-event-listener-factory\n  (web-request/tap-on-before-request-events chan (clj-\u003ejs {\"urls\" [\"\u003call_urls\u003e\"]}) #js [\"blocking\"]))\n...\n```\n\nWhat happened here? We have specified our own event listener factory which is responsible for creating a new\nevent callback function whenever chromex asks for it. The default implementation is [here](src/lib/chromex/defaults.cljs).\nThis function is part of our config object, so it can be redefined during runtime.\n[`with-custom-event-listener-factory`](https://github.com/binaryage/chromex/blob/master/src/lib/chromex/config.clj)\nis just a convenience macro to override this config setting temporarily.\n\nThis way we get all benefits of chromex (marshalling, logging, API deprecation/version checking, etc.) but still we\nhave a flexibility to hook our own custom listener code if needed. Please note that event listener has to return a native value.\nWe don't have type information here to do the marshalling automatically. Also note that incoming parameters into\nevent listener get marshalled to ClojureScript (as expected). And obviously this event won't appear on the channel unless you `put!`\nit there in your custom listener code.\n\nAlso note how we passed extra arguments for .addListener call. This was discussed in the previous section.\n\nAdvanced tip: similarly you can replace some other configurable functions in the config object. For example you can change the\nway how callbacks are turned into core.async channels. Theoretically you could replace it with some other mechanism (e.g. with js promises).\n\n### Projects using Chromex\n\n  * [chromex/examples](https://github.com/binaryage/chromex/tree/master/examples) - example projects of Chromex usage\n  * [binaryage/dirac](https://github.com/binaryage/dirac) - a Chrome DevTools fork for ClojureScript developers\n  * [madvas/thai2english-chrome-extension](https://github.com/madvas/thai2english-chrome-extension) - a Chrome extension to translate Thai to English\n  * [jazzytomato/hnlookup](https://github.com/jazzytomato/hnlookup) - a Chrome popup extension to look up pages on Hacker News\n  * [Scout, by Room Key](https://www.roomkey.com/scout) - Chrome extension that finds lower hotel rates as you browse major travel search sites\n\n### Similar libraries\n\n  * [suprematic/khroma](https://github.com/suprematic/khroma)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinaryage%2Fchromex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbinaryage%2Fchromex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinaryage%2Fchromex/lists"}