{"id":13817895,"url":"https://github.com/ascorbic/trackless","last_synced_at":"2026-03-18T00:36:56.188Z","repository":{"id":32478416,"uuid":"134962328","full_name":"ascorbic/trackless","owner":"ascorbic","description":"Add a GDPR-friendly Google Analytics opt-in/opt-out button to your site","archived":false,"fork":false,"pushed_at":"2022-12-09T09:31:59.000Z","size":508,"stargazers_count":135,"open_issues_count":10,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-26T12:54:56.293Z","etag":null,"topics":["analytics","gdpr","privacy"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ascorbic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-26T13:10:03.000Z","updated_at":"2025-08-28T16:37:30.000Z","dependencies_parsed_at":"2023-01-14T21:20:55.202Z","dependency_job_id":null,"html_url":"https://github.com/ascorbic/trackless","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ascorbic/trackless","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Ftrackless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Ftrackless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Ftrackless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Ftrackless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ascorbic","download_url":"https://codeload.github.com/ascorbic/trackless/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ascorbic%2Ftrackless/sbom","scorecard":{"id":211143,"data":{"date":"2025-08-11","repo":{"name":"github.com/ascorbic/trackless","commit":"2363dcacc2eb974aebf84986b31a599ee783adad"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 5 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"62 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-qrmc-fj45-qfc2","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-q42p-pg8m-cqh6","Warn: Project is vulnerable to: GHSA-w457-6q6x-cgp9","Warn: Project is vulnerable to: GHSA-62gr-4qp9-h98f","Warn: Project is vulnerable to: GHSA-f52g-6jhx-586p","Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv","Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8","Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65","Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-4xcv-9jjx-gfj3","Warn: Project is vulnerable to: GHSA-f9cm-qmx5-m98h","Warn: Project is vulnerable to: GHSA-7wpw-2hjm-89gp","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-fhjf-83wg-r2j9","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4g88-fppr-53pp","Warn: Project is vulnerable to: GHSA-4jqc-8m5r-9rpr","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T00:47:18.564Z","repository_id":32478416,"created_at":"2025-08-17T00:47:18.564Z","updated_at":"2025-08-17T00:47:18.564Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30637247,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-18T00:09:27.587Z","status":"ssl_error","status_checked_at":"2026-03-18T00:09:26.123Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["analytics","gdpr","privacy"],"created_at":"2024-08-04T06:00:59.140Z","updated_at":"2026-03-18T00:36:56.164Z","avatar_url":"https://github.com/ascorbic.png","language":"TypeScript","readme":"# Trackless\n\n## Let your users opt-out of Google Analytics\n\nGoogle Analytics is super-useful, but lots of people don't like being tracked.\nThe GDPR says that users should be able to choose whether they share personal\ninformation. This script lets you easily give your site visitors a way of\nopting-out of Google Analytics tracking. This preference is stored in the\nbrowser's localStorage.\n\n![](demo/screencast.gif)\n\nTracking is disabled by setting `window['ga-disable-GA_TRACKING_ID'] = true;`,\nas documented\n[here](https://developers.google.com/analytics/devguides/collection/gtagjs/user-opt-out).\nThis needs to be done before the Google Analytics call is made, so the script\nshould be loaded before the Google Analytics script.\n\n## How to use\n\n[![npm](https://img.shields.io/npm/dt/trackless.svg)](https://www.npmjs.com/package/trackless)\n[![GitHub issues](https://img.shields.io/github/issues/ascorbic/trackless.svg)](https://github.com/ascorbic/trackless/issues)\n\nThe script can either be loaded as a module if you are using a bundler, or\ndirectly from a script tag.\n\nTo load as a package, install from NPM:\n\n```sh\nnpm install trackless\n```\nor\n```sh\nyarn add trackless\n```\nThen in your source file:\n```js\nimport { Trackless } from \"trackless\";\n\nconst trackless = new Trackless({ trackingID: \"MY_TRACKING_ID\" });\ntrackless.bindElements();\n```\n\nWhen loading from a script tag, place it in the head before your GA tag. We use\nthe same method as GA for loading asynchronously, so its safe to load it in the\nhead. You can either load it directly from unpkg.com, or [download the UMD file](https://unpkg.com/trackless) and host it yourself.\n\n```html\n\u003cscript async src=\"//unpkg.com/trackless@1\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    window.TracklessQueue = window.TracklessQueue || [];\n    TracklessQueue.push(\n        function (Trackless) {\n            new Trackless({ trackingID: \"MY_TRACKING_ID\" }).bindElements();\n        }\n    )\n\u003c/script\u003e\n```\n\nBy default it binds to all elements with the class \"trackless\". You can change\nthis by passing a different selector to `bindElements()`. e.g.\n`bindElements(\"button.optOut\")` will bind to all buttons with the class\n\"optOut\". You can click any bound element to toggle the preference. This will\nautomatically update the setting in localStorage, as well as the label for\nitself and all other bound elements. For checkboxes, it updates the checked\nvalue. For other `\u003cinput\u003e` elements it sets the `value` property. For all other\nelements, it sets the innerText.\n\n[Demo](https://unpkg.com/trackless/demo/index.html)\n\n## API\n\n### Options\n\nThe following options can be assed to the constructor. All fields are optional, except trackingID.\n\n| Param          | Type       | Default           | Description                                                                                                                                                                                     |\n| -------------- | ---------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| trackingID     | `string`   | -                 | Your Google Analytics tracking ID. e.g. GA-XX-XXXXX                                                                                                                                             |\n| optOutText     | `string`   | \"Don't track me\"  | The text prompting the user to opt out.                                                                                                                                                         |\n| optInText      | `string`   | \"Enable tracking\" | The text prompting the user to opt in.                                                                                                                                                          |\n| initialOptOut  | `boolean`  | false             | Initial opt-out value if no preference is stored.                                                                                                                                               |\n| overrideOptOut | `boolean`  | false             | Override opt-out value. If true, sets the stored preference to the `initialOptOut` value.  Use this if, for example, you have a logged-in user and want to sync the preference between devices. |\n| callback       | `Function` | -                 | Called when opt-out value is changed. Passed the new opt-out value.                                                                                                                             |\n| invertCheckbox | `boolean`  | false             | Invert the behaviour of checkboxes. By default, a checkbox is checked if the user is not opted-out. Set this to true so that it is checked if the user is opted-out.                            |\n\n### The Trackless object\n\n### constructor(options) \n\n\n| Param   | Type      | Description        |\n| ------- | --------- | ------------------ |\n| options | `Options` | An options object. |\n\n\n### bindElements(selectors)\nAdds onClick listeners to the selected elements, and sets their value according to the current preferences.  \n\n| Param     | Type     | Description                                                                                                 |\n| --------- | -------- | ----------------------------------------------------------------------------------------------------------- |\n| selectors | `string` | The selectors of the elements to bind. Default is `\".trackless\"`, i.e. any element with `class=\"trackless\"` |\n\n### unBindAllElements()\nUnbinds all previously bound elements.\n\n### setPreference(optOut)\nSets the opt-out preference, stores the value in localStorage, and updates all elements. If `callback()` is set then it is invoked with the new value.\n\n| Param  | Type      | Description      |\n| ------ | --------- | ---------------- |\n| optOut | `boolean` | The optOut value |\n\n\n### `static` processQueue(queue)\n\nUsed by the async loader to run queued commands. You shouldn't need to use this yourself unless you are handling your own queue. It should be an array of functions, which are passed the `Trackless` class. The UMD file looks for a `window.TracklessQueue` array, which it passes to this when the script has loaded. If you are loading it as a module you won't get this behaviour, so if you need async processing then this is the way to implement it.\n```js\nTrackless.processQueue(window.TracklessQueue);\n```\n\n| Param | Type    | Description                                                                         |\n| ----- | ------- | ----------------------------------------------------------------------------------- |\n| queue | `array` | An array of callbacks. These are passed the `Trackless` class as the only argument. |\n\n## FAQ\n- **Does this stop the Analytics script from loading?**\nNo. The script still loads. However because the opt-out flag is set, Google does not set a cookie or track the visit.\n- **Should I default to opted-out?** I am not a lawyer, but if you want to be totally sure then yes, pass `initialOptOut: true` in the options and it will default to opted-out, and not track the user unless they positively opt in. \n- **Is this required by the GDPR?** I am not a lawyer. Google allows you to anonymise IP addresses in Analytics (which you should absolutely do), but it still tracks the user via a client ID. It could certainly be argued that this is PII (and I expect some lawyers will be doing just that).","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fascorbic%2Ftrackless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fascorbic%2Ftrackless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fascorbic%2Ftrackless/lists"}