├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets ├── canvas-demo.gif ├── logo.svg └── windows-demo.gif ├── bower.json ├── demo.html ├── eslint.config.mjs ├── iframe.html ├── package-lock.json ├── package.json ├── proxy.html ├── sysend.d.ts ├── sysend.js ├── test.ts └── windows.html /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jcubic 2 | ko_fi: jcubic 3 | custom: https://www.paypal.me/jcubic 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | tmp 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | tmp 3 | assets 4 | bower.json 5 | demo.html 6 | iframe.html 7 | windows.html 8 | sysend.php 9 | test.ts 10 | .github 11 | eslint.config.mjs 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jakub T. Jankiewicz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Sysend.js logo 3 |

4 | 5 | [![npm](https://img.shields.io/badge/npm-1.17.5-blue.svg)](https://www.npmjs.com/package/sysend) 6 | ![bower](https://img.shields.io/badge/bower-1.17.5-yellow.svg) 7 | ![downloads](https://img.shields.io/npm/dt/sysend.svg) 8 | [![jsdelivr](https://img.shields.io/jsdelivr/npm/hm/sysend)](https://www.jsdelivr.com/package/npm/sysend) 9 | 10 | # [Web application synchronization between different tabs](https://github.com/jcubic/sysend.js/) 11 | 12 | sysend.js is a small library that allows to send messages between pages that are open in the same 13 | browser. It also supports Cross-Domain communication (Cross-Origin). The library doesn't have any 14 | dependencies and uses the HTML5 LocalStorage API or BroadcastChannel API. If your browser don't 15 | support BroadcastChannel (see [Can I Use](https://caniuse.com/#feat=broadcastchannel)) then you can 16 | send any object that can be serialized to JSON. With BroadcastChannel you can send any object (it 17 | will not be serialized to string but the values are limited to the ones that can be copied by the 18 | [structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#structured-clone)). 19 | You can also send empty notifications. 20 | 21 | Tested on: 22 | 23 | GNU/Linux: in Chromium 34, Firefox 29, Opera 12.16 (64bit)
24 | Windows 10 64bit: in IE11 and Edge 38, Chrome 56, Firefox 51
25 | MacOS X El Captain: Safari 9, Chrome 56, Firefox 51 26 | 27 | ## Note about Safari 7+ and Cross-Domain communication 28 | 29 | All cross-domain communication is disabled by default with Safari 7+. Because of a feature that 30 | blocks 3rd party tracking for iframe, and any iframe used for cross-domain communication runs in 31 | a sandboxed environment. That's why this library like any other solution for cross-domain 32 | communication, don't work on Safari. 33 | 34 | ## Note about Chrome 115+ and different domains 35 | 36 | Since version 115 Google Chrome introduced Third-party storage partitioning. Because of this feature, 37 | Cross-domain communication only works on subdomains. There will probably be a way to share the context 38 | using some kind of permission API, that in the future may also land in Safari (hopefully). More information 39 | about this can be found in [#54](https://github.com/jcubic/sysend.js/issues/54). 40 | Information about the API can also be found in Google Chrome documentation: 41 | [Storage Partitioning](https://developer.chrome.com/docs/privacy-sandbox/storage-partitioning/) 42 | 43 | There is a new API: Storage Access API. It's available when you 44 | [register](https://developer.chrome.com/origintrials/#/trials/active) for the 45 | [Origin Trial](https://developer.chrome.com/origintrials/#/view_trial/577023702256844801). 46 | 47 | You can register two and more domains, you will have a token that you need to add to the HTML files (in the head tag): 48 | 49 | ```html 50 | 51 | ``` 52 | 53 | You can also use the HTTP header: 54 | 55 | ``` 56 | Origin-Trial: 57 | ``` 58 | 59 | Right now, the API only works with localStorage fallback (when inside iframes). 60 | 61 | ## Installation 62 | 63 | Include `sysend.js` file in your html, you can grab the file from npm: 64 | 65 | ``` 66 | npm install sysend 67 | ``` 68 | 69 | or bower 70 | 71 | 72 | ``` 73 | bower install sysend 74 | ``` 75 | 76 | you can also get it from unpkg.com CDN: 77 | 78 | ``` 79 | https://unpkg.com/sysend 80 | ``` 81 | 82 | or jsDelivr: 83 | 84 | ``` 85 | https://cdn.jsdelivr.net/npm/sysend 86 | ``` 87 | 88 | jsDelivr will minify the file. From my testing it's faster than unpkg.com. 89 | 90 | ## Usage 91 | 92 | ```javascript 93 | window.onload = function() { 94 | sysend.on('foo', function(data) { 95 | console.log(data.message); 96 | }); 97 | var input = document.getElementsByTagName('input')[0]; 98 | document.getElementsByTagName('button')[0].onclick = function() { 99 | sysend.broadcast('foo', { message: input.value }); 100 | }; 101 | }; 102 | ``` 103 | 104 | ### Windows/tabs tracking 105 | 106 | Tracking is high level API build on top of `on()` and `broadcast()`, that allows to manage windows/tabs. You can sent message directly to other windows/tabs: 107 | 108 | ```javascript 109 | sysend.track('message', ({data, origin}) => { 110 | console.log(`${origin} send message "${data}"`); 111 | }); 112 | sysend.post('', 'Hello other window/tab'); 113 | ``` 114 | 115 | and listen to events like: 116 | 117 | ```javascript 118 | sysend.track('open', (data) => { 119 | console.log(`${data.id} window/tab just opened`); 120 | }); 121 | ``` 122 | 123 | Other tracking events includes: close/primary/secondary executed when window/tab is closed or become primary or secondary. Track method was added in version 1.6.0. Another required event is `ready` (added in 1.10.0) that should be used when you want to get list of windows/tabs: 124 | 125 | ```javascript 126 | sysend.track('ready', () => { 127 | sysend.list().then(tabs => { 128 | console.log(tabs); 129 | }); 130 | }); 131 | ``` 132 | 133 | with `list()` method and `open`/`close` events you can implement dynamic list of windows/tab. That will change when new window/tab is open or close. 134 | 135 | ```javascript 136 | let list = []; 137 | 138 | sysend.track('open', data => { 139 | if (data.id !== sysend.id) { 140 | list.push(data); 141 | populate_list(list); 142 | } 143 | }); 144 | 145 | sysend.track('close', data => { 146 | list = list.filter(tab => data.id !== tab.id); 147 | populate_list(list); 148 | }); 149 | 150 | sysend.track('ready', () => { 151 | sysend.list().then(tabs => { 152 | list = tabs; 153 | populate_list(list); 154 | }); 155 | }); 156 | 157 | function populate_list() { 158 | select.innerHTML = ''; 159 | list.forEach(tab => { 160 | const option = document.createElement('option'); 161 | option.value = tab.id; 162 | option.innerText = tab.id; 163 | select.appendChild(option); 164 | }); 165 | } 166 | ``` 167 | 168 | In version 1.16.0 this code was abstracted into: 169 | 170 | ```javascript 171 | sysend.track('update', (list) => { 172 | populate_list(list); 173 | }); 174 | ``` 175 | 176 | This can be simplified with point free style: 177 | 178 | ```javascript 179 | sysend.track('update', populate_list); 180 | ``` 181 | 182 | ### RPC mechanism 183 | 184 | In version 1.15.0 new API was added called `rpc()` (build on top of tracking mechanism) that allow to use RPC (Remote Procedure Call) between open windows/tabs. 185 | 186 | ```javascript 187 | const rpc = sysend.rpc({ 188 | get_message() { 189 | return document.querySelector('input').value; 190 | } 191 | }); 192 | 193 | button.addEventListener('click', () => { 194 | rpc.get_message('').then(message => { 195 | console.log(`Message from other tab is "${message}"`); 196 | }).catch(e => { 197 | console.log(`get_message (ERROR) ${e.message}`); 198 | }); 199 | }); 200 | ``` 201 | 202 | ### Cross-Domain communication 203 | 204 | If you want to add support for Cross-Domain communication, you need to call proxy method with url on target domain 205 | that have [proxy.html file](https://github.com/jcubic/sysend.js/blob/master/proxy.html). 206 | 207 | ```javascript 208 | sysend.proxy('https://jcubic.pl'); 209 | sysend.proxy('https://terminal.jcubic.pl'); 210 | ``` 211 | 212 | on Firefox you need to add **CORS** for the proxy.html that will be loaded into iframe (see [Cross-Domain LocalStorage](https://jcubic.wordpress.com/2014/06/20/cross-domain-localstorage/)). 213 | 214 | ### Serialization 215 | 216 | if you want to send custom data you can use serializer (new in 1.4.0) this API 217 | was created for localStorage that needs serialization. 218 | 219 | Example serializer can be [json-dry](https://github.com/11ways/json-dry): 220 | 221 | ```javascript 222 | sysend.serializer(function(data) { 223 | return Dry.stringify(data); 224 | }, function(string) { 225 | return Dry.parse(string); 226 | }); 227 | ```` 228 | 229 | or [JSON5](https://json5.org/): 230 | 231 | ```javascript 232 | sysend.serializer(function(data) { 233 | return JSON5.stringify(string); 234 | }, function(string) { 235 | return JSON5.parse(string); 236 | }); 237 | ```` 238 | 239 | ### Security protection 240 | 241 | Since version 1.10.0 as a security mesure Cross-Domain communication has been limited to only those domains that are allowed. 242 | To allow domain to listen to sysend communication you need to specify channel inside iframe. You need add your origins to the 243 | `sysend.channel()` function (origin is combination of protocol domain and optional port). 244 | 245 | 246 | ## Demos 247 | 248 | * [Simple demo using iframes](https://jcubic.pl/sysend-demo/). 249 | * [All feature demo](http://jcubic.pl/sysend.php) (this one require open in two tabs/windows, there is also link to other domain). 250 | * [ReactJS shopping cart synchronization](https://codepen.io/jcubic/pen/QWgmBmE). 251 | * [multiple window tracking demo](https://jcubic.pl/windows.html). Open the link in multiple windows (not tabs). First window will track position and size for all windows. 252 | * [Multiple windows with interactive Canvas demo](https://codepen.io/jcubic/pen/ZEjXBVg), this demo draws circle on the Canvas that follow the mouse. Open the page in multiple windows (not tabs). The best effect is when the circle is between two windows. 253 | 254 | ![Screen capture of Operating System Windows dragging and moving around animation](https://github.com/jcubic/sysend.js/blob/master/assets/windows-demo.gif?raw=true) 255 | 256 | ![Screen capture of multiple browser windows and interactive circle that follow the mouse](https://github.com/jcubic/sysend.js/blob/master/assets/canvas-demo.gif?raw=true) 257 | 258 | ## API 259 | 260 | sysend object: 261 | 262 | | function | description | arguments | Version | 263 | |---|---|---|---| 264 | | `on(name, callback)` | add event handler for specified name | name - `string` - The name of the event
callback - function `(object, name) => void` | 1.0.0 | 265 | | `off(name [, callback])` | remove event handler for given name, if callback is not specified it will remove all callbacks for given name | name - `string` - The name of the event
callback - optional function `(object, name) => void` | 1.0.0 | 266 | | `broadcast(name [, object])` | send any object and fire all events with specified name (in different pages that register callback using on). You can also just send notification without an object | name - string - The name of the event
object - optional any data | 1.0.0 | 267 | | `proxy()` | create iframe proxy for different domain, the target domain/URL should have [proxy.html](https://github.com/jcubic/sysend.js/blob/master/proxy.html)
file. If url domain is the same as page domain, it's ignored. So you can put both proxy calls on both | url - string | 1.3.0 | 268 | | `serializer(to_string, from_string)` | add serializer and deserializer functions | both arguments are functions (data: any) => string | 1.4.0 | 269 | | `emit(name, [, object])` | same as `broadcast()` but also invoke the even on same page | name - string - The name of the event
object - optional any data | 1.5.0 | 270 | | `post(, [, object])` | send any data to other window | window_id - string of the target window (use `'primary'` to send to primary window)
object - any data | 1.6.0 / `'primary'` target 1.14.0 | 271 | | `list()` | returns a Promise of objects `{id:, primary}` for other windows, you can use those to send a message with `post()` | NA | 1.6.0 | 272 | | `track(event, callback)` | track inter window communication events | event - any of the strings: `"open"`, `"close"`, `"primary"`,
`"secondary"`, `"message"`, `"update"`
callback - different function depend on the event:
* `"message"` - `{data, origin}` - where data is anything the `post()` sends, and origin is `id` of the sender.
* `"open"` - `{count, primary, id}` when new window/tab is opened
* `"close"` - `{count, primary, id, self}` when window/tab is closed
* `"primary"` and `"secondary"` function has no arguments and is called when window/tab become secondary or primary.
* `"ready"` - event when tracking is ready. | 1.6.0 except `ready` - 1.10.0 and `update` - 1.16.0 | 273 | | `untrack(event [,callback])` | remove single event listener all listeners for a given event | event - any of the strings `'open'`, `'close'`, `'primary'`, `'secondary'`, `'message'`, or `'update'`. | 1.6.0 | 274 | | `isPrimary()` | function returns true if window is primary (first open or last that remain) | NA | 1.6.0 | 275 | | `channel()` | function restrict cross domain communication to only allowed domains. You need to call this function on proxy iframe to limit number of domains (origins) that can listen and send events. | any number of origins (e.g. 'http://localhost:8080' or 'https://jcubic.github.io') you can also use valid URL. | 1.10.0 | 276 | | `useLocalStorage([toggle])` | Function set or toggle localStorage mode. | argument is optional and can be `true` or `false`. | 1.14.0 | 277 | | `rpc(object): Promise` | Function create RPC async functions which accept first additional argument that is ID of window/tab that it should sent request to. The other window/tab call the function and return value resolve original promise. | The function accept an object with methods and return a Promise that resolve to object with same methods but async. | 1.15.0 | 278 | 279 | To see details of using the API, see [demo.html source code](https://github.com/jcubic/sysend.js/blob/master/demo.html) or [TypeScript definition file](https://github.com/jcubic/sysend.js/blob/master/sysend.d.ts). 280 | 281 | ## Story 282 | 283 | The story of this library came from my question on StackOverflow from 2014: [Sending notifications between instances of the page in the same browser](https://stackoverflow.com/q/24182409/387194), with hint from user called **Niet the Dark Absol**, I was able to create a PoC of the solution using localStorage. I quickly created a library from my solution. I've also explained how to have [Cross-Domain LocalStorage](https://jcubic.wordpress.com/2014/06/20/cross-domain-localstorage/). The blog post have steady number of visitors (actually it's most viewed post on that blog). 284 | 285 | And the name of the library is just random word "sy" and "send" suffix. But it can be an backronym for **Synchronizing Send** as in synchronizing application between browser tabs. 286 | 287 | ## Articles 288 | * [CSRF Protection Problem and How to Fix it](https://www.freecodecamp.org/news/csrf-protection-problem-and-how-to-fix-it/) 289 | * [Synchronizacja stanu aplikacji www między zakładkami](https://bulldogjob.pl/news/1804-synchronizacja-stanu-aplikacji-www-miedzy-zakladkami) 290 | 291 | ## Press 292 | The library was featured in: 293 | * [Web Tools Weekly](https://webtoolsweekly.com/archives/issue-378/) 294 | * [JavaScript Weekly](https://javascriptweekly.com/issues/581) 295 | * [Impressive Webs](https://www.impressivewebs.com/most-interesting-front-end-developer-tools-2021/) 296 | * [Front-end Architecture](https://frontend-architecture.com/2022/03/30/messaging-between-browser-tabs/) 297 | * [Frontend Planet](https://www.frontendplanet.com/news-for-front-end-developers-13/) 298 | 299 | ## License 300 | 301 | Copyright (C) 2014 [Jakub T. Jankiewicz](https://jcubic.pl/me)
302 | Released under the [MIT license](https://opensource.org/licenses/MIT) 303 | 304 | This is free software; you are free to change and redistribute it.
305 | There is NO WARRANTY, to the extent permitted by law. 306 | -------------------------------------------------------------------------------- /assets/canvas-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcubic/sysend.js/dd8651579a4f9d0e425e41a37ca043b05805b2df/assets/canvas-demo.gif -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 34 | 37 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /assets/windows-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcubic/sysend.js/dd8651579a4f9d0e425e41a37ca043b05805b2df/assets/windows-demo.gif -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sysend", 3 | "description": "Send messages to other tabs/windows in the same origin and browser", 4 | "main": "sysend.js", 5 | "authors": [ 6 | "Jakub T. Jankiewicz (https://jcubic.pl/me/)" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "tabs", 11 | "windows", 12 | "events", 13 | "callback", 14 | "notifications", 15 | "browser" 16 | ], 17 | "homepage": "https://github.com/jcubic/sysend.js", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sysend demo 6 | 7 | 12 | 109 | 110 | 111 |

sysend.js demo

112 |

Open this page in two different tabs

113 | 114 | 115 | 116 |
117 | 118 | 119 |
120 |
121 |

122 |     

Window/tabs tracking

123 |
count: 
124 |

125 |     

Send to window

126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | 4 | 5 | export default [ 6 | { 7 | files: ["**/*.js"], 8 | languageOptions: { 9 | ecmaVersion: 5, 10 | sourceType: "script" 11 | } 12 | }, 13 | { 14 | languageOptions: { 15 | globals: globals.browser 16 | } 17 | }, 18 | { 19 | rules: { 20 | "no-unused-vars": [ 21 | "error", 22 | { 23 | caughtErrors: "none" 24 | } 25 | ] 26 | } 27 | }, 28 | pluginJs.configs.recommended 29 | ]; 30 | -------------------------------------------------------------------------------- /iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 23 | 24 | 25 |

26 | 
27 | 
28 | 


--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
   1 | {
   2 |   "name": "sysend",
   3 |   "version": "1.17.5",
   4 |   "lockfileVersion": 3,
   5 |   "requires": true,
   6 |   "packages": {
   7 |     "": {
   8 |       "name": "sysend",
   9 |       "version": "1.17.5",
  10 |       "license": "MIT",
  11 |       "devDependencies": {
  12 |         "@eslint/js": "^9.7.0",
  13 |         "eslint": "^9.7.0",
  14 |         "globals": "^15.8.0"
  15 |       }
  16 |     },
  17 |     "node_modules/@eslint-community/eslint-utils": {
  18 |       "version": "4.4.0",
  19 |       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
  20 |       "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
  21 |       "dev": true,
  22 |       "dependencies": {
  23 |         "eslint-visitor-keys": "^3.3.0"
  24 |       },
  25 |       "engines": {
  26 |         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
  27 |       },
  28 |       "peerDependencies": {
  29 |         "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
  30 |       }
  31 |     },
  32 |     "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
  33 |       "version": "3.4.3",
  34 |       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
  35 |       "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
  36 |       "dev": true,
  37 |       "engines": {
  38 |         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
  39 |       },
  40 |       "funding": {
  41 |         "url": "https://opencollective.com/eslint"
  42 |       }
  43 |     },
  44 |     "node_modules/@eslint-community/regexpp": {
  45 |       "version": "4.11.0",
  46 |       "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
  47 |       "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
  48 |       "dev": true,
  49 |       "engines": {
  50 |         "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
  51 |       }
  52 |     },
  53 |     "node_modules/@eslint/config-array": {
  54 |       "version": "0.17.0",
  55 |       "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz",
  56 |       "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==",
  57 |       "dev": true,
  58 |       "dependencies": {
  59 |         "@eslint/object-schema": "^2.1.4",
  60 |         "debug": "^4.3.1",
  61 |         "minimatch": "^3.1.2"
  62 |       },
  63 |       "engines": {
  64 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
  65 |       }
  66 |     },
  67 |     "node_modules/@eslint/eslintrc": {
  68 |       "version": "3.1.0",
  69 |       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
  70 |       "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
  71 |       "dev": true,
  72 |       "dependencies": {
  73 |         "ajv": "^6.12.4",
  74 |         "debug": "^4.3.2",
  75 |         "espree": "^10.0.1",
  76 |         "globals": "^14.0.0",
  77 |         "ignore": "^5.2.0",
  78 |         "import-fresh": "^3.2.1",
  79 |         "js-yaml": "^4.1.0",
  80 |         "minimatch": "^3.1.2",
  81 |         "strip-json-comments": "^3.1.1"
  82 |       },
  83 |       "engines": {
  84 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
  85 |       },
  86 |       "funding": {
  87 |         "url": "https://opencollective.com/eslint"
  88 |       }
  89 |     },
  90 |     "node_modules/@eslint/eslintrc/node_modules/globals": {
  91 |       "version": "14.0.0",
  92 |       "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
  93 |       "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
  94 |       "dev": true,
  95 |       "engines": {
  96 |         "node": ">=18"
  97 |       },
  98 |       "funding": {
  99 |         "url": "https://github.com/sponsors/sindresorhus"
 100 |       }
 101 |     },
 102 |     "node_modules/@eslint/js": {
 103 |       "version": "9.7.0",
 104 |       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz",
 105 |       "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==",
 106 |       "dev": true,
 107 |       "engines": {
 108 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 109 |       }
 110 |     },
 111 |     "node_modules/@eslint/object-schema": {
 112 |       "version": "2.1.4",
 113 |       "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
 114 |       "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
 115 |       "dev": true,
 116 |       "engines": {
 117 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 118 |       }
 119 |     },
 120 |     "node_modules/@humanwhocodes/module-importer": {
 121 |       "version": "1.0.1",
 122 |       "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
 123 |       "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
 124 |       "dev": true,
 125 |       "engines": {
 126 |         "node": ">=12.22"
 127 |       },
 128 |       "funding": {
 129 |         "type": "github",
 130 |         "url": "https://github.com/sponsors/nzakas"
 131 |       }
 132 |     },
 133 |     "node_modules/@humanwhocodes/retry": {
 134 |       "version": "0.3.0",
 135 |       "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
 136 |       "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
 137 |       "dev": true,
 138 |       "engines": {
 139 |         "node": ">=18.18"
 140 |       },
 141 |       "funding": {
 142 |         "type": "github",
 143 |         "url": "https://github.com/sponsors/nzakas"
 144 |       }
 145 |     },
 146 |     "node_modules/@nodelib/fs.scandir": {
 147 |       "version": "2.1.5",
 148 |       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 149 |       "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
 150 |       "dev": true,
 151 |       "dependencies": {
 152 |         "@nodelib/fs.stat": "2.0.5",
 153 |         "run-parallel": "^1.1.9"
 154 |       },
 155 |       "engines": {
 156 |         "node": ">= 8"
 157 |       }
 158 |     },
 159 |     "node_modules/@nodelib/fs.stat": {
 160 |       "version": "2.0.5",
 161 |       "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
 162 |       "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
 163 |       "dev": true,
 164 |       "engines": {
 165 |         "node": ">= 8"
 166 |       }
 167 |     },
 168 |     "node_modules/@nodelib/fs.walk": {
 169 |       "version": "1.2.8",
 170 |       "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
 171 |       "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
 172 |       "dev": true,
 173 |       "dependencies": {
 174 |         "@nodelib/fs.scandir": "2.1.5",
 175 |         "fastq": "^1.6.0"
 176 |       },
 177 |       "engines": {
 178 |         "node": ">= 8"
 179 |       }
 180 |     },
 181 |     "node_modules/acorn": {
 182 |       "version": "8.12.1",
 183 |       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
 184 |       "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
 185 |       "dev": true,
 186 |       "bin": {
 187 |         "acorn": "bin/acorn"
 188 |       },
 189 |       "engines": {
 190 |         "node": ">=0.4.0"
 191 |       }
 192 |     },
 193 |     "node_modules/acorn-jsx": {
 194 |       "version": "5.3.2",
 195 |       "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
 196 |       "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
 197 |       "dev": true,
 198 |       "peerDependencies": {
 199 |         "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
 200 |       }
 201 |     },
 202 |     "node_modules/ajv": {
 203 |       "version": "6.12.6",
 204 |       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
 205 |       "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
 206 |       "dev": true,
 207 |       "dependencies": {
 208 |         "fast-deep-equal": "^3.1.1",
 209 |         "fast-json-stable-stringify": "^2.0.0",
 210 |         "json-schema-traverse": "^0.4.1",
 211 |         "uri-js": "^4.2.2"
 212 |       },
 213 |       "funding": {
 214 |         "type": "github",
 215 |         "url": "https://github.com/sponsors/epoberezkin"
 216 |       }
 217 |     },
 218 |     "node_modules/ansi-regex": {
 219 |       "version": "5.0.1",
 220 |       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 221 |       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 222 |       "dev": true,
 223 |       "engines": {
 224 |         "node": ">=8"
 225 |       }
 226 |     },
 227 |     "node_modules/ansi-styles": {
 228 |       "version": "4.3.0",
 229 |       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 230 |       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
 231 |       "dev": true,
 232 |       "dependencies": {
 233 |         "color-convert": "^2.0.1"
 234 |       },
 235 |       "engines": {
 236 |         "node": ">=8"
 237 |       },
 238 |       "funding": {
 239 |         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 240 |       }
 241 |     },
 242 |     "node_modules/argparse": {
 243 |       "version": "2.0.1",
 244 |       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
 245 |       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
 246 |       "dev": true
 247 |     },
 248 |     "node_modules/balanced-match": {
 249 |       "version": "1.0.2",
 250 |       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 251 |       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
 252 |       "dev": true
 253 |     },
 254 |     "node_modules/brace-expansion": {
 255 |       "version": "1.1.11",
 256 |       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
 257 |       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
 258 |       "dev": true,
 259 |       "dependencies": {
 260 |         "balanced-match": "^1.0.0",
 261 |         "concat-map": "0.0.1"
 262 |       }
 263 |     },
 264 |     "node_modules/callsites": {
 265 |       "version": "3.1.0",
 266 |       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 267 |       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
 268 |       "dev": true,
 269 |       "engines": {
 270 |         "node": ">=6"
 271 |       }
 272 |     },
 273 |     "node_modules/chalk": {
 274 |       "version": "4.1.2",
 275 |       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 276 |       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
 277 |       "dev": true,
 278 |       "dependencies": {
 279 |         "ansi-styles": "^4.1.0",
 280 |         "supports-color": "^7.1.0"
 281 |       },
 282 |       "engines": {
 283 |         "node": ">=10"
 284 |       },
 285 |       "funding": {
 286 |         "url": "https://github.com/chalk/chalk?sponsor=1"
 287 |       }
 288 |     },
 289 |     "node_modules/color-convert": {
 290 |       "version": "2.0.1",
 291 |       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 292 |       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
 293 |       "dev": true,
 294 |       "dependencies": {
 295 |         "color-name": "~1.1.4"
 296 |       },
 297 |       "engines": {
 298 |         "node": ">=7.0.0"
 299 |       }
 300 |     },
 301 |     "node_modules/color-name": {
 302 |       "version": "1.1.4",
 303 |       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
 304 |       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
 305 |       "dev": true
 306 |     },
 307 |     "node_modules/concat-map": {
 308 |       "version": "0.0.1",
 309 |       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 310 |       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
 311 |       "dev": true
 312 |     },
 313 |     "node_modules/cross-spawn": {
 314 |       "version": "7.0.3",
 315 |       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
 316 |       "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
 317 |       "dev": true,
 318 |       "dependencies": {
 319 |         "path-key": "^3.1.0",
 320 |         "shebang-command": "^2.0.0",
 321 |         "which": "^2.0.1"
 322 |       },
 323 |       "engines": {
 324 |         "node": ">= 8"
 325 |       }
 326 |     },
 327 |     "node_modules/debug": {
 328 |       "version": "4.3.5",
 329 |       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
 330 |       "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 331 |       "dev": true,
 332 |       "dependencies": {
 333 |         "ms": "2.1.2"
 334 |       },
 335 |       "engines": {
 336 |         "node": ">=6.0"
 337 |       },
 338 |       "peerDependenciesMeta": {
 339 |         "supports-color": {
 340 |           "optional": true
 341 |         }
 342 |       }
 343 |     },
 344 |     "node_modules/deep-is": {
 345 |       "version": "0.1.4",
 346 |       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 347 |       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
 348 |       "dev": true
 349 |     },
 350 |     "node_modules/escape-string-regexp": {
 351 |       "version": "4.0.0",
 352 |       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
 353 |       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
 354 |       "dev": true,
 355 |       "engines": {
 356 |         "node": ">=10"
 357 |       },
 358 |       "funding": {
 359 |         "url": "https://github.com/sponsors/sindresorhus"
 360 |       }
 361 |     },
 362 |     "node_modules/eslint": {
 363 |       "version": "9.7.0",
 364 |       "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz",
 365 |       "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==",
 366 |       "dev": true,
 367 |       "dependencies": {
 368 |         "@eslint-community/eslint-utils": "^4.2.0",
 369 |         "@eslint-community/regexpp": "^4.11.0",
 370 |         "@eslint/config-array": "^0.17.0",
 371 |         "@eslint/eslintrc": "^3.1.0",
 372 |         "@eslint/js": "9.7.0",
 373 |         "@humanwhocodes/module-importer": "^1.0.1",
 374 |         "@humanwhocodes/retry": "^0.3.0",
 375 |         "@nodelib/fs.walk": "^1.2.8",
 376 |         "ajv": "^6.12.4",
 377 |         "chalk": "^4.0.0",
 378 |         "cross-spawn": "^7.0.2",
 379 |         "debug": "^4.3.2",
 380 |         "escape-string-regexp": "^4.0.0",
 381 |         "eslint-scope": "^8.0.2",
 382 |         "eslint-visitor-keys": "^4.0.0",
 383 |         "espree": "^10.1.0",
 384 |         "esquery": "^1.5.0",
 385 |         "esutils": "^2.0.2",
 386 |         "fast-deep-equal": "^3.1.3",
 387 |         "file-entry-cache": "^8.0.0",
 388 |         "find-up": "^5.0.0",
 389 |         "glob-parent": "^6.0.2",
 390 |         "ignore": "^5.2.0",
 391 |         "imurmurhash": "^0.1.4",
 392 |         "is-glob": "^4.0.0",
 393 |         "is-path-inside": "^3.0.3",
 394 |         "json-stable-stringify-without-jsonify": "^1.0.1",
 395 |         "levn": "^0.4.1",
 396 |         "lodash.merge": "^4.6.2",
 397 |         "minimatch": "^3.1.2",
 398 |         "natural-compare": "^1.4.0",
 399 |         "optionator": "^0.9.3",
 400 |         "strip-ansi": "^6.0.1",
 401 |         "text-table": "^0.2.0"
 402 |       },
 403 |       "bin": {
 404 |         "eslint": "bin/eslint.js"
 405 |       },
 406 |       "engines": {
 407 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 408 |       },
 409 |       "funding": {
 410 |         "url": "https://eslint.org/donate"
 411 |       }
 412 |     },
 413 |     "node_modules/eslint-scope": {
 414 |       "version": "8.0.2",
 415 |       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
 416 |       "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
 417 |       "dev": true,
 418 |       "dependencies": {
 419 |         "esrecurse": "^4.3.0",
 420 |         "estraverse": "^5.2.0"
 421 |       },
 422 |       "engines": {
 423 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 424 |       },
 425 |       "funding": {
 426 |         "url": "https://opencollective.com/eslint"
 427 |       }
 428 |     },
 429 |     "node_modules/eslint-visitor-keys": {
 430 |       "version": "4.0.0",
 431 |       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
 432 |       "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
 433 |       "dev": true,
 434 |       "engines": {
 435 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 436 |       },
 437 |       "funding": {
 438 |         "url": "https://opencollective.com/eslint"
 439 |       }
 440 |     },
 441 |     "node_modules/espree": {
 442 |       "version": "10.1.0",
 443 |       "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
 444 |       "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
 445 |       "dev": true,
 446 |       "dependencies": {
 447 |         "acorn": "^8.12.0",
 448 |         "acorn-jsx": "^5.3.2",
 449 |         "eslint-visitor-keys": "^4.0.0"
 450 |       },
 451 |       "engines": {
 452 |         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 453 |       },
 454 |       "funding": {
 455 |         "url": "https://opencollective.com/eslint"
 456 |       }
 457 |     },
 458 |     "node_modules/esquery": {
 459 |       "version": "1.6.0",
 460 |       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
 461 |       "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
 462 |       "dev": true,
 463 |       "dependencies": {
 464 |         "estraverse": "^5.1.0"
 465 |       },
 466 |       "engines": {
 467 |         "node": ">=0.10"
 468 |       }
 469 |     },
 470 |     "node_modules/esrecurse": {
 471 |       "version": "4.3.0",
 472 |       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
 473 |       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
 474 |       "dev": true,
 475 |       "dependencies": {
 476 |         "estraverse": "^5.2.0"
 477 |       },
 478 |       "engines": {
 479 |         "node": ">=4.0"
 480 |       }
 481 |     },
 482 |     "node_modules/estraverse": {
 483 |       "version": "5.3.0",
 484 |       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
 485 |       "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
 486 |       "dev": true,
 487 |       "engines": {
 488 |         "node": ">=4.0"
 489 |       }
 490 |     },
 491 |     "node_modules/esutils": {
 492 |       "version": "2.0.3",
 493 |       "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
 494 |       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
 495 |       "dev": true,
 496 |       "engines": {
 497 |         "node": ">=0.10.0"
 498 |       }
 499 |     },
 500 |     "node_modules/fast-deep-equal": {
 501 |       "version": "3.1.3",
 502 |       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 503 |       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 504 |       "dev": true
 505 |     },
 506 |     "node_modules/fast-json-stable-stringify": {
 507 |       "version": "2.1.0",
 508 |       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
 509 |       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
 510 |       "dev": true
 511 |     },
 512 |     "node_modules/fast-levenshtein": {
 513 |       "version": "2.0.6",
 514 |       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
 515 |       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
 516 |       "dev": true
 517 |     },
 518 |     "node_modules/fastq": {
 519 |       "version": "1.17.1",
 520 |       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
 521 |       "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
 522 |       "dev": true,
 523 |       "dependencies": {
 524 |         "reusify": "^1.0.4"
 525 |       }
 526 |     },
 527 |     "node_modules/file-entry-cache": {
 528 |       "version": "8.0.0",
 529 |       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
 530 |       "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
 531 |       "dev": true,
 532 |       "dependencies": {
 533 |         "flat-cache": "^4.0.0"
 534 |       },
 535 |       "engines": {
 536 |         "node": ">=16.0.0"
 537 |       }
 538 |     },
 539 |     "node_modules/find-up": {
 540 |       "version": "5.0.0",
 541 |       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
 542 |       "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
 543 |       "dev": true,
 544 |       "dependencies": {
 545 |         "locate-path": "^6.0.0",
 546 |         "path-exists": "^4.0.0"
 547 |       },
 548 |       "engines": {
 549 |         "node": ">=10"
 550 |       },
 551 |       "funding": {
 552 |         "url": "https://github.com/sponsors/sindresorhus"
 553 |       }
 554 |     },
 555 |     "node_modules/flat-cache": {
 556 |       "version": "4.0.1",
 557 |       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
 558 |       "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
 559 |       "dev": true,
 560 |       "dependencies": {
 561 |         "flatted": "^3.2.9",
 562 |         "keyv": "^4.5.4"
 563 |       },
 564 |       "engines": {
 565 |         "node": ">=16"
 566 |       }
 567 |     },
 568 |     "node_modules/flatted": {
 569 |       "version": "3.3.1",
 570 |       "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
 571 |       "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
 572 |       "dev": true
 573 |     },
 574 |     "node_modules/glob-parent": {
 575 |       "version": "6.0.2",
 576 |       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
 577 |       "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
 578 |       "dev": true,
 579 |       "dependencies": {
 580 |         "is-glob": "^4.0.3"
 581 |       },
 582 |       "engines": {
 583 |         "node": ">=10.13.0"
 584 |       }
 585 |     },
 586 |     "node_modules/globals": {
 587 |       "version": "15.8.0",
 588 |       "resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz",
 589 |       "integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==",
 590 |       "dev": true,
 591 |       "engines": {
 592 |         "node": ">=18"
 593 |       },
 594 |       "funding": {
 595 |         "url": "https://github.com/sponsors/sindresorhus"
 596 |       }
 597 |     },
 598 |     "node_modules/has-flag": {
 599 |       "version": "4.0.0",
 600 |       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
 601 |       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
 602 |       "dev": true,
 603 |       "engines": {
 604 |         "node": ">=8"
 605 |       }
 606 |     },
 607 |     "node_modules/ignore": {
 608 |       "version": "5.3.1",
 609 |       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
 610 |       "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
 611 |       "dev": true,
 612 |       "engines": {
 613 |         "node": ">= 4"
 614 |       }
 615 |     },
 616 |     "node_modules/import-fresh": {
 617 |       "version": "3.3.0",
 618 |       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 619 |       "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
 620 |       "dev": true,
 621 |       "dependencies": {
 622 |         "parent-module": "^1.0.0",
 623 |         "resolve-from": "^4.0.0"
 624 |       },
 625 |       "engines": {
 626 |         "node": ">=6"
 627 |       },
 628 |       "funding": {
 629 |         "url": "https://github.com/sponsors/sindresorhus"
 630 |       }
 631 |     },
 632 |     "node_modules/imurmurhash": {
 633 |       "version": "0.1.4",
 634 |       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
 635 |       "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
 636 |       "dev": true,
 637 |       "engines": {
 638 |         "node": ">=0.8.19"
 639 |       }
 640 |     },
 641 |     "node_modules/is-extglob": {
 642 |       "version": "2.1.1",
 643 |       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 644 |       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
 645 |       "dev": true,
 646 |       "engines": {
 647 |         "node": ">=0.10.0"
 648 |       }
 649 |     },
 650 |     "node_modules/is-glob": {
 651 |       "version": "4.0.3",
 652 |       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
 653 |       "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
 654 |       "dev": true,
 655 |       "dependencies": {
 656 |         "is-extglob": "^2.1.1"
 657 |       },
 658 |       "engines": {
 659 |         "node": ">=0.10.0"
 660 |       }
 661 |     },
 662 |     "node_modules/is-path-inside": {
 663 |       "version": "3.0.3",
 664 |       "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
 665 |       "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
 666 |       "dev": true,
 667 |       "engines": {
 668 |         "node": ">=8"
 669 |       }
 670 |     },
 671 |     "node_modules/isexe": {
 672 |       "version": "2.0.0",
 673 |       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 674 |       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
 675 |       "dev": true
 676 |     },
 677 |     "node_modules/js-yaml": {
 678 |       "version": "4.1.0",
 679 |       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
 680 |       "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
 681 |       "dev": true,
 682 |       "dependencies": {
 683 |         "argparse": "^2.0.1"
 684 |       },
 685 |       "bin": {
 686 |         "js-yaml": "bin/js-yaml.js"
 687 |       }
 688 |     },
 689 |     "node_modules/json-buffer": {
 690 |       "version": "3.0.1",
 691 |       "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
 692 |       "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
 693 |       "dev": true
 694 |     },
 695 |     "node_modules/json-schema-traverse": {
 696 |       "version": "0.4.1",
 697 |       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
 698 |       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
 699 |       "dev": true
 700 |     },
 701 |     "node_modules/json-stable-stringify-without-jsonify": {
 702 |       "version": "1.0.1",
 703 |       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 704 |       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
 705 |       "dev": true
 706 |     },
 707 |     "node_modules/keyv": {
 708 |       "version": "4.5.4",
 709 |       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
 710 |       "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
 711 |       "dev": true,
 712 |       "dependencies": {
 713 |         "json-buffer": "3.0.1"
 714 |       }
 715 |     },
 716 |     "node_modules/levn": {
 717 |       "version": "0.4.1",
 718 |       "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
 719 |       "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
 720 |       "dev": true,
 721 |       "dependencies": {
 722 |         "prelude-ls": "^1.2.1",
 723 |         "type-check": "~0.4.0"
 724 |       },
 725 |       "engines": {
 726 |         "node": ">= 0.8.0"
 727 |       }
 728 |     },
 729 |     "node_modules/locate-path": {
 730 |       "version": "6.0.0",
 731 |       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
 732 |       "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
 733 |       "dev": true,
 734 |       "dependencies": {
 735 |         "p-locate": "^5.0.0"
 736 |       },
 737 |       "engines": {
 738 |         "node": ">=10"
 739 |       },
 740 |       "funding": {
 741 |         "url": "https://github.com/sponsors/sindresorhus"
 742 |       }
 743 |     },
 744 |     "node_modules/lodash.merge": {
 745 |       "version": "4.6.2",
 746 |       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 747 |       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
 748 |       "dev": true
 749 |     },
 750 |     "node_modules/minimatch": {
 751 |       "version": "3.1.2",
 752 |       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
 753 |       "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
 754 |       "dev": true,
 755 |       "dependencies": {
 756 |         "brace-expansion": "^1.1.7"
 757 |       },
 758 |       "engines": {
 759 |         "node": "*"
 760 |       }
 761 |     },
 762 |     "node_modules/ms": {
 763 |       "version": "2.1.2",
 764 |       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
 765 |       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
 766 |       "dev": true
 767 |     },
 768 |     "node_modules/natural-compare": {
 769 |       "version": "1.4.0",
 770 |       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
 771 |       "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
 772 |       "dev": true
 773 |     },
 774 |     "node_modules/optionator": {
 775 |       "version": "0.9.4",
 776 |       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
 777 |       "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
 778 |       "dev": true,
 779 |       "dependencies": {
 780 |         "deep-is": "^0.1.3",
 781 |         "fast-levenshtein": "^2.0.6",
 782 |         "levn": "^0.4.1",
 783 |         "prelude-ls": "^1.2.1",
 784 |         "type-check": "^0.4.0",
 785 |         "word-wrap": "^1.2.5"
 786 |       },
 787 |       "engines": {
 788 |         "node": ">= 0.8.0"
 789 |       }
 790 |     },
 791 |     "node_modules/p-limit": {
 792 |       "version": "3.1.0",
 793 |       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
 794 |       "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
 795 |       "dev": true,
 796 |       "dependencies": {
 797 |         "yocto-queue": "^0.1.0"
 798 |       },
 799 |       "engines": {
 800 |         "node": ">=10"
 801 |       },
 802 |       "funding": {
 803 |         "url": "https://github.com/sponsors/sindresorhus"
 804 |       }
 805 |     },
 806 |     "node_modules/p-locate": {
 807 |       "version": "5.0.0",
 808 |       "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
 809 |       "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
 810 |       "dev": true,
 811 |       "dependencies": {
 812 |         "p-limit": "^3.0.2"
 813 |       },
 814 |       "engines": {
 815 |         "node": ">=10"
 816 |       },
 817 |       "funding": {
 818 |         "url": "https://github.com/sponsors/sindresorhus"
 819 |       }
 820 |     },
 821 |     "node_modules/parent-module": {
 822 |       "version": "1.0.1",
 823 |       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 824 |       "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
 825 |       "dev": true,
 826 |       "dependencies": {
 827 |         "callsites": "^3.0.0"
 828 |       },
 829 |       "engines": {
 830 |         "node": ">=6"
 831 |       }
 832 |     },
 833 |     "node_modules/path-exists": {
 834 |       "version": "4.0.0",
 835 |       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 836 |       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
 837 |       "dev": true,
 838 |       "engines": {
 839 |         "node": ">=8"
 840 |       }
 841 |     },
 842 |     "node_modules/path-key": {
 843 |       "version": "3.1.1",
 844 |       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
 845 |       "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
 846 |       "dev": true,
 847 |       "engines": {
 848 |         "node": ">=8"
 849 |       }
 850 |     },
 851 |     "node_modules/prelude-ls": {
 852 |       "version": "1.2.1",
 853 |       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
 854 |       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
 855 |       "dev": true,
 856 |       "engines": {
 857 |         "node": ">= 0.8.0"
 858 |       }
 859 |     },
 860 |     "node_modules/punycode": {
 861 |       "version": "2.3.1",
 862 |       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 863 |       "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
 864 |       "dev": true,
 865 |       "engines": {
 866 |         "node": ">=6"
 867 |       }
 868 |     },
 869 |     "node_modules/queue-microtask": {
 870 |       "version": "1.2.3",
 871 |       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
 872 |       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
 873 |       "dev": true,
 874 |       "funding": [
 875 |         {
 876 |           "type": "github",
 877 |           "url": "https://github.com/sponsors/feross"
 878 |         },
 879 |         {
 880 |           "type": "patreon",
 881 |           "url": "https://www.patreon.com/feross"
 882 |         },
 883 |         {
 884 |           "type": "consulting",
 885 |           "url": "https://feross.org/support"
 886 |         }
 887 |       ]
 888 |     },
 889 |     "node_modules/resolve-from": {
 890 |       "version": "4.0.0",
 891 |       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
 892 |       "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
 893 |       "dev": true,
 894 |       "engines": {
 895 |         "node": ">=4"
 896 |       }
 897 |     },
 898 |     "node_modules/reusify": {
 899 |       "version": "1.0.4",
 900 |       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
 901 |       "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
 902 |       "dev": true,
 903 |       "engines": {
 904 |         "iojs": ">=1.0.0",
 905 |         "node": ">=0.10.0"
 906 |       }
 907 |     },
 908 |     "node_modules/run-parallel": {
 909 |       "version": "1.2.0",
 910 |       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
 911 |       "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
 912 |       "dev": true,
 913 |       "funding": [
 914 |         {
 915 |           "type": "github",
 916 |           "url": "https://github.com/sponsors/feross"
 917 |         },
 918 |         {
 919 |           "type": "patreon",
 920 |           "url": "https://www.patreon.com/feross"
 921 |         },
 922 |         {
 923 |           "type": "consulting",
 924 |           "url": "https://feross.org/support"
 925 |         }
 926 |       ],
 927 |       "dependencies": {
 928 |         "queue-microtask": "^1.2.2"
 929 |       }
 930 |     },
 931 |     "node_modules/shebang-command": {
 932 |       "version": "2.0.0",
 933 |       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
 934 |       "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
 935 |       "dev": true,
 936 |       "dependencies": {
 937 |         "shebang-regex": "^3.0.0"
 938 |       },
 939 |       "engines": {
 940 |         "node": ">=8"
 941 |       }
 942 |     },
 943 |     "node_modules/shebang-regex": {
 944 |       "version": "3.0.0",
 945 |       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
 946 |       "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
 947 |       "dev": true,
 948 |       "engines": {
 949 |         "node": ">=8"
 950 |       }
 951 |     },
 952 |     "node_modules/strip-ansi": {
 953 |       "version": "6.0.1",
 954 |       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
 955 |       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
 956 |       "dev": true,
 957 |       "dependencies": {
 958 |         "ansi-regex": "^5.0.1"
 959 |       },
 960 |       "engines": {
 961 |         "node": ">=8"
 962 |       }
 963 |     },
 964 |     "node_modules/strip-json-comments": {
 965 |       "version": "3.1.1",
 966 |       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 967 |       "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
 968 |       "dev": true,
 969 |       "engines": {
 970 |         "node": ">=8"
 971 |       },
 972 |       "funding": {
 973 |         "url": "https://github.com/sponsors/sindresorhus"
 974 |       }
 975 |     },
 976 |     "node_modules/supports-color": {
 977 |       "version": "7.2.0",
 978 |       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 979 |       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
 980 |       "dev": true,
 981 |       "dependencies": {
 982 |         "has-flag": "^4.0.0"
 983 |       },
 984 |       "engines": {
 985 |         "node": ">=8"
 986 |       }
 987 |     },
 988 |     "node_modules/text-table": {
 989 |       "version": "0.2.0",
 990 |       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
 991 |       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
 992 |       "dev": true
 993 |     },
 994 |     "node_modules/type-check": {
 995 |       "version": "0.4.0",
 996 |       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
 997 |       "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
 998 |       "dev": true,
 999 |       "dependencies": {
1000 |         "prelude-ls": "^1.2.1"
1001 |       },
1002 |       "engines": {
1003 |         "node": ">= 0.8.0"
1004 |       }
1005 |     },
1006 |     "node_modules/uri-js": {
1007 |       "version": "4.4.1",
1008 |       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
1009 |       "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
1010 |       "dev": true,
1011 |       "dependencies": {
1012 |         "punycode": "^2.1.0"
1013 |       }
1014 |     },
1015 |     "node_modules/which": {
1016 |       "version": "2.0.2",
1017 |       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1018 |       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1019 |       "dev": true,
1020 |       "dependencies": {
1021 |         "isexe": "^2.0.0"
1022 |       },
1023 |       "bin": {
1024 |         "node-which": "bin/node-which"
1025 |       },
1026 |       "engines": {
1027 |         "node": ">= 8"
1028 |       }
1029 |     },
1030 |     "node_modules/word-wrap": {
1031 |       "version": "1.2.5",
1032 |       "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
1033 |       "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
1034 |       "dev": true,
1035 |       "engines": {
1036 |         "node": ">=0.10.0"
1037 |       }
1038 |     },
1039 |     "node_modules/yocto-queue": {
1040 |       "version": "0.1.0",
1041 |       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
1042 |       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
1043 |       "dev": true,
1044 |       "engines": {
1045 |         "node": ">=10"
1046 |       },
1047 |       "funding": {
1048 |         "url": "https://github.com/sponsors/sindresorhus"
1049 |       }
1050 |     }
1051 |   }
1052 | }
1053 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "sysend",
 3 |   "version": "1.17.5",
 4 |   "description": "Communication and Synchronization between browser tabs/windows. Works cross-domain.",
 5 |   "main": "sysend.js",
 6 |   "typings": "sysend.d.ts",
 7 |   "scripts": {
 8 |     "lint": "eslint"
 9 |   },
10 |   "repository": {
11 |     "type": "git",
12 |     "url": "git+https://github.com/jcubic/sysend.js.git"
13 |   },
14 |   "keywords": [
15 |     "tabs",
16 |     "rpc",
17 |     "windows",
18 |     "messages",
19 |     "cross-domain",
20 |     "cors",
21 |     "domain",
22 |     "communication",
23 |     "events",
24 |     "callback",
25 |     "notifications",
26 |     "browser"
27 |   ],
28 |   "author": "Jakub T. Jankiewicz  (https://jcubic.pl/me/)",
29 |   "license": "MIT",
30 |   "bugs": {
31 |     "url": "https://github.com/jcubic/sysend.js/issues"
32 |   },
33 |   "homepage": "https://github.com/jcubic/sysend.js#readme",
34 |   "devDependencies": {
35 |     "@eslint/js": "^9.7.0",
36 |     "eslint": "^9.7.0",
37 |     "globals": "^15.8.0"
38 |   }
39 | }
40 | 


--------------------------------------------------------------------------------
/proxy.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
12 | 
13 | 
14 | 
15 | 
16 | 


--------------------------------------------------------------------------------
/sysend.d.ts:
--------------------------------------------------------------------------------
 1 | /**@license
 2 |  *  sysend.js - send messages between browser windows/tabs version 1.17.5
 3 |  *
 4 |  *  Copyright (C) 2014 Jakub T. Jankiewicz 
 5 |  *  Released under the MIT license
 6 |  *
 7 |  */
 8 | type callback = (message: T, event: string) => void;
 9 | 
10 | interface Sysend {
11 |     id: string;
12 |     broadcast(event: string, data?: T): void;
13 |     emit(event: string, data?: unknown): void;
14 |     on(event: string, callback: callback): void;
15 |     off(event: string, callback?: callback): void;
16 |     proxy(...args: string[]): void;
17 |     serializer(to: (data: T) => string, from: (data: string) => T): void;
18 |     track(event: 'open', callback: (data: {id: string, count: number, primary: boolean}) => void): void;
19 |     track(event: 'close', callback: (data: {id: string, count: number, primary: boolean, self: boolean}) => void): void;
20 |     track(event: 'primary', callback: () => void): void;
21 |     track(event: 'message', callback: (payload: {data: T, origin: string}) => void): void;
22 |     track(event: 'secondary', callback: () => void): void;
23 |     track(event: 'update', callback: (payload: Array<{ id: string, primary: boolean }>) => void): void;
24 |     untrack(event: 'open' | 'close' | 'primary' | 'secondary' | 'message' | 'update', fn?: (input?: unknown) => void): void;
25 |     list(): Promise>;
26 |     post(target: string, data?: T): void;
27 |     channel(...domains: string[]): void;
28 |     isPrimary(): boolean;
29 |     rpc, U>(object: Record U>): Promise Promise>>
30 | }
31 | 
32 | declare const sysend: Sysend;
33 | 
34 | export default sysend;
35 | 


--------------------------------------------------------------------------------
/sysend.js:
--------------------------------------------------------------------------------
  1 | /**@license
  2 |  *  sysend.js - send messages between browser windows/tabs version 1.17.5
  3 |  *
  4 |  *  Copyright (C) 2014 Jakub T. Jankiewicz 
  5 |  *  Released under the MIT license
  6 |  *
  7 |  *  The idea for localStorage implementation came from this StackOverflow question:
  8 |  *  http://stackoverflow.com/q/24182409/387194
  9 |  *
 10 |  */
 11 | /* global define, module, exports, Symbol, Promise */
 12 | (function (root, factory) {
 13 |     if (typeof define === 'function' && define.amd) {
 14 |         define(['sysend'], factory);
 15 |     } else if (typeof exports === 'object') {
 16 |         module.exports = factory();
 17 |     } else {
 18 |         root.sysend = factory();
 19 |     }
 20 | })(typeof window !== 'undefined' ? window : this, function() {
 21 |     var DEBUG = false;
 22 |     var collecting_timeout = 400;
 23 |     // we use prefix so `foo' event don't collide with `foo' locaStorage value
 24 |     var uniq_prefix = '___sysend___';
 25 |     var prefix_re = new RegExp(uniq_prefix, 'g');
 26 |     var random_value = Math.random();
 27 |     var serializer = {};
 28 |     // object with user events as keys and values arrays of callback functions
 29 |     var callbacks = {};
 30 |     var has_primary;
 31 |     var iframes = [];
 32 |     var index = 0;
 33 |     var proxy_mode = false;
 34 |     var channel;
 35 |     var primary = true;
 36 |     var force_ls = false;
 37 |     // we use id because storage event is not executed if message was not
 38 |     // changed, and we want it if user send same object twice (before it will
 39 |     // be removed)
 40 |     var id = 0;
 41 |     // identifier for making each call to list unique
 42 |     var list_id = 0;
 43 |     // Storage Access API handler
 44 |     var sa_handle;
 45 | 
 46 |     // id of the window/tabAnd two-way communication is tracked in
 47 |     var target_id = generate_uuid();
 48 |     var target_count = 1;
 49 |     var rpc_count = 0;
 50 |     var domains;
 51 | 
 52 |     var handlers = {
 53 |         primary: [],
 54 |         close: [],
 55 |         open: [],
 56 |         secondary: [],
 57 |         message: [],
 58 |         visbility: [],
 59 |         ready: [],
 60 |         update: []
 61 |     };
 62 |     var events = Object.keys(handlers);
 63 |     // -------------------------------------------------------------------------
 64 |     var serialize = make_process(serializer, 'to');
 65 |     var unserialize = make_process(serializer, 'from');
 66 |     // -------------------------------------------------------------------------
 67 |     var sysend = {
 68 |         id: target_id,
 69 |         broadcast: function(event, data) {
 70 |             if (channel && !force_ls) {
 71 |                 log('broadcast', { event: event, data: data });
 72 |                 channel.postMessage({name: event, data: serialize(data)});
 73 |             } else {
 74 |                 set(event, to_json(data));
 75 |                 // clean up localstorage
 76 |                 setTimeout(function() {
 77 |                     remove(event);
 78 |                 }, 0);
 79 |             }
 80 |             send_to_iframes(event, data);
 81 |             return sysend;
 82 |         },
 83 |         emit: function(event, data) {
 84 |             log('emit', { event: event, data: data });
 85 |             sysend.broadcast(event, data);
 86 |             invoke(event, data);
 87 |             return sysend;
 88 |         },
 89 |         serializer: function(to, from) {
 90 |             if (!(is_function(to) && is_function(from))) {
 91 |                 throw new Error('sysend::serializer: Invalid argument, expecting' +
 92 |                                 ' function');
 93 |             }
 94 |             serializer.to = to;
 95 |             serializer.from = from;
 96 |             return sysend;
 97 |         },
 98 |         proxy: function() {
 99 |             var args = Array.prototype.slice.call(arguments);
100 |             args.forEach(function(url) {
101 |                 if (is_string(url) && host(url) !== window.location.host) {
102 |                     domains = domains || [];
103 |                     var orig = origin(url);
104 |                     if (domains.includes(orig)) {
105 |                         var selector = 'iframe[src="' + url + '"]';
106 |                         if (document.querySelector(selector)) {
107 |                             warn('You already called proxy on ' + url +
108 |                                  ' you only need to call this function once');
109 |                             return;
110 |                         }
111 |                     }
112 |                     domains.push(orig);
113 |                     var iframe = document.createElement('iframe');
114 |                     iframe.style.width = iframe.style.height = 0;
115 |                     iframe.style.position = 'absolute';
116 |                     iframe.style.top = iframe.style.left = '-9999px';
117 |                     iframe.style.border = 'none';
118 |                     var proxy_url = url;
119 |                     if (!url.match(/\.html|\.php|\?/)) {
120 |                         proxy_url = url.replace(/\/$/, '') + '/proxy.html';
121 |                     }
122 |                     iframe.addEventListener('error', function handler() {
123 |                         setTimeout(function() {
124 |                             throw new Error('html proxy file not found on "' + url +
125 |                                             '" url');
126 |                         }, 0);
127 |                         iframe.removeEventListener('error', handler);
128 |                     });
129 |                     iframe.addEventListener('load', function handler() {
130 |                         var win;
131 |                         // fix for Safari
132 |                         // https://stackoverflow.com/q/42632188/387194
133 |                         try {
134 |                             win = iframe.contentWindow;
135 |                         } catch(e) {
136 |                             win = iframe.contentWindow;
137 |                         }
138 |                         iframes.push({window: win, node: iframe});
139 |                         iframe.removeEventListener('load', handler);
140 |                     });
141 |                     document.body.appendChild(iframe);
142 |                     iframe.src = proxy_url;
143 |                 }
144 |             });
145 |             if (!arguments.length && is_iframe) {
146 |                 proxy_mode = true;
147 |             }
148 |             return sysend;
149 |         },
150 |         on: function(event, fn) {
151 |             if (!callbacks[event]) {
152 |                 callbacks[event] = [];
153 |             }
154 |             callbacks[event].push(fn);
155 |             return sysend;
156 |         },
157 |         off: function(event, fn, internal) {
158 |             if (callbacks[event]) {
159 |                 if (fn) {
160 |                     for (var i = callbacks[event].length; i--;) {
161 |                         if (callbacks[event][i] == fn) {
162 |                             callbacks[event].splice(i, 1);
163 |                         }
164 |                     }
165 |                 } else if (internal && is_internal(event) || !internal) {
166 |                     callbacks[event] = [];
167 |                 }
168 |             }
169 |             return sysend;
170 |         },
171 |         track: function(event, fn, internal) {
172 |             if (internal) {
173 |                 fn[Symbol.for(uniq_prefix)] = true;
174 |             }
175 |             if (events.includes(event)) {
176 |                 handlers[event].push(fn);
177 |             }
178 |             return sysend;
179 |         },
180 |         untrack: function(event, fn, internal) {
181 |             if (events.includes(event) && handlers[event].length) {
182 |                 if (fn === undefined) {
183 |                     if (internal) {
184 |                         handlers[event] = [];
185 |                     } else {
186 |                         handlers[event] = handlers[event].filter(function(fn) {
187 |                             return !fn[Symbol.for(uniq_prefix)];
188 |                         });
189 |                     }
190 |                 } else {
191 |                     handlers[event] = handlers[event].filter(function(handler) {
192 |                         return handler !== fn;
193 |                     });
194 |                 }
195 |             }
196 |             return sysend;
197 |         },
198 |         post: function(target, data) {
199 |             return sysend.broadcast(make_internal('__message__'), {
200 |                 target: target,
201 |                 data: data,
202 |                 origin: target_id
203 |             });
204 |         },
205 |         list: function() {
206 |             var id = list_id++;
207 |             var marker = { target: target_id, id: id, origin: self.origin };
208 |             var timer = delay(sysend.timeout);
209 |             return new Promise(function(resolve) {
210 |                 var ids = [];
211 |                 function handler(data) {
212 |                     log('__window_ack__', { data: data, marker: marker });
213 |                     if (data.origin.target === target_id && data.origin.id === id) {
214 |                         ids.push({
215 |                             id: data.id,
216 |                             primary: data.primary
217 |                         });
218 |                     }
219 |                 }
220 |                 sysend.on(make_internal('__window_ack__'), handler);
221 |                 sysend.broadcast(make_internal('__window__'), { id: marker });
222 |                 timer().then(function() {
223 |                     log('timeout', { ids: ids });
224 |                     sysend.off(make_internal('__window_ack__'), handler);
225 |                     resolve(ids);
226 |                 });
227 |             });
228 |         },
229 |         channel: function() {
230 |             var args = Array.prototype.slice.call(arguments);
231 |             domains = args.map(origin);
232 |             return sysend;
233 |         },
234 |         isPrimary: function() {
235 |             return primary;
236 |         },
237 |         useLocalStorage: function(toggle) {
238 |             if (typeof toggle === 'boolean') {
239 |                 force_ls = toggle;
240 |             } else {
241 |                 force_ls = true;
242 |             }
243 |         },
244 |         rpc: function(object) {
245 |             var prefix = ++rpc_count;
246 |             var req = "__" + prefix + "_rpc_request__";
247 |             var res = "__" + prefix + "_rpc_response__";
248 |             var request_index = 0;
249 |             var timeout = 1000;
250 |             function request(id, method, args) {
251 |                 args = args || [];
252 |                 var req_id = ++request_index;
253 |                 return new Promise(function(resolve, reject) {
254 |                     sysend.track('message', function handler(message) {
255 |                         var data = message.data;
256 |                         var origin = message.origin;
257 |                         if (data.type === res) {
258 |                             if (origin === id && req_id === data.id) {
259 |                                 if (data.error) {
260 |                                     reject(data.error);
261 |                                 } else {
262 |                                     resolve(data.result);
263 |                                 }
264 |                                 clearTimeout(timer);
265 |                                 sysend.untrack('message', handler);
266 |                             }
267 |                         }
268 |                     }, true);
269 |                     var payload = {
270 |                         method: method,
271 |                         id: req_id,
272 |                         type: req,
273 |                         args: args
274 |                     };
275 |                     sysend.post(id, payload);
276 |                     var timer = setTimeout(function() {
277 |                         reject(new Error('Timeout error'));
278 |                     }, timeout);
279 |                 });
280 |             }
281 | 
282 |             sysend.track('message', function handler(message) {
283 |                 var data = message.data;
284 |                 var origin = message.origin;
285 |                 if (data.type == req) {
286 |                     var type = res;
287 |                     if (Object.hasOwn(object, data.method)) {
288 |                         try {
289 |                             var fn = object[data.method];
290 |                             unpromise(fn.apply(object, data.args), function(result) {
291 |                                 sysend.post(origin, { result: result, id: data.id, type: type });
292 |                             }, function(error) {
293 |                                 sysend.post(origin, { error: error.message, id: data.id, type: type });
294 |                             });
295 |                         } catch(e) {
296 |                             sysend.post(origin, {
297 |                                 error: e.message,
298 |                                 id: id,
299 |                                 type: type
300 |                             });
301 |                         }
302 |                     } else {
303 |                         sysend.post(origin, {
304 |                             error: 'Method not found',
305 |                             id: id,
306 |                             type: type
307 |                         });
308 | 
309 |                     }
310 |                 }
311 |             }, true);
312 |             var error_msg = 'You need to specify the target window/tab';
313 |             return Object.fromEntries(Object.keys(object).map(function(name) {
314 |                 return [name, function(id) {
315 |                     if (!id) {
316 |                         return Promise.reject(new Error(error_msg));
317 |                     }
318 |                     var args = Array.prototype.slice.call(arguments, 1);
319 |                     return request(id, name, args);
320 |                 }];
321 |             }));
322 |         }
323 |     };
324 |     // -------------------------------------------------------------------------
325 |     Object.defineProperty(sysend, 'timeout', {
326 |         enumerable: true,
327 |         get: function() {
328 |             return collecting_timeout;
329 |         },
330 |         set: function(value) {
331 |             if (typeof value === 'number' && !isNaN(value)) {
332 |                 collecting_timeout = value;
333 |             }
334 |         }
335 |     });
336 |     // -------------------------------------------------------------------------
337 |     var host = (function() {
338 |         if (typeof URL !== 'undefined') {
339 |             return function(url) {
340 |                 if (!url) {
341 |                     return url;
342 |                 }
343 |                 url = new URL(url);
344 |                 return url.host;
345 |             };
346 |         }
347 |         var a = document.createElement('a');
348 |         return function(url) {
349 |             if (!url) {
350 |                 return url;
351 |             }
352 |             a.href = url;
353 |             return a.host;
354 |         };
355 |     })();
356 |     // -------------------------------------------------------------------------
357 |     function unpromise(obj, callback, error) {
358 |         if (is_promise(obj)) {
359 |             var ret = obj.then(callback);
360 |             if (!error) {
361 |                 return ret;
362 |             } else {
363 |                 return ret.catch(error);
364 |             }
365 |         } else {
366 |             return callback(obj);
367 |         }
368 |     }
369 |     // -------------------------------------------------------------------------
370 |     function delay(time) {
371 |         return function() {
372 |             return new Promise(function(resolve) {
373 |                 setTimeout(resolve, time);
374 |             });
375 |         };
376 |     }
377 |     // -------------------------------------------------------------------------
378 |     var origin = (function() {
379 |         function tc(f) {
380 |             return function origin(url) {
381 |                 try {
382 |                     return f(url);
383 |                 } catch(e) {
384 |                     return url;
385 |                 }
386 |             };
387 |         }
388 |         if (window.URL) {
389 |             return tc(function origin(url) {
390 |                 return new URL(url).origin;
391 |             });
392 |         }
393 |         var a = document.createElement('a');
394 |         return tc(function origin(url) {
395 |             a.href = url;
396 |             return a.origin;
397 |         });
398 |     })();
399 |     // -------------------------------------------------------------------------
400 |     // :: show only single message of this kind
401 |     // -------------------------------------------------------------------------
402 |     var warn_messages = [];
403 |     function warn(message) {
404 |         if (!warn_messages.includes(message)) {
405 |             warn_messages.push(message);
406 |             if (console && console.warn) {
407 |                 console.warn(message);
408 |             } else {
409 |                 setTimeout(function() {
410 |                     throw new Error(message);
411 |                 }, 0);
412 |             }
413 |         }
414 |     }
415 |     // -------------------------------------------------------------------------
416 |     function log() {
417 |         if (DEBUG) {
418 |             console.log.apply(null, [self.origin].concat(Array.from(arguments)));
419 |         }
420 |     }
421 |     // -------------------------------------------------------------------------
422 |     function iframe_loaded() {
423 |         var iframes = Array.from(document.querySelectorAll('iframe'));
424 |         return Promise.all(iframes.filter(function(iframe) {
425 |             return iframe.src;
426 |         }).map(function(iframe) {
427 |             return new Promise(function(resolve, reject) {
428 |                 iframe.addEventListener('load', resolve, true);
429 |                 iframe.addEventListener('error', reject, true);
430 |             });
431 |         })).then(delay(sysend.timeout));
432 |         // delay is required, something with browser, it's not intialized
433 |         // properly. The number was picked by experimentation
434 |     }
435 |     // -------------------------------------------------------------------------
436 |     function make_internal(name) {
437 |         return uniq_prefix + name;
438 |     }
439 |     // -------------------------------------------------------------------------
440 |     function is_promise(obj) {
441 |         return obj && typeof obj == 'object' && is_function(obj.then);
442 |     }
443 |     // -------------------------------------------------------------------------
444 |     function is_function(o) {
445 |         return typeof o === 'function';
446 |     }
447 |     // -------------------------------------------------------------------------
448 |     function is_string(o) {
449 |         return typeof o === 'string';
450 |     }
451 |     // -------------------------------------------------------------------------
452 |     function is_internal(name) {
453 |         return name.match(prefix_re);
454 |     }
455 |     // -------------------------------------------------------------------------
456 |     // :: valid sysend message
457 |     // -------------------------------------------------------------------------
458 |     function is_sysend_post_message(e) {
459 |         return is_string(e.data) && is_internal(e.data);
460 |     }
461 |     // -------------------------------------------------------------------------
462 |     function is_secured_iframe() {
463 |         return is_proxy_iframe() && window.isSecureContext;
464 |     }
465 |     // -------------------------------------------------------------------------
466 |     function is_valid_origin(origin) {
467 |         if (!domains) {
468 |             warn('Call sysend.channel() on iframe to restrict domains that can '+
469 |                  'use sysend channel');
470 |             return true;
471 |         }
472 |         var valid = domains.includes(origin);
473 |         if (!valid) {
474 |             warn(origin + ' domain is not on the list of allowed '+
475 |                  'domains use sysend.channel() on iframe to allow'+
476 |                  ' access to this domain');
477 |         }
478 |         return valid;
479 |     }
480 |     // -------------------------------------------------------------------------
481 |     // :: ref: https://stackoverflow.com/a/8809472/387194
482 |     // :: license: Public Domain/MIT
483 |     // -------------------------------------------------------------------------
484 |     function generate_uuid() {
485 |         var d = new Date().getTime();
486 |         //Time in microseconds since page-load or 0 if unsupported
487 |         var d2 = (performance && performance.now && (performance.now() * 1000)) || 0;
488 |         return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
489 |             var r = Math.random() * 16;
490 |             if (d > 0) { // Use timestamp until depleted
491 |                 r = (d + r)%16 | 0;
492 |                 d = Math.floor(d/16);
493 |             } else { // Use microseconds since page-load if supported
494 |                 r = (d2 + r)%16 | 0;
495 |                 d2 = Math.floor(d2/16);
496 |             }
497 |             return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
498 |         });
499 |     }
500 |     // -------------------------------------------------------------------------
501 |     function trigger(arr) {
502 |         var args = Array.prototype.slice.call(arguments, 1);
503 |         arr.forEach(function(fn) {
504 |             fn.apply(null, args);
505 |         });
506 |     }
507 |     // -------------------------------------------------------------------------
508 |     function ls() {
509 |         if (sa_handle) {
510 |             return sa_handle.localStorage;
511 |         } else {
512 |             return localStorage;
513 |         }
514 |     }
515 |     // -------------------------------------------------------------------------
516 |     function get(key) {
517 |         log({get: key});
518 |         return ls().getItem(make_internal(key));
519 |     }
520 |     // -------------------------------------------------------------------------
521 |     function set(key, value) {
522 |         // storage event is not fired when value is set first time
523 |         log({set: key, value: value});
524 |         if (id == 0) {
525 |             ls().setItem(make_internal(key), random_value);
526 |         }
527 |         ls().setItem(make_internal(key), value);
528 |     }
529 |     // -------------------------------------------------------------------------
530 |     function remove(key) {
531 |         log({remove: key});
532 |         ls().removeItem(make_internal(key));
533 |     }
534 |     // -------------------------------------------------------------------------
535 |     function make_process(object, prop) {
536 |         var labels = {
537 |             from: 'Unserialize',
538 |             to: 'Serialize'
539 |         };
540 |         var prefix_message = labels[prop] + ' Error: ';
541 |         return function(data) {
542 |             var fn = object[prop];
543 |             try {
544 |                 if (fn) {
545 |                     return fn(data);
546 |                 }
547 |                 return data;
548 |             } catch (e) {
549 |                 warn(prefix_message + e.message);
550 |             }
551 |         };
552 |     }
553 |     // -------------------------------------------------------------------------
554 |     // ref: https://stackoverflow.com/a/326076/387194
555 |     // -------------------------------------------------------------------------
556 |     var is_iframe = (function is_iframe() {
557 |         try {
558 |             return window.self !== window.top;
559 |         } catch (e) {
560 |             return true;
561 |         }
562 |     })();
563 |     // -------------------------------------------------------------------------
564 |     var is_private_mode = (function is_private_mode() {
565 |         try {
566 |             ls().setItem(uniq_prefix, 1);
567 |             ls().removeItem(uniq_prefix);
568 |             return false;
569 |         } catch (e) {
570 |             return true;
571 |         }
572 |     })();
573 |     // -------------------------------------------------------------------------
574 |     function is_proxy_iframe() {
575 |         return is_iframe && proxy_mode;
576 |     }
577 |     // -------------------------------------------------------------------------
578 |     function send_to_iframes(key, data) {
579 |         // propagate events to iframes
580 |         iframes.forEach(function(iframe) {
581 |             var payload = {
582 |               name: uniq_prefix,
583 |               key: key,
584 |               data: data
585 |             };
586 |             if (is_valid_origin(origin(iframe.node.src))) {
587 |                 iframe.window.postMessage(JSON.stringify(payload), '*');
588 |             }
589 |         });
590 |     }
591 |     // -------------------------------------------------------------------------
592 |     function to_json(input) {
593 |         // save random_value in storage to fix issue in IE that storage event
594 |         // is fired on same page where setItem was called
595 |         var obj = [id++, random_value];
596 |         // undefined in array get stringified as null
597 |         if (typeof input != 'undefined') {
598 |             obj.push(input);
599 |         }
600 |         var data = serialize(obj);
601 |         if (data === obj) {
602 |             return JSON.stringify(obj);
603 |         }
604 |         return data;
605 |     }
606 |     // -------------------------------------------------------------------------
607 |     function from_json(json) {
608 |         var result = unserialize(json);
609 |         if (result === json) {
610 |             return JSON.parse(json);
611 |         }
612 |         return result;
613 |     }
614 |     // -------------------------------------------------------------------------
615 |     function invoke(key, data) {
616 |         callbacks[key].forEach(function(fn) {
617 |             fn(data, key);
618 |         });
619 |     }
620 |     // -------------------------------------------------------------------------
621 |     function init_visiblity() {
622 |         // ref: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
623 |         var hidden, visibilityChange;
624 |         if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
625 |             hidden = 'hidden';
626 |             visibilityChange = 'visibilitychange';
627 |         } else if (typeof document.msHidden !== 'undefined') {
628 |             hidden = 'msHidden';
629 |             visibilityChange = 'msvisibilitychange';
630 |         } else if (typeof document.webkitHidden !== 'undefined') {
631 |             hidden = 'webkitHidden';
632 |             visibilityChange = 'webkitvisibilitychange';
633 |         }
634 |         if (is_function(document.addEventListener) && hidden) {
635 |             document.addEventListener(visibilityChange, function() {
636 |                 trigger(handlers.visbility, !document[hidden]);
637 |             }, false);
638 |         }
639 |     }
640 |     // -------------------------------------------------------------------------
641 |     function become_primary() {
642 |         primary = true;
643 |         trigger(handlers.primary);
644 |         sysend.emit(make_internal('__primary__'));
645 |     }
646 |     // -------------------------------------------------------------------------
647 |     function document_ready() {
648 |         return ['interactive', 'complete'].indexOf(document.readyState) !== -1;
649 |     }
650 |     // -------------------------------------------------------------------------
651 |     if (document_ready()) {
652 |         init();
653 |     } else {
654 |         window.addEventListener('load', function() {
655 |             setTimeout(init, 0);
656 |         });
657 |     }
658 |     // -------------------------------------------------------------------------
659 |     function setup_ls() {
660 |         log('setup_ls()');
661 |         // we need to clean up localStorage if broadcast called on unload
662 |         // because setTimeout will never fire, even setTimeout 0
663 |         var re = new RegExp('^' + uniq_prefix);
664 |         var localStorage = ls();
665 |         for(var key in localStorage) {
666 |             if (key.match(re)) {
667 |                 localStorage.removeItem(key);
668 |             }
669 |         }
670 |         window.addEventListener('storage', function(e) {
671 |             // prevent event to be executed on remove in IE
672 |             if (e.key && e.key.match(re) && index++ % 2 === 0) {
673 |                 var key = e.key.replace(re, '');
674 |                 log('__key__', e.key + ' ==> ' + key, {
675 |                     callbacks: callbacks[key],
676 |                     again: callbacks[key.replace(re, '')]
677 |                 });
678 |                 if (callbacks[key]) {
679 |                     var value = e.newValue || get(e.key);
680 |                     if (value && value != random_value) {
681 |                         var obj = from_json(value);
682 |                         // don't call on remove
683 |                         if (obj && obj[1] != random_value) {
684 |                             invoke(key, obj[2]);
685 |                         }
686 |                     }
687 |                 }
688 |             }
689 |         }, false);
690 |     }
691 |     // -------------------------------------------------------------------------
692 |     function setup_update_tracking(init_list) {
693 |         var list = init_list || [];
694 | 
695 |         update();
696 | 
697 |         function update() {
698 |             trigger(handlers.update, list);
699 |         }
700 | 
701 |         sysend.track('open', function(data) {
702 |             if (data.id !== sysend.id) {
703 |                 list.push(data);
704 |                 log({ list: list, action: 'open' });
705 |                 update();
706 |             }
707 |         }, true);
708 | 
709 |         sysend.track('close', function(data) {
710 |             list = list.filter(function(tab) {
711 |                 return data.id !== tab.id;
712 |             });
713 |             log({ list: list, action: 'close' });
714 |             update();
715 |         }, true);
716 | 
717 | 
718 |     }
719 |     // -------------------------------------------------------------------------
720 |     function setup_channel() {
721 |         if (sa_handle) {
722 |             var hasOwnProperty = Object.prototype.hasOwnProperty;
723 |             if (hasOwnProperty.call(sa_handle, 'BroadcastChannel')) {
724 |                 channel = new sa_handle.BroadcastChannel(uniq_prefix);
725 |             }
726 |         } else {
727 |             channel = new window.BroadcastChannel(uniq_prefix);
728 |         }
729 |         if (!channel) {
730 |             return;
731 |         }
732 |         channel.addEventListener('message', function(event) {
733 |             if (event.target.name === uniq_prefix) {
734 |                 log('message', { data: event.data, iframe: is_proxy_iframe() });
735 |                 if (is_proxy_iframe()) {
736 |                     var payload = {
737 |                         name: uniq_prefix,
738 |                         data: event.data,
739 |                         iframe_id: target_id
740 |                     };
741 |                     if (is_valid_origin(origin(document.referrer))) {
742 |                         window.parent.postMessage(JSON.stringify(payload), '*');
743 |                     }
744 |                 } else {
745 |                     var key = event.data && event.data.name;
746 |                     if (callbacks[key]) {
747 |                         invoke(key, unserialize(event.data.data));
748 |                     }
749 |                 }
750 |             }
751 |         });
752 |     }
753 |     // -------------------------------------------------------------------------
754 |     function seutp() {
755 |         setup_channel();
756 | 
757 |         if (!is_private_mode) {
758 |             setup_ls();
759 |         }
760 | 
761 |         if (is_proxy_iframe()) {
762 |             window.addEventListener('message', function(e) {
763 |                 if (is_sysend_post_message(e) && is_valid_origin(e.origin)) {
764 | 
765 |                     try {
766 |                         var payload = JSON.parse(e.data);
767 |                         log('iframe', payload, payload.name === uniq_prefix);
768 |                         if (payload && payload.name === uniq_prefix) {
769 |                             var data = unserialize(payload.data);
770 |                             sysend.broadcast(payload.key, data);
771 |                         }
772 |                     } catch(e) {
773 |                         // ignore wrong JSON, the message don't came from Sysend
774 |                         // even that the string have unix_prefix, this is just in case
775 |                     }
776 |                 }
777 |             });
778 |         } else {
779 |             init_visiblity();
780 | 
781 |             sysend.track('visbility', function(visible) {
782 |                 if (visible && !has_primary) {
783 |                     become_primary();
784 |                 }
785 |             }, true);
786 | 
787 |             sysend.on(make_internal('__primary__'), function() {
788 |                 log('__primary__');
789 |                 has_primary = true;
790 |             });
791 | 
792 |             sysend.on(make_internal('__open__'), function(data) {
793 |                 log('__open__', data);
794 |                 var id = data.id;
795 |                 target_count++;
796 |                 if (primary) {
797 |                     sysend.broadcast(make_internal('__ack__'));
798 |                 }
799 |                 trigger(handlers.open, {
800 |                     count: target_count,
801 |                     primary: data.primary,
802 |                     id: data.id
803 |                 });
804 |                 if (id === target_id) {
805 |                     trigger(handlers.ready);
806 |                 }
807 |             });
808 | 
809 |             sysend.on(make_internal('__ack__'), function() {
810 |                 if (!primary) {
811 |                     trigger(handlers.secondary);
812 |                 }
813 |             });
814 | 
815 |             sysend.on(make_internal('__close__'), function(data) {
816 |                 log('__close__', data);
817 |                 --target_count;
818 |                 var last = target_count === 1;
819 |                 if (data.wasPrimary && !primary) {
820 |                     has_primary = false;
821 |                 }
822 |                 var payload = {
823 |                     id: data.id,
824 |                     count: target_count,
825 |                     primary: data.wasPrimary,
826 |                     self: data.id === target_id
827 |                 };
828 |                 // we need to trigger primary when tab is in different window
829 |                 // and is not be hidden
830 |                 if (last) {
831 |                     become_primary();
832 |                 }
833 |                 trigger(handlers.close, payload);
834 |             });
835 | 
836 |             sysend.on(make_internal('__window__'), function(data) {
837 |                 log('__window__', { data: data })
838 |                 sysend.broadcast(make_internal('__window_ack__'), {
839 |                     id: target_id,
840 |                     origin: data.id,
841 |                     from: self.origin,
842 |                     primary: primary
843 |                 });
844 |             });
845 | 
846 |             sysend.on(make_internal('__message__'), function(data) {
847 |                 log('__message__', data);
848 |                 if (data.target === 'primary' && primary) {
849 |                     trigger(handlers.message, data);
850 |                 } else if (data.target === target_id) {
851 |                     trigger(handlers.message, data);
852 |                 }
853 |             });
854 | 
855 |             addEventListener('beforeunload', function() {
856 |                 log('beforeunload');
857 |                 sysend.emit(make_internal('__close__'), {
858 |                     id: target_id,
859 |                     wasPrimary: primary
860 |                 });
861 |             }, { capture: true });
862 | 
863 |             iframe_loaded().then(function() {
864 |                 setTimeout(function() {
865 |                     sysend.list().then(function(list) {
866 |                         log('sysend.list()', list);
867 |                         target_count = list.length;
868 |                         primary = list.length === 0;
869 |                         var found = list.find(function(item) {
870 |                             return item.primary;
871 |                         });
872 |                         if (found || primary) {
873 |                             has_primary = true;
874 |                         }
875 |                         sysend.emit(make_internal('__open__'), {
876 |                             id: target_id,
877 |                             primary: primary
878 |                         });
879 |                         if (primary) {
880 |                             trigger(handlers.primary);
881 |                         }
882 |                         setup_update_tracking(list);
883 |                     });
884 |                 }, 0);
885 |             });
886 |         }
887 |     }
888 |     // -------------------------------------------------------------------------
889 |     function init() {
890 |         if (is_function(window.BroadcastChannel)) {
891 |             var ssa = document.requestStorageAccess && 'hasUnpartitionedCookieAccess' in document;
892 |             if (is_secured_iframe() && ssa) {
893 |                 document.requestStorageAccess({
894 |                     all: true
895 |                 }).then(function(handle) {
896 |                     sa_handle = handle;
897 |                     log({init: handle});
898 |                     seutp();
899 |                 }).catch(seutp);
900 |             } else {
901 |                 seutp();
902 |             }
903 |             return sysend;
904 |         } else if (is_private_mode) {
905 |             warn('Your browser don\'t support localStorgage. ' +
906 |                  'In Safari this is most of the time because ' +
907 |                  'of "Private Browsing Mode"');
908 |         }
909 |         seutp();
910 |     }
911 |     return sysend;
912 | });
913 | 


--------------------------------------------------------------------------------
/test.ts:
--------------------------------------------------------------------------------
 1 | // this is test file for TypeScript types
 2 | 
 3 | import sysend from '.';
 4 | 
 5 | sysend.on("foo", function(message: string) {
 6 |     console.log(message.toUpperCase());
 7 | });
 8 | 
 9 | sysend.broadcast("foo", "hello");
10 | 
11 | type payload = {
12 |     message: string
13 | };
14 | 
15 | sysend.on("bar", function(data: payload) {
16 |     console.log(data.message.toUpperCase());
17 | });
18 | 
19 | const data: payload = { message: "something" };
20 | 
21 | sysend.broadcast("bar", data);
22 | 
23 | sysend.proxy("https://example.com");
24 | 
25 | sysend.serializer(function(data) {
26 |     return JSON.stringify(data);
27 | }, function(string) {
28 |     return JSON.parse(string);
29 | });
30 | 
31 | 
32 | const rpc = sysend.rpc({
33 |     hello(str: string) {
34 |         return parseInt(str, 10);
35 |     }
36 | });
37 | 
38 | rpc.then(service => {
39 |     service.hello("", "10").then(n => {
40 |         const x: number = n + 10;
41 |         console.log(x);
42 |     });
43 | });
44 | 


--------------------------------------------------------------------------------
/windows.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |     
  5 |     Window Management Demo
  6 |     
  7 |     
 10 |     
 44 |     
 45 |     
 46 | 
 47 | 
 48 |   

 49 |   
50 | 187 | 188 | 203 | 204 | 205 | 206 | --------------------------------------------------------------------------------