{"id":13397834,"url":"https://github.com/akamai/boomerang","last_synced_at":"2025-10-22T21:29:02.293Z","repository":{"id":2363423,"uuid":"3327272","full_name":"akamai/boomerang","owner":"akamai","description":"End user oriented web performance testing and beaconing","archived":false,"fork":true,"pushed_at":"2024-10-03T18:58:55.000Z","size":40004,"stargazers_count":1878,"open_issues_count":32,"forks_count":294,"subscribers_count":96,"default_branch":"master","last_synced_at":"2025-01-08T15:17:53.846Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://akamai.github.io/boomerang/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"bluesmoon/boomerang","license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akamai.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}},"created_at":"2012-02-01T17:46:18.000Z","updated_at":"2025-01-08T06:38:36.000Z","dependencies_parsed_at":"2023-07-10T11:48:07.664Z","dependency_job_id":null,"html_url":"https://github.com/akamai/boomerang","commit_stats":{"total_commits":2042,"total_committers":60,"mean_commits":34.03333333333333,"dds":0.7296767874632712,"last_synced_commit":"3fe199709116f25293ecf08eee8ef05a877949b8"},"previous_names":["soasta/boomerang"],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akamai%2Fboomerang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akamai%2Fboomerang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akamai%2Fboomerang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akamai%2Fboomerang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akamai","download_url":"https://codeload.github.com/akamai/boomerang/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235542253,"owners_count":19006820,"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":[],"created_at":"2024-07-30T18:01:47.904Z","updated_at":"2025-10-06T18:32:20.977Z","avatar_url":"https://github.com/akamai.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","🛠️ Developer Tools"],"sub_categories":[],"readme":"**boomerang always comes back, except when it hits something.**\n\n# Summary\n\n[![Join the chat at https://gitter.im/SOASTA/boomerang](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SOASTA/boomerang)\n\nboomerang is a JavaScript library that measures the page load time experienced by\nreal users, commonly called RUM (Real User Measurement).  It has the ability to\nsend this data back to your server for further analysis.  With boomerang, you\nfind out exactly how fast your users think your site is.\n\nApart from page load time, boomerang measures performance timings, metrics and\ncharacteristics of your user's web browsing experience.  All you have to do is\ninclude it in your web pages and call the `BOOMR.init()` method.  Once the\nperformance data is captured, it will be beaconed to your chosen URL.\n\nboomerang is designed to be a performant and flexible library that can be adapted\nto your site's needs.  It has an extensive plugin architecture, and works with\nboth traditional and modern websites (including Single Page Apps).\n\nboomerang's goal is to not affect the load time of the page (avoiding the\n[Observer Effect](https://en.wikipedia.org/wiki/Observer_effect_(information_technology))).\nIt can be loaded in an asynchronous way that will not delay the page load even\nif `boomerang.js` is unavailable.\n\n# Features\n\n* Supports:\n  * IE 6+, Edge, all major versions of Firefox, Chrome, Opera, and Safari\n  * Desktop and mobile devices\n* Captures (all optional):\n  * Page characteristics such as the URL and Referrer\n  * Overall page load times (via [NavigationTiming](https://www.w3.org/TR/navigation-timing/) if available)\n  * DNS, TCP, Request and Response timings (via [NavigationTiming](https://www.w3.org/TR/navigation-timing/))\n  * Browser characteristics such as screen size, orientation, memory usage, visibility state\n  * DOM characteristics such as the number of nodes, HTML length, number of images, scripts, etc\n  * [ResourceTiming](https://www.w3.org/TR/resource-timing-1/) data (to reconstruct the page's Waterfall)\n  * Bandwidth\n  * Mobile connection data\n  * DNS latency\n  * JavaScript Errors\n  * XMLHttpRequest instrumentation\n  * Third-Party analytics providers IDs\n  * Single Page App interactions\n\n# Usage\n\nboomerang can be included on your page in one of two ways: [synchronously](#synchronously) or [asynchronously](#asynchronously).\n\nThe asynchronous method is recommended.\n\n\u003ca name=\"synchronously\"\u003e\u003c/a\u003e\n\n## The simple synchronous way\n\n```html\n\u003cscript src=\"boomerang.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"plugins/rt.js\"\u003e\u003c/script\u003e\n\u003c!-- any other plugins you want to include --\u003e\n\u003cscript\u003e\n  BOOMR.init({\n    beacon_url: \"http://yoursite.com/beacon/\"\n  });\n\u003c/script\u003e\n```\n\n**Note:** You must include at least one plugin (it doesn't have to be `RT`) or\nelse the beacon will never fire.\n\nEach plugin has its own configuration as well -- these configuration options\nshould be included in the `BOOMR.init()` call:\n\n```javascript\nBOOMR.init({\n  beacon_url: \"http://yoursite.com/beacon/\",\n  ResourceTiming: {\n    enabled: true,\n    clearOnBeacon: true\n  }\n});\n```\n\n\u003ca name=\"asynchronously\"\u003e\u003c/a\u003e\n\n## The faster, more involved, asynchronous way\n\nLoading boomerang asynchronously ensures that even if `boomerang.js` is\nunavailable (or loads slowly), your host page will not be affected.\n\n### 1. Add a plugin to init your code\n\nCreate a plugin (or use the sample `zzz-last-plugin.js`) with a call\nto `BOOMR.init`:\n\n```javascript\nBOOMR.init({\n  config: parameters,\n  ...\n});\nBOOMR.t_end = new Date().getTime();\n```\n\nYou could also include any other code you need.  For example, you could include\na timer to measure when boomerang has finished loading (as above).\n\n### 2. Build boomerang\n\nThe [build process](#documentation) bundles `boomerang.js` and all of the plugins\nlisted in `plugins.json` (in that order).\n\nTo build boomerang with all of your desired plugins, you would run:\n\n```bash\ngrunt clean build\n```\n\nThis creates a deployable boomerang in the `build` directory, e.g. `build/boomerang-\u003cversion\u003e.min.js`.\n\nInstall this file on your web server or origin server where your CDN can pick it\nup.  Set a far future\n[max-age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)\nheader for it.  This file will never change.\n\n### 3. Asynchronously include the script on your page\n\nThere are two methods of asynchronously including boomerang on your page: by\nadding it to your main document, or via the IFRAME/Preload method.\n\nThe former method could block your `onload` event (affecting the measured\nperformance of your page), so the later method is recommended.\n\n\u003ca name=\"asynchronously-document\"\u003e\u003c/a\u003e\n\n#### 3.1. Adding it to the main document\n\nInclude the following code at the _top_ of your HTML document:\n\n```javascript\n\u003cscript\u003e\n(function(d, s) {\n  var js = d.createElement(s),\n      sc = d.getElementsByTagName(s)[0];\n\n  js.src=\"http://your-cdn.host.com/path/to/boomerang-\u003cversion\u003e.js\";\n  sc.parentNode.insertBefore(js, sc);\n}(document, \"script\"));\n\u003c/script\u003e\n```\n\nBest practices will suggest including all scripts at the bottom of your page.\nHowever, that only applies to scripts that block downloading of other resources.\n\nIncluding a script this way will not block other resources, however it _will_\nblock `onload`.\n\nIncluding the script at the top of your page gives it a good chance of loading\nbefore the rest of your page does, thereby reducing the probability of it\nblocking the `onload` event.\n\nIf you don't want to block `onload` either, use the following IFRAME/Preload method:\n\n\u003ca name=\"asynchronously-iframe\"\u003e\u003c/a\u003e\n\n#### 3.2. Adding it via an IFRAME/Preload\n\nThe method described in 3.1 will still block `onload` on most browsers.\n\nTo avoid blocking `onload`, we can load boomerang in an asynchronous IFRAME or via LINK preload (for browsers that support\nit). The general process is documented on in\n[this blog post](https://calendar.perfplanet.com/2018/a-csp-compliant-non-blocking-script-loader/).\n\nFor boomerang, the asynchronous loader snippet you'll use is:\n\n```javascript\n\u003cscript\u003e\n(function() {\n  // Boomerang Loader Snippet version 15\n  if (window.BOOMR \u0026\u0026 (window.BOOMR.version || window.BOOMR.snippetExecuted)) {\n    return;\n  }\n\n  window.BOOMR = window.BOOMR || {};\n  window.BOOMR.snippetStart = new Date().getTime();\n  window.BOOMR.snippetExecuted = true;\n  window.BOOMR.snippetVersion = 15;\n\n  // NOTE: Set Boomerang URL here\n  window.BOOMR.url = \"\";\n\n  // document.currentScript is supported in all browsers other than IE\n  var where = document.currentScript || document.getElementsByTagName(\"script\")[0],\n      // Parent element of the script we inject\n      parentNode = where.parentNode,\n      // Whether or not Preload method has worked\n      promoted = false,\n      // How long to wait for Preload to work before falling back to iframe method\n      LOADER_TIMEOUT = 3000;\n\n  // Tells the browser to execute the Preloaded script by adding it to the DOM\n  function promote() {\n    if (promoted) {\n      return;\n    }\n\n    var script = document.createElement(\"script\");\n\n    script.id = \"boomr-scr-as\";\n    script.src = window.BOOMR.url;\n\n    // Not really needed since dynamic scripts are async by default and the script is already in cache at this point,\n    // but some naive parsers will see a missing async attribute and think we're not async\n    script.async = true;\n\n    parentNode.appendChild(script);\n\n    promoted = true;\n  }\n\n  // Non-blocking iframe loader (fallback for non-Preload scenarios) for all recent browsers.\n  // For IE 6/7/8, falls back to dynamic script node.\n  function iframeLoader(wasFallback) {\n    promoted = true;\n\n    var dom,\n        doc = document,\n        bootstrap, iframe, iframeStyle,\n        win = window;\n\n    window.BOOMR.snippetMethod = wasFallback ? \"if\" : \"i\";\n\n    // Adds Boomerang within the iframe\n    bootstrap = function(parent, scriptId) {\n      var script = doc.createElement(\"script\");\n\n      script.id = scriptId || \"boomr-if-as\";\n      script.src = window.BOOMR.url;\n\n      BOOMR_lstart = new Date().getTime();\n\n      parent = parent || doc.body;\n      parent.appendChild(script);\n    };\n\n    // For IE 6/7/8, we'll just load the script in the current frame:\n    // * IE 6/7 don't support 'about:blank' for an iframe src (it triggers warnings on secure sites)\n    // * IE 8 required a doc write call for it to work, which is bad practice\n    // This means loading on IE 6/7/8 may cause SPoF.\n    if (!window.addEventListener \u0026\u0026 window.attachEvent \u0026\u0026 navigator.userAgent.match(/MSIE [678]\\./)) {\n      window.BOOMR.snippetMethod = \"s\";\n\n      bootstrap(parentNode, \"boomr-async\");\n\n      return;\n    }\n\n    // The rest of this function is for browsers that don't support Preload hints but will work with CSP \u0026 iframes\n    iframe = document.createElement(\"IFRAME\");\n\n    // An empty frame\n    iframe.src = \"about:blank\";\n\n    // We set title and role appropriately to play nicely with screen readers and other assistive technologies\n    iframe.title = \"\";\n    iframe.role = \"presentation\";\n\n    // Ensure we're not loaded lazily\n    iframe.loading = \"eager\";\n\n    // Hide the iframe\n    iframeStyle = (iframe.frameElement || iframe).style;\n    iframeStyle.width = 0;\n    iframeStyle.height = 0;\n    iframeStyle.border = 0;\n    iframeStyle.display = \"none\";\n\n    // Append to the end of the current block\n    parentNode.appendChild(iframe);\n\n    // Try to get the iframe's document object\n    try {\n      win = iframe.contentWindow;\n      doc = win.document.open();\n    }\n    catch (e) {\n      // document.domain has been changed and we're on an old version of IE, so we got an access denied.\n      // Note: the only browsers that have this problem also do not have CSP support.\n\n      // Get document.domain of the parent window\n      dom = document.domain;\n\n      // Set the src of the iframe to a JavaScript URL that will immediately set its document.domain\n      // to match the parent.\n      // This lets us access the iframe document long enough to inject our script.\n      // Our script may need to do more domain massaging later.\n      iframe.src = \"javascript:var d=document.open();d.domain='\" + dom + \"';void 0;\";\n      win = iframe.contentWindow;\n\n      doc = win.document.open();\n    }\n\n    // document.domain hasn't changed, regular method should be OK\n    win._boomrl = function() {\n      bootstrap();\n    };\n\n    if (win.addEventListener) {\n      win.addEventListener(\"load\", win._boomrl, false);\n    }\n    else if (win.attachEvent) {\n      win.attachEvent(\"onload\", win._boomrl);\n    }\n\n    // Finish the document\n    doc.close();\n  }\n\n  // See if Preload is supported or not\n  var link = document.createElement(\"link\");\n\n  if (link.relList \u0026\u0026\n      typeof link.relList.supports === \"function\" \u0026\u0026\n      link.relList.supports(\"preload\") \u0026\u0026\n      (\"as\" in link)) {\n    window.BOOMR.snippetMethod = \"p\";\n\n    // Set attributes to trigger a Preload\n    link.href = window.BOOMR.url;\n    link.rel  = \"preload\";\n    link.as   = \"script\";\n\n    // Add our script tag if successful, fallback to iframe if not\n    link.addEventListener(\"load\", promote);\n    link.addEventListener(\"error\", function() {\n      iframeLoader(true);\n    });\n\n    // Have a fallback in case Preload does nothing or is slow\n    setTimeout(function() {\n      if (!promoted) {\n        iframeLoader(true);\n      }\n    }, LOADER_TIMEOUT);\n\n    // Note the timestamp we started trying to Preload\n    BOOMR_lstart = new Date().getTime();\n\n    // Append our link tag\n    parentNode.appendChild(link);\n  }\n  else {\n    // No Preload support, use iframe loader\n    iframeLoader(false);\n  }\n\n  // Save when the onload event happened, in case this is a non-NavigationTiming browser\n  function boomerangSaveLoadTime(e) {\n    window.BOOMR_onload = (e \u0026\u0026 e.timeStamp) || new Date().getTime();\n  }\n\n  if (window.addEventListener) {\n    window.addEventListener(\"load\", boomerangSaveLoadTime, false);\n  }\n  else if (window.attachEvent) {\n    window.attachEvent(\"onload\", boomerangSaveLoadTime);\n  }\n})();\n\u003c/script\u003e\n```\n\nMinified:\n\n```javascript\n\u003cscript\u003e(function(){if(window.BOOMR\u0026\u0026(window.BOOMR.version||window.BOOMR.snippetExecuted)){return}window.BOOMR=window.BOOMR||{};window.BOOMR.snippetStart=(new Date).getTime();window.BOOMR.snippetExecuted=true;window.BOOMR.snippetVersion=15;window.BOOMR.url=\"\";var e=document.currentScript||document.getElementsByTagName(\"script\")[0],a=e.parentNode,s=false,t=3e3;function n(){if(s){return}var e=document.createElement(\"script\");e.id=\"boomr-scr-as\";e.src=window.BOOMR.url;e.async=true;a.appendChild(e);s=true}function i(e){s=true;var t,i=document,n,o,d,r=window;window.BOOMR.snippetMethod=e?\"if\":\"i\";n=function(e,t){var n=i.createElement(\"script\");n.id=t||\"boomr-if-as\";n.src=window.BOOMR.url;BOOMR_lstart=(new Date).getTime();e=e||i.body;e.appendChild(n)};if(!window.addEventListener\u0026\u0026window.attachEvent\u0026\u0026navigator.userAgent.match(/MSIE [678]\\./)){window.BOOMR.snippetMethod=\"s\";n(a,\"boomr-async\");return}o=document.createElement(\"IFRAME\");o.src=\"about:blank\";o.title=\"\";o.role=\"presentation\";o.loading=\"eager\";d=(o.frameElement||o).style;d.width=0;d.height=0;d.border=0;d.display=\"none\";a.appendChild(o);try{r=o.contentWindow;i=r.document.open()}catch(e){t=document.domain;o.src=\"javascript:var d=document.open();d.domain='\"+t+\"';void 0;\";r=o.contentWindow;i=r.document.open()}r._boomrl=function(){n()};if(r.addEventListener){r.addEventListener(\"load\",r._boomrl,false)}else if(r.attachEvent){r.attachEvent(\"onload\",r._boomrl)}i.close()}var o=document.createElement(\"link\");if(o.relList\u0026\u0026typeof o.relList.supports===\"function\"\u0026\u0026o.relList.supports(\"preload\")\u0026\u0026\"as\"in o){window.BOOMR.snippetMethod=\"p\";o.href=window.BOOMR.url;o.rel=\"preload\";o.as=\"script\";o.addEventListener(\"load\",n);o.addEventListener(\"error\",function(){i(true)});setTimeout(function(){if(!s){i(true)}},t);BOOMR_lstart=(new Date).getTime();a.appendChild(o)}else{i(false)}function d(e){window.BOOMR_onload=e\u0026\u0026e.timeStamp||(new Date).getTime()}if(window.addEventListener){window.addEventListener(\"load\",d,false)}else if(window.attachEvent){window.attachEvent(\"onload\",d)}})();\u003c/script\u003e\n```\n\nChange the `boomerangUrl` to the location of Boomerang on your server.\n\nThe `id` of the script node created by this code MUST be `boomr-if-as` (for IFRAME mode) or `boomr-scr-as` (for\nPreload mode) as boomerang looks for those ids to determine if it's running within an IFRAME and to determine the\nURL of the script.\n\nboomerang will still export the `BOOMR` object to the parent window if running\ninside an IFRAME, so the rest of your code should remain unchanged.\n\n#### 3.3. Identifying when boomerang has loaded\n\nIf you load boomerang asynchronously, there's some uncertainty in when boomerang\nhas completed loading.  To get around this, you can subscribe to the\n`onBoomerangLoaded` Custom Event on the `document` object:\n\n```javascript\n// Modern browsers\nif (document.addEventListener) {\n  document.addEventListener(\"onBoomerangLoaded\", function(e) {\n    // e.detail.BOOMR is a reference to the BOOMR global object\n  });\n}\n// IE 6, 7, 8 we use onPropertyChange and look for propertyName === \"onBoomerangLoaded\"\nelse if (document.attachEvent) {\n  document.attachEvent(\"onpropertychange\", function(e) {\n    if (!e) e=event;\n    if (e.propertyName === \"onBoomerangLoaded\") {\n      // e.detail.BOOMR is a reference to the BOOMR global object\n    }\n  });\n}\n```\n\nNote that this only works on browsers that support the CustomEvent interface,\nwhich is Chrome (including Android), Firefox 6+ (including Android), Opera\n(including Android, but not Opera Mini), Safari (including iOS), IE 6+\n(but see the code above for the special way to listen for the event on IE6, 7 \u0026 8).\n\nboomerang also fires the `onBeforeBoomerangBeacon` and `onBoomerangBeacon`\nevents just before and during beaconing.\n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n# Installation\n\nThere are several ways of including Boomerang in your project:\n\n1. Boomerang can be downloaded from the official [Boomerang Github repository](https://github.com/akamai/boomerang).\n\n2. NPM: `npm install boomerangjs`\n\n3. Bower: `bower install boomerang`\n\nOnce fetched, see [Building Boomerang](https://akamai.github.io/boomerang/tutorial-building.html)\nfor more details on how to include the plugins you require.\n\n\u003ca name=\"documentation\"\u003e\u003c/a\u003e\n\n# Documentation\n\nDocumentation is in the `docs/` directory.  Boomerang documentation is\nwritten in Markdown and is built via [JSDoc](http://usejsdoc.org/).\n\nYou can build the current documentation by running Grunt:\n\n```bash\ngrunt jsdoc\n```\n\nHTML files will be built under `build/docs`.\n\nOpen-source Boomerang Documentation is currently published at\n[akamai.github.io/boomerang/](https://akamai.github.io/boomerang/).\n\nThe team at Akamai works on mPulse Boomerang, which contains a few mPulse-specific plugins and may have additional\nchanges being tested before being backported to the open-source Boomerang.  mPulse Boomerang usage documentation is\navailable at [docs.soasta.com/boomerang/](https://docs.soasta.com/boomerang/) and mPulse Boomerang API documentation\nis at [developer.akamai.com/tools/boomerang/docs/](https://developer.akamai.com/tools/boomerang/docs/).\n\nAdditional documentation:\n\n* [API Documentation](https://akamai.github.io/boomerang/): The `BOOMR` API\n* [Building Boomerang](https://akamai.github.io/boomerang/tutorial-building.html): How to build boomerang with plugins\n* [Contributing](https://akamai.github.io/boomerang/tutorial-contributing.html): Contributing to the open-source project\n* [Creating Plugins](https://akamai.github.io/boomerang/tutorial-creating-plugins.html): Creating a plugin\n* [Methodology](https://akamai.github.io/boomerang/tutorial-methodology.html): How boomerang works internally\n* [How-Tos](https://akamai.github.io/boomerang/tutorial-howtos.html): Short recipes on how to do a bunch of things with boomerang\n\n# Source code\n\nThe boomerang source code is primarily on GitHub at [github.com/akamai/boomerang](https://github.com/akamai/boomerang).\n\nFeel free to fork it and [contribute](https://akamai.github.io/boomerang/tutorial-contributing.html) to it.\n\nYou can also get a [check out the releases](https://github.com/akamai/boomerang/releases)\nor download a [tarball](https://github.com/akamai/boomerang/archive/master.tar.gz) or\n[zip](http://github.com/akamai/boomerang/archive/master.zip) of the code.\n\n# Support\n\nWe use [GitHub Issues](https://github.com/akamai/boomerang/issues) for discussions,\nfeature requests and bug reports.\n\nGet in touch at [github.com/akamai/boomerang/issues](https://github.com/akamai/boomerang/issues).\n\nboomerang is supported by the developers at [Akamai](http://akamai.com/), and the\nawesome community of open-source developers that use and hack it.  That's you.  Thank you!\n\n# Contributions\n\nBoomerang is brought to you by:\n\n* the former [Exceptional Performance](http://developer.yahoo.com/performance/) team at the company once known as\n    [Yahoo!](http://www.yahoo.com/), aided by the [Yahoo! Developer Network](http://developer.yahoo.com/),\n* the folks at [LogNormal](http://www.lognormal.com/), continued by\n* the mPulse team at [SOASTA](https://www.soasta.com/), ongoing by\n* the mPulse team at [Akamai](https://www.akamai.com/), and\n* many independent contributors whose contributions are cemented in our git history\n\nTo help out, please read our [contributing](https://akamai.github.io/boomerang/tutorial-contributing.html) page.\n\n# Copyright\n\n* _Copyright (c) 2011, Yahoo! Inc.  All rights reserved._\n* _Copyright (c) 2011-2012, Log-Normal Inc.  All rights reserved._\n* _Copyright (c) 2012-2017 SOASTA, Inc. All rights reserved._\n* _Copyright (c) 2017-2023, Akamai Technologies, Inc. All rights reserved._\n* _Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakamai%2Fboomerang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakamai%2Fboomerang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakamai%2Fboomerang/lists"}